Add blob sidecar gossip validation (#4785)
* Add blob gossip validation * lint * Add test for getBlobSidecarTopic * Fix closure variable capture issue * Update beacon_chain/nimbus_beacon_node.nim Co-authored-by: tersec <tersec@users.noreply.github.com> --------- Co-authored-by: tersec <tersec@users.noreply.github.com>
This commit is contained in:
parent
5167a373ab
commit
04302081b4
|
@ -41,6 +41,10 @@ declareCounter beacon_blocks_received,
|
|||
"Number of valid blocks processed by this node"
|
||||
declareCounter beacon_blocks_dropped,
|
||||
"Number of invalid blocks dropped by this node", labels = ["reason"]
|
||||
declareCounter blob_sidecars_received,
|
||||
"Number of valid blobs processed by this node"
|
||||
declareCounter blob_sidecars_dropped,
|
||||
"Number of invalid blobs dropped by this node", labels = ["reason"]
|
||||
declareCounter beacon_attester_slashings_received,
|
||||
"Number of valid attester slashings processed by this node"
|
||||
declareCounter beacon_attester_slashings_dropped,
|
||||
|
@ -85,6 +89,9 @@ declareHistogram beacon_aggregate_delay,
|
|||
declareHistogram beacon_block_delay,
|
||||
"Time(s) between slot start and beacon block reception", buckets = delayBuckets
|
||||
|
||||
declareHistogram blob_sidecar_delay,
|
||||
"Time(s) between slot start and blob sidecar reception", buckets = delayBuckets
|
||||
|
||||
type
|
||||
DoppelgangerProtection = object
|
||||
broadcastStartEpoch*: Epoch ##\
|
||||
|
@ -246,6 +253,41 @@ proc processSignedBeaconBlock*(
|
|||
|
||||
v
|
||||
|
||||
proc processSignedBlobSidecar*(
|
||||
self: var Eth2Processor, src: MsgSource,
|
||||
signedBlobSidecar: deneb.SignedBlobSidecar, idx: BlobIndex): ValidationRes =
|
||||
let
|
||||
wallTime = self.getCurrentBeaconTime()
|
||||
(afterGenesis, wallSlot) = wallTime.toSlot()
|
||||
|
||||
logScope:
|
||||
blob = shortLog(signedBlobSidecar.message)
|
||||
signature = shortLog(signedBlobSidecar.signature)
|
||||
wallSlot
|
||||
|
||||
# Potential under/overflows are fine; would just create odd metrics and logs
|
||||
let delay = wallTime - signedBlobSidecar.message.slot.start_beacon_time
|
||||
|
||||
debug "Blob received", delay
|
||||
|
||||
let v =
|
||||
self.dag.validateBlobSidecar(self.quarantine, signedBlobSidecar, wallTime, idx)
|
||||
|
||||
if v.isOk():
|
||||
trace "Blob validated"
|
||||
|
||||
# TODO
|
||||
# hand blob off to blob quarantine
|
||||
|
||||
blob_sidecars_received.inc()
|
||||
blob_sidecar_delay.observe(delay.toFloatSeconds())
|
||||
else:
|
||||
debug "Dropping blob", error = v.error()
|
||||
|
||||
blob_sidecars_dropped.inc(1, [$v.error[0]])
|
||||
|
||||
v
|
||||
|
||||
proc setupDoppelgangerDetection*(self: var Eth2Processor, slot: Slot) =
|
||||
# When another client's already running, this is very likely to detect
|
||||
# potential duplicate validators, which can trigger slashing.
|
||||
|
|
|
@ -220,6 +220,79 @@ template validateBeaconBlockBellatrix(
|
|||
# `ACCEPTED` or `SYNCING` from the EL to get this far.
|
||||
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/deneb/p2p-interface.md#blob_sidecar_index
|
||||
proc validateBlobSidecar*(
|
||||
dag: ChainDAGRef, quarantine: ref Quarantine,
|
||||
sbs: SignedBlobSidecar,
|
||||
wallTime: BeaconTime, idx: BlobIndex): Result[void, ValidationError] =
|
||||
|
||||
# [REJECT] The sidecar is for the correct topic --
|
||||
# i.e. sidecar.index matches the topic {index}.
|
||||
if sbs.message.index != idx:
|
||||
return errReject("SignedBlobSidecar: mismatched gossip topic index")
|
||||
|
||||
if dag.getBlockRef(sbs.message.block_root).isSome():
|
||||
return errIgnore("SignedBlobSidecar: already have block")
|
||||
|
||||
# [IGNORE] The sidecar is not from a future slot (with a
|
||||
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. validate that
|
||||
# sidecar.slot <= current_slot (a client MAY queue future sidecars
|
||||
# for processing at the appropriate slot).
|
||||
if not (sbs.message.slot <=
|
||||
(wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY).slotOrZero):
|
||||
return errIgnore("SignedBlobSidecar: slot too high")
|
||||
|
||||
# [IGNORE] The block is from a slot greater than the latest
|
||||
# finalized slot -- i.e. validate that
|
||||
# signed_beacon_block.message.slot >
|
||||
# compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
|
||||
if not (sbs.message.slot > dag.finalizedHead.slot):
|
||||
return errIgnore("SignedBlobSidecar: slot already finalized")
|
||||
|
||||
# [REJECT] The sidecar's block's parent (defined by sidecar.block_parent_root)
|
||||
# passes validation.
|
||||
let parent = dag.getBlockRef(sbs.message.block_parent_root).valueOr:
|
||||
return errReject("SignedBlobSidecar: parent not validated")
|
||||
|
||||
# [REJECT] The sidecar is from a higher slot than the sidecar's
|
||||
# block's parent (defined by sidecar.block_parent_root).
|
||||
if sbs.message.slot <= parent.bid.slot:
|
||||
return errReject("SignedBlobSidecar: slot lower than parents'")
|
||||
|
||||
# [REJECT] The sidecar is proposed by the expected proposer_index
|
||||
# for the block's slot in the context of the current shuffling
|
||||
# (defined by block_parent_root/slot). If the proposer_index
|
||||
# cannot immediately be verified against the expected shuffling,
|
||||
# the sidecar MAY be queued for later processing while proposers
|
||||
# for the block's branch are calculated -- in such a case do not
|
||||
# REJECT, instead IGNORE this message.
|
||||
let
|
||||
proposer = getProposer(
|
||||
dag, parent, sbs.message.slot).valueOr:
|
||||
warn "cannot compute proposer for blob"
|
||||
return errIgnore("SignedBlobSidecar: Cannot compute proposer")
|
||||
|
||||
if uint64(proposer) != sbs.message.proposer_index:
|
||||
return errReject("SignedBlobSidecar: Unexpected proposer")
|
||||
|
||||
# [REJECT] The proposer signature, signed_blob_sidecar.signature,
|
||||
# is valid as verified by verify_sidecar_signature.
|
||||
if not verify_blob_signature(
|
||||
dag.forkAtEpoch(sbs.message.slot.epoch),
|
||||
getStateField(dag.headState, genesis_validators_root),
|
||||
sbs.message.slot,
|
||||
sbs.message,
|
||||
dag.validatorKey(proposer).get(),
|
||||
sbs.signature):
|
||||
return errReject("SignedBlobSidecar: invalid blob signature")
|
||||
|
||||
# [IGNORE] The sidecar is the only sidecar with valid signature received for the tuple (sidecar.block_root, sidecar.index).
|
||||
# TODO
|
||||
# check that there isn't a conflicting sidecar in blob_quarantine
|
||||
|
||||
ok()
|
||||
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.9/specs/phase0/p2p-interface.md#beacon_block
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/bellatrix/p2p-interface.md#beacon_block
|
||||
proc validateBeaconBlock*(
|
||||
|
|
|
@ -1478,6 +1478,15 @@ proc installMessageValidators(node: BeaconNode) =
|
|||
toValidationResult(node.processor[].processSignedBeaconBlock(
|
||||
MsgSource.gossip, signedBlock)))
|
||||
|
||||
for i in 0 ..< MAX_BLOBS_PER_BLOCK:
|
||||
closureScope:
|
||||
let idx = i
|
||||
node.network.addValidator(
|
||||
getBlobSidecarTopic(forkDigests.deneb, idx),
|
||||
proc (signedBlobSidecar: deneb.SignedBlobSidecar): ValidationResult =
|
||||
toValidationResult(node.processor[].processSignedBlobSidecar(
|
||||
MsgSource.gossip, signedBlobSidecar, idx)))
|
||||
|
||||
template installSyncCommitteeeValidators(digest: auto) =
|
||||
for subcommitteeIdx in SyncSubcommitteeIndex:
|
||||
closureScope:
|
||||
|
|
|
@ -39,6 +39,9 @@ const
|
|||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/capella/beacon-chain.md#domain-types
|
||||
DOMAIN_BLS_TO_EXECUTION_CHANGE* = DomainType([byte 0x0a, 0x00, 0x00, 0x00])
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/deneb/beacon-chain.md
|
||||
DOMAIN_BLOB_SIDECAR* = DomainType([byte 0x0b, 0x00, 0x00, 0x00])
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/bellatrix/beacon-chain.md#transition-settings
|
||||
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH* = FAR_FUTURE_EPOCH
|
||||
|
||||
|
|
|
@ -525,6 +525,22 @@ func shortLog*(v: SomeBeaconBlock): auto =
|
|||
fee_recipient: to0xHex(v.body.execution_payload.fee_recipient.data),
|
||||
)
|
||||
|
||||
func shortLog*(v: BlobSidecar): auto =
|
||||
(
|
||||
block_root: shortLog(v.block_root),
|
||||
index: v.index,
|
||||
slot: shortLog(v.slot),
|
||||
block_parent_root: shortLog(v.block_parent_root),
|
||||
proposer_index: v.proposer_index,
|
||||
bloblen: v.blob.len(),
|
||||
)
|
||||
|
||||
func shortLog*(v: SignedBlobSidecar): auto =
|
||||
(
|
||||
blob: shortLog(v.message),
|
||||
signature: shortLog(v.signature)
|
||||
)
|
||||
|
||||
func shortLog*(v: SomeSignedBeaconBlock): auto =
|
||||
(
|
||||
blck: shortLog(v.message),
|
||||
|
|
|
@ -97,6 +97,11 @@ func getSyncCommitteeContributionAndProofTopic*(forkDigest: ForkDigest): string
|
|||
## For subscribing and unsubscribing to/from a subnet.
|
||||
eth2Prefix(forkDigest) & "sync_committee_contribution_and_proof/ssz_snappy"
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/deneb/p2p-interface.md#blob_sidecar_index
|
||||
func getBlobSidecarTopic*(forkDigest: ForkDigest,
|
||||
index: BlobIndex): string =
|
||||
eth2Prefix(forkDigest) & "blob_sidecar_" & $index & "/ssz_snappy"
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/altair/light-client/p2p-interface.md#light_client_finality_update
|
||||
func getLightClientFinalityUpdateTopic*(forkDigest: ForkDigest): string =
|
||||
## For broadcasting or obtaining the latest `LightClientFinalityUpdate`.
|
||||
|
|
|
@ -98,6 +98,15 @@ func compute_block_signing_root*(
|
|||
fork, DOMAIN_BEACON_PROPOSER, epoch, genesis_validators_root)
|
||||
compute_signing_root(blck, domain)
|
||||
|
||||
func compute_blob_signing_root*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
blob: BlobSidecar): Eth2Digest =
|
||||
let
|
||||
epoch = epoch(slot)
|
||||
domain = get_domain(fork, DOMAIN_BLOB_SIDECAR, epoch,
|
||||
genesis_validators_root)
|
||||
compute_signing_root(blob, domain)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/phase0/validator.md#signature
|
||||
func get_block_signature*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
|
@ -118,6 +127,17 @@ proc verify_block_signature*(
|
|||
|
||||
blsVerify(pubkey, signing_root.data, signature)
|
||||
|
||||
proc verify_blob_signature*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
blobSidecar: BlobSidecar,
|
||||
pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool =
|
||||
withTrust(signature):
|
||||
let
|
||||
signing_root = compute_blob_signing_root(
|
||||
fork, genesis_validators_root, slot, blobSidecar)
|
||||
|
||||
blsVerify(pubkey, signing_root.data, signature)
|
||||
|
||||
func compute_aggregate_and_proof_signing_root*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest,
|
||||
aggregate_and_proof: AggregateAndProof): Eth2Digest =
|
||||
|
|
|
@ -76,6 +76,8 @@ suite "Honest validator":
|
|||
"/eth2/00000000/sync_committee_1/ssz_snappy"
|
||||
getSyncCommitteeTopic(forkDigest, SyncSubcommitteeIndex(3)) ==
|
||||
"/eth2/00000000/sync_committee_3/ssz_snappy"
|
||||
getBlobSidecarTopic(forkDigest, BlobIndex(1)) ==
|
||||
"/eth2/00000000/blob_sidecar_1/ssz_snappy"
|
||||
|
||||
test "is_aggregator":
|
||||
check:
|
||||
|
|
Loading…
Reference in New Issue