2021-03-26 06:52: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].}
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
import
|
2019-10-25 10:59:56 +00:00
|
|
|
options, tables, sets, macros,
|
2020-03-22 20:54:47 +00:00
|
|
|
chronicles, chronos, stew/ranges/bitranges, libp2p/switch,
|
2021-03-02 10:27:45 +00:00
|
|
|
../spec/[datatypes, network, crypto, digest],
|
2021-03-05 13:12:00 +00:00
|
|
|
../beacon_node_types,
|
|
|
|
../networking/eth2_network,
|
|
|
|
../consensus_object_pools/blockchain_dag
|
2019-03-12 15:03:14 +00:00
|
|
|
|
2019-11-11 01:28:13 +00:00
|
|
|
logScope:
|
|
|
|
topics = "sync"
|
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
const
|
2020-08-05 23:22:12 +00:00
|
|
|
MAX_REQUEST_BLOCKS = 1024
|
2020-05-23 22:24:47 +00:00
|
|
|
|
2020-10-09 13:44:51 +00:00
|
|
|
blockByRootLookupCost = allowedOpsPerSecondCost(50)
|
|
|
|
blockResponseCost = allowedOpsPerSecondCost(100)
|
|
|
|
blockByRangeLookupCost = allowedOpsPerSecondCost(20)
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
type
|
2020-04-20 14:59:18 +00:00
|
|
|
StatusMsg* = object
|
|
|
|
forkDigest*: ForkDigest
|
|
|
|
finalizedRoot*: Eth2Digest
|
|
|
|
finalizedEpoch*: Epoch
|
|
|
|
headRoot*: Eth2Digest
|
|
|
|
headSlot*: Slot
|
|
|
|
|
2019-07-08 13:19:52 +00:00
|
|
|
ValidatorSetDeltaFlags {.pure.} = enum
|
|
|
|
Activation = 0
|
|
|
|
Exit = 1
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
ValidatorChangeLogEntry* = object
|
|
|
|
case kind*: ValidatorSetDeltaFlags
|
2018-12-03 17:46:22 +00:00
|
|
|
of Activation:
|
2018-11-29 01:08:34 +00:00
|
|
|
pubkey: ValidatorPubKey
|
2018-11-23 23:58:49 +00:00
|
|
|
else:
|
|
|
|
index: uint32
|
|
|
|
|
2021-03-24 16:20:55 +00:00
|
|
|
BeaconBlockCallback* = proc(signedBlock: SignedBeaconBlock) {.gcsafe, raises: [Defect].}
|
2020-04-15 02:41:22 +00:00
|
|
|
|
2019-09-08 22:03:41 +00:00
|
|
|
BeaconSyncNetworkState* = ref object
|
2020-07-31 14:49:06 +00:00
|
|
|
chainDag*: ChainDAGRef
|
2020-04-15 02:41:22 +00:00
|
|
|
forkDigest*: ForkDigest
|
2019-02-18 10:34:39 +00:00
|
|
|
|
2019-09-08 22:03:41 +00:00
|
|
|
BeaconSyncPeerState* = ref object
|
2020-09-23 15:58:02 +00:00
|
|
|
statusLastTime*: chronos.Moment
|
2020-04-20 14:59:18 +00:00
|
|
|
statusMsg*: StatusMsg
|
2019-09-08 22:03:41 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
BlockRootSlot* = object
|
|
|
|
blockRoot: Eth2Digest
|
|
|
|
slot: Slot
|
|
|
|
|
2020-08-05 23:22:12 +00:00
|
|
|
BlockRootsList* = List[Eth2Digest, Limit MAX_REQUEST_BLOCKS]
|
2019-11-25 14:36:25 +00:00
|
|
|
|
2020-09-06 08:39:25 +00:00
|
|
|
func shortLog*(s: StatusMsg): auto =
|
2020-05-11 18:08:52 +00:00
|
|
|
(
|
|
|
|
forkDigest: s.forkDigest,
|
|
|
|
finalizedRoot: shortLog(s.finalizedRoot),
|
|
|
|
finalizedEpoch: shortLog(s.finalizedEpoch),
|
|
|
|
headRoot: shortLog(s.headRoot),
|
|
|
|
headSlot: shortLog(s.headSlot)
|
|
|
|
)
|
|
|
|
chronicles.formatIt(StatusMsg): shortLog(it)
|
|
|
|
|
2020-06-20 07:24:33 +00:00
|
|
|
func disconnectReasonName(reason: uint64): string =
|
|
|
|
# haha, nim doesn't support uint64 in `case`!
|
|
|
|
if reason == uint64(ClientShutDown): "Client shutdown"
|
|
|
|
elif reason == uint64(IrrelevantNetwork): "Irrelevant network"
|
|
|
|
elif reason == uint64(FaultOrError): "Fault or error"
|
|
|
|
else: "Disconnected (" & $reason & ")"
|
|
|
|
|
2020-05-11 18:08:52 +00:00
|
|
|
proc getCurrentStatus*(state: BeaconSyncNetworkState): StatusMsg {.gcsafe.} =
|
2019-09-09 23:55:01 +00:00
|
|
|
let
|
2020-07-30 19:18:17 +00:00
|
|
|
chainDag = state.chainDag
|
|
|
|
headBlock = chainDag.head
|
2019-09-09 23:55:01 +00:00
|
|
|
|
2019-09-11 16:45:22 +00:00
|
|
|
StatusMsg(
|
2020-04-15 02:41:22 +00:00
|
|
|
forkDigest: state.forkDigest,
|
2021-04-08 08:24:25 +00:00
|
|
|
finalizedRoot:
|
|
|
|
getStateField(chainDag.headState, finalized_checkpoint).root,
|
|
|
|
finalizedEpoch:
|
|
|
|
getStateField(chainDag.headState, finalized_checkpoint).epoch,
|
2020-05-11 18:08:52 +00:00
|
|
|
headRoot: headBlock.root,
|
|
|
|
headSlot: headBlock.slot)
|
2019-09-08 22:03:41 +00:00
|
|
|
|
2020-05-13 06:37:58 +00:00
|
|
|
proc handleStatus(peer: Peer,
|
|
|
|
state: BeaconSyncNetworkState,
|
|
|
|
ourStatus: StatusMsg,
|
|
|
|
theirStatus: StatusMsg): Future[void] {.gcsafe.}
|
2019-09-08 13:08:44 +00:00
|
|
|
|
2020-05-11 18:08:52 +00:00
|
|
|
proc setStatusMsg(peer: Peer, statusMsg: StatusMsg) {.gcsafe.}
|
|
|
|
|
2021-03-26 06:52:01 +00:00
|
|
|
{.pop.} # TODO fix p2p macro for raises
|
2018-11-29 01:08:34 +00:00
|
|
|
p2pProtocol BeaconSync(version = 1,
|
2019-09-08 22:03:41 +00:00
|
|
|
networkState = BeaconSyncNetworkState,
|
|
|
|
peerState = BeaconSyncPeerState):
|
2019-02-18 10:34:39 +00:00
|
|
|
|
2020-08-10 10:58:34 +00:00
|
|
|
onPeerConnected do (peer: Peer, incoming: bool) {.async.}:
|
2020-06-11 05:14:26 +00:00
|
|
|
debug "Peer connected",
|
2020-08-10 10:58:34 +00:00
|
|
|
peer, peerInfo = shortLog(peer.info), incoming
|
|
|
|
# Per the eth2 protocol, whoever dials must send a status message when
|
|
|
|
# connected for the first time, but because of how libp2p works, there may
|
|
|
|
# be a race between incoming and outgoing connections and disconnects that
|
|
|
|
# makes the incoming flag unreliable / obsolete by the time we get to
|
|
|
|
# this point - instead of making assumptions, we'll just send a status
|
|
|
|
# message redundantly.
|
2020-11-10 18:41:04 +00:00
|
|
|
# TODO(zah)
|
|
|
|
# the spec does not prohibit sending the extra status message on
|
2020-08-10 10:58:34 +00:00
|
|
|
# incoming connections, but it should not be necessary - this would
|
|
|
|
# need a dedicated flow in libp2p that resolves the race conditions -
|
|
|
|
# this needs more thinking around the ordering of events and the
|
|
|
|
# given incoming flag
|
|
|
|
let
|
|
|
|
ourStatus = peer.networkState.getCurrentStatus()
|
2020-11-12 15:30:12 +00:00
|
|
|
theirStatus = await peer.status(ourStatus, timeout = RESP_TIMEOUT)
|
2019-09-09 23:55:01 +00:00
|
|
|
|
2020-08-10 10:58:34 +00:00
|
|
|
if theirStatus.isOk:
|
|
|
|
await peer.handleStatus(peer.networkState,
|
|
|
|
ourStatus, theirStatus.get())
|
|
|
|
else:
|
2020-10-01 18:56:42 +00:00
|
|
|
debug "Status response not received in time",
|
2020-12-07 18:47:07 +00:00
|
|
|
peer, error = theirStatus.error
|
|
|
|
await peer.disconnect(FaultOrError)
|
2019-02-18 10:34:39 +00:00
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
proc status(peer: Peer,
|
|
|
|
theirStatus: StatusMsg,
|
|
|
|
response: SingleChunkResponse[StatusMsg])
|
|
|
|
{.async, libp2pProtocol("status", 1).} =
|
|
|
|
let ourStatus = peer.networkState.getCurrentStatus()
|
|
|
|
trace "Sending status message", peer = peer, status = ourStatus
|
|
|
|
await response.send(ourStatus)
|
|
|
|
await peer.handleStatus(peer.networkState, ourStatus, theirStatus)
|
|
|
|
|
|
|
|
proc ping(peer: Peer, value: uint64): uint64
|
|
|
|
{.libp2pProtocol("ping", 1).} =
|
|
|
|
return peer.network.metadata.seq_number
|
|
|
|
|
2021-05-04 15:28:48 +00:00
|
|
|
proc getMetaData(peer: Peer): MetaData
|
2020-05-23 22:24:47 +00:00
|
|
|
{.libp2pProtocol("metadata", 1).} =
|
|
|
|
return peer.network.metadata
|
|
|
|
|
2020-06-25 10:23:10 +00:00
|
|
|
proc beaconBlocksByRange(
|
|
|
|
peer: Peer,
|
|
|
|
startSlot: Slot,
|
2020-08-05 23:22:12 +00:00
|
|
|
reqCount: uint64,
|
|
|
|
reqStep: uint64,
|
2020-06-25 10:23:10 +00:00
|
|
|
response: MultipleChunksResponse[SignedBeaconBlock])
|
|
|
|
{.async, libp2pProtocol("beacon_blocks_by_range", 1).} =
|
2020-08-05 23:22:12 +00:00
|
|
|
trace "got range request", peer, startSlot,
|
|
|
|
count = reqCount, step = reqStep
|
2020-10-09 13:44:51 +00:00
|
|
|
if reqCount > 0'u64 and reqStep > 0'u64:
|
2020-08-05 23:22:12 +00:00
|
|
|
var blocks: array[MAX_REQUEST_BLOCKS, BlockRef]
|
2020-05-23 22:24:47 +00:00
|
|
|
let
|
2020-07-30 19:18:17 +00:00
|
|
|
chainDag = peer.networkState.chainDag
|
2020-05-23 22:24:47 +00:00
|
|
|
# Limit number of blocks in response
|
2020-08-05 23:22:12 +00:00
|
|
|
count = int min(reqCount, blocks.lenu64)
|
2019-09-08 22:03:41 +00:00
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
let
|
|
|
|
endIndex = count - 1
|
|
|
|
startIndex =
|
2020-08-05 23:22:12 +00:00
|
|
|
chainDag.getBlockRange(startSlot, reqStep,
|
|
|
|
blocks.toOpenArray(0, endIndex))
|
2020-10-09 13:44:51 +00:00
|
|
|
peer.updateRequestQuota(
|
|
|
|
blockByRangeLookupCost +
|
|
|
|
max(0, endIndex - startIndex + 1).float * blockResponseCost)
|
2020-10-13 12:37:25 +00:00
|
|
|
peer.awaitNonNegativeRequestQuota()
|
2019-02-18 10:34:39 +00:00
|
|
|
|
2020-08-27 06:32:51 +00:00
|
|
|
for i in startIndex..endIndex:
|
|
|
|
doAssert not blocks[i].isNil, "getBlockRange should return non-nil blocks only"
|
|
|
|
trace "wrote response block",
|
|
|
|
slot = blocks[i].slot, roor = shortLog(blocks[i].root)
|
|
|
|
await response.write(chainDag.get(blocks[i]).data)
|
2019-05-22 07:13:15 +00:00
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
debug "Block range request done",
|
2020-08-05 23:22:12 +00:00
|
|
|
peer, startSlot, count, reqStep, found = count - startIndex
|
2020-10-09 13:44:51 +00:00
|
|
|
else:
|
2020-10-13 12:37:25 +00:00
|
|
|
raise newException(InvalidInputsError, "Empty range requested")
|
2019-08-05 00:00:49 +00:00
|
|
|
|
2020-06-25 10:23:10 +00:00
|
|
|
proc beaconBlocksByRoot(
|
|
|
|
peer: Peer,
|
2020-08-21 18:50:46 +00:00
|
|
|
# Please note that the SSZ list here ensures that the
|
|
|
|
# spec constant MAX_REQUEST_BLOCKS is enforced:
|
2020-06-25 10:23:10 +00:00
|
|
|
blockRoots: BlockRootsList,
|
|
|
|
response: MultipleChunksResponse[SignedBeaconBlock])
|
|
|
|
{.async, libp2pProtocol("beacon_blocks_by_root", 1).} =
|
2020-10-09 13:44:51 +00:00
|
|
|
if blockRoots.len == 0:
|
2020-10-13 12:37:25 +00:00
|
|
|
raise newException(InvalidInputsError, "No blocks requested")
|
2020-10-09 13:44:51 +00:00
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
let
|
2020-07-30 19:18:17 +00:00
|
|
|
chainDag = peer.networkState.chainDag
|
2020-05-23 22:24:47 +00:00
|
|
|
count = blockRoots.len
|
2020-04-22 06:18:45 +00:00
|
|
|
|
2020-10-09 13:44:51 +00:00
|
|
|
peer.updateRequestQuota(count.float * blockByRootLookupCost)
|
2020-10-13 12:37:25 +00:00
|
|
|
peer.awaitNonNegativeRequestQuota()
|
2020-04-21 06:43:39 +00:00
|
|
|
|
2020-10-09 13:44:51 +00:00
|
|
|
var found = 0
|
2020-08-27 06:32:51 +00:00
|
|
|
for i in 0..<count:
|
|
|
|
let blockRef = chainDag.getRef(blockRoots[i])
|
2020-05-23 22:24:47 +00:00
|
|
|
if not isNil(blockRef):
|
2020-07-30 19:18:17 +00:00
|
|
|
await response.write(chainDag.get(blockRef).data)
|
2020-05-23 22:24:47 +00:00
|
|
|
inc found
|
2019-08-05 00:00:49 +00:00
|
|
|
|
2020-10-09 13:44:51 +00:00
|
|
|
peer.updateRequestQuota(found.float * blockResponseCost)
|
|
|
|
|
2020-05-23 22:24:47 +00:00
|
|
|
debug "Block root request done",
|
|
|
|
peer, roots = blockRoots.len, count, found
|
|
|
|
|
2020-05-26 17:07:18 +00:00
|
|
|
proc goodbye(peer: Peer,
|
2020-06-20 07:24:33 +00:00
|
|
|
reason: uint64)
|
2020-05-26 17:07:18 +00:00
|
|
|
{.async, libp2pProtocol("goodbye", 1).} =
|
2020-06-20 07:24:33 +00:00
|
|
|
debug "Received Goodbye message", reason = disconnectReasonName(reason), peer
|
2019-05-22 07:13:15 +00:00
|
|
|
|
2020-05-11 18:08:52 +00:00
|
|
|
proc setStatusMsg(peer: Peer, statusMsg: StatusMsg) =
|
2020-10-30 12:33:52 +00:00
|
|
|
debug "Peer status", peer, statusMsg
|
2020-05-11 18:08:52 +00:00
|
|
|
peer.state(BeaconSync).statusMsg = statusMsg
|
2020-09-23 15:58:02 +00:00
|
|
|
peer.state(BeaconSync).statusLastTime = Moment.now()
|
2020-05-11 18:08:52 +00:00
|
|
|
|
|
|
|
proc updateStatus*(peer: Peer): Future[bool] {.async.} =
|
|
|
|
## Request `status` of remote peer ``peer``.
|
|
|
|
let
|
|
|
|
nstate = peer.networkState(BeaconSync)
|
|
|
|
ourStatus = getCurrentStatus(nstate)
|
|
|
|
|
2020-11-12 15:30:12 +00:00
|
|
|
let theirFut = awaitne peer.status(ourStatus, timeout = RESP_TIMEOUT)
|
2020-05-11 18:08:52 +00:00
|
|
|
if theirFut.failed():
|
2020-09-23 15:58:02 +00:00
|
|
|
return false
|
2020-05-11 18:08:52 +00:00
|
|
|
else:
|
|
|
|
let theirStatus = theirFut.read()
|
2020-05-12 22:37:07 +00:00
|
|
|
if theirStatus.isOk:
|
|
|
|
peer.setStatusMsg(theirStatus.get)
|
2020-09-23 15:58:02 +00:00
|
|
|
return true
|
|
|
|
else:
|
|
|
|
return false
|
2020-05-11 18:08:52 +00:00
|
|
|
|
2020-11-20 10:00:22 +00:00
|
|
|
proc getHeadSlot*(peer: Peer): Slot =
|
2020-05-11 18:08:52 +00:00
|
|
|
## Returns head slot for specific peer ``peer``.
|
|
|
|
result = peer.state(BeaconSync).statusMsg.headSlot
|
|
|
|
|
2020-05-13 06:37:58 +00:00
|
|
|
proc handleStatus(peer: Peer,
|
|
|
|
state: BeaconSyncNetworkState,
|
|
|
|
ourStatus: StatusMsg,
|
|
|
|
theirStatus: StatusMsg) {.async, gcsafe.} =
|
2020-04-15 02:41:22 +00:00
|
|
|
if theirStatus.forkDigest != state.forkDigest:
|
2020-10-01 18:56:42 +00:00
|
|
|
debug "Irrelevant peer", peer, theirStatus, ourStatus
|
2019-09-09 23:55:01 +00:00
|
|
|
await peer.disconnect(IrrelevantNetwork)
|
2020-05-13 06:37:58 +00:00
|
|
|
else:
|
|
|
|
peer.setStatusMsg(theirStatus)
|
2020-11-26 19:23:45 +00:00
|
|
|
if peer.connectionState == Connecting:
|
|
|
|
# As soon as we get here it means that we passed handshake succesfully. So
|
|
|
|
# we can add this peer to PeerPool.
|
|
|
|
await peer.handlePeer()
|
2019-12-02 15:38:18 +00:00
|
|
|
|
2020-07-31 14:49:06 +00:00
|
|
|
proc initBeaconSync*(network: Eth2Node, chainDag: ChainDAGRef,
|
2020-07-16 06:59:24 +00:00
|
|
|
forkDigest: ForkDigest) =
|
2020-04-15 02:41:22 +00:00
|
|
|
var networkState = network.protocolState(BeaconSync)
|
2020-07-30 19:18:17 +00:00
|
|
|
networkState.chainDag = chainDag
|
2020-04-15 02:41:22 +00:00
|
|
|
networkState.forkDigest = forkDigest
|