Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,15 @@ AllTests-mainnet
```diff
+ pre-1.1.0 OK
```
## Payload attestation pool [Preset: mainnet]
```diff
+ Can add and retrieve payload attestations [Preset: mainnet] OK
+ Can get payload attestations for block production [Preset: mainnet] OK
+ Different payload presence values [Preset: mainnet] OK
+ Duplicate validator in PTC - multiple signatures [Preset: mainnet] OK
+ Multiple validators in PTC can attest [Preset: mainnet] OK
+ Payload attestations get pruned [Preset: mainnet] OK
```
## PeerPool testing suite
```diff
+ Access peers by key test OK
Expand Down
171 changes: 171 additions & 0 deletions beacon_chain/consensus_object_pools/payload_attestation_pool.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# beacon_chain
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [], gcsafe.}

import
# Status libraries
metrics,
chronicles,
# Internal
../spec/[eth2_merkleization, forks, validator],
"."/[spec_cache, blockchain_dag],
../beacon_clock

from ../spec/beaconstate import get_ptc_list

logScope: topics = "payattpool"

declareGauge payload_attestation_pool_block_packing_time,
"Time it took to create list of payload attestations for block"

type
PayloadAttestationEntry = object
data: PayloadAttestationData
messages: Table[ValidatorIndex, PayloadAttestationMessage]
aggregated: Opt[PayloadAttestation]

PayloadAttestationPool* = object
dag*: ChainDAGRef
attestations: Table[Slot, Table[Eth2Digest, PayloadAttestationEntry]]

func init*(T: type PayloadAttestationPool, dag: ChainDAGRef): T =
T(dag: dag)

func pruneOldEntries(pool: var PayloadAttestationPool, wallTime: BeaconTime) =
let current_slot = wallTime.slotOrZero(pool.dag.timeParams)

# keep only recent slots - since payload attestations
# are only valid for 1 slot
var slotsToRemove: seq[Slot]
for slot in pool.attestations.keys:
if slot + 2 < current_slot:
slotsToRemove.add(slot)

for slot in slotsToRemove:
pool.attestations.del(slot)

proc addPayloadAttestation*(
pool: var PayloadAttestationPool, message: PayloadAttestationMessage,
wallTime: BeaconTime): bool =
let
slot = message.data.slot
beacon_block_root = message.data.beacon_block_root
validator_index = message.validator_index

pool.pruneOldEntries(wallTime)

# create an entry for this block and slot
let
slotEntries = addr pool.attestations.mgetOrPut(
slot, initTable[Eth2Digest, PayloadAttestationEntry]())
entry = addr slotEntries[].mgetOrPut(
beacon_block_root, PayloadAttestationEntry(data: message.data))

# Check for duplicate
let vidx = ValidatorIndex(validator_index)
if vidx in entry[].messages:
return false

entry[].messages[vidx] = message

entry[].aggregated = Opt.none(PayloadAttestation)

true

proc aggregateMessages(
pool: PayloadAttestationPool, slot: Slot,
entry: var PayloadAttestationEntry, cache: var StateCache
): Opt[PayloadAttestation] =

if entry.messages.len == 0:
return Opt.none(PayloadAttestation)

withState(pool.dag.headState):
when consensusFork >= ConsensusFork.Gloas:
var
aggregation_bits: BitArray[int(PTC_SIZE)]
signatures: seq[CookedSig]
ptc_index = 0

let ptc_list = get_ptc_list(forkyState.data, slot, cache)
for ptc_validator_index in ptc_list:
entry.messages.withValue(ptc_validator_index, message):
let cookedSig = message[].signature.load().valueOr:
continue
aggregation_bits[ptc_index] = true
signatures.add(cookedSig)
ptc_index += 1

if signatures.len == 0:
return Opt.none(PayloadAttestation)

var aggregated_signature = AggregateSignature.init(signatures[0])
for i in 1..<signatures.len:
aggregated_signature.aggregate(signatures[i])

Opt.some(PayloadAttestation(
aggregation_bits: aggregation_bits,
data: entry.data,
signature: aggregated_signature.finish().toValidatorSig()
))
else:
Opt.none(PayloadAttestation)

proc getAggregatedPayloadAttestation*(
pool: var PayloadAttestationPool, slot: Slot,
beacon_block_root: Eth2Digest, cache: var StateCache
): Opt[PayloadAttestation] =
## Get aggregated payload attestation for a specific block and slot

pool.attestations.withValue(slot, slotEntries):
slotEntries[].withValue(beacon_block_root, entry):
if entry[].aggregated.isNone():
entry[].aggregated = pool.aggregateMessages(slot, entry[], cache)
return entry[].aggregated

Opt.none(PayloadAttestation)

proc getPayloadAttestationsForBlock*(
pool: var PayloadAttestationPool, target_slot: Slot,
cache: var StateCache): seq[PayloadAttestation] =
## Get payload attestations to include in a block for a target slot
let startPackingTick = Moment.now()

if target_slot == 0:
return @[]

let attestation_slot = target_slot - 1
var
payload_attestations: seq[PayloadAttestation]
totalCandidates = 0

if attestation_slot notin pool.attestations:
return @[]

pool.attestations.withValue(attestation_slot, slotEntries):
for beacon_block_root, entry in slotEntries[]:
totalCandidates += 1
let aggregated =
pool.getAggregatedPayloadAttestation(
attestation_slot, beacon_block_root, cache)
if aggregated.isSome():
payload_attestations.add(aggregated.get())

if payload_attestations.len >= MAX_PAYLOAD_ATTESTATIONS.int:
break

let packingDur = Moment.now() - startPackingTick

debug "Packed payload attestations for block",
target_slot = target_slot, attestation_slot = attestation_slot,
packingDur = packingDur, totalCandidates = totalCandidates,
payload_attestations = payload_attestations.len()

payload_attestation_pool_block_packing_time.set(packingDur.toFloatSeconds())

payload_attestations
6 changes: 6 additions & 0 deletions beacon_chain/spec/beaconstate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2782,6 +2782,12 @@ iterator get_ptc(state: gloas.BeaconState, slot: Slot, cache: var StateCache):
state, indices, seed, size=PTC_SIZE, shuffle_indices=false):
yield candidate_index

func get_ptc_list*(state: gloas.BeaconState, slot: Slot,
cache: var StateCache): seq[ValidatorIndex] =
# workaround for https://github.com/nim-lang/Nim/issues/25287
# TODO: Remove if issue gets fixed and use iterator directly
toSeq(get_ptc(state, slot, cache))

# https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.6/specs/gloas/beacon-chain.md#new-get_indexed_payload_attestation
func get_indexed_payload_attestation*(
state: gloas.BeaconState, slot: Slot,
Expand Down
1 change: 1 addition & 0 deletions tests/all_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import # Unit test
./test_light_client_processor,
./test_light_client,
./test_network_metadata,
./test_payload_attestation_pool,
./test_peer_pool,
./test_peerdas_helpers,
./test_remote_keystore,
Expand Down
Loading
Loading