fix docs. refactor sequencer logic to check size before executing

This commit is contained in:
Sergio Chouhy 2026-04-06 21:07:55 -03:00
parent ed1926b38a
commit d9ddd5e3f6
5 changed files with 59 additions and 60 deletions

View File

@ -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<Self, TransactionMalformationError> {
// 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![]),
)
}

View File

@ -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,

View File

@ -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,

View File

@ -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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
while let Some(tx) = self.mempool.pop() {
let tx_hash = tx.hash();
let validated_diff = match tx.validate_on_state(
&self.state,
new_block_height,
new_block_timestamp,
) {
Ok(diff) => 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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
break;
}
let validated_diff = match tx.validate_on_state(
&self.state,
new_block_height,
new_block_timestamp,
) {
Ok(diff) => 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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
}
}
// Append the Block Context Program invocation as the mandatory last transaction.
let clock_nssa_tx = NSSATransaction::clock_invocation(new_block_timestamp);
// Append the Clock Program invocation as the mandatory last transaction.
let clock_tx = clock_invocation(new_block_timestamp);
self.state
.transition_from_public_transaction(
match &clock_nssa_tx {
NSSATransaction::Public(tx) => 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
))]
);
}

View File

@ -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].