add error handling

This commit is contained in:
Sergio Chouhy 2025-08-09 19:49:07 -03:00
parent 04f6474799
commit 55e241dc97
7 changed files with 69 additions and 38 deletions

View File

@ -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"

View File

@ -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" }

View File

@ -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
}

16
nssa/src/error.rs Normal file
View File

@ -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,
}

View File

@ -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<Vec<Account>, ()> {
) -> Result<Vec<Account>, 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<Account> = session_info.journal.decode().map_err(|_| ())?;
let mut post_states: Vec<Account> = 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() {

View File

@ -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<HashMap<Address, Account>, ()> {
) -> Result<HashMap<Address, Account>, NssaError> {
let message = tx.message();
let witness_set = tx.witness_set();
// All addresses must be different
if message.addresses.iter().collect::<HashSet<_>>().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())

View File

@ -94,7 +94,7 @@ impl SequencerCore {
fn execute_check_transaction_on_state(
&mut self,
tx: nssa::PublicTransaction,
) -> Result<nssa::PublicTransaction, ()> {
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> {
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);