mirror of https://github.com/status-im/nim-eth.git
Refactored the p2pProtocol macro to eliminate most code duplication in the backends
This commit is contained in:
parent
47c7a9887e
commit
f761889518
|
@ -1,4 +1,4 @@
|
||||||
proc getState(peer: Peer, proto: ProtocolInfo): RootRef =
|
proc getState*(peer: Peer, proto: ProtocolInfo): RootRef =
|
||||||
peer.protocolStates[proto.index]
|
peer.protocolStates[proto.index]
|
||||||
|
|
||||||
template state*(peer: Peer, Protocol: type): untyped =
|
template state*(peer: Peer, Protocol: type): untyped =
|
||||||
|
@ -8,7 +8,7 @@ template state*(peer: Peer, Protocol: type): untyped =
|
||||||
bind getState
|
bind getState
|
||||||
cast[Protocol.State](getState(peer, Protocol.protocolInfo))
|
cast[Protocol.State](getState(peer, Protocol.protocolInfo))
|
||||||
|
|
||||||
proc getNetworkState(node: EthereumNode, proto: ProtocolInfo): RootRef =
|
proc getNetworkState*(node: EthereumNode, proto: ProtocolInfo): RootRef =
|
||||||
node.protocolStates[proto.index]
|
node.protocolStates[proto.index]
|
||||||
|
|
||||||
template protocolState*(node: EthereumNode, Protocol: type): untyped =
|
template protocolState*(node: EthereumNode, Protocol: type): untyped =
|
||||||
|
@ -23,44 +23,6 @@ template networkState*(connection: Peer, Protocol: type): untyped =
|
||||||
|
|
||||||
proc initProtocolState*[T](state: T, x: Peer|EthereumNode) {.gcsafe.} = discard
|
proc initProtocolState*[T](state: T, x: Peer|EthereumNode) {.gcsafe.} = discard
|
||||||
|
|
||||||
proc createPeerState[ProtocolState](peer: Peer): RootRef =
|
|
||||||
var res = new ProtocolState
|
|
||||||
mixin initProtocolState
|
|
||||||
initProtocolState(res, peer)
|
|
||||||
return cast[RootRef](res)
|
|
||||||
|
|
||||||
proc createNetworkState[NetworkState](network: EthereumNode): RootRef {.gcsafe.} =
|
|
||||||
var res = new NetworkState
|
|
||||||
mixin initProtocolState
|
|
||||||
initProtocolState(res, network)
|
|
||||||
return cast[RootRef](res)
|
|
||||||
|
|
||||||
proc chooseFieldType(n: NimNode): NimNode =
|
|
||||||
## Examines the parameter types used in the message signature
|
|
||||||
## and selects the corresponding field type for use in the
|
|
||||||
## message object type (i.e. `p2p.hello`).
|
|
||||||
##
|
|
||||||
## For now, only openarray types are remapped to sequences.
|
|
||||||
result = n
|
|
||||||
if n.kind == nnkBracketExpr and eqIdent(n[0], "openarray"):
|
|
||||||
result = n.copyNimTree
|
|
||||||
result[0] = ident("seq")
|
|
||||||
|
|
||||||
proc popTimeoutParam(n: NimNode): NimNode =
|
|
||||||
var lastParam = n.params[^1]
|
|
||||||
if eqIdent(lastParam[0], "timeout"):
|
|
||||||
if lastParam[2].kind == nnkEmpty:
|
|
||||||
macros.error "You must specify a default value for the `timeout` parameter", lastParam
|
|
||||||
result = lastParam
|
|
||||||
n.params.del(n.params.len - 1)
|
|
||||||
|
|
||||||
proc verifyStateType(t: NimNode): NimNode =
|
|
||||||
result = t[1]
|
|
||||||
if result.kind == nnkSym and $result == "nil":
|
|
||||||
return nil
|
|
||||||
if result.kind != nnkBracketExpr or $result[0] != "ref":
|
|
||||||
macros.error($result & " must be a ref type")
|
|
||||||
|
|
||||||
proc initFuture[T](loc: var Future[T]) =
|
proc initFuture[T](loc: var Future[T]) =
|
||||||
loc = newFuture[T]()
|
loc = newFuture[T]()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,554 @@
|
||||||
|
import
|
||||||
|
macros,
|
||||||
|
std_shims/macros_shim, chronos/timer
|
||||||
|
|
||||||
|
type
|
||||||
|
MessageKind* = enum
|
||||||
|
msgHandshake
|
||||||
|
msgNotification
|
||||||
|
msgRequest
|
||||||
|
msgResponse
|
||||||
|
|
||||||
|
Message* = ref object
|
||||||
|
id*: int
|
||||||
|
ident*: NimNode
|
||||||
|
kind*: MessageKind
|
||||||
|
procDef*: NimNode
|
||||||
|
timeoutParam*: NimNode
|
||||||
|
recIdent*: NimNode
|
||||||
|
recBody*: NimNode
|
||||||
|
userHandler*: NimNode
|
||||||
|
|
||||||
|
Request* = ref object
|
||||||
|
queries*: seq[Message]
|
||||||
|
response*: Message
|
||||||
|
|
||||||
|
P2PProtocol* = ref object
|
||||||
|
# Settings
|
||||||
|
name*: string
|
||||||
|
version*: int
|
||||||
|
timeouts*: int64
|
||||||
|
useRequestIds*: bool
|
||||||
|
shortName*: string
|
||||||
|
outgoingRequestDecorator*: NimNode
|
||||||
|
incomingRequestDecorator*: NimNode
|
||||||
|
incomingRequestThunkDecorator*: NimNode
|
||||||
|
incomingResponseDecorator*: NimNode
|
||||||
|
incomingResponseThunkDecorator*: NimNode
|
||||||
|
PeerStateType*: NimNode
|
||||||
|
NetworkStateType*: NimNode
|
||||||
|
backend*: Backend
|
||||||
|
|
||||||
|
# Cached properties
|
||||||
|
nameIdent*: NimNode
|
||||||
|
protocolInfoVar*: NimNode
|
||||||
|
|
||||||
|
# All messages
|
||||||
|
messages*: seq[Message]
|
||||||
|
|
||||||
|
# Messages by type:
|
||||||
|
handshake*: Message
|
||||||
|
notifications*: seq[Message]
|
||||||
|
requests*: seq[Request]
|
||||||
|
|
||||||
|
# Output procs
|
||||||
|
outSendProcs*: NimNode
|
||||||
|
outRecvProcs*: NimNode
|
||||||
|
outProcRegistrations*: NimNode
|
||||||
|
|
||||||
|
# Event handlers
|
||||||
|
onPeerConnected*: NimNode
|
||||||
|
onPeerDisconnected*: NimNode
|
||||||
|
|
||||||
|
Backend* = ref object
|
||||||
|
# Code generators
|
||||||
|
implementMsg*: proc (p: P2PProtocol, msg: Message, resp: Message = nil)
|
||||||
|
implementProtocolInit*: proc (p: P2PProtocol): NimNode
|
||||||
|
afterProtocolInit*: proc (p: P2PProtocol)
|
||||||
|
|
||||||
|
# Bound symbols to the back-end run-time types and procs
|
||||||
|
PeerType*: NimNode
|
||||||
|
NetworkType*: NimNode
|
||||||
|
|
||||||
|
registerProtocol*: NimNode
|
||||||
|
setEventHandlers*: NimNode
|
||||||
|
|
||||||
|
BackendFactory* = proc (p: P2PProtocol): Backend
|
||||||
|
|
||||||
|
const
|
||||||
|
defaultReqTimeout = 10.seconds
|
||||||
|
|
||||||
|
proc createPeerState[Peer, ProtocolState](peer: Peer): RootRef =
|
||||||
|
var res = new ProtocolState
|
||||||
|
mixin initProtocolState
|
||||||
|
initProtocolState(res, peer)
|
||||||
|
return cast[RootRef](res)
|
||||||
|
|
||||||
|
proc createNetworkState[NetworkNode, NetworkState](network: NetworkNode): RootRef {.gcsafe.} =
|
||||||
|
var res = new NetworkState
|
||||||
|
mixin initProtocolState
|
||||||
|
initProtocolState(res, network)
|
||||||
|
return cast[RootRef](res)
|
||||||
|
|
||||||
|
proc expectBlockWithProcs*(n: NimNode): seq[NimNode] =
|
||||||
|
template helperName: auto = $n[0]
|
||||||
|
|
||||||
|
if n.len != 2 or n[1].kind != nnkStmtList:
|
||||||
|
error(helperName & " expects a block", n)
|
||||||
|
|
||||||
|
for p in n[1]:
|
||||||
|
if p.kind == nnkProcDef:
|
||||||
|
result.add p
|
||||||
|
elif p.kind == nnkCommentStmt:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
error(helperName & " expects a proc definition.", p)
|
||||||
|
|
||||||
|
proc nameOrNil*(procDef: NimNode): NimNode =
|
||||||
|
if procDef != nil:
|
||||||
|
procDef.name
|
||||||
|
else:
|
||||||
|
newNilLit()
|
||||||
|
|
||||||
|
proc chooseFieldType(n: NimNode): NimNode =
|
||||||
|
## Examines the parameter types used in the message signature
|
||||||
|
## and selects the corresponding field type for use in the
|
||||||
|
## message object type (i.e. `p2p.hello`).
|
||||||
|
##
|
||||||
|
## For now, only openarray types are remapped to sequences.
|
||||||
|
result = n
|
||||||
|
if n.kind == nnkBracketExpr and eqIdent(n[0], "openarray"):
|
||||||
|
result = n.copyNimTree
|
||||||
|
result[0] = ident("seq")
|
||||||
|
|
||||||
|
proc verifyStateType(t: NimNode): NimNode =
|
||||||
|
result = t[1]
|
||||||
|
if result.kind == nnkSym and $result == "nil":
|
||||||
|
return nil
|
||||||
|
if result.kind != nnkBracketExpr or $result[0] != "ref":
|
||||||
|
error $result & " must be a ref type"
|
||||||
|
|
||||||
|
proc processProtocolBody*(p: P2PProtocol, protocolBody: NimNode)
|
||||||
|
|
||||||
|
proc init*(T: type P2PProtocol, backendFactory: BackendFactory,
|
||||||
|
name: string, version: int, body: NimNode,
|
||||||
|
timeouts: int64, useRequestIds: bool, shortName: string,
|
||||||
|
outgoingRequestDecorator: NimNode,
|
||||||
|
incomingRequestDecorator: NimNode,
|
||||||
|
incomingRequestThunkDecorator: NimNode,
|
||||||
|
incomingResponseDecorator: NimNode,
|
||||||
|
incomingResponseThunkDecorator: NimNode,
|
||||||
|
peerState, networkState: NimNode): P2PProtocol =
|
||||||
|
|
||||||
|
result = P2PProtocol(
|
||||||
|
name: name,
|
||||||
|
version: version,
|
||||||
|
timeouts: timeouts,
|
||||||
|
useRequestIds: useRequestIds,
|
||||||
|
shortName: shortName,
|
||||||
|
outgoingRequestDecorator: outgoingRequestDecorator,
|
||||||
|
incomingRequestDecorator: incomingRequestDecorator,
|
||||||
|
incomingRequestThunkDecorator: incomingRequestThunkDecorator,
|
||||||
|
incomingResponseDecorator: incomingResponseDecorator,
|
||||||
|
incomingResponseThunkDecorator: incomingResponseThunkDecorator,
|
||||||
|
PeerStateType: verifyStateType peerState,
|
||||||
|
NetworkStateType: verifyStateType networkState,
|
||||||
|
nameIdent: ident(name),
|
||||||
|
protocolInfoVar: ident(name & "Protocol"),
|
||||||
|
outSendProcs: newStmtList(),
|
||||||
|
outRecvProcs: newStmtList(),
|
||||||
|
outProcRegistrations: newStmtList())
|
||||||
|
|
||||||
|
result.backend = backendFactory(result)
|
||||||
|
result.processProtocolBody body
|
||||||
|
|
||||||
|
if not result.backend.afterProtocolInit.isNil:
|
||||||
|
result.backend.afterProtocolInit(result)
|
||||||
|
|
||||||
|
proc augmentUserHandler(p: P2PProtocol, userHandlerProc: NimNode, msgId = -1) =
|
||||||
|
## This procs adds a set of common helpers available in all messages handlers
|
||||||
|
## (e.g. `perProtocolMsgId`, `peer.state`, etc).
|
||||||
|
var
|
||||||
|
prelude = newStmtList()
|
||||||
|
getState = ident"getState"
|
||||||
|
getNetworkState = ident"getNetworkState"
|
||||||
|
currentProtocolSym = ident"CurrentProtocol"
|
||||||
|
perProtocolMsgId = ident"perProtocolMsgId"
|
||||||
|
protocolInfoVar = p.protocolInfoVar
|
||||||
|
protocolNameIdent = p.nameIdent
|
||||||
|
PeerType = p.backend.PeerType
|
||||||
|
PeerStateType = p.PeerStateType
|
||||||
|
NetworkStateType = p.NetworkStateType
|
||||||
|
|
||||||
|
userHandlerProc.addPragma ident"gcsafe"
|
||||||
|
userHandlerProc.addPragma ident"async"
|
||||||
|
|
||||||
|
userHandlerProc.body.insert 0, prelude
|
||||||
|
|
||||||
|
when false:
|
||||||
|
# TODO
|
||||||
|
## Turns a regular proc definition into an async proc and adds
|
||||||
|
## the helpers for accessing the peer and network protocol states.
|
||||||
|
case msgKind
|
||||||
|
of msgRequest: userHandlerProc.applyDecorator incomingRequestDecorator
|
||||||
|
of msgResponse: userHandlerProc.applyDecorator incomingResponseDecorator
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
# We allow the user handler to use `openarray` params, but we turn
|
||||||
|
# those into sequences to make the `async` pragma happy.
|
||||||
|
for i in 1 ..< userHandlerProc.params.len:
|
||||||
|
var param = userHandlerProc.params[i]
|
||||||
|
param[^2] = chooseFieldType(param[^2])
|
||||||
|
|
||||||
|
prelude.add quote do:
|
||||||
|
type `currentProtocolSym` = `protocolNameIdent`
|
||||||
|
|
||||||
|
if msgId >= 0:
|
||||||
|
prelude.add quote do:
|
||||||
|
const `perProtocolMsgId` = `msgId`
|
||||||
|
|
||||||
|
# Define local accessors for the peer and the network protocol states
|
||||||
|
# inside each user message handler proc (e.g. peer.state.foo = bar)
|
||||||
|
if PeerStateType != nil:
|
||||||
|
prelude.add quote do:
|
||||||
|
template state(p: `PeerType`): `PeerStateType` =
|
||||||
|
cast[`PeerStateType`](`getState`(p, `protocolInfoVar`))
|
||||||
|
|
||||||
|
if NetworkStateType != nil:
|
||||||
|
prelude.add quote do:
|
||||||
|
template networkState(p: `PeerType`): `NetworkStateType` =
|
||||||
|
cast[`NetworkStateType`](`getNetworkState`(p.network, `protocolInfoVar`))
|
||||||
|
|
||||||
|
proc addPreludeDefs*(userHandlerProc: NimNode, definitions: NimNode) =
|
||||||
|
userHandlerProc.body[0].add definitions
|
||||||
|
|
||||||
|
proc eventHandlerToProc(p: P2PProtocol, doBlock: NimNode, handlerName: string): NimNode =
|
||||||
|
## Turns a "named" do block to a regular async proc
|
||||||
|
## (e.g. onPeerConnected do ...)
|
||||||
|
result = newTree(nnkProcDef)
|
||||||
|
doBlock.copyChildrenTo(result)
|
||||||
|
result.name = ident(p.name & handlerName) # genSym(nskProc, p.name & handlerName)
|
||||||
|
p.augmentUserHandler result
|
||||||
|
|
||||||
|
proc ensureTimeoutParam(procDef: NimNode, timeouts: int64): NimNode =
|
||||||
|
## Make sure the messages has a timeout parameter and it has the correct type.
|
||||||
|
## The parameter will be removed from the signature and returned for caching
|
||||||
|
## in the Message's timeoutParam field. It is needed only for the send procs.
|
||||||
|
var
|
||||||
|
Duration = bindSym"Duration"
|
||||||
|
milliseconds = bindSym"milliseconds"
|
||||||
|
lastParam = procDef.params[^1]
|
||||||
|
|
||||||
|
if eqIdent(lastParam[0], "timeout"):
|
||||||
|
if lastParam[2].kind == nnkEmpty:
|
||||||
|
error "You must specify a default value for the `timeout` parameter", lastParam
|
||||||
|
lastParam[2] = newCall(milliseconds, newLit(100))# newCall(Duration, lastParam[2])
|
||||||
|
if lastParam[1].kind == nnkEmpty:
|
||||||
|
lastParam[1] = Duration
|
||||||
|
elif not eqIdent(lastParam[1], "Duration"):
|
||||||
|
error "The timeout parameter should be of type 'chronos.Duration'", lastParam[1]
|
||||||
|
|
||||||
|
result = lastParam
|
||||||
|
procDef.params.del(procDef.params.len - 1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = newTree(nnkIdentDefs,
|
||||||
|
ident"timeout",
|
||||||
|
Duration,
|
||||||
|
newCall(milliseconds, newLit(timeouts)))
|
||||||
|
|
||||||
|
proc newMsg(p: P2PProtocol, kind: MessageKind, id: int,
|
||||||
|
procDef: NimNode, timeoutParam: NimNode = nil): Message =
|
||||||
|
|
||||||
|
if procDef[0].kind == nnkPostfix:
|
||||||
|
error("p2pProcotol procs are public by default. " &
|
||||||
|
"Please remove the postfix `*`.", procDef)
|
||||||
|
|
||||||
|
var
|
||||||
|
msgIdent = procDef.name
|
||||||
|
msgName = $msgIdent
|
||||||
|
recFields = newTree(nnkRecList)
|
||||||
|
recBody = newTree(nnkObjectTy, newEmptyNode(), newEmptyNode(), recFields)
|
||||||
|
recName = ident(msgName & "Obj")
|
||||||
|
|
||||||
|
for param, paramType in procDef.typedParams(skip = 1):
|
||||||
|
recFields.add newTree(nnkIdentDefs,
|
||||||
|
newTree(nnkPostfix, ident("*"), param), # The fields are public
|
||||||
|
chooseFieldType(paramType), # some types such as openarray
|
||||||
|
# are automatically remapped
|
||||||
|
newEmptyNode())
|
||||||
|
|
||||||
|
result = Message(id: id, ident: msgIdent, kind: kind,
|
||||||
|
procDef: procDef, recIdent: recName, recBody: recBody,
|
||||||
|
timeoutParam: timeoutParam)
|
||||||
|
|
||||||
|
if procDef.body.kind != nnkEmpty:
|
||||||
|
result.userHandler = copy procDef
|
||||||
|
p.augmentUserHandler result.userHandler
|
||||||
|
result.userHandler.name = genSym(nskProc, msgName)
|
||||||
|
|
||||||
|
p.messages.add result
|
||||||
|
|
||||||
|
proc identWithExportMarker*(msg: Message): NimNode =
|
||||||
|
newTree(nnkPostfix, ident("*"), msg.ident)
|
||||||
|
|
||||||
|
proc createSendProc*(msg: Message, procType = nnkProcDef): NimNode =
|
||||||
|
# TODO: file an issue:
|
||||||
|
# macros.newProc and macros.params doesn't work with nnkMacroDef
|
||||||
|
|
||||||
|
let pragmas = if procType == nnkProcDef: newTree(nnkPragma, ident"gcsafe")
|
||||||
|
else: newEmptyNode()
|
||||||
|
|
||||||
|
result = newNimNode(procType).add(
|
||||||
|
msg.identWithExportMarker, ## name
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
msg.procDef.params.copy, ## params
|
||||||
|
pragmas,
|
||||||
|
newEmptyNode(),
|
||||||
|
newStmtList()) ## body
|
||||||
|
|
||||||
|
if msg.kind in {msgHandshake, msgRequest}:
|
||||||
|
result[3].add msg.timeoutParam
|
||||||
|
|
||||||
|
result[3][0] = if procType == nnkMacroDef: ident "untyped"
|
||||||
|
else: newTree(nnkBracketExpr, ident("Future"), msg.recIdent)
|
||||||
|
|
||||||
|
proc appendAllParams*(node: NimNode, procDef: NimNode, skipFirst = 0): NimNode =
|
||||||
|
result = node
|
||||||
|
for p, _ in procDef.typedParams(skip = skipFirst):
|
||||||
|
result.add p
|
||||||
|
|
||||||
|
proc netInit*(p: P2PProtocol): NimNode =
|
||||||
|
if p.NetworkStateType == nil:
|
||||||
|
newNilLit()
|
||||||
|
else:
|
||||||
|
newTree(nnkBracketExpr, bindSym"createNetworkState",
|
||||||
|
p.backend.NetworkType,
|
||||||
|
p.NetworkStateType)
|
||||||
|
|
||||||
|
proc peerInit*(p: P2PProtocol): NimNode =
|
||||||
|
if p.PeerStateType == nil:
|
||||||
|
newNilLit()
|
||||||
|
else:
|
||||||
|
newTree(nnkBracketExpr, bindSym"createPeerState",
|
||||||
|
p.backend.PeerType,
|
||||||
|
p.PeerStateType)
|
||||||
|
|
||||||
|
proc processProtocolBody*(p: P2PProtocol, protocolBody: NimNode) =
|
||||||
|
## This procs handles all DSL statements valid inside a p2pProtocol.
|
||||||
|
##
|
||||||
|
## It will populate the protocol's fields such as:
|
||||||
|
## * handshake
|
||||||
|
## * requests
|
||||||
|
## * notifications
|
||||||
|
## * onPeerConnected
|
||||||
|
## * onPeerDisconnected
|
||||||
|
##
|
||||||
|
## All messages will have properly computed numeric IDs
|
||||||
|
##
|
||||||
|
var nextId = 0
|
||||||
|
|
||||||
|
for n in protocolBody:
|
||||||
|
case n.kind
|
||||||
|
of {nnkCall, nnkCommand}:
|
||||||
|
if eqIdent(n[0], "nextID"):
|
||||||
|
# By default message IDs are assigned in increasing order
|
||||||
|
# `nextID` can be used to skip some of the numeric slots
|
||||||
|
if n.len == 2 and n[1].kind == nnkIntLit:
|
||||||
|
nextId = n[1].intVal.int
|
||||||
|
else:
|
||||||
|
error("nextID expects a single int value", n)
|
||||||
|
|
||||||
|
elif eqIdent(n[0], "requestResponse"):
|
||||||
|
# `requestResponse` can be given a block of 2 or more procs.
|
||||||
|
# The last one is considered to be a response message, while
|
||||||
|
# all preceeding ones are requests triggering the response.
|
||||||
|
# The system makes sure to automatically insert a hidden `reqId`
|
||||||
|
# parameter used to discriminate the individual messages.
|
||||||
|
let procs = expectBlockWithProcs(n)
|
||||||
|
if procs.len < 2:
|
||||||
|
error "requestResponse expects a block with at least two proc definitions"
|
||||||
|
|
||||||
|
var queries = newSeq[Message]()
|
||||||
|
for i in 0 .. procs.len - 2:
|
||||||
|
var timeout = ensureTimeoutParam(procs[i], p.timeouts)
|
||||||
|
queries.add p.newMsg(msgRequest, nextId + i, procs[i], timeout)
|
||||||
|
|
||||||
|
p.requests.add Request(
|
||||||
|
queries: queries,
|
||||||
|
response: p.newMsg(msgResponse, nextId + procs.len - 1, procs[^1]))
|
||||||
|
|
||||||
|
inc nextId, procs.len
|
||||||
|
|
||||||
|
elif eqIdent(n[0], "handshake"):
|
||||||
|
let procs = expectBlockWithProcs(n)
|
||||||
|
if procs.len != 1:
|
||||||
|
error "handshake expects a block with a single proc definition", n
|
||||||
|
|
||||||
|
if p.handshake != nil:
|
||||||
|
error "The handshake for the protocol is already defined", n
|
||||||
|
|
||||||
|
var timeout = ensureTimeoutParam(procs[0], p.timeouts)
|
||||||
|
p.handshake = p.newMsg(msgHandshake, nextId, procs[0], timeout)
|
||||||
|
inc nextId
|
||||||
|
|
||||||
|
elif eqIdent(n[0], "onPeerConnected"):
|
||||||
|
p.onPeerConnected = p.eventHandlerToProc(n[1], "PeerConnected")
|
||||||
|
|
||||||
|
elif eqIdent(n[0], "onPeerDisconnected"):
|
||||||
|
p.onPeerDisconnected = p.eventHandlerToProc(n[1], "PeerDisconnected")
|
||||||
|
|
||||||
|
else:
|
||||||
|
error(repr(n) & " is not a recognized call in P2P protocol definitions", n)
|
||||||
|
|
||||||
|
of nnkProcDef:
|
||||||
|
p.notifications.add p.newMsg(msgNotification, nextId, n)
|
||||||
|
inc nextId
|
||||||
|
|
||||||
|
of nnkCommentStmt:
|
||||||
|
discard
|
||||||
|
|
||||||
|
else:
|
||||||
|
error "Illegal syntax in a P2P protocol definition", n
|
||||||
|
|
||||||
|
proc genTypeSection*(p: P2PProtocol): NimNode =
|
||||||
|
var
|
||||||
|
protocolName = p.nameIdent
|
||||||
|
peerState = p.PeerStateType
|
||||||
|
networkState= p.NetworkStateType
|
||||||
|
|
||||||
|
result = newStmtList()
|
||||||
|
result.add quote do:
|
||||||
|
# Create a type acting as a pseudo-object representing the protocol
|
||||||
|
# (e.g. p2p)
|
||||||
|
type `protocolName`* = object
|
||||||
|
|
||||||
|
if peerState != nil:
|
||||||
|
result.add quote do:
|
||||||
|
template State*(P: type `protocolName`): type = `peerState`
|
||||||
|
|
||||||
|
if networkState != nil:
|
||||||
|
result.add quote do:
|
||||||
|
template NetworkState*(P: type `protocolName`): type = `networkState`
|
||||||
|
|
||||||
|
for msg in p.messages:
|
||||||
|
let
|
||||||
|
msgId = msg.id
|
||||||
|
msgName = msg.ident
|
||||||
|
msgRecName = msg.recIdent
|
||||||
|
msgRecBody = msg.recBody
|
||||||
|
|
||||||
|
result.add quote do:
|
||||||
|
# This is a type featuring a single field for each message param:
|
||||||
|
type `msgRecName`* = `msgRecBody`
|
||||||
|
|
||||||
|
# Add a helper template for accessing the message type:
|
||||||
|
# e.g. p2p.hello:
|
||||||
|
template `msgName`*(T: type `protocolName`): type = `msgRecName`
|
||||||
|
|
||||||
|
# Add a helper template for obtaining the message Id for
|
||||||
|
# a particular message type:
|
||||||
|
template msgId*(T: type `msgRecName`): int = `msgId`
|
||||||
|
template msgProtocol*(T: type `msgRecName`): type = `protocolName`
|
||||||
|
|
||||||
|
proc genCode*(p: P2PProtocol): NimNode =
|
||||||
|
# TODO: try switching to a simpler for msg in p.messages: loop
|
||||||
|
if p.handshake != nil:
|
||||||
|
p.backend.implementMsg p, p.handshake
|
||||||
|
|
||||||
|
for msg in p.notifications:
|
||||||
|
p.backend.implementMsg p, msg
|
||||||
|
|
||||||
|
for req in p.requests:
|
||||||
|
p.backend.implementMsg p, req.response
|
||||||
|
for query in req.queries: p.backend.implementMsg(p, query, req.response)
|
||||||
|
|
||||||
|
result = newStmtList()
|
||||||
|
result.add p.genTypeSection()
|
||||||
|
|
||||||
|
let
|
||||||
|
protocolInfoVar = p.protocolInfoVar
|
||||||
|
protocolName = p.nameIdent
|
||||||
|
protocolInit = p.backend.implementProtocolInit(p)
|
||||||
|
|
||||||
|
result.add quote do:
|
||||||
|
# One global variable per protocol holds the protocol run-time data
|
||||||
|
var p = `protocolInit`
|
||||||
|
var `protocolInfoVar` = addr p
|
||||||
|
|
||||||
|
# The protocol run-time data is available as a pseudo-field
|
||||||
|
# (e.g. `p2p.protocolInfo`)
|
||||||
|
template protocolInfo*(P: type `protocolName`): auto = `protocolInfoVar`
|
||||||
|
|
||||||
|
result.add p.outSendProcs,
|
||||||
|
p.outRecvProcs,
|
||||||
|
p.outProcRegistrations
|
||||||
|
|
||||||
|
if p.onPeerConnected != nil: result.add p.onPeerConnected
|
||||||
|
if p.onPeerDisconnected != nil: result.add p.onPeerDisconnected
|
||||||
|
|
||||||
|
result.add newCall(p.backend.setEventHandlers,
|
||||||
|
protocolInfoVar,
|
||||||
|
nameOrNil p.onPeerConnected,
|
||||||
|
nameOrNil p.onPeerDisconnected)
|
||||||
|
|
||||||
|
result.add newCall(p.backend.registerProtocol, protocolInfoVar)
|
||||||
|
|
||||||
|
macro emitForSingleBackend(
|
||||||
|
name: static[string],
|
||||||
|
version: static[int],
|
||||||
|
backend: static[BackendFactory],
|
||||||
|
body: untyped,
|
||||||
|
# TODO Nim can't handle a proper duration paramter here
|
||||||
|
timeouts: static[int64] = defaultReqTimeout.milliseconds,
|
||||||
|
useRequestIds: static[bool] = true,
|
||||||
|
shortName: static[string] = "",
|
||||||
|
outgoingRequestDecorator: untyped = nil,
|
||||||
|
incomingRequestDecorator: untyped = nil,
|
||||||
|
incomingRequestThunkDecorator: untyped = nil,
|
||||||
|
incomingResponseDecorator: untyped = nil,
|
||||||
|
incomingResponseThunkDecorator: untyped = nil,
|
||||||
|
peerState = type(nil),
|
||||||
|
networkState = type(nil)): untyped =
|
||||||
|
|
||||||
|
var p = P2PProtocol.init(
|
||||||
|
backend,
|
||||||
|
name, version, body, timeouts,
|
||||||
|
useRequestIds, shortName,
|
||||||
|
outgoingRequestDecorator,
|
||||||
|
incomingRequestDecorator,
|
||||||
|
incomingRequestThunkDecorator,
|
||||||
|
incomingResponseDecorator,
|
||||||
|
incomingResponseThunkDecorator,
|
||||||
|
peerState.getType, networkState.getType)
|
||||||
|
|
||||||
|
result = p.genCode()
|
||||||
|
|
||||||
|
when defined(debugRlpxProtocol) or defined(debugMacros):
|
||||||
|
echo repr(result)
|
||||||
|
|
||||||
|
macro emitForAllBackends(backendSyms: typed, options: untyped, body: untyped): untyped =
|
||||||
|
let name = $(options[0])
|
||||||
|
|
||||||
|
var backends = newSeq[NimNode]()
|
||||||
|
if backendSyms.kind == nnkSym:
|
||||||
|
backends.add backendSyms
|
||||||
|
else:
|
||||||
|
for backend in backendSyms:
|
||||||
|
backends.add backend
|
||||||
|
|
||||||
|
result = newStmtList()
|
||||||
|
|
||||||
|
for backend in backends:
|
||||||
|
let call = copy options
|
||||||
|
call[0] = bindSym"emitForSingleBackend"
|
||||||
|
call.add newTree(nnkExprEqExpr, ident("name"), newLit(name))
|
||||||
|
call.add newTree(nnkExprEqExpr, ident("backend"), backend)
|
||||||
|
call.add newTree(nnkExprEqExpr, ident("body"), body)
|
||||||
|
result.add call
|
||||||
|
|
||||||
|
template p2pProtocol*(options: untyped, body: untyped) {.dirty.} =
|
||||||
|
bind emitForAllBackends
|
||||||
|
emitForAllBackends(p2pProtocolBackendImpl, options, body)
|
||||||
|
|
|
@ -144,11 +144,6 @@ type
|
||||||
DisconnectionHandler* = proc(peer: Peer,
|
DisconnectionHandler* = proc(peer: Peer,
|
||||||
reason: DisconnectionReason): Future[void] {.gcsafe.}
|
reason: DisconnectionReason): Future[void] {.gcsafe.}
|
||||||
|
|
||||||
RlpxMessageKind* = enum
|
|
||||||
rlpxNotification,
|
|
||||||
rlpxRequest,
|
|
||||||
rlpxResponse
|
|
||||||
|
|
||||||
ConnectionState* = enum
|
ConnectionState* = enum
|
||||||
None,
|
None,
|
||||||
Connecting,
|
Connecting,
|
||||||
|
@ -177,3 +172,5 @@ type
|
||||||
|
|
||||||
Response*[MsgType] = distinct Peer
|
Response*[MsgType] = distinct Peer
|
||||||
|
|
||||||
|
proc `$`*(peer: Peer): string = $peer.remote
|
||||||
|
|
||||||
|
|
453
eth/p2p/rlpx.nim
453
eth/p2p/rlpx.nim
|
@ -1,18 +1,20 @@
|
||||||
import
|
import
|
||||||
macros, tables, algorithm, deques, hashes, options, typetraits,
|
macros, tables, algorithm, deques, hashes, options, typetraits,
|
||||||
std_shims/macros_shim, chronicles, nimcrypto, chronos, eth/[rlp, common, keys, async_utils],
|
std_shims/macros_shim, chronicles, nimcrypto, chronos, eth/[rlp, common, keys, async_utils],
|
||||||
private/p2p_types, kademlia, auth, rlpxcrypt, enode
|
private/p2p_types, kademlia, auth, rlpxcrypt, enode, p2p_protocol_dsl
|
||||||
|
|
||||||
when useSnappy:
|
when useSnappy:
|
||||||
import snappy
|
import snappy
|
||||||
const devp2pSnappyVersion* = 5
|
const devp2pSnappyVersion* = 5
|
||||||
|
|
||||||
|
export
|
||||||
|
p2pProtocol
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "rlpx"
|
topics = "rlpx"
|
||||||
|
|
||||||
const
|
const
|
||||||
devp2pVersion* = 4
|
devp2pVersion* = 4
|
||||||
defaultReqTimeout = milliseconds(10000)
|
|
||||||
maxMsgSize = 1024 * 1024
|
maxMsgSize = 1024 * 1024
|
||||||
|
|
||||||
include p2p_tracing
|
include p2p_tracing
|
||||||
|
@ -305,7 +307,7 @@ proc send*[Msg](peer: Peer, msg: Msg): Future[void] =
|
||||||
rlpWriter.appendRecordType(msg, Msg.rlpFieldsCount > 1)
|
rlpWriter.appendRecordType(msg, Msg.rlpFieldsCount > 1)
|
||||||
peer.sendMsg rlpWriter.finish
|
peer.sendMsg rlpWriter.finish
|
||||||
|
|
||||||
proc registerRequest*(peer: Peer,
|
proc registerRequest(peer: Peer,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
responseFuture: FutureBase,
|
responseFuture: FutureBase,
|
||||||
responseMsgId: int): int =
|
responseMsgId: int): int =
|
||||||
|
@ -581,164 +583,74 @@ proc dispatchMessages*(peer: Peer) {.async.} =
|
||||||
return
|
return
|
||||||
peer.awaitedMessages[msgId] = nil
|
peer.awaitedMessages[msgId] = nil
|
||||||
|
|
||||||
macro p2pProtocolImpl(name: static[string],
|
template applyDecorator(p: NimNode, decorator: NimNode) =
|
||||||
version: static[uint],
|
if decorator.kind != nnkNilLit: p.addPragma decorator
|
||||||
body: untyped,
|
|
||||||
# TODO Nim can't handle a proper duration paramter here
|
|
||||||
timeout: static[int64] = defaultReqTimeout.milliseconds,
|
|
||||||
useRequestIds: static[bool] = true,
|
|
||||||
shortName: static[string] = "",
|
|
||||||
outgoingRequestDecorator: untyped = nil,
|
|
||||||
incomingRequestDecorator: untyped = nil,
|
|
||||||
incomingRequestThunkDecorator: untyped = nil,
|
|
||||||
incomingResponseDecorator: untyped = nil,
|
|
||||||
incomingResponseThunkDecorator: untyped = nil,
|
|
||||||
peerState = type(nil),
|
|
||||||
networkState = type(nil)): untyped =
|
|
||||||
## The macro used to defined RLPx sub-protocols. See README.
|
|
||||||
var
|
|
||||||
# XXX: deal with a Nim bug causing the macro params to be
|
|
||||||
# zero when they are captured by a closure:
|
|
||||||
outgoingRequestDecorator = outgoingRequestDecorator
|
|
||||||
incomingRequestDecorator = incomingRequestDecorator
|
|
||||||
incomingRequestThunkDecorator = incomingRequestThunkDecorator
|
|
||||||
incomingResponseDecorator = incomingResponseDecorator
|
|
||||||
incomingResponseThunkDecorator = incomingResponseThunkDecorator
|
|
||||||
useRequestIds = useRequestIds
|
|
||||||
version = version
|
|
||||||
defaultTimeout = timeout
|
|
||||||
|
|
||||||
nextId = 0
|
proc p2pProtocolBackendImpl*(p: P2PProtocol): Backend =
|
||||||
protoName = name
|
let
|
||||||
shortName = if shortName.len > 0: shortName else: protoName
|
|
||||||
outTypes = newNimNode(nnkStmtList)
|
|
||||||
outSendProcs = newNimNode(nnkStmtList)
|
|
||||||
outRecvProcs = newNimNode(nnkStmtList)
|
|
||||||
outProcRegistrations = newNimNode(nnkStmtList)
|
|
||||||
protoNameIdent = ident(protoName)
|
|
||||||
resultIdent = ident "result"
|
resultIdent = ident "result"
|
||||||
perProtocolMsgId = ident"perProtocolMsgId"
|
isSubprotocol = p.version > 0
|
||||||
response = ident"response"
|
|
||||||
currentProtocolSym = ident"CurrentProtocol"
|
|
||||||
protocol = ident(protoName & "Protocol")
|
|
||||||
isSubprotocol = version > 0'u
|
|
||||||
peerState = verifyStateType peerState.getType
|
|
||||||
networkState = verifyStateType networkState.getType
|
|
||||||
handshake = newNilLit()
|
|
||||||
disconnectHandler = newNilLit()
|
|
||||||
Option = bindSym "Option"
|
Option = bindSym "Option"
|
||||||
# XXX: Binding the int type causes instantiation failure for some reason
|
# XXX: Binding the int type causes instantiation failure for some reason
|
||||||
# Int = bindSym "int"
|
# Int = bindSym "int"
|
||||||
Int = ident "int"
|
Int = ident "int"
|
||||||
Peer = bindSym "Peer"
|
Peer = bindSym "Peer"
|
||||||
Duration = bindSym "Duration"
|
EthereumNode = bindSym "EthereumNode"
|
||||||
milliseconds = bindSym "milliseconds"
|
Response = bindSym "Response"
|
||||||
createNetworkState = bindSym "createNetworkState"
|
ResponseWithId = bindSym "ResponseWithId"
|
||||||
createPeerState = bindSym "createPeerState"
|
perProtocolMsgId = ident"perProtocolMsgId"
|
||||||
finish = bindSym "finish"
|
|
||||||
initRlpWriter = bindSym "initRlpWriter"
|
initRlpWriter = bindSym "initRlpWriter"
|
||||||
safeEnterList = bindSym "safeEnterList"
|
safeEnterList = bindSym "safeEnterList"
|
||||||
messagePrinter = bindSym "messagePrinter"
|
rlpFromBytes = bindSym "rlpFromBytes"
|
||||||
initProtocol = bindSym "initProtocol"
|
|
||||||
nextMsgResolver = bindSym "nextMsgResolver"
|
|
||||||
append = bindSym("append", brForceOpen)
|
append = bindSym("append", brForceOpen)
|
||||||
read = bindSym("read", brForceOpen)
|
read = bindSym("read", brForceOpen)
|
||||||
|
startList = bindSym "startList"
|
||||||
|
enterList = bindSym "enterList"
|
||||||
|
finish = bindSym "finish"
|
||||||
|
|
||||||
|
messagePrinter = bindSym "messagePrinter"
|
||||||
|
nextMsgResolver = bindSym "nextMsgResolver"
|
||||||
registerRequest = bindSym "registerRequest"
|
registerRequest = bindSym "registerRequest"
|
||||||
requestResolver = bindSym "requestResolver"
|
requestResolver = bindSym "requestResolver"
|
||||||
resolveResponseFuture = bindSym "resolveResponseFuture"
|
resolveResponseFuture = bindSym "resolveResponseFuture"
|
||||||
rlpFromBytes = bindSym "rlpFromBytes"
|
|
||||||
checkedRlpRead = bindSym "checkedRlpRead"
|
checkedRlpRead = bindSym "checkedRlpRead"
|
||||||
sendMsg = bindSym "sendMsg"
|
sendMsg = bindSym "sendMsg"
|
||||||
startList = bindSym "startList"
|
nextMsg = bindSym "nextMsg"
|
||||||
|
initProtocol = bindSym"initProtocol"
|
||||||
|
registerMsg = bindSym "registerMsg"
|
||||||
writeMsgId = bindSym "writeMsgId"
|
writeMsgId = bindSym "writeMsgId"
|
||||||
getState = bindSym "getState"
|
|
||||||
getNetworkState = bindSym "getNetworkState"
|
|
||||||
perPeerMsgId = bindSym "perPeerMsgId"
|
perPeerMsgId = bindSym "perPeerMsgId"
|
||||||
perPeerMsgIdImpl = bindSym "perPeerMsgIdImpl"
|
perPeerMsgIdImpl = bindSym "perPeerMsgIdImpl"
|
||||||
linkSendFailureToReqFuture = bindSym "linkSendFailureToReqFuture"
|
linkSendFailureToReqFuture = bindSym "linkSendFailureToReqFuture"
|
||||||
|
shortName = if p.shortName.len > 0: p.shortName else: p.name
|
||||||
|
|
||||||
# By convention, all Ethereum protocol names must be abbreviated to 3 letters
|
# By convention, all Ethereum protocol names must be abbreviated to 3 letters
|
||||||
doAssert shortName.len == 3
|
doAssert shortName.len == 3
|
||||||
|
|
||||||
template applyDecorator(p: NimNode, decorator: NimNode) =
|
new result
|
||||||
if decorator.kind != nnkNilLit: p.addPragma decorator
|
|
||||||
|
|
||||||
proc augmentUserHandler(userHandlerProc: NimNode,
|
result.registerProtocol = bindSym "registerProtocol"
|
||||||
msgId = -1,
|
result.setEventHandlers = bindSym "setEventHandlers"
|
||||||
msgKind = rlpxNotification,
|
result.PeerType = Peer
|
||||||
extraDefinitions: NimNode = nil) =
|
result.NetworkType = EthereumNode
|
||||||
## Turns a regular proc definition into an async proc and adds
|
|
||||||
## the helpers for accessing the peer and network protocol states.
|
|
||||||
case msgKind
|
|
||||||
of rlpxRequest: userHandlerProc.applyDecorator incomingRequestDecorator
|
|
||||||
of rlpxResponse: userHandlerProc.applyDecorator incomingResponseDecorator
|
|
||||||
else: discard
|
|
||||||
|
|
||||||
userHandlerProc.addPragma ident"gcsafe"
|
|
||||||
userHandlerProc.addPragma ident"async"
|
|
||||||
|
|
||||||
# We allow the user handler to use `openarray` params, but we turn
|
|
||||||
# those into sequences to make the `async` pragma happy.
|
|
||||||
for i in 1 ..< userHandlerProc.params.len:
|
|
||||||
var param = userHandlerProc.params[i]
|
|
||||||
param[^2] = chooseFieldType(param[^2])
|
|
||||||
|
|
||||||
var userHandlerDefinitions = newStmtList()
|
|
||||||
|
|
||||||
userHandlerDefinitions.add quote do:
|
|
||||||
type `currentProtocolSym` = `protoNameIdent`
|
|
||||||
|
|
||||||
if extraDefinitions != nil:
|
|
||||||
userHandlerDefinitions.add extraDefinitions
|
|
||||||
|
|
||||||
if msgId >= 0:
|
|
||||||
userHandlerDefinitions.add quote do:
|
|
||||||
const `perProtocolMsgId` = `msgId`
|
|
||||||
|
|
||||||
# Define local accessors for the peer and the network protocol states
|
|
||||||
# inside each user message handler proc (e.g. peer.state.foo = bar)
|
|
||||||
if peerState != nil:
|
|
||||||
userHandlerDefinitions.add quote do:
|
|
||||||
template state(p: `Peer`): `peerState` =
|
|
||||||
cast[`peerState`](`getState`(p, `protocol`))
|
|
||||||
|
|
||||||
if networkState != nil:
|
|
||||||
userHandlerDefinitions.add quote do:
|
|
||||||
template networkState(p: `Peer`): `networkState` =
|
|
||||||
cast[`networkState`](`getNetworkState`(p.network, `protocol`))
|
|
||||||
|
|
||||||
userHandlerProc.body.insert 0, userHandlerDefinitions
|
|
||||||
|
|
||||||
proc liftEventHandler(doBlock: NimNode, handlerName: string): NimNode =
|
|
||||||
## Turns a "named" do block to a regular async proc
|
|
||||||
## (e.g. onPeerConnected do ...)
|
|
||||||
var fn = newTree(nnkProcDef)
|
|
||||||
doBlock.copyChildrenTo(fn)
|
|
||||||
result = genSym(nskProc, protoName & handlerName)
|
|
||||||
fn.name = result
|
|
||||||
augmentUserHandler fn
|
|
||||||
outRecvProcs.add fn
|
|
||||||
|
|
||||||
proc addMsgHandler(msgId: int, n: NimNode,
|
|
||||||
msgKind = rlpxNotification,
|
|
||||||
responseMsgId = -1,
|
|
||||||
responseRecord: NimNode = nil): NimNode =
|
|
||||||
if n[0].kind == nnkPostfix:
|
|
||||||
macros.error("p2pProcotol procs are public by default. " &
|
|
||||||
"Please remove the postfix `*`.", n)
|
|
||||||
|
|
||||||
let
|
|
||||||
msgIdent = n.name
|
|
||||||
msgName = $n.name
|
|
||||||
hasReqIds = useRequestIds and msgKind in {rlpxRequest, rlpxResponse}
|
|
||||||
|
|
||||||
|
result.implementMsg = proc (p: P2PProtocol, msg: Message, resp: Message = nil) =
|
||||||
var
|
var
|
||||||
|
msgId = msg.id
|
||||||
|
msgRecName = msg.recIdent
|
||||||
|
msgKind = msg.kind
|
||||||
|
n = msg.procDef
|
||||||
|
responseMsgId = if resp != nil: resp.id else: -1
|
||||||
|
responseRecord = if resp != nil: resp.recIdent else: nil
|
||||||
|
msgIdent = n.name
|
||||||
|
msgName = $msgIdent
|
||||||
|
hasReqIds = p.useRequestIds and msgKind in {msgRequest, msgResponse}
|
||||||
userPragmas = n.pragma
|
userPragmas = n.pragma
|
||||||
|
|
||||||
# variables used in the sending procs
|
# variables used in the sending procs
|
||||||
msgRecipient = ident"msgRecipient"
|
msgRecipient = ident"msgRecipient"
|
||||||
sendTo = ident"sendTo"
|
sendTo = ident"sendTo"
|
||||||
reqTimeout: NimNode
|
|
||||||
rlpWriter = ident"writer"
|
rlpWriter = ident"writer"
|
||||||
appendParams = newNimNode(nnkStmtList)
|
appendParams = newNimNode(nnkStmtList)
|
||||||
paramsToWrite = newSeq[NimNode](0)
|
paramsToWrite = newSeq[NimNode](0)
|
||||||
|
@ -754,37 +666,16 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
callResolvedResponseFuture = newNimNode(nnkStmtList)
|
callResolvedResponseFuture = newNimNode(nnkStmtList)
|
||||||
|
|
||||||
# nodes to store the user-supplied message handling proc if present
|
# nodes to store the user-supplied message handling proc if present
|
||||||
userHandlerProc: NimNode = nil
|
|
||||||
userHandlerCall: NimNode = nil
|
userHandlerCall: NimNode = nil
|
||||||
awaitUserHandler = newStmtList()
|
awaitUserHandler = newStmtList()
|
||||||
|
|
||||||
# a record type associated with the message
|
|
||||||
msgRecord = newIdentNode(msgName & "Obj")
|
|
||||||
msgRecordFields = newTree(nnkRecList)
|
|
||||||
msgRecordBody = newTree(nnkObjectTy,
|
|
||||||
newEmptyNode(),
|
|
||||||
newEmptyNode(),
|
|
||||||
msgRecordFields)
|
|
||||||
|
|
||||||
result = msgRecord
|
|
||||||
if hasReqIds:
|
if hasReqIds:
|
||||||
# Messages using request Ids
|
# Messages using request Ids
|
||||||
readParams.add quote do:
|
readParams.add quote do:
|
||||||
let `reqId` = `read`(`receivedRlp`, int)
|
let `reqId` = `read`(`receivedRlp`, int)
|
||||||
|
|
||||||
case msgKind
|
case msgKind
|
||||||
of rlpxNotification: discard
|
of msgRequest:
|
||||||
of rlpxRequest:
|
|
||||||
# If the request proc has a default timeout specified, remove it from
|
|
||||||
# the signature for now so we can generate the `thunk` proc without it.
|
|
||||||
# The parameter will be added back later only for to the sender proc.
|
|
||||||
# When the timeout is not specified, we use a default one.
|
|
||||||
reqTimeout = popTimeoutParam(n)
|
|
||||||
if reqTimeout == nil:
|
|
||||||
reqTimeout = newTree(nnkIdentDefs,
|
|
||||||
ident"timeout",
|
|
||||||
Duration, newCall(milliseconds, newLit(defaultTimeout)))
|
|
||||||
|
|
||||||
let reqToResponseOffset = responseMsgId - msgId
|
let reqToResponseOffset = responseMsgId - msgId
|
||||||
let responseMsgId = quote do: `perPeerMsgIdVar` + `reqToResponseOffset`
|
let responseMsgId = quote do: `perPeerMsgIdVar` + `reqToResponseOffset`
|
||||||
|
|
||||||
|
@ -794,7 +685,7 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
# assumes there is one outstanding request at a time (if there are
|
# assumes there is one outstanding request at a time (if there are
|
||||||
# multiple requests we'll resolve them in FIFO order).
|
# multiple requests we'll resolve them in FIFO order).
|
||||||
let registerRequestCall = newCall(registerRequest, msgRecipient,
|
let registerRequestCall = newCall(registerRequest, msgRecipient,
|
||||||
reqTimeout[0],
|
msg.timeoutParam[0],
|
||||||
resultIdent,
|
resultIdent,
|
||||||
responseMsgId)
|
responseMsgId)
|
||||||
if hasReqIds:
|
if hasReqIds:
|
||||||
|
@ -807,61 +698,51 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
initFuture `resultIdent`
|
initFuture `resultIdent`
|
||||||
discard `registerRequestCall`
|
discard `registerRequestCall`
|
||||||
|
|
||||||
of rlpxResponse:
|
of msgResponse:
|
||||||
let reqIdVal = if hasReqIds: `reqId` else: newLit(-1)
|
let reqIdVal = if hasReqIds: `reqId` else: newLit(-1)
|
||||||
callResolvedResponseFuture.add quote do:
|
callResolvedResponseFuture.add quote do:
|
||||||
`resolveResponseFuture`(`msgSender`,
|
`resolveResponseFuture`(`msgSender`,
|
||||||
`perPeerMsgId`(`msgSender`, `msgRecord`),
|
`perPeerMsgId`(`msgSender`, `msgRecName`),
|
||||||
addr(`receivedMsg`),
|
addr(`receivedMsg`),
|
||||||
`reqIdVal`)
|
`reqIdVal`)
|
||||||
if hasReqIds:
|
if hasReqIds:
|
||||||
paramsToWrite.add newDotExpr(sendTo, ident"id")
|
paramsToWrite.add newDotExpr(sendTo, ident"id")
|
||||||
|
|
||||||
if n.body.kind != nnkEmpty:
|
of msgHandshake, msgNotification: discard
|
||||||
# implement the receiving thunk proc that deserialzed the
|
|
||||||
# message parameters and calls the user proc:
|
|
||||||
userHandlerProc = n.copyNimTree
|
|
||||||
userHandlerProc.name = genSym(nskProc, msgName)
|
|
||||||
|
|
||||||
|
if msg.userHandler != nil:
|
||||||
var extraDefs: NimNode
|
var extraDefs: NimNode
|
||||||
if msgKind == rlpxRequest:
|
if msgKind == msgRequest:
|
||||||
let peer = userHandlerProc.params[1][0]
|
let peer = msg.userHandler.params[1][0]
|
||||||
|
let response = ident"response"
|
||||||
if hasReqIds:
|
if hasReqIds:
|
||||||
extraDefs = quote do:
|
extraDefs = quote do:
|
||||||
let `response` = ResponseWithId[`responseRecord`](peer: `peer`, id: `reqId`)
|
let `response` = `ResponseWithId`[`responseRecord`](peer: `peer`, id: `reqId`)
|
||||||
else:
|
else:
|
||||||
extraDefs = quote do:
|
extraDefs = quote do:
|
||||||
let `response` = Response[`responseRecord`](`peer`)
|
let `response` = `Response`[`responseRecord`](`peer`)
|
||||||
|
|
||||||
augmentUserHandler userHandlerProc, msgId, msgKind, extraDefs
|
msg.userHandler.addPreludeDefs extraDefs
|
||||||
|
|
||||||
# This is the call to the user supplied handled. Here we add only the
|
# This is the call to the user supplied handled. Here we add only the
|
||||||
# initial peer param, while the rest of the params will be added later.
|
# initial peer param, while the rest of the params will be added later.
|
||||||
userHandlerCall = newCall(userHandlerProc.name, msgSender)
|
userHandlerCall = newCall(msg.userHandler.name, msgSender)
|
||||||
|
|
||||||
if hasReqIds:
|
if hasReqIds:
|
||||||
userHandlerProc.params.insert(2, newIdentDefs(reqId, ident"int"))
|
msg.userHandler.params.insert(2, newIdentDefs(reqId, ident"int"))
|
||||||
userHandlerCall.add reqId
|
userHandlerCall.add reqId
|
||||||
|
|
||||||
# When there is a user handler, it must be awaited in the thunk proc.
|
# When there is a user handler, it must be awaited in the thunk proc.
|
||||||
# Above, by default `awaitUserHandler` is set to a no-op statement list.
|
# Above, by default `awaitUserHandler` is set to a no-op statement list.
|
||||||
awaitUserHandler = newCall("await", userHandlerCall)
|
awaitUserHandler = newCall("await", userHandlerCall)
|
||||||
|
|
||||||
outRecvProcs.add(userHandlerProc)
|
p.outRecvProcs.add(msg.userHandler)
|
||||||
|
|
||||||
for param, paramType in n.typedParams(skip = 1):
|
for param, paramType in n.typedParams(skip = 1):
|
||||||
# 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:
|
||||||
paramsToWrite.add param
|
paramsToWrite.add param
|
||||||
|
|
||||||
# Each message has a corresponding record type.
|
|
||||||
# Here, we create its fields one by one:
|
|
||||||
msgRecordFields.add newTree(nnkIdentDefs,
|
|
||||||
newTree(nnkPostfix, ident("*"), param), # The fields are public
|
|
||||||
chooseFieldType(paramType), # some types such as openarray
|
|
||||||
# are automatically remapped
|
|
||||||
newEmptyNode())
|
|
||||||
|
|
||||||
# The received RLP data is deserialized to a local variable of
|
# The received RLP data is deserialized to a local variable of
|
||||||
# the message-specific type. This is done field by field here:
|
# the message-specific type. This is done field by field here:
|
||||||
let msgNameLit = newLit(msgName)
|
let msgNameLit = newLit(msgName)
|
||||||
|
@ -885,7 +766,7 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
var thunkProc = quote do:
|
var thunkProc = quote do:
|
||||||
proc `thunkName`(`msgSender`: `Peer`, _: int, data: Rlp) {.gcsafe.} =
|
proc `thunkName`(`msgSender`: `Peer`, _: int, data: Rlp) {.gcsafe.} =
|
||||||
var `receivedRlp` = data
|
var `receivedRlp` = data
|
||||||
var `receivedMsg` {.noinit.}: `msgRecord`
|
var `receivedMsg` {.noinit.}: `msgRecName`
|
||||||
`readParamsPrelude`
|
`readParamsPrelude`
|
||||||
`readParams`
|
`readParams`
|
||||||
`awaitUserHandler`
|
`awaitUserHandler`
|
||||||
|
@ -894,59 +775,48 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
for p in userPragmas: thunkProc.addPragma p
|
for p in userPragmas: thunkProc.addPragma p
|
||||||
|
|
||||||
case msgKind
|
case msgKind
|
||||||
of rlpxRequest: thunkProc.applyDecorator incomingRequestThunkDecorator
|
of msgRequest: thunkProc.applyDecorator p.incomingRequestThunkDecorator
|
||||||
of rlpxResponse: thunkProc.applyDecorator incomingResponseThunkDecorator
|
of msgResponse: thunkProc.applyDecorator p.incomingResponseThunkDecorator
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
thunkProc.addPragma ident"async"
|
thunkProc.addPragma ident"async"
|
||||||
|
|
||||||
outRecvProcs.add thunkProc
|
p.outRecvProcs.add thunkProc
|
||||||
|
|
||||||
outTypes.add quote do:
|
|
||||||
# This is a type featuring a single field for each message param:
|
|
||||||
type `msgRecord`* = `msgRecordBody`
|
|
||||||
|
|
||||||
# Add a helper template for accessing the message type:
|
|
||||||
# e.g. p2p.hello:
|
|
||||||
template `msgIdent`*(T: type `protoNameIdent`): type = `msgRecord`
|
|
||||||
|
|
||||||
# Add a helper template for obtaining the message Id for
|
|
||||||
# a particular message type:
|
|
||||||
template msgId*(T: type `msgRecord`): int = `msgId`
|
|
||||||
template msgProtocol*(T: type `msgRecord`): type = `protoNameIdent`
|
|
||||||
|
|
||||||
var msgSendProc = n
|
var msgSendProc = n
|
||||||
let msgSendProcName = n.name
|
let msgSendProcName = n.name
|
||||||
outSendProcs.add msgSendProc
|
p.outSendProcs.add msgSendProc
|
||||||
|
|
||||||
# TODO: check that the first param has the correct type
|
# TODO: check that the first param has the correct type
|
||||||
msgSendProc.params[1][0] = sendTo
|
msgSendProc.params[1][0] = sendTo
|
||||||
msgSendProc.addPragma ident"gcsafe"
|
msgSendProc.addPragma ident"gcsafe"
|
||||||
|
|
||||||
# Add a timeout parameter for all request procs
|
|
||||||
case msgKind
|
case msgKind
|
||||||
of rlpxRequest:
|
of msgRequest:
|
||||||
msgSendProc.params.add reqTimeout
|
# Add a timeout parameter for all request procs
|
||||||
of rlpxResponse:
|
msgSendProc.params.add msg.timeoutParam
|
||||||
|
of msgResponse:
|
||||||
# A response proc must be called with a response object that originates
|
# A response proc must be called with a response object that originates
|
||||||
# from a certain request. Here we change the Peer parameter at position
|
# from a certain request. Here we change the Peer parameter at position
|
||||||
# 1 to the correct strongly-typed ResponseType. The incoming procs still
|
# 1 to the correct strongly-typed ResponseType. The incoming procs still
|
||||||
# gets the normal Peer paramter.
|
# gets the normal Peer paramter.
|
||||||
|
# let rsp = bindSym "Response"
|
||||||
|
# let rspId = bindSym "ResponseWithId"
|
||||||
let
|
let
|
||||||
ResponseTypeHead = if useRequestIds: bindSym"ResponseWithId"
|
ResponseTypeHead = if p.useRequestIds: ResponseWithId
|
||||||
else: bindSym"Response"
|
else: Response
|
||||||
ResponseType = newTree(nnkBracketExpr, ResponseTypeHead, msgRecord)
|
ResponseType = newTree(nnkBracketExpr, ResponseTypeHead, msgRecName)
|
||||||
|
|
||||||
msgSendProc.params[1][1] = ResponseType
|
msgSendProc.params[1][1] = ResponseType
|
||||||
|
|
||||||
outSendProcs.add quote do:
|
p.outSendProcs.add quote do:
|
||||||
template send*(r: `ResponseType`, args: varargs[untyped]): auto =
|
template send*(r: `ResponseType`, args: varargs[untyped]): auto =
|
||||||
`msgSendProcName`(r, args)
|
`msgSendProcName`(r, args)
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
# We change the return type of the sending proc to a Future.
|
# We change the return type of the sending proc to a Future.
|
||||||
# If this is a request proc, the future will return the response record.
|
# If this is a request proc, the future will return the response record.
|
||||||
let rt = if msgKind != rlpxRequest: ident"void"
|
let rt = if msgKind != msgRequest: ident"void"
|
||||||
else: newTree(nnkBracketExpr, Option, responseRecord)
|
else: newTree(nnkBracketExpr, Option, responseRecord)
|
||||||
msgSendProc.params[0] = newTree(nnkBracketExpr, ident("Future"), rt)
|
msgSendProc.params[0] = newTree(nnkBracketExpr, ident("Future"), rt)
|
||||||
|
|
||||||
|
@ -956,7 +826,7 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
let `msgBytes` = `finish`(`rlpWriter`)
|
let `msgBytes` = `finish`(`rlpWriter`)
|
||||||
|
|
||||||
var sendCall = newCall(sendMsg, msgRecipient, msgBytes)
|
var sendCall = newCall(sendMsg, msgRecipient, msgBytes)
|
||||||
let senderEpilogue = if msgKind == rlpxRequest:
|
let senderEpilogue = if msgKind == msgRequest:
|
||||||
# In RLPx requests, the returned future was allocated here and passed
|
# In RLPx requests, the returned future was allocated here and passed
|
||||||
# to `registerRequest`. It's already assigned to the result variable
|
# to `registerRequest`. It's already assigned to the result variable
|
||||||
# of the proc, so we just wait for the sending operation to complete
|
# of the proc, so we just wait for the sending operation to complete
|
||||||
|
@ -969,7 +839,7 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
quote: return `sendCall`
|
quote: return `sendCall`
|
||||||
|
|
||||||
let perPeerMsgIdValue = if isSubprotocol:
|
let perPeerMsgIdValue = if isSubprotocol:
|
||||||
newCall(perPeerMsgIdImpl, msgRecipient, protocol, newLit(msgId))
|
newCall(perPeerMsgIdImpl, msgRecipient, p.protocolInfoVar, newLit(msgId))
|
||||||
else:
|
else:
|
||||||
newLit(msgId)
|
newLit(msgId)
|
||||||
|
|
||||||
|
@ -983,8 +853,39 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
for p in paramsToWrite:
|
for p in paramsToWrite:
|
||||||
appendParams.add newCall(append, rlpWriter, p)
|
appendParams.add newCall(append, rlpWriter, p)
|
||||||
|
|
||||||
|
if msgKind == msgHandshake:
|
||||||
|
var
|
||||||
|
rawSendProc = genSym(nskProc, msgName & "RawSend")
|
||||||
|
handshakeExchanger = newProc(name = msg.identWithExportMarker,
|
||||||
|
procType = nnkTemplateDef)
|
||||||
|
|
||||||
|
handshakeExchanger.params = msgSendProc.params.copyNimTree
|
||||||
|
handshakeExchanger.params.add msg.timeoutParam
|
||||||
|
handshakeExchanger.params[0] = newTree(nnkBracketExpr, ident("Future"), msgRecName)
|
||||||
|
|
||||||
|
var
|
||||||
|
forwardCall = newCall(rawSendProc).appendAllParams(handshakeExchanger)
|
||||||
|
peerVariable = ident"peer"
|
||||||
|
peerValue = forwardCall[1]
|
||||||
|
timeoutValue = msg.timeoutParam[0]
|
||||||
|
handshakeImpl = ident"handshakeImpl"
|
||||||
|
|
||||||
|
forwardCall[1] = peerVariable
|
||||||
|
forwardCall.del(forwardCall.len - 1)
|
||||||
|
|
||||||
|
handshakeExchanger.body = quote do:
|
||||||
|
let `peerVariable` = `peerValue`
|
||||||
|
let sendingFuture = `forwardCall`
|
||||||
|
`handshakeImpl`(`peerVariable`,
|
||||||
|
sendingFuture,
|
||||||
|
`nextMsg`(`peerVariable`, `msgRecName`),
|
||||||
|
`timeoutValue`)
|
||||||
|
|
||||||
|
msgSendProc.name = rawSendProc
|
||||||
|
p.outSendProcs.add handshakeExchanger
|
||||||
|
else:
|
||||||
# Make the send proc public
|
# Make the send proc public
|
||||||
msgSendProc.name = newTree(nnkPostfix, ident("*"), msgSendProc.name)
|
msgSendProc.name = msg.identWithExportMarker
|
||||||
|
|
||||||
let initWriter = quote do:
|
let initWriter = quote do:
|
||||||
var `rlpWriter` = `initRlpWriter`()
|
var `rlpWriter` = `initRlpWriter`()
|
||||||
|
@ -1003,123 +904,24 @@ macro p2pProtocolImpl(name: static[string],
|
||||||
`finalizeRequest`
|
`finalizeRequest`
|
||||||
`senderEpilogue`
|
`senderEpilogue`
|
||||||
|
|
||||||
if msgKind == rlpxRequest:
|
if msgKind == msgRequest:
|
||||||
msgSendProc.applyDecorator outgoingRequestDecorator
|
msgSendProc.applyDecorator p.outgoingRequestDecorator
|
||||||
|
|
||||||
outProcRegistrations.add(
|
p.outProcRegistrations.add(
|
||||||
newCall(bindSym("registerMsg"),
|
newCall(registerMsg,
|
||||||
protocol,
|
p.protocolInfoVar,
|
||||||
newIntLitNode(msgId),
|
newIntLitNode(msgId),
|
||||||
newStrLitNode($n.name),
|
newStrLitNode($n.name),
|
||||||
thunkName,
|
thunkName,
|
||||||
newTree(nnkBracketExpr, messagePrinter, msgRecord),
|
newTree(nnkBracketExpr, messagePrinter, msgRecName),
|
||||||
newTree(nnkBracketExpr, requestResolver, msgRecord),
|
newTree(nnkBracketExpr, requestResolver, msgRecName),
|
||||||
newTree(nnkBracketExpr, nextMsgResolver, msgRecord)))
|
newTree(nnkBracketExpr, nextMsgResolver, msgRecName)))
|
||||||
|
|
||||||
outTypes.add quote do:
|
result.implementProtocolInit = proc (p: P2PProtocol): NimNode =
|
||||||
# Create a type acting as a pseudo-object representing the protocol
|
return newCall(initProtocol,
|
||||||
# (e.g. p2p)
|
newLit(p.shortName),
|
||||||
type `protoNameIdent`* = object
|
newLit(p.version),
|
||||||
|
p.peerInit, p.netInit)
|
||||||
if peerState != nil:
|
|
||||||
outTypes.add quote do:
|
|
||||||
template State*(P: type `protoNameIdent`): type = `peerState`
|
|
||||||
|
|
||||||
if networkState != nil:
|
|
||||||
outTypes.add quote do:
|
|
||||||
template NetworkState*(P: type `protoNameIdent`): type = `networkState`
|
|
||||||
|
|
||||||
for n in body:
|
|
||||||
case n.kind
|
|
||||||
of {nnkCall, nnkCommand}:
|
|
||||||
if eqIdent(n[0], "nextID"):
|
|
||||||
# By default message IDs are assigned in increasing order
|
|
||||||
# `nextID` can be used to skip some of the numeric slots
|
|
||||||
if n.len == 2 and n[1].kind == nnkIntLit:
|
|
||||||
nextId = n[1].intVal.int
|
|
||||||
else:
|
|
||||||
macros.error("nextID expects a single int value", n)
|
|
||||||
elif eqIdent(n[0], "requestResponse"):
|
|
||||||
# `requestResponse` can be given a block of 2 or more procs.
|
|
||||||
# The last one is considered to be a response message, while
|
|
||||||
# all preceeding ones are requests triggering the response.
|
|
||||||
# The system makes sure to automatically insert a hidden `reqId`
|
|
||||||
# parameter used to discriminate the individual messages.
|
|
||||||
block processReqResp:
|
|
||||||
if n.len == 2 and n[1].kind == nnkStmtList:
|
|
||||||
var procs = newSeq[NimNode](0)
|
|
||||||
for def in n[1]:
|
|
||||||
if def.kind == nnkProcDef:
|
|
||||||
procs.add(def)
|
|
||||||
if procs.len > 1:
|
|
||||||
let responseMsgId = nextId + procs.len - 1
|
|
||||||
let responseRecord = addMsgHandler(responseMsgId,
|
|
||||||
procs[^1],
|
|
||||||
msgKind = rlpxResponse)
|
|
||||||
for i in 0 .. procs.len - 2:
|
|
||||||
discard addMsgHandler(nextId + i, procs[i],
|
|
||||||
msgKind = rlpxRequest,
|
|
||||||
responseMsgId = responseMsgId,
|
|
||||||
responseRecord = responseRecord)
|
|
||||||
|
|
||||||
inc nextId, procs.len
|
|
||||||
|
|
||||||
# we got all the way to here, so everything is fine.
|
|
||||||
# break the block so it doesn't reach the error call below
|
|
||||||
break processReqResp
|
|
||||||
macros.error("requestResponse expects a block with at least two proc definitions")
|
|
||||||
elif eqIdent(n[0], "onPeerConnected"):
|
|
||||||
handshake = liftEventHandler(n[1], "Handshake")
|
|
||||||
elif eqIdent(n[0], "onPeerDisconnected"):
|
|
||||||
disconnectHandler = liftEventHandler(n[1], "PeerDisconnect")
|
|
||||||
else:
|
|
||||||
macros.error(repr(n) & " is not a recognized call in P2P protocol definitions", n)
|
|
||||||
of nnkProcDef:
|
|
||||||
discard addMsgHandler(nextId, n)
|
|
||||||
inc nextId
|
|
||||||
|
|
||||||
of nnkCommentStmt:
|
|
||||||
discard
|
|
||||||
|
|
||||||
else:
|
|
||||||
macros.error("illegal syntax in a P2P protocol definition", n)
|
|
||||||
|
|
||||||
let peerInit = if peerState == nil: newNilLit()
|
|
||||||
else: newTree(nnkBracketExpr, createPeerState, peerState)
|
|
||||||
|
|
||||||
let netInit = if networkState == nil: newNilLit()
|
|
||||||
else: newTree(nnkBracketExpr, createNetworkState, networkState)
|
|
||||||
|
|
||||||
result = newNimNode(nnkStmtList)
|
|
||||||
result.add outTypes
|
|
||||||
result.add quote do:
|
|
||||||
# One global variable per protocol holds the protocol run-time data
|
|
||||||
var p = `initProtocol`(`shortName`, `version`, `peerInit`, `netInit`)
|
|
||||||
var `protocol` = addr p
|
|
||||||
|
|
||||||
# The protocol run-time data is available as a pseudo-field
|
|
||||||
# (e.g. `p2p.protocolInfo`)
|
|
||||||
template protocolInfo*(P: type `protoNameIdent`): ProtocolInfo = `protocol`
|
|
||||||
|
|
||||||
result.add outSendProcs, outRecvProcs, outProcRegistrations
|
|
||||||
result.add quote do:
|
|
||||||
setEventHandlers(`protocol`, `handshake`, `disconnectHandler`)
|
|
||||||
|
|
||||||
result.add newCall(bindSym("registerProtocol"), protocol)
|
|
||||||
|
|
||||||
when defined(debugRlpxProtocol) or defined(debugMacros):
|
|
||||||
echo repr(result)
|
|
||||||
|
|
||||||
macro p2pProtocol*(protocolOptions: untyped, body: untyped): untyped =
|
|
||||||
let protoName = $(protocolOptions[0])
|
|
||||||
result = protocolOptions
|
|
||||||
result[0] = bindSym"p2pProtocolImpl"
|
|
||||||
result.add(newTree(nnkExprEqExpr,
|
|
||||||
ident("name"),
|
|
||||||
newLit(protoName)))
|
|
||||||
result.add(newTree(nnkExprEqExpr,
|
|
||||||
ident("body"),
|
|
||||||
body))
|
|
||||||
|
|
||||||
p2pProtocol devp2p(version = 0, shortName = "p2p"):
|
p2pProtocol devp2p(version = 0, shortName = "p2p"):
|
||||||
proc hello(peer: Peer,
|
proc hello(peer: Peer,
|
||||||
|
@ -1162,7 +964,7 @@ proc callDisconnectHandlers(peer: Peer, reason: DisconnectionReason): Future[voi
|
||||||
|
|
||||||
return all(futures)
|
return all(futures)
|
||||||
|
|
||||||
proc handshakeImpl[T](peer: Peer,
|
proc handshakeImpl*[T](peer: Peer,
|
||||||
sendFut: Future[void],
|
sendFut: Future[void],
|
||||||
responseFut: Future[T],
|
responseFut: Future[T],
|
||||||
timeout: Duration): Future[T] {.async.} =
|
timeout: Duration): Future[T] {.async.} =
|
||||||
|
@ -1180,21 +982,6 @@ proc handshakeImpl[T](peer: Peer,
|
||||||
else:
|
else:
|
||||||
return responseFut.read
|
return responseFut.read
|
||||||
|
|
||||||
proc handshakeImpl(peer: Peer,
|
|
||||||
sendFut: Future[void],
|
|
||||||
timeout: Duration,
|
|
||||||
HandshakeType: type): Future[HandshakeType] =
|
|
||||||
handshakeImpl(peer, sendFut, nextMsg(peer, HandshakeType), timeout)
|
|
||||||
|
|
||||||
macro handshake*(peer: Peer, timeout: untyped, sendCall: untyped): untyped =
|
|
||||||
let
|
|
||||||
msgName = $sendCall[0]
|
|
||||||
msgType = newDotExpr(ident"CurrentProtocol", ident(msgName))
|
|
||||||
|
|
||||||
sendCall.insert(1, peer)
|
|
||||||
|
|
||||||
result = newCall(bindSym"handshakeImpl", peer, sendCall, timeout, msgType)
|
|
||||||
|
|
||||||
proc disconnect*(peer: Peer, reason: DisconnectionReason, notifyOtherPeer = false) {.async.} =
|
proc disconnect*(peer: Peer, reason: DisconnectionReason, notifyOtherPeer = false) {.async.} =
|
||||||
if peer.connectionState notin {Disconnecting, Disconnected}:
|
if peer.connectionState notin {Disconnecting, Disconnected}:
|
||||||
peer.connectionState = Disconnecting
|
peer.connectionState = Disconnecting
|
||||||
|
|
|
@ -46,12 +46,12 @@ p2pProtocol eth(version = protocolVersion,
|
||||||
chain = network.chain
|
chain = network.chain
|
||||||
bestBlock = chain.getBestBlockHeader
|
bestBlock = chain.getBestBlockHeader
|
||||||
|
|
||||||
let m = await peer.handshake(timeout = chronos.seconds(10),
|
let m = await peer.status(protocolVersion,
|
||||||
status(protocolVersion,
|
|
||||||
network.networkId,
|
network.networkId,
|
||||||
bestBlock.difficulty,
|
bestBlock.difficulty,
|
||||||
bestBlock.blockHash,
|
bestBlock.blockHash,
|
||||||
chain.genesisHash))
|
chain.genesisHash,
|
||||||
|
timeout = chronos.seconds(10))
|
||||||
|
|
||||||
if m.networkId == network.networkId and m.genesisHash == chain.genesisHash:
|
if m.networkId == network.networkId and m.genesisHash == chain.genesisHash:
|
||||||
trace "suitable peer", peer
|
trace "suitable peer", peer
|
||||||
|
@ -61,6 +61,7 @@ p2pProtocol eth(version = protocolVersion,
|
||||||
peer.state.bestDifficulty = m.totalDifficulty
|
peer.state.bestDifficulty = m.totalDifficulty
|
||||||
peer.state.bestBlockHash = m.bestHash
|
peer.state.bestBlockHash = m.bestHash
|
||||||
|
|
||||||
|
handshake:
|
||||||
proc status(peer: Peer,
|
proc status(peer: Peer,
|
||||||
protocolVersion: uint,
|
protocolVersion: uint,
|
||||||
networkId: uint,
|
networkId: uint,
|
||||||
|
|
|
@ -165,10 +165,7 @@ p2pProtocol les(version = lesVersion,
|
||||||
outgoingRequestDecorator = outgoingRequestDecorator,
|
outgoingRequestDecorator = outgoingRequestDecorator,
|
||||||
incomingRequestDecorator = incomingRequestDecorator,
|
incomingRequestDecorator = incomingRequestDecorator,
|
||||||
incomingResponseThunkDecorator = incomingResponseDecorator):
|
incomingResponseThunkDecorator = incomingResponseDecorator):
|
||||||
|
handshake:
|
||||||
## Handshake
|
|
||||||
##
|
|
||||||
|
|
||||||
proc status(p: Peer, values: openarray[KeyValuePair])
|
proc status(p: Peer, values: openarray[KeyValuePair])
|
||||||
|
|
||||||
onPeerConnected do (peer: Peer):
|
onPeerConnected do (peer: Peer):
|
||||||
|
@ -208,7 +205,7 @@ p2pProtocol les(version = lesVersion,
|
||||||
lesProperties.add(keyAnnounceType => lesNetwork.ourAnnounceType)
|
lesProperties.add(keyAnnounceType => lesNetwork.ourAnnounceType)
|
||||||
|
|
||||||
let
|
let
|
||||||
s = await peer.handshake(timeout = chronos.seconds(10), status(lesProperties))
|
s = await peer.status(lesProperties, timeout = chronos.seconds(10))
|
||||||
peerNetworkId = s.values.getRequiredValue(keyNetworkId, uint)
|
peerNetworkId = s.values.getRequiredValue(keyNetworkId, uint)
|
||||||
peerGenesisHash = s.values.getRequiredValue(keyGenesisHash, KeccakHash)
|
peerGenesisHash = s.values.getRequiredValue(keyGenesisHash, KeccakHash)
|
||||||
peerLesVersion = s.values.getRequiredValue(keyProtocolVersion, uint)
|
peerLesVersion = s.values.getRequiredValue(keyProtocolVersion, uint)
|
||||||
|
|
|
@ -733,11 +733,11 @@ p2pProtocol Whisper(version = whisperVersion,
|
||||||
whisperNet = peer.networkState
|
whisperNet = peer.networkState
|
||||||
whisperPeer = peer.state
|
whisperPeer = peer.state
|
||||||
|
|
||||||
let m = await handshake(peer, timeout = chronos.milliseconds(500),
|
let m = await peer.status(whisperVersion,
|
||||||
status(whisperVersion,
|
|
||||||
cast[uint](whisperNet.config.powRequirement),
|
cast[uint](whisperNet.config.powRequirement),
|
||||||
@(whisperNet.config.bloom),
|
@(whisperNet.config.bloom),
|
||||||
whisperNet.config.isLightNode))
|
whisperNet.config.isLightNode,
|
||||||
|
timeout = chronos.milliseconds(500))
|
||||||
|
|
||||||
if m.protocolVersion == whisperVersion:
|
if m.protocolVersion == whisperVersion:
|
||||||
debug "Whisper peer", peer, whisperVersion
|
debug "Whisper peer", peer, whisperVersion
|
||||||
|
@ -769,12 +769,12 @@ p2pProtocol Whisper(version = whisperVersion,
|
||||||
|
|
||||||
debug "Whisper peer initialized", peer
|
debug "Whisper peer initialized", peer
|
||||||
|
|
||||||
|
handshake:
|
||||||
proc status(peer: Peer,
|
proc status(peer: Peer,
|
||||||
protocolVersion: uint,
|
protocolVersion: uint,
|
||||||
powConverted: uint,
|
powConverted: uint,
|
||||||
bloom: Bytes,
|
bloom: Bytes,
|
||||||
isLightNode: bool) =
|
isLightNode: bool)
|
||||||
discard
|
|
||||||
|
|
||||||
proc messages(peer: Peer, envelopes: openarray[Envelope]) =
|
proc messages(peer: Peer, envelopes: openarray[Envelope]) =
|
||||||
if not peer.state.initialized:
|
if not peer.state.initialized:
|
||||||
|
@ -1051,7 +1051,6 @@ proc setLightNode*(node: EthereumNode, isLightNode: bool) =
|
||||||
## setting is only communicated at peer handshake
|
## setting is only communicated at peer handshake
|
||||||
node.protocolState(Whisper).config.isLightNode = isLightNode
|
node.protocolState(Whisper).config.isLightNode = isLightNode
|
||||||
|
|
||||||
|
|
||||||
proc configureWhisper*(node: EthereumNode, config: WhisperConfig) =
|
proc configureWhisper*(node: EthereumNode, config: WhisperConfig) =
|
||||||
## Apply a Whisper configuration
|
## Apply a Whisper configuration
|
||||||
## NOTE: Should be run before connection is made with peers as some
|
## NOTE: Should be run before connection is made with peers as some
|
||||||
|
|
|
@ -71,7 +71,7 @@ p2pProtocol xyz(version = 1,
|
||||||
proc bar(p: Peer, i: int, s: string)
|
proc bar(p: Peer, i: int, s: string)
|
||||||
|
|
||||||
requestResponse:
|
requestResponse:
|
||||||
proc xyzReq(p: Peer, n: int, timeout = 3000) =
|
proc xyzReq(p: Peer, n: int, timeout = 3.seconds) =
|
||||||
echo "got req ", n
|
echo "got req ", n
|
||||||
|
|
||||||
proc xyzRes(p: Peer, data: string) =
|
proc xyzRes(p: Peer, data: string) =
|
||||||
|
|
Loading…
Reference in New Issue