Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions application/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub struct Actor<
built_block: Arc<Mutex<Option<(Block, Round)>>>,
genesis_hash: [u8; 32],
epocher: ES,
allowed_timestamp_future_ms: u64,
cancellation_token: CancellationToken,
_scheme_marker: PhantomData<S>,
_key_marker: PhantomData<P>,
Expand Down Expand Up @@ -77,7 +76,6 @@ impl<
built_block: Arc::new(Mutex::new(None)),
genesis_hash,
epocher: cfg.epocher,
allowed_timestamp_future_ms: cfg.allowed_timestamp_future.as_millis() as u64,
cancellation_token: cfg.cancellation_token,
_scheme_marker: PhantomData,
_key_marker: PhantomData,
Expand Down Expand Up @@ -236,7 +234,6 @@ impl<
let mut syncer = syncer.clone();
let mut finalizer_clone = finalizer.clone();
let epocher = self.epocher.clone();
let allowed_timestamp_future_ms = self.allowed_timestamp_future_ms;
move |context| async move {
let requester = try_join(parent_request, block_request);
select! {
Expand Down Expand Up @@ -282,7 +279,7 @@ impl<
}

let now_millis = context.current().epoch_millis();
if handle_verify(&block, parent, &epocher, &aux_data, now_millis, allowed_timestamp_future_ms) {
if handle_verify(&block, parent, &epocher, &aux_data, now_millis) {
// persist valid block
syncer.verified(round, block).await;

Expand Down Expand Up @@ -581,7 +578,6 @@ fn handle_verify<ES: Epocher>(
epocher: &ES,
aux_data: &BlockAuxData,
now_millis: u64,
allowed_timestamp_future_ms: u64,
) -> bool {
// You can only re-propose the same block if it's the last height in the epoch.
if parent.digest() == block.digest() {
Expand All @@ -604,10 +600,12 @@ fn handle_verify<ES: Epocher>(
warn!("block timestamp not increasing");
return false;
}
if block.timestamp() > now_millis + allowed_timestamp_future_ms {
if block.timestamp() > now_millis + aux_data.allowed_timestamp_future_ms {
warn!(
block_timestamp = block.timestamp(),
now_millis, allowed_timestamp_future_ms, "block timestamp too far in the future"
now_millis,
allowed_timestamp_future_ms = aux_data.allowed_timestamp_future_ms,
"block timestamp too far in the future"
);
return false;
}
Expand Down
7 changes: 0 additions & 7 deletions application/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use commonware_consensus::types::Epocher;
use std::time::Duration;
use summit_types::EngineClient;
use tokio_util::sync::CancellationToken;

Expand All @@ -18,11 +17,5 @@ pub struct ApplicationConfig<C: EngineClient, ES: Epocher> {
/// Epocher for determining epoch boundaries.
pub epocher: ES,

/// Maximum allowed delta between a block's timestamp and the
/// local wall clock. Blocks with timestamps that differ from
/// the local time by more than this are rejected during
/// verification.
pub allowed_timestamp_future: Duration,

pub cancellation_token: CancellationToken,
}
19 changes: 11 additions & 8 deletions docs/ssz-merklization.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The state tree is a two-level design: a fixed top-level tree containing scalar f

### Top-Level Tree

32 leaf slots (depth 5), 17 used. Each leaf is a 32-byte `hash_tree_root` value. Leaves 17–31 are unused (zero-filled).
32 leaf slots (depth 5), 18 used. Each leaf is a 32-byte `hash_tree_root` value. Leaves 18–31 are unused (zero-filled).

| Leaf Index | Field | Type |
|------------|-------|------|
Expand All @@ -51,12 +51,13 @@ The state tree is a two-level design: a fixed top-level tree containing scalar f
| 8 | `forkchoice_head_block_hash` | Scalar |
| 9 | `forkchoice_safe_block_hash` | Scalar |
| 10 | `forkchoice_finalized_block_hash` | Scalar |
| 11 | `validator_accounts` | Collection root |
| 12 | `deposit_queue` | Collection root |
| 13 | `withdrawal_queue` | Collection root |
| 14 | `protocol_param_changes` | Collection root |
| 15 | `added_validators` | Collection root |
| 16 | `removed_validators` | Collection root |
| 11 | `allowed_timestamp_future_ms` | Scalar |
| 12 | `validator_accounts` | Collection root |
| 13 | `deposit_queue` | Collection root |
| 14 | `withdrawal_queue` | Collection root |
| 15 | `protocol_param_changes` | Collection root |
| 16 | `added_validators` | Collection root |
| 17 | `removed_validators` | Collection root |

### Collection Subtrees

Expand Down Expand Up @@ -141,7 +142,7 @@ A `HashMap<pubkey, (epoch_slot, item_slot)>` index enables O(1) proof lookup by

All leaf values are 32 bytes, produced by SSZ `hash_tree_root`:

- **`u64`**: Little-endian encoded, zero-padded to 32 bytes. Used by: epoch, view, latest_height, balance, amount, index, joining_epoch, last_deposit_index, next_withdrawal_index, minimum/maximum_stake, validator_index, balance_deduction.
- **`u64`**: Little-endian encoded, zero-padded to 32 bytes. Used by: epoch, view, latest_height, balance, amount, index, joining_epoch, last_deposit_index, next_withdrawal_index, minimum/maximum_stake, allowed_timestamp_future_ms, validator_index, balance_deduction.
- **`bool`**: `0x01` or `0x00`, zero-padded to 32 bytes. Used by: has_pending_deposit, has_pending_withdrawal.
- **`ValidatorStatus` (enum)**: Single byte (Active=0, Inactive=1, SubmittedExitRequest=2, Joining=3), zero-padded to 32 bytes.
- **`[u8; 32]`**: Used directly as the leaf value. Used by: head_digest, epoch_genesis_hash, forkchoice hashes, withdrawal_credentials (deposit), pubkey (withdrawal).
Expand All @@ -168,6 +169,7 @@ Single top-level leaf write + rehash of the 5-level path to root.
| `set_epoch_genesis_hash()` | `ssz_tree.set_epoch_genesis_hash()` |
| `set_minimum_stake()` | `ssz_tree.set_validator_minimum_stake()` |
| `set_maximum_stake()` | `ssz_tree.set_validator_maximum_stake()` |
| `set_allowed_timestamp_future_ms()` | `ssz_tree.set_allowed_timestamp_future_ms()` |
| `set_next_withdrawal_index()` | `ssz_tree.set_next_withdrawal_index()` |
| `set_forkchoice_head()` | `ssz_tree.set_forkchoice_head_block_hash()` |
| `set_forkchoice_safe_and_finalized()` | Two setter calls (safe + finalized) |
Expand Down Expand Up @@ -392,6 +394,7 @@ Keys are human-readable strings parsed by `types/src/ssz_tree_key.rs`:
| `epoch_genesis_hash` | Genesis hash for current epoch |
| `validator_minimum_stake` | Minimum validator stake |
| `validator_maximum_stake` | Maximum validator stake |
| `allowed_timestamp_future_ms` | Allowed timestamp future (ms) |
| `next_withdrawal_index` | Next withdrawal index |
| `forkchoice_head_block_hash` | Forkchoice head hash |
| `forkchoice_safe_block_hash` | Forkchoice safe hash |
Expand Down
1 change: 1 addition & 0 deletions example_genesis.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace = "_SUMMIT"
validator_minimum_stake = 32000000000
validator_maximum_stake = 32000000000
blocks_per_epoch = 10000
allowed_timestamp_future_ms = 10000

[[validators]]
node_public_key = "1be3cb06d7cc347602421fb73838534e4b54934e28959de98906d120d0799ef2"
Expand Down
6 changes: 6 additions & 0 deletions finalizer/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ impl<
forkchoice: *state.get_forkchoice(),
withdrawal_credentials,
state_root: state.get_state_root(),
allowed_timestamp_future_ms: state.get_allowed_timestamp_future_ms(),
}
} else {
BlockAuxData {
Expand All @@ -901,6 +902,7 @@ impl<
forkchoice: *state.get_forkchoice(),
withdrawal_credentials,
state_root: state.get_state_root(),
allowed_timestamp_future_ms: state.get_allowed_timestamp_future_ms(),
}
};
trace!(
Expand Down Expand Up @@ -970,6 +972,10 @@ impl<
let length = self.canonical_state.get_epocher().current_length();
let _ = sender.send(ConsensusStateResponse::EpochLength(length));
}
ConsensusStateRequest::GetAllowedTimestampFuture => {
let ms = self.canonical_state.get_allowed_timestamp_future_ms();
let _ = sender.send(ConsensusStateResponse::AllowedTimestampFuture(ms));
}
ConsensusStateRequest::GetEpochBounds(epoch) => {
let bounds = self
.canonical_state
Expand Down
18 changes: 18 additions & 0 deletions finalizer/src/ingress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,24 @@ impl<S: Scheme<B::Digest>, B: ConsensusBlock> FinalizerMailbox<S, B> {
length
}

pub async fn get_allowed_timestamp_future(&self) -> u64 {
let (response, rx) = oneshot::channel();
let request = ConsensusStateRequest::GetAllowedTimestampFuture;
let _ = self
.sender
.clone()
.send(FinalizerMessage::QueryState { request, response })
.await;

let res = rx
.await
.expect("consensus state query response sender dropped");
let ConsensusStateResponse::AllowedTimestampFuture(ms) = res else {
unreachable!("request and response variants must match");
};
ms
}

pub async fn get_epoch_bounds(&self, epoch: u64) -> Option<(u64, u64)> {
let (response, rx) = oneshot::channel();
let request = ConsensusStateRequest::GetEpochBounds(epoch);
Expand Down
8 changes: 7 additions & 1 deletion finalizer/src/tests/fork_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ fn create_test_initial_state(genesis_hash: [u8; 32], epoch_length: NonZeroU64) -
safe_block_hash: genesis_hash.into(),
finalized_block_hash: genesis_hash.into(),
};
let mut state = ConsensusState::new(forkchoice, 32_000_000_000, 64_000_000_000, epoch_length);
let mut state = ConsensusState::new(
forkchoice,
32_000_000_000,
64_000_000_000,
epoch_length,
10_000,
);
state.set_validator_accounts(validator_accounts);
state
}
Expand Down
8 changes: 7 additions & 1 deletion finalizer/src/tests/state_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ fn create_test_initial_state(genesis_hash: [u8; 32], epoch_length: NonZeroU64) -
safe_block_hash: genesis_hash.into(),
finalized_block_hash: genesis_hash.into(),
};
let mut state = ConsensusState::new(forkchoice, 32_000_000_000, 64_000_000_000, epoch_length);
let mut state = ConsensusState::new(
forkchoice,
32_000_000_000,
64_000_000_000,
epoch_length,
10_000,
);
state.set_validator_accounts(validator_accounts);
state
}
Expand Down
8 changes: 7 additions & 1 deletion finalizer/src/tests/syncing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ fn create_test_initial_state(genesis_hash: [u8; 32], epoch_length: NonZeroU64) -
safe_block_hash: genesis_hash.into(),
finalized_block_hash: genesis_hash.into(),
};
let mut state = ConsensusState::new(forkchoice, 32_000_000_000, 64_000_000_000, epoch_length);
let mut state = ConsensusState::new(
forkchoice,
32_000_000_000,
64_000_000_000,
epoch_length,
10_000,
);
state.set_validator_accounts(validator_accounts);
state
}
Expand Down
8 changes: 7 additions & 1 deletion finalizer/src/tests/validator_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ fn create_test_initial_state(genesis_hash: [u8; 32], epoch_length: NonZeroU64) -
safe_block_hash: genesis_hash.into(),
finalized_block_hash: genesis_hash.into(),
};
let mut state = ConsensusState::new(forkchoice, 32_000_000_000, 64_000_000_000, epoch_length);
let mut state = ConsensusState::new(
forkchoice,
32_000_000_000,
64_000_000_000,
epoch_length,
10_000,
);
state.set_validator_accounts(validator_accounts);
state
}
Expand Down
4 changes: 4 additions & 0 deletions node/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ impl Command {
genesis.validator_maximum_stake,
NonZeroU64::new(genesis.blocks_per_epoch)
.expect("blocks_per_epoch must be nonzero"),
genesis.allowed_timestamp_future_ms,
);
let peers = initial_state.get_validator_keys();

