Skip to content
Merged
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
53 changes: 41 additions & 12 deletions crates/blockchain/src/block_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//! seal `state_root`.

use std::{
cmp::Reverse,
collections::{HashMap, HashSet},
time::Instant,
};
Expand Down Expand Up @@ -237,7 +238,7 @@ fn pick_best_candidate(
projected: &ProjectedState,
) -> Option<(H256, EntryScore, HashSet<u64>)> {
let mut best: Option<(H256, EntryScore, HashSet<u64>)> = None;
let mut best_key: Option<(Tier, std::cmp::Reverse<usize>, u64, u64, H256)> = None;
let mut best_key: Option<OrderingKey> = None;

for (data_root, (att_data, proofs)) in chain.aggregated_payloads {
if processed_data_roots.contains(data_root) {
Expand Down Expand Up @@ -436,9 +437,17 @@ enum Tier {
/// Lower `tier` wins. Entries with zero new voters relative to the running
/// per-target-root voter set are dropped (returned as `None`).
///
/// Within a tier, ordering prefers more `new_voters` (descending), then
/// smaller `target_slot` (older chain progress first), then smaller
/// `att_slot`, then the entry's `data_root` for determinism.
/// The within-tier ordering is tier-dependent (leanSpec PR #1149):
///
/// - **Finalize / Justify**: the entry already crosses 2/3 on its target, so
/// newer chain progress leads: larger `target_slot`, then larger `att_slot`,
/// then more `new_voters`. Pushing the justified slot as far forward as
/// possible shortens recovery from a justification or finalization stall.
/// - **Build**: the entry only adds marginal voters toward the threshold, so
/// coverage leads: more `new_voters`, then larger `target_slot`, then larger
/// `att_slot`.
///
/// In both tiers `data_root` (ascending) is the final deterministic tiebreak.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EntryScore {
tier: Tier,
Expand All @@ -447,15 +456,35 @@ struct EntryScore {
att_slot: u64,
}

/// Total order over candidate entries; the smallest value is the best pick.
/// `tier` leads, then three tier-dependent `Reverse`-encoded priorities, then
/// `data_root` as the deterministic tiebreak. See [`EntryScore::ordering_key`].
type OrderingKey = (Tier, Reverse<u64>, Reverse<u64>, Reverse<u64>, H256);

impl EntryScore {
fn ordering_key(&self, data_root: H256) -> (Tier, std::cmp::Reverse<usize>, u64, u64, H256) {
(
self.tier,
std::cmp::Reverse(self.new_voters),
self.target_slot,
self.att_slot,
data_root,
)
/// Sort key where the smallest tuple is the best candidate. `tier` always
/// leads; the remaining three slots carry tier-dependent priorities (see
/// the type-level docs), all encoded as `Reverse` so "larger is better".
fn ordering_key(&self, data_root: H256) -> OrderingKey {
let more_new_voters = Reverse(self.new_voters as u64);
let newer_target = Reverse(self.target_slot);
let newer_att = Reverse(self.att_slot);
match self.tier {
Tier::Build => (
self.tier,
more_new_voters,
newer_target,
newer_att,
data_root,
),
Tier::Finalize | Tier::Justify => (
self.tier,
newer_target,
newer_att,
more_new_voters,
data_root,
),
}
}
}

Expand Down