mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-02-05 16:54:00 +00:00
575c69e6ba
* new: time_helper, types * new: path_desc * new: base_desc * Re-organised objects inheritance why: Previous code used macros to instantiate opaque object references. This has been re-implemented with OO inheritance based logic. * Normalised trace macros * Using distinct types for Hash256 aliases why: Better control of the meaning of the hashes, all or the same format caveat: The protocol handler DSL used by eth66.nim and snap1.nim uses the underlying type Hash256 and cannot handle the distinct alias in rlp and chronicles/log macros. So Hash256 is used directly (does not change readability as the type is clear by parameter names.)
302 lines
11 KiB
Nim
302 lines
11 KiB
Nim
# Nimbus - Ethereum Wire Protocol, version eth/65
|
|
#
|
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed
|
|
# except according to those terms.
|
|
|
|
## This module implements `eth/66`, the `Ethereum Wire Protocol version 66
|
|
## <https://github.com/ethereum/devp2p/blob/master/caps/eth.md>`_
|
|
##
|
|
## Optional peply processor function hooks
|
|
## ---------------------------------------
|
|
##
|
|
## The `onGetNodeData` and `onNodeData` hooks allow new sync code to register
|
|
## for providing reply data or consume incoming events without a circular
|
|
## import dependency involving the `p2pProtocol`.
|
|
##
|
|
## Without the hooks, the protocol file needs to import functions that consume
|
|
## incoming network messages. So the `p2pProtocol` can call them, and the
|
|
## functions that produce outgoing network messages need to import the protocol
|
|
## file.
|
|
##
|
|
## But related producer/consumer function pairs are typically located in the
|
|
## very same file because they are closely related. For an example see the
|
|
## producer of `GetNodeData` and the consumer of `NodeData`.
|
|
##
|
|
## In this specific case, we need to split the `requestResponse` relationship
|
|
## between `GetNodeData` and `NodeData` messages when pipelining.
|
|
##
|
|
## Among others, this way is the most practical to acomplish the split
|
|
## implementation. It allows different protocol-using modules to coexist
|
|
## easily. When the hooks aren't set, default behaviour applies.
|
|
|
|
import
|
|
chronicles,
|
|
chronos,
|
|
eth/[common/eth_types, p2p, p2p/private/p2p_types, p2p/blockchain_utils],
|
|
stew/byteutils,
|
|
".."/trace_helper,
|
|
./pickeled_eth_tracers
|
|
|
|
export
|
|
tracePackets, tracePacket,
|
|
traceGossips, traceGossip,
|
|
traceTimeouts, traceTimeout,
|
|
traceNetworkErrors, traceNetworkError,
|
|
tracePacketErrors, tracePacketError
|
|
|
|
type
|
|
NewBlockHashesAnnounce* = object
|
|
hash: Hash256
|
|
number: BlockNumber
|
|
|
|
NewBlockAnnounce* = EthBlock
|
|
|
|
ForkId* = object
|
|
forkHash: array[4, byte] # The RLP encoding must be exactly 4 bytes.
|
|
forkNext: BlockNumber # The RLP encoding must be variable-length
|
|
|
|
PeerState* = ref object
|
|
initialized*: bool
|
|
bestBlockHash*: Hash256
|
|
bestDifficulty*: DifficultyInt
|
|
|
|
onGetNodeData*:
|
|
proc (peer: Peer, hashes: openArray[Hash256],
|
|
data: var seq[Blob]) {.gcsafe.}
|
|
onNodeData*:
|
|
proc (peer: Peer, data: openArray[Blob]) {.gcsafe.}
|
|
|
|
const
|
|
maxStateFetch* = 384
|
|
maxBodiesFetch* = 128
|
|
maxReceiptsFetch* = 256
|
|
maxHeadersFetch* = 192
|
|
ethVersion* = 66
|
|
prettyEthProtoName* = "[eth/" & $ethVersion & "]"
|
|
|
|
|
|
p2pProtocol eth66(version = ethVersion,
|
|
rlpxName = "eth",
|
|
peerState = PeerState,
|
|
useRequestIds = true):
|
|
|
|
onPeerConnected do (peer: Peer):
|
|
let
|
|
network = peer.network
|
|
chain = network.chain
|
|
bestBlock = chain.getBestBlockHeader
|
|
chainForkId = chain.getForkId(bestBlock.blockNumber)
|
|
forkId = ForkId(
|
|
forkHash: chainForkId.crc.toBytesBE,
|
|
forkNext: chainForkId.nextFork.toBlockNumber)
|
|
|
|
traceSendSending "Status (0x00) " & prettyEthProtoName,
|
|
peer, td=bestBlock.difficulty,
|
|
bestHash=bestBlock.blockHash.toHex,
|
|
networkId=network.networkId,
|
|
genesis=chain.genesisHash.toHex,
|
|
forkHash=forkId.forkHash.toHex, forkNext=forkId.forkNext
|
|
|
|
let m = await peer.status(ethVersion,
|
|
network.networkId,
|
|
bestBlock.difficulty,
|
|
bestBlock.blockHash,
|
|
chain.genesisHash,
|
|
forkId,
|
|
timeout = chronos.seconds(10))
|
|
|
|
if traceHandshakes:
|
|
trace "Handshake: Local and remote networkId",
|
|
local=network.networkId, remote=m.networkId
|
|
trace "Handshake: Local and remote genesisHash",
|
|
local=chain.genesisHash.toHex, remote=m.genesisHash.toHex
|
|
trace "Handshake: Local and remote forkId",
|
|
local=(forkId.forkHash.toHex & "/" & $forkId.forkNext),
|
|
remote=(m.forkId.forkHash.toHex & "/" & $m.forkId.forkNext)
|
|
|
|
if m.networkId != network.networkId:
|
|
trace "Peer for a different network (networkId)", peer,
|
|
expectNetworkId=network.networkId, gotNetworkId=m.networkId
|
|
raise newException(UselessPeerError, "Eth handshake for different network")
|
|
|
|
if m.genesisHash != chain.genesisHash:
|
|
trace "Peer for a different network (genesisHash)", peer,
|
|
expectGenesis=chain.genesisHash.toHex, gotGenesis=m.genesisHash.toHex
|
|
raise newException(UselessPeerError, "Eth handshake for different network")
|
|
|
|
trace "Peer matches our network", peer
|
|
peer.state.initialized = true
|
|
peer.state.bestDifficulty = m.totalDifficulty
|
|
peer.state.bestBlockHash = m.bestHash
|
|
|
|
handshake:
|
|
# User message 0x00: Status.
|
|
proc status(peer: Peer,
|
|
ethVersionArg: uint,
|
|
networkId: NetworkId,
|
|
totalDifficulty: DifficultyInt,
|
|
bestHash: Hash256,
|
|
genesisHash: Hash256,
|
|
forkId: ForkId) =
|
|
traceRecvReceived "Status (0x00)",
|
|
peer, networkId, totalDifficulty, bestHash, genesisHash,
|
|
forkHash=forkId.forkHash.toHex, forkNext=forkId.forkNext
|
|
|
|
# User message 0x01: NewBlockHashes.
|
|
proc newBlockHashes(peer: Peer, hashes: openArray[NewBlockHashesAnnounce]) =
|
|
traceSendGossipDiscarding "NewBlockHashes (0x01)",
|
|
peer, hashes=hashes.len
|
|
discard
|
|
|
|
# User message 0x02: Transactions.
|
|
proc transactions(peer: Peer, transactions: openArray[Transaction]) =
|
|
traceSendGossipDiscarding "Transactions (0x02)",
|
|
peer, transactions=transactions.len
|
|
discard
|
|
|
|
requestResponse:
|
|
# User message 0x03: GetBlockHeaders.
|
|
proc getBlockHeaders(peer: Peer, request: BlocksRequest) =
|
|
if tracePackets:
|
|
if request.maxResults == 1 and request.startBlock.isHash:
|
|
traceRecvReceived "GetBlockHeaders/Hash (0x03)",
|
|
peer, blockHash=($request.startBlock.hash), count=1
|
|
elif request.maxResults == 1:
|
|
traceRecvReceived "GetBlockHeaders (0x03)",
|
|
peer, `block`=request.startBlock.number, count=1
|
|
elif request.startBlock.isHash:
|
|
traceRecvReceived "GetBlockHeaders/Hash (0x03)",
|
|
peer, firstBlockHash=($request.startBlock.hash),
|
|
count=request.maxResults,
|
|
step=traceStep(request)
|
|
else:
|
|
traceRecvReceived "GetBlockHeaders (0x03)",
|
|
peer, firstBlock=request.startBlock.number,
|
|
count=request.maxResults,
|
|
step=traceStep(request)
|
|
|
|
if request.maxResults > uint64(maxHeadersFetch):
|
|
debug "GetBlockHeaders (0x03) requested too many headers",
|
|
peer, requested=request.maxResults, max=maxHeadersFetch
|
|
await peer.disconnect(BreachOfProtocol)
|
|
return
|
|
|
|
let headers = peer.network.chain.getBlockHeaders(request)
|
|
if headers.len > 0:
|
|
traceSendReplying "with BlockHeaders (0x04)",
|
|
peer, sent=headers.len, requested=request.maxResults
|
|
else:
|
|
traceSendReplying "EMPTY BlockHeaders (0x04)",
|
|
peer, sent=0, requested=request.maxResults
|
|
|
|
await response.send(headers)
|
|
|
|
# User message 0x04: BlockHeaders.
|
|
proc blockHeaders(p: Peer, headers: openArray[BlockHeader])
|
|
|
|
requestResponse:
|
|
# User message 0x05: GetBlockBodies.
|
|
proc getBlockBodies(peer: Peer, hashes: openArray[Hash256]) =
|
|
traceRecvReceived "GetBlockBodies (0x05)",
|
|
peer, hashes=hashes.len
|
|
if hashes.len > maxBodiesFetch:
|
|
debug "GetBlockBodies (0x05) requested too many bodies",
|
|
peer, requested=hashes.len, max=maxBodiesFetch
|
|
await peer.disconnect(BreachOfProtocol)
|
|
return
|
|
|
|
let bodies = peer.network.chain.getBlockBodies(hashes)
|
|
if bodies.len > 0:
|
|
traceSendReplying "with BlockBodies (0x06)",
|
|
peer, sent=bodies.len, requested=hashes.len
|
|
else:
|
|
traceSendReplying "EMPTY BlockBodies (0x06)",
|
|
peer, sent=0, requested=hashes.len
|
|
|
|
await response.send(bodies)
|
|
|
|
# User message 0x06: BlockBodies.
|
|
proc blockBodies(peer: Peer, blocks: openArray[BlockBody])
|
|
|
|
# User message 0x07: NewBlock.
|
|
proc newBlock(peer: Peer, bh: EthBlock, totalDifficulty: DifficultyInt) =
|
|
# (Note, needs to use `EthBlock` instead of its alias `NewBlockAnnounce`
|
|
# because either `p2pProtocol` or RLPx doesn't work with an alias.)
|
|
traceSendGossipDiscarding "NewBlock (0x07)",
|
|
peer, totalDifficulty,
|
|
blockNumber = bh.header.blockNumber,
|
|
blockDifficulty = bh.header.difficulty
|
|
discard
|
|
|
|
# User message 0x08: NewPooledTransactionHashes.
|
|
proc newPooledTransactionHashes(peer: Peer, txHashes: openArray[Hash256]) =
|
|
traceSendGossipDiscarding "NewPooledTransactionHashes (0x08)",
|
|
peer, hashes=txHashes.len
|
|
discard
|
|
|
|
requestResponse:
|
|
# User message 0x09: GetPooledTransactions.
|
|
proc getPooledTransactions(peer: Peer, txHashes: openArray[Hash256]) =
|
|
traceRecvReceived "GetPooledTransactions (0x09)",
|
|
peer, hashes=txHashes.len
|
|
|
|
traceSendReplying "EMPTY PooledTransactions (0x10)",
|
|
peer, sent=0, requested=txHashes.len
|
|
await response.send([])
|
|
|
|
# User message 0x0a: PooledTransactions.
|
|
proc pooledTransactions(peer: Peer, transactions: openArray[Transaction])
|
|
|
|
nextId 0x0d
|
|
|
|
# User message 0x0d: GetNodeData.
|
|
proc getNodeData(peer: Peer, nodeHashes: openArray[Hash256]) =
|
|
traceRecvReceived "GetNodeData (0x0d)", peer,
|
|
hashes=nodeHashes.len
|
|
|
|
var data: seq[Blob]
|
|
if not peer.state.onGetNodeData.isNil:
|
|
peer.state.onGetNodeData(peer, nodeHashes, data)
|
|
else:
|
|
data = peer.network.chain.getStorageNodes(nodeHashes)
|
|
|
|
if data.len > 0:
|
|
traceSendReplying "with NodeData (0x0e)", peer,
|
|
sent=data.len, requested=nodeHashes.len
|
|
else:
|
|
traceSendReplying "EMPTY NodeData (0x0e)", peer,
|
|
sent=0, requested=nodeHashes.len
|
|
|
|
await peer.nodeData(data)
|
|
|
|
# User message 0x0e: NodeData.
|
|
proc nodeData(peer: Peer, data: openArray[Blob]) =
|
|
if not peer.state.onNodeData.isNil:
|
|
# The `onNodeData` should do its own `tracePacket`, because we don't
|
|
# know if this is a valid reply ("Got reply") or something else.
|
|
peer.state.onNodeData(peer, data)
|
|
else:
|
|
traceSendDiscarding "NodeData (0x0e)", peer,
|
|
bytes=data.len
|
|
|
|
requestResponse:
|
|
# User message 0x0f: GetReceipts.
|
|
proc getReceipts(peer: Peer, hashes: openArray[Hash256]) =
|
|
traceRecvReceived "GetReceipts (0x0f)", peer,
|
|
hashes=hashes.len
|
|
|
|
traceSendReplying "EMPTY Receipts (0x10)", peer,
|
|
sent=0, requested=hashes.len
|
|
await response.send([])
|
|
# TODO: implement `getReceipts` and reactivate this code
|
|
# await response.send(peer.network.chain.getReceipts(hashes))
|
|
|
|
# User message 0x10: Receipts.
|
|
proc receipts(peer: Peer, receipts: openArray[Receipt])
|