mirror of
https://github.com/logos-storage/logos-storage-nim-dht.git
synced 2026-01-02 13:33:08 +00:00
fix(discovery): prevent premature node eviction from routing table
The findNode and findNodeFast operations were using the default aggressive removal threshold (1.0) when timing out, while other timeout operations (ping, talkReq, getProviders) correctly used NoreplyRemoveThreshold (0.5). This inconsistency caused nodes with excellent reliability (1.0) to be removed during heavy load scenarios when findNode/findNodeFast operations timed out, even though the nodes were still healthy and simply slow to respond. Changed findNode and findNodeFast timeout paths to use NoreplyRemoveThreshold, ensuring consistent and more tolerant behavior across all timeout scenarios. This aligns with Kademlia's recommendation to be conservative about removing nodes, especially during temporary network congestion. Evidence from logs showing the issue: DBG - Node added to routing table topics="discv5 routingtable" tid=1 n=1ff*7a561e:10.244.0.208:6890 DBG - bucket topics="discv5" tid=1 depth=0 len=2 standby=0 DBG - node topics="discv5" tid=1 n=130*db8a1b:10.244.2.207:6890 rttMin=1 rttAvg=2 reliability=1.0 DBG - node topics="discv5" tid=1 n=1ff*7a561e:10.244.0.208:6890 rttMin=1 rttAvg=14 reliability=1.0 DBG - Node removed from routing table topics="discv5 routingtable" tid=1 n=1ff*7a561e:10.244.0.208:6890 DBG - Total nodes in discv5 routing table topics="discv5" tid=1 total=1 DBG - bucket topics="discv5" tid=1 depth=0 len=1 standby=0 DBG - node topics="discv5" tid=1 n=130*db8a1b:10.244.2.207:6890 rttMin=1 rttAvg=165 reliability=0.957 DBG - Node removed from routing table topics="discv5 routingtable" tid=1 n=130*db8a1b:10.244.2.207:6890 DBG - Total nodes in discv5 routing table topics="discv5" tid=1 total=0 First entry shows a node with perfect reliability (1.0) and 14ms RTT being removed. Second shows a node with 95.7% reliability also being evicted. Signed-off-by: Chrysostomos Nanakos <chris@include.gr>
This commit is contained in:
parent
99884b5971
commit
71bd679365
@ -136,7 +136,7 @@ const
|
||||
FindnodeSeenThreshold = 1.0 ## threshold used as findnode response filter
|
||||
LookupSeenThreshold = 0.0 ## threshold used for lookup nodeset selection
|
||||
QuerySeenThreshold = 0.0 ## threshold used for query nodeset selection
|
||||
NoreplyRemoveThreshold = 0.5 ## remove node on no reply if 'seen' is below this value
|
||||
NoreplyRemoveThreshold* = 0.5 ## remove node on no reply if 'seen' is below this value
|
||||
|
||||
func shortLog*(record: SignedPeerRecord): string =
|
||||
## Returns compact string representation of ``SignedPeerRecord``.
|
||||
@ -587,7 +587,7 @@ proc findNode*(d: Protocol, toNode: Node, distances: seq[uint16]):
|
||||
return ok(res)
|
||||
else:
|
||||
trace "findNode nodes not OK."
|
||||
d.replaceNode(toNode)
|
||||
d.replaceNode(toNode, NoreplyRemoveThreshold)
|
||||
return err(nodes.error)
|
||||
|
||||
proc findNodeFast*(d: Protocol, toNode: Node, target: NodeId):
|
||||
@ -605,7 +605,7 @@ proc findNodeFast*(d: Protocol, toNode: Node, target: NodeId):
|
||||
let res = verifyNodesRecords(nodes.get(), toNode, FindNodeFastResultLimit)
|
||||
return ok(res)
|
||||
else:
|
||||
d.replaceNode(toNode)
|
||||
d.replaceNode(toNode, NoreplyRemoveThreshold)
|
||||
return err(nodes.error)
|
||||
|
||||
|
||||
|
||||
@ -2,10 +2,15 @@
|
||||
|
||||
import
|
||||
std/tables,
|
||||
chronos, chronicles, stint, asynctest/chronos/unittest,
|
||||
stew/byteutils, bearssl/rand,
|
||||
chronos,
|
||||
chronicles,
|
||||
stint,
|
||||
asynctest/chronos/unittest,
|
||||
stew/byteutils,
|
||||
bearssl/rand,
|
||||
libp2p/crypto/crypto,
|
||||
codexdht/discv5/[transport, spr, node, routing_table, encoding, sessions, nodes_verification],
|
||||
codexdht/discv5/
|
||||
[transport, spr, node, routing_table, encoding, sessions, nodes_verification],
|
||||
codexdht/discv5/crypto as dhtcrypto,
|
||||
codexdht/discv5/protocol as discv5_protocol,
|
||||
../dht/test_helper
|
||||
@ -22,13 +27,13 @@ suite "Discovery v5 Tests":
|
||||
pk = PrivateKey.example(rng)
|
||||
targetPk = PrivateKey.example(rng)
|
||||
node = initDiscoveryNode(rng, pk, localAddress(20302))
|
||||
targetNode = targetPk.generateNode(port=26302)
|
||||
targetNode = targetPk.generateNode(port = 26302)
|
||||
|
||||
check node.addNode(targetNode)
|
||||
|
||||
for i in 0..<1000:
|
||||
for i in 0 ..< 1000:
|
||||
let pk = PrivateKey.example(rng)
|
||||
discard node.addNode(pk.generateNode(port=27302+i))
|
||||
discard node.addNode(pk.generateNode(port = 27302 + i))
|
||||
|
||||
let n = node.getNode(targetNode.id)
|
||||
check n.isSome()
|
||||
@ -39,14 +44,13 @@ suite "Discovery v5 Tests":
|
||||
test "Node deletion":
|
||||
let
|
||||
pkBootnode = PrivateKey.example(rng)
|
||||
bootnode = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20301))
|
||||
bootnode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302),
|
||||
@[bootnode.localNode.record])
|
||||
rng, PrivateKey.example(rng), localAddress(20302), @[bootnode.localNode.record]
|
||||
)
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20303),
|
||||
@[bootnode.localNode.record])
|
||||
rng, PrivateKey.example(rng), localAddress(20303), @[bootnode.localNode.record]
|
||||
)
|
||||
pong1 = await discv5_protocol.ping(node1, bootnode.localNode)
|
||||
pong2 = await discv5_protocol.ping(node1, node2.localNode)
|
||||
|
||||
@ -83,7 +87,7 @@ suite "Discovery v5 Tests":
|
||||
("0x0100", 9'u16),
|
||||
("0x01ff", 9'u16),
|
||||
("0x8000", 16'u16),
|
||||
("0xffff", 16'u16)
|
||||
("0xffff", 16'u16),
|
||||
]
|
||||
|
||||
for (id, d) in testValues:
|
||||
@ -93,20 +97,17 @@ suite "Discovery v5 Tests":
|
||||
# Values for this test are taken from
|
||||
# https://github.com/ethereum/go-ethereum/blob/d8ff53dfb8a516f47db37dbc7fd7ad18a1e8a125/p2p/discover/v4_lookup_test.go#L176
|
||||
const
|
||||
targetKey = "045d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
|
||||
targetKey =
|
||||
"045d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
|
||||
testValues = [
|
||||
("14a98db9b46a831d67eff29f3b85b1b485bb12ae9796aea98d91be3dc78d8a91", 248'u16),
|
||||
|
||||
("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169", 252'u16),
|
||||
("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c", 252'u16),
|
||||
|
||||
("ce1435a956a98ffec484cd11489c4f165cf1606819ab6b521cee440f0c677e9e", 253'u16),
|
||||
("120260dce739b6f71f171da6f65bc361b5fad51db74cf02d3e973347819a6518", 253'u16),
|
||||
|
||||
("a30599b12827b69120633f15b98a7f6bc9fc2e9a0fd6ae2ebb767c0e64d743ab", 254'u16),
|
||||
("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9", 254'u16),
|
||||
("996e7f8d1638be92d7328b4770f47e5420fc4bafecb4324fd33b1f5d9f403a75", 254'u16),
|
||||
|
||||
("d09e5eaeec0fd596236faed210e55ef45112409a5aa7f3276d26646080dcfaeb", 255'u16),
|
||||
("6cfbd7b8503073fc3dbdb746a7c672571648d3bd15197ccf7f7fef3d904f53a2", 255'u16),
|
||||
("9ae91101d6b5048607f41ec0f690ef5d09507928aded2410aabd9237aa2727d7", 255'u16),
|
||||
@ -116,7 +117,6 @@ suite "Discovery v5 Tests":
|
||||
("1fa56cf25d4b46c2bf94e82355aa631717b63190785ac6bae545a88aadc304a9", 255'u16),
|
||||
("3c38c503c0376f9b4adcbe935d5f4b890391741c764f61b03cd4d0d42deae002", 255'u16),
|
||||
("3a54af3e9fa162bc8623cdf3e5d9b70bf30ade1d54cc3abea8659aba6cff471f", 255'u16),
|
||||
|
||||
("511b1686e4e58a917f7f848e9bf5539d206a68f5ad6b54b552c2399fe7d174ae", 256'u16),
|
||||
("c1e20dbbf0d530e50573bd0a260b32ec15eb9190032b4633d44834afc8afe578", 256'u16),
|
||||
("ed5f38f5702d92d306143e5d9154fb21819777da39af325ea359f453d179e80b", 256'u16),
|
||||
@ -138,7 +138,7 @@ suite "Discovery v5 Tests":
|
||||
("d269609743ef29d6446e3355ec647e38d919c82a4eb5837e442efd7f4218944f", 256'u16),
|
||||
("937b1af801def4e8f5a3a8bd225a8bcff1db764e41d3e177f2e9376e8dd87233", 256'u16),
|
||||
("6799a02ea1999aefdcbcc4d3ff9544478be7365a328d0d0f37c26bd95ade0cda", 256'u16),
|
||||
("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116", 256'u16)
|
||||
("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116", 256'u16),
|
||||
]
|
||||
|
||||
let
|
||||
@ -163,7 +163,7 @@ suite "Discovery v5 Tests":
|
||||
("0x0008", 4'u16),
|
||||
("0x0080", 8'u16),
|
||||
("0x0100", 9'u16),
|
||||
("0x8000", 16'u16)
|
||||
("0x8000", 16'u16),
|
||||
]
|
||||
|
||||
for (id, d) in testValues:
|
||||
@ -171,14 +171,15 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Distance to id check with keys":
|
||||
const
|
||||
targetKey = "045d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
|
||||
targetKey =
|
||||
"045d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
|
||||
testValues = [ # possible id in that distance range
|
||||
("cd2c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 251'u16),
|
||||
("c12c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 252'u16),
|
||||
("d92c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 253'u16),
|
||||
("e92c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 254'u16),
|
||||
("892c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 255'u16),
|
||||
("492c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 256'u16)
|
||||
("492c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 256'u16),
|
||||
]
|
||||
|
||||
let
|
||||
@ -191,11 +192,11 @@ suite "Discovery v5 Tests":
|
||||
test "FindNode Test":
|
||||
const dist = 253'u16
|
||||
let
|
||||
mainNodeKey = PrivateKey.fromHex(
|
||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")
|
||||
mainNodeKey = PrivateKey
|
||||
.fromHex("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")
|
||||
.expect("Valid private key hex")
|
||||
testNodeKey = PrivateKey.fromHex(
|
||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")
|
||||
testNodeKey = PrivateKey
|
||||
.fromHex("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")
|
||||
.expect("Valid private key hex")
|
||||
mainNode = initDiscoveryNode(rng, mainNodeKey, localAddress(20301))
|
||||
testNode = initDiscoveryNode(rng, testNodeKey, localAddress(20302))
|
||||
@ -210,38 +211,33 @@ suite "Discovery v5 Tests":
|
||||
check (await mainNode.ping(testNode.localNode)).isOk()
|
||||
|
||||
# Get SPR of the node itself
|
||||
var discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[0'u16])
|
||||
var discovered = await findNode(testNode, mainNode.localNode, @[0'u16])
|
||||
check:
|
||||
discovered.isOk
|
||||
discovered[].len == 1
|
||||
discovered[][0] == mainNode.localNode
|
||||
# Get SPRs of nodes added at provided logarithmic distance
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[dist])
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[dist])
|
||||
check discovered.isOk
|
||||
check discovered[].len == 10
|
||||
for n in nodes:
|
||||
check discovered[].contains(n)
|
||||
|
||||
# Too high logarithmic distance, should return no nodes.
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[high(uint16)])
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[high(uint16)])
|
||||
check:
|
||||
discovered.isOk
|
||||
discovered[].len == 0
|
||||
|
||||
# Logarithmic distance of 256 should only return the testNode
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[256'u16])
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[256'u16])
|
||||
check:
|
||||
discovered.isOk
|
||||
discovered[].len == 1
|
||||
discovered[][0] == testNode.localNode
|
||||
|
||||
# Empty bucket
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[254'u16])
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[254'u16])
|
||||
check discovered.isOk
|
||||
check discovered[].len == 0
|
||||
|
||||
@ -250,8 +246,7 @@ suite "Discovery v5 Tests":
|
||||
discard mainNode.addSeenNode(n) # for testing only!
|
||||
|
||||
# Full bucket
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[dist])
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[dist])
|
||||
check discovered.isOk
|
||||
check discovered[].len == 16
|
||||
|
||||
@ -259,27 +254,26 @@ suite "Discovery v5 Tests":
|
||||
await testNode.closeWait()
|
||||
|
||||
test "FindNode with test table":
|
||||
|
||||
let mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
let mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
|
||||
# Generate 1000 random nodes and add to our main node's routing table
|
||||
for i in 0..<1000:
|
||||
discard mainNode.addSeenNode(generateNode(PrivateKey.example(rng), port=28302+i)) # for testing only!
|
||||
for i in 0 ..< 1000:
|
||||
discard mainNode.addSeenNode(
|
||||
generateNode(PrivateKey.example(rng), port = 28302 + i)
|
||||
) # for testing only!
|
||||
|
||||
let
|
||||
neighbours = mainNode.neighbours(mainNode.localNode.id)
|
||||
closest = neighbours[0]
|
||||
closestDistance = logDistance(closest.id, mainNode.localNode.id)
|
||||
|
||||
debug "Closest neighbour", closestDistance, id=closest.id.toHex()
|
||||
debug "Closest neighbour", closestDistance, id = closest.id.toHex()
|
||||
|
||||
let
|
||||
testNode = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302),
|
||||
@[mainNode.localNode.record])
|
||||
discovered = await findNode(testNode, mainNode.localNode,
|
||||
@[closestDistance])
|
||||
rng, PrivateKey.example(rng), localAddress(20302), @[mainNode.localNode.record]
|
||||
)
|
||||
discovered = await findNode(testNode, mainNode.localNode, @[closestDistance])
|
||||
|
||||
check discovered.isOk
|
||||
check closest in discovered[]
|
||||
@ -288,18 +282,22 @@ suite "Discovery v5 Tests":
|
||||
await testNode.closeWait()
|
||||
|
||||
proc testLookupTargets(fast: bool = false): Future[bool] {.async.} =
|
||||
const
|
||||
nodeCount = 17
|
||||
const nodeCount = 17
|
||||
|
||||
let bootNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
let bootNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
await bootNode.start()
|
||||
|
||||
var nodes = newSeqOfCap[discv5_protocol.Protocol](nodeCount)
|
||||
nodes.add(bootNode)
|
||||
for i in 1 ..< nodeCount:
|
||||
nodes.add(initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301 + i),
|
||||
@[bootNode.localNode.record]))
|
||||
nodes.add(
|
||||
initDiscoveryNode(
|
||||
rng,
|
||||
PrivateKey.example(rng),
|
||||
localAddress(20301 + i),
|
||||
@[bootNode.localNode.record],
|
||||
)
|
||||
)
|
||||
|
||||
# Make sure all nodes have "seen" each other by forcing pings
|
||||
for n in nodes:
|
||||
@ -314,9 +312,10 @@ suite "Discovery v5 Tests":
|
||||
for i in 1 ..< nodeCount:
|
||||
await nodes[i].start()
|
||||
|
||||
for i in 0..<nodeCount-1:
|
||||
for i in 0 ..< nodeCount - 1:
|
||||
let target = nodes[i]
|
||||
let discovered = await nodes[nodeCount-1].lookup(target.localNode.id, fast = fast)
|
||||
let discovered =
|
||||
await nodes[nodeCount - 1].lookup(target.localNode.id, fast = fast)
|
||||
debug "Lookup result", target = target.localNode, discovered
|
||||
if discovered[0] != target.localNode:
|
||||
return false
|
||||
@ -334,10 +333,8 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Resolve target":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
lookupNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
lookupNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
targetKey = PrivateKey.example(rng)
|
||||
targetAddress = localAddress(20303)
|
||||
targetNode = initDiscoveryNode(rng, targetKey, targetAddress)
|
||||
@ -356,20 +353,27 @@ suite "Discovery v5 Tests":
|
||||
n.isSome()
|
||||
n.get().id == targetId
|
||||
n.get().record.seqNum == targetSeqNum
|
||||
# Node will be removed because of failed findNode request.
|
||||
|
||||
# Verify node is still in routing table after timeout (high reliability preserved)
|
||||
let nodeInTable = mainNode.getNode(targetId)
|
||||
check:
|
||||
nodeInTable.isSome()
|
||||
nodeInTable.get().id == targetId
|
||||
nodeInTable.get().record.seqNum == targetSeqNum
|
||||
|
||||
# Bring target back online, update seqNum in SPR, check if we get the
|
||||
# updated SPR.
|
||||
block:
|
||||
targetNode.open()
|
||||
# Request the target SPR and manually add it to the routing table.
|
||||
# Request the target SPR and manually add/update it in the routing table.
|
||||
# Ping for handshake based SPR passing will not work as our previous
|
||||
# session will still be in the LRU cache.
|
||||
let nodes = await mainNode.findNode(targetNode.localNode, @[0'u16])
|
||||
check:
|
||||
nodes.isOk()
|
||||
nodes[].len == 1
|
||||
mainNode.addNode(nodes[][0])
|
||||
# Add/update from findNode result - false because node already exists
|
||||
mainNode.addNode(nodes[][0]) == false
|
||||
|
||||
targetSeqNum.inc()
|
||||
# need to add something to get the spr sequence number incremented
|
||||
@ -387,9 +391,8 @@ suite "Discovery v5 Tests":
|
||||
n.isSome()
|
||||
n.get().id == targetId
|
||||
n.get().record.seqNum == targetSeqNum
|
||||
|
||||
# Add the updated version
|
||||
discard mainNode.addNode(n.get())
|
||||
# Add the updated version - node already exsists, just updating
|
||||
mainNode.addNode(n.get()) == false
|
||||
|
||||
# Update seqNum in SPR again, ping lookupNode to be added in routing table,
|
||||
# close targetNode, resolve should lookup, check if we get updated SPR.
|
||||
@ -403,6 +406,12 @@ suite "Discovery v5 Tests":
|
||||
# ping node so that it becomes "seen" and thus will be forwarded on a
|
||||
# findNode request
|
||||
check (await lookupNode.ping(targetNode.localNode)).isOk()
|
||||
|
||||
# lower reliability so timeout will trigger removal with 0.5 threshold
|
||||
let existingNode = mainNode.getNode(targetId)
|
||||
if existingNode.isSome():
|
||||
existingNode.get().seen = 0.3 # below NoreplyRemoveThreshold (0.5)
|
||||
|
||||
await targetNode.closeWait()
|
||||
|
||||
check mainNode.addNode(lookupNode.localNode.record)
|
||||
@ -412,9 +421,79 @@ suite "Discovery v5 Tests":
|
||||
n.get().id == targetId
|
||||
n.get().record.seqNum == targetSeqNum
|
||||
|
||||
# Demonstrate seen degradation via repeated findNode failures
|
||||
# Node was re-added by lookup with seen=1.0, now repeatedly timeout to degrade
|
||||
block:
|
||||
var attempt = 0
|
||||
while true:
|
||||
attempt.inc()
|
||||
|
||||
let nodeBeforeTimeout = mainNode.getNode(targetId)
|
||||
if nodeBeforeTimeout.isNone():
|
||||
break
|
||||
|
||||
discard await mainNode.findNode(nodeBeforeTimeout.get(), @[0'u16])
|
||||
|
||||
if attempt > 10:
|
||||
break
|
||||
|
||||
# Verify node was eventually removed due to degraded reliability
|
||||
let finalCheck = mainNode.getNode(targetId)
|
||||
check finalCheck.isNone()
|
||||
|
||||
await mainNode.closeWait()
|
||||
await lookupNode.closeWait()
|
||||
|
||||
test "FindNode timeout preserves high reliability nodes":
|
||||
# Nodes with reliability > NoreplyRemoveThreshold are kept when no replacements available
|
||||
let
|
||||
mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20401))
|
||||
targetNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20402))
|
||||
targetId = targetNode.localNode.id
|
||||
|
||||
# add node to routing table via ping
|
||||
check (await mainNode.ping(targetNode.localNode)).isOk()
|
||||
check mainNode.addNode(targetNode.localNode.record)
|
||||
|
||||
var n = mainNode.getNode(targetId)
|
||||
check n.isSome()
|
||||
n.get().seen = 1.8 * discv5_protocol.NoreplyRemoveThreshold
|
||||
|
||||
# close node to trigger timeout
|
||||
await targetNode.closeWait()
|
||||
|
||||
discard await mainNode.findNode(n.get(), @[0'u16])
|
||||
|
||||
# verify node still in routing table
|
||||
n = mainNode.getNode(targetId)
|
||||
check n.isSome()
|
||||
|
||||
await mainNode.closeWait()
|
||||
|
||||
test "FindNode timeout removes low reliability nodes":
|
||||
# Nodes with reliability <= NoreplyRemoveThreshold are removed on timeout
|
||||
let
|
||||
mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20403))
|
||||
targetNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20404))
|
||||
targetId = targetNode.localNode.id
|
||||
|
||||
check (await mainNode.ping(targetNode.localNode)).isOk()
|
||||
check mainNode.addNode(targetNode.localNode.record)
|
||||
|
||||
var n = mainNode.getNode(targetId)
|
||||
check n.isSome()
|
||||
n.get().seen = 0.01 * discv5_protocol.NoreplyRemoveThreshold
|
||||
|
||||
await targetNode.closeWait()
|
||||
|
||||
discard await mainNode.findNode(n.get(), @[0'u16])
|
||||
|
||||
# verify node removed from routing table
|
||||
n = mainNode.getNode(targetId)
|
||||
check n.isNone()
|
||||
|
||||
await mainNode.closeWait()
|
||||
|
||||
test "Random nodes, also with filter":
|
||||
let
|
||||
lookupNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
@ -429,11 +508,18 @@ suite "Discovery v5 Tests":
|
||||
|
||||
let discovered = lookupNode.randomNodes(10)
|
||||
check discovered.len == 3
|
||||
let discoveredFiltered = lookupNode.randomNodes(10,
|
||||
proc(n: Node) : bool = n.address.get.port == Port(20302))
|
||||
check discoveredFiltered.len == 1 and discoveredFiltered.contains(targetNode.localNode)
|
||||
let discoveredEmpty = lookupNode.randomNodes(10,
|
||||
proc(n: Node) : bool = n.address.get.port == Port(20305))
|
||||
let discoveredFiltered = lookupNode.randomNodes(
|
||||
10,
|
||||
proc(n: Node): bool =
|
||||
n.address.get.port == Port(20302),
|
||||
)
|
||||
check discoveredFiltered.len == 1 and
|
||||
discoveredFiltered.contains(targetNode.localNode)
|
||||
let discoveredEmpty = lookupNode.randomNodes(
|
||||
10,
|
||||
proc(n: Node): bool =
|
||||
n.address.get.port == Port(20305),
|
||||
)
|
||||
check discoveredEmpty.len == 0
|
||||
|
||||
await lookupNode.closeWait()
|
||||
@ -441,22 +527,41 @@ suite "Discovery v5 Tests":
|
||||
await otherNode.closeWait()
|
||||
await anotherNode.closeWait()
|
||||
|
||||
|
||||
test "New protocol with spr":
|
||||
let
|
||||
privKey = PrivateKey.example(rng)
|
||||
ip = some(parseIpAddress("127.0.0.1"))
|
||||
port = Port(20301)
|
||||
node = newProtocol(privKey, ip, some(port), some(port), bindPort = port,
|
||||
rng = rng)
|
||||
noUpdatesNode = newProtocol(privKey, ip, some(port), some(port),
|
||||
bindPort = port, rng = rng, previousRecord = some(node.getRecord()))
|
||||
updatesNode = newProtocol(privKey, ip, some(port), some(Port(20302)),
|
||||
bindPort = port, rng = rng,
|
||||
previousRecord = some(noUpdatesNode.getRecord()))
|
||||
moreUpdatesNode = newProtocol(privKey, ip, some(port), some(port),
|
||||
bindPort = port, rng = rng, localEnrFields = {"addfield": @[byte 0]},
|
||||
previousRecord = some(updatesNode.getRecord()))
|
||||
node =
|
||||
newProtocol(privKey, ip, some(port), some(port), bindPort = port, rng = rng)
|
||||
noUpdatesNode = newProtocol(
|
||||
privKey,
|
||||
ip,
|
||||
some(port),
|
||||
some(port),
|
||||
bindPort = port,
|
||||
rng = rng,
|
||||
previousRecord = some(node.getRecord()),
|
||||
)
|
||||
updatesNode = newProtocol(
|
||||
privKey,
|
||||
ip,
|
||||
some(port),
|
||||
some(Port(20302)),
|
||||
bindPort = port,
|
||||
rng = rng,
|
||||
previousRecord = some(noUpdatesNode.getRecord()),
|
||||
)
|
||||
moreUpdatesNode = newProtocol(
|
||||
privKey,
|
||||
ip,
|
||||
some(port),
|
||||
some(port),
|
||||
bindPort = port,
|
||||
rng = rng,
|
||||
localEnrFields = {"addfield": @[byte 0]},
|
||||
previousRecord = some(updatesNode.getRecord()),
|
||||
)
|
||||
check:
|
||||
node.getRecord().seqNum == 1
|
||||
noUpdatesNode.getRecord().seqNum == 1
|
||||
@ -465,16 +570,20 @@ suite "Discovery v5 Tests":
|
||||
|
||||
# Defect (for now?) on incorrect key use
|
||||
expect ResultDefect:
|
||||
let incorrectKeyUpdates = newProtocol(PrivateKey.example(rng),
|
||||
ip, some(port), some(port), bindPort = port, rng = rng,
|
||||
previousRecord = some(updatesNode.getRecord()))
|
||||
let incorrectKeyUpdates = newProtocol(
|
||||
PrivateKey.example(rng),
|
||||
ip,
|
||||
some(port),
|
||||
some(port),
|
||||
bindPort = port,
|
||||
rng = rng,
|
||||
previousRecord = some(updatesNode.getRecord()),
|
||||
)
|
||||
|
||||
test "Update node record with revalidate":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
testNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
testNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
testNodeId = testNode.localNode.id
|
||||
|
||||
check:
|
||||
@ -505,14 +614,13 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Update node record with handshake":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
testNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
mainNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301))
|
||||
testNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
testNodeId = testNode.localNode.id
|
||||
|
||||
# Add the node (from the record, so new node!) so no handshake is done yet.
|
||||
check: mainNode.addNode(testNode.localNode.record)
|
||||
check:
|
||||
mainNode.addNode(testNode.localNode.record)
|
||||
|
||||
check:
|
||||
testNode.updateRecord().isOk()
|
||||
@ -539,9 +647,13 @@ suite "Discovery v5 Tests":
|
||||
test "Verify records of nodes message":
|
||||
let
|
||||
port = Port(9000)
|
||||
fromNoderecord = SignedPeerRecord.init(1, PrivateKey.example(rng),
|
||||
fromNoderecord = SignedPeerRecord.init(
|
||||
1,
|
||||
PrivateKey.example(rng),
|
||||
some(parseIpAddress("11.12.13.14")),
|
||||
some(port), some(port))[]
|
||||
some(port),
|
||||
some(port),
|
||||
)[]
|
||||
fromNode = newNode(fromNoderecord)[]
|
||||
privKey = PrivateKey.example(rng)
|
||||
pubKey = privKey.getPublicKey.expect("Valid private key for public key")
|
||||
@ -550,10 +662,9 @@ suite "Discovery v5 Tests":
|
||||
limit = 16
|
||||
|
||||
block: # Duplicates
|
||||
let
|
||||
record = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
let record = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")), some(port), some(port)
|
||||
)[]
|
||||
|
||||
# Exact duplicates
|
||||
var records = @[record, record]
|
||||
@ -562,16 +673,16 @@ suite "Discovery v5 Tests":
|
||||
|
||||
# Node id duplicates
|
||||
let recordSameId = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("212.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
1, privKey, some(parseIpAddress("212.13.14.15")), some(port), some(port)
|
||||
)[]
|
||||
records.add(recordSameId)
|
||||
nodes = verifyNodesRecords(records, fromNode, limit, targetDistance)
|
||||
check nodes.len == 1
|
||||
|
||||
block: # No address
|
||||
let
|
||||
recordNoAddress = SignedPeerRecord.init(
|
||||
1, privKey, none(IpAddress), some(port), some(port))[]
|
||||
recordNoAddress =
|
||||
SignedPeerRecord.init(1, privKey, none(IpAddress), some(port), some(port))[]
|
||||
records = [recordNoAddress]
|
||||
test = verifyNodesRecords(records, fromNode, limit, targetDistance)
|
||||
check test.len == 0
|
||||
@ -579,8 +690,8 @@ suite "Discovery v5 Tests":
|
||||
block: # Invalid address - site local
|
||||
let
|
||||
recordInvalidAddress = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("10.1.2.3")),
|
||||
some(port), some(port))[]
|
||||
1, privKey, some(parseIpAddress("10.1.2.3")), some(port), some(port)
|
||||
)[]
|
||||
records = [recordInvalidAddress]
|
||||
test = verifyNodesRecords(records, fromNode, limit, targetDistance)
|
||||
check test.len == 0
|
||||
@ -588,8 +699,8 @@ suite "Discovery v5 Tests":
|
||||
block: # Invalid address - loopback
|
||||
let
|
||||
recordInvalidAddress = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("127.0.0.1")),
|
||||
some(port), some(port))[]
|
||||
1, privKey, some(parseIpAddress("127.0.0.1")), some(port), some(port)
|
||||
)[]
|
||||
records = [recordInvalidAddress]
|
||||
test = verifyNodesRecords(records, fromNode, limit, targetDistance)
|
||||
check test.len == 0
|
||||
@ -597,8 +708,8 @@ suite "Discovery v5 Tests":
|
||||
block: # Invalid distance
|
||||
let
|
||||
recordInvalidDistance = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")), some(port), some(port)
|
||||
)[]
|
||||
records = [recordInvalidDistance]
|
||||
test = verifyNodesRecords(records, fromNode, limit, @[0'u16])
|
||||
check test.len == 0
|
||||
@ -606,8 +717,8 @@ suite "Discovery v5 Tests":
|
||||
block: # Invalid distance but distance validation is disabled
|
||||
let
|
||||
recordInvalidDistance = SignedPeerRecord.init(
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
1, privKey, some(parseIpAddress("12.13.14.15")), some(port), some(port)
|
||||
)[]
|
||||
records = [recordInvalidDistance]
|
||||
test = verifyNodesRecords(records, fromNode, limit)
|
||||
check test.len == 1
|
||||
@ -623,8 +734,8 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Handshake cleanup: different ids":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
let receiveNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
|
||||
# Create random packets with same ip but different node ids
|
||||
# and "receive" them on receiveNode
|
||||
@ -632,14 +743,22 @@ suite "Discovery v5 Tests":
|
||||
for i in 0 ..< 5:
|
||||
let
|
||||
privKey = PrivateKey.example(rng)
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(parseIpAddress("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
enrRec = SignedPeerRecord
|
||||
.init(
|
||||
1,
|
||||
privKey,
|
||||
some(parseIpAddress("127.0.0.1")),
|
||||
some(Port(9000)),
|
||||
some(Port(9000)),
|
||||
)
|
||||
.expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
|
||||
var codec =
|
||||
Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
|
||||
|
||||
let (packet, _, _) = encodeMessagePacket(rng[], codec,
|
||||
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
|
||||
let (packet, _, _) = encodeMessagePacket(
|
||||
rng[], codec, receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]
|
||||
)
|
||||
receiveNode.transport.receive(a, packet)
|
||||
|
||||
# Checking different nodeIds but same address
|
||||
@ -654,22 +773,29 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Handshake cleanup: different ips":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
let receiveNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
|
||||
# Create random packets with same node ids but different ips
|
||||
# and "receive" them on receiveNode
|
||||
let
|
||||
privKey = PrivateKey.example(rng)
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(parseIpAddress("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
enrRec = SignedPeerRecord
|
||||
.init(
|
||||
1,
|
||||
privKey,
|
||||
some(parseIpAddress("127.0.0.1")),
|
||||
some(Port(9000)),
|
||||
some(Port(9000)),
|
||||
)
|
||||
.expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
|
||||
for i in 0 ..< 5:
|
||||
let a = localAddress(20303 + i)
|
||||
let (packet, _, _) = encodeMessagePacket(rng[], codec,
|
||||
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
|
||||
let (packet, _, _) = encodeMessagePacket(
|
||||
rng[], codec, receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]
|
||||
)
|
||||
receiveNode.transport.receive(a, packet)
|
||||
|
||||
# Checking different nodeIds but same address
|
||||
@ -684,24 +810,31 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Handshake duplicates":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
let receiveNode =
|
||||
initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
|
||||
# Create random packets with same node ids and same ips
|
||||
# and "receive" them on receiveNode
|
||||
let
|
||||
a = localAddress(20303)
|
||||
privKey = PrivateKey.example(rng)
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(parseIpAddress("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
enrRec = SignedPeerRecord
|
||||
.init(
|
||||
1,
|
||||
privKey,
|
||||
some(parseIpAddress("127.0.0.1")),
|
||||
some(Port(9000)),
|
||||
some(Port(9000)),
|
||||
)
|
||||
.expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
var codec = Codec(localNode: sendNode, privKey: privKey, sessions: Sessions.init(5))
|
||||
|
||||
var firstRequestNonce: AESGCMNonce
|
||||
for i in 0 ..< 5:
|
||||
let (packet, requestNonce, _) = encodeMessagePacket(rng[], codec,
|
||||
receiveNode.localNode.id, receiveNode.localNode.address.get(), @[])
|
||||
let (packet, requestNonce, _) = encodeMessagePacket(
|
||||
rng[], codec, receiveNode.localNode.id, receiveNode.localNode.address.get(), @[]
|
||||
)
|
||||
receiveNode.transport.receive(a, packet)
|
||||
if i == 0:
|
||||
firstRequestNonce = requestNonce
|
||||
@ -717,12 +850,10 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Talkreq no protocol":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20303))
|
||||
talkresp = await discv5_protocol.talkReq(node1, node2.localNode,
|
||||
@[byte 0x01], @[])
|
||||
node1 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20303))
|
||||
talkresp =
|
||||
await discv5_protocol.talkReq(node1, node2.localNode, @[byte 0x01], @[])
|
||||
|
||||
check:
|
||||
talkresp.isOk()
|
||||
@ -733,21 +864,24 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Talkreq echo protocol":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20303))
|
||||
node1 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20303))
|
||||
talkProtocol = "echo".toBytes()
|
||||
|
||||
proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte]
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
proc handler(
|
||||
protocol: TalkProtocol,
|
||||
request: seq[byte],
|
||||
fromId: NodeId,
|
||||
fromUdpAddress: Address,
|
||||
): seq[byte] {.gcsafe, raises: [Defect].} =
|
||||
request
|
||||
|
||||
let echoProtocol = TalkProtocol(protocolHandler: handler)
|
||||
|
||||
check node2.registerTalkProtocol(talkProtocol, echoProtocol).isOk()
|
||||
let talkresp = await discv5_protocol.talkReq(node1, node2.localNode,
|
||||
talkProtocol, "hello".toBytes())
|
||||
let talkresp = await discv5_protocol.talkReq(
|
||||
node1, node2.localNode, talkProtocol, "hello".toBytes()
|
||||
)
|
||||
|
||||
check:
|
||||
talkresp.isOk()
|
||||
@ -758,14 +892,16 @@ suite "Discovery v5 Tests":
|
||||
|
||||
test "Talkreq register protocols":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.example(rng), localAddress(20303))
|
||||
node1 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302))
|
||||
node2 = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20303))
|
||||
talkProtocol = "echo".toBytes()
|
||||
|
||||
proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte]
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
proc handler(
|
||||
protocol: TalkProtocol,
|
||||
request: seq[byte],
|
||||
fromId: NodeId,
|
||||
fromUdpAddress: Address,
|
||||
): seq[byte] {.gcsafe, raises: [Defect].} =
|
||||
request
|
||||
|
||||
let echoProtocol = TalkProtocol(protocolHandler: handler)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user