From 55e241dc97f5ceee25bdfd562f6f1aec9a09c661 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 9 Aug 2025 19:49:07 -0300 Subject: [PATCH] add error handling --- Cargo.toml | 4 ++-- nssa/Cargo.toml | 1 + nssa/core/src/program.rs | 17 ++++++++--------- nssa/src/error.rs | 16 ++++++++++++++++ nssa/src/lib.rs | 24 ++++++++++++++++++------ nssa/src/state.rs | 38 +++++++++++++++++++++----------------- sequencer_core/src/lib.rs | 7 +++---- 7 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 nssa/src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 4a938b4..ac01a24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ ] [workspace.dependencies] -anyhow = "1.0" +anyhow = "1.0.98" num_cpus = "1.13.1" openssl = { version = "0.10", features = ["vendored"] } openssl-probe = { version = "0.1.2" } @@ -30,7 +30,7 @@ lazy_static = "1.5.0" env_logger = "0.10" log = "0.4" lru = "0.7.8" -thiserror = "1.0" +thiserror = "2.0.12" rs_merkle = "1.4" sha2 = "0.10.8" hex = "0.4.3" diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index ece3262..2bb5db1 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +thiserror = "2.0.12" risc0-zkvm = "2.3.1" nssa-core = { path = "core"} program-methods = { path = "program_methods" } diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 24db838..6ecd4cf 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,4 +1,3 @@ - use crate::account::{Account, AccountWithMetadata}; pub type ProgramId = [u32; 8]; @@ -19,26 +18,26 @@ pub fn validate_constraints( pre_states: &[AccountWithMetadata], post_states: &[Account], executing_program_id: ProgramId, -) -> Result<(), ()> { +) -> bool { // 1. Lengths must match if pre_states.len() != post_states.len() { - return Err(()); + return false; } for (pre, post) in pre_states.iter().zip(post_states) { // 2. Nonce must remain unchanged if pre.account.nonce != post.nonce { - return Err(()); + return false; } // 3. Ownership change only allowed from default accounts if pre.account.program_owner != post.program_owner && pre.account != Account::default() { - return Err(()); + return false; } // 4. Decreasing balance only allowed if owned by executing program if post.balance < pre.account.balance && pre.account.program_owner != executing_program_id { - return Err(()); + return false; } // 5. Data changes only allowed if owned by executing program @@ -46,7 +45,7 @@ pub fn validate_constraints( && (executing_program_id != pre.account.program_owner || executing_program_id != post.program_owner) { - return Err(()); + return false; } } @@ -54,8 +53,8 @@ pub fn validate_constraints( let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum(); let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum(); if total_balance_pre_states != total_balance_post_states { - return Err(()); + return false; } - Ok(()) + true } diff --git a/nssa/src/error.rs b/nssa/src/error.rs new file mode 100644 index 0000000..dd62570 --- /dev/null +++ b/nssa/src/error.rs @@ -0,0 +1,16 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NssaError { + #[error("Invalid input: {0}")] + InvalidInput(String), + + #[error("Operation failed")] + OperationFailed, + + #[error("Risc0 error: {0}")] + ProgramExecutionFailed(String), + + #[error("Program violated execution rules")] + InvalidProgramBehavior, +} diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index a8a3aac..11f2231 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -6,6 +6,7 @@ use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor}; mod address; +pub mod error; pub mod public_transaction; mod signature; mod state; @@ -18,6 +19,8 @@ pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; +use crate::error::NssaError; + pub const AUTHENTICATED_TRANSFER_PROGRAM: Program = Program { id: AUTHENTICATED_TRANSFER_ID, elf: AUTHENTICATED_TRANSFER_ELF, @@ -28,10 +31,14 @@ fn write_inputs( pre_states: &[AccountWithMetadata], instruction_data: u128, env_builder: &mut ExecutorEnvBuilder, -) -> Result<(), ()> { +) -> Result<(), NssaError> { let pre_states = pre_states.to_vec(); - env_builder.write(&pre_states).map_err(|_| ())?; - env_builder.write(&instruction_data).map_err(|_| ())?; + env_builder + .write(&pre_states) + .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; + env_builder + .write(&instruction_data) + .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; Ok(()) } @@ -39,7 +46,7 @@ fn execute_public( pre_states: &[AccountWithMetadata], instruction_data: u128, program: &Program, -) -> Result, ()> { +) -> Result, NssaError> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); write_inputs(pre_states, instruction_data, &mut env_builder)?; @@ -47,10 +54,15 @@ fn execute_public( // Execute the program (without proving) let executor = default_executor(); - let session_info = executor.execute(env, program.elf).map_err(|_| ())?; + let session_info = executor + .execute(env, program.elf) + .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; // Get outputs - let mut post_states: Vec = session_info.journal.decode().map_err(|_| ())?; + let mut post_states: Vec = session_info + .journal + .decode() + .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; // Claim any output account with default program owner field for account in post_states.iter_mut() { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 66328b1..6459a30 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,5 +1,5 @@ use crate::{ - AUTHENTICATED_TRANSFER_PROGRAM, address::Address, execute_public, + AUTHENTICATED_TRANSFER_PROGRAM, address::Address, error::NssaError, execute_public, public_transaction::PublicTransaction, }; use nssa_core::{ @@ -40,10 +40,11 @@ impl V01State { } } - pub fn transition_from_public_transaction(&mut self, tx: &PublicTransaction) -> Result<(), ()> { - let state_diff = self - .execute_and_verify_public_transaction(tx) - .map_err(|_| ())?; + pub fn transition_from_public_transaction( + &mut self, + tx: &PublicTransaction, + ) -> Result<(), NssaError> { + let state_diff = self.execute_and_verify_public_transaction(tx)?; for (address, post) in state_diff.into_iter() { let current_account = self.get_account_by_address_mut(address); @@ -71,17 +72,21 @@ impl V01State { fn execute_and_verify_public_transaction( &mut self, tx: &PublicTransaction, - ) -> Result, ()> { + ) -> Result, NssaError> { let message = tx.message(); let witness_set = tx.witness_set(); // All addresses must be different if message.addresses.iter().collect::>().len() != message.addresses.len() { - return Err(()); + return Err(NssaError::InvalidInput( + "Duplicate addresses found in message".into(), + )); } if message.nonces.len() != witness_set.signatures_and_public_keys.len() { - return Err(()); + return Err(NssaError::InvalidInput( + "Mismatch between number of nonces and signatures/public keys".into(), + )); } let mut authorized_addresses = Vec::new(); @@ -92,14 +97,16 @@ impl V01State { { // Check the signature is valid if !signature.is_valid_for(message, public_key) { - return Err(()); + return Err(NssaError::InvalidInput( + "Invalid signature for given message and public key".into(), + )); } // Check the nonce corresponds to the current nonce on the public state. let address = Address::from_public_key(public_key); let current_nonce = self.get_account_by_address(&address).nonce; if current_nonce != *nonce { - return Err(()); + return Err(NssaError::InvalidInput("Nonce mismatch".into())); } authorized_addresses.push(address); @@ -118,19 +125,16 @@ impl V01State { // Check the `program_id` corresponds to a built-in program // Only allowed program so far is the authenticated transfer program let Some(program) = self.builtin_programs.get(&message.program_id) else { - return Err(()); + return Err(NssaError::InvalidInput("Unknown program".into())); }; // // Execute program - let post_states = - execute_public(&pre_states, message.instruction_data, program).map_err(|_| ())?; + let post_states = execute_public(&pre_states, message.instruction_data, program)?; // Verify execution corresponds to a well-behaved program. // See the # Programs section for the definition of the `validate_constraints` method. - validate_constraints(&pre_states, &post_states, message.program_id).map_err(|_| ())?; - - if post_states.len() != message.addresses.len() { - return Err(()); + if !validate_constraints(&pre_states, &post_states, message.program_id) { + return Err(NssaError::InvalidProgramBehavior); } Ok(message.addresses.iter().cloned().zip(post_states).collect()) diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 2e34ff7..aec2703 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -94,7 +94,7 @@ impl SequencerCore { fn execute_check_transaction_on_state( &mut self, tx: nssa::PublicTransaction, - ) -> Result { + ) -> Result { self.store.state.transition_from_public_transaction(&tx)?; Ok(tx) @@ -405,7 +405,7 @@ mod tests { // Signature is not from sender. Execution fails let result = sequencer.execute_check_transaction_on_state(tx); - assert!(matches!(result, Err(()))); + assert!(matches!(result, Err(nssa::error::NssaError::ProgramExecutionFailed(_)))); } #[test] @@ -438,8 +438,7 @@ mod tests { let result = sequencer.execute_check_transaction_on_state(result.unwrap()); let is_failed_at_balance_mismatch = matches!( result.err().unwrap(), - // TransactionMalformationErrorKind::BalanceMismatch { tx: _ } - () + nssa::error::NssaError::ProgramExecutionFailed(_) ); assert!(is_failed_at_balance_mismatch);