# Nimbus # Copyright (c) 2021 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. import std/sequtils, unittest2, testutils, confutils, chronos, eth/p2p/discoveryv5/random2, eth/keys, ../../nimbus/rpc/[hexstrings, rpc_types], ../rpc/portal_rpc_client, ../rpc/eth_rpc_client, ../populate_db type PortalTestnetConf* = object nodeCount* {. defaultValue: 17 desc: "Number of nodes to test" name: "node-count" .}: int rpcAddress* {. desc: "Listening address of the JSON-RPC service for all nodes" defaultValue: "127.0.0.1" name: "rpc-address" }: string baseRpcPort* {. defaultValue: 7000 desc: "Port of the JSON-RPC service of the bootstrap (first) node" name: "base-rpc-port" .}: uint16 proc connectToRpcServers(config: PortalTestnetConf): Future[seq[RpcClient]] {.async.} = var clients: seq[RpcClient] for i in 0..= (min(config.nodeCount - 1, 16)) # grab a random node its `NodeInfo` and lookup that node from all nodes. let randomNodeInfo = sample(rng[], nodeInfos) for client in clients: var enr: Record enr = await client.discv5_lookupEnr(randomNodeInfo.nodeId) check enr == randomNodeInfo.nodeENR await client.close() asyncTest "Portal State - Random node lookup from each node": let clients = await connectToRpcServers(config) var nodeInfos: seq[NodeInfo] for client in clients: let nodeInfo = await client.portal_state_nodeInfo() await client.close() nodeInfos.add(nodeInfo) for client in clients: discard await client.portal_state_addEnrs(nodeInfos.map( proc(x: NodeInfo): Record = x.nodeENR)) await client.close() for client in clients: let routingTableInfo = await client.portal_state_routingTableInfo() await client.close() var start: seq[NodeId] let nodes = foldl(routingTableInfo.buckets, a & b, start) check nodes.len >= (min(config.nodeCount - 1, 16)) # grab a random node its `NodeInfo` and lookup that node from all nodes. let randomNodeInfo = sample(rng[], nodeInfos) for client in clients: var enr: Record try: enr = await client.portal_state_lookupEnr(randomNodeInfo.nodeId) except CatchableError as e: echo e.msg # TODO: For state network this occasionally fails. It might be because the # distance function is not used in all locations, or perhaps it just # doesn't converge to the target always with this distance function. To be # further investigated. skip() # check enr == randomNodeInfo.nodeENR await client.close() asyncTest "Portal History - Random node lookup from each node": let clients = await connectToRpcServers(config) var nodeInfos: seq[NodeInfo] for client in clients: let nodeInfo = await client.portal_history_nodeInfo() await client.close() nodeInfos.add(nodeInfo) for client in clients: discard await client.portal_history_addEnrs(nodeInfos.map( proc(x: NodeInfo): Record = x.nodeENR)) await client.close() for client in clients: let routingTableInfo = await client.portal_history_routingTableInfo() await client.close() var start: seq[NodeId] let nodes = foldl(routingTableInfo.buckets, a & b, start) check nodes.len >= (min(config.nodeCount - 1, 16)) # grab a random node its `NodeInfo` and lookup that node from all nodes. let randomNodeInfo = sample(rng[], nodeInfos) for client in clients: var enr: Record enr = await client.portal_history_lookupEnr(randomNodeInfo.nodeId) await client.close() check enr == randomNodeInfo.nodeENR asyncTest "Portal History - Propagate blocks and do content lookups": let clients = await connectToRpcServers(config) var nodeInfos: seq[NodeInfo] for client in clients: let nodeInfo = await client.portal_history_nodeInfo() await client.close() nodeInfos.add(nodeInfo) const dataFile = "./fluffy/scripts/test_data/mainnet_blocks_selected.json" # This will fill the first node its db with blocks from the data file. Next, # this node wil offer all these blocks their headers one by one. check (await clients[0].portal_history_propagate(dataFile)) await clients[0].close() # Note: Sleeping to make a test work is never great. Here it is needed # because the data needs to propagate over the nodes. What one could do is # add a json-rpc debug proc that returns whether the offer queue is empty or # not. And then poll every node until all nodes have an empty queue. await sleepAsync(60.seconds) let blockData = readBlockDataTable(dataFile) check blockData.isOk() for client in clients: # Note: Once there is the Canonical Indices Network, we don't need to # access this file anymore here for the block hashes. for hash in blockData.get().blockHashes(): let content = await client.eth_getBlockByHash( hash.ethHashStr(), false) check content.isSome() let blockObj = content.get() check blockObj.hash.get() == hash for tx in blockObj.transactions: var txObj: TransactionObject tx.fromJson("tx", txObj) check txObj.blockHash.get() == hash let filterOptions = FilterOptions( blockHash: some(hash) ) let logs = await client.eth_getLogs(filterOptions) for l in logs: check: l.blockHash == some(hash) # TODO: Check ommersHash, need the headers and not just the hashes # for uncle in blockObj.uncles: # discard await client.close()