Expand Down Expand Up @@ -502,6 +503,7 @@ pub fn run_node_local(
genesis.validator_minimum_stake,
genesis.validator_maximum_stake,
NonZeroU64::new(genesis.blocks_per_epoch).expect("blocks_per_epoch must be nonzero"),
genesis.allowed_timestamp_future_ms,
);
let peers = initial_state.get_validator_keys();

Expand Down Expand Up @@ -660,6 +662,7 @@ fn get_initial_state(
validator_minimum_stake: u64,
validator_maximum_stake: u64,
epoch_length: NonZeroU64,
allowed_timestamp_future_ms: u64,
) -> ConsensusState {
let genesis_hash: B256 = genesis_hash.into();
checkpoint.unwrap_or_else(|| {
Expand All @@ -673,6 +676,7 @@ fn get_initial_state(
validator_minimum_stake,
validator_maximum_stake,
epoch_length,
allowed_timestamp_future_ms,
);
// Add the genesis nodes to the consensus state with the minimum stake balance.
for validator in genesis_committee {
Expand Down
1 change: 1 addition & 0 deletions node/src/bin/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct GenesisConfig {
max_message_size_bytes: u64,
namespace: String,
blocks_per_epoch: u64,
allowed_timestamp_future_ms: u64,
pub validators: Vec<GenesisValidator>,
}

Expand Down
4 changes: 2 additions & 2 deletions node/src/bin/stake_and_checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ struct Args {
#[arg(long, default_value = "/tmp/summit_checkpointing_test")]
pub data_dir: String,
/// Height at which the joining node will download the checkpoint
#[arg(long, default_value_t = 1000)]
#[arg(long, default_value_t = 50)]
pub checkpoint_height: u64,
/// Height that all nodes must reach for the test to succeed
#[arg(long, default_value_t = 2000)]
#[arg(long, default_value_t = 100)]
pub stop_height: u64,
}

Expand Down
2 changes: 0 additions & 2 deletions node/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const BUFFER_POOL_PAGE_SIZE: u16 = 4_096; // 4KB
const BUFFER_POOL_CAPACITY: NonZero<usize> = NZUsize!(8_192); // 32MB
const PRUNABLE_ITEMS_PER_SECTION: NonZero<u64> = NZU64!(4_096);
const IMMUTABLE_ITEMS_PER_SECTION: NonZero<u64> = NZU64!(262_144);
const ALLOWED_TIMESTAMP_FUTURE: Duration = Duration::from_secs(10);
const FREEZER_TABLE_RESIZE_FREQUENCY: u8 = 4;
const FREEZER_TABLE_RESIZE_CHUNK_SIZE: u32 = 2u32.pow(16); // 3MB
const FREEZER_JOURNAL_TARGET_SIZE: u64 = 1024 * 1024 * 1024; // 1GB
Expand Down Expand Up @@ -174,7 +173,6 @@ where
partition_prefix: cfg.partition_prefix.clone(),
genesis_hash: cfg.genesis_hash,
epocher: epocher.clone(),
allowed_timestamp_future: ALLOWED_TIMESTAMP_FUTURE,
cancellation_token: cancellation_token.clone(),
},
)
Expand Down
1 change: 1 addition & 0 deletions node/src/test_harness/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ pub fn get_initial_state(
balance,
balance,
NonZeroU64::new(DEFAULT_BLOCKS_PER_EPOCH).unwrap(),
10_000, // 10 seconds
);
// Add the genesis nodes to the consensus state with the minimum stake balance.
for ((node_pubkey, consensus_pubkey), address) in committee.iter().zip(addresses.iter()) {
Expand Down
2 changes: 2 additions & 0 deletions node/src/tests/checkpointing/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn test_checkpoint_verification_fixed_committee() {
validator_minimum_stake: 32_000_000_000,
validator_maximum_stake: 32_000_000_000,
blocks_per_epoch: common::DEFAULT_BLOCKS_PER_EPOCH,
allowed_timestamp_future_ms: 10_000,
};

let node_public_keys: Vec<_> = validators.iter().map(|(pk, _)| pk.clone()).collect();
Expand Down Expand Up @@ -312,6 +313,7 @@ fn test_checkpoint_verification_dynamic_committee() {
validator_minimum_stake: min_stake,
validator_maximum_stake: min_stake,
blocks_per_epoch: common::DEFAULT_BLOCKS_PER_EPOCH,
allowed_timestamp_future_ms: 10_000,
};

let node_public_keys: Vec<_> = validators.iter().map(|(pk, _)| pk.clone()).collect();
Expand Down
Loading
Loading