refactor: update store to key by AttestationData#656
Conversation
…nstead of SignatureKey (closes #636) Implements leanEthereum/leanSpec#436: - Replace SignatureKey(validator_id, data_root) -> StoredSignature map with AttestationData -> HashMap(ValidatorIndex, StoredSignature) (SignaturesMap) - Replace SignatureKey -> AggregatedPayloadsList map with AttestationData -> AggregatedPayloadsList (AggregatedPayloadsMap) - Remove attestation_data_by_root reverse-lookup map (no longer needed) - Remove SignatureKey type (replaced by AttestationData as key) - Update computeAggregatedSignatures Phase 1 to look up by AttestationData - Update computeAggregatedSignatures Phase 2 greedy set-cover to use per-data candidate list instead of per-validator lookup - Update pruneStaleAttestationData to iterate gossip_signatures directly and prune by target.slot (renamed to prunePayloadMapBySlot) - Update all call sites: forkchoice.zig, fork_choice_runner.zig, mock.zig, block_signatures_testing.zig
…key after store refactor
anshalshukla
left a comment
There was a problem hiding this comment.
@zclawz address the comments
pkgs/types/src/block.zig
Outdated
| pub const GossipSignaturesInnerMap = std.AutoHashMap(ValidatorIndex, StoredSignature); | ||
|
|
||
| /// Map type for gossip signatures: AttestationData -> per-validator signatures. | ||
| /// Replaces the old SignatureKey(validator_id, data_root) -> StoredSignature design. |
There was a problem hiding this comment.
Remove this comment, we don't need to know the older map structure
pkgs/types/src/block.zig
Outdated
| /// Map type for aggregated payloads: SignatureKey -> list of AggregatedSignatureProof | ||
| pub const AggregatedPayloadsMap = std.AutoHashMap(SignatureKey, AggregatedPayloadsList); | ||
| /// Map type for aggregated payloads: AttestationData -> list of AggregatedSignatureProof. | ||
| /// Replaces the old SignatureKey(validator_id, data_root) -> AggregatedPayloadsList design. |
There was a problem hiding this comment.
Remove this comment, we don't need to know the older map structure
pkgs/types/src/block.zig
Outdated
|
|
||
| // Phase 2: Fallback to aggregated_payloads using greedy set-cover | ||
| // Phase 2: Fallback to aggregated_payloads using greedy set-cover. | ||
| // Candidates are now keyed by AttestationData directly (not per-validator). |
There was a problem hiding this comment.
Remove this comment, we don't need to know the older map structure
pkgs/node/src/forkchoice.zig
Outdated
| is_from_block: bool, | ||
| ) !void { | ||
| const data_root = try attestation_data.sszRoot(self.allocator); | ||
| _ = validator_ids; // No longer needed: payloads are keyed by AttestationData, not per-validator |
There was a problem hiding this comment.
remove it from the function argument itself
pkgs/node/src/forkchoice.zig
Outdated
| self.signatures_mutex.lock(); | ||
| defer self.signatures_mutex.unlock(); |
There was a problem hiding this comment.
earlier lock was scoped and the lock will be released as soon as the code section ends we should stay with that pattern and return lock as soon as the map has been written to
pkgs/types/src/block.zig
Outdated
| /// Map type for signatures_map: SignatureKey -> individual XMSS signature bytes + slot metadata | ||
| pub const SignaturesMap = std.AutoHashMap(SignatureKey, StoredSignature); | ||
| /// Inner map: ValidatorIndex -> StoredSignature for a given AttestationData. | ||
| pub const GossipSignaturesInnerMap = std.AutoHashMap(ValidatorIndex, StoredSignature); |
There was a problem hiding this comment.
make it a struct with the put and deinit methods, there are multiple places where we do multiple checks of initialization before writing that can be abstracted out, and iterating over the map of map can be easily missed so having a deinit function is better
- Remove 'Replaces the old...' and 'not per-validator' comments from
block.zig (three sites); reviewer noted these transition comments are
not needed in the final code.
- Make GossipSignaturesInnerMap a proper struct wrapping AutoHashMap
with init/put/get/deinit/iterator/count methods, so callers don't
need to manage the inner map lifecycle and the initialization pattern
(getOrPut + init if not found) stays consistent across all sites.
- Remove validator_ids parameter from storeAggregatedPayload — payloads
are now keyed by AttestationData so the param was unused. Update all
call sites: chain.zig (×2), forkchoice.zig test helper,
fork_choice_runner.zig.
- Scope signatures_mutex in storeAggregatedPayload using a {} block
with defer unlock, releasing the lock as soon as the map write
completes. Consistent with the scoped-lock pattern used everywhere
else in the file.
pkgs/node/src/chain.zig
Outdated
| /// Thin wrapper around validateAttestationData for callers that have a full Attestation. | ||
| pub fn validateAttestation(self: *Self, attestation: types.Attestation, is_from_block: bool) !void { | ||
| return self.validateAttestationData(attestation.data, is_from_block); | ||
| } | ||
|
|
There was a problem hiding this comment.
we don't need a wrapper function directly call validateAttestationData with attestation.data to validate full Attestation @zclawz
…; remove validateAttestation wrapper aggregateCommitteeSignaturesUnlocked / computeAggregatedSignatures: The old flow built a flat attestation list from gossip_signatures to pass as the first arg to computeAggregatedSignatures, which then re-grouped it by AttestationData. Since gossip_signatures is already keyed by AttestationData → GossipSignaturesInnerMap this round-trip was pure overhead. New flow: - computeAggregatedSignatures(validators, signatures_map, aggregated_payloads) drops the attestations_list parameter entirely. - Iterates the *union* of signatures_map and aggregated_payloads keys so groups that only exist in aggregated_payloads (individual sigs pruned but stored proof available) are still processed. - Phase 1 iterates the inner map directly (validator_id → stored_sig) with no intermediate grouping or root-index lookup. - Before Phase 2 greedy set-cover, seeds from proof participants not already in sigmap_available so proofs covering validators absent from signatures_map are still included. aggregateCommitteeSignaturesUnlocked (forkchoice.zig): - Drops the attestations ArrayList build loop entirely. - Calls computeAggregatedSignatures directly on gossip_signatures. chain.zig: - Removes getProposalAttestations() call (was only needed as the first arg); aggregation now reads gossip_signatures directly. - Removes validateAttestation() thin wrapper (review comment #r2955207656); callers should use validateAttestationData directly. All call sites updated: chain.zig, mock.zig, block_signatures_testing.zig.
|
Thanks for the review @anshalshukla! All feedback addressed:
Changes pushed — please re-review! 🙏 |
Port 14 files that don't conflict with #656 store refactor: - Dual keys: validator.zig, utils.zig, state.zig, configs/lib.zig - AggregatedSignatureProof.aggregate(): aggregation.zig - SignedBlock refactor: transition.zig, lib.zig, zk.zig - Proposer signing: validator_client.zig - Testing/infra: testing.zig, network.zig, database, state_transition_runner
- ValidatorKeys struct with attestation_keypair + proposal_keypair
- getAllPubkeys returns AllPubkeys{attestation_pubkeys, proposal_pubkeys}
- signBlockRoot for proposer block signing
- getAttestationPubkeyBytes / getProposalPubkeyBytes
- Retains #656 owned_keys tracking
- Replace SignatureKey/flat SignaturesMap with #656 wrapper (AttestationData -> InnerMap(ValidatorIndex -> StoredSignature)) - AggregatedPayloadsMap keyed by AttestationData (not Root) - Retain devnet4 AggregatedAttestationsResult + extendProofsGreedily - Add AggregateInnerMap helper from #656 (adapted for devnet4 aggregate) - SignedBlock refactoring (flattened, no BlockWithAttestation) - block_signatures_testing.zig needs further adaptation (TODO)
…ckages Propagate the #656 store refactor naming changes to all consumer files: - SignedBlockWithAttestation → SignedBlock, BlockWithAttestation removed - .message.block.field → .message.field (flattened block structure) - Validator.pubkey → attestation_pubkey + proposal_pubkey (dual keys) - GenesisSpec.validator_pubkeys → validator_attestation_pubkeys - key_manager.getAllPubkeys() returns AllPubkeys{attestation, proposal} - signAttestation → signBlockRoot for proposer signatures - Removed proposer_attestation from block processing - aggregateCommitteeSignatures → aggregate(state, false)
- Remove 'Replaces the old...' and 'not per-validator' comments from
block.zig (three sites); reviewer noted these transition comments are
not needed in the final code.
- Make GossipSignaturesInnerMap a proper struct wrapping AutoHashMap
with init/put/get/deinit/iterator/count methods, so callers don't
need to manage the inner map lifecycle and the initialization pattern
(getOrPut + init if not found) stays consistent across all sites.
- Remove validator_ids parameter from storeAggregatedPayload — payloads
are now keyed by AttestationData so the param was unused. Update all
call sites: chain.zig (×2), forkchoice.zig test helper,
fork_choice_runner.zig.
- Scope signatures_mutex in storeAggregatedPayload using a {} block
with defer unlock, releasing the lock as soon as the map write
completes. Consistent with the scoped-lock pattern used everywhere
else in the file.
Port 14 files that don't conflict with #656 store refactor: - Dual keys: validator.zig, utils.zig, state.zig, configs/lib.zig - AggregatedSignatureProof.aggregate(): aggregation.zig - SignedBlock refactor: transition.zig, lib.zig, zk.zig - Proposer signing: validator_client.zig - Testing/infra: testing.zig, network.zig, database, state_transition_runner
- ValidatorKeys struct with attestation_keypair + proposal_keypair
- getAllPubkeys returns AllPubkeys{attestation_pubkeys, proposal_pubkeys}
- signBlockRoot for proposer block signing
- getAttestationPubkeyBytes / getProposalPubkeyBytes
- Retains #656 owned_keys tracking
- Replace SignatureKey/flat SignaturesMap with #656 wrapper (AttestationData -> InnerMap(ValidatorIndex -> StoredSignature)) - AggregatedPayloadsMap keyed by AttestationData (not Root) - Retain devnet4 AggregatedAttestationsResult + extendProofsGreedily - Add AggregateInnerMap helper from #656 (adapted for devnet4 aggregate) - SignedBlock refactoring (flattened, no BlockWithAttestation) - block_signatures_testing.zig needs further adaptation (TODO)
…ckages Propagate the #656 store refactor naming changes to all consumer files: - SignedBlockWithAttestation → SignedBlock, BlockWithAttestation removed - .message.block.field → .message.field (flattened block structure) - Validator.pubkey → attestation_pubkey + proposal_pubkey (dual keys) - GenesisSpec.validator_pubkeys → validator_attestation_pubkeys - key_manager.getAllPubkeys() returns AllPubkeys{attestation, proposal} - signAttestation → signBlockRoot for proposer signatures - Removed proposer_attestation from block processing - aggregateCommitteeSignatures → aggregate(state, false)
- Remove 'Replaces the old...' and 'not per-validator' comments from
block.zig (three sites); reviewer noted these transition comments are
not needed in the final code.
- Make GossipSignaturesInnerMap a proper struct wrapping AutoHashMap
with init/put/get/deinit/iterator/count methods, so callers don't
need to manage the inner map lifecycle and the initialization pattern
(getOrPut + init if not found) stays consistent across all sites.
- Remove validator_ids parameter from storeAggregatedPayload — payloads
are now keyed by AttestationData so the param was unused. Update all
call sites: chain.zig (×2), forkchoice.zig test helper,
fork_choice_runner.zig.
- Scope signatures_mutex in storeAggregatedPayload using a {} block
with defer unlock, releasing the lock as soon as the map write
completes. Consistent with the scoped-lock pattern used everywhere
else in the file.
Port 14 files that don't conflict with #656 store refactor: - Dual keys: validator.zig, utils.zig, state.zig, configs/lib.zig - AggregatedSignatureProof.aggregate(): aggregation.zig - SignedBlock refactor: transition.zig, lib.zig, zk.zig - Proposer signing: validator_client.zig - Testing/infra: testing.zig, network.zig, database, state_transition_runner
- ValidatorKeys struct with attestation_keypair + proposal_keypair
- getAllPubkeys returns AllPubkeys{attestation_pubkeys, proposal_pubkeys}
- signBlockRoot for proposer block signing
- getAttestationPubkeyBytes / getProposalPubkeyBytes
- Retains #656 owned_keys tracking
- Replace SignatureKey/flat SignaturesMap with #656 wrapper (AttestationData -> InnerMap(ValidatorIndex -> StoredSignature)) - AggregatedPayloadsMap keyed by AttestationData (not Root) - Retain devnet4 AggregatedAttestationsResult + extendProofsGreedily - Add AggregateInnerMap helper from #656 (adapted for devnet4 aggregate) - SignedBlock refactoring (flattened, no BlockWithAttestation) - block_signatures_testing.zig needs further adaptation (TODO)
…ckages Propagate the #656 store refactor naming changes to all consumer files: - SignedBlockWithAttestation → SignedBlock, BlockWithAttestation removed - .message.block.field → .message.field (flattened block structure) - Validator.pubkey → attestation_pubkey + proposal_pubkey (dual keys) - GenesisSpec.validator_pubkeys → validator_attestation_pubkeys - key_manager.getAllPubkeys() returns AllPubkeys{attestation, proposal} - signAttestation → signBlockRoot for proposer signatures - Removed proposer_attestation from block processing - aggregateCommitteeSignatures → aggregate(state, false)
76cc969 to
8c6e076
Compare
pkgs/node/src/chain.zig
Outdated
| var processed_att_data = std.AutoHashMap(types.AttestationData, void).init(self.allocator); | ||
| defer processed_att_data.deinit(); | ||
|
|
||
| while (true) { |
There was a problem hiding this comment.
this logic needs to move to forkchoice.getProposalAttestations and it can take state as input
There was a problem hiding this comment.
Done — moved the greedy fixed-point selection loop into forkchoice.getProposalAttestations(pre_state, slot, proposer_index, parent_root). chain.produceBlock now just calls that and gets back the AggregatedAttestations + AttestationSignatures. The naive []Attestation version is replaced. Build + tests pass.
…posalAttestations Move the fixed-point greedy proof selection loop from chain.produceBlock into forkchoice.getProposalAttestations, which now takes pre_state, slot, proposer_index, and parent_root as inputs. This encapsulates the attestation selection strategy inside the fork choice module, where it belongs. chain.produceBlock now simply calls forkChoice.getProposalAttestations and uses the returned AggregatedAttestations + AttestationSignatures.
Port 14 files that don't conflict with #656 store refactor: - Dual keys: validator.zig, utils.zig, state.zig, configs/lib.zig - AggregatedSignatureProof.aggregate(): aggregation.zig - SignedBlock refactor: transition.zig, lib.zig, zk.zig - Proposer signing: validator_client.zig - Testing/infra: testing.zig, network.zig, database, state_transition_runner
- ValidatorKeys struct with attestation_keypair + proposal_keypair
- getAllPubkeys returns AllPubkeys{attestation_pubkeys, proposal_pubkeys}
- signBlockRoot for proposer block signing
- getAttestationPubkeyBytes / getProposalPubkeyBytes
- Retains #656 owned_keys tracking
- Replace SignatureKey/flat SignaturesMap with #656 wrapper (AttestationData -> InnerMap(ValidatorIndex -> StoredSignature)) - AggregatedPayloadsMap keyed by AttestationData (not Root) - Retain devnet4 AggregatedAttestationsResult + extendProofsGreedily - Add AggregateInnerMap helper from #656 (adapted for devnet4 aggregate) - SignedBlock refactoring (flattened, no BlockWithAttestation) - block_signatures_testing.zig needs further adaptation (TODO)
…ckages Propagate the #656 store refactor naming changes to all consumer files: - SignedBlockWithAttestation → SignedBlock, BlockWithAttestation removed - .message.block.field → .message.field (flattened block structure) - Validator.pubkey → attestation_pubkey + proposal_pubkey (dual keys) - GenesisSpec.validator_pubkeys → validator_attestation_pubkeys - key_manager.getAllPubkeys() returns AllPubkeys{attestation, proposal} - signAttestation → signBlockRoot for proposer signatures - Removed proposer_attestation from block processing - aggregateCommitteeSignatures → aggregate(state, false)
Closes #636
Implements leanEthereum/leanSpec#436.
Summary
Old design
gossip_signatures:SignatureKey(validator_id, data_root) → StoredSignatureaggregated_payloads:SignatureKey(validator_id, data_root) → AggregatedPayloadsListattestation_data_by_root:Root → AttestationData(reverse lookup needed to reconstruct data)New design
gossip_signatures:AttestationData → HashMap(ValidatorIndex, StoredSignature)aggregated_payloads:AttestationData → AggregatedPayloadsListattestation_data_by_rootremoved — data is now the key, no reverse lookup neededChanges
SignatureKeytypeattestation_data_by_rootfield fromForkChoiceSignaturesMapto nestedAttestationData → inner ValidatorIndex mapAggregatedPayloadsMapkey fromSignatureKeytoAttestationDatacomputeAggregatedSignaturesPhase 1 lookup (byAttestationData+ValidatorIndex)computeAggregatedSignaturesPhase 2 greedy set-cover to use single per-data candidate listpruneStaleAttestationDatanow iterates directly bytarget.slotwithout needing a separate root setforkchoice.zig,fork_choice_runner.zig,mock.zig,block_signatures_testing.zigTesting
zig buildpasseszig build testpasses (pre-existing IoUring/xev environment failures unrelated to these changes)