{.used.} import std/unittest, bearssl, ../../eth/keys, ../../eth/p2p/discoveryv5/[routing_table, node, enr], ./discv5_test_helper func customDistance*(a, b: NodeId): Uint256 = if a >= b: a - b else: b - a func customLogDistance*(a, b: NodeId): uint16 = let distance = customDistance(a, b) let modulo = distance mod (u256(uint8.high)) cast[uint16](modulo) func customIdAdDist*(id: NodeId, dist: uint16): NodeId = id + u256(dist) suite "Routing Table Tests": let rng = newRng() # Used for testing. Could also at runtime check whether the address is the # loopback address as these are only allowed to be added when coming from # another loopback nodes, however that check is done in the protocol code and # thus independent of routing_table. let ipLimits = TableIpLimits(tableIpLimit: 200, bucketIpLimit: BUCKET_SIZE + REPLACEMENT_CACHE_SIZE + 1) let customDistanceCalculator = DistanceCalculator( calculateDistance: customDistance, calculateLogDistance: customLogDistance, calculateIdAtDistance: customIdAdDist) test "Add local node": let node = generateNode(PrivateKey.random(rng[])) var table = RoutingTable.init(node, 1, ipLimits, rng = rng) check table.addNode(node) == LocalNode test "Bucket splitting in range branch b=1": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) for j in 0..5'u32: for i in 0.. Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # Add 16 nodes, distance 256 for i in 0.. Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # create a full bucket let bucketNodes = node.nodesAtDistance(rng[], 256, BUCKET_SIZE) for n in bucketNodes: check table.addNode(n) == Added # create a full replacement cache let replacementNodes = node.nodesAtDistance(rng[], 256, REPLACEMENT_CACHE_SIZE) for n in replacementNodes: check table.addNode(n) == ReplacementAdded # Add one more node to replacement (would drop first one) let lastNode = node.nodeAtDistance(rng[], 256) check table.addNode(lastNode) == ReplacementAdded # This should replace the last node in the bucket, with the last one of # the replacement cache. table.replaceNode(table.nodeToRevalidate()) block: # Should return the last node of the replacement cache successfully. let result = table.getNode(lastNode.id) check: result.isSome() result.get() == lastNode block: # This node should be removed check (table.getNode(bucketNodes[bucketNodes.high].id)).isNone() test "Empty bucket": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) check table.nodeToRevalidate().isNil() # try to replace not existing node table.replaceNode(generateNode(PrivateKey.random(rng[]))) check table.len == 0 let addedNode = generateNode(PrivateKey.random(rng[])) check table.addNode(addedNode) == Added check table.len == 1 # try to replace not existing node table.replaceNode(generateNode(PrivateKey.random(rng[]))) check table.len == 1 table.replaceNode(addedNode) check table.len == 0 test "Empty replacement cache": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # create a full bucket TODO: no need to store bucketNodes let bucketNodes = node.nodesAtDistance(rng[], 256, BUCKET_SIZE) for n in bucketNodes: check table.addNode(n) == Added table.replaceNode(table.nodeToRevalidate()) # This node should still be removed check (table.getNode(bucketNodes[bucketNodes.high].id)).isNone() test "Double add": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) let doubleNode = node.nodeAtDistance(rng[], 256) # Try to add the node twice check table.addNode(doubleNode) == Added check table.addNode(doubleNode) == Existing for n in 0.. Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # create a full bucket let bucketNodes = node.nodesAtDistance(rng[], 256, BUCKET_SIZE) for n in bucketNodes: check table.addNode(n) == Added # create a full replacement cache let replacementNodes = node.nodesAtDistance(rng[], 256, REPLACEMENT_CACHE_SIZE) for n in replacementNodes: check table.addNode(n) == ReplacementAdded check table.addNode(replacementNodes[0]) == ReplacementExisting table.replaceNode(table.nodeToRevalidate()) block: # Should return the last node of the replacement cache successfully. let result = table.getNode(replacementNodes[0].id) check: result.isSome() result.get() == replacementNodes[0] block: # This node should be removed check (table.getNode(bucketNodes[bucketNodes.high].id)).isNone() test "Just seen": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # create a full bucket let bucketNodes = node.nodesAtDistance(rng[], 256, BUCKET_SIZE) for n in bucketNodes: check table.addNode(n) == Added # swap seen order for n in bucketNodes: table.setJustSeen(n) for n in bucketNodes: table.replaceNode(table.nodeToRevalidate()) check (table.getNode(n.id)).isNone() test "Just seen replacement": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, ipLimits, rng = rng) # create a full bucket let bucketNodes = node.nodesAtDistance(rng[], 256, BUCKET_SIZE) for n in bucketNodes: check table.addNode(n) == Added # create a full replacement cache let replacementNodes = node.nodesAtDistance(rng[], 256, REPLACEMENT_CACHE_SIZE) for n in replacementNodes: check table.addNode(n) == ReplacementAdded for i in countdown(replacementNodes.high, 0): table.replaceNode(table.nodeToRevalidate()) table.setJustSeen(replacementNodes[i]) for n in replacementNodes: let result = table.getNode(n.id) check: result.isSome() result.get() == n for i in 0.. Split only the branch in range of own id var table = RoutingTable.init(node, 1, DefaultTableIpLimits, rng = rng) block: # First bucket let sameIpNodes = node.nodesAtDistance(rng[], 256, int(DefaultTableIpLimits.bucketIpLimit)) for n in sameIpNodes: check table.addNode(n) == Added # Try to add a node, which should fail due to ip bucket limit let anotherSameIpNode = node.nodeAtDistance(rng[], 256) check table.addNode(anotherSameIpNode) == IpLimitReached # Remove one and try add again table.replaceNode(table.nodeToRevalidate()) check table.addNode(anotherSameIpNode) == Added # Further fill the bucket with nodes with different ip. let diffIpNodes = node.nodesAtDistanceUniqueIp(rng[], 256, int(BUCKET_SIZE - DefaultTableIpLimits.bucketIpLimit), ValidIpAddress.init("192.168.0.1")) for n in diffIpNodes: check table.addNode(n) == Added block: # Second bucket # Try to add another node with the same IP, but different distance. # This should split the bucket and add it. let anotherSameIpNode = node.nodeAtDistance(rng[], 255) check table.addNode(anotherSameIpNode) == Added # Add more nodes with different ip and distance 255 to get in the new bucket let diffIpNodes = node.nodesAtDistanceUniqueIp(rng[], 255, int(BUCKET_SIZE - DefaultTableIpLimits.bucketIpLimit - 1), ValidIpAddress.init("192.168.1.1")) for n in diffIpNodes: check table.addNode(n) == Added let sameIpNodes = node.nodesAtDistance(rng[], 255, int(DefaultTableIpLimits.bucketIpLimit - 1)) for n in sameIpNodes: check table.addNode(n) == Added # Adding in another one should fail again let anotherSameIpNode2 = node.nodeAtDistance(rng[], 255) check table.addNode(anotherSameIpNode2) == IpLimitReached test "Ip limits on routing table": let node = generateNode(PrivateKey.random(rng[])) # bitsPerHop = 1 -> Split only the branch in range of own id var table = RoutingTable.init(node, 1, DefaultTableIpLimits, rng = rng) let amount = uint32(DefaultTableIpLimits.tableIpLimit div DefaultTableIpLimits.bucketIpLimit) # Fill `amount` of buckets, each with 14 nodes with different ips and 2 # with equal ones. for j in 0..= 1 check neighboursAtLogDist.contains(n)