feat: extend ValidityWindow with Unix timestamp bounds

This commit is contained in:
moudyellaz 2026-03-24 10:24:05 +01:00
parent 414abe32ba
commit 0043f29dc2
15 changed files with 215 additions and 84 deletions

View File

@ -4,6 +4,7 @@ use nssa::{AccountId, V03State};
use serde::{Deserialize, Serialize};
use crate::{HashType, block::BlockId};
use nssa_core::program::Timestamp;
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum NSSATransaction {
@ -69,11 +70,14 @@ impl NSSATransaction {
self,
state: &mut V03State,
block_id: BlockId,
timestamp_ms: Timestamp,
) -> Result<Self, nssa::error::NssaError> {
match &self {
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id),
Self::Public(tx) => {
state.transition_from_public_transaction(tx, block_id, timestamp_ms)
}
Self::PrivacyPreserving(tx) => {
state.transition_from_privacy_preserving_transaction(tx, block_id)
state.transition_from_privacy_preserving_transaction(tx, block_id, timestamp_ms)
}
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
}

View File

@ -183,11 +183,24 @@ pub fn TransactionPage() -> impl IntoView {
signatures_and_public_keys: _,
proof,
} = witness_set;
let validity_window_formatted = match validity_window.0 {
(Some(start), Some(end)) => format!("from {start} to {end}"),
(Some(start), None) => format!("from {start}"),
(None, Some(end)) => format!("until {end}"),
(None, None) => "unbounded".to_owned(),
let (block_from, block_to, ts_from, ts_to) = validity_window.0;
let block_part = match (block_from, block_to) {
(Some(start), Some(end)) => format!("block {start}..{end}"),
(Some(start), None) => format!("block {start}.."),
(None, Some(end)) => format!("block ..{end}"),
(None, None) => String::new(),
};
let ts_part = match (ts_from, ts_to) {
(Some(start), Some(end)) => format!("ts {start}..{end}"),
(Some(start), None) => format!("ts {start}.."),
(None, Some(end)) => format!("ts ..{end}"),
(None, None) => String::new(),
};
let validity_window_formatted = match (block_part.is_empty(), ts_part.is_empty()) {
(true, true) => "unbounded".to_owned(),
(false, true) => block_part,
(true, false) => ts_part,
(false, false) => format!("{block_part}, {ts_part}"),
};
let proof_len = proof.map_or(0, |p| p.0.len());

View File

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

View File

@ -302,7 +302,12 @@ impl From<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
.into_iter()
.map(|(n, d)| (n.into(), d.into()))
.collect(),
validity_window: ValidityWindow((validity_window.from(), validity_window.to())),
validity_window: ValidityWindow((
validity_window.from(),
validity_window.to(),
validity_window.from_timestamp(),
validity_window.to_timestamp(),
)),
}
}
}

View File

