Unify the bootstrap nodes handling code

We no longer discriminate between ENR, MultiAddress or ENode
bootstrap records (all of them are remapped to ENodes).

The discovery loop will stochastically try to reconnect to
accidentally disconnected nodes.
This commit is contained in:
Zahary Karadjov 2020-02-05 21:40:14 +01:00 committed by zah
parent 4fb654f2af
commit ee7c2c9dff
9 changed files with 271 additions and 184 deletions

View File

@ -6,12 +6,13 @@ import
stew/[objects, bitseqs, byteutils], stew/[objects, bitseqs, byteutils],
chronos, chronicles, confutils, metrics, chronos, chronicles, confutils, metrics,
json_serialization/std/[options, sets], serialization/errors, json_serialization/std/[options, sets], serialization/errors,
kvstore, kvstore_lmdb, eth/async_utils, eth/p2p/discoveryv5/enr, kvstore, kvstore_lmdb,
eth/p2p/enode, eth/[keys, async_utils], eth/p2p/discoveryv5/enr,
# Local modules # Local modules
spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network], spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network],
conf, time, state_transition, beacon_chain_db, conf, time, state_transition, beacon_chain_db, validator_pool, extras,
validator_pool, extras, attestation_pool, block_pool, eth2_network, attestation_pool, block_pool, eth2_network, eth2_discovery,
beacon_node_types, mainchain_monitor, version, ssz, ssz/dynamic_navigator, beacon_node_types, mainchain_monitor, version, ssz, ssz/dynamic_navigator,
sync_protocol, request_manager, validator_keygen, interop, statusbar sync_protocol, request_manager, validator_keygen, interop, statusbar
@ -45,10 +46,9 @@ type
nickname: string nickname: string
network: Eth2Node network: Eth2Node
forkVersion: array[4, byte] forkVersion: array[4, byte]
networkIdentity: Eth2NodeIdentity netKeys: DiscKeyPair
requestManager: RequestManager requestManager: RequestManager
bootstrapNodes: seq[BootstrapAddr] bootstrapNodes: seq[ENode]
bootstrapEnrs: seq[enr.Record]
db: BeaconChainDB db: BeaconChainDB
config: BeaconNodeConf config: BeaconNodeConf
attachedValidators: ValidatorPool attachedValidators: ValidatorPool
@ -121,55 +121,10 @@ proc getStateFromSnapshot(conf: BeaconNodeConf, state: var BeaconState): bool =
result = true result = true
proc addBootstrapAddr(v: var seq[BootstrapAddr], add: TaintedString) =
try:
v.add BootstrapAddr.initAddress(string add)
except CatchableError as e:
warn "Skipping invalid address", err = e.msg
proc loadBootstrapFile(bootstrapFile: string): seq[BootstrapAddr] =
if fileExists(bootstrapFile):
for line in lines(bootstrapFile):
result.addBootstrapAddr(line)
proc addEnrBootstrapNode(enrBase64: string,
bootNodes: var seq[BootstrapAddr],
enrs: var seq[enr.Record]) =
var enrRec: enr.Record
if enrRec.fromURI(enrBase64):
try:
let
ip = IpAddress(family: IpAddressFamily.IPv4,
address_v4: cast[array[4, uint8]](enrRec.get("ip", int)))
tcpPort = Port enrRec.get("tcp", int)
# udpPort = Port enrRec.get("udp", int)
bootNodes.add BootstrapAddr.initAddress(ip, tcpPort)
enrs.add enrRec
except CatchableError as err:
warn "Invalid ENR record", enrRec
else:
warn "Failed to parse ENR record", value = enrRec
proc useEnrBootstrapFile(bootstrapFile: string,
bootNodes: var seq[BootstrapAddr],
enrs: var seq[enr.Record]) =
let ext = splitFile(bootstrapFile).ext
if cmpIgnoreCase(ext, ".txt") == 0:
for ln in lines(bootstrapFile):
addEnrBootstrapNode(string ln, bootNodes, enrs)
elif cmpIgnoreCase(ext, ".yaml") == 0:
# TODO. This is very ugly, but let's try to negotiate the
# removal of YAML metadata.
for ln in lines(bootstrapFile):
addEnrBootstrapNode(string(ln[3..^2]), bootNodes, enrs)
else:
error "Unknown bootstrap file format", ext
quit 1
proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async.} = proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async.} =
let let
networkId = getPersistentNetIdentity(conf) netKeys = getPersistentNetKeys(conf)
nickname = if conf.nodeName == "auto": shortForm(networkId) nickname = if conf.nodeName == "auto": shortForm(netKeys)
else: conf.nodeName else: conf.nodeName
db = BeaconChainDB.init(kvStore LmdbStoreRef.init(conf.databaseDir)) db = BeaconChainDB.init(kvStore LmdbStoreRef.init(conf.databaseDir))
@ -222,34 +177,24 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
# monitor # monitor
mainchainMonitor.start() mainchainMonitor.start()
var var bootNodes: seq[ENode]
bootNodes: seq[BootstrapAddr] for node in conf.bootstrapNodes: bootNodes.addBootstrapNode(node)
enrs: seq[enr.Record] bootNodes.loadBootstrapFile(string conf.bootstrapNodesFile)
bootNodes.loadBootstrapFile(conf.dataDir / "bootstrap_nodes.txt")
for node in conf.bootstrapNodes: bootNodes.addBootstrapAddr(node) bootNodes = filterIt(bootNodes, it.pubkey != netKeys.pubkey)
bootNodes.add(loadBootstrapFile(string conf.bootstrapNodesFile))
bootNodes.add(loadBootstrapFile(conf.dataDir / "bootstrap_nodes.txt"))
let enrBootstrapFile = string conf.enrBootstrapNodesFile
if enrBootstrapFile.len > 0:
useEnrBootstrapFile(enrBootstrapFile, bootNodes, enrs)
bootNodes = filterIt(bootNodes, not it.isSameNode(networkId))
let let
network = await createEth2Node(conf, bootNodes, enrs) network = await createEth2Node(conf, bootNodes)
addressFile = string(conf.dataDir) / "beacon_node.address"
let addressFile = string(conf.dataDir) / "beacon_node.address"
network.saveConnectionAddressFile(addressFile) network.saveConnectionAddressFile(addressFile)
var res = BeaconNode( var res = BeaconNode(
nickname: nickname, nickname: nickname,
network: network, network: network,
forkVersion: blockPool.headState.data.data.fork.current_version, forkVersion: blockPool.headState.data.data.fork.current_version,
networkIdentity: networkId, netKeys: netKeys,
requestManager: RequestManager.init(network), requestManager: RequestManager.init(network),
bootstrapNodes: bootNodes, bootstrapNodes: bootNodes,
bootstrapEnrs: enrs,
db: db, db: db,
config: conf, config: conf,
attachedValidators: ValidatorPool.init(), attachedValidators: ValidatorPool.init(),
@ -1027,7 +972,7 @@ when hasPrompt:
# arbitrary expression that is resolvable through this API. # arbitrary expression that is resolvable through this API.
case expr.toLowerAscii case expr.toLowerAscii
of "connected_peers": of "connected_peers":
$(sync_protocol.libp2p_peers.value.int) $(libp2p_peers.value.int)
of "last_finalized_epoch": of "last_finalized_epoch":
var head = node.blockPool.finalizedHead var head = node.blockPool.finalizedHead

View File

@ -712,9 +712,10 @@ proc updateStateData*(pool: BlockPool, state: var StateData, bs: BlockSlot) =
# applied # applied
for i in countdown(ancestors.len - 1, 0): for i in countdown(ancestors.len - 1, 0):
let ok = let ok =
skipAndUpdateState(state.data, ancestors[i].data.message, {skipValidation}) do( skipAndUpdateState(state.data,
state: HashedBeaconState): ancestors[i].data.message,
pool.maybePutState(state, ancestors[i].refs) {skipValidation}) do (state: HashedBeaconState):
pool.maybePutState(state, ancestors[i].refs)
doAssert ok, "Blocks in database should never fail to apply.." doAssert ok, "Blocks in database should never fail to apply.."
skipAndUpdateState(state.data, bs.slot) do(state: HashedBeaconState): skipAndUpdateState(state.data, bs.slot) do(state: HashedBeaconState):

View File

@ -90,11 +90,6 @@ type
desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses." desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses."
name: "bootstrap-file" }: InputFile name: "bootstrap-file" }: InputFile
enrBootstrapNodesFile* {.
defaultValue: ""
desc: "Specifies a line-delimited file of bootstrap ENR records"
name: "enr-bootstrap-file" }: InputFile
tcpPort* {. tcpPort* {.
defaultValue: defaultPort(config) defaultValue: defaultPort(config)
desc: "TCP listening port." desc: "TCP listening port."

View File

@ -1,7 +1,9 @@
import import
net, os, net, strutils, strformat, parseutils,
eth/keys, eth/trie/db, chronicles, stew/result, eth/keys, eth/trie/db, eth/p2p/enode,
eth/p2p/discoveryv5/[protocol, node, discovery_db, types], eth/p2p/discoveryv5/[enr, protocol, node, discovery_db, types],
libp2p/[multiaddress, multicodec, peer],
libp2p/crypto/crypto as libp2pCrypto,
conf conf
type type
@ -9,7 +11,7 @@ type
Eth2DiscoveryId* = NodeId Eth2DiscoveryId* = NodeId
export export
Eth2DiscoveryProtocol, open, start, close Eth2DiscoveryProtocol, open, start, close, result
proc new*(T: type Eth2DiscoveryProtocol, proc new*(T: type Eth2DiscoveryProtocol,
conf: BeaconNodeConf, conf: BeaconNodeConf,
@ -24,3 +26,119 @@ proc new*(T: type Eth2DiscoveryProtocol,
newProtocol(pk, db, Port conf.udpPort) newProtocol(pk, db, Port conf.udpPort)
proc toENode*(a: MultiAddress): Result[ENode, cstring] =
if not IPFS.match(a):
return err "Unsupported MultiAddress"
try:
# TODO. This code is quite messy with so much string handling.
# MultiAddress can offer a more type-safe API?
var
peerId = PeerID.init(a[2].protoAddress())
addressFragments = split($a[0], "/")
portFragments = split($a[1], "/")
tcpPort: int
if addressFragments.len != 3 or
addressFragments[1] != "ip4" or
portFragments.len != 3 or
portFragments[1] notin ["tcp", "udp"] or
parseInt(portFragments[2], tcpPort) == 0:
return err "Only IPv4 MultiAddresses are supported"
let
ipAddress = parseIpAddress(addressFragments[2])
# TODO. The multiaddress will have either a TCP or a UDP value, but
# is it reasonable to assume that a client will use the same ports?
# Probably not, but how can we bootstrap then?
udpPort = tcpPort
var pubkey: libp2pCrypto.PublicKey
if peerId.extractPublicKey(pubkey):
if pubkey.scheme == Secp256k1:
return ok ENode(pubkey: pubkey.skkey,
address: Address(ip: ipAddress,
tcpPort: Port tcpPort,
udpPort: Port udpPort))
except CatchableError:
# This will reach the error exit path below
discard
return err "Invalid MultiAddress"
proc toMultiAddressStr*(enode: ENode): string =
var peerId = PeerID.init(libp2pCrypto.PublicKey(scheme: Secp256k1,
skkey: enode.pubkey))
&"/ip4/{enode.address.ip}/tcp/{enode.address.tcpPort}/p2p/{peerId.pretty}"
proc parseBootstrapAddress*(address: TaintedString): Result[ENode, cstring] =
if address.len == 0:
return err "an empty string is not a valid bootstrap node"
logScope:
address = string(address)
if address[0] == '/':
try:
let ma = MultiAddress.init(address)
return toENode(ma)
except CatchableError:
return err "Invalid bootstrap multiaddress"
else:
let lowerCaseAddress = toLowerAscii(string address)
if lowerCaseAddress.startsWith("enr:"):
var enrRec: enr.Record
if enrRec.fromURI(string address):
try:
let
ip = IpAddress(family: IpAddressFamily.IPv4,
address_v4: cast[array[4, uint8]](enrRec.get("ip", int)))
tcpPort = Port enrRec.get("tcp", int)
udpPort = Port enrRec.get("udp", int)
var pubKey: keys.PublicKey
if not enrRec.get(pubKey):
return err "Failed to read public key from ENR record"
return ok ENode(pubkey: pubkey,
address: Address(ip: ip,
tcpPort: tcpPort,
udpPort: udpPort))
except CatchableError:
# This will continue to the failure path below
discard
return err "Invalid ENR bootstrap record"
elif lowerCaseAddress.startsWith("enode:"):
try:
return ok initEnode(string address)
except CatchableError as err:
return err "Ignoring invalid enode bootstrap address"
else:
return err "Ignoring unrecognized bootstrap address type"
proc addBootstrapNode*(bootNodes: var seq[ENode],
bootstrapAddr: string) =
let enodeRes = parseBootstrapAddress(bootstrapAddr)
if enodeRes.isOk:
bootNodes.add enodeRes.value
else:
warn "Ignoring invalid bootstrap address",
bootstrapAddr, reason = enodeRes.error
proc loadBootstrapFile*(bootNodes: var seq[ENode],
bootstrapFile: string) =
if bootstrapFile.len == 0: return
let ext = splitFile(bootstrapFile).ext
if cmpIgnoreCase(ext, ".txt") == 0:
for ln in lines(bootstrapFile):
bootNodes.addBootstrapNode(ln)
elif cmpIgnoreCase(ext, ".yaml") == 0:
# TODO. This is very ugly, but let's try to negotiate the
# removal of YAML metadata.
for ln in lines(bootstrapFile):
bootNodes.addBootstrapNode(string(ln[3..^2]))
else:
error "Unknown bootstrap file format", ext
quit 1

View File

@ -1,8 +1,12 @@
import import
options, tables, options, tables, strutils, sequtils,
chronos, json_serialization, strutils, chronicles, metrics, chronos, json_serialization, chronicles, metrics, libp2p/crypto/secp,
eth/net/nat, eth/p2p/discoveryv5/enr, eth/keys, eth/p2p/enode, eth/net/nat, eth/p2p/discoveryv5/enr,
version, conf eth2_discovery, version, conf
type
DiscKeyPair* = keys.KeyPair
DiscPrivKey* = keys.PrivateKey
const const
clientId* = "Nimbus beacon node v" & fullVersionStr clientId* = "Nimbus beacon node v" & fullVersionStr
@ -61,7 +65,7 @@ when networkBackend in [libp2p, libp2pDaemon]:
import import
os, random, os, random,
stew/io, eth/async_utils, stew/io, eth/async_utils,
libp2p/crypto/crypto, libp2p/[multiaddress, multicodec], libp2p/crypto/crypto as libp2pCrypto, libp2p/[multiaddress, multicodec],
ssz ssz
export export
@ -94,11 +98,14 @@ when networkBackend in [libp2p, libp2pDaemon]:
netBackendName* = "libp2p" netBackendName* = "libp2p"
networkKeyFilename = "privkey.protobuf" networkKeyFilename = "privkey.protobuf"
type func asLibp2pKey*(key: DiscPrivKey): libp2pCrypto.PrivateKey =
BootstrapAddr* = MultiAddress libp2pCrypto.PrivateKey(scheme: Secp256k1,
Eth2NodeIdentity* = KeyPair skkey: SkPrivateKey(data: key.data))
proc initAddress*(T: type BootstrapAddr, str: string): T = func asLibp2pKey*(key: keys.PublicKey): libp2pCrypto.PublicKey =
libp2pCrypto.PublicKey(scheme: Secp256k1, skkey: key)
proc initAddress*(T: type MultiAddress, str: string): T =
let address = MultiAddress.init(str) let address = MultiAddress.init(str)
if IPFS.match(address) and matchPartial(multiaddress.TCP, address): if IPFS.match(address) and matchPartial(multiaddress.TCP, address):
result = address result = address
@ -109,36 +116,32 @@ when networkBackend in [libp2p, libp2pDaemon]:
template tcpEndPoint(address, port): auto = template tcpEndPoint(address, port): auto =
MultiAddress.init(address, Protocol.IPPROTO_TCP, port) MultiAddress.init(address, Protocol.IPPROTO_TCP, port)
proc initAddress*(T: type BootstrapAddr, ip: IpAddress, tcpPort: Port): T = proc genRandomNetKey: DiscPrivKey =
tcpEndPoint(ip, tcpPort) let skkey = SkPrivateKey.random
DiscPrivKey(data: skkey.data)
proc ensureNetworkIdFile(conf: BeaconNodeConf): string = proc ensureNetworkIdFile(conf: BeaconNodeConf): string =
result = conf.dataDir / networkKeyFilename result = conf.dataDir / networkKeyFilename
if not fileExists(result): if not fileExists(result):
createDir conf.dataDir.string createDir conf.dataDir.string
let pk = PrivateKey.random(Secp256k1) let pk = genRandomNetKey()
writeFile(result, pk.getBytes) writeFile(result, pk.data)
proc getPersistentNetIdentity*(conf: BeaconNodeConf): Eth2NodeIdentity = proc getPersistentNetKeys*(conf: BeaconNodeConf): DiscKeyPair =
let privateKeyFile = conf.dataDir / networkKeyFilename let privKeyPath = conf.dataDir / networkKeyFilename
var privKey: PrivateKey var privKey: DiscPrivKey
if not fileExists(privateKeyFile): if not fileExists(privKeyPath):
createDir conf.dataDir.string createDir conf.dataDir.string
privKey = PrivateKey.random(Secp256k1) privKey = genRandomNetKey()
writeFile(privateKeyFile, privKey.getBytes()) writeFile(privKeyPath, privKey.data)
else: else:
let strdata = readFile(privateKeyFile) let strdata = readFile(privKeyPath)
privKey = PrivateKey.init(cast[seq[byte]](strdata)) privKey = initPrivateKey(cast[seq[byte]](strdata))
result = KeyPair(seckey: privKey, pubkey: privKey.getKey()) DiscKeyPair(seckey: privKey, pubkey: privKey.getPublicKey())
proc allMultiAddresses(nodes: seq[BootstrapAddr]): seq[string] =
for node in nodes:
result.add $node
proc createEth2Node*(conf: BeaconNodeConf, proc createEth2Node*(conf: BeaconNodeConf,
bootstrapNodes: seq[BootstrapAddr], bootstrapNodes: seq[ENode]): Future[Eth2Node] {.async.} =
bootstrapEnrs: seq[enr.Record]): Future[Eth2Node] {.async.} =
var var
(extIp, extTcpPort, _) = setupNat(conf) (extIp, extTcpPort, _) = setupNat(conf)
hostAddress = tcpEndPoint(globalListeningAddr, Port conf.tcpPort) hostAddress = tcpEndPoint(globalListeningAddr, Port conf.tcpPort)
@ -150,16 +153,13 @@ when networkBackend in [libp2p, libp2pDaemon]:
bootstrapNodes bootstrapNodes
when networkBackend == libp2p: when networkBackend == libp2p:
let keys = conf.getPersistentNetIdentity let keys = conf.getPersistentNetKeys
# TODO nim-libp2p still doesn't have support for announcing addresses # TODO nim-libp2p still doesn't have support for announcing addresses
# that are different from the host address (this is relevant when we # that are different from the host address (this is relevant when we
# are running behind a NAT). # are running behind a NAT).
var switch = newStandardSwitch(some keys.seckey, hostAddress, var switch = newStandardSwitch(some keys.seckey.asLibp2pKey, hostAddress,
triggerSelf = true, gossip = false) triggerSelf = true, gossip = false)
result = Eth2Node.init(conf, switch, keys.seckey) result = Eth2Node.init(conf, switch, keys.seckey)
for enr in bootstrapEnrs:
result.addKnownPeer(enr)
await result.start()
else: else:
let keyFile = conf.ensureNetworkIdFile let keyFile = conf.ensureNetworkIdFile
@ -173,7 +173,7 @@ when networkBackend in [libp2p, libp2pDaemon]:
id = keyFile, id = keyFile,
hostAddresses = @[hostAddress], hostAddresses = @[hostAddress],
announcedAddresses = announcedAddresses, announcedAddresses = announcedAddresses,
bootstrapNodes = allMultiAddresses(bootstrapNodes), bootstrapNodes = mapIt(bootstrapNodes, it.toMultiAddressStr),
peersRequired = 1) peersRequired = 1)
mainDaemon = await daemonFut mainDaemon = await daemonFut
@ -185,52 +185,53 @@ when networkBackend in [libp2p, libp2pDaemon]:
result = await Eth2Node.init(mainDaemon) result = await Eth2Node.init(mainDaemon)
proc getPersistenBootstrapAddr*(conf: BeaconNodeConf, proc getPersistenBootstrapAddr*(conf: BeaconNodeConf,
ip: IpAddress, port: Port): BootstrapAddr = ip: IpAddress, port: Port): ENode =
let pair = getPersistentNetIdentity(conf) let pair = getPersistentNetKeys(conf)
let pidma = MultiAddress.init(multiCodec("p2p"), PeerID.init(pair.pubkey)) initENode(pair.pubkey, Address(ip: ip, udpPort: port))
result = tcpEndPoint(ip, port) & pidma
proc isSameNode*(bootstrapNode: BootstrapAddr, id: Eth2NodeIdentity): bool = proc shortForm*(id: DiscKeyPair): string =
if IPFS.match(bootstrapNode): $PeerID.init(id.pubkey.asLibp2pKey)
let pid1 = PeerID.init(bootstrapNode[2].protoAddress())
let pid2 = PeerID.init(id.pubkey)
result = (pid1 == pid2)
proc shortForm*(id: Eth2NodeIdentity): string = proc toPeerInfo(enode: ENode): PeerInfo =
$PeerID.init(id.pubkey) let
peerId = PeerID.init enode.pubkey.asLibp2pKey
proc multiAddressToPeerInfo(a: MultiAddress): PeerInfo = addresses = @[MultiAddress.init enode.toMultiAddressStr]
if IPFS.match(a): when networkBackend == libp2p:
let return PeerInfo.init(peerId, addresses)
peerId = PeerID.init(a[2].protoAddress()) else:
addresses = @[a[0] & a[1]] return PeerInfo(peer: peerId, addresses: addresses)
when networkBackend == libp2p:
return PeerInfo.init(peerId, addresses)
else:
return PeerInfo(peer: peerId, addresses: addresses)
proc connectToNetwork*(node: Eth2Node, proc connectToNetwork*(node: Eth2Node,
bootstrapNodes: seq[MultiAddress]) {.async.} = bootstrapNodes: seq[ENode]) {.async.} =
# TODO: perhaps we should do these in parallel when networkBackend == libp2pDaemon:
var connected = false var connected = false
for bootstrapNode in bootstrapNodes: for bootstrapNode in bootstrapNodes:
try: try:
let peerInfo = multiAddressToPeerInfo(bootstrapNode) let peerInfo = toPeerInfo(bootstrapNode)
when networkBackend == libp2p: when networkBackend == libp2p:
discard await node.switch.dial(peerInfo) discard await node.switch.dial(peerInfo)
else: else:
await node.daemon.connect(peerInfo.peer, peerInfo.addresses) await node.daemon.connect(peerInfo.peer, peerInfo.addresses)
var peer = node.getPeer(peerInfo) var peer = node.getPeer(peerInfo)
peer.wasDialed = true peer.wasDialed = true
await initializeConnection(peer) await initializeConnection(peer)
connected = true connected = true
except CatchableError as err: except CatchableError as err:
error "Failed to connect to bootstrap node", error "Failed to connect to bootstrap node",
node = bootstrapNode, err = err.msg node = bootstrapNode, err = err.msg
if bootstrapNodes.len > 0 and connected == false: if bootstrapNodes.len > 0 and connected == false:
fatal "Failed to connect to any bootstrap node. Quitting." fatal "Failed to connect to any bootstrap node. Quitting."
quit 1 quit 1
elif networkBackend == libp2p:
for bootstrapNode in bootstrapNodes:
node.addKnownPeer bootstrapNode
await node.start()
await sleepAsync(10.seconds)
if libp2p_peers.get == 0:
fatal "Failed to connect to any bootstrap node. Quitting"
quit 1
proc saveConnectionAddressFile*(node: Eth2Node, filename: string) = proc saveConnectionAddressFile*(node: Eth2Node, filename: string) =
when networkBackend == libp2p: when networkBackend == libp2p:

View File

@ -3,7 +3,7 @@ import
stew/[varints,base58], stew/shims/[macros, tables], chronos, chronicles, stew/[varints,base58], stew/shims/[macros, tables], chronos, chronicles,
stint, faststreams/output_stream, serialization, stint, faststreams/output_stream, serialization,
json_serialization/std/options, eth/p2p/p2p_protocol_dsl, json_serialization/std/options, eth/p2p/p2p_protocol_dsl,
eth/p2p/discoveryv5/enr, eth/p2p/discoveryv5/node,
# TODO: create simpler to use libp2p modules that use re-exports # TODO: create simpler to use libp2p modules that use re-exports
libp2p/[switch, multistream, connection, libp2p/[switch, multistream, connection,
multiaddress, peerinfo, peer, multiaddress, peerinfo, peer,
@ -177,22 +177,33 @@ proc toPeerInfo(r: Option[enr.TypedRecord]): PeerInfo =
if r.isSome: if r.isSome:
return r.get.toPeerInfo return r.get.toPeerInfo
proc dialPeer*(node: Eth2Node, enr: enr.Record) {.async.} = proc dialPeer*(node: Eth2Node, peerInfo: PeerInfo) {.async.} =
let peerInfo = enr.toTypedRecord.toPeerInfo debug "Dialing peer", peer = $peerInfo
if peerInfo != nil: discard await node.switch.dial(peerInfo)
discard await node.switch.dial(peerInfo) var peer = node.getPeer(peerInfo)
var peer = node.getPeer(peerInfo) peer.wasDialed = true
peer.wasDialed = true await initializeConnection(peer)
await initializeConnection(peer)
proc runDiscoveryLoop*(node: Eth2Node) {.async.} =
debug "Starting discovery loop"
proc runDiscoveryLoop(node: Eth2Node) {.async.} =
while true: while true:
if node.peersByDiscoveryId.len < node.wantedPeers: let currentPeerCount = node.switch.connections.len
let discoveredPeers = await node.discovery.lookupRandom() libp2p_peers.set currentPeerCount.int64
for peer in discoveredPeers: if currentPeerCount < node.wantedPeers:
if peer.id notin node.peersByDiscoveryId: try:
# TODO do this in parallel let discoveredPeers = await node.discovery.lookupRandom()
await node.dialPeer(peer.record) for peer in discoveredPeers:
debug "Discovered peer", peer = $peer
try:
let peerInfo = peer.record.toTypedRecord.toPeerInfo
if peerInfo != nil and peerInfo.id notin node.switch.connections:
# TODO do this in parallel
await node.dialPeer(peerInfo)
except CatchableError as err:
debug "Failed to connect to peer", peer = $peer
except CatchableError as err:
debug "Failure in discovery", err = err.msg
await sleepAsync seconds(1) await sleepAsync seconds(1)
@ -213,13 +224,14 @@ proc init*(T: type Eth2Node, conf: BeaconNodeConf,
if msg.protocolMounter != nil: if msg.protocolMounter != nil:
msg.protocolMounter result msg.protocolMounter result
proc addKnownPeer*(node: Eth2Node, peerEnr: enr.Record) = proc addKnownPeer*(node: Eth2Node, peer: ENode) =
node.discovery.addNode peerEnr node.discovery.addNode peer
proc start*(node: Eth2Node) {.async.} = proc start*(node: Eth2Node) {.async.} =
node.discovery.open() node.discovery.open()
node.discovery.start() node.discovery.start()
node.libp2pTransportLoops = await node.switch.start() node.libp2pTransportLoops = await node.switch.start()
traceAsyncErrors node.runDiscoveryLoop()
proc init*(T: type Peer, network: Eth2Node, info: PeerInfo): Peer = proc init*(T: type Peer, network: Eth2Node, info: PeerInfo): Peer =
new result new result

View File

@ -1,3 +1,6 @@
import
metrics
type type
ResponseCode* = enum ResponseCode* = enum
Success Success
@ -22,6 +25,8 @@ const
logScope: logScope:
topics = "libp2p" topics = "libp2p"
declarePublicGauge libp2p_peers, "Number of libp2p peers"
template libp2pProtocol*(name: string, version: int) {.pragma.} template libp2pProtocol*(name: string, version: int) {.pragma.}
proc getRequestProtoName(fn: NimNode): NimNode = proc getRequestProtoName(fn: NimNode): NimNode =

View File

@ -1,14 +1,12 @@
import import
options, tables, sets, macros, options, tables, sets, macros,
chronicles, chronos, metrics, stew/ranges/bitranges, chronicles, chronos, stew/ranges/bitranges,
spec/[datatypes, crypto, digest, helpers], spec/[datatypes, crypto, digest, helpers],
beacon_node_types, eth2_network, block_pool, ssz beacon_node_types, eth2_network, block_pool, ssz
when networkBackend == libp2p: when networkBackend == libp2p:
import libp2p/switch import libp2p/switch
declarePublicGauge libp2p_peers, "Number of libp2p peers"
logScope: logScope:
topics = "sync" topics = "sync"
@ -100,9 +98,6 @@ p2pProtocol BeaconSync(version = 1,
else: else:
warn "Status response not received in time" warn "Status response not received in time"
onPeerDisconnected do (peer: Peer):
libp2p_peers.set peer.network.peers.len.int64
requestResponse: requestResponse:
proc status(peer: Peer, theirStatus: StatusMsg) {.libp2pProtocol("status", 1).} = proc status(peer: Peer, theirStatus: StatusMsg) {.libp2pProtocol("status", 1).} =
let let
@ -181,8 +176,6 @@ proc handleInitialStatus(peer: Peer,
# where it needs to sync and it should execute the sync algorithm with a certain # where it needs to sync and it should execute the sync algorithm with a certain
# number of randomly selected peers. The algorithm itself must be extracted in a proc. # number of randomly selected peers. The algorithm itself must be extracted in a proc.
try: try:
libp2p_peers.set peer.network.peers.len.int64
debug "Peer connected. Initiating sync", peer, debug "Peer connected. Initiating sync", peer,
localHeadSlot = ourStatus.headSlot, localHeadSlot = ourStatus.headSlot,
remoteHeadSlot = theirStatus.headSlot, remoteHeadSlot = theirStatus.headSlot,

View File

@ -0,0 +1,17 @@
import
net, unittest, testutil,
eth/p2p/enode, libp2p/multiaddress,
../beacon_chain/eth2_discovery
suite "Discovery v5 utilities":
timedTest "Multiaddress to ENode":
let addrStr = "/ip4/178.128.140.61/tcp/9000/p2p/16Uiu2HAmL5A5DAiiupFi6sUTF6Zq1TCKf6Pd5T8oFt9opQJqLqTQ"
let ma = MultiAddress.init addrStr
let enode = ma.toENode
check:
enode.isOk
enode.value.address.tcpPort == Port(9000)
$enode.value.address.ip == "178.128.140.61"
enode.value.toMultiAddressStr == addrStr