feat(aggregation): score interval-2 jobs like the block builder#526
Conversation
🤖 Kimi Code ReviewOverall Assessment: High-quality PR. The refactoring correctly unifies scoring logic between block building and aggregation, fixes a critical chain-view bug for current-head attestations, and properly prevents XMSS double-inclusion. Good test coverage. Critical Consensus Correctness
Code Correctness & Edge Cases
Performance & Resource Management
Rust Best Practices
Code Structure
Minor Suggestions
Security Summary: No critical vulnerabilities introduced. The XMSS double-inclusion guard (Item 2) and chain-view fix (Item 1) are correctly implemented. The Approval: Approve with minor suggestion to handle duplicate validator IDs explicitly in Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
I did not run tests locally: Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR replaces interval-2 aggregation with a tiered greedy job selector. The main changes are:
Confidence Score: 5/5The latest changes look safe to merge.
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/aggregation.rs | Adds candidate resolution, chain filtering, projected-state scoring, capped greedy selection, and unit tests. |
| crates/blockchain/src/lib.rs | Routes interval-2 aggregation through the new slot-aware snapshot selector. |
| crates/blockchain/Cargo.toml | Adds test dependencies used to generate aggregation test signatures. |
| Cargo.lock | Records the new direct test dependencies for the blockchain crate. |
Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/scored-agg..." | Re-trigger Greptile
2bb7ac7 to
0277f5c
Compare
0594893 to
26a262f
Compare
Pull the round-by-round projection and candidate scoring out of select_attestations into ProjectedState methods: from_head_state() seeds it, advance() folds a selected entry's voters and moves justification/finalization forward, and score_entry() scores a candidate from its realized `coverage` against the projection (reading current_votes and finalized_slot from &self rather than as parameters). Widen ProjectedState with its methods, entry_passes_filters, Tier, EntryScore, OrderingKey, and EntryScore::ordering_key to pub(crate). Block-building behavior is unchanged: the method bodies are the former inline logic (advance()'s current_votes update simply moves after the trace it never fed), and pick_best_candidate now unions proof participants into `coverage` before scoring, the exact set the old score_entry derived internally. This exposes the projection and scoring primitives so interval-2 aggregation can reuse them instead of duplicating the logic.
0277f5c to
607d19f
Compare
26a262f to
ee9e06e
Compare
Move entry_passes_filters onto ProjectedState alongside score_entry: it reads justified_slots and finalized_slot from &self and still takes the chain view (known_block_roots, extended_historical_block_hashes) as arguments. Callers become projected.entry_passes_filters(att_data, known_roots, hist). Behavior-preserving.
ee9e06e to
630679e
Compare
The interval-2 session aggregated current-slot gossip groups in arbitrary order and ignored existing proofs, so it could not prioritize the groups whose aggregation most advances consensus. Rework snapshot_aggregation_inputs into a tiered greedy selector modeled on select_attestations: an up-front store pass resolves each candidate's material once (raw-first + trim), then a loop scores candidates by (current-slot-first, Finalize > Justify > Build) against an optimistically-projected state, emitting at most MAX_AGGREGATION_JOBS jobs. Candidates are filtered by the block builder's entry_passes_filters against a chain view covering [0, head_slot] (head root pushed onto the state's historical_block_hashes, which omits the head's own root), so prover time is spent only on aggregations a block could actually pack. Reuses the block builder's ProjectedState in full (from_head_state, advance, score_entry, and entry_passes_filters), so proposer and aggregator can never drift on the justify/finalize projection, candidate filtering, or tiering. The aggregator validates each candidate with projected.entry_passes_filters and scores its realized raw + child coverage with projected.score_entry. Deletes snapshot_current_slot_aggregation_inputs and build_raw_signature_job (subsumed). Builds on the block_builder refactor (ProjectedState owning projection + scoring).
630679e to
cd01561
Compare
🤖 Kimi Code ReviewThe PR introduces a tiered greedy selector for attestation aggregation that mirrors the block builder's logic, with proper handling of raw signatures vs. existing proofs to prevent double inclusion. Overall this is a solid refactor with good test coverage, but there are a few issues to address: Critical Issues1. Panic risk on store operations (lines 232, 247) // Line 232
let known_block_roots = store.get_block_roots().expect("block roots read works");
// Line 247
extended_historical_block_hashes.push(store.head().expect("head read works"));Suggestion: Convert to proper error handling or Security & Correctness2. Double inclusion prevention (lines 367-375) 3. Seed coverage in proof selection (line 622) Performance & Code Quality4. Unnecessary clone in candidate removal (line 283) let Candidate { hashed, resolved } = candidates
.remove(&data_root)
.expect("picked candidate exists in pool");Since 5. Test-only dependencies properly gated Consensus Logic6. Current head extension (lines 243-250) 7. Slot bucket prioritization (line 308) let slot_bucket: u8 = if att_data.slot == current_slot { 0 } else { 1 };Current-slot attestations correctly precede stale ones. This aligns with the block builder's priority and prevents interval-2 aggregation from wasting time on old slots when current-slot work is pending. Testing8. Comprehensive test coverage
Minor nit: In SummaryThe PR is technically sound and fixes a real consensus bug (the current head chain view issue). The main blocker is the use of Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
I couldn’t run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
…gationJob The interval-2 selector kept a separate Candidate (hashed + ResolvedMaterial) during scoring and converted the winner into an AggregationJob only at the end. AggregationJob already carries `hashed` plus every material field, and the one extra ResolvedMaterial field, `coverage`, is exactly `raw_ids ∪ accepted_child_ids`. resolve_job now builds an AggregationJob directly and `coverage` is a derived method on it, so the selector scores and emits a single type end to end.
What
Rework
snapshot_aggregation_inputsinto a tiered greedy selector modeled onthe block builder's
select_attestations: an up-front store pass resolves eachcandidate's aggregation material once (raw-first + trim), then a loop scores
candidates by (current-slot-first, Finalize > Justify > Build) against an
optimistically-projected state, emitting at most
MAX_AGGREGATION_JOBSjobs.Why
The interval-2 session aggregated current-slot gossip groups in arbitrary order
and ignored existing proofs, so it could not prioritize the groups whose
aggregation most advances consensus.
How
entry_passes_filtersagainst achain view covering
[0, head_slot](the head root is pushed onto the state'shistorical_block_hashes, which omits the head's own root), so prover time isspent only on aggregations a block could actually pack.
ProjectedState(from_head_state+advance) andTier/EntryScore/entry_passes_filtersvia a coverage-basedscore_from_coveragecore, so proposer and aggregator can never drift oneither the justify/finalize projection or the tiering.
snapshot_current_slot_aggregation_inputsandbuild_raw_signature_job(subsumed).Stacking
Stacked on #525 (the block builder refactor). Review/merge that first; GitHub
retargets this PR to
mainonce it lands.Checks
cargo fmtcleancargo clippy --all-targets -- -D warningscleancargo test -p ethlambda-blockchain --libgreen (incl. resolve_job / pick_best_candidate / snapshot tests)