@ -302,7 +302,9 @@ pub struct Nullifier(
);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct ValidityWindow(pub (Option<BlockId>, Option<BlockId>));
pub struct ValidityWindow(
pub (Option<BlockId>, Option<BlockId>, Option<TimeStamp>, Option<TimeStamp>),
);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct CommitmentSetDigest(

View File

@ -124,7 +124,7 @@ impl MockIndexerService {
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
CommitmentSetDigest([0xff; 32]),
)],
validity_window: ValidityWindow((None, None)),
validity_window: ValidityWindow((None, None, None, None)),
},
witness_set: WitnessSet {
signatures_and_public_keys: vec![],

View File

@ -1,6 +1,6 @@
use std::collections::HashSet;
#[cfg(feature = "host")]
#[cfg(any(feature = "host", test))]
use borsh::{BorshDeserialize, BorshSerialize};
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
use serde::{Deserialize, Serialize};
@ -154,6 +154,8 @@ impl AccountPostState {
}
pub type BlockId = u64;
/// Unix timestamp in milliseconds.
pub type Timestamp = u64;
#[derive(Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(
@ -163,6 +165,8 @@ pub type BlockId = u64;
pub struct ValidityWindow {
from: Option<BlockId>,
to: Option<BlockId>,
from_timestamp: Option<Timestamp>,
to_timestamp: Option<Timestamp>,
}
impl ValidityWindow {
@ -171,10 +175,22 @@ impl ValidityWindow {
Self {
from: None,
to: None,
from_timestamp: None,
to_timestamp: None,
}
}
/// Valid for block IDs in the range [from, to) and timestamps in [from_timestamp, to_timestamp).
#[must_use]
pub fn is_valid_for(&self, block_id: BlockId, timestamp_ms: Timestamp) -> bool {
self.from.is_none_or(|start| block_id >= start)
&& self.to.is_none_or(|end| block_id < end)
&& self.from_timestamp.is_none_or(|t| timestamp_ms >= t)
&& self.to_timestamp.is_none_or(|t| timestamp_ms < t)
}
/// Valid for block IDs in the range [from, to), where `from` is included and `to` is excluded.
/// Ignores timestamp bounds.
#[must_use]
pub fn is_valid_for_block_id(&self, id: BlockId) -> bool {
self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end)
@ -184,10 +200,14 @@ impl ValidityWindow {
if let (Some(from_id), Some(until_id)) = (self.from, self.to)
&& from_id >= until_id
{
Err(InvalidWindow)
} else {
Ok(())
return Err(InvalidWindow);
}
if let (Some(from_ts), Some(until_ts)) = (self.from_timestamp, self.to_timestamp)
&& from_ts >= until_ts
{
return Err(InvalidWindow);
}
Ok(())
}
#[must_use]
@ -199,7 +219,18 @@ impl ValidityWindow {
pub const fn to(&self) -> Option<BlockId> {
self.to
}
#[must_use]
pub const fn from_timestamp(&self) -> Option<Timestamp> {
self.from_timestamp
}
#[must_use]
pub const fn to_timestamp(&self) -> Option<Timestamp> {
self.to_timestamp
}
}
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
type Error = InvalidWindow;
@ -207,6 +238,37 @@ impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
let this = Self {
from: value.0,
to: value.1,
from_timestamp: None,
to_timestamp: None,
};
this.check_window()?;
Ok(this)
}
}
impl
TryFrom<(
Option<BlockId>,
Option<BlockId>,
Option<Timestamp>,
Option<Timestamp>,
)> for ValidityWindow
{
type Error = InvalidWindow;
fn try_from(
value: (
Option<BlockId>,
Option<BlockId>,
Option<Timestamp>,
Option<Timestamp>,
),
) -> Result<Self, Self::Error> {
let this = Self {
from: value.0,
to: value.1,
from_timestamp: value.2,
to_timestamp: value.3,
};
this.check_window()?;
Ok(this)
@ -271,6 +333,18 @@ impl ProgramOutput {
self.validity_window.check_window()?;
Ok(self)
}
pub fn valid_from_timestamp(mut self, ts: Option<Timestamp>) -> Result<Self, InvalidWindow> {
self.validity_window.from_timestamp = ts;
self.validity_window.check_window()?;
Ok(self)
}
pub fn valid_until_timestamp(mut self, ts: Option<Timestamp>) -> Result<Self, InvalidWindow> {
self.validity_window.to_timestamp = ts;
self.validity_window.check_window()?;
Ok(self)
}
}
/// Representation of a number as `lo + hi * 2^128`.

View File

@ -7,7 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
account::{Account, AccountWithMetadata},
program::{BlockId, ValidityWindow},
program::{BlockId, Timestamp, ValidityWindow},
};
use sha2::{Digest as _, digest::FixedOutput as _};
@ -37,6 +37,7 @@ impl PrivacyPreservingTransaction {
&self,
state: &V03State,
block_id: BlockId,
timestamp_ms: Timestamp,
) -> Result<HashMap<AccountId, Account>, NssaError> {
let message = &self.message;
let witness_set = &self.witness_set;
@ -94,7 +95,7 @@ impl PrivacyPreservingTransaction {
}
// Verify validity window
if !message.validity_window.is_valid_for_block_id(block_id) {
if !message.validity_window.is_valid_for(block_id, timestamp_ms) {
return Err(NssaError::OutOfValidityWindow);
}

View File

@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use log::debug;
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata},
program::{BlockId, ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
program::{BlockId, ChainedCall, DEFAULT_PROGRAM_ID, Timestamp, validate_execution},
};
use sha2::{Digest as _, digest::FixedOutput as _};
@ -71,6 +71,7 @@ impl PublicTransaction {
&self,
state: &V03State,
block_id: BlockId,
timestamp_ms: Timestamp,
) -> Result<HashMap<AccountId, Account>, NssaError> {
let message = self.message();
let witness_set = self.witness_set();
@ -195,7 +196,7 @@ impl PublicTransaction {
ensure!(
program_output
.validity_window
.is_valid_for_block_id(block_id),
.is_valid_for(block_id, timestamp_ms),
NssaError::OutOfValidityWindow
);
@ -368,7 +369,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state, 1);
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
@ -388,7 +389,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state, 1);
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
@ -409,7 +410,7 @@ pub mod tests {
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state, 1);
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
@ -429,7 +430,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state, 1);
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
@ -445,7 +446,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state, 1);
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
}

