From aef1caea43acc466c6a01426b128cd598d909b29 Mon Sep 17 00:00:00 2001 From: dicethedev Date: Fri, 3 Jul 2026 09:26:24 +0100 Subject: [PATCH 1/6] refactor: represent slot intervals with enum --- crates/blockchain/src/lib.rs | 59 ++++++++++++++++++++++++++++------ crates/blockchain/src/store.rs | 15 ++++----- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 8be7c89f..e8d83dc4 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet, VecDeque}; +use std::fmt; use std::time::{Duration, Instant, SystemTime}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; @@ -59,6 +60,44 @@ 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_slot_index(index: u64) -> Self { + match index { + 0 => Self::BlockPublication, + 1 => Self::AttestationProduction, + 2 => Self::Aggregation, + 3 => Self::SafeTargetUpdate, + 4 => Self::EndOfSlot, + _ => unreachable!("slots only have 5 intervals"), + } + } + + fn as_slot_index(self) -> u64 { + match self { + Self::BlockPublication => 0, + Self::AttestationProduction => 1, + Self::Aggregation => 2, + Self::SafeTargetUpdate => 3, + Self::EndOfSlot => 4, + } + } +} + +impl fmt::Display for SlotInterval { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_slot_index().fmt(f) + } +} + /// 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. @@ -192,7 +231,9 @@ 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_slot_index( + (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL, + ); // Idempotency guard // @@ -252,7 +293,7 @@ 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); @@ -260,7 +301,7 @@ impl BlockChainServer { // 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(); @@ -280,14 +321,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 @@ -309,7 +350,7 @@ impl BlockChainServer { } // ==== interval 2 ==== - 2 => { + SlotInterval::Aggregation => { if is_aggregator { coverage::emit_agg_start_new_coverage( &self.store, @@ -324,7 +365,7 @@ impl BlockChainServer { // ==== interval 3 ==== // // Safe-target update is handled inside `store::on_tick`. - 3 => {} + SlotInterval::SafeTargetUpdate => {} // ==== interval 4 ==== // @@ -335,7 +376,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) @@ -345,8 +386,6 @@ impl BlockChainServer { self.propose_block(next_slot, validator_id).await; } } - - _ => {} } // Update safe target slot metric (updated by store.on_tick at interval 3) diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 20f7cf5c..07e9e7c0 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -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, }; @@ -278,7 +278,7 @@ 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_slot_index(store.time() % INTERVALS_PER_SLOT); trace!(%slot, %interval, "processing tick"); @@ -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"), } } } From 49a8284544ec2a3e65b30586a96e5fcd1623f27f Mon Sep 17 00:00:00 2001 From: dicethedev Date: Fri, 3 Jul 2026 09:28:07 +0100 Subject: [PATCH 2/6] Revert "refactor: represent slot intervals with enum" This reverts commit aef1caea43acc466c6a01426b128cd598d909b29. --- crates/blockchain/src/lib.rs | 59 ++++++---------------------------- crates/blockchain/src/store.rs | 15 +++++---- 2 files changed, 18 insertions(+), 56 deletions(-) diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index e8d83dc4..8be7c89f 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, HashSet, VecDeque}; -use std::fmt; use std::time::{Duration, Instant, SystemTime}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; @@ -60,44 +59,6 @@ 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_slot_index(index: u64) -> Self { - match index { - 0 => Self::BlockPublication, - 1 => Self::AttestationProduction, - 2 => Self::Aggregation, - 3 => Self::SafeTargetUpdate, - 4 => Self::EndOfSlot, - _ => unreachable!("slots only have 5 intervals"), - } - } - - fn as_slot_index(self) -> u64 { - match self { - Self::BlockPublication => 0, - Self::AttestationProduction => 1, - Self::Aggregation => 2, - Self::SafeTargetUpdate => 3, - Self::EndOfSlot => 4, - } - } -} - -impl fmt::Display for SlotInterval { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_slot_index().fmt(f) - } -} - /// 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. @@ -231,9 +192,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 = SlotInterval::from_slot_index( - (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL, - ); + let interval = (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL; // Idempotency guard // @@ -293,7 +252,7 @@ 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 == SlotInterval::EndOfSlot + if interval == 4 && let Some(snapshot) = coverage::snapshot_new_payloads(&self.store) { self.pre_merge_coverage = Some(snapshot); @@ -301,7 +260,7 @@ impl BlockChainServer { // Whether one of our validators proposes this slot. Drives the store's // interval-0 attestation acceptance. - let is_proposer = (interval == SlotInterval::BlockPublication && slot > 0) + let is_proposer = (interval == 0 && slot > 0) .then(|| self.get_our_proposer(slot)) .flatten() .is_some(); @@ -321,14 +280,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. - SlotInterval::BlockPublication => {} + 0 => {} // ==== 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. - SlotInterval::AttestationProduction => { + 1 => { // 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 @@ -350,7 +309,7 @@ impl BlockChainServer { } // ==== interval 2 ==== - SlotInterval::Aggregation => { + 2 => { if is_aggregator { coverage::emit_agg_start_new_coverage( &self.store, @@ -365,7 +324,7 @@ impl BlockChainServer { // ==== interval 3 ==== // // Safe-target update is handled inside `store::on_tick`. - SlotInterval::SafeTargetUpdate => {} + 3 => {} // ==== interval 4 ==== // @@ -376,7 +335,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. - SlotInterval::EndOfSlot => { + 4 => { let next_slot = slot + 1; let next_proposer = self .get_our_proposer(next_slot) @@ -386,6 +345,8 @@ impl BlockChainServer { self.propose_block(next_slot, validator_id).await; } } + + _ => {} } // Update safe target slot metric (updated by store.on_tick at interval 3) diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 07e9e7c0..20f7cf5c 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -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, SlotInterval, + MILLISECONDS_PER_INTERVAL, MILLISECONDS_PER_SLOT, block_builder::{PostBlockCheckpoints, ProposerConfig, build_block}, metrics, }; @@ -278,7 +278,7 @@ 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 = SlotInterval::from_slot_index(store.time() % INTERVALS_PER_SLOT); + let interval = store.time() % INTERVALS_PER_SLOT; trace!(%slot, %interval, "processing tick"); @@ -292,26 +292,27 @@ 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 { - SlotInterval::BlockPublication => { + 0 => { // Start of slot - process attestations if proposal exists if should_signal_proposal { accept_new_attestations(store, false); } } - SlotInterval::AttestationProduction => { + 1 => { // Vote propagation — no action } - SlotInterval::Aggregation => { + 2 => { // Aggregation is driven by the actor (off-thread); nothing to do here. } - SlotInterval::SafeTargetUpdate => { + 3 => { // Update safe target for validators update_safe_target(store); } - SlotInterval::EndOfSlot => { + 4 => { // End of slot - accept accumulated attestations and log tree accept_new_attestations(store, true); } + _ => unreachable!("slots only have 5 intervals"), } } } From 4e37b0b950b04d65c19678e2181d74f03c8fe560 Mon Sep 17 00:00:00 2001 From: dicethedev Date: Fri, 3 Jul 2026 09:32:51 +0100 Subject: [PATCH 3/6] refactor: represent slot intervals with enum --- crates/blockchain/src/lib.rs | 59 ++++++++++++++++++++++++++++------ crates/blockchain/src/store.rs | 15 ++++----- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 8be7c89f..e8d83dc4 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet, VecDeque}; +use std::fmt; use std::time::{Duration, Instant, SystemTime}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; @@ -59,6 +60,44 @@ 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_slot_index(index: u64) -> Self { + match index { + 0 => Self::BlockPublication, + 1 => Self::AttestationProduction, + 2 => Self::Aggregation, + 3 => Self::SafeTargetUpdate, + 4 => Self::EndOfSlot, + _ => unreachable!("slots only have 5 intervals"), + } + } + + fn as_slot_index(self) -> u64 { + match self { + Self::BlockPublication => 0, + Self::AttestationProduction => 1, + Self::Aggregation => 2, + Self::SafeTargetUpdate => 3, + Self::EndOfSlot => 4, + } + } +} + +impl fmt::Display for SlotInterval { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_slot_index().fmt(f) + } +} + /// 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. @@ -192,7 +231,9 @@ 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_slot_index( + (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL, + ); // Idempotency guard // @@ -252,7 +293,7 @@ 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); @@ -260,7 +301,7 @@ impl BlockChainServer { // 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(); @@ -280,14 +321,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 @@ -309,7 +350,7 @@ impl BlockChainServer { } // ==== interval 2 ==== - 2 => { + SlotInterval::Aggregation => { if is_aggregator { coverage::emit_agg_start_new_coverage( &self.store, @@ -324,7 +365,7 @@ impl BlockChainServer { // ==== interval 3 ==== // // Safe-target update is handled inside `store::on_tick`. - 3 => {} + SlotInterval::SafeTargetUpdate => {} // ==== interval 4 ==== // @@ -335,7 +376,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) @@ -345,8 +386,6 @@ impl BlockChainServer { self.propose_block(next_slot, validator_id).await; } } - - _ => {} } // Update safe target slot metric (updated by store.on_tick at interval 3) diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 20f7cf5c..07e9e7c0 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -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, }; @@ -278,7 +278,7 @@ 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_slot_index(store.time() % INTERVALS_PER_SLOT); trace!(%slot, %interval, "processing tick"); @@ -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"), } } } From 31860e4d526f1466af7089278716a21235532a46 Mon Sep 17 00:00:00 2001 From: Blessing Samuel Date: Mon, 6 Jul 2026 11:49:16 +0100 Subject: [PATCH 4/6] Update crates/blockchain/src/store.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com> --- crates/blockchain/src/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 07e9e7c0..818b58b4 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -278,7 +278,7 @@ 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 = SlotInterval::from_slot_index(store.time() % INTERVALS_PER_SLOT); + let interval = SlotInterval::from_intervals_since_genesis(store.time()); trace!(%slot, %interval, "processing tick"); From d6d8c6e2248ebbcc35dd2ce310d269edf2cf2783 Mon Sep 17 00:00:00 2001 From: Blessing Samuel Date: Mon, 6 Jul 2026 11:49:26 +0100 Subject: [PATCH 5/6] Update crates/blockchain/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com> --- crates/blockchain/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index e8d83dc4..0c4ffa29 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -231,9 +231,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 = SlotInterval::from_slot_index( - (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL, - ); + let interval = SlotInterval::from_ms_since_genesis(time_since_genesis_ms); // Idempotency guard // From 044efdc947502dbd4b6860d8e48238e4d4bf0a9a Mon Sep 17 00:00:00 2001 From: dicethedev Date: Mon, 6 Jul 2026 11:56:52 +0100 Subject: [PATCH 6/6] refactor: centralize slot interval conversion --- crates/blockchain/src/lib.rs | 27 +++++++-------------------- crates/blockchain/src/store.rs | 2 +- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 0c4ffa29..32c2ac15 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, HashSet, VecDeque}; -use std::fmt; use std::time::{Duration, Instant, SystemTime}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; @@ -70,8 +69,12 @@ pub(crate) enum SlotInterval { } impl SlotInterval { - pub(crate) fn from_slot_index(index: u64) -> Self { - match index { + 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, @@ -80,22 +83,6 @@ impl SlotInterval { _ => unreachable!("slots only have 5 intervals"), } } - - fn as_slot_index(self) -> u64 { - match self { - Self::BlockPublication => 0, - Self::AttestationProduction => 1, - Self::Aggregation => 2, - Self::SafeTargetUpdate => 3, - Self::EndOfSlot => 4, - } - } -} - -impl fmt::Display for SlotInterval { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_slot_index().fmt(f) - } } /// Milliseconds until the next interval boundary, measured relative to genesis. @@ -245,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" diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 818b58b4..4b12bb3f 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -280,7 +280,7 @@ pub fn on_tick(store: &mut Store, timestamp_ms: u64, has_proposal: bool) { let slot = 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;