2021-01-11 21:27:01 +00:00
# beacon_chain
# Copyright (c) 2018-2021 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 : [ Defect ] . }
2020-08-20 16:30:47 +00:00
import
2021-03-12 09:46:26 +00:00
std / tables ,
2020-08-20 16:30:47 +00:00
stew / results ,
2021-01-11 21:27:01 +00:00
chronicles , chronos , metrics ,
2021-08-10 20:46:35 +00:00
.. / spec / [ crypto , digest , forks ] ,
2021-07-15 19:01:07 +00:00
.. / spec / datatypes / [ altair , phase0 ] ,
2021-03-05 13:12:00 +00:00
.. / consensus_object_pools / [ block_clearance , blockchain_dag , exit_pool , attestation_pool ] ,
2021-05-28 16:34:00 +00:00
. / gossip_validation , . / block_processor ,
2021-04-02 14:36:43 +00:00
. / batch_validation ,
2021-03-05 13:12:00 +00:00
.. / validators / validator_pool ,
.. / beacon_node_types ,
2021-07-15 19:01:07 +00:00
.. / beacon_clock
2020-08-20 16:30:47 +00:00
# Metrics for tracking attestation and beacon block loss
declareCounter beacon_attestations_received ,
" Number of beacon chain attestations received by this peer "
declareCounter beacon_aggregates_received ,
" Number of beacon chain aggregate attestations received by this peer "
declareCounter beacon_blocks_received ,
" Number of beacon chain blocks received by this peer "
2020-09-14 14:26:31 +00:00
declareCounter beacon_attester_slashings_received ,
" Number of beacon chain attester slashings received by this peer "
declareCounter beacon_proposer_slashings_received ,
" Number of beacon chain proposer slashings received by this peer "
declareCounter beacon_voluntary_exits_received ,
" Number of beacon chain voluntary exits received by this peer "
2020-08-20 16:30:47 +00:00
const delayBuckets = [ 2 .0 , 4 .0 , 6 .0 , 8 .0 , 10 .0 , 12 .0 , 14 .0 , Inf ]
declareHistogram beacon_attestation_delay ,
" Time(s) between slot start and attestation reception " , buckets = delayBuckets
declareHistogram beacon_aggregate_delay ,
" Time(s) between slot start and aggregate reception " , buckets = delayBuckets
declareHistogram beacon_block_delay ,
" Time(s) between slot start and beacon block reception " , buckets = delayBuckets
type
2021-06-17 11:51:04 +00:00
DoppelgangerProtection = object
broadcastStartEpoch * : Epoch ##\
## Set anew, each time gossip is re-enabled after syncing completes, so
## might reset multiple times per instance. This allows some safe level
## of gossip interleaving between nodes so long as they don't gossip at
## the same time.
nodeLaunchSlot : Slot ##\
## Set once, at node launch. This functions as a basic protection against
## false positives from attestations persisting within the gossip network
## across quick restarts.
2020-08-20 16:30:47 +00:00
Eth2Processor * = object
2021-04-06 11:59:11 +00:00
doppelGangerDetectionEnabled * : bool
2021-03-11 10:10:57 +00:00
# Local sources of truth for validation
# ----------------------------------------------------------------
2021-06-01 11:13:40 +00:00
dag * : ChainDAGRef
2020-08-20 16:30:47 +00:00
attestationPool * : ref AttestationPool
2020-10-27 17:21:35 +00:00
validatorPool : ref ValidatorPool
2020-08-20 16:30:47 +00:00
2021-02-01 11:18:16 +00:00
doppelgangerDetection * : DoppelgangerProtection
2020-10-27 17:21:35 +00:00
2021-03-11 10:10:57 +00:00
# Gossip validated -> enqueue for further verification
# ----------------------------------------------------------------
2021-05-28 16:34:00 +00:00
blockProcessor : ref BlockProcessor
2020-08-20 16:30:47 +00:00
2021-03-11 10:10:57 +00:00
# Validated with no further verification required
# ----------------------------------------------------------------
exitPool : ref ExitPool
2020-08-20 16:30:47 +00:00
2021-04-02 14:36:43 +00:00
# Almost validated, pending cryptographic signature check
# ----------------------------------------------------------------
2021-04-03 01:50:47 +00:00
batchCrypto * : ref BatchCrypto
2021-04-02 14:36:43 +00:00
2021-03-11 10:10:57 +00:00
# Missing information
# ----------------------------------------------------------------
quarantine * : QuarantineRef
2020-08-20 16:30:47 +00:00
2021-07-07 09:09:47 +00:00
# Application-provided current time provider (to facilitate testing)
getTime * : GetTimeFn
2021-03-11 10:10:57 +00:00
# Initialization
# ------------------------------------------------------------------------------
2020-08-20 16:30:47 +00:00
2021-03-11 10:10:57 +00:00
proc new * ( T : type Eth2Processor ,
2021-04-06 11:59:11 +00:00
doppelGangerDetectionEnabled : bool ,
2021-05-28 16:34:00 +00:00
blockProcessor : ref BlockProcessor ,
2021-06-01 11:13:40 +00:00
dag : ChainDAGRef ,
2021-03-11 10:10:57 +00:00
attestationPool : ref AttestationPool ,
exitPool : ref ExitPool ,
validatorPool : ref ValidatorPool ,
quarantine : QuarantineRef ,
2021-04-02 14:36:43 +00:00
rng : ref BrHmacDrbgContext ,
2021-07-07 09:09:47 +00:00
getTime : GetTimeFn ) : ref Eth2Processor =
2021-03-11 10:10:57 +00:00
( ref Eth2Processor ) (
2021-04-06 11:59:11 +00:00
doppelGangerDetectionEnabled : doppelGangerDetectionEnabled ,
2021-06-17 11:51:04 +00:00
doppelgangerDetection : DoppelgangerProtection (
2021-07-07 09:09:47 +00:00
nodeLaunchSlot : dag . beaconClock . now . slotOrZero ) ,
2021-05-28 16:34:00 +00:00
blockProcessor : blockProcessor ,
2021-06-01 11:13:40 +00:00
dag : dag ,
2021-03-11 10:10:57 +00:00
attestationPool : attestationPool ,
exitPool : exitPool ,
validatorPool : validatorPool ,
2021-04-02 14:36:43 +00:00
quarantine : quarantine ,
2021-07-07 09:09:47 +00:00
getTime : getTime ,
2021-04-26 20:39:44 +00:00
batchCrypto : BatchCrypto . new (
rng = rng ,
# Only run eager attestation signature verification if we're not
# processing blocks in order to give priority to block processing
2021-05-28 16:34:00 +00:00
eager = proc ( ) : bool = not blockProcessor [ ] . hasBlocks ( ) )
2021-03-11 10:10:57 +00:00
)
2020-08-20 16:30:47 +00:00
2021-07-07 09:09:47 +00:00
proc getCurrentBeaconTime * ( self : Eth2Processor | ref Eth2Processor ) : BeaconTime =
self . dag . beaconClock . toBeaconTime ( self . getTime ( ) )
2021-03-11 10:10:57 +00:00
# Gossip Management
# -----------------------------------------------------------------------------------
2021-01-11 21:27:01 +00:00
2020-08-20 16:30:47 +00:00
proc blockValidator * (
self : var Eth2Processor ,
2021-07-15 19:01:07 +00:00
signedBlock : phase0 . SignedBeaconBlock | altair . SignedBeaconBlock ) : ValidationResult =
2020-08-20 16:30:47 +00:00
logScope :
signedBlock = shortLog ( signedBlock . message )
blockRoot = shortLog ( signedBlock . root )
let
2021-07-07 09:09:47 +00:00
wallTime = self . getCurrentBeaconTime ( )
2020-08-20 16:30:47 +00:00
( afterGenesis , wallSlot ) = wallTime . toSlot ( )
if not afterGenesis :
2020-10-20 12:31:20 +00:00
return ValidationResult . Ignore # not an issue with block, so don't penalize
2020-08-20 16:30:47 +00:00
logScope : wallSlot
let delay = wallTime - signedBlock . message . slot . toBeaconTime
2021-06-01 11:13:40 +00:00
if signedBlock . root in self . dag :
2020-08-20 16:30:47 +00:00
# The gossip algorithm itself already does one round of hashing to find
# already-seen data, but it is fairly aggressive about forgetting about
# what it has seen already
debug " Dropping already-seen gossip block " , delay
2020-10-20 12:31:20 +00:00
return ValidationResult . Ignore # "[IGNORE] The block is the first block ..."
2020-08-20 16:30:47 +00:00
# Start of block processing - in reality, we have already gone through SSZ
# decoding at this stage, which may be significant
debug " Block received " , delay
2021-06-01 11:13:40 +00:00
let blck = self . dag . isValidBeaconBlock (
2020-10-28 13:04:21 +00:00
self . quarantine , signedBlock , wallTime , { } )
2020-08-20 16:30:47 +00:00
2021-05-28 16:34:00 +00:00
self . blockProcessor [ ] . dumpBlock ( signedBlock , blck )
2020-08-20 16:30:47 +00:00
if not blck . isOk :
2020-09-18 11:53:09 +00:00
return blck . error [ 0 ]
2020-08-20 16:30:47 +00:00
2020-08-27 07:34:12 +00:00
beacon_blocks_received . inc ( )
2020-11-11 13:39:36 +00:00
beacon_block_delay . observe ( delay . toFloatSeconds ( ) )
2020-08-27 07:34:12 +00:00
2020-08-20 16:30:47 +00:00
# Block passed validation - enqueue it for processing. The block processing
# queue is effectively unbounded as we use a freestanding task to enqueue
# the block - this is done so that when blocks arrive concurrently with
# sync, we don't lose the gossip blocks, but also don't block the gossip
# propagation of seemingly good blocks
2020-08-27 07:34:12 +00:00
trace " Block validated "
2021-05-28 16:34:00 +00:00
self . blockProcessor [ ] . addBlock (
2021-07-15 19:01:07 +00:00
ForkedSignedBeaconBlock . init ( signedBlock ) ,
validationDur = self . getCurrentBeaconTime ( ) - wallTime )
2020-08-20 16:30:47 +00:00
2020-10-20 12:31:20 +00:00
ValidationResult . Accept
2020-08-20 16:30:47 +00:00
2021-01-29 12:38:52 +00:00
proc checkForPotentialDoppelganger (
2021-05-28 12:51:15 +00:00
self : var Eth2Processor , attestation : Attestation ,
2021-06-17 11:51:04 +00:00
attesterIndices : openArray [ ValidatorIndex ] ) =
# Only check for attestations after node launch. There might be one slot of
# overlap in quick intra-slot restarts so trade off a few true negatives in
# the service of avoiding more likely false positives.
if attestation . data . slot < = self . doppelgangerDetection . nodeLaunchSlot + 1 :
2021-03-01 10:09:05 +00:00
return
2021-06-17 11:51:04 +00:00
if attestation . data . slot . epoch <
self . doppelgangerDetection . broadcastStartEpoch :
2021-06-01 11:13:40 +00:00
let tgtBlck = self . dag . getRef ( attestation . data . target . root )
2020-10-27 17:21:35 +00:00
doAssert not tgtBlck . isNil # because attestation is valid above
2021-06-01 11:13:40 +00:00
let epochRef = self . dag . getEpochRef (
2021-05-28 12:51:15 +00:00
tgtBlck , attestation . data . target . epoch )
2020-10-27 17:21:35 +00:00
for validatorIndex in attesterIndices :
2021-06-10 07:37:02 +00:00
let validatorPubkey = epochRef . validatorKey ( validatorIndex ) . get ( ) . toPubKey ( )
2021-04-26 19:41:35 +00:00
if self . doppelgangerDetectionEnabled and
self . validatorPool [ ] . getValidator ( validatorPubkey ) ! =
default ( AttachedValidator ) :
warn " We believe you are currently running another instance of the same validator. We ' ve disconnected you from the network as this presents a significant slashing risk. Possible next steps are (a) making sure you ' ve disconnected your validator from your old machine before restarting the client; and (b) running the client again with the gossip-slashing-protection option disabled, only if you are absolutely sure this is the only instance of your validator running, and reporting the issue at https://github.com/status-im/nimbus-eth2/issues. " ,
2020-10-27 17:21:35 +00:00
validatorIndex ,
2021-03-01 10:09:05 +00:00
validatorPubkey ,
2021-05-28 12:51:15 +00:00
attestation = shortLog ( attestation )
2021-04-26 19:41:35 +00:00
quit QuitFailure
2020-10-27 17:21:35 +00:00
2020-08-20 16:30:47 +00:00
proc attestationValidator * (
2021-04-02 14:36:43 +00:00
self : ref Eth2Processor ,
2020-08-20 16:30:47 +00:00
attestation : Attestation ,
2021-05-10 07:13:36 +00:00
subnet_id : SubnetId ,
checkSignature : bool = true ) : Future [ ValidationResult ] {. async . } =
2020-08-20 16:30:47 +00:00
logScope :
attestation = shortLog ( attestation )
2021-05-10 07:13:36 +00:00
subnet_id
2020-08-20 16:30:47 +00:00
2021-07-07 09:09:47 +00:00
let wallTime = self . getCurrentBeaconTime ( )
2021-06-17 14:38:25 +00:00
var ( afterGenesis , wallSlot ) = wallTime . toSlot ( )
2020-08-20 16:30:47 +00:00
if not afterGenesis :
notice " Attestation before genesis "
2020-10-20 12:31:20 +00:00
return ValidationResult . Ignore
2020-08-20 16:30:47 +00:00
logScope : wallSlot
2020-09-02 16:16:25 +00:00
# Potential under/overflows are fine; would just create odd metrics and logs
2020-08-20 16:30:47 +00:00
let delay = wallTime - attestation . data . slot . toBeaconTime
2020-10-09 08:58:54 +00:00
debug " Attestation received " , delay
2021-04-02 14:36:43 +00:00
# Now proceed to validation
let v = await self . attestationPool . validateAttestation (
2021-05-10 07:13:36 +00:00
self . batchCrypto , attestation , wallTime , subnet_id , checkSignature )
2020-08-27 07:34:12 +00:00
if v . isErr ( ) :
2021-06-10 08:39:53 +00:00
debug " Dropping attestation " , validationError = v . error
2020-09-18 11:53:09 +00:00
return v . error [ 0 ]
2020-08-20 16:30:47 +00:00
2021-06-17 14:38:25 +00:00
# Due to async validation the wallSlot here might have changed
2021-07-07 09:09:47 +00:00
( afterGenesis , wallSlot ) = self . getCurrentBeaconTime ( ) . toSlot ( )
2021-06-17 14:38:25 +00:00
2020-08-20 16:30:47 +00:00
beacon_attestations_received . inc ( )
2020-11-11 13:39:36 +00:00
beacon_attestation_delay . observe ( delay . toFloatSeconds ( ) )
2020-08-20 16:30:47 +00:00
2021-04-26 20:39:44 +00:00
let ( attestation_index , sig ) = v . get ( )
2021-06-17 11:51:04 +00:00
self [ ] . checkForPotentialDoppelganger ( attestation , [ attestation_index ] )
2020-10-27 17:21:35 +00:00
2020-08-27 07:34:12 +00:00
trace " Attestation validated "
2021-04-26 20:39:44 +00:00
self . attestationPool [ ] . addAttestation (
attestation , [ attestation_index ] , sig , wallSlot )
2020-08-20 16:30:47 +00:00
2021-04-02 14:36:43 +00:00
return ValidationResult . Accept
2020-08-20 16:30:47 +00:00
proc aggregateValidator * (
2021-04-02 14:36:43 +00:00
self : ref Eth2Processor ,
signedAggregateAndProof : SignedAggregateAndProof ) : Future [ ValidationResult ] {. async . } =
2020-08-20 16:30:47 +00:00
logScope :
aggregate = shortLog ( signedAggregateAndProof . message . aggregate )
signature = shortLog ( signedAggregateAndProof . signature )
2021-07-07 09:09:47 +00:00
let wallTime = self . getCurrentBeaconTime ( )
2021-06-17 14:38:25 +00:00
var ( afterGenesis , wallSlot ) = wallTime . toSlot ( )
2020-08-20 16:30:47 +00:00
if not afterGenesis :
notice " Aggregate before genesis "
2020-10-20 12:31:20 +00:00
return ValidationResult . Ignore
2020-08-20 16:30:47 +00:00
logScope : wallSlot
2020-09-02 16:16:25 +00:00
# Potential under/overflows are fine; would just create odd logs
2020-08-20 16:30:47 +00:00
let delay =
wallTime - signedAggregateAndProof . message . aggregate . data . slot . toBeaconTime
debug " Aggregate received " , delay
2021-04-02 14:36:43 +00:00
let v = await self . attestationPool . validateAggregate (
self . batchCrypto ,
2020-08-27 07:34:12 +00:00
signedAggregateAndProof , wallTime )
if v . isErr :
2020-12-14 20:58:32 +00:00
debug " Dropping aggregate " ,
2021-06-10 08:39:53 +00:00
validationError = v . error ,
2020-12-14 20:58:32 +00:00
aggregator_index = signedAggregateAndProof . message . aggregator_index ,
selection_proof = signedAggregateAndProof . message . selection_proof ,
wallSlot
2020-09-18 11:53:09 +00:00
return v . error [ 0 ]
2020-08-20 16:30:47 +00:00
2021-06-17 14:38:25 +00:00
# Due to async validation the wallSlot here might have changed
2021-07-07 09:09:47 +00:00
( afterGenesis , wallSlot ) = self . getCurrentBeaconTime ( ) . toSlot ( )
2021-06-17 14:38:25 +00:00
2020-08-20 16:30:47 +00:00
beacon_aggregates_received . inc ( )
2020-11-11 13:39:36 +00:00
beacon_aggregate_delay . observe ( delay . toFloatSeconds ( ) )
2020-08-20 16:30:47 +00:00
2021-04-26 20:39:44 +00:00
let ( attesting_indices , sig ) = v . get ( )
2021-04-02 14:36:43 +00:00
self [ ] . checkForPotentialDoppelganger (
2021-06-17 11:51:04 +00:00
signedAggregateAndProof . message . aggregate , attesting_indices )
2020-10-27 17:21:35 +00:00
2020-12-14 20:58:32 +00:00
trace " Aggregate validated " ,
aggregator_index = signedAggregateAndProof . message . aggregator_index ,
2021-06-17 14:38:25 +00:00
selection_proof = signedAggregateAndProof . message . selection_proof
2020-12-14 20:58:32 +00:00
2021-04-26 20:39:44 +00:00
self . attestationPool [ ] . addAttestation (
signedAggregateAndProof . message . aggregate , attesting_indices , sig , wallSlot )
2020-08-20 16:30:47 +00:00
2021-04-02 14:36:43 +00:00
return ValidationResult . Accept
2020-09-14 14:26:31 +00:00
proc attesterSlashingValidator * (
2020-09-18 11:53:09 +00:00
self : var Eth2Processor , attesterSlashing : AttesterSlashing ) :
ValidationResult =
2020-09-14 14:26:31 +00:00
logScope :
attesterSlashing = shortLog ( attesterSlashing )
let v = self . exitPool [ ] . validateAttesterSlashing ( attesterSlashing )
if v . isErr :
2021-06-10 08:39:53 +00:00
debug " Dropping attester slashing " , validationError = v . error
2020-09-18 11:53:09 +00:00
return v . error [ 0 ]
2020-09-14 14:26:31 +00:00
beacon_attester_slashings_received . inc ( )
2020-10-20 12:31:20 +00:00
ValidationResult . Accept
2020-09-18 11:53:09 +00:00
2020-09-14 14:26:31 +00:00
proc proposerSlashingValidator * (
2020-09-18 11:53:09 +00:00
self : var Eth2Processor , proposerSlashing : ProposerSlashing ) :
ValidationResult =
2020-09-14 14:26:31 +00:00
logScope :
proposerSlashing = shortLog ( proposerSlashing )
let v = self . exitPool [ ] . validateProposerSlashing ( proposerSlashing )
if v . isErr :
2021-06-10 08:39:53 +00:00
debug " Dropping proposer slashing " , validationError = v . error
2020-09-18 11:53:09 +00:00
return v . error [ 0 ]
2020-09-14 14:26:31 +00:00
beacon_proposer_slashings_received . inc ( )
2020-10-20 12:31:20 +00:00
ValidationResult . Accept
2020-09-18 11:53:09 +00:00
2020-09-14 14:26:31 +00:00
proc voluntaryExitValidator * (
2020-09-24 17:05:49 +00:00
self : var Eth2Processor , signedVoluntaryExit : SignedVoluntaryExit ) :
ValidationResult =
2020-09-14 14:26:31 +00:00
logScope :
2020-09-24 17:05:49 +00:00
signedVoluntaryExit = shortLog ( signedVoluntaryExit )
2020-09-14 14:26:31 +00:00
2020-09-24 17:05:49 +00:00
let v = self . exitPool [ ] . validateVoluntaryExit ( signedVoluntaryExit )
2020-09-14 14:26:31 +00:00
if v . isErr :
2021-06-10 08:39:53 +00:00
debug " Dropping voluntary exit " , validationError = v . error
2020-09-18 11:53:09 +00:00
return v . error [ 0 ]
2020-09-14 14:26:31 +00:00
beacon_voluntary_exits_received . inc ( )
2020-10-20 12:31:20 +00:00
ValidationResult . Accept