View File

@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
account::{Account, AccountId, Nonce},
program::{BlockId, ProgramId},
program::{BlockId, ProgramId, Timestamp},
};
use crate::{
@ -158,8 +158,9 @@ impl V03State {
&mut self,
tx: &PublicTransaction,
block_id: BlockId,
timestamp_ms: Timestamp,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
let state_diff = tx.validate_and_produce_public_state_diff(self, block_id, timestamp_ms)?;
#[expect(
clippy::iter_over_hash_type,
@ -183,9 +184,10 @@ impl V03State {
&mut self,
tx: &PrivacyPreservingTransaction,
block_id: BlockId,
timestamp_ms: Timestamp,
) -> Result<(), NssaError> {
// 1. Verify the transaction satisfies acceptance criteria
let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id, timestamp_ms)?;
let message = tx.message();
@ -340,7 +342,7 @@ pub mod tests {
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
program::{BlockId, PdaSeed, ProgramId},
program::{BlockId, PdaSeed, ProgramId, Timestamp},
};
use crate::{
@ -570,7 +572,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 95);
assert_eq!(state.get_account_by_id(to).balance, 5);
@ -591,7 +593,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(state.get_account_by_id(from).balance, 100);
@ -615,7 +617,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 192);
assert_eq!(state.get_account_by_id(to).balance, 108);
@ -635,10 +637,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
@ -660,7 +662,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -677,7 +679,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -694,7 +696,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -718,7 +720,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -742,7 +744,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -766,7 +768,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -790,7 +792,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -818,7 +820,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -843,7 +845,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -861,7 +863,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -890,7 +892,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -1083,7 +1085,7 @@ pub mod tests {
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx, 1)
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
let sender_post = state.get_account_by_id(sender_keys.account_id());
@ -1153,7 +1155,7 @@ pub mod tests {
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx, 1)
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
@ -1217,7 +1219,7 @@ pub mod tests {
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx, 1)
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
@ -2145,7 +2147,7 @@ pub mod tests {
);
state
.transition_from_privacy_preserving_transaction(&tx, 1)
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap();
let sender_private_account = Account {
@ -2163,7 +2165,7 @@ pub mod tests {
&state,
);
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
@ -2240,7 +2242,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let recipient_post = state.get_account_by_id(to);
@ -2283,7 +2285,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2323,7 +2325,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(
result,
Err(NssaError::MaxChainedCallsDepthExceeded)
@ -2364,7 +2366,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2420,7 +2422,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let from_post = state.get_account_by_id(from);
let to_post = state.get_account_by_id(to);
@ -2529,7 +2531,7 @@ pub mod tests {
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&transaction, 1)
.transition_from_privacy_preserving_transaction(&transaction, 1, 0)
.unwrap();
// Assert
@ -2585,7 +2587,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
// Execution of winner's token holding account initialization
let instruction = token_core::Instruction::InitializeAccount;
@ -2598,7 +2600,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
// Submit a solution to the pinata program to claim the prize
let solution: u128 = 989_106;
@ -2615,7 +2617,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, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
assert_eq!(
@ -2645,7 +2647,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
@ -2691,7 +2693,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, 1);
let res = state.transition_from_public_transaction(&tx, 1, 0);
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
let sender_post = state.get_account_by_id(sender_id);
@ -2760,7 +2762,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, 1);
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
assert!(result.is_ok());
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
@ -2813,7 +2815,7 @@ pub mod tests {
// Claim should succeed
assert!(
state
.transition_from_privacy_preserving_transaction(&tx, 1)
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
.is_ok()
);
@ -2862,7 +2864,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
// Should succeed - no changes made, no claim needed
assert!(result.is_ok());
@ -2887,7 +2889,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, 1);
let result = state.transition_from_public_transaction(&tx, 1, 0);
// Should fail - cannot modify data without claiming the account
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
@ -3024,17 +3026,18 @@ pub mod tests {
let account_ids = vec![pre.account_id];
let nonces = vec![];
let program_id = validity_window_program.id();
let instruction = (validity_window.0, validity_window.1, None::<Timestamp>, None::<Timestamp>);
let message = public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
validity_window,
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
PublicTransaction::new(message, witness_set)
};
let result = state.transition_from_public_transaction(&tx, block_id);
let result = state.transition_from_public_transaction(&tx, block_id, 0);
let is_inside_validity_window = match validity_window {
(Some(s), Some(e)) => s <= block_id && block_id < e,
(Some(s), None) => s <= block_id,
@ -3074,9 +3077,10 @@ pub mod tests {
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
let epk = EphemeralPublicKey::from_scalar(esk);
let instruction = (validity_window.0, validity_window.1, None::<Timestamp>, None::<Timestamp>);
let (output, proof) = circuit::execute_and_prove(
vec![pre],
Program::serialize_instruction(validity_window).unwrap(),
Program::serialize_instruction(instruction).unwrap(),
vec![2],
vec![(account_keys.npk(), shared_secret)],
vec![],
@ -3096,7 +3100,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
};
let result = state.transition_from_privacy_preserving_transaction(&tx, block_id);
let result = state.transition_from_privacy_preserving_transaction(&tx, block_id, 0);
let is_inside_validity_window = match validity_window {
(Some(s), Some(e)) => s <= block_id && block_id < e,
(Some(s), None) => s <= block_id,

View File

@ -34,10 +34,21 @@ impl ExecutionState {
.iter()
.filter_map(|output| output.validity_window.to())
.min();
let valid_from_ts = program_outputs
.iter()
.filter_map(|output| output.validity_window.from_timestamp())
.max();
let valid_until_ts = program_outputs
.iter()
.filter_map(|output| output.validity_window.to_timestamp())
.min();
let validity_window = (valid_from_id, valid_until_id).try_into().expect(
"There should be non empty intersection in the program output validity windows",
);
let validity_window =
(valid_from_id, valid_until_id, valid_from_ts, valid_until_ts)
.try_into()
.expect(
"There should be non empty intersection in the program output validity windows",
);
let mut execution_state = Self {
pre_states: Vec::new(),

View File

@ -2733,7 +2733,7 @@ fn simple_amm_remove() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -2813,7 +2813,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -2897,7 +2897,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -2969,7 +2969,7 @@ fn simple_amm_new_definition_uninitialized_pool() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -3031,7 +3031,7 @@ fn simple_amm_add() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -3088,7 +3088,7 @@ fn simple_amm_swap_1() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
@ -3138,7 +3138,7 @@ fn simple_amm_swap_2() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 1).unwrap();
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());

View File

@ -142,17 +142,24 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
(sequencer_core, mempool_handle)
}
fn next_block_timestamp_ms(&self) -> nssa_core::program::Timestamp {
u64::try_from(chrono::Utc::now().timestamp_millis())
.expect("Timestamp must be positive")
}
fn execute_check_transaction_on_state(
&mut self,
tx: NSSATransaction,
) -> Result<NSSATransaction, nssa::error::NssaError> {
let block_id = self.next_block_id();
let timestamp_ms = self.next_block_timestamp_ms();
match &tx {
NSSATransaction::Public(tx) => self
.state
.transition_from_public_transaction(tx, self.next_block_id()),
.transition_from_public_transaction(tx, block_id, timestamp_ms),
NSSATransaction::PrivacyPreserving(tx) => self
.state
.transition_from_privacy_preserving_transaction(tx, self.next_block_id()),
.transition_from_privacy_preserving_transaction(tx, block_id, timestamp_ms),
NSSATransaction::ProgramDeployment(tx) => self
.state
.transition_from_program_deployment_transaction(tx),

View File

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

View File

@ -1,14 +1,19 @@
use nssa_core::program::{
AccountPostState, BlockId, ProgramInput, ProgramOutput, read_nssa_inputs,
AccountPostState, BlockId, ProgramInput, ProgramOutput, Timestamp, read_nssa_inputs,
};
type Instruction = (Option<BlockId>, Option<BlockId>);
type Instruction = (
Option<BlockId>,
Option<BlockId>,
Option<Timestamp>,
Option<Timestamp>,
);
fn main() {
let (
ProgramInput {
pre_states,
instruction: (from_id, until_id),
instruction: (from_id, until_id, from_ts, until_ts),
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
@ -27,6 +32,10 @@ fn main() {
.valid_from_id(from_id)
.unwrap()
.valid_until_id(until_id)
.unwrap()
.valid_from_timestamp(from_ts)
.unwrap()
.valid_until_timestamp(until_ts)
.unwrap();
output.write();