From ee7c2c9dffe82931ac3e9525e0d8e812e8dec042 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 5 Feb 2020 21:40:14 +0100 Subject: [PATCH] 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. --- beacon_chain/beacon_node.nim | 89 +++----------- beacon_chain/block_pool.nim | 7 +- beacon_chain/conf.nim | 5 - beacon_chain/eth2_discovery.nim | 126 +++++++++++++++++++- beacon_chain/eth2_network.nim | 151 ++++++++++++------------ beacon_chain/libp2p_backend.nim | 46 +++++--- beacon_chain/libp2p_backends_common.nim | 5 + beacon_chain/sync_protocol.nim | 9 +- tests/test_discovery_helpers.nim | 17 +++ 9 files changed, 271 insertions(+), 184 deletions(-) create mode 100644 tests/test_discovery_helpers.nim diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index a3b4ef7c8..d4361b84a 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -6,12 +6,13 @@ import stew/[objects, bitseqs, byteutils], chronos, chronicles, confutils, metrics, 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 spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network], - conf, time, state_transition, beacon_chain_db, - validator_pool, extras, attestation_pool, block_pool, eth2_network, + conf, time, state_transition, beacon_chain_db, validator_pool, extras, + attestation_pool, block_pool, eth2_network, eth2_discovery, beacon_node_types, mainchain_monitor, version, ssz, ssz/dynamic_navigator, sync_protocol, request_manager, validator_keygen, interop, statusbar @@ -45,10 +46,9 @@ type nickname: string network: Eth2Node forkVersion: array[4, byte] - networkIdentity: Eth2NodeIdentity + netKeys: DiscKeyPair requestManager: RequestManager - bootstrapNodes: seq[BootstrapAddr] - bootstrapEnrs: seq[enr.Record] + bootstrapNodes: seq[ENode] db: BeaconChainDB config: BeaconNodeConf attachedValidators: ValidatorPool @@ -121,55 +121,10 @@ proc getStateFromSnapshot(conf: BeaconNodeConf, state: var BeaconState): bool = 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.} = let - networkId = getPersistentNetIdentity(conf) - nickname = if conf.nodeName == "auto": shortForm(networkId) + netKeys = getPersistentNetKeys(conf) + nickname = if conf.nodeName == "auto": shortForm(netKeys) else: conf.nodeName db = BeaconChainDB.init(kvStore LmdbStoreRef.init(conf.databaseDir)) @@ -222,34 +177,24 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async # monitor mainchainMonitor.start() - var - bootNodes: seq[BootstrapAddr] - enrs: seq[enr.Record] - - for node in conf.bootstrapNodes: bootNodes.addBootstrapAddr(node) - 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)) + var bootNodes: seq[ENode] + for node in conf.bootstrapNodes: bootNodes.addBootstrapNode(node) + bootNodes.loadBootstrapFile(string conf.bootstrapNodesFile) + bootNodes.loadBootstrapFile(conf.dataDir / "bootstrap_nodes.txt") + bootNodes = filterIt(bootNodes, it.pubkey != netKeys.pubkey) let - network = await createEth2Node(conf, bootNodes, enrs) - - let addressFile = string(conf.dataDir) / "beacon_node.address" + network = await createEth2Node(conf, bootNodes) + addressFile = string(conf.dataDir) / "beacon_node.address" network.saveConnectionAddressFile(addressFile) var res = BeaconNode( nickname: nickname, network: network, forkVersion: blockPool.headState.data.data.fork.current_version, - networkIdentity: networkId, + netKeys: netKeys, requestManager: RequestManager.init(network), bootstrapNodes: bootNodes, - bootstrapEnrs: enrs, db: db, config: conf, attachedValidators: ValidatorPool.init(), @@ -1027,7 +972,7 @@ when hasPrompt: # arbitrary expression that is resolvable through this API. case expr.toLowerAscii of "connected_peers": - $(sync_protocol.libp2p_peers.value.int) + $(libp2p_peers.value.int) of "last_finalized_epoch": var head = node.blockPool.finalizedHead diff --git a/beacon_chain/block_pool.nim b/beacon_chain/block_pool.nim index 1e556061e..38c5c6f8b 100644 --- a/beacon_chain/block_pool.nim +++ b/beacon_chain/block_pool.nim @@ -712,9 +712,10 @@ proc updateStateData*(pool: BlockPool, state: var StateData, bs: BlockSlot) = # applied for i in countdown(ancestors.len - 1, 0): let ok = - skipAndUpdateState(state.data, ancestors[i].data.message, {skipValidation}) do( - state: HashedBeaconState): - pool.maybePutState(state, ancestors[i].refs) + skipAndUpdateState(state.data, + ancestors[i].data.message, + {skipValidation}) do (state: HashedBeaconState): + pool.maybePutState(state, ancestors[i].refs) doAssert ok, "Blocks in database should never fail to apply.." skipAndUpdateState(state.data, bs.slot) do(state: HashedBeaconState): diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index ed200ac03..c09cbdee7 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -90,11 +90,6 @@ type desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses." name: "bootstrap-file" }: InputFile - enrBootstrapNodesFile* {. - defaultValue: "" - desc: "Specifies a line-delimited file of bootstrap ENR records" - name: "enr-bootstrap-file" }: InputFile - tcpPort* {. defaultValue: defaultPort(config) desc: "TCP listening port." diff --git a/beacon_chain/eth2_discovery.nim b/beacon_chain/eth2_discovery.nim index bfc3d12eb..8a80c2d91 100644 --- a/beacon_chain/eth2_discovery.nim +++ b/beacon_chain/eth2_discovery.nim @@ -1,7 +1,9 @@ import - net, - eth/keys, eth/trie/db, - eth/p2p/discoveryv5/[protocol, node, discovery_db, types], + os, net, strutils, strformat, parseutils, + chronicles, stew/result, eth/keys, eth/trie/db, eth/p2p/enode, + eth/p2p/discoveryv5/[enr, protocol, node, discovery_db, types], + libp2p/[multiaddress, multicodec, peer], + libp2p/crypto/crypto as libp2pCrypto, conf type @@ -9,7 +11,7 @@ type Eth2DiscoveryId* = NodeId export - Eth2DiscoveryProtocol, open, start, close + Eth2DiscoveryProtocol, open, start, close, result proc new*(T: type Eth2DiscoveryProtocol, conf: BeaconNodeConf, @@ -24,3 +26,119 @@ proc new*(T: type Eth2DiscoveryProtocol, 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 + diff --git a/beacon_chain/eth2_network.nim b/beacon_chain/eth2_network.nim index 0824e1404..a62713320 100644 --- a/beacon_chain/eth2_network.nim +++ b/beacon_chain/eth2_network.nim @@ -1,8 +1,12 @@ import - options, tables, - chronos, json_serialization, strutils, chronicles, metrics, - eth/net/nat, eth/p2p/discoveryv5/enr, - version, conf + options, tables, strutils, sequtils, + chronos, json_serialization, chronicles, metrics, libp2p/crypto/secp, + eth/keys, eth/p2p/enode, eth/net/nat, eth/p2p/discoveryv5/enr, + eth2_discovery, version, conf + +type + DiscKeyPair* = keys.KeyPair + DiscPrivKey* = keys.PrivateKey const clientId* = "Nimbus beacon node v" & fullVersionStr @@ -61,7 +65,7 @@ when networkBackend in [libp2p, libp2pDaemon]: import os, random, stew/io, eth/async_utils, - libp2p/crypto/crypto, libp2p/[multiaddress, multicodec], + libp2p/crypto/crypto as libp2pCrypto, libp2p/[multiaddress, multicodec], ssz export @@ -94,11 +98,14 @@ when networkBackend in [libp2p, libp2pDaemon]: netBackendName* = "libp2p" networkKeyFilename = "privkey.protobuf" - type - BootstrapAddr* = MultiAddress - Eth2NodeIdentity* = KeyPair + func asLibp2pKey*(key: DiscPrivKey): libp2pCrypto.PrivateKey = + libp2pCrypto.PrivateKey(scheme: Secp256k1, + 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) if IPFS.match(address) and matchPartial(multiaddress.TCP, address): result = address @@ -109,36 +116,32 @@ when networkBackend in [libp2p, libp2pDaemon]: template tcpEndPoint(address, port): auto = MultiAddress.init(address, Protocol.IPPROTO_TCP, port) - proc initAddress*(T: type BootstrapAddr, ip: IpAddress, tcpPort: Port): T = - tcpEndPoint(ip, tcpPort) + proc genRandomNetKey: DiscPrivKey = + let skkey = SkPrivateKey.random + DiscPrivKey(data: skkey.data) proc ensureNetworkIdFile(conf: BeaconNodeConf): string = result = conf.dataDir / networkKeyFilename if not fileExists(result): createDir conf.dataDir.string - let pk = PrivateKey.random(Secp256k1) - writeFile(result, pk.getBytes) + let pk = genRandomNetKey() + writeFile(result, pk.data) - proc getPersistentNetIdentity*(conf: BeaconNodeConf): Eth2NodeIdentity = - let privateKeyFile = conf.dataDir / networkKeyFilename - var privKey: PrivateKey - if not fileExists(privateKeyFile): + proc getPersistentNetKeys*(conf: BeaconNodeConf): DiscKeyPair = + let privKeyPath = conf.dataDir / networkKeyFilename + var privKey: DiscPrivKey + if not fileExists(privKeyPath): createDir conf.dataDir.string - privKey = PrivateKey.random(Secp256k1) - writeFile(privateKeyFile, privKey.getBytes()) + privKey = genRandomNetKey() + writeFile(privKeyPath, privKey.data) else: - let strdata = readFile(privateKeyFile) - privKey = PrivateKey.init(cast[seq[byte]](strdata)) + let strdata = readFile(privKeyPath) + privKey = initPrivateKey(cast[seq[byte]](strdata)) - result = KeyPair(seckey: privKey, pubkey: privKey.getKey()) - - proc allMultiAddresses(nodes: seq[BootstrapAddr]): seq[string] = - for node in nodes: - result.add $node + DiscKeyPair(seckey: privKey, pubkey: privKey.getPublicKey()) proc createEth2Node*(conf: BeaconNodeConf, - bootstrapNodes: seq[BootstrapAddr], - bootstrapEnrs: seq[enr.Record]): Future[Eth2Node] {.async.} = + bootstrapNodes: seq[ENode]): Future[Eth2Node] {.async.} = var (extIp, extTcpPort, _) = setupNat(conf) hostAddress = tcpEndPoint(globalListeningAddr, Port conf.tcpPort) @@ -150,16 +153,13 @@ when networkBackend in [libp2p, libp2pDaemon]: bootstrapNodes when networkBackend == libp2p: - let keys = conf.getPersistentNetIdentity + let keys = conf.getPersistentNetKeys # TODO nim-libp2p still doesn't have support for announcing addresses # that are different from the host address (this is relevant when we # are running behind a NAT). - var switch = newStandardSwitch(some keys.seckey, hostAddress, + var switch = newStandardSwitch(some keys.seckey.asLibp2pKey, hostAddress, triggerSelf = true, gossip = false) result = Eth2Node.init(conf, switch, keys.seckey) - for enr in bootstrapEnrs: - result.addKnownPeer(enr) - await result.start() else: let keyFile = conf.ensureNetworkIdFile @@ -173,7 +173,7 @@ when networkBackend in [libp2p, libp2pDaemon]: id = keyFile, hostAddresses = @[hostAddress], announcedAddresses = announcedAddresses, - bootstrapNodes = allMultiAddresses(bootstrapNodes), + bootstrapNodes = mapIt(bootstrapNodes, it.toMultiAddressStr), peersRequired = 1) mainDaemon = await daemonFut @@ -185,52 +185,53 @@ when networkBackend in [libp2p, libp2pDaemon]: result = await Eth2Node.init(mainDaemon) proc getPersistenBootstrapAddr*(conf: BeaconNodeConf, - ip: IpAddress, port: Port): BootstrapAddr = - let pair = getPersistentNetIdentity(conf) - let pidma = MultiAddress.init(multiCodec("p2p"), PeerID.init(pair.pubkey)) - result = tcpEndPoint(ip, port) & pidma + ip: IpAddress, port: Port): ENode = + let pair = getPersistentNetKeys(conf) + initENode(pair.pubkey, Address(ip: ip, udpPort: port)) - proc isSameNode*(bootstrapNode: BootstrapAddr, id: Eth2NodeIdentity): bool = - if IPFS.match(bootstrapNode): - let pid1 = PeerID.init(bootstrapNode[2].protoAddress()) - let pid2 = PeerID.init(id.pubkey) - result = (pid1 == pid2) + proc shortForm*(id: DiscKeyPair): string = + $PeerID.init(id.pubkey.asLibp2pKey) - proc shortForm*(id: Eth2NodeIdentity): string = - $PeerID.init(id.pubkey) - - proc multiAddressToPeerInfo(a: MultiAddress): PeerInfo = - if IPFS.match(a): - let - peerId = PeerID.init(a[2].protoAddress()) - addresses = @[a[0] & a[1]] - when networkBackend == libp2p: - return PeerInfo.init(peerId, addresses) - else: - return PeerInfo(peer: peerId, addresses: addresses) + proc toPeerInfo(enode: ENode): PeerInfo = + let + peerId = PeerID.init enode.pubkey.asLibp2pKey + addresses = @[MultiAddress.init enode.toMultiAddressStr] + when networkBackend == libp2p: + return PeerInfo.init(peerId, addresses) + else: + return PeerInfo(peer: peerId, addresses: addresses) proc connectToNetwork*(node: Eth2Node, - bootstrapNodes: seq[MultiAddress]) {.async.} = - # TODO: perhaps we should do these in parallel - var connected = false - for bootstrapNode in bootstrapNodes: - try: - let peerInfo = multiAddressToPeerInfo(bootstrapNode) - when networkBackend == libp2p: - discard await node.switch.dial(peerInfo) - else: - await node.daemon.connect(peerInfo.peer, peerInfo.addresses) - var peer = node.getPeer(peerInfo) - peer.wasDialed = true - await initializeConnection(peer) - connected = true - except CatchableError as err: - error "Failed to connect to bootstrap node", - node = bootstrapNode, err = err.msg + bootstrapNodes: seq[ENode]) {.async.} = + when networkBackend == libp2pDaemon: + var connected = false + for bootstrapNode in bootstrapNodes: + try: + let peerInfo = toPeerInfo(bootstrapNode) + when networkBackend == libp2p: + discard await node.switch.dial(peerInfo) + else: + await node.daemon.connect(peerInfo.peer, peerInfo.addresses) + var peer = node.getPeer(peerInfo) + peer.wasDialed = true + await initializeConnection(peer) + connected = true + except CatchableError as err: + error "Failed to connect to bootstrap node", + node = bootstrapNode, err = err.msg - if bootstrapNodes.len > 0 and connected == false: - fatal "Failed to connect to any bootstrap node. Quitting." - quit 1 + if bootstrapNodes.len > 0 and connected == false: + fatal "Failed to connect to any bootstrap node. Quitting." + 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) = when networkBackend == libp2p: diff --git a/beacon_chain/libp2p_backend.nim b/beacon_chain/libp2p_backend.nim index df87d30da..95529999b 100644 --- a/beacon_chain/libp2p_backend.nim +++ b/beacon_chain/libp2p_backend.nim @@ -3,7 +3,7 @@ import stew/[varints,base58], stew/shims/[macros, tables], chronos, chronicles, stint, faststreams/output_stream, serialization, 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 libp2p/[switch, multistream, connection, multiaddress, peerinfo, peer, @@ -177,22 +177,33 @@ proc toPeerInfo(r: Option[enr.TypedRecord]): PeerInfo = if r.isSome: return r.get.toPeerInfo -proc dialPeer*(node: Eth2Node, enr: enr.Record) {.async.} = - let peerInfo = enr.toTypedRecord.toPeerInfo - if peerInfo != nil: - discard await node.switch.dial(peerInfo) - var peer = node.getPeer(peerInfo) - peer.wasDialed = true - await initializeConnection(peer) +proc dialPeer*(node: Eth2Node, peerInfo: PeerInfo) {.async.} = + debug "Dialing peer", peer = $peerInfo + discard await node.switch.dial(peerInfo) + var peer = node.getPeer(peerInfo) + peer.wasDialed = true + await initializeConnection(peer) + +proc runDiscoveryLoop*(node: Eth2Node) {.async.} = + debug "Starting discovery loop" -proc runDiscoveryLoop(node: Eth2Node) {.async.} = while true: - if node.peersByDiscoveryId.len < node.wantedPeers: - let discoveredPeers = await node.discovery.lookupRandom() - for peer in discoveredPeers: - if peer.id notin node.peersByDiscoveryId: - # TODO do this in parallel - await node.dialPeer(peer.record) + let currentPeerCount = node.switch.connections.len + libp2p_peers.set currentPeerCount.int64 + if currentPeerCount < node.wantedPeers: + try: + let discoveredPeers = await node.discovery.lookupRandom() + 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) @@ -213,13 +224,14 @@ proc init*(T: type Eth2Node, conf: BeaconNodeConf, if msg.protocolMounter != nil: msg.protocolMounter result -proc addKnownPeer*(node: Eth2Node, peerEnr: enr.Record) = - node.discovery.addNode peerEnr +proc addKnownPeer*(node: Eth2Node, peer: ENode) = + node.discovery.addNode peer proc start*(node: Eth2Node) {.async.} = node.discovery.open() node.discovery.start() node.libp2pTransportLoops = await node.switch.start() + traceAsyncErrors node.runDiscoveryLoop() proc init*(T: type Peer, network: Eth2Node, info: PeerInfo): Peer = new result diff --git a/beacon_chain/libp2p_backends_common.nim b/beacon_chain/libp2p_backends_common.nim index 6c2cdf14c..faa567d24 100644 --- a/beacon_chain/libp2p_backends_common.nim +++ b/beacon_chain/libp2p_backends_common.nim @@ -1,3 +1,6 @@ +import + metrics + type ResponseCode* = enum Success @@ -22,6 +25,8 @@ const logScope: topics = "libp2p" +declarePublicGauge libp2p_peers, "Number of libp2p peers" + template libp2pProtocol*(name: string, version: int) {.pragma.} proc getRequestProtoName(fn: NimNode): NimNode = diff --git a/beacon_chain/sync_protocol.nim b/beacon_chain/sync_protocol.nim index bc58fa0e3..f94fbaaea 100644 --- a/beacon_chain/sync_protocol.nim +++ b/beacon_chain/sync_protocol.nim @@ -1,14 +1,12 @@ import options, tables, sets, macros, - chronicles, chronos, metrics, stew/ranges/bitranges, + chronicles, chronos, stew/ranges/bitranges, spec/[datatypes, crypto, digest, helpers], beacon_node_types, eth2_network, block_pool, ssz when networkBackend == libp2p: import libp2p/switch -declarePublicGauge libp2p_peers, "Number of libp2p peers" - logScope: topics = "sync" @@ -100,9 +98,6 @@ p2pProtocol BeaconSync(version = 1, else: warn "Status response not received in time" - onPeerDisconnected do (peer: Peer): - libp2p_peers.set peer.network.peers.len.int64 - requestResponse: proc status(peer: Peer, theirStatus: StatusMsg) {.libp2pProtocol("status", 1).} = let @@ -181,8 +176,6 @@ proc handleInitialStatus(peer: Peer, # 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. try: - libp2p_peers.set peer.network.peers.len.int64 - debug "Peer connected. Initiating sync", peer, localHeadSlot = ourStatus.headSlot, remoteHeadSlot = theirStatus.headSlot, diff --git a/tests/test_discovery_helpers.nim b/tests/test_discovery_helpers.nim new file mode 100644 index 000000000..6c2ed5b87 --- /dev/null +++ b/tests/test_discovery_helpers.nim @@ -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 +