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
46 changes: 35 additions & 11 deletions crates/blockchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ pub use ethlambda_types::block::MAX_ATTESTATIONS_DATA;
/// See: leanSpec PR #682.
pub const GOSSIP_DISPARITY_INTERVALS: u64 = 1;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SlotInterval {
BlockPublication,
AttestationProduction,
Aggregation,
SafeTargetUpdate,
EndOfSlot,
}

impl SlotInterval {
pub(crate) fn from_ms_since_genesis(ms_since_genesis: u64) -> Self {
Self::from_intervals_since_genesis(ms_since_genesis / MILLISECONDS_PER_INTERVAL)
}

pub(crate) fn from_intervals_since_genesis(intervals_since_genesis: u64) -> Self {
match intervals_since_genesis % INTERVALS_PER_SLOT {
0 => Self::BlockPublication,
1 => Self::AttestationProduction,
2 => Self::Aggregation,
3 => Self::SafeTargetUpdate,
4 => Self::EndOfSlot,
_ => unreachable!("slots only have 5 intervals"),
}
}
}

/// Milliseconds until the next interval boundary, measured relative to genesis.
fn ms_until_next_interval(now_ms: u64, genesis_time_ms: u64) -> u64 {
// Before genesis: wait until genesis itself.
Expand Down Expand Up @@ -192,7 +218,7 @@ impl BlockChainServer {
// Calculate current slot and interval from milliseconds
let time_since_genesis_ms = timestamp_ms.saturating_sub(genesis_time_ms);
let slot = time_since_genesis_ms / MILLISECONDS_PER_SLOT;
let interval = (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL;
let interval = SlotInterval::from_ms_since_genesis(time_since_genesis_ms);

// Idempotency guard
//
Expand All @@ -206,7 +232,7 @@ impl BlockChainServer {
if store_time > 0 && tick_interval <= store_time {
debug!(
%slot,
%interval,
?interval,
tick_interval,
store_time,
"Skipping already-processed tick"
Expand Down Expand Up @@ -252,15 +278,15 @@ impl BlockChainServer {
// needs (those stragglers surface in the `late` section instead). Skip
// empty snapshots so a missed round keeps the last set we saw. Pure
// observability.
if interval == 4
if interval == SlotInterval::EndOfSlot
&& let Some(snapshot) = coverage::snapshot_new_payloads(&self.store)
{
self.pre_merge_coverage = Some(snapshot);
}

// Whether one of our validators proposes this slot. Drives the store's
// interval-0 attestation acceptance.
let is_proposer = (interval == 0 && slot > 0)
let is_proposer = (interval == SlotInterval::BlockPublication && slot > 0)
.then(|| self.get_our_proposer(slot))
.flatten()
.is_some();
Expand All @@ -280,14 +306,14 @@ impl BlockChainServer {
// advances the store to this slot's interval 0 before building (see
// `propose_block`). The real interval-0 tick is then skipped by the
// idempotency guard above, since the store clock is already here.
0 => {}
SlotInterval::BlockPublication => {}

// ==== interval 1 ====
//
// Produce attestations at interval 1 (all validators including
// proposer). Reuse the same snapshot so self-delivery decisions
// match the rest of the tick.
1 => {
SlotInterval::AttestationProduction => {
// Emit the post-block coverage report for the previous slot.
// Fired at interval 1 (not 0) so the block carrying `slot - 1`'s
// votes β€” proposed at interval 0 of this slot β€” has typically
Expand All @@ -309,7 +335,7 @@ impl BlockChainServer {
}

// ==== interval 2 ====
2 => {
SlotInterval::Aggregation => {
if is_aggregator {
coverage::emit_agg_start_new_coverage(
&self.store,
Expand All @@ -324,7 +350,7 @@ impl BlockChainServer {
// ==== interval 3 ====
//
// Safe-target update is handled inside `store::on_tick`.
3 => {}
SlotInterval::SafeTargetUpdate => {}

// ==== interval 4 ====
//
Expand All @@ -335,7 +361,7 @@ impl BlockChainServer {
// rather than stashing it for the interval-0 tick β€” keeps it robust:
// `on_tick` skips the interval-0 tick whenever this build overruns
// its interval.
4 => {
SlotInterval::EndOfSlot => {
let next_slot = slot + 1;
let next_proposer = self
.get_our_proposer(next_slot)
Expand All @@ -345,8 +371,6 @@ impl BlockChainServer {
self.propose_block(next_slot, validator_id).await;
}
}

_ => {}
}

// Update safe target slot metric (updated by store.on_tick at interval 3)
Expand Down
17 changes: 8 additions & 9 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use tracing::{info, trace, warn};

use crate::{
GOSSIP_DISPARITY_INTERVALS, INTERVALS_PER_SLOT, MAX_ATTESTATIONS_DATA,
MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT,
MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT, SlotInterval,
block_builder::{PostBlockCheckpoints, ProposerConfig, build_block},
metrics,
};
Expand Down Expand Up @@ -278,9 +278,9 @@ pub fn on_tick(store: &mut Store, timestamp_ms: u64, has_proposal: bool) {
.expect("set_time should succeed");

let slot = store.time() / INTERVALS_PER_SLOT;
let interval = store.time() % INTERVALS_PER_SLOT;
let interval = SlotInterval::from_intervals_since_genesis(store.time());

trace!(%slot, %interval, "processing tick");
trace!(%slot, ?interval, "processing tick");

// has_proposal is only signaled for the final tick (matching Python spec behavior)
let is_final_tick = store.time() == time;
Expand All @@ -292,27 +292,26 @@ pub fn on_tick(store: &mut Store, timestamp_ms: u64, has_proposal: bool) {
// the actor's message loop stays unblocked during the expensive XMSS
// proofs. See `BlockChainServer::start_aggregation_session` in `lib.rs`.
match interval {
0 => {
SlotInterval::BlockPublication => {
// Start of slot - process attestations if proposal exists
if should_signal_proposal {
accept_new_attestations(store, false);
}
}
1 => {
SlotInterval::AttestationProduction => {
// Vote propagation β€” no action
}
2 => {
SlotInterval::Aggregation => {
// Aggregation is driven by the actor (off-thread); nothing to do here.
}
3 => {
SlotInterval::SafeTargetUpdate => {
// Update safe target for validators
update_safe_target(store);
}
4 => {
SlotInterval::EndOfSlot => {
// End of slot - accept accumulated attestations and log tree
accept_new_attestations(store, true);
}
_ => unreachable!("slots only have 5 intervals"),
}
}
}
Expand Down