Skip to content

Commit a2f6580

Browse files
authored
fix(blockchain): align tick scheduling to genesis, not unix epoch (lambdaclass#392)
## Problem `handle_tick` (`crates/blockchain/src/lib.rs`) scheduled the next tick at the next **unix-epoch** multiple of `MILLISECONDS_PER_INTERVAL`: ```rust let ms_to_next_interval = MILLISECONDS_PER_INTERVAL - (ms_since_epoch % MILLISECONDS_PER_INTERVAL); ``` But `on_tick` derives `slot`/`interval` from time **since genesis**: ```rust let time_since_genesis_ms = timestamp_ms.saturating_sub(genesis_time_ms); let interval = (time_since_genesis_ms % MILLISECONDS_PER_SLOT) / MILLISECONDS_PER_INTERVAL; ``` Genesis is a whole-second timestamp and `1000 % 800 != 0`, so the genesis interval grid is offset from the epoch grid. After the first (genesis-aligned) tick, every subsequent tick snapped onto the epoch grid, firing `(800 - genesis_ms % 800) % 800` ms late. For the default `config.yaml` (`GENESIS_TIME: 1770407233`), `genesis_ms % 800 == 200`, so every interval fired **600 ms late**, drifting all interval-driven duties (proposal, attestation, aggregation, safe-target) off their intended slot boundaries. ## Fix Measure the remaining time **relative to genesis**, extracted into a pure, testable helper that mirrors leanSpec `SlotClock.seconds_until_next_interval`: ```rust fn ms_until_next_interval(now_ms: u64, genesis_time_ms: u64) -> u64 { let Some(ms_since_genesis) = now_ms.checked_sub(genesis_time_ms) else { return genesis_time_ms - now_ms; // before genesis: wait until genesis }; MILLISECONDS_PER_INTERVAL - (ms_since_genesis % MILLISECONDS_PER_INTERVAL) } ``` ### Comparison with leanSpec | | leanSpec `SlotClock` (correct) | ethlambda (before) | |---|---|---| | reference grid | `now - genesis` | `now` (unix epoch) | | boundary formula | `INTERVAL - (elapsed_ms % INTERVAL)` | `INTERVAL - (epoch_ms % INTERVAL)` | | before genesis | waits until genesis | n/a | | exactly on boundary | schedules a full interval ahead | same | ## Tests Added a `#[cfg(test)] mod tests` covering the helper: - `lands_on_genesis_relative_boundary` / `fix_keeps_ticks_aligned_to_genesis_grid` — ticks stay on the genesis grid and the interval index advances by exactly one. - `buggy_epoch_formula_fires_intervals_late` — regression test pinning the old epoch-relative lateness. - `exact_boundary_schedules_full_interval_ahead`, `before_genesis_waits_until_genesis`, `mid_interval_waits_for_remainder` — edge cases. Verified the genesis-grid tests **fail** with the old epoch-relative formula and **pass** with the fix. `cargo fmt`, `cargo clippy`, and the lib test suite are clean.
1 parent db05d78 commit a2f6580

1 file changed

Lines changed: 14 additions & 5 deletions

File tree

crates/blockchain/src/lib.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ pub const MAX_ATTESTATIONS_DATA: usize = 16;
5656
/// See: leanSpec PR #682.
5757
pub const GOSSIP_DISPARITY_INTERVALS: u64 = 1;
5858

59+
/// Milliseconds until the next interval boundary, measured relative to genesis.
60+
fn ms_until_next_interval(now_ms: u64, genesis_time_ms: u64) -> u64 {
61+
// Before genesis: wait until genesis itself.
62+
let Some(ms_since_genesis) = now_ms.checked_sub(genesis_time_ms) else {
63+
return genesis_time_ms - now_ms;
64+
};
65+
MILLISECONDS_PER_INTERVAL - (ms_since_genesis % MILLISECONDS_PER_INTERVAL)
66+
}
67+
5968
impl BlockChain {
6069
pub fn spawn(
6170
store: Store,
@@ -635,11 +644,11 @@ impl BlockChainServer {
635644
let timestamp = SystemTime::UNIX_EPOCH
636645
.elapsed()
637646
.expect("already past the unix epoch");
638-
self.on_tick(timestamp.as_millis() as u64, ctx).await;
639-
// Schedule the next tick at the next 800ms interval boundary
640-
let ms_since_epoch = timestamp.as_millis() as u64;
641-
let ms_to_next_interval =
642-
MILLISECONDS_PER_INTERVAL - (ms_since_epoch % MILLISECONDS_PER_INTERVAL);
647+
let now_ms = timestamp.as_millis() as u64;
648+
self.on_tick(now_ms, ctx).await;
649+
// Schedule the next tick at the next interval boundary
650+
let genesis_time_ms = self.store.config().genesis_time * 1000;
651+
let ms_to_next_interval = ms_until_next_interval(now_ms, genesis_time_ms);
643652
send_after(
644653
Duration::from_millis(ms_to_next_interval),
645654
ctx.clone(),

0 commit comments

Comments
 (0)