diff --git a/Cargo.lock b/Cargo.lock index d9c0bc76..fa55320f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1466,6 +1466,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" name = "clock_core" version = "0.1.0" dependencies = [ + "borsh", "nssa_core", ] @@ -7146,7 +7147,6 @@ dependencies = [ "borsh", "bytesize", "chrono", - "clock_core", "common", "futures", "humantime-serde", diff --git a/common/src/transaction.rs b/common/src/transaction.rs index c6153271..f5b25b18 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -44,7 +44,7 @@ impl NSSATransaction { } } - /// Returns the canonical Block Context Program invocation transaction for the given block + /// 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 { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f525c4a2..c21793e8 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -177,9 +177,9 @@ impl V03State { Account { program_owner: clock_program_id, data: data - .to_vec() + .clone() .try_into() - .expect("16 bytes should fit within accounts data"), + .expect("Clock account data should fit within accounts data"), ..Account::default() }, ); @@ -724,7 +724,7 @@ 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 parsed = clock_core::ClockAccountData::from_bytes(data[..16].try_into().unwrap()); + let parsed = clock_core::ClockAccountData::from_bytes(&data); (parsed.block_id, parsed.timestamp) } diff --git a/nssa/src/validated_state_diff.rs b/nssa/src/validated_state_diff.rs index 4ac0306a..71f697dd 100644 --- a/nssa/src/validated_state_diff.rs +++ b/nssa/src/validated_state_diff.rs @@ -35,7 +35,7 @@ pub struct StateDiff { /// The validated output of executing or verifying a transaction, ready to be applied to the state. /// /// Can only be constructed by the transaction validation functions inside this crate, ensuring the -/// diff has been cryptographically checked before any state mutation occurs. +/// diff has been checked before any state mutation occurs. pub struct ValidatedStateDiff(StateDiff); impl ValidatedStateDiff { diff --git a/program_methods/guest/src/bin/clock.rs b/program_methods/guest/src/bin/clock.rs index 4cdc86dc..c06b7336 100644 --- a/program_methods/guest/src/bin/clock.rs +++ b/program_methods/guest/src/bin/clock.rs @@ -1,3 +1,13 @@ +//! Clock Program. +//! +//! A system program that records the current block ID and timestamp into dedicated clock accounts. +//! Three accounts are maintained, updated at different block intervals (every 1, 10, and 50 +//! blocks), allowing programs to read recent timestamps at various granularities. +//! +//! This program can only be invoked exclusively by the sequencer as the last transaction in every +//! block. Clock accounts are assigned to the clock program at genesis, so no claiming is required +//! here. + use clock_core::{ CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, ClockAccountData, Instruction, @@ -11,14 +21,14 @@ fn update_if_multiple( pre: AccountWithMetadata, divisor: u64, current_block_id: u64, - updated_data: [u8; 16], + updated_data: &[u8], ) -> (AccountWithMetadata, AccountPostState) { if current_block_id.is_multiple_of(divisor) { let mut post_account = pre.account.clone(); post_account.data = updated_data .to_vec() .try_into() - .expect("16 bytes should fit in account data"); + .expect("Clock account data should fit in account data"); (pre, AccountPostState::new(post_account)) } else { let post = AccountPostState::new(pre.account.clone()); @@ -48,11 +58,15 @@ fn main() { 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 be 16 bytes"), - ); + // Verify all clock accounts are owned by this program (assigned at genesis). + if pre_01.account.program_owner != self_program_id + || pre_10.account.program_owner != self_program_id + || pre_50.account.program_owner != self_program_id + { + panic!("Clock accounts must be owned by the clock program"); + } + + let prev_data = ClockAccountData::from_bytes(&pre_01.account.data.clone().into_inner()); let current_block_id = prev_data .block_id .checked_add(1) @@ -64,9 +78,9 @@ fn main() { } .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); - let (pre_50, post_50) = update_if_multiple(pre_50, 50, current_block_id, updated_data); + 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); + let (pre_50, post_50) = update_if_multiple(pre_50, 50, current_block_id, &updated_data); ProgramOutput::new( self_program_id, diff --git a/programs/clock/core/Cargo.toml b/programs/clock/core/Cargo.toml index 28acdb48..53a43b6d 100644 --- a/programs/clock/core/Cargo.toml +++ b/programs/clock/core/Cargo.toml @@ -9,3 +9,4 @@ workspace = true [dependencies] nssa_core.workspace = true +borsh.workspace = true diff --git a/programs/clock/core/src/lib.rs b/programs/clock/core/src/lib.rs index 77f2ff6e..1dd8e629 100644 --- a/programs/clock/core/src/lib.rs +++ b/programs/clock/core/src/lib.rs @@ -1,5 +1,6 @@ //! Core data structures and constants for the Clock Program. +use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{Timestamp, account::AccountId}; pub const CLOCK_01_PROGRAM_ACCOUNT_ID: AccountId = @@ -22,7 +23,7 @@ pub const CLOCK_PROGRAM_ACCOUNT_IDS: [AccountId; 3] = [ pub type Instruction = Timestamp; /// The data stored in a clock account: `[block_id: u64 LE | timestamp: u64 LE]`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct ClockAccountData { pub block_id: u64, pub timestamp: Timestamp, @@ -30,20 +31,12 @@ pub struct ClockAccountData { impl ClockAccountData { #[must_use] - pub fn to_bytes(self) -> [u8; 16] { - let mut data = [0_u8; 16]; - data[..8].copy_from_slice(&self.block_id.to_le_bytes()); - data[8..].copy_from_slice(&self.timestamp.to_le_bytes()); - data + pub fn to_bytes(self) -> Vec { + borsh::to_vec(&self).expect("ClockAccountData serialization should not fail") } #[must_use] - pub fn from_bytes(bytes: &[u8; 16]) -> Self { - let block_id = u64::from_le_bytes(bytes[..8].try_into().unwrap()); - let timestamp = u64::from_le_bytes(bytes[8..].try_into().unwrap()); - Self { - block_id, - timestamp, - } + pub fn from_bytes(bytes: &[u8]) -> Self { + borsh::from_slice(bytes).expect("ClockAccountData deserialization should not fail") } } diff --git a/sequencer/core/Cargo.toml b/sequencer/core/Cargo.toml index a02420bd..8e16ecb4 100644 --- a/sequencer/core/Cargo.toml +++ b/sequencer/core/Cargo.toml @@ -41,4 +41,3 @@ mock = [] [dev-dependencies] futures.workspace = true test_program_methods.workspace = true -clock_core.workspace = true diff --git a/sequencer/core/src/config.rs b/sequencer/core/src/config.rs index 2fb101aa..893370d8 100644 --- a/sequencer/core/src/config.rs +++ b/sequencer/core/src/config.rs @@ -24,7 +24,7 @@ pub struct SequencerConfig { pub genesis_id: u64, /// If `True`, then adds random sequence of bytes to genesis block. pub is_genesis_random: bool, - /// Maximum number of transactions in block. + /// Maximum number of user transactions in a block (excludes the mandatory clock transaction). pub max_num_tx_in_block: usize, /// Maximum block size (includes header and transactions). #[serde(default = "default_max_block_size")]