2020-05-06 13:23:45 +00:00
|
|
|
# beacon_chain
|
2021-01-29 12:38:52 +00:00
|
|
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
2020-05-06 13:23:45 +00:00
|
|
|
# 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.
|
|
|
|
|
2021-03-26 06:52:01 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
import
|
|
|
|
# Standard library
|
2021-03-23 06:57:10 +00:00
|
|
|
std/[os, osproc, sequtils, streams, tables],
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
# Nimble packages
|
2021-05-20 10:44:13 +00:00
|
|
|
stew/[assign2, byteutils, objects],
|
2021-03-26 14:11:06 +00:00
|
|
|
chronos, metrics,
|
2021-08-28 22:27:51 +00:00
|
|
|
chronicles, chronicles/timings,
|
2020-05-06 13:23:45 +00:00
|
|
|
json_serialization/std/[options, sets, net], serialization/errors,
|
2020-05-14 11:19:10 +00:00
|
|
|
eth/db/kvstore,
|
2021-05-12 12:31:02 +00:00
|
|
|
eth/keys, eth/p2p/discoveryv5/[protocol, enr],
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
# Local modules
|
2021-08-12 13:08:20 +00:00
|
|
|
../spec/datatypes/[phase0, altair, merge],
|
2021-08-18 18:57:58 +00:00
|
|
|
../spec/[
|
|
|
|
eth2_merkleization, forks, helpers, network, signatures, state_transition],
|
2021-03-04 09:13:44 +00:00
|
|
|
../consensus_object_pools/[
|
2021-08-28 22:27:51 +00:00
|
|
|
spec_cache, blockchain_dag, block_clearance, attestation_pool, exit_pool,
|
|
|
|
sync_committee_msg_pool],
|
2021-03-03 06:23:05 +00:00
|
|
|
../eth1/eth1_monitor,
|
2021-03-05 13:12:00 +00:00
|
|
|
../networking/eth2_network,
|
2021-08-18 18:57:58 +00:00
|
|
|
../sszdump, ../sync/sync_manager,
|
2021-08-12 13:08:20 +00:00
|
|
|
../gossip_processing/consensus_manager,
|
|
|
|
".."/[conf, beacon_clock, beacon_node_common, beacon_node_types, version],
|
2021-08-24 19:49:51 +00:00
|
|
|
"."/[slashing_protection, validator_pool, keystore_management]
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
# Metrics for tracking attestation and beacon block loss
|
2020-11-11 13:39:36 +00:00
|
|
|
const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05,
|
2020-11-11 12:14:09 +00:00
|
|
|
0.05, 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, Inf]
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
declareCounter beacon_attestations_sent,
|
|
|
|
"Number of beacon chain attestations sent by this peer"
|
2021-08-28 22:27:51 +00:00
|
|
|
|
2020-11-11 12:14:09 +00:00
|
|
|
declareHistogram beacon_attestation_sent_delay,
|
|
|
|
"Time(s) between slot start and attestation sent moment",
|
|
|
|
buckets = delayBuckets
|
2021-08-28 22:27:51 +00:00
|
|
|
|
|
|
|
declareCounter beacon_sync_committee_messages_sent,
|
|
|
|
"Number of sync committee messages sent by this peer"
|
|
|
|
|
|
|
|
declareCounter beacon_sync_committee_contributions_sent,
|
|
|
|
"Number of sync committee contributions sent by this peer"
|
|
|
|
|
|
|
|
declareHistogram beacon_sync_committee_message_sent_delay,
|
|
|
|
"Time(s) between slot start and sync committee message sent moment",
|
|
|
|
buckets = delayBuckets
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
declareCounter beacon_blocks_proposed,
|
|
|
|
"Number of beacon chain blocks sent by this peer"
|
|
|
|
|
2020-11-27 23:34:25 +00:00
|
|
|
declareGauge(attached_validator_balance,
|
|
|
|
"Validator balance at slot end of the first 64 validators, in Gwei",
|
|
|
|
labels = ["pubkey"])
|
2021-08-28 22:27:51 +00:00
|
|
|
|
2020-11-28 18:53:51 +00:00
|
|
|
declarePublicGauge(attached_validator_balance_total,
|
2020-11-27 23:34:25 +00:00
|
|
|
"Validator balance of all attached validators, in Gwei")
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
logScope: topics = "beacval"
|
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
type
|
|
|
|
SendResult* = Result[void, cstring]
|
|
|
|
SendBlockResult* = Result[bool, cstring]
|
2021-08-29 14:50:21 +00:00
|
|
|
ForkedBlockResult* = Result[ForkedBeaconBlock, string]
|
2021-08-23 10:41:48 +00:00
|
|
|
|
2021-04-13 13:05:44 +00:00
|
|
|
proc findValidator(validators: auto, pubKey: ValidatorPubKey):
|
2020-11-27 23:34:25 +00:00
|
|
|
Option[ValidatorIndex] =
|
2021-06-11 17:51:46 +00:00
|
|
|
let idx = validators.findIt(it.pubKey == pubKey)
|
2020-05-06 13:23:45 +00:00
|
|
|
if idx == -1:
|
|
|
|
# We allow adding a validator even if its key is not in the state registry:
|
|
|
|
# it might be that the deposit for this validator has not yet been processed
|
2020-11-27 23:34:25 +00:00
|
|
|
notice "Validator deposit not yet processed, monitoring", pubKey
|
|
|
|
none(ValidatorIndex)
|
|
|
|
else:
|
|
|
|
some(idx.ValidatorIndex)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2021-04-16 08:49:37 +00:00
|
|
|
proc addLocalValidator(node: BeaconNode,
|
|
|
|
privKey: ValidatorPrivKey) =
|
2021-07-13 11:15:07 +00:00
|
|
|
node.attachedValidators[].addLocalValidator(privKey)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-07-30 15:48:25 +00:00
|
|
|
proc addLocalValidators*(node: BeaconNode) =
|
|
|
|
for validatorKey in node.config.validatorKeys:
|
2021-07-13 11:15:07 +00:00
|
|
|
node.addLocalValidator(validatorKey)
|
2020-06-11 12:13:12 +00:00
|
|
|
|
2021-03-26 06:52:01 +00:00
|
|
|
proc addRemoteValidators*(node: BeaconNode) {.raises: [Defect, OSError, IOError].} =
|
2020-09-01 13:44:40 +00:00
|
|
|
# load all the validators from the child process - loop until `end`
|
|
|
|
var line = newStringOfCap(120).TaintedString
|
|
|
|
while line != "end" and running(node.vcProcess):
|
|
|
|
if node.vcProcess.outputStream.readLine(line) and line != "end":
|
2020-11-27 23:34:25 +00:00
|
|
|
let
|
performance fixes (#2259)
* performance fixes
* don't mark tree cache as dirty on read-only List accesses
* store only blob in memory for keys and signatures, parse blob lazily
* compare public keys by blob instead of parsing / converting to raw
* compare Eth2Digest using non-constant-time comparison
* avoid some unnecessary validator copying
This branch will in particular speed up deposit processing which has
been slowing down block replay.
Pre (mainnet, 1600 blocks):
```
All time are ms
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3450.269, 0.000, 3450.269, 3450.269, 1, Initialize DB
0.417, 0.822, 0.036, 21.098, 1400, Load block from database
16.521, 0.000, 16.521, 16.521, 1, Load state from database
27.906, 50.846, 8.104, 1507.633, 1350, Apply block
52.617, 37.029, 20.640, 135.938, 50, Apply epoch block
```
Post:
```
3502.715, 0.000, 3502.715, 3502.715, 1, Initialize DB
0.080, 0.560, 0.035, 21.015, 1400, Load block from database
17.595, 0.000, 17.595, 17.595, 1, Load state from database
15.706, 11.028, 8.300, 107.537, 1350, Apply block
33.217, 12.622, 17.331, 60.580, 50, Apply epoch block
```
* more perf fixes
* load EpochRef cache into StateCache more aggressively
* point out security concern with public key cache
* reuse proposer index from state when processing block
* avoid genericAssign in a few more places
* don't parse key when signature is unparseable
* fix `==` overload for Eth2Digest
* preallocate validator list when getting active validators
* speed up proposer index calculation a little bit
* reuse cache when replaying blocks in ncli_db
* avoid a few more copying loops
```
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3279.158, 0.000, 3279.158, 3279.158, 1, Initialize DB
0.072, 0.357, 0.035, 13.400, 1400, Load block from database
17.295, 0.000, 17.295, 17.295, 1, Load state from database
5.918, 9.896, 0.198, 98.028, 1350, Apply block
15.888, 10.951, 7.902, 39.535, 50, Apply epoch block
0.000, 0.000, 0.000, 0.000, 0, Database block store
```
* clear full balance cache before processing rewards and penalties
```
All time are ms
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3947.901, 0.000, 3947.901, 3947.901, 1, Initialize DB
0.124, 0.506, 0.026, 202.370, 363345, Load block from database
97.614, 0.000, 97.614, 97.614, 1, Load state from database
0.186, 0.188, 0.012, 99.561, 357262, Advance slot, non-epoch
14.161, 5.966, 1.099, 395.511, 11524, Advance slot, epoch
1.372, 4.170, 0.017, 276.401, 363345, Apply block, no slot processing
0.000, 0.000, 0.000, 0.000, 0, Database block store
```
2021-01-25 12:04:18 +00:00
|
|
|
key = ValidatorPubKey.fromHex(line).get()
|
2021-04-13 13:05:44 +00:00
|
|
|
index = findValidator(
|
2021-06-11 17:51:46 +00:00
|
|
|
getStateField(node.dag.headState.data, validators).asSeq, key)
|
2021-06-01 11:13:40 +00:00
|
|
|
pk = key.load()
|
|
|
|
if pk.isSome():
|
2021-07-13 11:15:07 +00:00
|
|
|
let v = AttachedValidator(pubKey: key,
|
2021-06-01 11:13:40 +00:00
|
|
|
index: index,
|
|
|
|
kind: ValidatorKind.remote,
|
|
|
|
connection: ValidatorConnection(
|
|
|
|
inStream: node.vcProcess.inputStream,
|
|
|
|
outStream: node.vcProcess.outputStream,
|
|
|
|
pubKeyStr: $key))
|
2021-07-13 11:15:07 +00:00
|
|
|
node.attachedValidators[].addRemoteValidator(key, v)
|
2021-06-01 11:13:40 +00:00
|
|
|
else:
|
|
|
|
warn "Could not load public key", line
|
2020-09-01 13:44:40 +00:00
|
|
|
|
2020-08-10 13:21:31 +00:00
|
|
|
proc getAttachedValidator*(node: BeaconNode,
|
|
|
|
pubkey: ValidatorPubKey): AttachedValidator =
|
2021-02-22 16:17:48 +00:00
|
|
|
node.attachedValidators[].getValidator(pubkey)
|
2020-08-10 13:21:31 +00:00
|
|
|
|
2020-07-23 18:24:38 +00:00
|
|
|
proc getAttachedValidator*(node: BeaconNode,
|
2021-04-13 13:05:44 +00:00
|
|
|
state_validators: auto,
|
2020-06-11 12:13:12 +00:00
|
|
|
idx: ValidatorIndex): AttachedValidator =
|
2021-04-13 13:05:44 +00:00
|
|
|
if idx < state_validators.len.ValidatorIndex:
|
|
|
|
let validator = node.getAttachedValidator(state_validators[idx].pubkey)
|
2021-06-29 15:09:29 +00:00
|
|
|
if validator != nil and validator.index != some(idx):
|
2020-11-27 23:34:25 +00:00
|
|
|
# Update index, in case the validator was activated!
|
|
|
|
notice "Validator activated", pubkey = validator.pubkey, index = idx
|
2021-06-29 15:09:29 +00:00
|
|
|
validator.index = some(idx)
|
2020-11-27 23:34:25 +00:00
|
|
|
validator
|
2020-08-10 13:21:31 +00:00
|
|
|
else:
|
|
|
|
warn "Validator index out of bounds",
|
2021-04-13 13:05:44 +00:00
|
|
|
idx, validators = state_validators.len
|
2020-08-10 13:21:31 +00:00
|
|
|
nil
|
|
|
|
|
|
|
|
proc getAttachedValidator*(node: BeaconNode,
|
|
|
|
epochRef: EpochRef,
|
|
|
|
idx: ValidatorIndex): AttachedValidator =
|
2021-06-10 07:37:02 +00:00
|
|
|
let key = epochRef.validatorKey(idx)
|
|
|
|
if key.isSome():
|
|
|
|
let validator = node.getAttachedValidator(key.get().toPubKey())
|
2020-11-27 23:34:25 +00:00
|
|
|
if validator != nil and validator.index != some(idx.ValidatorIndex):
|
|
|
|
# Update index, in case the validator was activated!
|
|
|
|
notice "Validator activated", pubkey = validator.pubkey, index = idx
|
|
|
|
validator.index = some(idx.ValidatorIndex)
|
|
|
|
validator
|
2020-08-10 13:21:31 +00:00
|
|
|
else:
|
2021-06-10 07:37:02 +00:00
|
|
|
warn "Validator key not found",
|
|
|
|
idx, epoch = epochRef.epoch
|
2020-08-10 13:21:31 +00:00
|
|
|
nil
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-07-08 10:11:22 +00:00
|
|
|
proc isSynced*(node: BeaconNode, head: BlockRef): bool =
|
2020-05-06 13:23:45 +00:00
|
|
|
## TODO This function is here as a placeholder for some better heurestics to
|
|
|
|
## determine if we're in sync and should be producing blocks and
|
|
|
|
## attestations. Generally, the problem is that slot time keeps advancing
|
|
|
|
## even when there are no blocks being produced, so there's no way to
|
|
|
|
## distinguish validators geniunely going missing from the node not being
|
|
|
|
## well connected (during a network split or an internet outage for
|
|
|
|
## example). It would generally be correct to simply keep running as if
|
|
|
|
## we were the only legit node left alive, but then we run into issues:
|
|
|
|
## with enough many empty slots, the validator pool is emptied leading
|
|
|
|
## to empty committees and lots of empty slot processing that will be
|
|
|
|
## thrown away as soon as we're synced again.
|
|
|
|
|
|
|
|
let
|
|
|
|
# The slot we should be at, according to the clock
|
|
|
|
beaconTime = node.beaconClock.now()
|
|
|
|
wallSlot = beaconTime.toSlot()
|
|
|
|
|
2020-09-16 11:30:03 +00:00
|
|
|
# TODO: MaxEmptySlotCount should likely involve the weak subjectivity period.
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
# TODO if everyone follows this logic, the network will not recover from a
|
|
|
|
# halt: nobody will be producing blocks because everone expects someone
|
|
|
|
# else to do it
|
|
|
|
if wallSlot.afterGenesis and head.slot + MaxEmptySlotCount < wallSlot.slot:
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
true
|
|
|
|
|
2020-06-23 10:38:59 +00:00
|
|
|
proc sendAttestation*(
|
2021-05-10 07:13:36 +00:00
|
|
|
node: BeaconNode, attestation: Attestation,
|
|
|
|
subnet_id: SubnetId, checkSignature: bool): Future[bool] {.async.} =
|
|
|
|
# Validate attestation before sending it via gossip - validation will also
|
|
|
|
# register the attestation with the attestation pool. Notably, although
|
|
|
|
# libp2p calls the data handler for any subscription on the subnet
|
|
|
|
# topic, it does not perform validation.
|
|
|
|
let ok = await node.processor.attestationValidator(
|
|
|
|
attestation, subnet_id, checkSignature)
|
|
|
|
|
|
|
|
return case ok
|
|
|
|
of ValidationResult.Accept:
|
2021-08-23 10:41:48 +00:00
|
|
|
node.network.broadcastAttestation(subnet_id, attestation)
|
2021-05-10 07:13:36 +00:00
|
|
|
beacon_attestations_sent.inc()
|
2021-09-22 12:17:15 +00:00
|
|
|
if not(isNil(node.onAttestationSent)):
|
|
|
|
node.onAttestationSent(attestation)
|
2021-05-10 07:13:36 +00:00
|
|
|
true
|
|
|
|
else:
|
|
|
|
notice "Produced attestation failed validation",
|
|
|
|
attestation = shortLog(attestation),
|
|
|
|
result = $ok
|
|
|
|
false
|
2020-06-05 09:57:40 +00:00
|
|
|
|
2021-08-28 22:27:51 +00:00
|
|
|
proc sendSyncCommitteeMessage*(
|
|
|
|
node: BeaconNode, msg: SyncCommitteeMessage,
|
2021-09-23 22:13:25 +00:00
|
|
|
committeeIdx: SyncCommitteeIndex,
|
|
|
|
checkSignature: bool): Future[SendResult] {.async.} =
|
2021-08-28 22:27:51 +00:00
|
|
|
# Validate sync committee message before sending it via gossip
|
|
|
|
# validation will also register the message with the sync committee
|
|
|
|
# message pool. Notably, although libp2p calls the data handler for
|
|
|
|
# any subscription on the subnet topic, it does not perform validation.
|
2021-09-23 22:13:25 +00:00
|
|
|
let res = node.processor.syncCommitteeMsgValidator(msg, committeeIdx,
|
|
|
|
checkSignature)
|
|
|
|
return
|
|
|
|
case res
|
2021-08-28 22:27:51 +00:00
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastSyncCommitteeMessage(msg, committeeIdx)
|
|
|
|
beacon_sync_committee_messages_sent.inc()
|
2021-09-23 22:13:25 +00:00
|
|
|
SendResult.ok()
|
2021-08-28 22:27:51 +00:00
|
|
|
else:
|
2021-09-23 22:13:25 +00:00
|
|
|
notice "Sync committee message failed validation",
|
|
|
|
msg, result = $res
|
|
|
|
SendResult.err("Sync committee message failed validation")
|
|
|
|
|
|
|
|
proc sendSyncCommitteeMessages*(node: BeaconNode,
|
|
|
|
msgs: seq[SyncCommitteeMessage]
|
|
|
|
): Future[seq[SendResult]] {.async.} =
|
|
|
|
let validators = getStateField(node.dag.headState.data, validators)
|
|
|
|
var statuses = newSeq[Option[SendResult]](len(msgs))
|
|
|
|
|
|
|
|
let ranges =
|
|
|
|
block:
|
|
|
|
let
|
|
|
|
headSlot = getStateField(node.dag.headState.data, slot)
|
|
|
|
headCommitteePeriod = syncCommitteePeriod(headSlot)
|
|
|
|
currentStart = syncCommitteePeriodStartSlot(headCommitteePeriod)
|
|
|
|
currentFinish = currentStart + SLOTS_PER_SYNC_COMMITTEE_PERIOD
|
|
|
|
nextStart = currentFinish
|
|
|
|
nextFinish = nextStart + SLOTS_PER_SYNC_COMMITTEE_PERIOD
|
|
|
|
(curStart: Slot(currentStart), curFinish: Slot(currentFinish),
|
|
|
|
nxtStart: Slot(nextStart), nxtFinish: Slot(nextFinish))
|
|
|
|
|
|
|
|
let (keysCur, keysNxt) =
|
|
|
|
block:
|
|
|
|
var resCur: Table[ValidatorPubKey, int]
|
|
|
|
var resNxt: Table[ValidatorPubKey, int]
|
|
|
|
for index, msg in msgs.pairs():
|
|
|
|
if msg.validator_index < lenu64(validators):
|
|
|
|
if (msg.slot >= ranges.curStart) and (msg.slot < ranges.curFinish):
|
|
|
|
resCur[validators[msg.validator_index].pubkey] = index
|
|
|
|
elif (msg.slot >= ranges.nxtStart) and (msg.slot < ranges.nxtFinish):
|
|
|
|
resNxt[validators[msg.validator_index].pubkey] = index
|
|
|
|
else:
|
|
|
|
statuses[index] =
|
|
|
|
some(SendResult.err("Message's slot out of state's head range"))
|
|
|
|
else:
|
|
|
|
statuses[index] = some(SendResult.err("Incorrect validator's index"))
|
|
|
|
if (len(resCur) == 0) and (len(resNxt) == 0):
|
|
|
|
return statuses.mapIt(it.get())
|
|
|
|
(resCur, resNxt)
|
|
|
|
|
|
|
|
template curParticipants(): untyped =
|
|
|
|
node.dag.headState.data.hbsAltair.data.current_sync_committee.pubkeys.data
|
|
|
|
template nxtParticipants(): untyped =
|
|
|
|
node.dag.headState.data.hbsAltair.data.next_sync_committee.pubkeys.data
|
|
|
|
|
|
|
|
let (pending, indices) =
|
|
|
|
block:
|
|
|
|
var resFutures: seq[Future[SendResult]]
|
|
|
|
var resIndices: seq[int]
|
|
|
|
for committeeIdx in allSyncCommittees():
|
|
|
|
for valKey in syncSubcommittee(curParticipants(), committeeIdx):
|
|
|
|
let index = keysCur.getOrDefault(valKey, -1)
|
|
|
|
if index >= 0:
|
|
|
|
resIndices.add(index)
|
|
|
|
resFutures.add(node.sendSyncCommitteeMessage(msgs[index],
|
|
|
|
committeeIdx, true))
|
|
|
|
for committeeIdx in allSyncCommittees():
|
|
|
|
for valKey in syncSubcommittee(nxtParticipants(), committeeIdx):
|
|
|
|
let index = keysNxt.getOrDefault(valKey, -1)
|
|
|
|
if index >= 0:
|
|
|
|
resIndices.add(index)
|
|
|
|
resFutures.add(node.sendSyncCommitteeMessage(msgs[index],
|
|
|
|
committeeIdx, true))
|
|
|
|
(resFutures, resIndices)
|
|
|
|
|
|
|
|
await allFutures(pending)
|
|
|
|
|
|
|
|
for index, future in pending.pairs():
|
|
|
|
if future.done():
|
|
|
|
let fres = future.read()
|
|
|
|
if fres.isErr():
|
|
|
|
statuses[indices[index]] = some(SendResult.err(fres.error()))
|
|
|
|
else:
|
|
|
|
statuses[indices[index]] = some(SendResult.ok())
|
|
|
|
elif future.failed() or future.cancelled():
|
|
|
|
let exc = future.readError()
|
|
|
|
debug "Unexpected failure while sending committee message",
|
|
|
|
message = msgs[indices[index]], error = $exc.msg
|
|
|
|
statuses[indices[index]] = some(SendResult.err(
|
|
|
|
"Unexpected failure while sending committee message"))
|
|
|
|
|
|
|
|
let results =
|
|
|
|
block:
|
|
|
|
var res: seq[SendResult]
|
|
|
|
for item in statuses:
|
|
|
|
if item.isSome():
|
|
|
|
res.add(item.get())
|
|
|
|
else:
|
|
|
|
res.add(SendResult.err("Message validator not in sync committee"))
|
|
|
|
res
|
|
|
|
return results
|
2021-08-28 22:27:51 +00:00
|
|
|
|
|
|
|
proc sendSyncCommitteeContribution*(
|
|
|
|
node: BeaconNode,
|
|
|
|
msg: SignedContributionAndProof,
|
2021-09-23 22:13:25 +00:00
|
|
|
checkSignature: bool): Future[SendResult] {.async.} =
|
2021-08-28 22:27:51 +00:00
|
|
|
let ok = node.processor.syncCommitteeContributionValidator(
|
|
|
|
msg, checkSignature)
|
|
|
|
|
|
|
|
return case ok
|
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastSignedContributionAndProof(msg)
|
|
|
|
beacon_sync_committee_contributions_sent.inc()
|
2021-09-23 22:13:25 +00:00
|
|
|
SendResult.ok()
|
2021-08-28 22:27:51 +00:00
|
|
|
else:
|
2021-09-23 22:13:25 +00:00
|
|
|
notice "Sync committee contribution failed validation",
|
2021-08-28 22:27:51 +00:00
|
|
|
msg, result = $ok
|
2021-09-23 22:13:25 +00:00
|
|
|
SendResult.err("Sync committee contribution failed validation")
|
2021-08-28 22:27:51 +00:00
|
|
|
|
2020-06-05 09:57:40 +00:00
|
|
|
proc createAndSendAttestation(node: BeaconNode,
|
|
|
|
fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
validator: AttachedValidator,
|
|
|
|
attestationData: AttestationData,
|
|
|
|
committeeLen: int,
|
2020-06-23 10:38:59 +00:00
|
|
|
indexInCommittee: int,
|
2021-05-10 07:13:36 +00:00
|
|
|
subnet_id: SubnetId) {.async.} =
|
|
|
|
try:
|
|
|
|
var
|
|
|
|
attestation = await validator.produceAndSignAttestation(
|
|
|
|
attestationData, committeeLen, indexInCommittee, fork,
|
|
|
|
genesis_validators_root)
|
|
|
|
|
|
|
|
let ok = await node.sendAttestation(
|
|
|
|
attestation, subnet_id, checkSignature = false)
|
|
|
|
if not ok: # Logged in sendAttestation
|
|
|
|
return
|
|
|
|
|
|
|
|
if node.config.dumpEnabled:
|
2021-07-13 11:15:07 +00:00
|
|
|
dump(node.config.dumpDirOutgoing, attestation.data,
|
|
|
|
validator.pubKey)
|
2021-05-10 07:13:36 +00:00
|
|
|
|
|
|
|
let wallTime = node.beaconClock.now()
|
|
|
|
let deadline = attestationData.slot.toBeaconTime() +
|
|
|
|
seconds(int(SECONDS_PER_SLOT div 3))
|
|
|
|
|
|
|
|
let (delayStr, delaySecs) =
|
|
|
|
if wallTime < deadline:
|
|
|
|
("-" & $(deadline - wallTime), -toFloatSeconds(deadline - wallTime))
|
|
|
|
else:
|
|
|
|
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
|
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
notice "Attestation sent",
|
|
|
|
attestation = shortLog(attestation), validator = shortLog(validator),
|
|
|
|
delay = delayStr
|
2021-05-10 07:13:36 +00:00
|
|
|
|
|
|
|
beacon_attestation_sent_delay.observe(delaySecs)
|
|
|
|
except CatchableError as exc:
|
|
|
|
# An error could happen here when the signature task fails - we must
|
|
|
|
# not leak the exception because this is an asyncSpawn task
|
|
|
|
notice "Error sending attestation", err = exc.msg
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
proc getBlockProposalEth1Data*(node: BeaconNode,
|
2021-06-11 17:51:46 +00:00
|
|
|
state: ForkedHashedBeaconState):
|
|
|
|
BlockProposalEth1Data =
|
2020-11-19 17:19:03 +00:00
|
|
|
if node.eth1Monitor.isNil:
|
2021-04-14 09:34:35 +00:00
|
|
|
var pendingDepositsCount =
|
2021-06-11 17:51:46 +00:00
|
|
|
getStateField(state, eth1_data).deposit_count -
|
|
|
|
getStateField(state, eth1_deposit_index)
|
2020-11-24 21:21:47 +00:00
|
|
|
if pendingDepositsCount > 0:
|
|
|
|
result.hasMissingDeposits = true
|
|
|
|
else:
|
2021-06-11 17:51:46 +00:00
|
|
|
result.vote = getStateField(state, eth1_data)
|
2020-11-19 17:19:03 +00:00
|
|
|
else:
|
2021-06-01 11:13:40 +00:00
|
|
|
let finalizedEpochRef = node.dag.getFinalizedEpochRef()
|
2020-11-24 21:21:47 +00:00
|
|
|
result = node.eth1Monitor.getBlockProposalData(
|
2021-06-11 17:51:46 +00:00
|
|
|
state, finalizedEpochRef.eth1_data,
|
2021-04-14 09:34:35 +00:00
|
|
|
finalizedEpochRef.eth1_deposit_index)
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2021-05-20 10:44:13 +00:00
|
|
|
func getOpaqueTransaction(s: string): OpaqueTransaction =
|
|
|
|
try:
|
|
|
|
# Effectively an internal logic error in the Eth1/Eth2 client system, as
|
|
|
|
# it's not possible to just omit a malformatted transaction: it would be
|
|
|
|
# the wrong ExecutionPayload blockHash overall, and rejected by newBlock
|
|
|
|
# when one attempted to reinsert it into Geth (which, while not all Eth2
|
|
|
|
# clients might connect to, some will). It's also not possible to skip a
|
|
|
|
# whole ExecutionPayload being that it's an integral part of BeaconBlock
|
|
|
|
# construction. So not much better to do than bail if an incoming string
|
|
|
|
# representation of the OpaqueTransaction is invalid. init() could catch
|
|
|
|
# this, but it'd make its interface clumsier in a way it doesn't .add().
|
|
|
|
let opaqueTransactionSeq = hexToSeqByte(s)
|
|
|
|
if opaqueTransactionSeq.len > MAX_BYTES_PER_OPAQUE_TRANSACTION:
|
|
|
|
raiseAssert "Execution engine returned too-long opaque transaction"
|
|
|
|
OpaqueTransaction(List[byte, MAX_BYTES_PER_OPAQUE_TRANSACTION].init(
|
|
|
|
opaqueTransactionSeq))
|
|
|
|
except ValueError:
|
|
|
|
raiseAssert "Execution engine returned invalidly formatted transaction"
|
|
|
|
|
2020-05-22 17:04:52 +00:00
|
|
|
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
2020-10-22 10:53:33 +00:00
|
|
|
randao_reveal: ValidatorSig,
|
2020-05-22 17:04:52 +00:00
|
|
|
validator_index: ValidatorIndex,
|
2020-06-29 17:30:19 +00:00
|
|
|
graffiti: GraffitiBytes,
|
2021-08-27 09:00:06 +00:00
|
|
|
head: BlockRef, slot: Slot
|
2021-08-29 14:50:21 +00:00
|
|
|
): Future[ForkedBlockResult] {.async.} =
|
2020-10-22 10:53:33 +00:00
|
|
|
# Advance state to the slot that we're proposing for
|
2021-03-17 10:17:15 +00:00
|
|
|
|
|
|
|
let
|
2021-06-01 11:13:40 +00:00
|
|
|
proposalState = assignClone(node.dag.headState)
|
2021-03-17 10:17:15 +00:00
|
|
|
proposalStateAddr = unsafeAddr proposalState[]
|
|
|
|
|
2021-06-01 11:13:40 +00:00
|
|
|
node.dag.withState(proposalState[], head.atSlot(slot)):
|
2020-05-22 14:21:22 +00:00
|
|
|
let
|
2021-06-11 17:51:46 +00:00
|
|
|
eth1Proposal = node.getBlockProposalEth1Data(stateData.data)
|
2021-06-01 11:13:40 +00:00
|
|
|
poolPtr = unsafeAddr node.dag # safe because restore is short-lived
|
2020-05-22 14:21:22 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
if eth1Proposal.hasMissingDeposits:
|
|
|
|
error "Eth1 deposits not available. Skipping block proposal", slot
|
2021-08-29 14:50:21 +00:00
|
|
|
return ForkedBlockResult.err("Eth1 deposits not available")
|
|
|
|
|
|
|
|
let doPhase0 = slot.epoch < node.dag.cfg.ALTAIR_FORK_EPOCH
|
|
|
|
return if doPhase0:
|
|
|
|
func restore(v: var phase0.HashedBeaconState) =
|
|
|
|
# TODO address this ugly workaround - there should probably be a
|
|
|
|
# `state_transition` that takes a `StateData` instead and updates
|
|
|
|
# the block as well
|
|
|
|
doAssert v.addr == addr proposalStateAddr.data.hbsPhase0
|
|
|
|
assign(proposalStateAddr[], poolPtr.headState)
|
|
|
|
|
|
|
|
makeBeaconBlock(
|
|
|
|
node.dag.cfg,
|
|
|
|
stateData.data.hbsPhase0,
|
|
|
|
validator_index,
|
|
|
|
head.root,
|
|
|
|
randao_reveal,
|
|
|
|
eth1Proposal.vote,
|
|
|
|
graffiti,
|
|
|
|
node.attestationPool[].getAttestationsForBlock(
|
|
|
|
stateData.data.hbsPhase0, cache),
|
|
|
|
eth1Proposal.deposits,
|
|
|
|
node.exitPool[].getProposerSlashingsForBlock(),
|
|
|
|
node.exitPool[].getAttesterSlashingsForBlock(),
|
|
|
|
node.exitPool[].getVoluntaryExitsForBlock(),
|
|
|
|
default(ExecutionPayload),
|
|
|
|
restore,
|
|
|
|
cache).map(proc (t: auto): auto = ForkedBeaconBlock.init(t))
|
|
|
|
else:
|
|
|
|
func restore(v: var altair.HashedBeaconState) =
|
|
|
|
# TODO address this ugly workaround - there should probably be a
|
|
|
|
# `state_transition` that takes a `StateData` instead and updates
|
|
|
|
# the block as well
|
|
|
|
doAssert v.addr == addr proposalStateAddr.data.hbsAltair
|
|
|
|
assign(proposalStateAddr[], poolPtr.headState)
|
|
|
|
|
|
|
|
makeBeaconBlock(
|
|
|
|
node.dag.cfg,
|
|
|
|
stateData.data.hbsAltair,
|
|
|
|
validator_index,
|
|
|
|
head.root,
|
|
|
|
randao_reveal,
|
|
|
|
eth1Proposal.vote,
|
|
|
|
graffiti,
|
|
|
|
node.attestationPool[].getAttestationsForBlock(
|
|
|
|
stateData.data.hbsAltair, cache),
|
|
|
|
eth1Proposal.deposits,
|
|
|
|
node.exitPool[].getProposerSlashingsForBlock(),
|
|
|
|
node.exitPool[].getAttesterSlashingsForBlock(),
|
|
|
|
node.exitPool[].getVoluntaryExitsForBlock(),
|
2021-08-30 01:00:37 +00:00
|
|
|
node.sync_committee_msg_pool[].produceSyncAggregate(head.root),
|
2021-08-29 14:50:21 +00:00
|
|
|
default(ExecutionPayload),
|
|
|
|
restore,
|
|
|
|
cache).map(proc (t: auto): auto = ForkedBeaconBlock.init(t))
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-06-05 09:57:40 +00:00
|
|
|
proc proposeSignedBlock*(node: BeaconNode,
|
|
|
|
head: BlockRef,
|
|
|
|
validator: AttachedValidator,
|
2021-08-29 14:50:21 +00:00
|
|
|
newBlock: ForkedSignedBeaconBlock):
|
2021-06-29 15:09:29 +00:00
|
|
|
Future[BlockRef] {.async.} =
|
2021-08-29 14:50:21 +00:00
|
|
|
let newBlockRef =
|
|
|
|
case newBlock.kind:
|
|
|
|
of BeaconBlockFork.Phase0:
|
|
|
|
node.dag.addRawBlock(node.quarantine, newBlock.phase0Block) do (
|
|
|
|
blckRef: BlockRef, trustedBlock: phase0.TrustedSignedBeaconBlock,
|
|
|
|
epochRef: EpochRef):
|
|
|
|
# Callback add to fork choice if signed block valid (and becomes trusted)
|
|
|
|
node.attestationPool[].addForkChoice(
|
|
|
|
epochRef, blckRef, trustedBlock.message,
|
|
|
|
node.beaconClock.now().slotOrZero())
|
|
|
|
of BeaconBlockFork.Altair:
|
|
|
|
node.dag.addRawBlock(node.quarantine, newBlock.altairBlock) do (
|
|
|
|
blckRef: BlockRef, trustedBlock: altair.TrustedSignedBeaconBlock,
|
|
|
|
epochRef: EpochRef):
|
|
|
|
# Callback add to fork choice if signed block valid (and becomes trusted)
|
|
|
|
node.attestationPool[].addForkChoice(
|
|
|
|
epochRef, blckRef, trustedBlock.message,
|
|
|
|
node.beaconClock.now().slotOrZero())
|
2020-07-09 09:29:32 +00:00
|
|
|
|
2020-06-05 09:57:40 +00:00
|
|
|
if newBlockRef.isErr:
|
2021-08-29 14:50:21 +00:00
|
|
|
withBlck(newBlock):
|
|
|
|
warn "Unable to add proposed block to block pool",
|
|
|
|
newBlock = blck.message, root = blck.root
|
2020-06-05 09:57:40 +00:00
|
|
|
return head
|
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
withBlck(newBlock):
|
|
|
|
notice "Block proposed",
|
|
|
|
blck = shortLog(blck.message), root = blck.root,
|
|
|
|
validator = shortLog(validator)
|
2020-06-05 09:57:40 +00:00
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
if node.config.dumpEnabled:
|
|
|
|
dump(node.config.dumpDirOutgoing, blck)
|
2020-06-05 09:57:40 +00:00
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
node.network.broadcastBeaconBlock(newBlock)
|
2020-06-05 09:57:40 +00:00
|
|
|
|
|
|
|
beacon_blocks_proposed.inc()
|
|
|
|
|
|
|
|
return newBlockRef[]
|
|
|
|
|
2020-05-22 17:04:52 +00:00
|
|
|
proc proposeBlock(node: BeaconNode,
|
|
|
|
validator: AttachedValidator,
|
|
|
|
validator_index: ValidatorIndex,
|
|
|
|
head: BlockRef,
|
|
|
|
slot: Slot): Future[BlockRef] {.async.} =
|
|
|
|
if head.slot >= slot:
|
|
|
|
# We should normally not have a head newer than the slot we're proposing for
|
|
|
|
# but this can happen if block proposal is delayed
|
|
|
|
warn "Skipping proposal, have newer head already",
|
|
|
|
headSlot = shortLog(head.slot),
|
|
|
|
headBlockRoot = shortLog(head.root),
|
2020-07-16 13:16:51 +00:00
|
|
|
slot = shortLog(slot)
|
2020-05-22 17:04:52 +00:00
|
|
|
return head
|
2020-05-22 14:21:22 +00:00
|
|
|
|
2020-10-22 10:53:33 +00:00
|
|
|
let
|
2021-08-27 09:00:06 +00:00
|
|
|
fork = node.dag.forkAtEpoch(slot.epoch)
|
2020-10-22 10:53:33 +00:00
|
|
|
genesis_validators_root =
|
2021-06-11 17:51:46 +00:00
|
|
|
getStateField(node.dag.headState.data, genesis_validators_root)
|
2020-10-22 10:53:33 +00:00
|
|
|
randao = await validator.genRandaoReveal(
|
|
|
|
fork, genesis_validators_root, slot)
|
2021-08-29 14:50:21 +00:00
|
|
|
var newBlock = await makeBeaconBlockForHeadAndSlot(
|
|
|
|
node, randao, validator_index, node.graffitiBytes, head, slot)
|
2021-05-04 13:17:28 +00:00
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
if newBlock.isErr():
|
2020-05-22 17:04:52 +00:00
|
|
|
return head # already logged elsewhere!
|
2021-05-04 13:17:28 +00:00
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
let blck = newBlock.get()
|
|
|
|
|
|
|
|
# TODO abstract this, or move it into makeBeaconBlockForHeadAndSlot, and in
|
|
|
|
# general this is far too much copy/paste
|
|
|
|
let forked = case blck.kind:
|
|
|
|
of BeaconBlockFork.Phase0:
|
|
|
|
let root = hash_tree_root(blck.phase0Block)
|
|
|
|
|
|
|
|
# TODO: recomputed in block proposal
|
|
|
|
let signing_root = compute_block_root(
|
|
|
|
fork, genesis_validators_root, slot, root)
|
|
|
|
let notSlashable = node.attachedValidators
|
|
|
|
.slashingProtection
|
|
|
|
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
|
|
|
|
|
|
|
|
if notSlashable.isErr:
|
|
|
|
warn "Slashing protection activated",
|
|
|
|
validator = validator.pubkey,
|
|
|
|
slot = slot,
|
|
|
|
existingProposal = notSlashable.error
|
|
|
|
return head
|
|
|
|
|
|
|
|
let signature = await validator.signBlockProposal(
|
|
|
|
fork, genesis_validators_root, slot, root)
|
|
|
|
ForkedSignedBeaconBlock.init(
|
|
|
|
phase0.SignedBeaconBlock(
|
|
|
|
message: blck.phase0Block, root: root, signature: signature)
|
|
|
|
)
|
|
|
|
of BeaconBlockFork.Altair:
|
|
|
|
let root = hash_tree_root(blck.altairBlock)
|
|
|
|
|
|
|
|
# TODO: recomputed in block proposal
|
|
|
|
let signing_root = compute_block_root(
|
|
|
|
fork, genesis_validators_root, slot, root)
|
|
|
|
let notSlashable = node.attachedValidators
|
|
|
|
.slashingProtection
|
|
|
|
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
|
|
|
|
|
|
|
|
if notSlashable.isErr:
|
|
|
|
warn "Slashing protection activated",
|
|
|
|
validator = validator.pubkey,
|
|
|
|
slot = slot,
|
|
|
|
existingProposal = notSlashable.error
|
|
|
|
return head
|
|
|
|
|
|
|
|
let signature = await validator.signBlockProposal(
|
|
|
|
fork, genesis_validators_root, slot, root)
|
|
|
|
|
|
|
|
ForkedSignedBeaconBlock.init(
|
|
|
|
altair.SignedBeaconBlock(
|
|
|
|
message: blck.altairBlock, root: root, signature: signature)
|
2020-05-22 17:04:52 +00:00
|
|
|
)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2021-08-29 14:50:21 +00:00
|
|
|
return await node.proposeSignedBlock(head, validator, forked)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
|
|
|
## Perform all attestations that the validators attached to this node should
|
|
|
|
## perform during the given slot
|
|
|
|
if slot + SLOTS_PER_EPOCH < head.slot:
|
|
|
|
# The latest block we know about is a lot newer than the slot we're being
|
|
|
|
# asked to attest to - this makes it unlikely that it will be included
|
|
|
|
# at all.
|
|
|
|
# TODO the oldest attestations allowed are those that are older than the
|
|
|
|
# finalized epoch.. also, it seems that posting very old attestations
|
|
|
|
# is risky from a slashing perspective. More work is needed here.
|
2020-10-01 18:56:42 +00:00
|
|
|
warn "Skipping attestation, head is too recent",
|
2020-05-06 13:23:45 +00:00
|
|
|
headSlot = shortLog(head.slot),
|
|
|
|
slot = shortLog(slot)
|
|
|
|
return
|
|
|
|
|
|
|
|
let attestationHead = head.atSlot(slot)
|
|
|
|
if head != attestationHead.blck:
|
|
|
|
# In rare cases, such as when we're busy syncing or just slow, we'll be
|
|
|
|
# attesting to a past state - we must then recreate the world as it looked
|
|
|
|
# like back then
|
|
|
|
notice "Attesting to a state in the past, falling behind?",
|
|
|
|
headSlot = shortLog(head.slot),
|
|
|
|
attestationHeadSlot = shortLog(attestationHead.slot),
|
|
|
|
attestationSlot = shortLog(slot)
|
|
|
|
|
|
|
|
trace "Checking attestations",
|
|
|
|
attestationHeadRoot = shortLog(attestationHead.blck.root),
|
2020-07-16 13:16:51 +00:00
|
|
|
attestationSlot = shortLog(slot)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
# We need to run attestations exactly for the slot that we're attesting to.
|
|
|
|
# In case blocks went missing, this means advancing past the latest block
|
|
|
|
# using empty slots as fillers.
|
2021-03-02 06:04:14 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#validator-assignments
|
2020-08-10 13:21:31 +00:00
|
|
|
let
|
2021-06-01 11:13:40 +00:00
|
|
|
epochRef = node.dag.getEpochRef(
|
2020-08-10 13:21:31 +00:00
|
|
|
attestationHead.blck, slot.compute_epoch_at_slot())
|
2021-05-10 07:13:36 +00:00
|
|
|
committees_per_slot = get_committee_count_per_slot(epochRef)
|
2021-08-24 19:49:51 +00:00
|
|
|
fork = node.dag.forkAtEpoch(slot.epoch)
|
2020-08-10 13:21:31 +00:00
|
|
|
genesis_validators_root =
|
2021-06-11 17:51:46 +00:00
|
|
|
getStateField(node.dag.headState.data, genesis_validators_root)
|
2020-08-10 13:21:31 +00:00
|
|
|
|
2021-05-10 07:13:36 +00:00
|
|
|
for committee_index in get_committee_indices(epochRef):
|
|
|
|
let committee = get_beacon_committee(epochRef, slot, committee_index)
|
2020-08-10 13:21:31 +00:00
|
|
|
|
2021-05-04 13:17:28 +00:00
|
|
|
for index_in_committee, validator_index in committee:
|
|
|
|
let validator = node.getAttachedValidator(epochRef, validator_index)
|
2021-05-10 07:13:36 +00:00
|
|
|
if validator == nil:
|
|
|
|
continue
|
|
|
|
|
|
|
|
let
|
|
|
|
data = makeAttestationData(epochRef, attestationHead, committee_index)
|
|
|
|
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
|
|
|
|
signing_root = compute_attestation_root(
|
|
|
|
fork, genesis_validators_root, data)
|
|
|
|
registered = node.attachedValidators
|
|
|
|
.slashingProtection
|
|
|
|
.registerAttestation(
|
|
|
|
validator_index,
|
2021-07-13 11:15:07 +00:00
|
|
|
validator.pubkey,
|
2021-05-10 07:13:36 +00:00
|
|
|
data.source.epoch,
|
|
|
|
data.target.epoch,
|
|
|
|
signing_root)
|
|
|
|
if registered.isOk():
|
|
|
|
let subnet_id = compute_subnet_for_attestation(
|
|
|
|
committees_per_slot, data.slot, data.index.CommitteeIndex)
|
|
|
|
asyncSpawn createAndSendAttestation(
|
|
|
|
node, fork, genesis_validators_root, validator, data,
|
|
|
|
committee.len(), index_in_committee, subnet_id)
|
|
|
|
else:
|
|
|
|
warn "Slashing protection activated for attestation",
|
|
|
|
validator = validator.pubkey,
|
|
|
|
badVoteDetails = $registered.error()
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2021-08-30 00:58:30 +00:00
|
|
|
proc createAndSendSyncCommitteeMessage(node: BeaconNode,
|
|
|
|
slot: Slot,
|
|
|
|
validator: AttachedValidator,
|
|
|
|
committeeIdx: SyncCommitteeIndex,
|
|
|
|
head: BlockRef) {.async.} =
|
|
|
|
try:
|
|
|
|
let
|
|
|
|
fork = node.dag.forkAtEpoch(slot.epoch)
|
|
|
|
genesisValidatorsRoot = node.dag.genesisValidatorsRoot
|
|
|
|
msg = await signSyncCommitteeMessage(validator, slot, fork,
|
|
|
|
genesisValidatorsRoot, head.root)
|
|
|
|
|
2021-09-23 22:13:25 +00:00
|
|
|
let res = await node.sendSyncCommitteeMessage(
|
2021-08-30 00:58:30 +00:00
|
|
|
msg, committeeIdx, checkSignature = false)
|
2021-09-23 22:13:25 +00:00
|
|
|
if res.isErr():
|
|
|
|
# Logged in sendSyncCommitteeMessage
|
2021-08-30 00:58:30 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if node.config.dumpEnabled:
|
|
|
|
dump(node.config.dumpDirOutgoing, msg, validator.pubKey)
|
|
|
|
|
|
|
|
let
|
|
|
|
wallTime = node.beaconClock.now()
|
|
|
|
deadline = msg.slot.toBeaconTime() +
|
|
|
|
seconds(int(SECONDS_PER_SLOT div 3))
|
|
|
|
|
|
|
|
let (delayStr, delaySecs) =
|
|
|
|
if wallTime < deadline:
|
|
|
|
("-" & $(deadline - wallTime), -toFloatSeconds(deadline - wallTime))
|
|
|
|
else:
|
|
|
|
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
|
|
|
|
|
|
|
|
notice "Sync committee message sent",
|
|
|
|
message = shortLog(msg),
|
|
|
|
validator = shortLog(validator),
|
|
|
|
delay = delayStr
|
|
|
|
|
|
|
|
beacon_sync_committee_message_sent_delay.observe(delaySecs)
|
|
|
|
except CatchableError as exc:
|
|
|
|
# An error could happen here when the signature task fails - we must
|
|
|
|
# not leak the exception because this is an asyncSpawn task
|
|
|
|
notice "Error sending sync committee message", err = exc.msg
|
|
|
|
|
|
|
|
proc handleSyncCommitteeMessages(node: BeaconNode, head: BlockRef, slot: Slot) =
|
|
|
|
# TODO Use a view type to avoid the copy
|
|
|
|
var syncCommittee = @(node.dag.syncCommitteeParticipants(slot + 1))
|
|
|
|
|
|
|
|
for committeeIdx in allSyncCommittees():
|
|
|
|
for valKey in syncSubcommittee(syncCommittee, committeeIdx):
|
|
|
|
let validator = node.getAttachedValidator(valKey)
|
2021-09-23 22:13:25 +00:00
|
|
|
if isNil(validator) or validator.index.isNone():
|
2021-08-30 00:58:30 +00:00
|
|
|
continue
|
|
|
|
asyncSpawn createAndSendSyncCommitteeMessage(node, slot, validator,
|
|
|
|
committeeIdx, head)
|
|
|
|
|
|
|
|
proc signAndSendContribution(node: BeaconNode,
|
|
|
|
validator: AttachedValidator,
|
|
|
|
contribution: SyncCommitteeContribution,
|
|
|
|
selectionProof: ValidatorSig) {.async.} =
|
|
|
|
try:
|
|
|
|
let msg = (ref SignedContributionAndProof)(
|
|
|
|
message: ContributionAndProof(
|
|
|
|
aggregator_index: uint64 validator.index.get,
|
|
|
|
contribution: contribution,
|
|
|
|
selection_proof: selectionProof))
|
|
|
|
|
|
|
|
await validator.sign(msg,
|
|
|
|
node.dag.forkAtEpoch(contribution.slot.epoch),
|
|
|
|
node.dag.genesisValidatorsRoot)
|
|
|
|
|
|
|
|
# Failures logged in sendSyncCommitteeContribution
|
|
|
|
discard await node.sendSyncCommitteeContribution(msg[], false)
|
|
|
|
except CatchableError as exc:
|
|
|
|
# An error could happen here when the signature task fails - we must
|
|
|
|
# not leak the exception because this is an asyncSpawn task
|
|
|
|
notice "Error sending sync committee contribution", err = exc.msg
|
|
|
|
|
|
|
|
proc handleSyncCommitteeContributions(node: BeaconNode,
|
|
|
|
head: BlockRef, slot: Slot) {.async.} =
|
|
|
|
# TODO Use a view type to avoid the copy
|
|
|
|
let
|
|
|
|
fork = node.dag.forkAtEpoch(slot.epoch)
|
|
|
|
genesisValidatorsRoot = node.dag.genesisValidatorsRoot
|
|
|
|
syncCommittee = @(node.dag.syncCommitteeParticipants(slot + 1))
|
|
|
|
|
|
|
|
type
|
|
|
|
AggregatorCandidate = object
|
|
|
|
validator: AttachedValidator
|
|
|
|
committeeIdx: SyncCommitteeIndex
|
|
|
|
|
|
|
|
var candidateAggregators: seq[AggregatorCandidate]
|
|
|
|
var selectionProofs: seq[Future[ValidatorSig]]
|
|
|
|
|
|
|
|
var time = timeIt:
|
|
|
|
for committeeIdx in allSyncCommittees():
|
|
|
|
# TODO Hoist outside of the loop with a view type
|
|
|
|
# to avoid the repeated offset calculations
|
|
|
|
for valKey in syncSubcommittee(syncCommittee, committeeIdx):
|
|
|
|
let validator = node.getAttachedValidator(valKey)
|
|
|
|
if validator == nil:
|
|
|
|
continue
|
|
|
|
|
|
|
|
candidateAggregators.add AggregatorCandidate(
|
|
|
|
validator: validator,
|
|
|
|
committeeIdx: committeeIdx)
|
|
|
|
|
|
|
|
selectionProofs.add validator.getSyncCommitteeSelectionProof(
|
|
|
|
fork, genesisValidatorsRoot, slot, committeeIdx.asUInt64)
|
|
|
|
|
|
|
|
await allFutures(selectionProofs)
|
|
|
|
|
|
|
|
debug "Prepared contributions selection proofs",
|
|
|
|
count = selectionProofs.len, time
|
|
|
|
|
|
|
|
var contributionsSent = 0
|
|
|
|
time = timeIt:
|
|
|
|
for i in 0 ..< selectionProofs.len:
|
|
|
|
if not selectionProofs[i].completed:
|
|
|
|
continue
|
|
|
|
|
|
|
|
let selectionProof = selectionProofs[i].read
|
|
|
|
if not is_sync_committee_aggregator(selectionProof):
|
|
|
|
continue
|
|
|
|
|
|
|
|
var contribution: SyncCommitteeContribution
|
|
|
|
let contributionWasProduced = node.syncCommitteeMsgPool[].produceContribution(
|
2021-08-30 01:00:37 +00:00
|
|
|
slot, head.root, candidateAggregators[i].committeeIdx, contribution)
|
2021-08-30 00:58:30 +00:00
|
|
|
|
|
|
|
if contributionWasProduced:
|
|
|
|
asyncSpawn signAndSendContribution(
|
|
|
|
node,
|
|
|
|
candidateAggregators[i].validator,
|
|
|
|
contribution,
|
|
|
|
selectionProof)
|
|
|
|
debug "Contribution sent", contribution = shortLog(contribution)
|
|
|
|
inc contributionsSent
|
|
|
|
else:
|
|
|
|
debug "Failure to produce contribution",
|
|
|
|
slot, head, subnet = candidateAggregators[i].committeeIdx
|
|
|
|
|
2021-08-30 01:00:37 +00:00
|
|
|
if contributionsSent > 0:
|
|
|
|
notice "Contributions sent", count = contributionsSent, time
|
2021-08-30 00:58:30 +00:00
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
|
|
|
|
Future[BlockRef] {.async.} =
|
|
|
|
## Perform the proposal for the given slot, iff we have a validator attached
|
2020-11-20 14:16:04 +00:00
|
|
|
## that is supposed to do so, given the shuffling at that slot for the given
|
|
|
|
## head - to compute the proposer, we need to advance a state to the given
|
|
|
|
## slot
|
2021-06-01 11:13:40 +00:00
|
|
|
let proposer = node.dag.getProposer(head, slot)
|
2020-05-22 14:21:22 +00:00
|
|
|
if proposer.isNone():
|
2020-05-06 13:23:45 +00:00
|
|
|
return head
|
|
|
|
|
2021-06-01 11:13:40 +00:00
|
|
|
let
|
2021-08-27 09:00:06 +00:00
|
|
|
proposerKey = node.dag.validatorKey(proposer.get).get().toPubKey
|
2021-06-01 11:13:40 +00:00
|
|
|
validator = node.attachedValidators[].getValidator(proposerKey)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
if validator != nil:
|
2021-06-01 11:13:40 +00:00
|
|
|
return await proposeBlock(node, validator, proposer.get(), head, slot)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
debug "Expecting block proposal",
|
|
|
|
headRoot = shortLog(head.root),
|
|
|
|
slot = shortLog(slot),
|
2021-06-01 11:13:40 +00:00
|
|
|
proposer_index = proposer.get(),
|
|
|
|
proposer = shortLog(proposerKey)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
return head
|
|
|
|
|
2021-08-24 19:49:51 +00:00
|
|
|
proc makeAggregateAndProof*(
|
|
|
|
pool: var AttestationPool, epochRef: EpochRef, slot: Slot, index: CommitteeIndex,
|
|
|
|
validatorIndex: ValidatorIndex, slot_signature: ValidatorSig): Option[AggregateAndProof] =
|
|
|
|
doAssert validatorIndex in get_beacon_committee(epochRef, slot, index)
|
|
|
|
doAssert index.uint64 < get_committee_count_per_slot(epochRef)
|
|
|
|
|
|
|
|
# TODO for testing purposes, refactor this into the condition check
|
|
|
|
# and just calculation
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/validator.md#aggregation-selection
|
|
|
|
if not is_aggregator(epochRef, slot, index, slot_signature):
|
|
|
|
return none(AggregateAndProof)
|
|
|
|
|
|
|
|
let maybe_slot_attestation = getAggregatedAttestation(pool, slot, index)
|
|
|
|
if maybe_slot_attestation.isNone:
|
|
|
|
return none(AggregateAndProof)
|
|
|
|
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/validator.md#construct-aggregate
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/validator.md#aggregateandproof
|
|
|
|
some(AggregateAndProof(
|
|
|
|
aggregator_index: validatorIndex.uint64,
|
|
|
|
aggregate: maybe_slot_attestation.get,
|
|
|
|
selection_proof: slot_signature))
|
|
|
|
|
2021-08-19 10:45:31 +00:00
|
|
|
proc sendAggregatedAttestations(
|
2020-09-01 13:44:40 +00:00
|
|
|
node: BeaconNode, aggregationHead: BlockRef, aggregationSlot: Slot) {.async.} =
|
2020-05-06 13:23:45 +00:00
|
|
|
# The index is via a
|
|
|
|
# locally attested validator. Unlike in handleAttestations(...) there's a
|
|
|
|
# single one at most per slot (because that's how aggregation attestation
|
|
|
|
# works), so the machinery that has to handle looping across, basically a
|
|
|
|
# set of locally attached validators is in principle not necessary, but a
|
|
|
|
# way to organize this. Then the private key for that validator should be
|
|
|
|
# the corresponding one -- whatver they are, they match.
|
|
|
|
|
2020-10-22 10:53:33 +00:00
|
|
|
let
|
2021-06-01 11:13:40 +00:00
|
|
|
epochRef = node.dag.getEpochRef(aggregationHead, aggregationSlot.epoch)
|
2021-08-24 19:49:51 +00:00
|
|
|
fork = node.dag.forkAtEpoch(aggregationSlot.epoch)
|
2020-10-22 10:53:33 +00:00
|
|
|
genesis_validators_root =
|
2021-06-11 17:51:46 +00:00
|
|
|
getStateField(node.dag.headState.data, genesis_validators_root)
|
2020-10-22 10:53:33 +00:00
|
|
|
committees_per_slot = get_committee_count_per_slot(epochRef)
|
|
|
|
|
|
|
|
var
|
|
|
|
slotSigs: seq[Future[ValidatorSig]] = @[]
|
|
|
|
slotSigsData: seq[tuple[committee_index: uint64,
|
|
|
|
validator_idx: ValidatorIndex,
|
|
|
|
v: AttachedValidator]] = @[]
|
|
|
|
|
|
|
|
for committee_index in 0'u64..<committees_per_slot:
|
|
|
|
let committee = get_beacon_committee(
|
|
|
|
epochRef, aggregationSlot, committee_index.CommitteeIndex)
|
|
|
|
|
|
|
|
for index_in_committee, validatorIdx in committee:
|
|
|
|
let validator = node.getAttachedValidator(epochRef, validatorIdx)
|
|
|
|
if validator != nil:
|
|
|
|
# the validator index and private key pair.
|
|
|
|
slotSigs.add getSlotSig(validator, fork,
|
|
|
|
genesis_validators_root, aggregationSlot)
|
|
|
|
slotSigsData.add (committee_index, validatorIdx, validator)
|
|
|
|
|
|
|
|
await allFutures(slotSigs)
|
|
|
|
|
|
|
|
for curr in zip(slotSigsData, slotSigs):
|
|
|
|
let aggregateAndProof =
|
2021-08-24 19:49:51 +00:00
|
|
|
makeAggregateAndProof(node.attestationPool[], epochRef, aggregationSlot,
|
|
|
|
curr[0].committee_index.CommitteeIndex,
|
2020-10-22 10:53:33 +00:00
|
|
|
curr[0].validator_idx,
|
|
|
|
curr[1].read)
|
|
|
|
|
|
|
|
# Don't broadcast when, e.g., this node isn't aggregator
|
|
|
|
if aggregateAndProof.isSome:
|
|
|
|
let sig = await signAggregateAndProof(curr[0].v,
|
|
|
|
aggregateAndProof.get, fork, genesis_validators_root)
|
|
|
|
var signedAP = SignedAggregateAndProof(
|
|
|
|
message: aggregateAndProof.get,
|
|
|
|
signature: sig)
|
2021-08-24 19:49:51 +00:00
|
|
|
node.network.broadcastAggregateAndProof(signedAP)
|
2020-10-22 10:53:33 +00:00
|
|
|
notice "Aggregated attestation sent",
|
|
|
|
attestation = shortLog(signedAP.message.aggregate),
|
2020-12-07 11:56:49 +00:00
|
|
|
validator = shortLog(curr[0].v),
|
2021-08-03 10:32:55 +00:00
|
|
|
signature = shortLog(signedAP.signature),
|
2020-12-07 11:56:49 +00:00
|
|
|
aggregationSlot
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-12-16 13:03:04 +00:00
|
|
|
proc updateValidatorMetrics*(node: BeaconNode) =
|
2021-08-28 22:27:51 +00:00
|
|
|
# Technically, this only needs to be done on epoch transitions and if there's
|
|
|
|
# a reorg that spans an epoch transition, but it's easier to implement this
|
|
|
|
# way for now.
|
|
|
|
|
|
|
|
# We'll limit labelled metrics to the first 64, so that we don't overload
|
|
|
|
# Prometheus.
|
|
|
|
|
|
|
|
var total: Gwei
|
|
|
|
var i = 0
|
|
|
|
for _, v in node.attachedValidators[].validators:
|
|
|
|
let balance =
|
|
|
|
if v.index.isNone():
|
|
|
|
0.Gwei
|
|
|
|
elif v.index.get().uint64 >=
|
|
|
|
getStateField(node.dag.headState.data, balances).lenu64:
|
|
|
|
debug "Cannot get validator balance, index out of bounds",
|
|
|
|
pubkey = shortLog(v.pubkey), index = v.index.get(),
|
|
|
|
balances = getStateField(node.dag.headState.data, balances).len,
|
|
|
|
stateRoot = getStateRoot(node.dag.headState.data)
|
|
|
|
0.Gwei
|
|
|
|
else:
|
|
|
|
getStateField(node.dag.headState.data, balances)[v.index.get()]
|
|
|
|
|
|
|
|
if i < 64:
|
|
|
|
attached_validator_balance.set(
|
|
|
|
balance.toGaugeValue, labelValues = [shortLog(v.pubkey)])
|
|
|
|
|
|
|
|
inc i
|
|
|
|
total += balance
|
|
|
|
|
|
|
|
node.attachedValidatorBalanceTotal = total
|
|
|
|
attached_validator_balance_total.set(total.toGaugeValue)
|
2020-11-27 23:34:25 +00:00
|
|
|
|
2020-10-28 07:55:36 +00:00
|
|
|
proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
2020-07-22 08:04:21 +00:00
|
|
|
## Perform validator duties - create blocks, vote and aggregate existing votes
|
2021-02-22 16:17:48 +00:00
|
|
|
if node.attachedValidators[].count == 0:
|
2020-05-06 13:23:45 +00:00
|
|
|
# Nothing to do because we have no validator attached
|
2020-06-10 06:58:12 +00:00
|
|
|
return
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2021-06-01 11:13:40 +00:00
|
|
|
# The dag head might be updated by sync while we're working due to the
|
2020-10-28 07:55:36 +00:00
|
|
|
# await calls, thus we use a local variable to keep the logic straight here
|
2021-06-01 11:13:40 +00:00
|
|
|
var head = node.dag.head
|
2020-05-06 13:23:45 +00:00
|
|
|
if not node.isSynced(head):
|
2020-11-02 07:52:16 +00:00
|
|
|
notice "Syncing in progress; skipping validator duties for now",
|
2020-05-06 13:23:45 +00:00
|
|
|
slot, headSlot = head.slot
|
2020-12-16 13:03:04 +00:00
|
|
|
|
|
|
|
# Rewards will be growing though, as we sync..
|
|
|
|
updateValidatorMetrics(node)
|
|
|
|
|
2020-06-10 06:58:12 +00:00
|
|
|
return
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
var curSlot = lastSlot + 1
|
|
|
|
|
2021-01-29 12:38:52 +00:00
|
|
|
# If broadcastStartEpoch is 0, it hasn't had time to initialize yet, which
|
|
|
|
# means that it'd be okay not to continue, but it won't gossip regardless.
|
|
|
|
if curSlot.epoch <
|
2021-02-01 11:18:16 +00:00
|
|
|
node.processor[].doppelgangerDetection.broadcastStartEpoch and
|
2021-02-03 17:11:42 +00:00
|
|
|
node.config.doppelgangerDetection:
|
2021-01-29 15:01:25 +00:00
|
|
|
debug "Waiting to gossip out to detect potential duplicate validators",
|
2020-10-27 17:21:35 +00:00
|
|
|
broadcastStartEpoch =
|
2021-02-01 11:18:16 +00:00
|
|
|
node.processor[].doppelgangerDetection.broadcastStartEpoch
|
2020-10-27 17:21:35 +00:00
|
|
|
return
|
|
|
|
|
2020-05-06 13:23:45 +00:00
|
|
|
# Start by checking if there's work we should have done in the past that we
|
|
|
|
# can still meaningfully do
|
|
|
|
while curSlot < slot:
|
2020-10-01 18:56:42 +00:00
|
|
|
notice "Catching up on validator duties",
|
2020-05-06 13:23:45 +00:00
|
|
|
curSlot = shortLog(curSlot),
|
|
|
|
lastSlot = shortLog(lastSlot),
|
2020-07-16 13:16:51 +00:00
|
|
|
slot = shortLog(slot)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
# For every slot we're catching up, we'll propose then send
|
|
|
|
# attestations - head should normally be advancing along the same branch
|
|
|
|
# in this case
|
|
|
|
head = await handleProposal(node, head, curSlot)
|
|
|
|
|
|
|
|
# For each slot we missed, we need to send out attestations - if we were
|
|
|
|
# proposing during this time, we'll use the newly proposed head, else just
|
|
|
|
# keep reusing the same - the attestation that goes out will actually
|
|
|
|
# rewind the state to what it looked like at the time of that slot
|
|
|
|
handleAttestations(node, head, curSlot)
|
|
|
|
|
|
|
|
curSlot += 1
|
|
|
|
|
|
|
|
head = await handleProposal(node, head, slot)
|
|
|
|
|
2021-03-23 06:57:10 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#attesting
|
2021-03-01 16:36:06 +00:00
|
|
|
# Milliseconds to wait from the start of the slot before sending out
|
2021-03-23 06:57:10 +00:00
|
|
|
# attestations
|
|
|
|
const attestationOffset = SECONDS_PER_SLOT.int64 * 1000 div 3
|
2021-03-01 16:36:06 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
# The latest point in time when we'll be sending out attestations
|
2021-03-23 06:57:10 +00:00
|
|
|
attestationCutoffTime = slot.toBeaconTime(millis(attestationOffset))
|
2021-03-01 16:36:06 +00:00
|
|
|
attestationCutoff = node.beaconClock.fromNow(attestationCutoffTime)
|
|
|
|
|
|
|
|
if attestationCutoff.inFuture:
|
|
|
|
debug "Waiting to send attestations",
|
|
|
|
head = shortLog(head),
|
|
|
|
attestationCutoff = shortLog(attestationCutoff.offset)
|
|
|
|
|
|
|
|
# Wait either for the block or the attestation cutoff time to arrive
|
2021-03-11 10:10:57 +00:00
|
|
|
if await node.consensusManager[].expectBlock(slot).withTimeout(attestationCutoff.offset):
|
2021-03-01 16:36:06 +00:00
|
|
|
# The expected block arrived (or expectBlock was called again which
|
2021-03-23 06:57:10 +00:00
|
|
|
# shouldn't happen as this is the only place we use it) - in our async
|
|
|
|
# loop however, we might have been doing other processing that caused delays
|
2021-03-01 16:36:06 +00:00
|
|
|
# here so we'll cap the waiting to the time when we would have sent out
|
|
|
|
# attestations had the block not arrived.
|
|
|
|
# An opposite case is that we received (or produced) a block that has
|
|
|
|
# not yet reached our neighbours. To protect against our attestations
|
|
|
|
# being dropped (because the others have not yet seen the block), we'll
|
2021-07-06 13:11:18 +00:00
|
|
|
# impose a minimum delay of 1000ms. The delay is enforced only when we're
|
2021-03-01 16:36:06 +00:00
|
|
|
# not hitting the "normal" cutoff time for sending out attestations.
|
2021-07-06 13:11:18 +00:00
|
|
|
# An earlier delay of 250ms has proven to be not enough, increasing the
|
|
|
|
# risk of losing attestations.
|
|
|
|
# Regardless, because we "just" received the block, we'll impose the
|
|
|
|
# delay.
|
2021-03-01 16:36:06 +00:00
|
|
|
|
2021-07-06 13:11:18 +00:00
|
|
|
const afterBlockDelay = 1000
|
2021-03-01 16:36:06 +00:00
|
|
|
let
|
|
|
|
afterBlockTime = node.beaconClock.now() + millis(afterBlockDelay)
|
|
|
|
afterBlockCutoff = node.beaconClock.fromNow(
|
2021-07-06 13:11:18 +00:00
|
|
|
min(afterBlockTime, attestationCutoffTime + millis(afterBlockDelay)))
|
2021-03-01 16:36:06 +00:00
|
|
|
|
|
|
|
if afterBlockCutoff.inFuture:
|
|
|
|
debug "Got block, waiting to send attestations",
|
|
|
|
head = shortLog(head),
|
|
|
|
afterBlockCutoff = shortLog(afterBlockCutoff.offset)
|
|
|
|
|
|
|
|
await sleepAsync(afterBlockCutoff.offset)
|
|
|
|
|
|
|
|
# Time passed - we might need to select a new head in that case
|
2021-03-11 10:10:57 +00:00
|
|
|
node.consensusManager[].updateHead(slot)
|
2021-06-01 11:13:40 +00:00
|
|
|
head = node.dag.head
|
2020-05-06 13:23:45 +00:00
|
|
|
|
|
|
|
handleAttestations(node, head, slot)
|
2021-08-30 00:58:30 +00:00
|
|
|
handleSyncCommitteeMessages(node, head, slot)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2020-12-16 13:03:04 +00:00
|
|
|
updateValidatorMetrics(node) # the important stuff is done, update the vanity numbers
|
2020-11-27 23:34:25 +00:00
|
|
|
|
2021-03-02 06:04:14 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#broadcast-aggregate
|
2020-05-06 13:23:45 +00:00
|
|
|
# If the validator is selected to aggregate (is_aggregator), then they
|
|
|
|
# broadcast their best aggregate as a SignedAggregateAndProof to the global
|
|
|
|
# aggregate channel (beacon_aggregate_and_proof) two-thirds of the way
|
|
|
|
# through the slot-that is, SECONDS_PER_SLOT * 2 / 3 seconds after the start
|
|
|
|
# of slot.
|
|
|
|
if slot > 2:
|
2021-03-01 16:36:06 +00:00
|
|
|
let
|
|
|
|
aggregateWaitTime = node.beaconClock.fromNow(
|
|
|
|
slot.toBeaconTime(seconds(int64(SECONDS_PER_SLOT * 2) div 3)))
|
|
|
|
if aggregateWaitTime.inFuture:
|
|
|
|
debug "Waiting to send aggregate attestations",
|
|
|
|
aggregateWaitTime = shortLog(aggregateWaitTime.offset)
|
|
|
|
await sleepAsync(aggregateWaitTime.offset)
|
2020-05-06 13:23:45 +00:00
|
|
|
|
2021-08-19 10:45:31 +00:00
|
|
|
let sendAggregatedAttestationsFut =
|
|
|
|
sendAggregatedAttestations(node, head, slot)
|
|
|
|
|
2021-08-30 00:58:30 +00:00
|
|
|
let handleSyncCommitteeContributionsFut =
|
|
|
|
handleSyncCommitteeContributions(node, head, slot)
|
|
|
|
|
|
|
|
await handleSyncCommitteeContributionsFut
|
2021-08-19 10:45:31 +00:00
|
|
|
await sendAggregatedAttestationsFut
|
2020-11-30 23:59:35 +00:00
|
|
|
|
|
|
|
if node.eth1Monitor != nil and (slot mod SLOTS_PER_EPOCH) == 0:
|
2021-06-01 11:13:40 +00:00
|
|
|
let finalizedEpochRef = node.dag.getFinalizedEpochRef()
|
2020-11-30 23:59:35 +00:00
|
|
|
discard node.eth1Monitor.trackFinalizedState(
|
|
|
|
finalizedEpochRef.eth1_data, finalizedEpochRef.eth1_deposit_index)
|
2021-08-23 10:41:48 +00:00
|
|
|
|
|
|
|
proc sendAttestation*(node: BeaconNode,
|
|
|
|
attestation: Attestation): Future[SendResult] {.async.} =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let attestationBlock =
|
|
|
|
block:
|
|
|
|
let res = node.dag.getRef(attestation.data.beacon_block_root)
|
|
|
|
if isNil(res):
|
|
|
|
debug "Attempt to send attestation without corresponding block",
|
|
|
|
attestation = shortLog(attestation)
|
|
|
|
return SendResult.err(
|
|
|
|
"Attempt to send attestation without corresponding block")
|
|
|
|
res
|
|
|
|
let
|
|
|
|
epochRef = node.dag.getEpochRef(
|
|
|
|
attestationBlock, attestation.data.target.epoch)
|
|
|
|
subnet_id = compute_subnet_for_attestation(
|
|
|
|
get_committee_count_per_slot(epochRef), attestation.data.slot,
|
|
|
|
attestation.data.index.CommitteeIndex)
|
|
|
|
res = await node.sendAttestation(attestation, subnet_id,
|
|
|
|
checkSignature = true)
|
|
|
|
if not(res):
|
|
|
|
return SendResult.err("Attestation failed validation")
|
|
|
|
return SendResult.ok()
|
|
|
|
|
|
|
|
proc sendAggregateAndProof*(node: BeaconNode,
|
|
|
|
proof: SignedAggregateAndProof): Future[SendResult] {.
|
|
|
|
async.} =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let res = await node.processor.aggregateValidator(proof)
|
|
|
|
case res
|
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastAggregateAndProof(proof)
|
|
|
|
return SendResult.ok()
|
|
|
|
else:
|
|
|
|
notice "Aggregate and proof failed validation",
|
|
|
|
proof = shortLog(proof.message.aggregate), result = $res
|
|
|
|
return SendResult.err("Aggregate and proof failed validation")
|
|
|
|
|
|
|
|
proc sendVoluntaryExit*(node: BeaconNode,
|
|
|
|
exit: SignedVoluntaryExit): SendResult =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let res = node.processor[].voluntaryExitValidator(exit)
|
|
|
|
case res
|
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastVoluntaryExit(exit)
|
|
|
|
ok()
|
|
|
|
else:
|
|
|
|
notice "Voluntary exit request failed validation",
|
|
|
|
exit = shortLog(exit.message), result = $res
|
|
|
|
err("Voluntary exit request failed validation")
|
|
|
|
|
|
|
|
proc sendAttesterSlashing*(node: BeaconNode,
|
|
|
|
slashing: AttesterSlashing): SendResult =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let res = node.processor[].attesterSlashingValidator(slashing)
|
|
|
|
case res
|
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastAttesterSlashing(slashing)
|
|
|
|
ok()
|
|
|
|
else:
|
|
|
|
notice "Attester slashing request failed validation",
|
|
|
|
slashing = shortLog(slashing), result = $res
|
|
|
|
err("Attester slashing request failed validation")
|
|
|
|
|
|
|
|
proc sendProposerSlashing*(node: BeaconNode,
|
|
|
|
slashing: ProposerSlashing): SendResult =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let res = node.processor[].proposerSlashingValidator(slashing)
|
|
|
|
case res
|
|
|
|
of ValidationResult.Accept:
|
|
|
|
node.network.broadcastProposerSlashing(slashing)
|
|
|
|
else:
|
|
|
|
notice "Proposer slashing request failed validation",
|
|
|
|
slashing = shortLog(slashing), result = $res
|
|
|
|
return SendResult.err("Proposer slashing request failed validation")
|
|
|
|
|
|
|
|
proc sendBeaconBlock*(node: BeaconNode, forked: ForkedSignedBeaconBlock
|
|
|
|
): Future[SendBlockResult] {.async.} =
|
|
|
|
# REST/JSON-RPC API helper procedure.
|
|
|
|
let head = node.dag.head
|
|
|
|
if not(node.isSynced(head)):
|
|
|
|
return SendBlockResult.err("Beacon node is currently syncing")
|
|
|
|
if head.slot >= forked.slot():
|
|
|
|
node.network.broadcastBeaconBlock(forked)
|
|
|
|
return SendBlockResult.ok(false)
|
2021-08-29 14:50:21 +00:00
|
|
|
|
|
|
|
let res = await node.proposeSignedBlock(head, AttachedValidator(), forked)
|
2021-08-23 10:41:48 +00:00
|
|
|
if res == head:
|
|
|
|
# `res == head` means failure, in such case we need to broadcast block
|
|
|
|
# manually because of the specification.
|
|
|
|
node.network.broadcastBeaconBlock(forked)
|
|
|
|
return SendBlockResult.ok(false)
|
|
|
|
return SendBlockResult.ok(true)
|