2018-03-28 00:17:01 +00:00
|
|
|
#
|
|
|
|
# Ethereum P2P
|
|
|
|
# (c) Copyright 2018
|
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
2018-04-30 17:40:04 +00:00
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
2018-03-28 00:17:01 +00:00
|
|
|
#
|
|
|
|
|
2018-07-12 11:14:22 +00:00
|
|
|
import
|
2018-10-15 14:36:43 +00:00
|
|
|
tables, algorithm, random,
|
|
|
|
asyncdispatch2, asyncdispatch2/timer, chronicles,
|
|
|
|
eth_keys, eth_common/eth_types,
|
|
|
|
eth_p2p/[kademlia, discovery, enode, peer_pool, rlpx],
|
|
|
|
eth_p2p/private/types
|
|
|
|
|
2018-07-12 11:14:22 +00:00
|
|
|
export
|
2018-11-10 00:18:00 +00:00
|
|
|
types, rlpx, enode, kademlia
|
2018-07-12 11:14:22 +00:00
|
|
|
|
2018-11-27 23:52:22 +00:00
|
|
|
proc addCapability*(node: var EthereumNode, p: ProtocolInfo) =
|
|
|
|
assert node.connectionState == ConnectionState.None
|
|
|
|
|
|
|
|
let pos = lowerBound(node.protocols, p, rlpx.cmp)
|
|
|
|
node.protocols.insert(p, pos)
|
|
|
|
node.capabilities.insert(p.asCapability, pos)
|
2018-07-12 11:14:22 +00:00
|
|
|
|
2018-11-27 23:52:22 +00:00
|
|
|
if p.networkStateInitializer != nil:
|
|
|
|
node.protocolStates[p.index] = p.networkStateInitializer(node)
|
|
|
|
|
|
|
|
template addCapability*(node: var EthereumNode, Protocol: type) =
|
|
|
|
addCapability(node, Protocol.protocolInfo)
|
2018-07-12 11:14:22 +00:00
|
|
|
|
|
|
|
proc newEthereumNode*(keys: KeyPair,
|
|
|
|
address: Address,
|
2018-07-22 21:32:45 +00:00
|
|
|
networkId: uint,
|
2018-07-12 11:14:22 +00:00
|
|
|
chain: AbstractChainDB,
|
2018-10-15 14:36:43 +00:00
|
|
|
clientId = "nim-eth-p2p/0.2.0", # TODO: read this value from nimble somehow
|
2018-11-02 13:45:57 +00:00
|
|
|
addAllCapabilities = true,
|
2018-12-28 16:44:37 +00:00
|
|
|
useCompression: bool = false,
|
|
|
|
minPeers = 10): EthereumNode =
|
2018-07-12 11:14:22 +00:00
|
|
|
new result
|
|
|
|
result.keys = keys
|
|
|
|
result.networkId = networkId
|
|
|
|
result.clientId = clientId
|
2018-11-27 23:52:22 +00:00
|
|
|
result.protocols.newSeq 0
|
|
|
|
result.capabilities.newSeq 0
|
2018-07-12 11:14:22 +00:00
|
|
|
result.address = address
|
|
|
|
result.connectionState = ConnectionState.None
|
|
|
|
|
2018-11-05 10:42:28 +00:00
|
|
|
when useSnappy:
|
2018-11-07 02:28:51 +00:00
|
|
|
result.protocolVersion = if useCompression: devp2pSnappyVersion
|
|
|
|
else: devp2pVersion
|
2018-11-02 13:45:57 +00:00
|
|
|
|
2018-11-27 23:52:22 +00:00
|
|
|
result.protocolStates.newSeq allProtocols.len
|
|
|
|
|
2018-12-28 16:44:37 +00:00
|
|
|
result.peerPool = newPeerPool(result, networkId,
|
|
|
|
keys, nil,
|
|
|
|
clientId, address.tcpPort,
|
|
|
|
minPeers = minPeers)
|
|
|
|
|
2018-07-12 11:14:22 +00:00
|
|
|
if addAllCapabilities:
|
2018-11-27 23:52:22 +00:00
|
|
|
for p in allProtocols:
|
2018-10-15 14:36:43 +00:00
|
|
|
result.addCapability(p)
|
2018-07-12 11:14:22 +00:00
|
|
|
|
|
|
|
proc processIncoming(server: StreamServer,
|
|
|
|
remote: StreamTransport): Future[void] {.async, gcsafe.} =
|
|
|
|
var node = getUserData[EthereumNode](server)
|
|
|
|
let peerfut = node.rlpxAccept(remote)
|
|
|
|
yield peerfut
|
|
|
|
if not peerfut.failed:
|
|
|
|
let peer = peerfut.read()
|
2018-11-12 12:54:15 +00:00
|
|
|
if node.peerPool != nil:
|
2018-11-13 09:54:56 +00:00
|
|
|
if not node.peerPool.addPeer(peer):
|
|
|
|
# In case an outgoing connection was added in the meanwhile or a
|
|
|
|
# malicious peer opens multiple connections
|
|
|
|
debug "Disconnecting peer (incoming)", reason = AlreadyConnected
|
2018-11-12 12:54:15 +00:00
|
|
|
await peer.disconnect(AlreadyConnected)
|
2018-07-12 11:14:22 +00:00
|
|
|
else:
|
|
|
|
remote.close()
|
|
|
|
|
2018-12-19 10:25:35 +00:00
|
|
|
proc listeningAddress*(node: EthereumNode): ENode =
|
|
|
|
return initENode(node.keys.pubKey, node.address)
|
|
|
|
|
2018-07-22 21:32:45 +00:00
|
|
|
proc startListening*(node: EthereumNode) =
|
2018-07-12 11:14:22 +00:00
|
|
|
let ta = initTAddress(node.address.ip, node.address.tcpPort)
|
|
|
|
if node.listeningServer == nil:
|
|
|
|
node.listeningServer = createStreamServer(ta, processIncoming,
|
|
|
|
{ReuseAddr},
|
2018-08-03 11:27:37 +00:00
|
|
|
udata = cast[pointer](node))
|
2018-07-12 11:14:22 +00:00
|
|
|
node.listeningServer.start()
|
2018-12-28 10:25:28 +00:00
|
|
|
info "RLPx listener up", self = node.listeningAddress
|
2018-07-12 11:14:22 +00:00
|
|
|
|
2018-07-22 21:32:45 +00:00
|
|
|
proc connectToNetwork*(node: EthereumNode,
|
|
|
|
bootstrapNodes: seq[ENode],
|
2018-09-25 13:17:24 +00:00
|
|
|
startListening = true,
|
2018-12-28 16:44:37 +00:00
|
|
|
enableDiscovery = true) {.async.} =
|
2018-07-12 11:14:22 +00:00
|
|
|
assert node.connectionState == ConnectionState.None
|
|
|
|
|
|
|
|
node.connectionState = Connecting
|
|
|
|
node.discovery = newDiscoveryProtocol(node.keys.seckey,
|
|
|
|
node.address,
|
|
|
|
bootstrapNodes)
|
2018-12-28 16:44:37 +00:00
|
|
|
node.peerPool.discovery = node.discovery
|
2018-07-12 11:14:22 +00:00
|
|
|
|
|
|
|
if startListening:
|
2018-07-22 21:32:45 +00:00
|
|
|
eth_p2p.startListening(node)
|
2018-07-12 11:14:22 +00:00
|
|
|
|
2018-09-25 13:17:24 +00:00
|
|
|
if enableDiscovery:
|
|
|
|
node.discovery.open()
|
|
|
|
await node.discovery.bootstrap()
|
|
|
|
else:
|
2018-12-14 12:54:43 +00:00
|
|
|
info "Discovery disabled"
|
2018-07-22 21:32:45 +00:00
|
|
|
|
|
|
|
node.peerPool.start()
|
|
|
|
|
2018-08-03 11:27:37 +00:00
|
|
|
while node.peerPool.connectedNodes.len == 0:
|
2018-12-14 12:54:43 +00:00
|
|
|
trace "Waiting for more peers", peers = node.peerPool.connectedNodes.len
|
2018-08-03 11:27:37 +00:00
|
|
|
await sleepAsync(500)
|
|
|
|
|
2018-07-22 21:32:45 +00:00
|
|
|
proc stopListening*(node: EthereumNode) =
|
|
|
|
node.listeningServer.stop()
|
|
|
|
|
2018-10-01 11:33:59 +00:00
|
|
|
iterator peers*(node: EthereumNode): Peer =
|
|
|
|
for peer in node.peerPool.peers:
|
|
|
|
yield peer
|
|
|
|
|
|
|
|
iterator peers*(node: EthereumNode, Protocol: type): Peer =
|
|
|
|
for peer in node.peerPool.peers(Protocol):
|
|
|
|
yield peer
|
|
|
|
|
2018-11-27 23:52:22 +00:00
|
|
|
iterator protocolPeers*(node: EthereumNode, Protocol: type): auto =
|
|
|
|
mixin state
|
|
|
|
for peer in node.peerPool.peers(Protocol):
|
|
|
|
yield peer.state(Protocol)
|
|
|
|
|
2018-07-23 21:39:41 +00:00
|
|
|
iterator randomPeers*(node: EthereumNode, maxPeers: int): Peer =
|
|
|
|
# TODO: this can be implemented more efficiently
|
|
|
|
|
|
|
|
# XXX: this doesn't compile, why?
|
|
|
|
# var peer = toSeq node.peers
|
|
|
|
var peers = newSeqOfCap[Peer](node.peerPool.connectedNodes.len)
|
|
|
|
for peer in node.peers: peers.add(peer)
|
|
|
|
|
|
|
|
shuffle(peers)
|
|
|
|
for i in 0 ..< min(maxPeers, peers.len):
|
|
|
|
yield peers[i]
|
|
|
|
|
|
|
|
proc randomPeer*(node: EthereumNode): Peer =
|
|
|
|
let peerIdx = random(node.peerPool.connectedNodes.len)
|
|
|
|
var i = 0
|
|
|
|
for peer in node.peers:
|
|
|
|
if i == peerIdx: return peer
|
|
|
|
inc i
|
2018-11-29 01:10:05 +00:00
|
|
|
|
|
|
|
proc randomPeerWith*(node: EthereumNode, Protocol: type): Peer =
|
|
|
|
mixin state
|
|
|
|
var candidates = newSeq[Peer]()
|
|
|
|
for p in node.peers(Protocol):
|
|
|
|
candidates.add(p)
|
|
|
|
if candidates.len > 0:
|
2019-01-25 08:58:37 +00:00
|
|
|
return candidates.rand()
|
2018-11-29 01:10:05 +00:00
|
|
|
|