From d9ddd5e3f66ab54ab34b850c746447f7de04febd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 6 Apr 2026 21:07:55 -0300 Subject: [PATCH] fix docs. refactor sequencer logic to check size before executing --- common/src/transaction.rs | 34 ++++----- indexer/core/src/block_store.rs | 6 +- programs/clock/core/src/lib.rs | 2 +- sequencer/core/src/lib.rs | 75 +++++++++---------- .../guest/src/bin/pinata_cooldown.rs | 2 +- 5 files changed, 59 insertions(+), 60 deletions(-) diff --git a/common/src/transaction.rs b/common/src/transaction.rs index f5b25b18..7ce0e76f 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -44,23 +44,6 @@ impl NSSATransaction { } } - /// Returns the canonical Clock 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: clock_core::Instruction) -> Self { - let message = nssa::public_transaction::Message::try_new( - nssa::program::Program::clock().id(), - clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), - vec![], - timestamp, - ) - .expect("Clock invocation message should always be constructable"); - Self::Public(nssa::PublicTransaction::new( - message, - nssa::public_transaction::WitnessSet::from_raw_parts(vec![]), - )) - } - // TODO: Introduce type-safe wrapper around checked transaction, e.g. AuthenticatedTransaction pub fn transaction_stateless_check(self) -> Result { // Stateless checks here @@ -170,3 +153,20 @@ pub enum TransactionMalformationError { #[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")] TransactionTooLarge { size: usize, max: usize }, } + +/// Returns the canonical Clock 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: clock_core::Instruction) -> nssa::PublicTransaction { + let message = nssa::public_transaction::Message::try_new( + nssa::program::Program::clock().id(), + clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), + vec![], + timestamp, + ) + .expect("Clock invocation message should always be constructable"); + nssa::PublicTransaction::new( + message, + nssa::public_transaction::WitnessSet::from_raw_parts(vec![]), + ) +} diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index 73636790..a76d1b26 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -4,7 +4,7 @@ use anyhow::Result; use bedrock_client::HeaderId; use common::{ block::{BedrockStatus, Block}, - transaction::NSSATransaction, + transaction::{NSSATransaction, clock_invocation}, }; use nssa::{Account, AccountId, V03State}; use nssa_core::BlockId; @@ -129,7 +129,7 @@ impl IndexerStore { .ok_or_else(|| anyhow::anyhow!("Block has no transactions"))?; anyhow::ensure!( - *clock_tx == NSSATransaction::clock_invocation(block.header.timestamp), + *clock_tx == NSSATransaction::Public(clock_invocation(block.header.timestamp)), "Last transaction in block must be the clock invocation for the block timestamp" ); @@ -236,7 +236,7 @@ mod tests { ); let block_id = u64::try_from(i).unwrap(); let block_timestamp = block_id.saturating_mul(100); - let clock_tx = NSSATransaction::clock_invocation(block_timestamp); + let clock_tx = NSSATransaction::Public(clock_invocation(block_timestamp)); let next_block = common::test_utils::produce_dummy_block( block_id, diff --git a/programs/clock/core/src/lib.rs b/programs/clock/core/src/lib.rs index 0a5471c4..5fc03633 100644 --- a/programs/clock/core/src/lib.rs +++ b/programs/clock/core/src/lib.rs @@ -22,7 +22,7 @@ pub const CLOCK_PROGRAM_ACCOUNT_IDS: [AccountId; 3] = [ /// The instruction type for the Clock Program. The sequencer passes the current block timestamp. pub type Instruction = Timestamp; -/// The data stored in a clock account: `[block_id: u64 LE | timestamp: u64 LE]`. +/// The data stored in a clock account. #[derive(Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct ClockAccountData { pub block_id: u64, diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 26e3c439..51c4d3a9 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -7,7 +7,7 @@ use common::PINATA_BASE58; use common::{ HashType, block::{BedrockStatus, Block, HashableBlockData}, - transaction::NSSATransaction, + transaction::{NSSATransaction, clock_invocation}, }; use config::SequencerConfig; use log::{error, info, warn}; @@ -205,20 +205,6 @@ impl SequencerCore diff, - Err(err) => { - error!( - "Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it", - ); - continue; - } - }; - // Check if block size exceeds limit let temp_valid_transactions = [valid_transactions.as_slice(), std::slice::from_ref(&tx)].concat(); @@ -244,6 +230,20 @@ impl SequencerCore diff, + Err(err) => { + error!( + "Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it", + ); + continue; + } + }; + self.state.apply_state_diff(validated_diff); valid_transactions.push(tx); @@ -253,22 +253,12 @@ impl SequencerCore tx, - NSSATransaction::PrivacyPreserving(_) - | NSSATransaction::ProgramDeployment(_) => { - unreachable!("clock_invocation always returns Public") - } - }, - new_block_height, - new_block_timestamp, - ) + .transition_from_public_transaction(&clock_tx, new_block_height, new_block_timestamp) .context("Clock transaction failed. Aborting block production.")?; - valid_transactions.push(clock_nssa_tx); + valid_transactions.push(NSSATransaction::Public(clock_tx)); let hashable_data = HashableBlockData { block_id: new_block_height, @@ -393,7 +383,10 @@ mod tests { use std::{pin::pin, time::Duration}; use bedrock_client::BackoffConfig; - use common::{test_utils::sequencer_sign_key_for_testing, transaction::NSSATransaction}; + use common::{ + test_utils::sequencer_sign_key_for_testing, + transaction::{NSSATransaction, clock_invocation}, + }; use logos_blockchain_core::mantle::ops::channel::ChannelId; use mempool::MemPoolHandle; use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys}; @@ -656,7 +649,7 @@ mod tests { block.body.transactions, vec![ tx.clone(), - NSSATransaction::clock_invocation(block.header.timestamp) + NSSATransaction::Public(clock_invocation(block.header.timestamp)) ] ); } @@ -688,7 +681,7 @@ mod tests { block.body.transactions, vec![ tx.clone(), - NSSATransaction::clock_invocation(block.header.timestamp) + NSSATransaction::Public(clock_invocation(block.header.timestamp)) ] ); @@ -705,7 +698,9 @@ mod tests { // The replay is rejected, so only the clock tx is in the block. assert_eq!( block.body.transactions, - vec![NSSATransaction::clock_invocation(block.header.timestamp)] + vec![NSSATransaction::Public(clock_invocation( + block.header.timestamp + ))] ); } @@ -745,7 +740,7 @@ mod tests { block.body.transactions, vec![ tx.clone(), - NSSATransaction::clock_invocation(block.header.timestamp) + NSSATransaction::Public(clock_invocation(block.header.timestamp)) ] ); } @@ -879,7 +874,7 @@ mod tests { new_block.body.transactions, vec![ tx, - NSSATransaction::clock_invocation(new_block.header.timestamp) + NSSATransaction::Public(clock_invocation(new_block.header.timestamp)) ], "New block should contain the submitted transaction and the clock invocation" ); @@ -905,7 +900,7 @@ mod tests { )) }; mempool_handle - .push(NSSATransaction::clock_invocation(0)) + .push(NSSATransaction::Public(clock_invocation(0))) .await .unwrap(); mempool_handle.push(crafted_clock_tx).await.unwrap(); @@ -922,7 +917,9 @@ mod tests { // Both transactions were dropped. Only the system-appended clock tx remains. assert_eq!( block.body.transactions, - vec![NSSATransaction::clock_invocation(block.header.timestamp)] + vec![NSSATransaction::Public(clock_invocation( + block.header.timestamp + ))] ); } @@ -1027,7 +1024,9 @@ mod tests { // The user tx must have been dropped; only the mandatory clock invocation remains. assert_eq!( block.body.transactions, - vec![NSSATransaction::clock_invocation(block.header.timestamp)] + vec![NSSATransaction::Public(clock_invocation( + block.header.timestamp + ))] ); } diff --git a/test_program_methods/guest/src/bin/pinata_cooldown.rs b/test_program_methods/guest/src/bin/pinata_cooldown.rs index 460fb34b..1ea3465b 100644 --- a/test_program_methods/guest/src/bin/pinata_cooldown.rs +++ b/test_program_methods/guest/src/bin/pinata_cooldown.rs @@ -7,7 +7,7 @@ //! Expected pre-states (in order): //! 0 - pinata account (authorized, owned by this program) //! 1 - winner account -//! 2 - clock account (read-only, e.g. `CLOCK_01`). +//! 2 - clock account `CLOCK_01`. //! //! Pinata account data layout (24 bytes): //! [prize: u64 LE | `cooldown_ms`: u64 LE | `last_claim_timestamp`: u64 LE].