Skip to content

Commit f34e81c

Browse files
committed
fix(blockchain): drop the proposer's interval-0 attestation accept
With block proposal moved to interval 4 of the previous slot, the interval-0 accept_new_attestations fired after the block had already been built and published: too late to be included in it, and it diverged the proposer's fork-choice view from the one its block closed over. Accepting should happen immediately before the build, which the unconditional interval-4 accept already does (it runs at the top of the same tick, just before propose_block). Comment out the interval-0 accept and its gate. has_proposal is now unused but kept in the signature (discarded via `let _`) so re-enabling needs no call-site change.
1 parent e48f43c commit f34e81c

1 file changed

Lines changed: 24 additions & 8 deletions

File tree

crates/blockchain/src/store.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ fn validate_attestation_data(store: &Store, data: &AttestationData) -> Result<()
255255
/// slot = store.time() / INTERVALS_PER_SLOT
256256
/// interval = store.time() % INTERVALS_PER_SLOT
257257
pub fn on_tick(store: &mut Store, timestamp_ms: u64, has_proposal: bool) {
258+
// `has_proposal` currently has no effect: it gated the interval-0 attestation
259+
// accept that is now disabled (see the interval 0 arm below). Kept in the
260+
// signature so re-enabling that path needs no call-site change.
261+
let _ = has_proposal;
262+
258263
// Convert UNIX timestamp (ms) to interval count since genesis
259264
let genesis_time_ms = store.config().genesis_time * 1000;
260265
let time_delta_ms = timestamp_ms.saturating_sub(genesis_time_ms);
@@ -274,21 +279,32 @@ pub fn on_tick(store: &mut Store, timestamp_ms: u64, has_proposal: bool) {
274279

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

277-
// has_proposal is only signaled for the final tick (matching Python spec behavior)
278-
let is_final_tick = store.time() == time;
279-
let should_signal_proposal = has_proposal && is_final_tick;
280-
281282
// NOTE: here we assume on_tick never skips intervals.
282283
// Interval 2 (committee-signature aggregation) is no longer handled here:
283284
// the blockchain actor orchestrates the aggregation worker directly so
284285
// the actor's message loop stays unblocked during the expensive XMSS
285286
// proofs. See `BlockChainServer::start_aggregation_session` in `lib.rs`.
286287
match interval {
287288
0 => {
288-
// Start of slot - process attestations if proposal exists
289-
if should_signal_proposal {
290-
accept_new_attestations(store, false);
291-
}
289+
// Interval-0 attestation acceptance is intentionally disabled.
290+
//
291+
// It used to promote pending attestations (new -> known) and
292+
// refresh the head at the start of the slot, so the proposer's
293+
// block — built at interval 0 — closed over the freshest votes.
294+
// The proposer now builds and publishes its block at interval 4
295+
// of the PREVIOUS slot, so by the time we reach interval 0 the
296+
// block is already out. Promoting here would accept attestations
297+
// only AFTER the block was built: too late to be included, and
298+
// it would diverge the proposer's fork-choice view from the one
299+
// its block closed over. We want the accept immediately BEFORE
300+
// the build, which is exactly what the unconditional interval-4
301+
// accept below does — it runs at the top of the same tick, just
302+
// before `propose_block`. (`has_proposal` is thus no longer read.)
303+
//
304+
// let is_final_tick = store.time() == time;
305+
// if has_proposal && is_final_tick {
306+
// accept_new_attestations(store, false);
307+
// }
292308
}
293309
1 => {
294310
// Vote propagation — no action

0 commit comments

Comments
 (0)