Support for creating JSON dumps of all P2P network traffic

Enable by compiling with -d:p2pdump. A chronicles log file named
p2p_messages.json will be created in the working directory. This
file will be consumed by the upcoming Chronicles Tail GUI (more
details will be provided on the wiki of this repo).

Other changes:

* Removes the use of package_visible_types (only partially so far)
* Simplifies the new Snappy code a little bit
This commit is contained in:
Zahary Karadjov 2018-11-10 02:18:00 +02:00
parent 7787e27427
commit eca93509b4
6 changed files with 327 additions and 212 deletions

View File

@ -15,10 +15,8 @@ import
eth_p2p/[kademlia, discovery, enode, peer_pool, rlpx], eth_p2p/[kademlia, discovery, enode, peer_pool, rlpx],
eth_p2p/private/types eth_p2p/private/types
types.forwardPublicTypes
export export
rlpx, enode, kademlia types, rlpx, enode, kademlia
proc addCapability*(n: var EthereumNode, p: ProtocolInfo) = proc addCapability*(n: var EthereumNode, p: ProtocolInfo) =
assert n.connectionState == ConnectionState.None assert n.connectionState == ConnectionState.None

View File

@ -18,8 +18,10 @@ requires "nim > 0.18.0",
"chronicles", "chronicles",
"asyncdispatch2", "asyncdispatch2",
"eth_common", "eth_common",
"snappy",
"package_visible_types", "package_visible_types",
"snappy" "serialization",
"json_serialization"
proc runTest(name: string, defs = "", lang = "c") = proc runTest(name: string, defs = "", lang = "c") =
exec "nim " & lang & " " & defs & " -d:testing --experimental:ForLoopMacros -r tests/" & name exec "nim " & lang & " " & defs & " -d:testing --experimental:ForLoopMacros -r tests/" & name

86
eth_p2p/p2p_tracing.nim Normal file
View File

@ -0,0 +1,86 @@
import
macros,
chronicles, serialization, serialization/streams, json_serialization/writer,
private/types
export
# XXX: Nim visibility rules get in the way here.
# It would be nice if the users of this module don't have to
# import json_serializer, but this won't work at the moment,
# because the `encode` call inside `logMsgEvent` has its symbols
# mixed in from the module where `logMsgEvent` is called
# (instead of from this module, which will be more logical).
init, writeValue, getOutput
# TODO: File this as an issue
const tracingEnabled* = defined(p2pdump)
when tracingEnabled:
logStream p2pMessages[json[file(p2p_messages.json,truncate)]]
proc logMsgEventImpl(eventName: static[string],
peer: Peer,
protocol: ProtocolInfo,
msgId: int,
json: string) =
# this is kept as a separate proc to reduce the code bloat
p2pMessages.log LogLevel.NONE, eventName, port = int(peer.network.address.tcpPort),
peer = $peer.remote,
protocol = protocol.name,
msgId, data = JsonString(json)
proc logMsgEvent[Msg](eventName: static[string], peer: Peer, msg: Msg) =
mixin msgProtocol, protocolInfo, msgId
logMsgEventImpl(eventName, peer,
Msg.msgProtocol.protocolInfo,
Msg.msgId,
StringJsonWriter.encode(msg))
proc logSentMsgFields*(peer: NimNode,
protocolInfo: NimNode,
msgId: int,
fields: openarray[NimNode]): NimNode =
## This generates the tracing code inserted in the message sending procs
## `fields` contains all the params that were serialized in the message
var tracer = ident("tracer")
result = quote do:
var `tracer` = init StringJsonWriter
beginRecord(`tracer`)
for f in fields:
result.add newCall(bindSym"writeField", tracer, newLit($f), f)
result.add quote do:
endRecord(`tracer`)
logMsgEventImpl("outgoing_msg", `peer`, `protocolInfo`, `msgId`, getOutput(`tracer`))
template logSentMsg*(peer: Peer, msg: auto) =
logMsgEvent("outgoing_msg", peer, msg)
template logReceivedMsg*(peer: Peer, msg: auto) =
logMsgEvent("incoming_msg", peer, msg)
template logConnectedPeer*(peer: Peer) =
p2pMessages.log LogLevel.NONE, "peer_connected",
port = int(peer.network.address.tcpPort),
peer = $peer.remote
template logAcceptedPeer*(peer: Peer) =
p2pMessages.log LogLevel.NONE, "peer_accepted",
port = int(peer.network.address.tcpPort),
peer = $peer.remote
template logDisconnectedPeer*(peer: Peer) =
p2pMessages.log LogLevel.NONE, "peer_disconnected",
port = int(peer.network.address.tcpPort),
peer = $peer.remote
else:
template logSentMsg*(peer: Peer, msg: auto) = discard
template logReceivedMsg*(peer: Peer, msg: auto) = discard
template logConnectedPeer*(peer: Peer) = discard
template logAcceptedPeer*(peer: Peer) = discard
template logDisconnectedPeer*(peer: Peer) = discard

