Refactor and clean-up of Portal wire protocol test (#1089)

This commit is contained in:
Kim De Mey 2022-05-16 13:17:42 +02:00 committed by GitHub
parent 62d31d6f1d
commit 56c3e31efc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,5 +1,5 @@
# Nimbus - Portal Network # Nimbus - Portal Network
# Copyright (c) 2021 Status Research & Development GmbH # Copyright (c) 2021-2022 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -18,76 +18,79 @@ import
const protocolId = [byte 0x50, 0x00] const protocolId = [byte 0x50, 0x00]
type Default2NodeTest = ref object proc toContentId(contentKey: ByteList): Option[ContentId] =
node1: discv5_protocol.Protocol # Note: Returning sha256 digest as content id here. This content key to
node2: discv5_protocol.Protocol # content id derivation is different for the different content networks
proto1: PortalProtocol # and their content types.
proto2: PortalProtocol
proc testHandler(contentKey: ByteList): Option[ContentId] =
# Note: Returning a static content id here, as in practice this depends
# on the content key to content id derivation, which is different for the
# different content networks. And we want these tests to be independent from
# that.
let idHash = sha256.digest("test")
some(readUintBE[256](idHash.data))
proc testHandlerSha256(contentKey: ByteList): Option[ContentId] =
# Note: Returning a static content id here, as in practice this depends
# on the content key to content id derivation, which is different for the
# different content networks. And we want these tests to be independent from
# that.
let idHash = sha256.digest(contentKey.asSeq()) let idHash = sha256.digest(contentKey.asSeq())
some(readUintBE[256](idHash.data)) some(readUintBE[256](idHash.data))
proc validateContent(content: openArray[byte], contentKey: ByteList): bool = proc validateContent(content: openArray[byte], contentKey: ByteList): bool =
true true
proc defaultTestCase(rng: ref BrHmacDrbgContext): Default2NodeTest = proc initPortalProtocol(
rng: ref BrHmacDrbgContext,
privKey: PrivateKey,
address: Address,
bootstrapRecords: openArray[Record] = []): PortalProtocol =
let let
node1 = initDiscoveryNode( d = initDiscoveryNode(rng, privKey, address, bootstrapRecords)
rng, PrivateKey.random(rng[]), localAddress(20302)) db = ContentDB.new("", uint32.high, inMemory = true)
node2 = initDiscoveryNode( proto = PortalProtocol.new(
rng, PrivateKey.random(rng[]), localAddress(20303)) d, protocolId, db, toContentId, validateContent,
bootstrapRecords = bootstrapRecords)
db1 = ContentDB.new("", uint32.high, inMemory = true) socketConfig = SocketConfig.init(
db2 = ContentDB.new("", uint32.high, inMemory = true) incomingSocketReceiveTimeout = none(Duration),
payloadSize = uint32(maxUtpPayloadSize))
streamTransport = UtpDiscv5Protocol.new(
d, utpProtocolId,
registerIncomingSocketCallback(@[proto.stream]),
allowRegisteredIdCallback(@[proto.stream]),
socketConfig)
proto.stream.setTransport(streamTransport)
return proto
proc stopPortalProtocol(proto: PortalProtocol) {.async.} =
proto.stop()
await proto.baseProtocol.closeWait()
proc defaultTestSetup(rng: ref BrHmacDrbgContext):
(PortalProtocol, PortalProtocol) =
let
proto1 = proto1 =
PortalProtocol.new(node1, protocolId, db1, testHandler, validateContent) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20302))
proto2 = proto2 =
PortalProtocol.new(node2, protocolId, db2, testHandler, validateContent) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20303))
Default2NodeTest(node1: node1, node2: node2, proto1: proto1, proto2: proto2) (proto1, proto2)
proc stopTest(test: Default2NodeTest) {.async.} =
test.proto1.stop()
test.proto2.stop()
await test.node1.closeWait()
await test.node2.closeWait()
procSuite "Portal Wire Protocol Tests": procSuite "Portal Wire Protocol Tests":
let rng = newRng() let rng = newRng()
asyncTest "Ping/Pong": asyncTest "Ping/Pong":
let test = defaultTestCase(rng) let (proto1, proto2) = defaultTestSetup(rng)
let pong = await test.proto1.ping(test.proto2.localNode) let pong = await proto1.ping(proto2.localNode)
let customPayload = ByteList(SSZ.encode(CustomPayload(dataRadius: UInt256.high()))) let customPayload =
ByteList(SSZ.encode(CustomPayload(dataRadius: UInt256.high())))
check: check:
pong.isOk() pong.isOk()
pong.get().enrSeq == 1'u64 pong.get().enrSeq == 1'u64
pong.get().customPayload == customPayload pong.get().customPayload == customPayload
await test.stopTest() await proto1.stopPortalProtocol()
await proto2.stopPortalProtocol()
asyncTest "FindNodes/Nodes": asyncTest "FindNodes/Nodes":
let test = defaultTestCase(rng) let (proto1, proto2) = defaultTestSetup(rng)
block: # Find itself block: # Find itself
let nodes = await test.proto1.findNodesImpl(test.proto2.localNode, let nodes = await proto1.findNodesImpl(proto2.localNode,
List[uint16, 256](@[0'u16])) List[uint16, 256](@[0'u16]))
check: check:
@ -97,7 +100,7 @@ procSuite "Portal Wire Protocol Tests":
block: # Find nothing: this should result in nothing as we haven't started block: # Find nothing: this should result in nothing as we haven't started
# the seeding of the portal protocol routing table yet. # the seeding of the portal protocol routing table yet.
let nodes = await test.proto1.findNodesImpl(test.proto2.localNode, let nodes = await proto1.findNodesImpl(proto2.localNode,
List[uint16, 256](@[])) List[uint16, 256](@[]))
check: check:
@ -109,15 +112,15 @@ procSuite "Portal Wire Protocol Tests":
# ping in one direction to add, ping in the other to update as seen, # ping in one direction to add, ping in the other to update as seen,
# adding the node in the discovery v5 routing table. Could also launch # adding the node in the discovery v5 routing table. Could also launch
# with bootstrap node instead. # with bootstrap node instead.
check (await test.node1.ping(test.node2.localNode)).isOk() check (await proto1.baseProtocol.ping(proto2.localNode)).isOk()
check (await test.node2.ping(test.node1.localNode)).isOk() check (await proto2.baseProtocol.ping(proto1.localNode)).isOk()
# Start the portal protocol to seed nodes from the discoveryv5 routing # Start the portal protocol to seed nodes from the discoveryv5 routing
# table. # table.
test.proto2.start() proto2.start()
let distance = logDistance(test.node1.localNode.id, test.node2.localNode.id) let distance = logDistance(proto1.localNode.id, proto2.localNode.id)
let nodes = await test.proto1.findNodesImpl(test.proto2.localNode, let nodes = await proto1.findNodesImpl(proto2.localNode,
List[uint16, 256](@[distance])) List[uint16, 256](@[distance]))
check: check:
@ -125,152 +128,135 @@ procSuite "Portal Wire Protocol Tests":
nodes.get().total == 1'u8 nodes.get().total == 1'u8
nodes.get().enrs.len() == 1 nodes.get().enrs.len() == 1
await test.stopTest() await proto1.stopPortalProtocol()
await proto2.stopPortalProtocol()
asyncTest "FindContent/Content - send enrs": asyncTest "FindContent/Content - send enrs":
let test = defaultTestCase(rng) let (proto1, proto2) = defaultTestSetup(rng)
# ping in one direction to add, ping in the other to update as seen. # ping in one direction to add, ping in the other to update as seen.
check (await test.node1.ping(test.node2.localNode)).isOk() check (await proto1.baseProtocol.ping(proto2.localNode)).isOk()
check (await test.node2.ping(test.node1.localNode)).isOk() check (await proto2.baseProtocol.ping(proto1.localNode)).isOk()
# Start the portal protocol to seed nodes from the discoveryv5 routing # Start the portal protocol to seed nodes from the discoveryv5 routing
# table. # table.
test.proto2.start() proto2.start()
let contentKey = ByteList.init(@[1'u8]) let contentKey = ByteList.init(@[1'u8])
# content does not exist so this should provide us with the closest nodes # content does not exist so this should provide us with the closest nodes
# to the content, which is the only node in the routing table. # to the content, which is the only node in the routing table.
let content = await test.proto1.findContentImpl(test.proto2.localNode, let content = await proto1.findContentImpl(proto2.localNode, contentKey)
contentKey)
check: check:
content.isOk() content.isOk()
content.get().enrs.len() == 1 content.get().enrs.len() == 1
await test.stopTest() await proto1.stopPortalProtocol()
await proto2.stopPortalProtocol()
asyncTest "Offer/Accept": asyncTest "Offer/Accept":
let test = defaultTestCase(rng) let (proto1, proto2) = defaultTestSetup(rng)
let contentKeys = ContentKeysList(List(@[ByteList(@[byte 0x01, 0x02, 0x03])])) let contentKeys = ContentKeysList(@[ByteList(@[byte 0x01, 0x02, 0x03])])
let accept = await test.proto1.offerImpl( let accept = await proto1.offerImpl(
test.proto2.baseProtocol.localNode, contentKeys) proto2.baseProtocol.localNode, contentKeys)
check: check:
accept.isOk() accept.isOk()
accept.get().connectionId.len == 2 accept.get().connectionId.len == 2
accept.get().contentKeys.len == contentKeys.len accept.get().contentKeys.len == contentKeys.len
await test.stopTest() await proto1.stopPortalProtocol()
await proto2.stopPortalProtocol()
asyncTest "Correctly mark node as seen after request": asyncTest "Correctly mark node as seen after request":
let test = defaultTestCase(rng) let (proto1, proto2) = defaultTestSetup(rng)
let initialNeighbours = test.proto1.neighbours(test.proto1.localNode.id, let initialNeighbours = proto1.neighbours(proto1.localNode.id,
seenOnly = false) seenOnly = false)
check: check:
len(initialNeighbours) == 0 len(initialNeighbours) == 0
discard test.proto1.addNode(test.proto2.baseProtocol.localNode) discard proto1.addNode(proto2.localNode)
let allNeighboursAfterAdd = test.proto1.neighbours( let allNeighboursAfterAdd = proto1.neighbours(
test.proto1.localNode.id, seenOnly = false) proto1.localNode.id, seenOnly = false)
let seenNeighboursAfterAdd = test.proto1.neighbours( let seenNeighboursAfterAdd = proto1.neighbours(
test.proto1.localNode.id, seenOnly = true) proto1.localNode.id, seenOnly = true)
check: check:
len(allNeighboursAfterAdd) == 1 len(allNeighboursAfterAdd) == 1
len(seenNeighboursAfterAdd) == 0 len(seenNeighboursAfterAdd) == 0
let pong = await test.proto1.ping(test.proto2.baseProtocol.localNode) let pong = await proto1.ping(proto2.localNode)
let allNeighboursAfterPing = test.proto1.neighbours( let allNeighboursAfterPing = proto1.neighbours(
test.proto1.localNode.id, seenOnly = false) proto1.localNode.id, seenOnly = false)
let seenNeighboursAfterPing = test.proto1.neighbours( let seenNeighboursAfterPing = proto1.neighbours(
test.proto1.localNode.id, seenOnly = true) proto1.localNode.id, seenOnly = true)
check: check:
pong.isOk() pong.isOk()
len(allNeighboursAfterPing) == 1 len(allNeighboursAfterPing) == 1
len(seenNeighboursAfterPing) == 1 len(seenNeighboursAfterPing) == 1
await test.stopTest() await proto1.stopPortalProtocol()
await proto2.stopPortalProtocol()
asyncTest "Lookup nodes": asyncTest "Lookup nodes":
let let
node1 = initDiscoveryNode( node1 =
rng, PrivateKey.random(rng[]), localAddress(20302)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20302))
node2 = initDiscoveryNode( node2 =
rng, PrivateKey.random(rng[]), localAddress(20303)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20303))
node3 = initDiscoveryNode( node3 =
rng, PrivateKey.random(rng[]), localAddress(20304)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20304))
db1 = ContentDB.new("", uint32.high, inMemory = true) # Make node1 know about node2, and node2 about node3
db2 = ContentDB.new("", uint32.high, inMemory = true) # node1 will then do a lookup for node3
db3 = ContentDB.new("", uint32.high, inMemory = true) check node1.addNode(node2.localNode) == Added
check node2.addNode(node3.localNode) == Added
proto1 = PortalProtocol.new( check (await node2.ping(node3.localNode)).isOk()
node1, protocolId, db1, testHandler, validateContent)
proto2 = PortalProtocol.new(
node2, protocolId, db2, testHandler, validateContent)
proto3 = PortalProtocol.new(
node3, protocolId, db3, testHandler, validateContent)
# Node1 knows about Node2, and Node2 knows about Node3 which hold all content let lookupResult = await node1.lookup(node3.localNode.id)
check proto1.addNode(node2.localNode) == Added
check proto2.addNode(node3.localNode) == Added
check (await proto2.ping(node3.localNode)).isOk()
let lookuResult = await proto1.lookup(node3.localNode.id)
check: check:
# returned result should contain node3 as it is in node2 routing table # Result should contain node3 as it is in the routing table of node2
lookuResult.contains(node3.localNode) lookupResult.contains(node3.localNode)
await node1.closeWait() await node1.stopPortalProtocol()
await node2.closeWait() await node2.stopPortalProtocol()
await node3.closeWait() await node3.stopPortalProtocol()
asyncTest "Content lookup should return info about nodes interested in content": asyncTest "Lookup content - nodes interested":
let let
node1 = initDiscoveryNode( node1 =
rng, PrivateKey.random(rng[]), localAddress(20302)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20302))
node2 = initDiscoveryNode( node2 =
rng, PrivateKey.random(rng[]), localAddress(20303)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20303))
node3 = initDiscoveryNode( node3 =
rng, PrivateKey.random(rng[]), localAddress(20304)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20304))
db1 = ContentDB.new("", uint32.high, inMemory = true)
db2 = ContentDB.new("", uint32.high, inMemory = true)
db3 = ContentDB.new("", uint32.high, inMemory = true)
proto1 = PortalProtocol.new(
node1, protocolId, db1, testHandlerSha256, validateContent)
proto2 = PortalProtocol.new(
node2, protocolId, db2, testHandlerSha256, validateContent)
proto3 = PortalProtocol.new(
node3, protocolId, db3, testHandlerSha256, validateContent)
content = @[byte 1, 2] content = @[byte 1, 2]
contentList = List[byte, 2048].init(content) contentList = List[byte, 2048].init(content)
contentId = readUintBE[256](sha256.digest(content).data) contentId = readUintBE[256](sha256.digest(content).data)
# Only node3 have content # Store the content on node3
discard db3.put(contentId, content, proto3.localNode.id) discard node3.contentDB.put(contentId, content, node3.localNode.id)
# Node1 knows about Node2, and Node2 knows about Node3 which hold all content # Make node1 know about node2, and node2 about node3
# Node1 needs to known Node2 radius to determine if node2 is interested in content check node1.addNode(node2.localNode) == Added
check proto1.addNode(node2.localNode) == Added check node2.addNode(node3.localNode) == Added
check proto2.addNode(node3.localNode) == Added
check (await proto1.ping(node2.localNode)).isOk() # node1 needs to know the radius of the nodes to determine if they are
check (await proto2.ping(node3.localNode)).isOk() # interested in content, so a ping is done.
check (await node1.ping(node2.localNode)).isOk()
check (await node2.ping(node3.localNode)).isOk()
let lookupResult = await proto1.contentLookup(contentList, contentId) let lookupResult = await node1.contentLookup(contentList, contentId)
check: check:
lookupResult.isSome() lookupResult.isSome()
@ -281,91 +267,77 @@ procSuite "Portal Wire Protocol Tests":
res.content == content res.content == content
res.nodesInterestedInContent.contains(node2.localNode) res.nodesInterestedInContent.contains(node2.localNode)
await node1.closeWait() await node1.stopPortalProtocol()
await node2.closeWait() await node2.stopPortalProtocol()
await node3.closeWait() await node3.stopPortalProtocol()
asyncTest "Valid Bootstrap Node": asyncTest "Valid Bootstrap Node":
let let
node1 = initDiscoveryNode( node1 =
rng, PrivateKey.random(rng[]), localAddress(20302)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20302))
node2 = initDiscoveryNode( node2 =
rng, PrivateKey.random(rng[]), localAddress(20303)) initPortalProtocol(
rng, PrivateKey.random(rng[]), localAddress(20303),
db1 = ContentDB.new("", uint32.high, inMemory = true)
db2 = ContentDB.new("", uint32.high, inMemory = true)
proto1 = PortalProtocol.new(
node1, protocolId, db1, testHandler, validateContent)
proto2 = PortalProtocol.new(
node2, protocolId, db2, testHandler, validateContent,
bootstrapRecords = [node1.localNode.record]) bootstrapRecords = [node1.localNode.record])
proto1.start() node1.start()
proto2.start() node2.start()
check proto2.neighbours(proto2.localNode.id).len == 1 check node2.neighbours(node2.localNode.id).len == 1
proto1.stop() await node1.stopPortalProtocol()
proto2.stop() await node2.stopPortalProtocol()
await node1.closeWait()
await node2.closeWait()
asyncTest "Invalid Bootstrap Node": asyncTest "Invalid Bootstrap Node":
let let
node1 = initDiscoveryNode( node1 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20302)) rng, PrivateKey.random(rng[]), localAddress(20302))
node2 = initDiscoveryNode( node2 =
rng, PrivateKey.random(rng[]), localAddress(20303)) initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20303),
bootstrapRecords = [node1.localNode.record])
db = ContentDB.new("", uint32.high, inMemory = true)
# No portal protocol for node1, hence an invalid bootstrap node
proto2 = PortalProtocol.new(node2, protocolId, db, testHandler,
validateContent, bootstrapRecords = [node1.localNode.record])
# seedTable to add node1 to the routing table # seedTable to add node1 to the routing table
proto2.seedTable() node2.seedTable()
check proto2.neighbours(proto2.localNode.id).len == 1 check node2.neighbours(node2.localNode.id).len == 1
# This should fail and drop node1 from the routing table # This should fail and drop node1 from the routing table
await proto2.revalidateNode(node1.localNode) await node2.revalidateNode(node1.localNode)
check proto2.neighbours(proto2.localNode.id).len == 0 check node2.neighbours(node2.localNode.id).len == 0
proto2.stop()
await node1.closeWait() await node1.closeWait()
await node2.closeWait() await node2.stopPortalProtocol()
asyncTest "Adjusting radius after hitting full database": asyncTest "Adjusting radius after hitting full database":
let let
node1 = initDiscoveryNode( node1 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20303)) rng, PrivateKey.random(rng[]), localAddress(20303))
dbLimit = 100000'u32 dbLimit = 100_000'u32
db = ContentDB.new("", dbLimit, inMemory = true) db = ContentDB.new("", dbLimit, inMemory = true)
proto1 = PortalProtocol.new(node1, protocolId, db, testHandler, proto1 = PortalProtocol.new(node1, protocolId, db, toContentId,
validateContent) validateContent)
let item = genByteSeq(10000) let item = genByteSeq(10_000)
var distances: seq[UInt256] = @[] var distances: seq[UInt256] = @[]
for i in 0..8: for i in 0..8:
proto1.storeContent(u256(i), item) proto1.storeContent(u256(i), item)
distances.add(u256(i) xor proto1.localNode.id) distances.add(u256(i) xor proto1.localNode.id)
# With current setting i.e limit 100000bytes and 10000 byte element each
# two furthest elements should be delted i.e index 0 and 1.
# index 2 should be still be in database and it distance should always be
# <= updated radius
distances.sort(order = SortOrder.Descending) distances.sort(order = SortOrder.Descending)
# With the selected db limit of 100_000 bytes and added elements of 10_000
# bytes each, the two furthest elements should be prined, i.e index 0 and 1.
# Index 2 should be still be in database and its distance should be <=
# updated radius
check: check:
db.get((distances[0] xor proto1.localNode.id)).isNone() db.get((distances[0] xor proto1.localNode.id)).isNone()
db.get((distances[1] xor proto1.localNode.id)).isNone() db.get((distances[1] xor proto1.localNode.id)).isNone()
db.get((distances[2] xor proto1.localNode.id)).isSome() db.get((distances[2] xor proto1.localNode.id)).isSome()
# our radius have been updated and is lower than max # The radius has been updated and is lower than the maximum start value.
proto1.dataRadius < UInt256.high proto1.dataRadius < UInt256.high
# but higher or equal to furthest non deleted element # Yet higher than or equal to the furthest non deleted element.
proto1.dataRadius >= distances[2] proto1.dataRadius >= distances[2]
proto1.stop() proto1.stop()