mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-26 04:03:06 +00:00
add sequencer checks
This commit is contained in:
parent
90f20a7040
commit
3324bcf391
BIN
artifacts/program_methods/clock.bin
Normal file
BIN
artifacts/program_methods/clock.bin
Normal file
Binary file not shown.
@ -43,6 +43,23 @@ impl NSSATransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical Block Context Program invocation transaction.
|
||||
/// Every valid block must end with exactly one occurrence of this transaction.
|
||||
#[must_use]
|
||||
pub fn clock_invocation() -> Self {
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
nssa::program::Program::clock().id(),
|
||||
vec![nssa::CLOCK_PROGRAM_ACCOUNT_ID],
|
||||
vec![],
|
||||
(),
|
||||
)
|
||||
.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
|
||||
|
||||
@ -119,6 +119,30 @@ impl IndexerStore {
|
||||
|
||||
pub async fn put_block(&self, mut block: Block, l1_header: HeaderId) -> Result<()> {
|
||||
{
|
||||
let canonical_clock_tx = NSSATransaction::clock_invocation();
|
||||
|
||||
// Validate block structure: the last transaction must be the sole clock invocation.
|
||||
let last_tx = block
|
||||
.body
|
||||
.transactions
|
||||
.last()
|
||||
.ok_or_else(|| anyhow::anyhow!("Block must contain at least one transaction"))?;
|
||||
anyhow::ensure!(
|
||||
last_tx == &canonical_clock_tx,
|
||||
"Last transaction in block must be the canonical clock invocation"
|
||||
);
|
||||
|
||||
let clock_count = block
|
||||
.body
|
||||
.transactions
|
||||
.iter()
|
||||
.filter(|tx| *tx == &canonical_clock_tx)
|
||||
.count();
|
||||
anyhow::ensure!(
|
||||
clock_count == 1,
|
||||
"Block must contain exactly one Block Context Program invocation"
|
||||
);
|
||||
|
||||
let mut state_guard = self.current_state.write().await;
|
||||
|
||||
for transaction in &block.body.transactions {
|
||||
@ -208,11 +232,12 @@ mod tests {
|
||||
10,
|
||||
&sign_key,
|
||||
);
|
||||
let clock_tx = NSSATransaction::clock_invocation();
|
||||
|
||||
let next_block = common::test_utils::produce_dummy_block(
|
||||
u64::try_from(i).unwrap(),
|
||||
Some(prev_hash),
|
||||
vec![tx],
|
||||
vec![tx, clock_tx],
|
||||
);
|
||||
prev_hash = next_block.header.hash;
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ pub use program_deployment_transaction::ProgramDeploymentTransaction;
|
||||
pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID;
|
||||
pub use public_transaction::PublicTransaction;
|
||||
pub use signature::{PrivateKey, PublicKey, Signature};
|
||||
pub use state::V03State;
|
||||
pub use state::{CLOCK_PROGRAM_ACCOUNT_ID, V03State};
|
||||
|
||||
pub mod encoding;
|
||||
pub mod error;
|
||||
|
||||
@ -154,6 +154,17 @@ impl PrivacyPreservingTransaction {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the post-state the transaction declares for `account_id`, or `None` if the account
|
||||
/// is not part of this transaction's public execution.
|
||||
#[must_use]
|
||||
pub fn public_post_state_for(&self, account_id: &AccountId) -> Option<&Account> {
|
||||
self.message
|
||||
.public_account_ids
|
||||
.iter()
|
||||
.position(|id| id == account_id)
|
||||
.map(|i| &self.message.public_post_states[i])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
let mut acc_set = self
|
||||
|
||||
@ -16,6 +16,9 @@ use crate::{
|
||||
|
||||
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
||||
|
||||
pub const CLOCK_PROGRAM_ACCOUNT_ID: AccountId =
|
||||
AccountId::new(*b"/LEZ/ClockProgramAccount/0000001");
|
||||
|
||||
#[derive(Clone, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
pub struct CommitmentSet {
|
||||
@ -148,8 +151,8 @@ impl V03State {
|
||||
this.insert_program(Program::amm());
|
||||
this.insert_program(Program::clock());
|
||||
|
||||
this.force_insert_account(
|
||||
AccountId::new(b"/LEZ/ClockProgramAccount/0000001".to_owned()),
|
||||
this.public_state.insert(
|
||||
CLOCK_PROGRAM_ACCOUNT_ID,
|
||||
Account {
|
||||
program_owner: Program::clock().id(),
|
||||
data: 0_u64
|
||||
|
||||
@ -202,9 +202,27 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
let curr_time = u64::try_from(chrono::Utc::now().timestamp_millis())
|
||||
.expect("Timestamp must be positive");
|
||||
|
||||
let clock_program_id = nssa::program::Program::clock().id();
|
||||
let clock_account_pre = self.state.get_account_by_id(nssa::CLOCK_PROGRAM_ACCOUNT_ID);
|
||||
|
||||
while let Some(tx) = self.mempool.pop() {
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
// The Block Context Program is system-only. Reject:
|
||||
// - any public tx that invokes the clock program ID, and
|
||||
// - any PP tx that declares a modified post-state for the clock account.
|
||||
let touches_system = match &tx {
|
||||
NSSATransaction::Public(p) => p.message().program_id == clock_program_id,
|
||||
NSSATransaction::PrivacyPreserving(pp) => pp
|
||||
.public_post_state_for(&nssa::CLOCK_PROGRAM_ACCOUNT_ID)
|
||||
.is_some_and(|post| post != &clock_account_pre),
|
||||
NSSATransaction::ProgramDeployment(_) => false,
|
||||
};
|
||||
if touches_system {
|
||||
warn!("Dropping transaction from mempool: user transactions may not modify the system clock account");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if block size exceeds limit
|
||||
let temp_valid_transactions =
|
||||
[valid_transactions.as_slice(), std::slice::from_ref(&tx)].concat();
|
||||
@ -249,6 +267,16 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
}
|
||||
}
|
||||
|
||||
// Append the Block Context Program invocation as the mandatory last transaction.
|
||||
match self.execute_check_transaction_on_state(NSSATransaction::clock_invocation()) {
|
||||
Ok(clock_nssa_tx) => {
|
||||
valid_transactions.push(clock_nssa_tx);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Clock transaction failed execution check: {err:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
let hashable_data = HashableBlockData {
|
||||
block_id: new_block_height,
|
||||
transactions: valid_transactions,
|
||||
@ -368,7 +396,8 @@ mod tests {
|
||||
use base58::ToBase58 as _;
|
||||
use bedrock_client::BackoffConfig;
|
||||
use common::{
|
||||
block::AccountInitialData, test_utils::sequencer_sign_key_for_testing,
|
||||
block::AccountInitialData,
|
||||
test_utils::sequencer_sign_key_for_testing,
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||
@ -694,8 +723,8 @@ mod tests {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Only one should be included in the block
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
// Only one user tx should be included; the clock tx is always appended last.
|
||||
assert_eq!(block.body.transactions, vec![tx.clone(), NSSATransaction::clock_invocation()]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -721,7 +750,7 @@ mod tests {
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
assert_eq!(block.body.transactions, vec![tx.clone(), NSSATransaction::clock_invocation()]);
|
||||
|
||||
// Add same transaction should fail
|
||||
mempool_handle.push(tx.clone()).await.unwrap();
|
||||
@ -733,7 +762,8 @@ mod tests {
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(block.body.transactions.is_empty());
|
||||
// The replay is rejected, so only the clock tx is in the block.
|
||||
assert_eq!(block.body.transactions, vec![NSSATransaction::clock_invocation()]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -768,7 +798,7 @@ mod tests {
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
assert_eq!(block.body.transactions, vec![tx.clone(), NSSATransaction::clock_invocation()]);
|
||||
}
|
||||
|
||||
// Instantiating a new sequencer from the same config. This should load the existing block
|
||||
@ -898,11 +928,50 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
new_block.body.transactions,
|
||||
vec![tx],
|
||||
"New block should contain the submitted transaction"
|
||||
vec![tx, NSSATransaction::clock_invocation()],
|
||||
"New block should contain the submitted transaction and the clock invocation"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn transactions_touching_clock_account_are_dropped_from_block() {
|
||||
let (mut sequencer, mempool_handle) = common_setup().await;
|
||||
|
||||
// Canonical clock invocation and a crafted variant with different parameters targeting
|
||||
// the clock program — both must be dropped since the program_id is the clock program.
|
||||
let crafted_clock_tx = {
|
||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
nssa::program::Program::clock().id(),
|
||||
vec![acc1],
|
||||
vec![],
|
||||
42_u64,
|
||||
)
|
||||
.unwrap();
|
||||
NSSATransaction::Public(nssa::PublicTransaction::new(
|
||||
message,
|
||||
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
||||
))
|
||||
};
|
||||
mempool_handle
|
||||
.push(NSSATransaction::clock_invocation())
|
||||
.await
|
||||
.unwrap();
|
||||
mempool_handle.push(crafted_clock_tx).await.unwrap();
|
||||
sequencer
|
||||
.produce_new_block_with_mempool_transactions()
|
||||
.unwrap();
|
||||
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Both transactions were dropped. Only the system-appended clock tx remains.
|
||||
assert_eq!(block.body.transactions, vec![NSSATransaction::clock_invocation()]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_from_config_uses_db_height_not_config_genesis() {
|
||||
let mut config = setup_sequencer_config();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user