View File

@ -4,9 +4,9 @@ import
rlp, asyncdispatch2, eth_common/eth_types, eth_keys, rlp, asyncdispatch2, eth_common/eth_types, eth_keys,
../enode, ../kademlia, ../discovery, ../options, ../rlpxcrypt ../enode, ../kademlia, ../discovery, ../options, ../rlpxcrypt
const useSnappy* = defined(useSnappy) const
useSnappy* = defined(useSnappy)
packageTypes:
type type
EthereumNode* = ref object EthereumNode* = ref object
networkId*: uint networkId*: uint
@ -15,74 +15,95 @@ packageTypes:
connectionState*: ConnectionState connectionState*: ConnectionState
keys*: KeyPair keys*: KeyPair
address*: Address address*: Address
rlpxCapabilities: seq[Capability]
rlpxProtocols: seq[ProtocolInfo]
listeningServer: StreamServer
protocolStates: seq[RootRef]
discovery: DiscoveryProtocol
peerPool*: PeerPool peerPool*: PeerPool
# Private fields:
rlpxCapabilities*: seq[Capability]
rlpxProtocols*: seq[ProtocolInfo]
listeningServer*: StreamServer
protocolStates*: seq[RootRef]
discovery*: DiscoveryProtocol
when useSnappy: when useSnappy:
protocolVersion: uint protocolVersion*: uint
Peer* = ref object Peer* = ref object
transport: StreamTransport
dispatcher: Dispatcher
lastReqId*: int
network*: EthereumNode
secretsState: SecretState
connectionState: ConnectionState
remote*: Node remote*: Node
protocolStates: seq[RootRef] network*: EthereumNode
outstandingRequests: seq[Deque[OutstandingRequest]]
awaitedMessages: seq[FutureBase]
when useSnappy:
snappyEnabled: bool
OutstandingRequest = object # Private fields:
id: int transport*: StreamTransport
future: FutureBase dispatcher*: Dispatcher
timeoutAt: uint64 lastReqId*: int
secretsState*: SecretState
connectionState*: ConnectionState
protocolStates*: seq[RootRef]
outstandingRequests*: seq[Deque[OutstandingRequest]]
awaitedMessages*: seq[FutureBase]
when useSnappy:
snappyEnabled*: bool
PeerPool* = ref object PeerPool* = ref object
network: EthereumNode # Private fields:
keyPair: KeyPair network*: EthereumNode
networkId: uint keyPair*: KeyPair
minPeers: int networkId*: uint
clientId: string minPeers*: int
discovery: DiscoveryProtocol clientId*: string
lastLookupTime: float discovery*: DiscoveryProtocol
connectedNodes: Table[Node, Peer] lastLookupTime*: float
connectingNodes: HashSet[Node] connectedNodes*: Table[Node, Peer]
running: bool connectingNodes*: HashSet[Node]
running*: bool
listenPort*: Port listenPort*: Port
observers: Table[int, PeerObserver] observers*: Table[int, PeerObserver]
PeerObserver* = object
onPeerConnected*: proc(p: Peer)
onPeerDisconnected*: proc(p: Peer)
Capability* = object
name*: string
version*: int
UnsupportedProtocol* = object of Exception
# This is raised when you attempt to send a message from a particular
# protocol to a peer that doesn't support the protocol.
MalformedMessageError* = object of Exception
PeerDisconnected* = object of Exception
reason*: DisconnectionReason
UselessPeerError* = object of Exception
##
## Quasy-private types. Use at your own risk.
##
ProtocolInfo* = ref object
name*: string
version*: int
messages*: seq[MessageInfo]
index*: int # the position of the protocol in the
# ordered list of supported protocols
# Private fields:
peerStateInitializer*: PeerStateInitializer
networkStateInitializer*: NetworkStateInitializer
handshake*: HandshakeStep
disconnectHandler*: DisconnectionHandler
MessageInfo* = object MessageInfo* = object
id*: int id*: int
name*: string name*: string
# Private fields:
thunk*: MessageHandler thunk*: MessageHandler
printer*: MessageContentPrinter printer*: MessageContentPrinter
requestResolver: RequestResolver requestResolver*: RequestResolver
nextMsgResolver: NextMsgResolver nextMsgResolver*: NextMsgResolver
CapabilityName* = array[3, char] Dispatcher* = ref object # private
Capability* = object
name*: CapabilityName
version*: int
ProtocolInfo* = ref object
name*: CapabilityName
version*: int
messages*: seq[MessageInfo]
index: int # the position of the protocol in the
# ordered list of supported protocols
peerStateInitializer: PeerStateInitializer
networkStateInitializer: NetworkStateInitializer
handshake: HandshakeStep
disconnectHandler: DisconnectionHandler
Dispatcher = ref object
# The dispatcher stores the mapping of negotiated message IDs between # The dispatcher stores the mapping of negotiated message IDs between
# two connected peers. The dispatcher objects are shared between # two connected peers. The dispatcher objects are shared between
# connections running with the same set of supported protocols. # connections running with the same set of supported protocols.
@ -95,23 +116,29 @@ packageTypes:
# #
# `messages` holds a mapping from valid message IDs to their handler procs. # `messages` holds a mapping from valid message IDs to their handler procs.
# #
protocolOffsets: seq[int] protocolOffsets*: seq[int]
messages: seq[ptr MessageInfo] messages*: seq[ptr MessageInfo]
activeProtocols: seq[ProtocolInfo] activeProtocols*: seq[ProtocolInfo]
PeerObserver* = object ##
onPeerConnected*: proc(p: Peer) ## Private types:
onPeerDisconnected*: proc(p: Peer) ##
MessageHandlerDecorator = proc(msgId: int, n: NimNode): NimNode OutstandingRequest* = object
MessageHandler = proc(x: Peer, msgId: int, data: Rlp): Future[void] id*: int
MessageContentPrinter = proc(msg: pointer): string future*: FutureBase
RequestResolver = proc(msg: pointer, future: FutureBase) timeoutAt*: uint64
NextMsgResolver = proc(msgData: Rlp, future: FutureBase)
PeerStateInitializer = proc(peer: Peer): RootRef # Private types:
NetworkStateInitializer = proc(network: EthereumNode): RootRef MessageHandlerDecorator* = proc(msgId: int, n: NimNode): NimNode
HandshakeStep = proc(peer: Peer): Future[void] MessageHandler* = proc(x: Peer, msgId: int, data: Rlp): Future[void]
DisconnectionHandler = proc(peer: Peer, MessageContentPrinter* = proc(msg: pointer): string
RequestResolver* = proc(msg: pointer, future: FutureBase)
NextMsgResolver* = proc(msgData: Rlp, future: FutureBase)
PeerStateInitializer* = proc(peer: Peer): RootRef
NetworkStateInitializer* = proc(network: EthereumNode): RootRef
HandshakeStep* = proc(peer: Peer): Future[void]
DisconnectionHandler* = proc(peer: Peer,
reason: DisconnectionReason): Future[void] {.gcsafe.} reason: DisconnectionReason): Future[void] {.gcsafe.}
RlpxMessageKind* = enum RlpxMessageKind* = enum
@ -141,14 +168,3 @@ packageTypes:
MessageTimeout, MessageTimeout,
SubprotocolReason = 0x10 SubprotocolReason = 0x10
UnsupportedProtocol* = object of Exception
# This is raised when you attempt to send a message from a particular
# protocol to a peer that doesn't support the protocol.
MalformedMessageError* = object of Exception
PeerDisconnected* = object of Exception
reason*: DisconnectionReason
UselessPeerError* = object of Exception

