add validity range to program output

This commit is contained in:
Sergio Chouhy 2026-03-19 12:10:02 -03:00
parent be4f6c0c78
commit 895dd942cf
27 changed files with 78 additions and 50 deletions

1
Cargo.lock generated
View File

@ -8971,6 +8971,7 @@ dependencies = [
"nssa",
"nssa_core",
"optfield",
"rand 0.8.5",
"serde",
"serde_json",
"sha2",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,7 @@ use log::warn;
use nssa::{AccountId, V02State};
use serde::{Deserialize, Serialize};
use crate::HashType;
use crate::{block::BlockId, HashType};
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum NSSATransaction {
@ -56,10 +56,11 @@ impl NSSATransaction {
pub fn execute_check_on_state(
self,
state: &mut V02State,
block_id: BlockId
) -> Result<Self, nssa::error::NssaError> {
match &self {
Self::Public(tx) => state.transition_from_public_transaction(tx),
Self::PrivacyPreserving(tx) => state.transition_from_privacy_preserving_transaction(tx),
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id),
Self::PrivacyPreserving(tx) => state.transition_from_privacy_preserving_transaction(tx, block_id),
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
}
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;

View File

@ -120,7 +120,7 @@ impl IndexerStore {
transaction
.clone()
.transaction_stateless_check()?
.execute_check_on_state(&mut state_guard)?;
.execute_check_on_state(&mut state_guard, block.header.block_id)?;
}
}

View File

@ -151,6 +151,9 @@ impl AccountPostState {
}
}
pub type BlockId = u64;
pub type ValidityRange = (Option<BlockId> , Option<BlockId>);
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
pub struct ProgramOutput {
@ -160,8 +163,24 @@ pub struct ProgramOutput {
pub pre_states: Vec<AccountWithMetadata>,
pub post_states: Vec<AccountPostState>,
pub chained_calls: Vec<ChainedCall>,
pub validity_range: ValidityRange,
}
impl ProgramOutput {
#[must_use]
pub const fn valid_from_id(mut self, id: BlockId) -> Self {
self.validity_range.0 = Some(id);
self
}
#[must_use]
pub const fn valid_until_id(mut self, id: BlockId) -> Self {
self.validity_range.1 = Some(id);
self
}
}
/// Representation of a number as `lo + hi * 2^128`.
#[derive(PartialEq, Eq)]
struct WrappedBalanceSum {
@ -229,6 +248,7 @@ pub fn write_nssa_outputs(
pre_states,
post_states,
chained_calls: Vec::new(),
validity_range: (None, None)
};
env::commit(&output);
}
@ -244,6 +264,7 @@ pub fn write_nssa_outputs_with_chained_call(
pre_states,
post_states,
chained_calls,
validity_range: (None, None)
};
env::commit(&output);
}

View File

@ -2,9 +2,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
account::{Account, AccountId, Nonce},
program::ProgramId,
account::{Account, AccountId, Nonce}, program::{BlockId, ProgramId}, Commitment, CommitmentSetDigest, MembershipProof, Nullifier, DUMMY_COMMITMENT
};
use crate::{
@ -157,6 +155,7 @@ impl V02State {
pub fn transition_from_public_transaction(
&mut self,
tx: &PublicTransaction,
_block_id: BlockId,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
@ -181,6 +180,7 @@ impl V02State {
pub fn transition_from_privacy_preserving_transaction(
&mut self,
tx: &PrivacyPreservingTransaction,
_block_id: BlockId,
) -> Result<(), NssaError> {
// 1. Verify the transaction satisfies acceptance criteria
let public_state_diff = tx.validate_and_produce_public_state_diff(self)?;
@ -567,7 +567,7 @@ pub mod tests {
let balance_to_move = 5;
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 95);
assert_eq!(state.get_account_by_id(to).balance, 5);
@ -588,7 +588,7 @@ pub mod tests {
assert!(state.get_account_by_id(from).balance < balance_to_move);
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(state.get_account_by_id(from).balance, 100);
@ -612,7 +612,7 @@ pub mod tests {
let balance_to_move = 8;
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 192);
assert_eq!(state.get_account_by_id(to).balance, 108);
@ -632,10 +632,10 @@ pub mod tests {
let balance_to_move = 5;
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let balance_to_move = 3;
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
@ -657,7 +657,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -674,7 +674,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -691,7 +691,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -715,7 +715,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -739,7 +739,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -763,7 +763,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -787,7 +787,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -815,7 +815,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -840,7 +840,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -858,7 +858,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -887,7 +887,7 @@ pub mod tests {
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -1080,7 +1080,7 @@ pub mod tests {
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx)
.transition_from_privacy_preserving_transaction(&tx, 1)
.unwrap();
let sender_post = state.get_account_by_id(sender_keys.account_id());
@ -1150,7 +1150,7 @@ pub mod tests {
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.transition_from_privacy_preserving_transaction(&tx, 1)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
@ -1214,7 +1214,7 @@ pub mod tests {
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.transition_from_privacy_preserving_transaction(&tx, 1)
.unwrap();
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
@ -2142,7 +2142,7 @@ pub mod tests {
);
state
.transition_from_privacy_preserving_transaction(&tx)
.transition_from_privacy_preserving_transaction(&tx, 1)
.unwrap();
let sender_private_account = Account {
@ -2160,7 +2160,7 @@ pub mod tests {
&state,
);
let result = state.transition_from_privacy_preserving_transaction(&tx);
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
@ -2237,7 +2237,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let recipient_post = state.get_account_by_id(to);
@ -2280,7 +2280,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2320,7 +2320,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(
result,
Err(NssaError::MaxChainedCallsDepthExceeded)
@ -2361,7 +2361,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2417,7 +2417,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2526,7 +2526,7 @@ pub mod tests {
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&transaction)
.transition_from_privacy_preserving_transaction(&transaction, 1)
.unwrap();
// Assert
@ -2582,7 +2582,7 @@ pub mod tests {
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
// Execution of winner's token holding account initialization
let instruction = token_core::Instruction::InitializeAccount;
@ -2595,7 +2595,7 @@ pub mod tests {
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
// Submit a solution to the pinata program to claim the prize
let solution: u128 = 989_106;
@ -2612,7 +2612,7 @@ pub mod tests {
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx).unwrap();
state.transition_from_public_transaction(&tx, 1).unwrap();
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
assert_eq!(
@ -2642,7 +2642,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -2688,7 +2688,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
let tx = PublicTransaction::new(message, witness_set);
let res = state.transition_from_public_transaction(&tx);
let res = state.transition_from_public_transaction(&tx, 1);
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
let sender_post = state.get_account_by_id(sender_id);
@ -2757,7 +2757,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
let result = state.transition_from_privacy_preserving_transaction(&tx);
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
assert!(result.is_ok());
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
@ -2810,7 +2810,7 @@ pub mod tests {
// Claim should succeed
assert!(
state
.transition_from_privacy_preserving_transaction(&tx)
.transition_from_privacy_preserving_transaction(&tx, 1)
.is_ok()
);
@ -2859,7 +2859,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
// Should succeed - no changes made, no claim needed
assert!(result.is_ok());
@ -2884,7 +2884,7 @@ pub mod tests {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);
let result = state.transition_from_public_transaction(&tx, 1);
// Should fail - cannot modify data without claiming the account
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));

View File

@ -146,10 +146,12 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
tx: NSSATransaction,
) -> Result<NSSATransaction, nssa::error::NssaError> {
match &tx {
NSSATransaction::Public(tx) => self.state.transition_from_public_transaction(tx),
NSSATransaction::Public(tx) => self
.state
.transition_from_public_transaction(tx, self.next_block_id()),
NSSATransaction::PrivacyPreserving(tx) => self
.state
.transition_from_privacy_preserving_transaction(tx),
.transition_from_privacy_preserving_transaction(tx, self.next_block_id()),
NSSATransaction::ProgramDeployment(tx) => self
.state
.transition_from_program_deployment_transaction(tx),
@ -183,10 +185,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
) -> Result<(SignedMantleTx, MsgId)> {
let now = Instant::now();
let new_block_height = self
.chain_height
.checked_add(1)
.with_context(|| format!("Max block height reached: {}", self.chain_height))?;
let new_block_height = self.next_block_id();
let mut valid_transactions = vec![];
@ -333,6 +332,12 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
pub fn indexer_client(&self) -> IC {
self.indexer_client.clone()
}
fn next_block_id(&self) -> u64 {
self.chain_height
.checked_add(1)
.expect(&format!("Max block height reached: {}", self.chain_height))
}
}
/// Load signing key from file or generate a new one if it doesn't exist.

View File

@ -675,7 +675,7 @@ impl RocksDBIO {
"transaction pre check failed with err {err:?}"
))
})?
.execute_check_on_state(&mut breakpoint)
.execute_check_on_state(&mut breakpoint, block.header.block_id)
.map_err(|err| {
DbError::db_interaction_error(format!(
"transaction execution failed with err {err:?}"