add more clock accounts

This commit is contained in:
Sergio Chouhy 2026-03-31 01:39:02 -03:00
parent d6a92f443e
commit d8ffa22b81
6 changed files with 208 additions and 59 deletions

Binary file not shown.

View File

@ -49,7 +49,11 @@ impl NSSATransaction {
pub fn clock_invocation(timestamp: nssa_core::Timestamp) -> Self {
let message = nssa::public_transaction::Message::try_new(
nssa::program::Program::clock().id(),
vec![nssa::CLOCK_PROGRAM_ACCOUNT_ID],
vec![
nssa::CLOCK_01_PROGRAM_ACCOUNT_ID,
nssa::CLOCK_10_PROGRAM_ACCOUNT_ID,
nssa::CLOCK_50_PROGRAM_ACCOUNT_ID,
],
vec![],
timestamp,
)

View File

@ -16,7 +16,9 @@ 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::{CLOCK_PROGRAM_ACCOUNT_ID, V03State};
pub use state::{
CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, CLOCK_01_PROGRAM_ACCOUNT_ID, V03State,
};
pub mod encoding;
pub mod error;

View File

@ -16,9 +16,15 @@ use crate::{
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
pub const CLOCK_PROGRAM_ACCOUNT_ID: AccountId =
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 {
@ -160,17 +166,24 @@ impl V03State {
fn insert_clock_accounts(&mut self, genesis_timestamp: nssa_core::Timestamp) {
let mut data = [0u8; 16];
data[8..].copy_from_slice(&genesis_timestamp.to_le_bytes());
self.public_state.insert(
CLOCK_PROGRAM_ACCOUNT_ID,
Account {
program_owner: Program::clock().id(),
data: data
.to_vec()
.try_into()
.expect("16 bytes should fit within accounts data"),
..Account::default()
},
);
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,
] {
self.public_state.insert(
account_id,
Account {
program_owner: clock_program_id,
data: data
.to_vec()
.try_into()
.expect("16 bytes should fit within accounts data"),
..Account::default()
},
);
}
}
pub(crate) fn insert_program(&mut self, program: Program) {
@ -377,7 +390,10 @@ pub mod tests {
program::Program,
public_transaction,
signature::PrivateKey,
state::{CLOCK_PROGRAM_ACCOUNT_ID, MAX_NUMBER_CHAINED_CALLS},
state::{
CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, CLOCK_01_PROGRAM_ACCOUNT_ID,
MAX_NUMBER_CHAINED_CALLS,
},
};
impl V03State {
@ -515,17 +531,23 @@ pub mod tests {
..Account::default()
},
);
this.insert(
CLOCK_PROGRAM_ACCOUNT_ID,
Account {
program_owner: clock_program.id(),
data: [0u8; 16]
.to_vec()
.try_into()
.unwrap(),
..Account::default()
},
);
for account_id in [
CLOCK_01_PROGRAM_ACCOUNT_ID,
CLOCK_10_PROGRAM_ACCOUNT_ID,
CLOCK_50_PROGRAM_ACCOUNT_ID,
] {
this.insert(
account_id,
Account {
program_owner: clock_program.id(),
data: [0u8; 16]
.to_vec()
.try_into()
.unwrap(),
..Account::default()
},
);
}
this
};
let expected_builtin_programs = {
@ -684,7 +706,11 @@ pub mod tests {
fn clock_transaction(timestamp: nssa_core::Timestamp) -> PublicTransaction {
let message = public_transaction::Message::try_new(
Program::clock().id(),
vec![CLOCK_PROGRAM_ACCOUNT_ID],
vec![
CLOCK_01_PROGRAM_ACCOUNT_ID,
CLOCK_10_PROGRAM_ACCOUNT_ID,
CLOCK_50_PROGRAM_ACCOUNT_ID,
],
vec![],
timestamp,
)
@ -695,11 +721,11 @@ pub mod tests {
)
}
fn clock_account_data(state: &V03State) -> (u64, nssa_core::Timestamp) {
let data = state
.get_account_by_id(CLOCK_PROGRAM_ACCOUNT_ID)
.data
.into_inner();
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)
@ -710,7 +736,7 @@ pub mod tests {
let genesis_timestamp = 1_000_000u64;
let state = V03State::new_with_genesis_accounts(&[], &[], genesis_timestamp);
let (block_id, timestamp) = clock_account_data(&state);
let (block_id, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id, 0);
assert_eq!(timestamp, genesis_timestamp);
@ -723,7 +749,7 @@ pub mod tests {
let tx = clock_transaction(1234);
state.transition_from_public_transaction(&tx).unwrap();
let (block_id, _) = clock_account_data(&state);
let (block_id, _) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id, 1);
}
@ -735,7 +761,7 @@ pub mod tests {
let tx = clock_transaction(block_timestamp);
state.transition_from_public_transaction(&tx).unwrap();
let (_, timestamp) = clock_account_data(&state);
let (_, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
assert_eq!(timestamp, block_timestamp);
}
@ -747,12 +773,90 @@ pub mod tests {
let tx = clock_transaction(expected_block_id * 1000);
state.transition_from_public_transaction(&tx).unwrap();
let (block_id, timestamp) = clock_account_data(&state);
let (block_id, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id, expected_block_id);
assert_eq!(timestamp, expected_block_id * 1000);
}
}
#[test]
fn clock_10_account_not_updated_when_block_id_not_multiple_of_10() {
let genesis_timestamp = 0u64;
let mut state = V03State::new_with_genesis_accounts(&[], &[], genesis_timestamp);
// Run 9 clock ticks (block_ids 1..=9), none of which are multiples of 10.
for tick in 1u64..=9 {
let tx = clock_transaction(tick * 1000);
state.transition_from_public_transaction(&tx).unwrap();
}
let (block_id_10, timestamp_10) =
clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID);
// The 10-block account should still reflect genesis state.
assert_eq!(block_id_10, 0);
assert_eq!(timestamp_10, genesis_timestamp);
}
#[test]
fn clock_10_account_updated_when_block_id_is_multiple_of_10() {
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
// Run 10 clock ticks so block_id reaches 10.
for tick in 1u64..=10 {
let tx = clock_transaction(tick * 1000);
state.transition_from_public_transaction(&tx).unwrap();
}
let (block_id_1, timestamp_1) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
let (block_id_10, timestamp_10) =
clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id_1, 10);
assert_eq!(block_id_10, 10);
assert_eq!(timestamp_10, timestamp_1);
}
#[test]
fn clock_50_account_only_updated_at_multiples_of_50() {
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
// After 49 ticks the 50-block account should be unchanged.
for tick in 1u64..=49 {
let tx = clock_transaction(tick * 1000);
state.transition_from_public_transaction(&tx).unwrap();
}
let (block_id_50, _) = clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id_50, 0);
// Tick 50 — now the 50-block account should update.
let tx = clock_transaction(50 * 1000);
state.transition_from_public_transaction(&tx).unwrap();
let (block_id_50, timestamp_50) =
clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id_50, 50);
assert_eq!(timestamp_50, 50 * 1000);
}
#[test]
fn all_three_clock_accounts_updated_at_multiple_of_50() {
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
// Advance to block 50 (a multiple of both 10 and 50).
for tick in 1u64..=50 {
let tx = clock_transaction(tick * 1000);
state.transition_from_public_transaction(&tx).unwrap();
}
let (block_id_1, ts_1) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID);
let (block_id_10, ts_10) = clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID);
let (block_id_50, ts_50) = clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID);
assert_eq!(block_id_1, 50);
assert_eq!(block_id_10, 50);
assert_eq!(block_id_50, 50);
assert_eq!(ts_1, ts_10);
assert_eq!(ts_1, ts_50);
}
#[test]
fn program_should_fail_if_modifies_nonces() {
let initial_data = [(AccountId::new([1; 32]), 100)];

View File

@ -1,7 +1,29 @@
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
use nssa_core::{
account::AccountWithMetadata,
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
};
type Instruction = nssa_core::Timestamp;
fn update_if_multiple(
pre: AccountWithMetadata,
divisor: u64,
current_block_id: u64,
updated_data: [u8; 16],
) -> (AccountWithMetadata, AccountPostState) {
if current_block_id % divisor == 0 {
let mut post_account = pre.account.clone();
post_account.data = updated_data
.to_vec()
.try_into()
.expect("16 bytes should fit in account data");
(pre, AccountPostState::new(post_account))
} else {
let post = AccountPostState::new(pre.account.clone());
(pre, post)
}
}
fn main() {
let (
ProgramInput {
@ -11,31 +33,33 @@ fn main() {
instruction_words,
) = read_nssa_inputs::<Instruction>();
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
let Ok([pre_01, pre_10, pre_50]) = <[_; 3]>::try_from(pre_states) else {
return;
};
let account_pre = &pre.account;
let account_pre_data = account_pre.data.clone().into_inner();
let block_id = u64::from_le_bytes(
account_pre_data[..8]
let prev_block_id = u64::from_le_bytes(
pre_01.account.data.clone().into_inner()[..8]
.try_into()
.expect("Block context program account data should contain a LE-encoded block_id u64"),
.expect("Clock account data should contain a LE-encoded block_id u64"),
);
let mut account_post = account_pre.clone();
let next_block_id = block_id
let current_block_id = prev_block_id
.checked_add(1)
.expect("Next block id should be within u64 boundaries");
let mut data = [0u8; 16];
data[..8].copy_from_slice(&next_block_id.to_le_bytes());
data[8..].copy_from_slice(&timestamp.to_le_bytes());
account_post.data = data
.to_vec()
.try_into()
.expect("16 bytes should fit in account data");
let post = AccountPostState::new(account_post);
let updated_data = {
let mut data = [0u8; 16];
data[..8].copy_from_slice(&current_block_id.to_le_bytes());
data[8..].copy_from_slice(&timestamp.to_le_bytes());
data
};
write_nssa_outputs(instruction_words, vec![pre], vec![post]);
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);
write_nssa_outputs(
instruction_words,
vec![pre_01, pre_10, pre_50],
vec![post_01, post_10, post_50],
);
}

View File

@ -207,7 +207,22 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
.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);
let clock_accounts_pre = [
(
nssa::CLOCK_01_PROGRAM_ACCOUNT_ID,
self.state.get_account_by_id(nssa::CLOCK_01_PROGRAM_ACCOUNT_ID),
),
(
nssa::CLOCK_10_PROGRAM_ACCOUNT_ID,
self.state
.get_account_by_id(nssa::CLOCK_10_PROGRAM_ACCOUNT_ID),
),
(
nssa::CLOCK_50_PROGRAM_ACCOUNT_ID,
self.state
.get_account_by_id(nssa::CLOCK_50_PROGRAM_ACCOUNT_ID),
),
];
while let Some(tx) = self.mempool.pop() {
let tx_hash = tx.hash();
@ -217,9 +232,9 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
// - 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::PrivacyPreserving(pp) => clock_accounts_pre
.iter()
.any(|(id, pre)| pp.public_post_state_for(id).is_some_and(|post| post != pre)),
NSSATransaction::ProgramDeployment(_) => false,
};
if touches_system {