View File

@ -1,12 +1,14 @@
import import
macros, tables, algorithm, deques, hashes, options, typetraits, macros, tables, algorithm, deques, hashes, options, typetraits,
chronicles, nimcrypto, asyncdispatch2, rlp, eth_common, eth_keys, chronicles, nimcrypto, asyncdispatch2, rlp, eth_common, eth_keys,
private/types, kademlia, auth, rlpxcrypt, enode private/types, kademlia, auth, rlpxcrypt, enode, p2p_tracing
when useSnappy: when useSnappy:
import snappy import snappy
const devp2pSnappyVersion* = 5
const const
devp2pSnappyVersion* = 5 tracingEnabled = defined(p2pdump)
logScope: logScope:
topics = "rlpx" topics = "rlpx"
@ -16,6 +18,15 @@ const
defaultReqTimeout = 10000 defaultReqTimeout = 10000
maxMsgSize = 1024 * 1024 maxMsgSize = 1024 * 1024
when tracingEnabled:
import
eth_common/eth_types_json_serialization
export
# XXX: This is a work-around for a Nim issue.
# See a more detailed comment in p2p_tracing.nim
init, writeValue, getOutput
var var
gProtocols: seq[ProtocolInfo] gProtocols: seq[ProtocolInfo]
gDispatchers = initSet[Dispatcher]() gDispatchers = initSet[Dispatcher]()
@ -32,7 +43,7 @@ proc newFuture[T](location: var Future[T]) =
proc `$`*(p: Peer): string {.inline.} = proc `$`*(p: Peer): string {.inline.} =
$p.remote $p.remote
proc disconnect*(peer: Peer, reason: DisconnectionReason) {.async.} proc disconnect*(peer: Peer, reason: DisconnectionReason, notifyOtherPeer = true) {.async.}
template raisePeerDisconnected(msg: string, r: DisconnectionReason) = template raisePeerDisconnected(msg: string, r: DisconnectionReason) =
var e = newException(PeerDisconnected, msg) var e = newException(PeerDisconnected, msg)
@ -137,9 +148,7 @@ proc newProtocol(name: string, version: int,
peerInit: PeerStateInitializer, peerInit: PeerStateInitializer,
networkInit: NetworkStateInitializer): ProtocolInfo = networkInit: NetworkStateInitializer): ProtocolInfo =
new result new result
result.name[0] = name[0] result.name = name
result.name[1] = name[1]
result.name[2] = name[2]
result.version = version result.version = version
result.messages = @[] result.messages = @[]
result.peerStateInitializer = peerInit result.peerStateInitializer = peerInit
@ -205,12 +214,12 @@ proc registerMsg(protocol: var ProtocolInfo,
nextMsgResolver: NextMsgResolver) = nextMsgResolver: NextMsgResolver) =
if protocol.messages.len <= id: if protocol.messages.len <= id:
protocol.messages.setLen(id + 1) protocol.messages.setLen(id + 1)
protocol.messages[id] = MessageInfo.init(id = id, protocol.messages[id] = MessageInfo(id: id,
name = name, name: name,
thunk = thunk, thunk: thunk,
printer = printer, printer: printer,
requestResolver = requestResolver, requestResolver: requestResolver,
nextMsgResolver = nextMsgResolver) nextMsgResolver: nextMsgResolver)
proc registerProtocol(protocol: ProtocolInfo) = proc registerProtocol(protocol: ProtocolInfo) =
# TODO: This can be done at compile-time in the future # TODO: This can be done at compile-time in the future
@ -225,16 +234,20 @@ proc registerProtocol(protocol: ProtocolInfo) =
# Message composition and encryption # Message composition and encryption
# #
proc protocolOffset(peer: Peer, Protocol: type): int = template protocolOffset(peer: Peer, Protocol: type): int =
peer.dispatcher.protocolOffsets[Protocol.protocolInfo.index] peer.dispatcher.protocolOffsets[Protocol.protocolInfo.index]
proc perPeerMsgId(peer: Peer, proto: type, msgId: int): int {.inline.} = proc perPeerMsgIdImpl(peer: Peer, proto: ProtocolInfo, msgId: int): int {.inline.} =
result = msgId result = msgId
if not peer.dispatcher.isNil: if not peer.dispatcher.isNil:
result += peer.protocolOffset(proto) result += peer.dispatcher.protocolOffsets[proto.index]
proc perPeerMsgId*(peer: Peer, MsgType: type): int {.inline.} = proc supports*(peer: Peer, Protocol: type): bool {.inline.} =
peer.perPeerMsgId(MsgType.msgProtocol, MsgType.msgId) ## Checks whether a Peer supports a particular protocol
peer.protocolOffset(Protocol) != -1
template perPeerMsgId(peer: Peer, MsgType: type): int =
perPeerMsgIdImpl(peer, MsgType.msgProtocol.protocolInfo, MsgType.msgId)
proc writeMsgId(p: ProtocolInfo, msgId: int, peer: Peer, proc writeMsgId(p: ProtocolInfo, msgId: int, peer: Peer,
rlpOut: var RlpWriter) = rlpOut: var RlpWriter) =
@ -268,8 +281,6 @@ template compressMsg(peer: Peer, data: Bytes): Bytes =
data data
proc sendMsg*(peer: Peer, data: Bytes) {.async.} = proc sendMsg*(peer: Peer, data: Bytes) {.async.} =
trace "sending msg", peer, msg = getMsgName(peer, rlpFromBytes(data).read(int))
var cipherText = encryptMsg(peer.compressMsg(data), peer.secretsState) var cipherText = encryptMsg(peer.compressMsg(data), peer.secretsState)
try: try:
@ -279,6 +290,8 @@ proc sendMsg*(peer: Peer, data: Bytes) {.async.} =
raise raise
proc send*[Msg](peer: Peer, msg: Msg): Future[void] = proc send*[Msg](peer: Peer, msg: Msg): Future[void] =
logSentMsg(peer, msg)
var rlpWriter = initRlpWriter() var rlpWriter = initRlpWriter()
rlpWriter.append perPeerMsgId(peer, Msg) rlpWriter.append perPeerMsgId(peer, Msg)
rlpWriter.appendRecordType(msg, Msg.rlpFieldsCount > 1) rlpWriter.appendRecordType(msg, Msg.rlpFieldsCount > 1)
@ -292,9 +305,9 @@ proc registerRequest*(peer: Peer,
result = peer.lastReqId result = peer.lastReqId
let timeoutAt = fastEpochTime() + uint64(timeout) let timeoutAt = fastEpochTime() + uint64(timeout)
let req = OutstandingRequest.init(id = result, let req = OutstandingRequest(id: result,
future = responseFuture, future: responseFuture,
timeoutAt = timeoutAt) timeoutAt: timeoutAt)
peer.outstandingRequests[responseMsgId].addLast req peer.outstandingRequests[responseMsgId].addLast req
assert(not peer.dispatcher.isNil) assert(not peer.dispatcher.isNil)
@ -395,7 +408,6 @@ proc recvMsg*(peer: Peer): Future[tuple[msgId: int, msgData: Rlp]] {.async.} =
await peer.disconnectAndRaise(BreachOfProtocol, await peer.disconnectAndRaise(BreachOfProtocol,
"Cannot decrypt RLPx frame header") "Cannot decrypt RLPx frame header")
trace "waiting for message bytes", peer, msgSize
if msgSize > maxMsgSize: if msgSize > maxMsgSize:
await peer.disconnectAndRaise(BreachOfProtocol, await peer.disconnectAndRaise(BreachOfProtocol,
"RLPx message exceeds maximum size") "RLPx message exceeds maximum size")
@ -461,14 +473,17 @@ proc waitSingleMsg(peer: Peer, MsgType: type): Future[MsgType] {.async.} =
if nextMsgId == wantedId: if nextMsgId == wantedId:
try: try:
return checkedRlpRead(peer, nextMsgData, MsgType) result = checkedRlpRead(peer, nextMsgData, MsgType)
logReceivedMsg(peer, result)
return
except RlpError: except RlpError:
await peer.disconnectAndRaise(BreachOfProtocol, await peer.disconnectAndRaise(BreachOfProtocol,
"Invalid RLPx message body") "Invalid RLPx message body")
elif nextMsgId == 1: # p2p.disconnect elif nextMsgId == 1: # p2p.disconnect
raisePeerDisconnected("Unexpected disconnect", let reason = DisconnectionReason nextMsgData.listElem(0).toInt(uint32)
DisconnectionReason nextMsgData.listElem(0).toInt(uint32)) await peer.disconnect(reason, notifyOtherPeer = false)
raisePeerDisconnected("Unexpected disconnect", reason)
else: else:
warn "Dropped RLPX message", warn "Dropped RLPX message",
msg = peer.dispatcher.messages[nextMsgId].name msg = peer.dispatcher.messages[nextMsgId].name
@ -489,13 +504,11 @@ proc nextMsg*(peer: Peer, MsgType: type): Future[MsgType] =
proc dispatchMessages*(peer: Peer) {.async.} = proc dispatchMessages*(peer: Peer) {.async.} =
while true: while true:
var (msgId, msgData) = await peer.recvMsg() var (msgId, msgData) = await peer.recvMsg()
trace "received msg ", peer, msg = getMsgName(peer, msgId)
# rpl = msgData.inspect
if msgId == 1: # p2p.disconnect if msgId == 1: # p2p.disconnect
await peer.transport.closeWait() await peer.transport.closeWait()
debug "remote peer disconnected", peer, let reason = msgData.listElem(0).toInt(uint32).DisconnectionReason
reason = msgData.listElem(0).toInt(uint32).DisconnectionReason await peer.disconnect(reason, notifyOtherPeer = false)
break break
try: try:
@ -532,10 +545,6 @@ proc chooseFieldType(n: NimNode): NimNode =
proc getState(peer: Peer, proto: ProtocolInfo): RootRef = proc getState(peer: Peer, proto: ProtocolInfo): RootRef =
peer.protocolStates[proto.index] peer.protocolStates[proto.index]
proc supports*(peer: Peer, Protocol: type): bool {.inline.} =
## Checks whether a Peer supports a particular protocol
peer.protocolOffset(Protocol) != -1
template state*(peer: Peer, Protocol: type): untyped = template state*(peer: Peer, Protocol: type): untyped =
## Returns the state object of a particular protocol for a ## Returns the state object of a particular protocol for a
## particular connection. ## particular connection.
@ -651,6 +660,7 @@ macro rlpxProtocolImpl(name: static[string],
getState = bindSym "getState" getState = bindSym "getState"
getNetworkState = bindSym "getNetworkState" getNetworkState = bindSym "getNetworkState"
perPeerMsgId = bindSym "perPeerMsgId" perPeerMsgId = bindSym "perPeerMsgId"
perPeerMsgIdImpl = bindSym "perPeerMsgIdImpl"
linkSendFailureToReqFuture = bindSym "linkSendFailureToReqFuture" linkSendFailureToReqFuture = bindSym "linkSendFailureToReqFuture"
# By convention, all Ethereum protocol names must be abbreviated to 3 letters # By convention, all Ethereum protocol names must be abbreviated to 3 letters
@ -727,6 +737,7 @@ macro rlpxProtocolImpl(name: static[string],
reqTimeout: NimNode reqTimeout: NimNode
rlpWriter = ident"writer" rlpWriter = ident"writer"
appendParams = newNimNode(nnkStmtList) appendParams = newNimNode(nnkStmtList)
paramsToWrite = newSeq[NimNode](0)
reqId = ident"reqId" reqId = ident"reqId"
perPeerMsgIdVar = ident"perPeerMsgId" perPeerMsgIdVar = ident"perPeerMsgId"
@ -786,7 +797,7 @@ macro rlpxProtocolImpl(name: static[string],
appendParams.add quote do: appendParams.add quote do:
newFuture `resultIdent` newFuture `resultIdent`
let `reqId` = `registerRequestCall` let `reqId` = `registerRequestCall`
`append`(`rlpWriter`, `reqId`) paramsToWrite.add reqId
else: else:
appendParams.add quote do: appendParams.add quote do:
newFuture `resultIdent` newFuture `resultIdent`
@ -800,7 +811,7 @@ macro rlpxProtocolImpl(name: static[string],
addr(`receivedMsg`), addr(`receivedMsg`),
`reqIdVal`) `reqIdVal`)
if hasReqIds: if hasReqIds:
appendParams.add newCall(append, rlpWriter, reqId) paramsToWrite.add reqId
if n.body.kind != nnkEmpty: if n.body.kind != nnkEmpty:
# implement the receiving thunk proc that deserialzed the # implement the receiving thunk proc that deserialzed the
@ -828,7 +839,7 @@ macro rlpxProtocolImpl(name: static[string],
# This is a fragment of the sending proc that # This is a fragment of the sending proc that
# serializes each of the passed parameters: # serializes each of the passed parameters:
appendParams.add newCall(append, rlpWriter, param) paramsToWrite.add param
# Each message has a corresponding record type. # Each message has a corresponding record type.
# Here, we create its fields one by one: # Here, we create its fields one by one:
@ -852,6 +863,9 @@ macro rlpxProtocolImpl(name: static[string],
if paramCount > 1: if paramCount > 1:
readParamsPrelude.add newCall(enterList, receivedRlp) readParamsPrelude.add newCall(enterList, receivedRlp)
when tracingEnabled:
readParams.add newCall(bindSym"logReceivedMsg", msgSender, receivedMsg)
let thunkName = ident(msgName & "_thunk") let thunkName = ident(msgName & "_thunk")
var thunkProc = quote do: var thunkProc = quote do:
proc `thunkName`(`msgSender`: `Peer`, _: int, data: Rlp) = proc `thunkName`(`msgSender`: `Peer`, _: int, data: Rlp) =
@ -924,9 +938,9 @@ macro rlpxProtocolImpl(name: static[string],
quote: return `sendCall` quote: return `sendCall`
let `perPeerMsgIdValue` = if isSubprotocol: let `perPeerMsgIdValue` = if isSubprotocol:
newCall(perPeerMsgId, msgRecipient, protoNameIdent, perProtocolMsgId) newCall(perPeerMsgIdImpl, msgRecipient, protocol, newLit(msgId))
else: else:
perProtocolMsgId newLit(msgId)
if paramCount > 1: if paramCount > 1:
# In case there are more than 1 parameter, # In case there are more than 1 parameter,
@ -935,18 +949,25 @@ macro rlpxProtocolImpl(name: static[string],
newCall(startList, rlpWriter, newLit(paramCount)), newCall(startList, rlpWriter, newLit(paramCount)),
appendParams) appendParams)
for p in paramsToWrite:
appendParams.add newCall(append, rlpWriter, p)
# Make the send proc public # Make the send proc public
msgSendProc.name = newTree(nnkPostfix, ident("*"), msgSendProc.name) msgSendProc.name = newTree(nnkPostfix, ident("*"), msgSendProc.name)
let initWriter = quote do:
var `rlpWriter` = `initRlpWriter`()
const `perProtocolMsgId` = `msgId`
let `perPeerMsgIdVar` = `perPeerMsgIdValue`
`append`(`rlpWriter`, `perPeerMsgIdVar`)
when tracingEnabled:
appendParams.add logSentMsgFields(msgRecipient, protocol, msgId, paramsToWrite)
# let paramCountNode = newLit(paramCount) # let paramCountNode = newLit(paramCount)
msgSendProc.body = quote do: msgSendProc.body = quote do:
var `rlpWriter` = `initRlpWriter`() `initWriter`
let `perProtocolMsgId` = `msgId`
let `perPeerMsgIdVar` = `perPeerMsgIdValue`
`append`(`rlpWriter`, `perPeerMsgIdVar`)
`appendParams` `appendParams`
`finalizeRequest` `finalizeRequest`
`senderEpilogue` `senderEpilogue`
@ -1055,7 +1076,9 @@ macro rlpxProtocolImpl(name: static[string],
result.add newCall(bindSym("registerProtocol"), protocol) result.add newCall(bindSym("registerProtocol"), protocol)
when isMainModule: echo repr(result) when isMainModule: echo repr(result)
# echo repr(result)
when defined(debugRlpxProtocol) or defined(debugMacros):
echo repr(result)
macro rlpxProtocol*(protocolOptions: untyped, body: untyped): untyped = macro rlpxProtocol*(protocolOptions: untyped, body: untyped): untyped =
let protoName = $(protocolOptions[0]) let protoName = $(protocolOptions[0])
@ -1101,16 +1124,17 @@ proc callDisconnectHandlers(peer: Peer, reason: DisconnectionReason): Future[voi
return all(futures) return all(futures)
proc disconnect*(peer: Peer, reason: DisconnectionReason) {.async.} = proc disconnect*(peer: Peer, reason: DisconnectionReason, notifyOtherPeer = true) {.async.} =
if peer.connectionState notin {Disconnecting, Disconnected}: if peer.connectionState notin {Disconnecting, Disconnected}:
peer.connectionState = Disconnecting peer.connectionState = Disconnecting
try: try:
# TODO: investigate the failure here # TODO: investigate the failure here
if not peer.transport.closed and false: if false and notifyOtherPeer and not peer.transport.closed:
await peer.sendDisconnectMsg(reason) await peer.sendDisconnectMsg(reason)
finally: finally:
if not peer.dispatcher.isNil: if not peer.dispatcher.isNil:
await callDisconnectHandlers(peer, reason) await callDisconnectHandlers(peer, reason)
logDisconnectedPeer peer
peer.connectionState = Disconnected peer.connectionState = Disconnected
removePeer(peer.network, peer) removePeer(peer.network, peer)
@ -1184,22 +1208,10 @@ proc initSecretState(hs: var Handshake, authMsg, ackMsg: openarray[byte],
initSecretState(secrets, p.secretsState) initSecretState(secrets, p.secretsState)
burnMem(secrets) burnMem(secrets)
template baseProtocolVersion(node: EthereumNode): untyped = template checkSnappySupport(node: EthereumNode, handshake: Handshake, peer: Peer) =
when useSnappy: when useSnappy:
node.protocolVersion peer.snappyEnabled = node.protocolVersion >= devp2pSnappyVersion.uint and
else: handshake.version >= devp2pSnappyVersion.uint
devp2pVersion
template baseProtocolVersion(node: EthereumNode, peer: Peer): untyped =
when useSnappy:
if peer.snappyEnabled: node.protocolVersion
else: devp2pVersion
else:
devp2pVersion
template checkPeerProtocolVersion(peer: Peer, handshake: Handshake) =
when useSnappy:
peer.snappyEnabled = handshake.version >= devp2pSnappyVersion.uint
template getVersion(handshake: Handshake): uint = template getVersion(handshake: Handshake): uint =
when useSnappy: when useSnappy:
@ -1207,6 +1219,12 @@ template getVersion(handshake: Handshake): uint =
else: else:
devp2pVersion devp2pVersion
template baseProtocolVersion(node: EthereumNode): untyped =
when useSnappy:
node.protocolVersion
else:
devp2pVersion
template baseProtocolVersion(peer: Peer): uint = template baseProtocolVersion(peer: Peer): uint =
when useSnappy: when useSnappy:
if peer.snappyEnabled: devp2pSnappyVersion if peer.snappyEnabled: devp2pSnappyVersion
@ -1214,11 +1232,6 @@ template baseProtocolVersion(peer: Peer): uint =
else: else:
devp2pVersion devp2pVersion
template checkPeerNeedCompression(peer: Peer, node: EthereumNode) =
when useSnappy:
peer.snappyEnabled = peer.snappyEnabled and
node.protocolVersion >= devp2pSnappyVersion.uint
proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} = proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
new result new result
result.network = node result.network = node
@ -1228,7 +1241,7 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
var ok = false var ok = false
try: try:
result.transport = await connect(ta) result.transport = await connect(ta)
var handshake = newHandshake({Initiator, EIP8}, int(node.baseProtocolVersion())) var handshake = newHandshake({Initiator, EIP8}, int(node.baseProtocolVersion))
handshake.host = node.keys handshake.host = node.keys
var authMsg: array[AuthMessageMaxEIP8, byte] var authMsg: array[AuthMessageMaxEIP8, byte]
@ -1250,11 +1263,12 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
ret = handshake.decodeAckMessage(ackMsg) ret = handshake.decodeAckMessage(ackMsg)
check ret check ret
result.checkPeerProtocolVersion(handshake) node.checkSnappySupport(handshake, result)
initSecretState(handshake, ^authMsg, ackMsg, result) initSecretState(handshake, ^authMsg, ackMsg, result)
# if handshake.remoteHPubkey != remote.node.pubKey: # if handshake.remoteHPubkey != remote.node.pubKey:
# raise newException(Exception, "Remote pubkey is wrong") # raise newException(Exception, "Remote pubkey is wrong")
logConnectedPeer result
asyncCheck result.hello(handshake.getVersion(), asyncCheck result.hello(handshake.getVersion(),
node.clientId, node.clientId,
node.rlpxCapabilities, node.rlpxCapabilities,
@ -1267,7 +1281,6 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
warn "Remote nodeId is not its public key" # XXX: Do we care? warn "Remote nodeId is not its public key" # XXX: Do we care?
await postHelloSteps(result, response) await postHelloSteps(result, response)
result.checkPeerNeedCompression(node)
ok = true ok = true
except PeerDisconnected as e: except PeerDisconnected as e:
if e.reason != TooManyPeers: if e.reason != TooManyPeers:
@ -1313,8 +1326,8 @@ proc rlpxAccept*(node: EthereumNode,
ret = handshake.decodeAuthMessage(authMsg) ret = handshake.decodeAuthMessage(authMsg)
check ret check ret
result.checkPeerProtocolVersion(handshake) node.checkSnappySupport(handshake, result)
handshake.version = uint8(node.baseProtocolVersion(result)) handshake.version = uint8(result.baseProtocolVersion)
var ackMsg: array[AckMessageMaxEIP8, byte] var ackMsg: array[AckMessageMaxEIP8, byte]
var ackMsgLen: int var ackMsgLen: int
@ -1325,7 +1338,8 @@ proc rlpxAccept*(node: EthereumNode,
let listenPort = transport.localAddress().port let listenPort = transport.localAddress().port
await result.hello(result.baseProtocolVersion(), node.clientId, logAcceptedPeer result
await result.hello(result.baseProtocolVersion, node.clientId,
node.rlpxCapabilities, listenPort.uint, node.rlpxCapabilities, listenPort.uint,
node.keys.pubkey.getRaw()) node.keys.pubkey.getRaw())
@ -1339,7 +1353,6 @@ proc rlpxAccept*(node: EthereumNode,
result.remote = newNode(initEnode(handshake.remoteHPubkey, address)) result.remote = newNode(initEnode(handshake.remoteHPubkey, address))
await postHelloSteps(result, response) await postHelloSteps(result, response)
result.checkPeerNeedCompression(node)
except: except:
error "Exception in rlpxAccept", error "Exception in rlpxAccept",
err = getCurrentExceptionMsg(), err = getCurrentExceptionMsg(),

View File

@ -21,8 +21,8 @@ type
number: uint number: uint
NewBlockAnnounce* = object NewBlockAnnounce* = object
header: BlockHeader header*: BlockHeader
body {.rlpInline.}: BlockBody body* {.rlpInline.}: BlockBody
PeerState = ref object PeerState = ref object
initialized*: bool initialized*: bool