Skip to content

Commit a8af346

Browse files
committed
fix: address review comments from codex
1 parent e2ae1a9 commit a8af346

File tree

5 files changed

+44
-21
lines changed

5 files changed

+44
-21
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ requires-python = ">=3.12"
2929
dependencies = [
3030
"pydantic>=2.12.0,<3",
3131
"typing-extensions>=4.4",
32+
"lean-multisig-py>=0.1.0",
3233
]
3334

3435
[project.license]

src/lean_spec/subspecs/containers/block/block.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ def verify_signatures(
181181
):
182182
validator_ids = aggregated_attestation.aggregation_bits.to_validator_indices()
183183

184-
attestation_root = aggregated_attestation.data.data_root_bytes()
184+
attestation_data_root = aggregated_attestation.data.data_root_bytes()
185185

186-
# Verify the leanVM aggregated proof for this attestation
186+
# Verify the leanVM aggregated proof for this attestation data root
187187
for validator_id in validator_ids:
188188
# Ensure validator exists in the active set
189189
assert validator_id < Uint64(len(validators)), "Validator index out of range"
@@ -193,7 +193,7 @@ def verify_signatures(
193193
verify_aggregated_payload(
194194
public_keys=public_keys,
195195
payload=bytes(aggregated_signature),
196-
message=attestation_root,
196+
message=attestation_data_root,
197197
epoch=aggregated_attestation.data.slot,
198198
)
199199
except LeanMultisigError as exc:

src/lean_spec/subspecs/containers/state/state.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ def build_block(
735735
tuple[Uint64, bytes], list[tuple[AggregationBits, LeanAggregatedSignature]]
736736
]
737737
| None = None,
738+
_dropped_attestation_keys: set[tuple[Uint64, bytes]] | None = None,
738739
) -> tuple[Block, "State", list[Attestation], list[LeanAggregatedSignature]]:
739740
"""
740741
Build a valid block on top of this state.
@@ -761,12 +762,18 @@ def build_block(
761762
signatures.
762763
block_attestation_signatures: Map of (validator_id, data_root) to aggregated
763764
signature payloads.
765+
_dropped_attestation_keys: Internal accumulator tracking (validator_id, data_root)
766+
pairs that were proven unsignable during this build. Not intended for callers.
764767
765768
Returns:
766769
Tuple of (Block, post-State, collected attestations, signatures).
767770
"""
768771
# Initialize empty attestation set for iterative collection.
769772
attestations = list(attestations or [])
773+
if _dropped_attestation_keys is None:
774+
dropped_attestation_keys: set[tuple[Uint64, bytes]] = set()
775+
else:
776+
dropped_attestation_keys = _dropped_attestation_keys
770777

771778
# Iteratively collect valid attestations using fixed-point algorithm
772779
#
@@ -800,21 +807,9 @@ def build_block(
800807
data = attestation.data
801808
validator_id = attestation.validator_id
802809
data_root = data.data_root_bytes()
810+
cache_key = (validator_id, data_root)
803811

804-
# We can only include an attestation if we have *some* way to later provide
805-
# an aggregated payload for its attestation group:
806-
# - either a per-validator XMSS signature from gossip, or
807-
# - at least one aggregated payload learned from a block that references
808-
# this validator+data.
809-
has_gossip_sig = bool(
810-
gossip_attestation_signatures
811-
and gossip_attestation_signatures.get((validator_id, data_root)) is not None
812-
)
813-
has_block_payload = bool(
814-
block_attestation_signatures
815-
and block_attestation_signatures.get((validator_id, data_root))
816-
)
817-
if not (has_gossip_sig or has_block_payload):
812+
if cache_key in dropped_attestation_keys:
818813
continue
819814

820815
# Skip if target block is unknown
@@ -825,8 +820,25 @@ def build_block(
825820
if data.source != post_state.latest_justified:
826821
continue
827822

828-
# Add attestation if not already included
829-
if attestation not in attestations:
823+
# Avoid adding duplicates of attestations already in the candidate set
824+
if attestation in attestations:
825+
continue
826+
827+
# We can only include an attestation if we have some way to later provide
828+
# an aggregated payload for its group:
829+
# - either a per validator XMSS signature from gossip, or
830+
# - at least one aggregated payload learned from a block that references
831+
# this validator+data.
832+
has_gossip_sig = bool(
833+
gossip_attestation_signatures
834+
and gossip_attestation_signatures.get(cache_key) is not None
835+
)
836+
837+
has_block_payload = bool(
838+
block_attestation_signatures and block_attestation_signatures.get(cache_key)
839+
)
840+
841+
if has_gossip_sig or has_block_payload:
830842
new_attestations.append(attestation)
831843

832844
# Fixed point reached: no new attestations found
@@ -887,6 +899,14 @@ def build_block(
887899
pruned.extend(
888900
[Attestation(validator_id=vid, data=aggregated.data) for vid in subset]
889901
)
902+
if len(subset) < len(validator_ids):
903+
subset_set = set(subset)
904+
for vid in validator_ids:
905+
if vid not in subset_set:
906+
dropped_attestation_keys.add((vid, data_root))
907+
else:
908+
for vid in validator_ids:
909+
dropped_attestation_keys.add((vid, data_root))
890910

891911
# If pruning removed attestations, rerun once with the pruned set to keep
892912
# state_root consistent.
@@ -900,6 +920,7 @@ def build_block(
900920
known_block_roots=known_block_roots,
901921
gossip_attestation_signatures=gossip_attestation_signatures,
902922
block_attestation_signatures=block_attestation_signatures,
923+
_dropped_attestation_keys=dropped_attestation_keys,
903924
)
904925

905926
# Store the post state root in the block

src/lean_spec/subspecs/forkchoice/store.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,9 +571,8 @@ def on_block(
571571
# aggregated attestations, especially when we have aggregator roles.
572572
# This list can be recursively aggregated by the block proposer.
573573
key = (validator_id, data_root)
574-
existing = new_block_sigs.get(key)
575574
record = (participant_bits, aggregated_signature)
576-
new_block_sigs[key] = [record] if existing is None else (existing.append(record))
575+
new_block_sigs.setdefault(key, []).append(record)
577576

578577
# Import the attestation data into forkchoice for latest votes
579578
store = store.on_attestation(

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)