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
| 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
| /// 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
|
|
||
| // 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
| 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
| 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
| /// 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.
| /// 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
| 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)
* fix: validateAttestationData refactor + validate aggregated attestation (closes #654) * refactor: update store - key signatures/payloads by AttestationData instead 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 * fix: resolve CI failure - update tests to use AttestationData as map key after store refactor * refactor: address PR #656 review comments - 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. * refactor: eliminate attestation_list from computeAggregatedSignatures; 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. * fix: resolve CI failure - apply zig fmt to chain.zig (trailing blank line) * improve code and simplify aggregation * optimize blcok building * add checks in block production * refactor: move greedy proposal attestation logic to forkchoice.getProposalAttestations 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. --------- Co-authored-by: zclawz <zclawz@blockblaz.io> Co-authored-by: zclawz <zclawz@openclaw.ai> Co-authored-by: Anshal Shukla <53994948+anshalshukla@users.noreply.github.com> Co-authored-by: anshalshukla <shukla.anshal85@gmail.com> Co-authored-by: zclawz <zclawz@users.noreply.github.com>
* feat: port devnet4 non-overlapping files from gr/devnet4 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 * feat: port dual keypairs key-manager from devnet4 - 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 * feat: port block.zig with #656 SignaturesMap + devnet4 aggregation - 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) * feat: apply SignedBlock rename and dual-key refactoring across all packages 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) * fix: update tests for dual-key Validator struct changes - Update expected state root hash in genesis root comparison test - Update config.yaml fixture to use attestation_pubkey/proposal_pubkey format - Fix attestation_signatures field access in chain.zig test * fix: correct metric names and field references after rebase - Fix gossip_signatures → attestation_signatures in forkchoice.zig - Fix lean_pq_sig_attestation_signatures → lean_pq_sig_aggregated_signatures metric name in chain.zig * fix: memory leak in AggregateInnerMap — participants not freed after aggregate() aggregate() reads xmss_participants but does not consume them (it creates its own merged bitfield internally). The errdefer + participants_cleanup=false pattern prevented cleanup on the success path, leaking the AggregationBits. Fix: use unconditional defer instead of guarded errdefer. * chore: update leanSpec and lean-quickstart submodule pointers Update leanSpec to a5df895 (includes devnet4 dual-key fixtures) and lean-quickstart to match the devnet4 branch requirements. * refactor: align naming with leanSpec #449 and fix SignedBlock flattening - Rename aggregateCommitteeSignatures → aggregate, maybeAggregateCommitteeSignaturesOnInterval → maybeAggregateOnInterval - Rename validator_pubkeys → validator_attestation_pubkeys in comments - Remove stale "signed proposer attestation" references in logs - Fix signed_block.message.block → signed_block.message (SignedBlock flattening) * refactor: align with leanSpec a5df895..d0c5030 - Rename SignedBlock.message → SignedBlock.block (#465) - Add genesis special case for current_justified_root in block production (#464) - Update leanSpec submodule to d0c5030 * fix: correct fork choice spec test attestation handling - Register block body attestations with fork choice via onAttestation() to mirror chain.zig onBlock behavior for correct LMD-GHOST weights - Remove fabricated proposer gossip attestation that injected phantom votes not present in leanSpec fixtures, causing head slot mismatches - Remove safe target regression guard that incorrectly errored on legitimate safe target decreases during reorg scenarios - Add latestJustifiedRoot and latestJustifiedRootLabel check handlers * xmss, multisig-glue, hashsig-glue: implement recursive aggregate with devnet4 scheme - Update multisig-glue to use rec_aggregation from leanMultisig with children support (child pub keys, child proofs, log_inv_rate) - Update hashsig-glue to leansig_wrapper with devnet4 aborting hypercube scheme (V=46, Poseidon1) - Update Zig FFI layer (xmss/aggregation.zig) with children + log_inv_rate - Pass children pub keys and INVERSE_PROOF_SIZE=2 through aggregation.zig - Wire recursive child resolution in block.zig for block building - Fix SIGSIZE from 3112 to 2536 to match V=46 signature size * multisig-glue: add target-cpu=native for leanMultisig SIMD compat * build: use CARGO_ENCODED_RUSTFLAGS for leanMultisig SIMD compat leanMultisig's mt-koala-bear crate uses compile-time #[cfg(target_feature)] for SIMD dispatch (AVX2/AVX512/NEON). The previous .cargo/config.toml approach was ineffective because CI sets RUSTFLAGS=-D warnings via environment, which causes Cargo to ignore config.toml rustflags entirely. Switch to CARGO_ENCODED_RUSTFLAGS which has highest precedence and is not clobbered by the RUSTFLAGS environment variable. * build: limit CARGO_ENCODED_RUSTFLAGS to x86_64 only ring 0.17 fails compile-time feature assertions on aarch64-apple-darwin when target-cpu=native is set. The SIMD dispatch fix is only needed on x86_64 for leanMultisig's AVX2/AVX512 codepaths. Also include -Dwarnings in the encoded flags so CI warning-as-error behavior is preserved. * fix: address review comments from anshalshukla - Restore safe target regression check in forkchoice - Remove recursive flag, make recursive aggregation default - Remove dead code (attestationsFromAttestationSignatures, non-recursive branches) - Use inverse proof size 1 in tests to speed up CI - Remove stale bytecode_point TODO (removed from spec) - Clarify comment on duplicate keypair loading (FFI handle, no clone API) * fix: point Rust deps to upstream leanEthereum/leanMultisig devnet4 branch Switch hashsig-glue and multisig-glue from anshalshukla's fork to the upstream leanEthereum/leanMultisig PR #181 (devnet4 branch), which has the same leansig_wrapper API and includes the latest Poseidon changes. * fix: regenerate Cargo.lock to resolve Plonky3 p3-field version conflict After switching deps to leanEthereum/leanMultisig devnet4 branch, the old Cargo.lock had three conflicting p3-field versions causing leansig_wrapper to fail with as_canonical_u32 not found errors. Running cargo update resolves the dependency graph correctly. * fix: address review comments from anshalshukla - Add loadValidatorKeysFromFiles() to key-manager to encapsulate the dual-keypair loading pattern (attestation + proposal from same files) - Simplify cli/node.zig to use the new function instead of duplicating the loadKeypairFromFiles + error handling logic - Simplify loadPreGeneratedKey to delegate to the new function * fix: address remaining review comments from anshalshukla - Consolidate keypair loading to read files once (key-manager) - Migrate hashsig-glue to direct leansig + leansig_fast_keygen with SSZ serialization - Update multisig-glue to upstream leanEthereum rev - Refactor computeAggregatedSignatures to derive keys from maps directly - Remove attestationsFromGossipAndNewPayloads helper function - Scope signatures_mutex to map-access block only (forkchoice) - Rename INVERSE_PROOF_SIZE to LOG_INV_RATE_TEST/LOG_INV_RATE_PROD - Change FFI children_proofs from pointer to value type - Use LOG_INV_RATE_TEST constant in testing - Remove old pubkey fallback in spectest runners - Fix comment numbering in chain.zig and validator_client.zig * style: fix cargo fmt lint in hashsig-glue * style: fix zig fmt lint in forkchoice.zig * feat: compact attestations per AttestationData during block building Implement leanSpec PR #510 ("single attestation data"): - Add compactAttestations() in block.zig to merge multiple proofs sharing the same AttestationData into one via recursive children aggregation - Call compaction after greedy selection in forkchoice.zig - Add duplicate AttestationData validation in chain.zig onBlock - Re-export compactAttestations in lib.zig * feat: limit distinct AttestationData entries per block to MAX_ATTESTATIONS_DATA Implements leanSpec PR #536. Adds MAX_ATTESTATIONS_DATA=8 constant, enforces limit during block building (break when reached), and validates incoming blocks reject those exceeding the limit. * refactor: unify leansig dependency, remove leansig_fast_keygen fork leanEthereum/leanSig devnet4 branch now exports SecretKey types directly, so the TomWambsgans/leanSig fast-keygen fork is no longer needed. This removes unsafe transmutes between crate types and unifies the Cargo.lock to a single leansig entry shared with leanMultisig's transitive dep. * chore: update leanMultisig to fd8814045deb to fix lean_vm MemoryAlreadySet panic * chore: update test-keys submodule with regenerated keys Update to aec0971 which has keys regenerated using the unified leansig dependency (leanEthereum/leanSig devnet4). Fixes lean_vm MemoryAlreadySet panic in CI tests. * address nit picks --------- Co-authored-by: anshalshukla <shukla.anshal85@gmail.com> Co-authored-by: Anshal Shukla <53994948+anshalshukla@users.noreply.github.com>
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)