diff --git a/eth/p2p/discoveryv5/protocol.nim b/eth/p2p/discoveryv5/protocol.nim index 7e53237..ffa3d7f 100644 --- a/eth/p2p/discoveryv5/protocol.nim +++ b/eth/p2p/discoveryv5/protocol.nim @@ -61,7 +61,7 @@ proc addNode*(d: Protocol, enr: EnrUri) = doAssert(res) d.addNode newNode(r) -proc getNode*(d: Protocol, id: NodeId): Node = +proc getNode*(d: Protocol, id: NodeId): Option[Node] = d.routingTable.getNode(id) proc randomNodes*(d: Protocol, count: int): seq[Node] = @@ -213,9 +213,7 @@ proc receive*(d: Protocol, a: Address, msg: openArray[byte]) {.gcsafe, var packet: Packet let decoded = d.codec.decodeEncrypted(sender, a, msg, authTag, node, packet) if decoded == DecodeStatus.Success: - if node.isNil: - node = d.routingTable.getNode(sender) - else: + if not node.isNil: # Not filling table with nodes without correct IP in the ENR if a.ip == node.address.ip: debug "Adding new node to routing table", node = $node, @@ -232,7 +230,7 @@ proc receive*(d: Protocol, a: Address, msg: openArray[byte]) {.gcsafe, if d.awaitedPackets.take((sender, packet.reqId), waiter): waiter.complete(packet.some) else: - debug "TODO: handle packet: ", packet = packet.kind, origin = $node + debug "TODO: handle packet: ", packet = packet.kind, origin = a elif decoded == DecodeStatus.DecryptError: debug "Could not decrypt packet, respond with whoareyou", localNode = $d.localNode, address = a @@ -418,6 +416,31 @@ proc lookupRandom*(d: Protocol): Future[seq[Node]] raise newException(RandomSourceDepleted, "Could not randomize bytes") d.lookup(id) +proc resolve*(d: Protocol, id: NodeId): Future[Option[Node]] {.async.} = + ## Resolve a `Node` based on provided `NodeId`. + ## + ## This will first look in the own DHT. If the node is known, it will try to + ## contact if for newer information. If node is not known or it does not + ## reply, a lookup is done to see if it can find a (newer) record of the node + ## on the network. + + let node = d.getNode(id) + if node.isSome(): + let request = await d.findNode(node.get(), 0) + + if request.len > 0: + return some(request[0]) + + let discovered = await d.lookup(id) + for n in discovered: + if n.id == id: + # TODO: Not getting any new seqNum here as in a lookup nodes in table with + # new seqNum don't get replaced. + if node.isSome() and node.get().record.seqNum >= n.record.seqNum: + return node + else: + return some(n) + proc revalidateNode*(d: Protocol, n: Node) {.async, raises:[Defect, Exception].} = # TODO: Exception trace "Ping to revalidate node", node = $n diff --git a/eth/p2p/discoveryv5/routing_table.nim b/eth/p2p/discoveryv5/routing_table.nim index d98b499..b1b633b 100644 --- a/eth/p2p/discoveryv5/routing_table.nim +++ b/eth/p2p/discoveryv5/routing_table.nim @@ -1,5 +1,6 @@ import - std/[algorithm, times, sequtils, bitops, random, sets], stint, chronicles, + std/[algorithm, times, sequtils, bitops, random, sets, options], + stint, chronicles, types, node type @@ -174,11 +175,11 @@ proc addNode*(r: var RoutingTable, n: Node): Node = # Nothing added, ping evictionCandidate return evictionCandidate -proc getNode*(r: RoutingTable, id: NodeId): Node = +proc getNode*(r: RoutingTable, id: NodeId): Option[Node] = let b = r.bucketForNode(id) for n in b.nodes: if n.id == id: - return n + return some(n) proc contains*(r: RoutingTable, n: Node): bool = n in r.bucketForNode(n.id) diff --git a/tests/p2p/test_discoveryv5.nim b/tests/p2p/test_discoveryv5.nim index 263ac92..186069b 100644 --- a/tests/p2p/test_discoveryv5.nim +++ b/tests/p2p/test_discoveryv5.nim @@ -60,7 +60,9 @@ suite "Discovery v5 Tests": for i in 0..<1000: node.addNode(generateNode()) - check node.getNode(targetNode.id) == targetNode + let n = node.getNode(targetNode.id) + require n.isSome() + check n.get() == targetNode await node.closeWait() @@ -82,8 +84,10 @@ suite "Discovery v5 Tests": await node1.revalidateNode(bootnode.localNode) await node1.revalidateNode(node2.localNode) - check node1.getNode(bootnode.localNode.id) == bootnode.localNode - check node1.getNode(node2.localNode.id) == nil + let n = node1.getNode(bootnode.localNode.id) + require n.isSome() + check n.get() == bootnode.localNode + check node1.getNode(node2.localNode.id).isNone() await node1.closeWait() @@ -310,3 +314,66 @@ suite "Discovery v5 Tests": for node in nodes: await node.closeWait() + + asyncTest "Resolve target": + let + mainNode = initDiscoveryNode(PrivateKey.random()[], localAddress(20301)) + lookupNode = initDiscoveryNode(PrivateKey.random()[], localAddress(20302)) + targetKey = PrivateKey.random()[] + targetAddress = localAddress(20303) + targetNode = initDiscoveryNode(targetKey, targetAddress) + targetId = targetNode.localNode.id + + var targetSeqNum = targetNode.localNode.record.seqNum + + # Populate DHT with target through a ping. Next, close target and see + # if resolve works (only local lookup) + block: + let pong = await targetNode.ping(mainNode.localNode) + require pong.isSome() + await targetNode.closeWait() + let n = await mainNode.resolve(targetId) + require n.isSome() + check: + n.get().id == targetId + n.get().record.seqNum == targetSeqNum + + # Bring target back online, update seqNum in ENR, check if we get the + # updated ENR. + block: + # TODO: need to add some logic to update ENRs properly + targetSeqNum.inc() + let r = enr.Record.init(targetSeqNum, targetKey, + some(targetAddress.ip), targetAddress.tcpPort, targetAddress.udpPort) + targetNode.localNode.record = r + targetNode.open() + let n = await mainNode.resolve(targetId) + require n.isSome() + check: + n.get().id == targetId + n.get().record.seqNum == targetSeqNum + + # Update seqNum in ENR again, ping lookupNode to be added in DHT, + # close targetNode, resolve should lookup, check if we get updated ENR. + block: + targetSeqNum.inc() + let r = enr.Record.init(3, targetKey, some(targetAddress.ip), + targetAddress.tcpPort, targetAddress.udpPort) + targetNode.localNode.record = r + let pong = await targetNode.ping(lookupNode.localNode) + require pong.isSome() + + await targetNode.closeWait() + # TODO: This step should eventually not be needed and ENRs with new seqNum + # should just get updated in the lookup. + await mainNode.revalidateNode(targetNode.localNode) + + mainNode.addNode(lookupNode.localNode.record) + let n = await mainNode.resolve(targetId) + require n.isSome() + check: + n.get().id == targetId + n.get().record.seqNum == targetSeqNum + + await mainNode.closeWait() + await lookupNode.closeWait()