mirror of
https://github.com/status-im/nim-eth-p2p.git
synced 2025-01-23 13:19:38 +00:00
Alternative approach of Ethereum P2P networking.
This commit is contained in:
parent
7fac4cbf68
commit
eb72c6042f
348
eth_p2p/eth.nim
Normal file
348
eth_p2p/eth.nim
Normal file
@ -0,0 +1,348 @@
|
||||
#
|
||||
# Ethereum P2P
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
#
|
||||
|
||||
import asyncdispatch2, stint, rlp, eth_common, chronicles
|
||||
import protocols, peer
|
||||
|
||||
const
|
||||
# Max number of items we can ask for in ETH requests. These are the values
|
||||
# used in geth and if we ask for more than this the peers will disconnect
|
||||
# from us.
|
||||
MaxStateFetch* = 384
|
||||
MaxBodiesFetch* = 128
|
||||
MaxReceiptsFetch* = 256
|
||||
MaxHeadersFetch* = 192
|
||||
|
||||
const
|
||||
MsgStatus* = 0x00
|
||||
MsgNewBlockHashes* = 0x01
|
||||
MsgTransactions* = 0x02
|
||||
# eth/61
|
||||
MsgGetBlockHashes* = 0x03
|
||||
MsgBlockHashes* = 0x04
|
||||
MsgGetBlocks* = 0x05
|
||||
MsgBlocks* = 0x06
|
||||
MsgNewBlock* = 0x07
|
||||
MsgBlockHashesFromNumber* = 0x08
|
||||
# eth/62
|
||||
MsgGetBlockHeaders* = 0x03
|
||||
MsgBlockHeaders* = 0x04
|
||||
MsgGetBlockBodies* = 0x05
|
||||
MsgBlockBodies* = 0x06
|
||||
# eth/63
|
||||
MsgGetNodeData* = 0x0D
|
||||
MsgNodeData* = 0x0E
|
||||
MsgGetReceipts* = 0x0F
|
||||
MsgReceipts* = 0x10
|
||||
|
||||
|
||||
|
||||
const
|
||||
EthereumCap61* = initECap("eth", 61)
|
||||
EthereumCap62* = initECap("eth", 62)
|
||||
EthereumCap63* = initECap("eth", 63)
|
||||
|
||||
proc ethGetCmd*(epcap: EPeerCap, cmd: int): int =
|
||||
## Checks if specific message is supported by capability/protocol ``epcap``
|
||||
## and returns (zero based) specific to protocol message id.
|
||||
## If `cmd` identifier is not supported by specific protocol ``epcap`` -1 will
|
||||
## be returned.
|
||||
result = -1
|
||||
let cmdId = epcap.protoId(cmd)
|
||||
if epcap.cap == EthereumCap61:
|
||||
if cmdId in {MsgStatus, MsgNewBlockHashes, MsgTransactions,
|
||||
MsgGetBlockHashes, MsgBlockHashes, MsgGetBlocks,
|
||||
MsgBlocks, MsgNewBlock, MsgBlockHashesFromNumber}:
|
||||
result = cmdId
|
||||
elif epcap.cap == EthereumCap62:
|
||||
if cmdId in {MsgStatus, MsgNewBlockHashes, MsgTransactions,
|
||||
MsgGetBlockHeaders, MsgBlockHeaders, MsgGetBlockBodies,
|
||||
MsgBlockBodies, MsgNewBlock}:
|
||||
result = cmdId
|
||||
elif epcap.cap == EthereumCap63:
|
||||
if cmdId in {MsgStatus, MsgNewBlockHashes, MsgTransactions,
|
||||
MsgGetBlockHeaders, MsgBlockHeaders, MsgGetBlockBodies,
|
||||
MsgBlockBodies, MsgNewBlock, MsgGetNodeData, MsgNodeData,
|
||||
MsgGetReceipts, MsgReceipts}:
|
||||
result = cmdId
|
||||
else:
|
||||
discard
|
||||
|
||||
proc sendStatus*(peer: Peer, cap: EPeerCap, networkId: int,
|
||||
tdifficulty: UInt256, bestHash: Hash256,
|
||||
genesisHash: Hash256): Future[bool] {.async.} =
|
||||
## Send `Status` message to remote peer
|
||||
if peer.state notin {ConnectionState.None, Connected}: return false
|
||||
|
||||
let eindex = peer.supports([EthereumCap61, EthereumCap62, EthereumCap63])
|
||||
doAssert(eindex >= 0, "Peer do not support eth status()")
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgStatus)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(5)
|
||||
writer.append(int(cap.version))
|
||||
writer.append(int(networkId))
|
||||
writer.append(tdifficulty)
|
||||
writer.append(bestHash)
|
||||
writer.append(genesisHash)
|
||||
debug "Sending Status message", peer = $peer, version = $cap.version
|
||||
result = await peer.sendMessage(writer.finish())
|
||||
|
||||
proc sendNewBlockHashes*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[bool] {.async.} =
|
||||
if peer.state != Connected: return false
|
||||
|
||||
let eindex = peer.supports(EthereumCap61)
|
||||
doAssert(eindex >= 0, "Peer do not support NewBlockHashes()")
|
||||
let cap = peer.caps[eindex]
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgNewBlockHashes)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for hash in hashes:
|
||||
writer.append(hash)
|
||||
debug "Sending NewBlockHashes message", peer = $peer, version = $cap.version
|
||||
result = await peer.sendMessage(writer.finish())
|
||||
|
||||
proc sendNewBlockHashes*(peer: Peer,
|
||||
bhashes: seq[tuple[hash: Hash256, num: UInt256]]): Future[bool] {.async.} =
|
||||
if peer.state != Connected: return false
|
||||
|
||||
let eindex = peer.supports(EthereumCap62)
|
||||
doAssert(eindex >= 0, "Peer do not support NewBlockHashes()")
|
||||
let cap = peer.caps[eindex]
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgNewBlockHashes)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(bhashes))
|
||||
for item in bhashes:
|
||||
writer.startList(2)
|
||||
writer.append(item.hash)
|
||||
writer.append(item.num)
|
||||
debug "Sending NewBlockHashes message", peer = $peer, version = $cap.version
|
||||
result = await peer.sendMessage(writer.finish())
|
||||
|
||||
proc sendBlockHashes*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[bool] {.async.} =
|
||||
if peer.state != Connected: return false
|
||||
|
||||
let eindex = peer.supports(EthereumCap61)
|
||||
doAssert(eindex >= 0, "Peer do not support NewBlockHashes()")
|
||||
let cap = peer.caps[eindex]
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgBlockHashes)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for hash in hashes:
|
||||
writer.append(hash)
|
||||
debug "Sending BlockHashes message", peer = $peer, version = $cap.version
|
||||
result = await peer.sendMessage(writer.finish())
|
||||
|
||||
# Missing 0x01:sendNewBlockHashes eth/61
|
||||
# Missing 0x02:sendTransactions eth/61
|
||||
# Missing 0x06:sendBlocks eth/61
|
||||
# Missing 0x07:newBlock eth/61
|
||||
|
||||
# Missing 0x04:sendBlockHeaders eth/62
|
||||
# Missing 0x06:sendBlockBodies eth/62
|
||||
|
||||
# Missing 0x0E:sendNodeDat eth/63
|
||||
# Missing 0x10:sendReceipts eth/63
|
||||
|
||||
template sendReceive(peer: Peer, epcap: EPeerCap, sendMsg: BytesRange,
|
||||
msgId: int, name: string): EthereumMessage =
|
||||
let startTime = fastEpochTime()
|
||||
var msg = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
var sendfut = peer.sendMessage(sendMsg)
|
||||
yield sendfut
|
||||
let res = sendfut.read()
|
||||
if res:
|
||||
var fut = newFuture[EthereumMessage](name)
|
||||
peer.subscribe(epcap, msgId, fut)
|
||||
try:
|
||||
var msgfut = wait(fut, peer.responseTimeout)
|
||||
yield msgfut
|
||||
msg = msgfut.read()
|
||||
msg.elapsed = int(fastEpochTime() - startTime)
|
||||
peer.unsubscribe(epcap, msgId)
|
||||
except AsyncTimeoutError:
|
||||
msg = EthereumMessage(id: MsgTimeout, data: zeroBytesRlp)
|
||||
msg
|
||||
|
||||
proc getBlockHashesFromNumber*(peer: Peer, number: UInt256,
|
||||
maxBlocks: int): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap61)
|
||||
doAssert(eindex >= 0, "Peer do not support getblockHashesFromNumber()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgBlockHashesFromNumber)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(2)
|
||||
writer.append(number)
|
||||
writer.append(maxBlocks)
|
||||
debug "Sending BlockHashesFromNumber message", peer = $peer,
|
||||
version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlockHashes,
|
||||
"eth.getBlockHashesFromNumber")
|
||||
|
||||
proc getBlockHashes*(peer: Peer, hash: Hash256,
|
||||
maxBlocks: int): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap61)
|
||||
doAssert(eindex >= 0, "Peer do not support getBlockHashes()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetBlockHashes)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(2)
|
||||
writer.append(hash)
|
||||
writer.append(maxBlocks)
|
||||
debug "Sending GetBlockHashes message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlockHashes,
|
||||
"eth.getBlockHashes")
|
||||
|
||||
proc getBlocks*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap61)
|
||||
doAssert(eindex >= 0, "Peer do not support getBlocks()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgBlocks)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for item in hashes:
|
||||
writer.append(item)
|
||||
debug "Sending GetBlocks message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlocks, "eth.getBlocks")
|
||||
|
||||
proc getBlockHeaders*(peer: Peer, blok: UInt256, maxHeaders: int, skip: int,
|
||||
reverse: bool): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap62)
|
||||
doAssert(eindex >= 0, "Peer do not support getBlockHeaders()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetBlockHeaders)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(4)
|
||||
writer.append(blok)
|
||||
writer.append(maxHeaders)
|
||||
writer.append(skip)
|
||||
if reverse:
|
||||
writer.append(int(1))
|
||||
else:
|
||||
writer.append(int(0))
|
||||
debug "Sending GetBlockHeaders message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlockHeaders,
|
||||
"eth.getBlockHeaders")
|
||||
|
||||
proc getBlockHeaders*(peer: Peer, blok: Hash256, maxHeaders: int, skip: int,
|
||||
reverse: bool): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap62)
|
||||
doAssert(eindex >= 0, "Peer do not support getBlockHeaders()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetBlockHeaders)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(4)
|
||||
writer.append(blok)
|
||||
writer.append(maxHeaders)
|
||||
writer.append(skip)
|
||||
if reverse:
|
||||
writer.append(int(1))
|
||||
else:
|
||||
writer.append(int(0))
|
||||
debug "Sending GetBlockHeaders message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlockHeaders,
|
||||
"eth.getBlockHeaders")
|
||||
|
||||
proc getBlockBodies*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap62)
|
||||
doAssert(eindex >= 0, "Peer do not support getBlockBodies()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetBlockHeaders)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for item in hashes:
|
||||
writer.append(item)
|
||||
debug "Sending GetBlockBodies message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgBlockBodies,
|
||||
"eth.getBlockBodies")
|
||||
|
||||
proc getNodeData*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap63)
|
||||
doAssert(eindex >= 0, "Peer do not support getNodeData()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetNodeData)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for item in hashes:
|
||||
writer.append(item)
|
||||
debug "Sending GetNodeData message", peer = $peer, version = $cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgNodeData,
|
||||
"eth.getNodeData")
|
||||
|
||||
proc getReceipts*(peer: Peer,
|
||||
hashes: seq[Hash256]): Future[EthereumMessage] {.async.} =
|
||||
result = EthereumMessage(id: MsgBad, data: zeroBytesRlp)
|
||||
if peer.state != Connected: return
|
||||
|
||||
let eindex = peer.supports(EthereumCap63)
|
||||
doAssert(eindex >= 0, "Peer do not support getReceipts()")
|
||||
let cap = peer.caps[eindex]
|
||||
|
||||
var writer = initRlpWriter()
|
||||
let cmdId = cap.cmdId(MsgGetNodeData)
|
||||
writer.append(int(cmdId))
|
||||
writer.startList(len(hashes))
|
||||
for item in hashes:
|
||||
writer.append(item)
|
||||
debug "Sending GetReceipts message", peer = $peer,
|
||||
version = $cap.cap.version
|
||||
|
||||
result = peer.sendReceive(cap, writer.finish(), MsgReceipts,
|
||||
"eth.getReceipts")
|
1008
eth_p2p/peer.nim
Normal file
1008
eth_p2p/peer.nim
Normal file
File diff suppressed because it is too large
Load Diff
314
eth_p2p/protocols.nim
Normal file
314
eth_p2p/protocols.nim
Normal file
@ -0,0 +1,314 @@
|
||||
#
|
||||
# Ethereum P2P
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
#
|
||||
|
||||
import algorithm, hashes
|
||||
|
||||
type
|
||||
ECap* = distinct int
|
||||
## Internal representation of Ethereum Capability
|
||||
|
||||
CapName* = array[3, char]
|
||||
## Internal representation of Ethereum Capability protocol name
|
||||
|
||||
RlpCap* = object
|
||||
## RLP presentation of Ethereum Capability
|
||||
name*: CapName
|
||||
version*: int
|
||||
|
||||
EPeerCap* = object
|
||||
## Synchronized Ethereum Capability
|
||||
cap*: ECap
|
||||
offset*: int
|
||||
index*: int
|
||||
|
||||
ECapList* = seq[ECap]
|
||||
## Ethereum Capabilities list object
|
||||
RlpCapList* = seq[RlpCap]
|
||||
## RLP serialized list of Ethereum Capabilities
|
||||
EPeerCapList* = seq[EPeerCap]
|
||||
## Synchronized list of Ethereum Capabilities
|
||||
|
||||
proc newECapList*(): ECapList =
|
||||
## Create new empty Ethereum Capabilities list.
|
||||
result = newSeq[ECap]()
|
||||
|
||||
proc initECap*(name: string, version: int): ECap =
|
||||
## Create new Ethereum Capability using protocol name ``name`` and protocol
|
||||
## version ``version``.
|
||||
assert(len(name) >= 3)
|
||||
assert(version >= 0 and version <= 255)
|
||||
result = ECap((int(name[0]) shl 24) or (int(name[1]) shl 16) or
|
||||
(int(name[2]) shl 8) or (int(version) and 0xFF))
|
||||
|
||||
proc initECap*(rcap: RlpCap): ECap =
|
||||
## Create new Ethereum Capability using RLP serialized capability.
|
||||
assert(rcap.version >= 0 and rcap.version <= 255)
|
||||
result = ECap((int(rcap.name[0]) shl 24) or (int(rcap.name[1]) shl 16) or
|
||||
(int(rcap.name[2]) shl 8) or (int(rcap.version) and 0xFF))
|
||||
|
||||
proc initRCap*(ecap: ECap): RlpCap =
|
||||
## Create RLP serialized Ethereum Capability from internal representation of
|
||||
## Ethereum Capability ``ecap``.
|
||||
result.name[0] = chr((int(ecap) shr 24) and 0xFF)
|
||||
result.name[1] = chr((int(ecap) shr 16) and 0xFF)
|
||||
result.name[2] = chr((int(ecap) shr 8) and 0xFF)
|
||||
result.version = int(ecap) and 0xFF
|
||||
|
||||
proc hash*(ecap: ECap): Hash {.inline.} =
|
||||
## Calculate ``Hash`` for Ethereum Capability ``ecap``.
|
||||
result = Hash(ecap)
|
||||
|
||||
proc `$`*(ecap: ECap): string =
|
||||
## Get string representation of Ethereum Capability ``ecap``.
|
||||
result = newStringOfCap(8)
|
||||
result.setLen(4)
|
||||
result[3] = '/'
|
||||
result[2] = chr((int(ecap) shr 8) and 0xFF)
|
||||
result[1] = chr((int(ecap) shr 16) and 0xFF)
|
||||
result[0] = chr((int(ecap) shr 24) and 0xFF)
|
||||
result.add($(int(ecap) and 0xFF))
|
||||
|
||||
proc `$`*(rcap: RlpCap): string =
|
||||
## Get string representation of RLP serialized Ethereum Capability ``rcap``.
|
||||
result = newStringOfCap(8)
|
||||
result.setLen(4)
|
||||
result[3] = '/'
|
||||
result[2] = rcap.name[2]
|
||||
result[1] = rcap.name[1]
|
||||
result[0] = rcap.name[0]
|
||||
result.add($rcap.version)
|
||||
|
||||
proc cmpProto*(ecap1, ecap2: ECap): int =
|
||||
## Compare protocols of Ethereum Capabilities ``ecap1`` and ``ecap2``.
|
||||
result = ((int(ecap1) shr 8) and 0xFFFFFF) -
|
||||
((int(ecap2) shr 8) and 0xFFFFFF)
|
||||
|
||||
proc cmpVersion*(ecap1, ecap2: ECap): int =
|
||||
## Compare versions of Ethereum Capabilities ``ecap1`` and ``ecap2``.
|
||||
result = (int(ecap1) and 0xFF) - (int(ecap2) and 0xFF)
|
||||
|
||||
proc version*(ecap: ECap): int =
|
||||
## Get version of Ethereum Capability ``ecap`` as integer.
|
||||
result = (int(ecap) and 0xFF)
|
||||
|
||||
proc protocol*(ecap: ECap): string =
|
||||
## Get protocol of Ethereum Capability ``ecap`` as string.
|
||||
result = newString(3)
|
||||
result[0] = chr((int(ecap) shr 24) and 0xFF)
|
||||
result[1] = chr((int(ecap) shr 16) and 0xFF)
|
||||
result[2] = chr((int(ecap) shr 8) and 0xFF)
|
||||
|
||||
proc protocol*(epcap: EPeerCap): string {.inline.} =
|
||||
## Get protocol of Ethereum Capability ``epcap`` as string.
|
||||
result = epcap.cap.protocol()
|
||||
|
||||
proc version*(epcap: EPeerCap): int {.inline.} =
|
||||
## Get version of Ethereum Capability ``ecap`` as integer.
|
||||
result = epcap.cap.version()
|
||||
|
||||
proc `==`*(x: ECap, y: ECap): bool {.borrow.}
|
||||
## Compare Ethereum Capabilities ``x`` and ``y``.
|
||||
|
||||
type
|
||||
EProtocol* = object
|
||||
cap*: ECap
|
||||
length*: int
|
||||
|
||||
const
|
||||
EthereumProtocols* = [
|
||||
EProtocol(cap: initECap("eth", 61), length: 9),
|
||||
EProtocol(cap: initECap("eth", 62), length: 8),
|
||||
EProtocol(cap: initECap("eth", 63), length: 16),
|
||||
EProtocol(cap: initECap("les", 1), length: 15),
|
||||
EProtocol(cap: initECap("les", 2), length: 21)
|
||||
]
|
||||
|
||||
proc protoLength*(cap: ECap): int =
|
||||
## Get number of commands for Ethereum Capability ``cap``.
|
||||
for item in EthereumProtocols:
|
||||
if item.cap == cap:
|
||||
result = item.length
|
||||
break
|
||||
|
||||
proc cmp*(x, y: ECap): int =
|
||||
## Comparison function for sorting Ethereum Capabilities.
|
||||
if x == y: return 0
|
||||
if int(x) < int(y): return -1
|
||||
return 1
|
||||
|
||||
proc sync*(secap, ecap: ECapList): EPeerCapList =
|
||||
## Synchronize local and remote lists of Ethereum Capabilities, and calculate
|
||||
## protocol commands' offsets.
|
||||
##
|
||||
## Please note, that ``secap`` list must be sorted!
|
||||
result = newSeq[EPeerCap]()
|
||||
var curindex = 0
|
||||
for cap1 in secap:
|
||||
for cap2 in ecap:
|
||||
if cap1 == cap2:
|
||||
if len(result) == 0:
|
||||
# first added protocol has 0x10 offset
|
||||
result.add(EPeerCap(cap: cap1, offset: 16, index: curindex))
|
||||
inc(curindex)
|
||||
else:
|
||||
let prev = result[^1]
|
||||
if cmpProto(prev.cap, cap1) == 0:
|
||||
if cmpVersion(prev.cap, cap1) < 0:
|
||||
# replacing same protocol with most recent version
|
||||
result[^1] = EPeerCap(cap: cap1, offset: prev.offset)
|
||||
else:
|
||||
# adding new protocol with offset
|
||||
let offset = prev.offset + protoLength(prev.cap)
|
||||
result.add(EPeerCap(cap: cap1, offset: offset, index: curindex))
|
||||
inc(curindex)
|
||||
|
||||
proc register*(lcap: var ECapList, cap: ECap) =
|
||||
## Registers Ethereum Capability ``cap`` in list ``lcap``.
|
||||
##
|
||||
## Procedure keeps list ``lcap`` sorted.
|
||||
if len(lcap) == 0:
|
||||
lcap.add(cap)
|
||||
else:
|
||||
for item in lcap:
|
||||
if item == cap:
|
||||
return
|
||||
lcap.add(cap)
|
||||
sort(lcap, cmp)
|
||||
|
||||
proc register*(lcap: var ECapList, caps: openarray[ECap]) =
|
||||
## Registers array of Ethereum Capabilities ``caps`` in list ``lcap``.
|
||||
##
|
||||
## Procedure keeps ``lcap`` sorted.
|
||||
for item in caps:
|
||||
lcap.register(item)
|
||||
|
||||
proc unregister*(lcap: var ECapList, cap: ECap) =
|
||||
## Unregister Ethereum Capability ``cap`` from list ``lcap``.
|
||||
##
|
||||
## Procedure keeps list ``lcap`` sorted.
|
||||
var scap: seq[ECap]
|
||||
for item in lcap:
|
||||
if item != cap:
|
||||
scap.add(item)
|
||||
if len(scap) != len(lcap):
|
||||
shallowCopy(lcap, scap)
|
||||
|
||||
proc unregister*(lcap: var ECapList, caps: openarray[ECap]) =
|
||||
## Unregister array of Ethereum Capabilities from list ``lcap``.
|
||||
##
|
||||
## Procedure keeps list ``lcap`` sorted.
|
||||
for item in caps:
|
||||
lcap.unregister(item)
|
||||
|
||||
proc newECapList*(caps: openarray[ECap]): ECapList =
|
||||
## Create new Ethereum Capabilities list and populate it with capabilities
|
||||
## from ``caps``.
|
||||
result = newSeq[ECap]()
|
||||
result.register(caps)
|
||||
|
||||
proc newECapList*(cap: ECap): ECapList =
|
||||
## Create new Ethereum Capabilities list and register capability ``cap``
|
||||
## in it.
|
||||
result = newSeq[ECap]()
|
||||
result.register(cap)
|
||||
|
||||
proc `$`*(lcap: ECapList): string =
|
||||
## Get string representation of Ethereum Capabilities list ``lcap``.
|
||||
result = ""
|
||||
for item in lcap:
|
||||
if len(result) > 0:
|
||||
result.add(", ")
|
||||
result.add($item)
|
||||
else:
|
||||
result.add($item)
|
||||
|
||||
proc `$`*(pcap: EPeerCapList): string =
|
||||
## Get string representation of synchronized remote peer capabilities
|
||||
## ``pcap``.
|
||||
result = ""
|
||||
for item in pcap:
|
||||
if len(result) > 0:
|
||||
result.add(", ")
|
||||
result.add($item.cap)
|
||||
result.add(" (")
|
||||
result.add($item.offset)
|
||||
result.add("/")
|
||||
result.add($item.index)
|
||||
result.add(")")
|
||||
else:
|
||||
result.add($item.cap)
|
||||
result.add(" (")
|
||||
result.add($item.offset)
|
||||
result.add(" / ")
|
||||
result.add($item.index)
|
||||
result.add(")")
|
||||
|
||||
proc `$`*(rcap: RlpCapList): string =
|
||||
## Get string representation of RLP serialized Ethereum Capabilities list.
|
||||
result = ""
|
||||
for item in rcap:
|
||||
if len(result) > 0:
|
||||
result.add(", ")
|
||||
result.add($item)
|
||||
else:
|
||||
result.add($item)
|
||||
|
||||
proc cmdId*(epcap: EPeerCap, cmdid: int): int {.inline.} =
|
||||
## Get actual command id of command with ``cmdid`` using data from
|
||||
## synchronized peer capability ``epcap``.
|
||||
result = cmdid + epcap.offset
|
||||
|
||||
proc cmdId*(pcap: EPeerCapList, proto: string, cmdid: int): int =
|
||||
## Get actual command id from protocol with name ``proto`` and list of
|
||||
## synchronized peer capabilities ``pcap``.
|
||||
result = cmdid
|
||||
var cap = initECap(proto, 0)
|
||||
for item in pcap:
|
||||
if cmpProto(item.cap, cap) == 0:
|
||||
result += item.offset
|
||||
break
|
||||
|
||||
proc protoId*(epcap: EPeerCap, cmdid: int): int {.inline.} =
|
||||
## Get sub-protocol specific ``message id`` (zero based) from peer's cmd id.
|
||||
result = cmdid - epcap.offset
|
||||
|
||||
proc fromRlp*(lrlpcap: openarray[RlpCap]): ECapList =
|
||||
## Convert list of RLP serialized Ethereum Capabilities to list of Ethereum
|
||||
## Capabilities.
|
||||
result = newECapList()
|
||||
for item in lrlpcap:
|
||||
result.register(initECap(item))
|
||||
|
||||
proc toRlp*(lcap: openarray[ECap]): RlpCapList =
|
||||
## Convert list of Ethereum Capabilities ``lcap`` to list of RLP serialized
|
||||
## Ethereum Capabilities.
|
||||
result = newSeq[RlpCap]()
|
||||
for item in lcap:
|
||||
result.add(initRCap(item))
|
||||
|
||||
when isMainModule:
|
||||
var lcaplist = newECapList()
|
||||
var rcaplist = newECapList()
|
||||
|
||||
var a = initECap("eth", 61)
|
||||
var b = initECap("eth", 62)
|
||||
var c = initECap("eth", 63)
|
||||
var d = initECap("les", 1)
|
||||
var e = initECap("les", 2)
|
||||
var f = initECap("par", 1)
|
||||
var g = initECap("par", 2)
|
||||
|
||||
lcaplist.register([a, b, c, d])
|
||||
rcaplist.register([e, d, d, c, b, a, f, g])
|
||||
|
||||
echo "local ", lcaplist
|
||||
echo "remote ", rcaplist
|
||||
|
||||
echo sync(lcaplist, rcaplist)
|
149
tests/testpeer.nim
Normal file
149
tests/testpeer.nim
Normal file
@ -0,0 +1,149 @@
|
||||
import asyncdispatch2, eth_keys, eth_common, rlp, chronicles, nimcrypto, stint
|
||||
import peer, protocols, eth, enode
|
||||
|
||||
const
|
||||
NodeKey = "1b5b2a9c891067139c2aac53f66a84e2888ce494407a21c662dc546150e7e170"
|
||||
# enode://425f2261ef52010ed833bdbebbc67c36dfc208c0330e2c248fadef3feeb291c677265289bf64437055116b7d1dc3f78be5122d0041f021c01a501b876c664d4f@[::]:30303
|
||||
ENodeAddress = "enode://410a034fbd91e872cbe52a5fb5bec1f030d4239dab35efbdf53b6a3f09f42a84965f0d5c76c44d42b791339ff249675ec7fc656d69d10cb95b315a1136f6634e@192.168.2.10:30303"
|
||||
GenesisHash = "D4E56740F876AEF8C010B86A40D5F56745A118D0906A34E69AEC8C0DB1CB8FA3"
|
||||
|
||||
proc getInterface(en: EthereumNode, peer: Peer, epcap: EPeerCap): EInterface =
|
||||
var
|
||||
version: int
|
||||
network: int
|
||||
totalDifficulty: UInt256
|
||||
bestHash: Hash256
|
||||
genesisHash: Hash256
|
||||
|
||||
var currentTotalDifficulty = 0.u256
|
||||
var currentBestHash: Hash256
|
||||
var currentGenesisHash: Hash256
|
||||
|
||||
var hash = fromHex(GenesisHash)
|
||||
copyMem(addr currentGenesisHash, addr hash[0], 32)
|
||||
|
||||
proc handshake(peer: Peer): Future[bool] {.async.} =
|
||||
# Sending `Status` message to remote peer.
|
||||
let res = await peer.sendStatus(epcap, en.network, currentTotalDifficulty,
|
||||
currentBestHash, currentGenesisHash)
|
||||
if not res: return false
|
||||
|
||||
# Waiting for `Status` message from remote peer.
|
||||
var msg = await peer.getMessage(epcap)
|
||||
|
||||
# Converting synchronized command id back to protocol message id.
|
||||
var ethid = epcap.ethGetCmd(msg.id)
|
||||
|
||||
if msg.id == MsgBad or msg.id == MsgDisconnect:
|
||||
## Received message is rather incorrect or disconnect.
|
||||
result = false
|
||||
else:
|
||||
if ethid == MsgStatus:
|
||||
result = true
|
||||
# Decoding `Status` message frame.
|
||||
if (not msg.data.isList()) or (msg.data.listLen() != 5):
|
||||
debug "Malformed status message received", peer = $peer,
|
||||
isList = msg.data.isList(),
|
||||
listLength = msg.data.listLen()
|
||||
result = false
|
||||
|
||||
if not result: return
|
||||
|
||||
try:
|
||||
msg.data.enterList()
|
||||
version = msg.data.read(int)
|
||||
network = msg.data.read(int)
|
||||
totalDifficulty = msg.data.read(UInt256)
|
||||
bestHash = msg.data.read(Hash256)
|
||||
genesisHash = msg.data.read(Hash256)
|
||||
except:
|
||||
debug "Malformed status message received", peer = $peer
|
||||
result = false
|
||||
|
||||
if not result: return
|
||||
|
||||
# Verification of received data from `Status` message frame.
|
||||
if version != epcap.version():
|
||||
debug "Sub-protocol version did not match", peer = $peer,
|
||||
remoteVersion = $version,
|
||||
localVersion = $epcap.version
|
||||
await peer.disconnect(UselessPeer)
|
||||
return false
|
||||
|
||||
# Remote network id must be equal to our network id
|
||||
if network != en.network:
|
||||
debug "Different network id specified", peer = $peer,
|
||||
remoteNetwork = network,
|
||||
localNetwork = en.network
|
||||
await peer.disconnect(UselessPeer)
|
||||
return false
|
||||
|
||||
# Remote Genesis must be equal to our Genesis
|
||||
if genesisHash != currentGenesisHash:
|
||||
debug "Genesis hash did not match", peer = $peer,
|
||||
remoteGenesis = $genesisHash,
|
||||
localGenesis = $currentGenesisHash
|
||||
await peer.disconnect(UselessPeer)
|
||||
return false
|
||||
|
||||
debug "Ethereum Protocol started", peer = $peer,
|
||||
version = $epcap.version
|
||||
result = true
|
||||
else:
|
||||
# There must be no other messages, except `Status` message.
|
||||
debug "Incorrect message received", peer = $peer, msgId = $msg.id
|
||||
result = false
|
||||
|
||||
proc run(peer: Peer) {.async.} =
|
||||
while true:
|
||||
var msg = await peer.getMessage(epcap)
|
||||
if msg.id == MsgBad:
|
||||
# Remote peer sent malformed message or get disconnected without reason.
|
||||
# You don't need to close peer here, this is just notification so you
|
||||
# can break your cycle.
|
||||
debug "Sub-protocol received notification", peer = $peer
|
||||
break
|
||||
elif msg.id == MsgDisconnect:
|
||||
# Remote peer send `Disconnect` message with a reason.
|
||||
# You don't need to close peer here, this is just notification so you
|
||||
# can break your cycle.
|
||||
debug "Sub-protocol received disconnect notification", peer = $peer
|
||||
break
|
||||
else:
|
||||
# Here we can get any message specific exactly to this protocol.
|
||||
let ethid = epcap.ethGetCmd(msg.id)
|
||||
if ethid == -1:
|
||||
# Received message with id, which is not related to protocol
|
||||
debug "Sub-protocol received incorrect message", peer = $peer,
|
||||
msgid = $msg.id
|
||||
# peer.disconnect() will do `peer.close()` for us
|
||||
await peer.disconnect(BreachOfProtocol)
|
||||
break
|
||||
else:
|
||||
debug "Sub-protocol received message", peer = $peer,
|
||||
msgid = $msg.id,
|
||||
ethid = $ethid
|
||||
if ethid == MsgGetBlockHeaders:
|
||||
debug "Received MsgGetBlockHeaders", peer = $peer
|
||||
elif ethid == MsgGetBlockBodies:
|
||||
debug "Received MsgGetBlockBodies", peer = $peer
|
||||
elif ethid == MsgGetNodeData:
|
||||
debug "Received MsgGetNodeData", peer = $peer
|
||||
elif ethid == MsgGetReceipts:
|
||||
debug "Received MsgGetReceipts", peer = $peer
|
||||
|
||||
new result
|
||||
result.handshake = handshake
|
||||
result.run = run
|
||||
|
||||
proc test() {.async.} =
|
||||
var en = newEthereumNode(1, initPrivateKey(NodeKey))
|
||||
en.registerProtocol(initECap("eth", 63), getInterface)
|
||||
let peer = await connect(en, ENodeAddress)
|
||||
await sleepAsync(1000)
|
||||
var time = await peer.ping()
|
||||
echo "PONG RECEIVED in ", time, "ms"
|
||||
await sleepAsync(1000000)
|
||||
|
||||
when isMainModule:
|
||||
waitFor test()
|
Loading…
x
Reference in New Issue
Block a user