Skip to content

Commit

Permalink
Sync: Support for eth/65 protocol
Browse files Browse the repository at this point in the history
This patch adds the `eth/65` protocol, documented at
https://github.com/ethereum/devp2p/blob/master/caps/eth.md.

This is an intentionally simple patch, designed to not break, change or depend
on other functionality much, so that the "_old_ sync" methods can be run
usefully again and observed.  This patch isn't "new sync" (a different set of
sync algorithms), but it is one of the foundations.

For a while now Nimbus Eth1 only supported protocol `eth/63`.  But that's
obsolete, and very few nodes still support it.  This meant Nimbus Eth1 would
make slow progress trying to sync, as most up to date clients rejected it.

The current specification is `eth/66`, and the functionality we really need is
in `eth/64`.

So why `eth/65`?

- `eth/64` is essential because of the `forkId` feature.  But `eth/64` is on
  its way out as well.  Many clients, especially the most up to date Geth
  running the current hard-forks (Berlin/London) don't talk `eth/64` any more.

- `eth/66` is the current specification, but some clients don't talk `eth/66`
  yet.  We'd like to have the best peer connectivity during tests, and
  currently everything that talks `eth/66` also talks `eth/65`.

- Nimbus Eth1 RLPx only talks one version at a time.  (Without changes to the
  RLPx module.  When those go in we'll add `eth/64..eth/66` for greater peer
  reach and testing the `eth/66` behaviour.  For simplicity and decoupling,
  this patch contains just one version, the most useful.)

What are `eth/64` and `eth/65`?

- `eth/64` (EIP-2364) added `forkId` which allows nodes to distinguish between
  Ethereum (ETH) and Ethereum Classic (ETC) blockchains, which share the same
  genesis block.  `forkId` also protects the system when a new hard fork is
  going to be rolled out, by blocking interaction with out of date nodes.  The
  feature is required nowadays.

  We send the right details to allow connection (this has been tested a lot),
  but don't apply the full validation rules of EIP-2124/EIP-2364 in this patch.
  It's to keep this patch simple (in its effects) and because those rules have
  consequences best tested separately.  In practice the other node will reject
  us when we would reject it, so this is ok for testing, as long as it doesn't
  get seriously deployed.

- `eth/65` added more efficient transaction pool methods.

- Then a later version of `eth/65` (without a new number) added typed
  transactions, described in [EIP-2976](https://eips.ethereum.org/EIPS/eip-2976).

Why it's moved to `nimbus-eth1`:

- Mainly because `eth/64` onwards depend on the current state of block
  synchronisation, as well as the blockchain's sequence of hard-fork block
  numbers, both of which are part of `nimbus-eth1` run-time state.  These
  aren't available to pure `nim-eth` code.  Although it would be possible to
  add an API to let `nimbus-eth1` set these numbers, there isn't any point
  because the protocol would still only be useful to `nimbus-eth1`.

Signed-off-by: Jamie Lokier <[email protected]>
  • Loading branch information
jlokier committed Jul 27, 2021
1 parent a0d0e35 commit 3161d39
Showing 1 changed file with 160 additions and 0 deletions.
160 changes: 160 additions & 0 deletions nimbus/sync/protocol_eth65.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# 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:https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http:https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.

## This module implements Ethereum Wire Protocol version 65, `eth/65`.
## Specification:
## https://github.com/ethereum/devp2p/blob/master/caps/eth.md

import
chronos, stint, chronicles, stew/byteutils, macros,
eth/[common/eth_types, rlp, p2p],
eth/p2p/[rlpx, private/p2p_types, blockchain_utils],
../p2p/chain

type
NewBlockHashesAnnounce* = object
hash: KeccakHash
number: uint64 # Note: Was `uint`, wrong on 32-bit targets.

NewBlockAnnounce* = object
header*: BlockHeader
body* {.rlpInline.}: BlockBody

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*: KeccakHash
bestDifficulty*: DifficultyInt

const
maxStateFetch* = 384
maxBodiesFetch* = 128
maxReceiptsFetch* = 256
maxHeadersFetch* = 192
protocolVersion* = 65

func toHex(x: KeccakHash): string = x.data.toHex

p2pProtocol eth(version = protocolVersion,
peerState = PeerState,
useRequestIds = false):

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.u256,
)

let m = await peer.status(protocolVersion,
network.networkId,
bestBlock.difficulty,
bestBlock.blockHash,
chain.genesisHash,
forkId,
timeout = chronos.seconds(10))

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,
protocolVersion: uint,
networkId: NetworkId,
totalDifficulty: DifficultyInt,
bestHash: KeccakHash,
genesisHash: KeccakHash,
forkId: ForkId)

# User message 0x01: NewBlockHashes.
proc newBlockHashes(peer: Peer, hashes: openArray[NewBlockHashesAnnounce]) =
discard

# User message 0x02: Transactions.
proc transactions(peer: Peer, transactions: openArray[Transaction]) =
discard

requestResponse:
# User message 0x03: GetBlockHeaders.
proc getBlockHeaders(peer: Peer, request: BlocksRequest) =
if request.maxResults > uint64(maxHeadersFetch):
await peer.disconnect(BreachOfProtocol)
return

await response.send(peer.network.chain.getBlockHeaders(request))

# User message 0x04: BlockHeaders.
proc blockHeaders(p: Peer, headers: openArray[BlockHeader])

requestResponse:
# User message 0x05: GetBlockBodies.
proc getBlockBodies(peer: Peer, hashes: openArray[KeccakHash]) =
if hashes.len > maxBodiesFetch:
await peer.disconnect(BreachOfProtocol)
return

await response.send(peer.network.chain.getBlockBodies(hashes))

# User message 0x06: BlockBodies.
proc blockBodies(peer: Peer, blocks: openArray[BlockBody])

# User message 0x07: NewBlock.
proc newBlock(peer: Peer, bh: NewBlockAnnounce, totalDifficulty: DifficultyInt) =
discard

# User message 0x08: NewPooledTransactionHashes.
proc newPooledTransactionHashes(peer: Peer, hashes: openArray[KeccakHash]) =
discard

requestResponse:
# User message 0x09: GetPooledTransactions.
proc getPooledTransactions(peer: Peer, hashes: openArray[KeccakHash]) =
await response.send([])

# User message 0x0a: PooledTransactions.
proc pooledTransactions(peer: Peer, transactions: openArray[Transaction])

nextId 0x0d

requestResponse:
# User message 0x0d: GetNodeData.
proc getNodeData(peer: Peer, hashes: openArray[KeccakHash]) =
await response.send(peer.network.chain.getStorageNodes(hashes))

# User message 0x0e: NodeData.
proc nodeData(peer: Peer, data: openArray[Blob])

requestResponse:
# User message 0x0f: GetReceipts.
proc getReceipts(peer: Peer, hashes: openArray[KeccakHash]) =
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])

0 comments on commit 3161d39

Please sign in to comment.