Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update Electra attestation and aggregation gossip validation #6917

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Changes from all 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
83 changes: 62 additions & 21 deletions beacon_chain/gossip_processing/gossip_validation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1085,31 +1085,74 @@ proc validateAttestation*(
return pool.checkedResult(v.error)
v.get()

if attestation.attester_index > high(ValidatorIndex).uint64:
return errReject("SingleAttestation: attester index too high")
let validator_index = attestation.attester_index.ValidatorIndex

# [REJECT] The signature of `attestation` is valid.
# In the spec, is_valid_indexed_attestation is used to verify the signature -
# here, we do a batch verification instead
var sigchecked = false
var sig: CookedSig
template doSigCheck: untyped =
let
fork = pool.dag.forkAtEpoch(attestation.data.slot.epoch)
pubkey = pool.dag.validatorKey(validator_index).valueOr:
# can't happen, in theory, because we checked the aggregator index above
return errIgnore("Attestation: cannot find validator pubkey")

sigchecked = true
sig =
if checkSignature:
# Attestation signatures are batch-verified
let deferredCrypto = batchCrypto
.scheduleAttestationCheck(
fork, attestation.data, pubkey,
attestation.signature)
if deferredCrypto.isErr():
return pool.checkedReject(deferredCrypto.error)

let (cryptoFut, sig) = deferredCrypto.get()
# Await the crypto check
let x = (await cryptoFut)
case x
of BatchResult.Invalid:
return pool.checkedReject("Attestation: invalid signature")
of BatchResult.Timeout:
beacon_attestations_dropped_queue_full.inc()
return errIgnore("Attestation: timeout checking signature")
of BatchResult.Valid:
sig # keep going only in this case
else:
attestation.signature.load().valueOr:
return pool.checkedReject("Attestation: unable to load signature")

# The following rule follows implicitly from that we clear out any
# unviable blocks from the chain dag:
#
# [IGNORE] The current finalized_checkpoint is an ancestor of the block
# defined by attestation.data.beacon_block_root -- i.e.
# get_checkpoint_block(store, attestation.data.beacon_block_root,
# store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root
var sigchecked = false
var sig: CookedSig
let shufflingRef =
pool.dag.findShufflingRef(target.blck.bid, target.slot.epoch).valueOr:
# getShufflingRef might be slow here, so first try to eliminate by
# signature check
sig = attestation.signature.load().valueOr:
return pool.checkedReject("SingleAttestation: unable to load signature")
sigchecked = true
doSigCheck()
pool.dag.getShufflingRef(target.blck, target.slot.epoch, false).valueOr:
# Target is verified - shouldn't happen
warn "No shuffling for SingleAttestation - report bug",
attestation = shortLog(attestation), target = shortLog(target)
return errIgnore("SingleAttestation: no shuffling")

if attestation.attester_index > high(ValidatorIndex).uint64:
return errReject("SingleAttestation: attester index too high")
let validator_index = attestation.attester_index.ValidatorIndex
# [REJECT] The committee index is within the expected range -- i.e.
# data.index < get_committee_count_per_slot(state, data.target.epoch).
let committee_index = block:
let idx = shufflingRef.get_committee_index(attestation.committee_index)
if idx.isErr():
return pool.checkedReject(
"Attestation: committee index not within expected range")
idx.get()
tersec marked this conversation as resolved.
Show resolved Hide resolved

# [REJECT] The attester is a member of the committee -- i.e.
# attestation.attester_index in
Expand All @@ -1122,15 +1165,6 @@ proc validateAttestation*(
if index_in_committee < 0:
return pool.checkedReject("SingleAttestation: attester index not in beacon committee")

# [REJECT] The committee index is within the expected range -- i.e.
# data.index < get_committee_count_per_slot(state, data.target.epoch).
let committee_index = block:
let idx = shufflingRef.get_committee_index(attestation.committee_index)
if idx.isErr():
return pool.checkedReject(
"Attestation: committee index not within expected range")
idx.get()

# [REJECT] The attestation is for the correct subnet -- i.e.
# compute_subnet_for_attestation(committees_per_slot,
# attestation.data.slot, attestation.data.index) == subnet_id, where
Expand All @@ -1148,9 +1182,14 @@ proc validateAttestation*(
if not sigchecked:
# findShufflingRef did find a cached ShufflingRef, which means the early
# signature check was skipped, so do it now.
sig = attestation.signature.load().valueOr:
return pool.checkedReject("SingleAttestation: unable to load signature")
doSigCheck()

# Only valid attestations go in the list, which keeps validator_index
# in range
if not (pool.nextAttestationEpoch.lenu64 > validator_index.uint64):
pool.nextAttestationEpoch.setLen(validator_index.int + 1)
pool.nextAttestationEpoch[validator_index].subnet =
attestation.data.target.epoch + 1
ok((validator_index, beacon_committee.len, index_in_committee, sig))

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/phase0/p2p-interface.md#beacon_aggregate_and_proof
Expand Down Expand Up @@ -1244,14 +1283,16 @@ proc validateAggregate*(
# data.index < get_committee_count_per_slot(state, data.target.epoch).
let committee_index = block:
when signedAggregateAndProof is electra.SignedAggregateAndProof:
let idx = get_committee_index_one(aggregate.committee_bits)
let agg_idx = get_committee_index_one(aggregate.committee_bits).valueOr:
return pool.checkedReject("Aggregate: got multiple committee bits")
let idx = shufflingRef.get_committee_index(agg_idx.uint64)
elif signedAggregateAndProof is phase0.SignedAggregateAndProof:
let idx = shufflingRef.get_committee_index(aggregate.data.index)
else:
static: doAssert false
if idx.isErr():
return pool.checkedReject(
"Attestation: committee index not within expected range")
"Aggregate: committee index not within expected range")
idx.get()
if not aggregate.aggregation_bits.compatible_with_shuffling(
shufflingRef, slot, committee_index):
Expand Down
Loading