# Nimbus - Portal Network # Copyright (c) 2021-2023 Status Research & Development GmbH # Licensed and distributed under either of # * 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). # at your option. This file may not be copied, modified, or distributed except according to those terms. {.used.} import std/[algorithm, sequtils], chronos, testutils/unittests, stew/shims/net, stew/results, eth/keys, eth/p2p/discoveryv5/routing_table, nimcrypto/[hash, sha2], eth/p2p/discoveryv5/protocol as discv5_protocol, ../network/wire/[portal_protocol, portal_stream, portal_protocol_config], ../content_db, ./test_helpers const protocolId = [byte 0x50, 0x00] proc toContentId(contentKey: ByteList): results.Opt[ContentId] = # Note: Returning sha256 digest as content id here. This content key to # content id derivation is different for the different content networks # and their content types. let idHash = sha256.digest(contentKey.asSeq()) ok(readUintBE[256](idHash.data)) proc initPortalProtocol( rng: ref HmacDrbgContext, privKey: PrivateKey, address: Address, bootstrapRecords: openArray[Record] = []): PortalProtocol = let d = initDiscoveryNode(rng, privKey, address, bootstrapRecords) db = ContentDB.new("", uint32.high, inMemory = true) manager = StreamManager.new(d) q = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50) stream = manager.registerNewStream(q) proto = PortalProtocol.new( d, protocolId, toContentId, createGetHandler(db), stream, bootstrapRecords = bootstrapRecords) proto.dbPut = createStoreHandler(db, defaultRadiusConfig, proto) return proto proc stopPortalProtocol(proto: PortalProtocol) {.async.} = proto.stop() await proto.baseProtocol.closeWait() proc defaultTestSetup(rng: ref HmacDrbgContext): (PortalProtocol, PortalProtocol) = let proto1 = initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20302)) proto2 = initPortalProtocol(rng, PrivateKey.random(rng[]), localAddress(20303)) (proto1, proto2) procSuite "Portal Wire Protocol Tests": let rng = newRng() asyncTest "Ping/Pong": let (proto1, proto2) = defaultTestSetup(rng) let pong = await proto1.ping(proto2.localNode) let customPayload = ByteList(SSZ.encode(CustomPayload(dataRadius: UInt256.high()))) check: pong.isOk() pong.get().enrSeq == 1'u64 pong.get().customPayload == customPayload await proto1.stopPortalProtocol() await proto2.stopPortalProtocol() asyncTest "FindNodes/Nodes": let (proto1, proto2) = defaultTestSetup(rng) block: # Find itself let nodes = await proto1.findNodesImpl(proto2.localNode, List[uint16, 256](@[0'u16])) check: nodes.isOk() nodes.get().total == 1'u8 nodes.get().enrs.len() == 1 block: # Find nothing: this should result in nothing as we haven't started # the seeding of the portal protocol routing table yet. let nodes = await proto1.findNodesImpl(proto2.localNode, List[uint16, 256](@[])) check: nodes.isOk() nodes.get().total == 1'u8 nodes.get().enrs.len() == 0 block: # Find for distance # 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 # with bootstrap node instead. check (await proto1.baseProtocol.ping(proto2.localNode)).isOk() check (await proto2.baseProtocol.ping(proto1.localNode)).isOk() # Start the portal protocol to seed nodes from the discoveryv5 routing # table. proto2.start() let distance = logDistance(proto1.localNode.id, proto2.localNode.id) let nodes = await proto1.findNodesImpl(proto2.localNode, List[uint16, 256](@[distance])) check: nodes.isOk() nodes.get().total == 1'u8 nodes.get().enrs.len() == 1 await proto1.stopPortalProtocol() await proto2.stopPortalProtocol() asyncTest "FindContent/Content - send enrs": let (proto1, proto2) = defaultTestSetup(rng) # ping in one direction to add, ping in the other to update as seen. check (await proto1.baseProtocol.ping(proto2.localNode)).isOk() check (await proto2.baseProtocol.ping(proto1.localNode)).isOk() let contentKey = ByteList.init(@[1'u8]) # 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. let content = await proto1.findContentImpl(proto2.localNode, contentKey) check: content.isOk() content.get().enrs.len() == 1 await proto1.stopPortalProtocol() await proto2.stopPortalProtocol() asyncTest "Offer/Accept": let (proto1, proto2) = defaultTestSetup(rng) let contentKeys = ContentKeysList(@[ByteList(@[byte 0x01, 0x02, 0x03])]) let accept = await proto1.offerImpl( proto2.baseProtocol.localNode, contentKeys) check: accept.isOk() accept.get().connectionId.len == 2 accept.get().contentKeys.len == contentKeys.len await proto1.stopPortalProtocol() await proto2.stopPortalProtocol() asyncTest "Offer/Accept/Stream": let (proto1, proto2) = defaultTestSetup(rng) var content: seq[ContentKV] for i in 0..= distances[3] proto1.stop() await node1.closeWait()