refactor: remove debug assert from proof aggregation#288
Conversation
`compact_attestations` and `extend_proofs_greedily` previously tracked attestations and signature proofs in parallel `Vec`s guarded by a `debug_assert_eq!` on length. Switching to `Vec<(att, proof)>` makes the invariant structural: the two lists can no longer drift out of sync, and the debug assertion is gone. Paired tuples flow end-to-end through `build_block`, unzipping only at the final SSZ boundary. Also eliminates an `AggregatedXMSS::clone()` per child inside `aggregate_mixed` / `aggregate_proofs`: the new `unzip` pattern lets us borrow pubkey slices (required by `xmss_aggregate`'s tuple type) while moving the large aggregate values into the child list. Removes the dead `to_children_refs` helper.
🤖 Kimi Code ReviewOverall Assessment: This is a high-quality refactor that eliminates a class of synchronization bugs between attestations and their signature proofs while improving performance in the XMSS aggregation layer. No critical issues found. crates/blockchain/src/store.rsSafety & Correctness
Consensus Logic Testing crates/common/crypto/src/lib.rsPerformance & Lifetimes Nitpick Conclusion: The changes are mechanically correct, improve memory safety guarantees through better type coupling, and optimize the XMSS hot path by removing unnecessary clones. Approved for merge. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code ReviewNo findings. The refactor in store.rs looks behavior-preserving: pairing Residual risk: I could not run the touched tests in this environment. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewNow I have enough context for a thorough review. Review: PR 288 —
|
Greptile SummaryThis PR refactors the proof aggregation pipeline by replacing parallel Confidence Score: 5/5Safe to merge; the refactoring is structurally correct and the only finding is a minor optimization opportunity. All changes are internally consistent: the paired-tuple invariant is correctly threaded end-to-end, the unzip pattern in the crypto layer correctly moves AggregatedXMSS values without cloning, and the borrow lifetimes are sound (pks_list outlives children_refs in the same scope). The one remaining comment (P2) is an avoidable ~253KB ByteListMiB clone per compacted group entry that could be eliminated with into_iter(); it does not affect correctness. No files require special attention; both changed files are safe.
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/store.rs | Replaces parallel Vec + Vec with Vec<(AggregatedAttestation, AggregatedSignatureProof)> throughout compact_attestations, extend_proofs_greedily, and build_block; unzips only at the final SSZ boundary. One minor optimization missed: proof_data.clone() in compact_attestations could be avoided. |
| crates/common/crypto/src/lib.rs | Replaces the old clone-heavy child-ref pattern in aggregate_mixed and aggregate_proofs with an unzip-and-rezip approach: deserialized children are unzipped into (pks_list, aggs), pks_list is borrowed as slices, and aggs are moved into children_refs — eliminating one AggregatedXMSS::clone() per child. Dead to_children_refs helper is removed. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["extend_proofs_greedily\n(proofs → selected: Vec<(Att,Proof)>)"] --> B["build_block loop\n(intermediate candidate: map att only)"]
B --> C{justification\nadvanced?}
C -->|yes| B
C -->|no| D["compact_attestations\nVec<(Att,Proof)> → Vec<(Att,Proof)>\naggregate_proofs inside"]
D --> E["unzip\n(attestations, signatures)"]
E --> F["Block body\n+ AggregatedSignatureProof list"]
subgraph crypto["crates/common/crypto"]
G["deserialize_children\nVec<(pubkeys, ByteListMiB)>"] --> H["into_iter().unzip()\n→ (pks_list, aggs)"]
H --> I["children_refs: Vec<(&[pk], AggregatedXMSS)>\nmove aggs, borrow pks_list"]
I --> J["xmss_aggregate"]
end
D -.calls.-> G
Comments Outside Diff (1)
-
crates/blockchain/src/store.rs, line 1233-1249 (link)proof_dataclone avoidable viainto_iter()group_itemsis borrowed formerged_bitson lines 1226–1229 (the fold completes before this block), then borrowed again here with.iter(), forcing aByteListMiB::clone()(~253 KB in production) per group entry. Sincegroup_itemsis no longer needed after this point, switching to.into_iter()would moveproof_datainstead of cloning it.Prompt To Fix With AI
This is a comment left during a code review. Path: crates/blockchain/src/store.rs Line: 1233-1249 Comment: **`proof_data` clone avoidable via `into_iter()`** `group_items` is borrowed for `merged_bits` on lines 1226–1229 (the fold completes before this block), then borrowed *again* here with `.iter()`, forcing a `ByteListMiB::clone()` (~253 KB in production) per group entry. Since `group_items` is no longer needed after this point, switching to `.into_iter()` would move `proof_data` instead of cloning it. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 1233-1249
Comment:
**`proof_data` clone avoidable via `into_iter()`**
`group_items` is borrowed for `merged_bits` on lines 1226–1229 (the fold completes before this block), then borrowed *again* here with `.iter()`, forcing a `ByteListMiB::clone()` (~253 KB in production) per group entry. Since `group_items` is no longer needed after this point, switching to `.into_iter()` would move `proof_data` instead of cloning it.
```suggestion
let children: Vec<(Vec<_>, _)> = group_items
.into_iter()
.map(|(_, proof)| {
let pubkeys = proof
.participant_indices()
.map(|vid| {
head_state
.validators
.get(vid as usize)
.ok_or(StoreError::InvalidValidatorIndex)?
.get_attestation_pubkey()
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
})
.collect::<Result<Vec<_>, _>>()?;
Ok((pubkeys, proof.proof_data))
})
.collect::<Result<Vec<_>, StoreError>>()?;
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "refactor: pair (attestation, proof) tupl..." | Re-trigger Greptile
compact_attestationsandextend_proofs_greedilypreviously tracked attestations and signature proofs in parallelVecs guarded by adebug_assert_eq!on length. Switching toVec<(att, proof)>makes the invariant structural: the two lists can no longer drift out of sync, and the debug assertion is gone. Paired tuples flow end-to-end throughbuild_block, unzipping only at the final SSZ boundary.Also eliminates an
AggregatedXMSS::clone()per child insideaggregate_mixed/aggregate_proofs: the newunzippattern lets us borrow pubkey slices (required byxmss_aggregate's tuple type) while moving the large aggregate values into the child list. Removes the deadto_children_refshelper.