diff --git a/CHANGELOG.md b/CHANGELOG.md index f41141b4dc1d..34840f558cd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ ### Fixed +- [#7412](https://github.com/ChainSafe/forest/issues/7412): Fixes quicknet "unchained" logic to fetch the `max_beacon_round` for all covered epochs + ## Forest v0.35.0 "Shravan" Non-mandatory release for all node operators. It includes some fixes and improvements, notably around state-related RPC. Note that this release contains breaking changes, so please read the changelog carefully before upgrading. diff --git a/src/beacon/drand.rs b/src/beacon/drand.rs index ad41db5ddfcc..61ebddbca410 100644 --- a/src/beacon/drand.rs +++ b/src/beacon/drand.rs @@ -88,10 +88,12 @@ impl BeaconSchedule { if cb_epoch != pb_epoch { // Fork logic, take entries from the last two rounds of the new beacon. let round = curr_beacon.max_beacon_round_for_epoch(network_version, epoch); - let mut entries = Vec::with_capacity(2); - entries.push(curr_beacon.entry(round - 1).await?); - entries.push(curr_beacon.entry(round).await?); - return Ok(entries); + + let out = vec![ + curr_beacon.entry(round - 1).await?, + curr_beacon.entry(round).await?, + ]; + return Ok(out); } } @@ -113,13 +115,15 @@ impl BeaconSchedule { prev.round() }; - // We only ever need one entry after drand quicknet upgrade (FIP-0063) + let mut out = Vec::with_capacity(2); if curr_beacon.network().is_unchained() { - let entry = curr_beacon.entry(max_round).await?; - Ok(vec![entry]) + for covered_epoch in (parent_epoch + 1)..=epoch { + let round = curr_beacon.max_beacon_round_for_epoch(network_version, covered_epoch); + out.push(curr_beacon.entry(round).await?); + } + Ok(out) } else { let mut cur = max_round; - let mut out = Vec::new(); while cur > prev_round { // Push all entries from rounds elapsed since the last chain epoch. let entry = curr_beacon.entry(cur).await?; diff --git a/src/beacon/tests/drand.rs b/src/beacon/tests/drand.rs index 350b8d2399ff..1b0fbdfb2509 100644 --- a/src/beacon/tests/drand.rs +++ b/src/beacon/tests/drand.rs @@ -1,8 +1,13 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use itertools::Itertools; + use crate::{ - beacon::{Beacon, ChainInfo, DrandBeacon, DrandConfig, DrandNetwork}, + beacon::{ + Beacon, BeaconEntry, BeaconPoint, BeaconSchedule, ChainInfo, DrandBeacon, DrandConfig, + DrandNetwork, + }, shim::version::NetworkVersion, }; use std::borrow::Cow; @@ -144,3 +149,33 @@ fn test_max_beacon_round_for_epoch_quicknet() { ((1598306400 + 3547000 * 30) - 1692803367 - 30) / 3 + 1 ); } + +#[tokio::test] +async fn beacon_entries_for_block_covers_null_rounds_quicknet() { + // (parent epoch, its beacon round, block epoch, expected rounds) + let cases = [ + // Null round at 6216199: entries for both 6216199 and 6216200. + (6216198, 30662982, 6216200, vec![30662992, 30663002]), + // No null round in between: only 6216200's entry. + (6216199, 30662992, 6216200, vec![30663002]), + ]; + + let schedule = BeaconSchedule(vec![BeaconPoint::new(0, new_beacon_quicknet())]); + + for (prev_epoch, prev_epoch_round, epoch, expected_rounds) in cases { + let (_, prev_beacon) = schedule.beacon_for_epoch(prev_epoch).unwrap(); + let prev_beacon_entry = prev_beacon.entry(prev_epoch_round).await.unwrap(); + + let entries = schedule + .beacon_entries_for_block(NetworkVersion::V22, epoch, prev_epoch, &prev_beacon_entry) + .await + .unwrap(); + + let rounds = entries.iter().map(BeaconEntry::round).collect_vec(); + + assert_eq!( + rounds, expected_rounds, + "epoch {epoch}, parent {prev_epoch}" + ); + } +}