11# beacon_chain
2- # Copyright (c) 2018-2024 Status Research & Development GmbH
2+ # Copyright (c) 2018-2025 Status Research & Development GmbH
33# Licensed and distributed under either of
44# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
88{.push raises : [].}
99
1010import
11- std/ algorithm,
1211 # Status libraries
1312 metrics,
1413 chronicles, stew/ byteutils,
1918 ../ fork_choice/ fork_choice,
2019 ../ beacon_clock
2120
21+ from std/ algorithm import sort
2222from std/ sequtils import keepItIf, maxIndex
2323
2424export blockchain_dag, fork_choice
6666 # # voted on different states - this map keeps track of each vote keyed by
6767 # # getAttestationCandidateKey()
6868
69+ CandidateIdxType {.pure .} = enum
70+ phase0Idx
71+ electraIdx
72+
6973 AttestationPool * = object
7074 # # The attestation pool keeps track of all attestations that potentially
7175 # # could be added to a block during block production.
@@ -198,11 +202,12 @@ proc addForkChoiceVotes(
198202 # hopefully the fork choice will heal itself over time.
199203 error " Couldn't add attestation to fork choice, bug?" , err = v.error ()
200204
201- func candidateIdx (pool: AttestationPool , slot: Slot ,
202- isElectra: bool = false ): Opt [int ] =
205+ func candidateIdx (
206+ pool: AttestationPool , slot: Slot , candidateIdxType: CandidateIdxType ):
207+ Opt [int ] =
203208 static : doAssert pool.phase0Candidates.len == pool.electraCandidates.len
204209
205- let poolLength = if isElectra :
210+ let poolLength = if candidateIdxtype == CandidateIdxType .electraIdx :
206211 pool.electraCandidates.lenu64 else : pool.phase0Candidates.lenu64
207212
208213 if slot >= pool.startingSlot and
@@ -413,6 +418,11 @@ proc addAttestation(
413418
414419 true
415420
421+ func getAttestationCandidateKey (
422+ attestationDataRoot: Eth2Digest , committee_index: CommitteeIndex ):
423+ Eth2Digest =
424+ hash_tree_root ([attestationDataRoot, hash_tree_root (committee_index.uint64 )])
425+
416426func getAttestationCandidateKey (
417427 data: AttestationData ,
418428 committee_index: Opt [CommitteeIndex ]): Eth2Digest =
@@ -424,13 +434,7 @@ func getAttestationCandidateKey(
424434 # i.e. no committees selected, so it can't be an actual Electra attestation
425435 hash_tree_root (data)
426436 else :
427- hash_tree_root ([hash_tree_root (data),
428- hash_tree_root (committee_index.get.uint64 )])
429-
430- func getAttestationCandidateKey (
431- attestationDataRoot: Eth2Digest , committee_index: CommitteeIndex ):
432- Eth2Digest =
433- hash_tree_root ([attestationDataRoot, hash_tree_root (committee_index.uint64 )])
437+ getAttestationCandidateKey (hash_tree_root (data), committee_index.get)
434438
435439proc addAttestation * (
436440 pool: var AttestationPool ,
@@ -450,7 +454,14 @@ proc addAttestation*(
450454
451455 updateCurrent (pool, wallTime.slotOrZero)
452456
453- let candidateIdx = pool.candidateIdx (attestation.data.slot)
457+ when kind (typeof (attestation)) == ConsensusFork .Electra :
458+ let candidateIdx = pool.candidateIdx (
459+ attestation.data.slot, CandidateIdxType .electraIdx)
460+ elif kind (typeof (attestation)) == ConsensusFork .Phase0 :
461+ let candidateIdx = pool.candidateIdx (
462+ attestation.data.slot, CandidateIdxType .phase0Idx)
463+ else :
464+ static : doAssert false
454465 if candidateIdx.isNone:
455466 debug " Skipping old attestation for block production" ,
456467 startingSlot = pool.startingSlot
@@ -464,14 +475,20 @@ proc addAttestation*(
464475 # creating an unnecessary AttestationEntry on the hot path and avoiding
465476 # multiple lookups
466477 template addAttToPool (attCandidates: untyped , entry: untyped , committee_index: untyped ) =
467- let attestation_data_root = getAttestationCandidateKey (entry.data, committee_index)
468-
469- attCandidates[candidateIdx.get ()].withValue (attestation_data_root, entry) do :
478+ # `AttestationData.index == 0` in Electra, but the attestation pool always
479+ # represents an AttestationEntry regardless as having the actual committee
480+ # index. The entry, therefore, is not the same as the AttestationData, and
481+ # thus cannot function as the basis for deriving the hashtable key for the
482+ # entry. Instead use the (correctly data.index == 0) attestation passed to
483+ # addAttestation.
484+ let candidate_key = getAttestationCandidateKey (attestation.data, committee_index)
485+
486+ attCandidates[candidateIdx.get ()].withValue (candidate_key, entry) do :
470487 if not addAttestation (entry[], attestation, index_in_committee, signature):
471488 return
472489 do :
473490 if not addAttestation (
474- attCandidates[candidateIdx.get ()].mgetOrPut (attestation_data_root , entry),
491+ attCandidates[candidateIdx.get ()].mgetOrPut (candidate_key , entry),
475492 attestation, index_in_committee, signature):
476493 # Returns from overall function, not only template
477494 return
@@ -540,7 +557,7 @@ func covers*(
540557 # # the existing aggregates, making it redundant
541558 # # the `var` attestation pool is needed to use `withValue`, else Table becomes
542559 # # unusably inefficient
543- let candidateIdx = pool.candidateIdx (data.slot)
560+ let candidateIdx = pool.candidateIdx (data.slot, CandidateIdxType .phase0Idx )
544561 if candidateIdx.isNone:
545562 return false
546563
@@ -558,7 +575,7 @@ func covers*(
558575 # # the existing aggregates, making it redundant
559576 # # the `var` attestation pool is needed to use `withValue`, else Table becomes
560577 # # unusably inefficient
561- let candidateIdx = pool.candidateIdx (data.slot)
578+ let candidateIdx = pool.candidateIdx (data.slot, CandidateIdxType .electraIdx )
562579 if candidateIdx.isNone:
563580 return false
564581
@@ -593,7 +610,8 @@ iterator attestations*(
593610 committee_index: Opt [CommitteeIndex ]): phase0.Attestation =
594611 let candidateIndices =
595612 if slot.isSome ():
596- let candidateIdx = pool.candidateIdx (slot.get ())
613+ let candidateIdx = pool.candidateIdx (
614+ slot.get (), CandidateIdxType .phase0Idx)
597615 if candidateIdx.isSome ():
598616 candidateIdx.get () .. candidateIdx.get ()
599617 else :
@@ -622,7 +640,8 @@ iterator electraAttestations*(
622640 committee_index: Opt [CommitteeIndex ]): electra.Attestation =
623641 let candidateIndices =
624642 if slot.isSome ():
625- let candidateIdx = pool.candidateIdx (slot.get (), true )
643+ let candidateIdx = pool.candidateIdx (
644+ slot.get (), CandidateIdxType .electraIdx)
626645 if candidateIdx.isSome ():
627646 candidateIdx.get () .. candidateIdx.get ()
628647 else :
@@ -795,7 +814,7 @@ proc getAttestationsForBlock*(pool: var AttestationPool,
795814
796815 let
797816 slot = Slot (maxAttestationSlot - i)
798- candidateIdx = pool.candidateIdx (slot)
817+ candidateIdx = pool.candidateIdx (slot, CandidateIdxType .phase0Idx )
799818
800819 if candidateIdx.isNone ():
801820 # Passed the collection horizon - shouldn't happen because it's based on
@@ -931,7 +950,7 @@ proc getElectraAttestationsForBlock*(
931950
932951 let
933952 slot = Slot (maxAttestationSlot - i)
934- candidateIdx = pool.candidateIdx (slot)
953+ candidateIdx = pool.candidateIdx (slot, CandidateIdxType .electraIdx )
935954
936955 if candidateIdx.isNone ():
937956 # Passed the collection horizon - shouldn't happen because it's based on
@@ -1096,7 +1115,7 @@ func getElectraAggregatedAttestation*(
10961115 Opt [electra.Attestation ] =
10971116
10981117 let
1099- candidateIdx = pool.candidateIdx (slot)
1118+ candidateIdx = pool.candidateIdx (slot, CandidateIdxType .electraIdx )
11001119 if candidateIdx.isNone:
11011120 return Opt .none (electra.Attestation )
11021121
@@ -1124,7 +1143,7 @@ func getElectraAggregatedAttestation*(
11241143 # be used here, because otherwise they wouldn't have the same value. It thus
11251144 # leaves the cross-committee aggregation for getElectraAttestationsForBlock,
11261145 # which does do this.
1127- let candidateIdx = pool.candidateIdx (slot)
1146+ let candidateIdx = pool.candidateIdx (slot, CandidateIdxType .electraIdx )
11281147 if candidateIdx.isNone:
11291148 return Opt .none (electra.Attestation )
11301149
@@ -1147,7 +1166,7 @@ func getPhase0AggregatedAttestation*(
11471166 pool: var AttestationPool , slot: Slot , attestation_data_root: Eth2Digest ):
11481167 Opt [phase0.Attestation ] =
11491168 let
1150- candidateIdx = pool.candidateIdx (slot)
1169+ candidateIdx = pool.candidateIdx (slot, CandidateIdxType .phase0Idx )
11511170 if candidateIdx.isNone:
11521171 return Opt .none (phase0.Attestation )
11531172
@@ -1168,7 +1187,7 @@ func getPhase0AggregatedAttestation*(
11681187 # # Select the attestation that has the most votes going for it in the given
11691188 # # slot/index
11701189 # # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#construct-aggregate
1171- let candidateIdx = pool.candidateIdx (slot)
1190+ let candidateIdx = pool.candidateIdx (slot, CandidateIdxType .phase0Idx )
11721191 if candidateIdx.isNone:
11731192 return Opt .none (phase0.Attestation )
11741193
0 commit comments