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 +