mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-08 00:03:09 +00:00
add error handling
This commit is contained in:
parent
04f6474799
commit
55e241dc97
@ -17,7 +17,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0.98"
|
||||||
num_cpus = "1.13.1"
|
num_cpus = "1.13.1"
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
openssl-probe = { version = "0.1.2" }
|
openssl-probe = { version = "0.1.2" }
|
||||||
@ -30,7 +30,7 @@ lazy_static = "1.5.0"
|
|||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru = "0.7.8"
|
lru = "0.7.8"
|
||||||
thiserror = "1.0"
|
thiserror = "2.0.12"
|
||||||
rs_merkle = "1.4"
|
rs_merkle = "1.4"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
|||||||
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "2.0.12"
|
||||||
risc0-zkvm = "2.3.1"
|
risc0-zkvm = "2.3.1"
|
||||||
nssa-core = { path = "core"}
|
nssa-core = { path = "core"}
|
||||||
program-methods = { path = "program_methods" }
|
program-methods = { path = "program_methods" }
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
use crate::account::{Account, AccountWithMetadata};
|
use crate::account::{Account, AccountWithMetadata};
|
||||||
|
|
||||||
pub type ProgramId = [u32; 8];
|
pub type ProgramId = [u32; 8];
|
||||||
@ -19,26 +18,26 @@ pub fn validate_constraints(
|
|||||||
pre_states: &[AccountWithMetadata],
|
pre_states: &[AccountWithMetadata],
|
||||||
post_states: &[Account],
|
post_states: &[Account],
|
||||||
executing_program_id: ProgramId,
|
executing_program_id: ProgramId,
|
||||||
) -> Result<(), ()> {
|
) -> bool {
|
||||||
// 1. Lengths must match
|
// 1. Lengths must match
|
||||||
if pre_states.len() != post_states.len() {
|
if pre_states.len() != post_states.len() {
|
||||||
return Err(());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (pre, post) in pre_states.iter().zip(post_states) {
|
for (pre, post) in pre_states.iter().zip(post_states) {
|
||||||
// 2. Nonce must remain unchanged
|
// 2. Nonce must remain unchanged
|
||||||
if pre.account.nonce != post.nonce {
|
if pre.account.nonce != post.nonce {
|
||||||
return Err(());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Ownership change only allowed from default accounts
|
// 3. Ownership change only allowed from default accounts
|
||||||
if pre.account.program_owner != post.program_owner && pre.account != Account::default() {
|
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
|
// 4. Decreasing balance only allowed if owned by executing program
|
||||||
if post.balance < pre.account.balance && pre.account.program_owner != executing_program_id {
|
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
|
// 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 != pre.account.program_owner
|
||||||
|| executing_program_id != post.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_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();
|
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
|
||||||
if total_balance_pre_states != total_balance_post_states {
|
if total_balance_pre_states != total_balance_post_states {
|
||||||
return Err(());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
16
nssa/src/error.rs
Normal file
16
nssa/src/error.rs
Normal 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,
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
|
|||||||
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor};
|
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor};
|
||||||
|
|
||||||
mod address;
|
mod address;
|
||||||
|
pub mod error;
|
||||||
pub mod public_transaction;
|
pub mod public_transaction;
|
||||||
mod signature;
|
mod signature;
|
||||||
mod state;
|
mod state;
|
||||||
@ -18,6 +19,8 @@ pub use signature::PublicKey;
|
|||||||
pub use signature::Signature;
|
pub use signature::Signature;
|
||||||
pub use state::V01State;
|
pub use state::V01State;
|
||||||
|
|
||||||
|
use crate::error::NssaError;
|
||||||
|
|
||||||
pub const AUTHENTICATED_TRANSFER_PROGRAM: Program = Program {
|
pub const AUTHENTICATED_TRANSFER_PROGRAM: Program = Program {
|
||||||
id: AUTHENTICATED_TRANSFER_ID,
|
id: AUTHENTICATED_TRANSFER_ID,
|
||||||
elf: AUTHENTICATED_TRANSFER_ELF,
|
elf: AUTHENTICATED_TRANSFER_ELF,
|
||||||
@ -28,10 +31,14 @@ fn write_inputs(
|
|||||||
pre_states: &[AccountWithMetadata],
|
pre_states: &[AccountWithMetadata],
|
||||||
instruction_data: u128,
|
instruction_data: u128,
|
||||||
env_builder: &mut ExecutorEnvBuilder,
|
env_builder: &mut ExecutorEnvBuilder,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), NssaError> {
|
||||||
let pre_states = pre_states.to_vec();
|
let pre_states = pre_states.to_vec();
|
||||||
env_builder.write(&pre_states).map_err(|_| ())?;
|
env_builder
|
||||||
env_builder.write(&instruction_data).map_err(|_| ())?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +46,7 @@ fn execute_public(
|
|||||||
pre_states: &[AccountWithMetadata],
|
pre_states: &[AccountWithMetadata],
|
||||||
instruction_data: u128,
|
instruction_data: u128,
|
||||||
program: &Program,
|
program: &Program,
|
||||||
) -> Result<Vec<Account>, ()> {
|
) -> Result<Vec<Account>, NssaError> {
|
||||||
// Write inputs to the program
|
// Write inputs to the program
|
||||||
let mut env_builder = ExecutorEnv::builder();
|
let mut env_builder = ExecutorEnv::builder();
|
||||||
write_inputs(pre_states, instruction_data, &mut env_builder)?;
|
write_inputs(pre_states, instruction_data, &mut env_builder)?;
|
||||||
@ -47,10 +54,15 @@ fn execute_public(
|
|||||||
|
|
||||||
// Execute the program (without proving)
|
// Execute the program (without proving)
|
||||||
let executor = default_executor();
|
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
|
// 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
|
// Claim any output account with default program owner field
|
||||||
for account in post_states.iter_mut() {
|
for account in post_states.iter_mut() {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AUTHENTICATED_TRANSFER_PROGRAM, address::Address, execute_public,
|
AUTHENTICATED_TRANSFER_PROGRAM, address::Address, error::NssaError, execute_public,
|
||||||
public_transaction::PublicTransaction,
|
public_transaction::PublicTransaction,
|
||||||
};
|
};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
@ -40,10 +40,11 @@ impl V01State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transition_from_public_transaction(&mut self, tx: &PublicTransaction) -> Result<(), ()> {
|
pub fn transition_from_public_transaction(
|
||||||
let state_diff = self
|
&mut self,
|
||||||
.execute_and_verify_public_transaction(tx)
|
tx: &PublicTransaction,
|
||||||
.map_err(|_| ())?;
|
) -> Result<(), NssaError> {
|
||||||
|
let state_diff = self.execute_and_verify_public_transaction(tx)?;
|
||||||
|
|
||||||
for (address, post) in state_diff.into_iter() {
|
for (address, post) in state_diff.into_iter() {
|
||||||
let current_account = self.get_account_by_address_mut(address);
|
let current_account = self.get_account_by_address_mut(address);
|
||||||
@ -71,17 +72,21 @@ impl V01State {
|
|||||||
fn execute_and_verify_public_transaction(
|
fn execute_and_verify_public_transaction(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &PublicTransaction,
|
tx: &PublicTransaction,
|
||||||
) -> Result<HashMap<Address, Account>, ()> {
|
) -> Result<HashMap<Address, Account>, NssaError> {
|
||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
let witness_set = tx.witness_set();
|
let witness_set = tx.witness_set();
|
||||||
|
|
||||||
// All addresses must be different
|
// All addresses must be different
|
||||||
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
|
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() {
|
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();
|
let mut authorized_addresses = Vec::new();
|
||||||
@ -92,14 +97,16 @@ impl V01State {
|
|||||||
{
|
{
|
||||||
// Check the signature is valid
|
// Check the signature is valid
|
||||||
if !signature.is_valid_for(message, public_key) {
|
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.
|
// Check the nonce corresponds to the current nonce on the public state.
|
||||||
let address = Address::from_public_key(public_key);
|
let address = Address::from_public_key(public_key);
|
||||||
let current_nonce = self.get_account_by_address(&address).nonce;
|
let current_nonce = self.get_account_by_address(&address).nonce;
|
||||||
if current_nonce != *nonce {
|
if current_nonce != *nonce {
|
||||||
return Err(());
|
return Err(NssaError::InvalidInput("Nonce mismatch".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
authorized_addresses.push(address);
|
authorized_addresses.push(address);
|
||||||
@ -118,19 +125,16 @@ impl V01State {
|
|||||||
// Check the `program_id` corresponds to a built-in program
|
// Check the `program_id` corresponds to a built-in program
|
||||||
// Only allowed program so far is the authenticated transfer program
|
// Only allowed program so far is the authenticated transfer program
|
||||||
let Some(program) = self.builtin_programs.get(&message.program_id) else {
|
let Some(program) = self.builtin_programs.get(&message.program_id) else {
|
||||||
return Err(());
|
return Err(NssaError::InvalidInput("Unknown program".into()));
|
||||||
};
|
};
|
||||||
|
|
||||||
// // Execute program
|
// // Execute program
|
||||||
let post_states =
|
let post_states = execute_public(&pre_states, message.instruction_data, program)?;
|
||||||
execute_public(&pre_states, message.instruction_data, program).map_err(|_| ())?;
|
|
||||||
|
|
||||||
// Verify execution corresponds to a well-behaved program.
|
// Verify execution corresponds to a well-behaved program.
|
||||||
// See the # Programs section for the definition of the `validate_constraints` method.
|
// See the # Programs section for the definition of the `validate_constraints` method.
|
||||||
validate_constraints(&pre_states, &post_states, message.program_id).map_err(|_| ())?;
|
if !validate_constraints(&pre_states, &post_states, message.program_id) {
|
||||||
|
return Err(NssaError::InvalidProgramBehavior);
|
||||||
if post_states.len() != message.addresses.len() {
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(message.addresses.iter().cloned().zip(post_states).collect())
|
Ok(message.addresses.iter().cloned().zip(post_states).collect())
|
||||||
|
|||||||
@ -94,7 +94,7 @@ impl SequencerCore {
|
|||||||
fn execute_check_transaction_on_state(
|
fn execute_check_transaction_on_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: nssa::PublicTransaction,
|
tx: nssa::PublicTransaction,
|
||||||
) -> Result<nssa::PublicTransaction, ()> {
|
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> {
|
||||||
self.store.state.transition_from_public_transaction(&tx)?;
|
self.store.state.transition_from_public_transaction(&tx)?;
|
||||||
|
|
||||||
Ok(tx)
|
Ok(tx)
|
||||||
@ -405,7 +405,7 @@ mod tests {
|
|||||||
// Signature is not from sender. Execution fails
|
// Signature is not from sender. Execution fails
|
||||||
let result = sequencer.execute_check_transaction_on_state(tx);
|
let result = sequencer.execute_check_transaction_on_state(tx);
|
||||||
|
|
||||||
assert!(matches!(result, Err(())));
|
assert!(matches!(result, Err(nssa::error::NssaError::ProgramExecutionFailed(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -438,8 +438,7 @@ mod tests {
|
|||||||
let result = sequencer.execute_check_transaction_on_state(result.unwrap());
|
let result = sequencer.execute_check_transaction_on_state(result.unwrap());
|
||||||
let is_failed_at_balance_mismatch = matches!(
|
let is_failed_at_balance_mismatch = matches!(
|
||||||
result.err().unwrap(),
|
result.err().unwrap(),
|
||||||
// TransactionMalformationErrorKind::BalanceMismatch { tx: _ }
|
nssa::error::NssaError::ProgramExecutionFailed(_)
|
||||||
()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(is_failed_at_balance_mismatch);
|
assert!(is_failed_at_balance_mismatch);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user