From fa2fd857a93023baabc01d615f92bb39f3374cde Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 1 Apr 2026 00:01:11 -0300 Subject: [PATCH] minor refactor --- Cargo.lock | 10 +++++ Cargo.toml | 2 + common/Cargo.toml | 1 + common/src/transaction.rs | 30 ++++++++++++--- indexer/core/src/block_store.rs | 2 +- nssa/Cargo.toml | 1 + nssa/src/lib.rs | 3 +- nssa/src/state.rs | 43 +++++++-------------- program_methods/guest/Cargo.toml | 1 + program_methods/guest/src/bin/clock.rs | 35 ++++++++++------- sequencer/core/src/lib.rs | 53 +++++++++----------------- 11 files changed, 93 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf582227..c66a6aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1462,6 +1462,13 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "clock_core" +version = "0.1.0" +dependencies = [ + "nssa_core", +] + [[package]] name = "cobs" version = "0.3.0" @@ -1511,6 +1518,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "borsh", + "clock_core", "hex", "log", "logos-blockchain-common-http-client", @@ -5259,6 +5267,7 @@ version = "0.1.0" dependencies = [ "anyhow", "borsh", + "clock_core", "env_logger", "hex", "hex-literal 1.1.0", @@ -5897,6 +5906,7 @@ dependencies = [ "amm_program", "ata_core", "ata_program", + "clock_core", "nssa_core", "risc0-zkvm", "serde", diff --git a/Cargo.toml b/Cargo.toml index c2853089..1d9aa707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "nssa/core", "programs/amm/core", "programs/amm", + "programs/clock/core", "programs/token/core", "programs/token", "programs/associated_token_account/core", @@ -56,6 +57,7 @@ indexer_service_protocol = { path = "indexer/service/protocol" } indexer_service_rpc = { path = "indexer/service/rpc" } wallet = { path = "wallet" } wallet-ffi = { path = "wallet-ffi", default-features = false } +clock_core = { path = "programs/clock/core" } token_core = { path = "programs/token/core" } token_program = { path = "programs/token" } amm_core = { path = "programs/amm/core" } diff --git a/common/Cargo.toml b/common/Cargo.toml index 0ae0b220..dbf5ec0c 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] nssa.workspace = true nssa_core.workspace = true +clock_core.workspace = true anyhow.workspace = true thiserror.workspace = true diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 3d08a8e9..2c004d6b 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -47,14 +47,10 @@ impl NSSATransaction { /// Returns the canonical Block Context Program invocation transaction for the given block /// timestamp. Every valid block must end with exactly one occurrence of this transaction. #[must_use] - pub fn clock_invocation(timestamp: nssa_core::Timestamp) -> Self { + pub fn clock_invocation(timestamp: clock_core::Instruction) -> Self { let message = nssa::public_transaction::Message::try_new( nssa::program::Program::clock().id(), - vec![ - nssa::CLOCK_01_PROGRAM_ACCOUNT_ID, - nssa::CLOCK_10_PROGRAM_ACCOUNT_ID, - nssa::CLOCK_50_PROGRAM_ACCOUNT_ID, - ], + clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), vec![], timestamp, ) @@ -65,6 +61,28 @@ impl NSSATransaction { )) } + /// Returns `true` if this transaction is a user invocation of the clock program. + /// + /// For public transactions: checks whether the program ID matches the clock program. + /// For privacy-preserving transactions: checks whether any clock account has a modified + /// post-state (i.e. `post != pre`), using the provided pre-state snapshot. + /// Pass an empty slice when only the public case is relevant (e.g. in committed blocks where + /// PP clock-touching transactions are already filtered out by the sequencer). + #[must_use] + pub fn is_invocation_of_clock_program( + &self, + clock_pre_states: &[(nssa::AccountId, nssa::Account)], + ) -> bool { + let clock_program_id = nssa::program::Program::clock().id(); + match self { + Self::Public(tx) => tx.message().program_id == clock_program_id, + Self::PrivacyPreserving(pp) => clock_pre_states + .iter() + .any(|(id, pre)| pp.public_post_state_for(id).is_some_and(|post| post != pre)), + Self::ProgramDeployment(_) => false, + } + } + // TODO: Introduce type-safe wrapper around checked transaction, e.g. AuthenticatedTransaction pub fn transaction_stateless_check(self) -> Result { // Stateless checks here diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index 58344030..cfec9a01 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -136,7 +136,7 @@ impl IndexerStore { .body .transactions .iter() - .filter(|tx| *tx == &expected_clock_tx) + .filter(|tx| tx.is_invocation_of_clock_program(&[])) .count(); anyhow::ensure!( clock_count == 1, diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 07f5fe53..d8f0807c 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] nssa_core = { workspace = true, features = ["host"] } +clock_core.workspace = true anyhow.workspace = true thiserror.workspace = true diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 1d64b3c9..723c5633 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -17,7 +17,8 @@ pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; pub use public_transaction::PublicTransaction; pub use signature::{PrivateKey, PublicKey, Signature}; pub use state::{ - CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, V03State, + CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, + CLOCK_PROGRAM_ACCOUNT_IDS, V03State, }; pub mod encoding; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index d162a756..801707a7 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,6 +8,12 @@ use nssa_core::{ program::ProgramId, }; +pub use clock_core::{ + CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, + CLOCK_PROGRAM_ACCOUNT_IDS, +}; +use clock_core::ClockAccountData; + use crate::{ error::NssaError, merkle_tree::MerkleTree, privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program, @@ -17,15 +23,6 @@ use crate::{ pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; -pub const CLOCK_01_PROGRAM_ACCOUNT_ID: AccountId = - AccountId::new(*b"/LEZ/ClockProgramAccount/0000001"); - -pub const CLOCK_10_PROGRAM_ACCOUNT_ID: AccountId = - AccountId::new(*b"/LEZ/ClockProgramAccount/0000010"); - -pub const CLOCK_50_PROGRAM_ACCOUNT_ID: AccountId = - AccountId::new(*b"/LEZ/ClockProgramAccount/0000050"); - #[derive(Clone, BorshSerialize, BorshDeserialize)] #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct CommitmentSet { @@ -166,14 +163,9 @@ impl V03State { } fn insert_clock_accounts(&mut self, genesis_timestamp: nssa_core::Timestamp) { - let mut data = [0_u8; 16]; - data[8..].copy_from_slice(&genesis_timestamp.to_le_bytes()); + let data = ClockAccountData { block_id: 0, timestamp: genesis_timestamp }.to_bytes(); let clock_program_id = Program::clock().id(); - for account_id in [ - CLOCK_01_PROGRAM_ACCOUNT_ID, - CLOCK_10_PROGRAM_ACCOUNT_ID, - CLOCK_50_PROGRAM_ACCOUNT_ID, - ] { + for account_id in CLOCK_PROGRAM_ACCOUNT_IDS { self.public_state.insert( account_id, Account { @@ -400,7 +392,7 @@ pub mod tests { signature::PrivateKey, state::{ CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, - MAX_NUMBER_CHAINED_CALLS, + CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, }, }; @@ -543,11 +535,7 @@ pub mod tests { ..Account::default() }, ); - for account_id in [ - CLOCK_01_PROGRAM_ACCOUNT_ID, - CLOCK_10_PROGRAM_ACCOUNT_ID, - CLOCK_50_PROGRAM_ACCOUNT_ID, - ] { + for account_id in CLOCK_PROGRAM_ACCOUNT_IDS { this.insert( account_id, Account { @@ -736,11 +724,7 @@ pub mod tests { fn clock_transaction(timestamp: nssa_core::Timestamp) -> PublicTransaction { let message = public_transaction::Message::try_new( Program::clock().id(), - vec![ - CLOCK_01_PROGRAM_ACCOUNT_ID, - CLOCK_10_PROGRAM_ACCOUNT_ID, - CLOCK_50_PROGRAM_ACCOUNT_ID, - ], + CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), vec![], timestamp, ) @@ -753,9 +737,8 @@ pub mod tests { fn clock_account_data(state: &V03State, account_id: AccountId) -> (u64, nssa_core::Timestamp) { let data = state.get_account_by_id(account_id).data.into_inner(); - let block_id = u64::from_le_bytes(data[..8].try_into().unwrap()); - let timestamp = u64::from_le_bytes(data[8..].try_into().unwrap()); - (block_id, timestamp) + let parsed = clock_core::ClockAccountData::from_bytes(data[..16].try_into().unwrap()); + (parsed.block_id, parsed.timestamp) } #[test] diff --git a/program_methods/guest/Cargo.toml b/program_methods/guest/Cargo.toml index 29ef8304..dc2077b7 100644 --- a/program_methods/guest/Cargo.toml +++ b/program_methods/guest/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] nssa_core.workspace = true +clock_core.workspace = true token_core.workspace = true token_program.workspace = true amm_core.workspace = true diff --git a/program_methods/guest/src/bin/clock.rs b/program_methods/guest/src/bin/clock.rs index c74265d1..c31383c6 100644 --- a/program_methods/guest/src/bin/clock.rs +++ b/program_methods/guest/src/bin/clock.rs @@ -1,10 +1,12 @@ +use clock_core::{ + CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, + ClockAccountData, Instruction, +}; use nssa_core::{ account::AccountWithMetadata, program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}, }; -type Instruction = nssa_core::Timestamp; - fn update_if_multiple( pre: AccountWithMetadata, divisor: u64, @@ -34,24 +36,28 @@ fn main() { ) = read_nssa_inputs::(); let Ok([pre_01, pre_10, pre_50]) = <[_; 3]>::try_from(pre_states) else { - return; + panic!("Invalid number of input accounts"); }; - let prev_block_id = u64::from_le_bytes( - pre_01.account.data.clone().into_inner()[..8] + // Verify pre-states correspond to the expected clock account IDs. + if pre_01.account_id != CLOCK_01_PROGRAM_ACCOUNT_ID + || pre_10.account_id != CLOCK_10_PROGRAM_ACCOUNT_ID + || pre_50.account_id != CLOCK_50_PROGRAM_ACCOUNT_ID + { + panic!("Invalid input accounts"); + } + + let prev_data = ClockAccountData::from_bytes( + pre_01.account.data.clone().into_inner()[..16] .try_into() - .expect("Clock account data should contain a LE-encoded block_id u64"), + .expect("Clock account data should be 16 bytes"), ); - let current_block_id = prev_block_id + let current_block_id = prev_data + .block_id .checked_add(1) .expect("Next block id should be within u64 boundaries"); - let updated_data = { - let mut data = [0_u8; 16]; - data[..8].copy_from_slice(¤t_block_id.to_le_bytes()); - data[8..].copy_from_slice(×tamp.to_le_bytes()); - data - }; + let updated_data = ClockAccountData { block_id: current_block_id, timestamp }.to_bytes(); let (pre_01, post_01) = update_if_multiple(pre_01, 1, current_block_id, updated_data); let (pre_10, post_10) = update_if_multiple(pre_10, 10, current_block_id, updated_data); @@ -61,5 +67,6 @@ fn main() { instruction_words, vec![pre_01, pre_10, pre_50], vec![post_01, post_10, post_50], - ).write(); + ) + .write(); } diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index ef92fa8a..01f8ab3a 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -225,38 +225,20 @@ impl SequencerCore p.message().program_id == clock_program_id, - NSSATransaction::PrivacyPreserving(pp) => clock_accounts_pre - .iter() - .any(|(id, pre)| pp.public_post_state_for(id).is_some_and(|post| post != pre)), - NSSATransaction::ProgramDeployment(_) => false, - }; + // - any public tx that invokes the clock program, and + // - any PP tx that declares a modified post-state for a clock account. + let touches_system = tx.is_invocation_of_clock_program(&clock_accounts_pre); if touches_system { warn!( "Dropping transaction from mempool: user transactions may not modify the system clock account" @@ -310,15 +292,14 @@ impl SequencerCore { - valid_transactions.push(clock_nssa_tx); - } - Err(err) => { - error!("Clock transaction failed execution check: {err:#?}"); - } - } + let clock_nssa_tx = self + .execute_check_transaction_on_state( + NSSATransaction::clock_invocation(new_block_timestamp), + new_block_height, + new_block_timestamp, + ) + .context("Clock transaction failed — aborting block production")?; + valid_transactions.push(clock_nssa_tx); let hashable_data = HashableBlockData { block_id: new_block_height,