@@ -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
0 commit comments