Skip to content

refactor: update store to key by AttestationData - #656

Merged
anshalshukla merged 17 commits into
mainfrom
feat/update-store-636
Mar 25, 2026
Merged

anshalshukla merged 17 commits into
mainfrom
feat/update-store-636

Conversation

@zclawz

@zclawz zclawz commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Closes #636

Implements leanEthereum/leanSpec#436.

Summary

Old design

  • gossip_signatures: SignatureKey(validator_id, data_root) → StoredSignature
  • aggregated_payloads: SignatureKey(validator_id, data_root) → AggregatedPayloadsList
  • attestation_data_by_root: Root → AttestationData (reverse lookup needed to reconstruct data)

New design

  • gossip_signatures: AttestationData → HashMap(ValidatorIndex, StoredSignature)
  • aggregated_payloads: AttestationData → AggregatedPayloadsList
  • attestation_data_by_root removed — data is now the key, no reverse lookup needed

Changes

  • Remove SignatureKey type
  • Remove attestation_data_by_root field from ForkChoice
  • Update SignaturesMap to nested AttestationData → inner ValidatorIndex map
  • Update AggregatedPayloadsMap key from SignatureKey to AttestationData
  • Update computeAggregatedSignatures Phase 1 lookup (by AttestationData + ValidatorIndex)
  • Update computeAggregatedSignatures Phase 2 greedy set-cover to use single per-data candidate list
  • pruneStaleAttestationData now iterates directly by target.slot without needing a separate root set
  • All call sites updated: forkchoice.zig, fork_choice_runner.zig, mock.zig, block_signatures_testing.zig

Testing

  • zig build passes
  • zig build test passes (pre-existing IoUring/xev environment failures unrelated to these changes)

zclawz and others added 4 commits March 10, 2026 14:15
…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

@anshalshukla anshalshukla left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zclawz address the comments

Comment thread 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment, we don't need to know the older map structure

Comment thread 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment, we don't need to know the older map structure

Comment thread 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).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this comment, we don't need to know the older map structure

Comment thread 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove it from the function argument itself

Comment thread pkgs/node/src/forkchoice.zig Outdated
Comment on lines +1219 to +1220
self.signatures_mutex.lock();
defer self.signatures_mutex.unlock();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread 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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Comment thread pkgs/node/src/chain.zig Outdated
Comment on lines +1430 to +1434
/// 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);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@zclawz

zclawz commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @anshalshukla! All feedback addressed:

  • validateAttestation() wrapper removed — callers use validateAttestationData(attestation.data, ...) directly
  • Mutex lock in storeAggregatedPayload is now scoped ({} block) so it releases immediately after the map write
  • Cleaned up stale comments about the old SignatureKey/data_root design
  • GossipSignaturesInnerMap now a proper struct with put/get/iterator/deinit
  • Removed unused validator_ids param

Changes pushed — please re-review! 🙏

GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
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
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
- 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
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
- 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)
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
…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)
GrapeBaBa pushed a commit that referenced this pull request Mar 20, 2026
- 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.
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
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
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
- 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
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
- 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)
GrapeBaBa added a commit that referenced this pull request Mar 20, 2026
…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)
GrapeBaBa pushed a commit that referenced this pull request Mar 21, 2026
- 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.
GrapeBaBa added a commit that referenced this pull request Mar 21, 2026
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
GrapeBaBa added a commit that referenced this pull request Mar 21, 2026
- 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
GrapeBaBa added a commit that referenced this pull request Mar 21, 2026
- 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)
GrapeBaBa added a commit that referenced this pull request Mar 21, 2026
…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)
@anshalshukla
anshalshukla force-pushed the feat/update-store-636 branch from 76cc969 to 8c6e076 Compare March 23, 2026 18:15
Comment thread 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic needs to move to forkchoice.getProposalAttestations and it can take state as input

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

zclawz and others added 3 commits March 23, 2026 18:41
…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.
@anshalshukla
anshalshukla merged commit 2cc06ef into main Mar 25, 2026
8 checks passed
@anshalshukla
anshalshukla deleted the feat/update-store-636 branch March 25, 2026 20:18
GrapeBaBa added a commit that referenced this pull request Mar 26, 2026
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
GrapeBaBa added a commit that referenced this pull request Mar 26, 2026
- 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
GrapeBaBa added a commit that referenced this pull request Mar 26, 2026
- 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)
GrapeBaBa added a commit that referenced this pull request Mar 26, 2026
…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)
GrapeBaBa pushed a commit that referenced this pull request Apr 2, 2026
* 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>
g11tech pushed a commit that referenced this pull request Apr 9, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update store

3 participants