309 lines
9.4 KiB
Nim
309 lines
9.4 KiB
Nim
# Nimbus - Portal Network
|
|
# Copyright (c) 2021-2024 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
|
|
chronos,
|
|
testutils/unittests,
|
|
json_rpc/rpcserver,
|
|
json_rpc/clients/httpclient,
|
|
stint,
|
|
eth/p2p/discoveryv5/enr,
|
|
eth/common/keys,
|
|
eth/common/[headers_rlp, blocks_rlp, receipts_rlp],
|
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
|
../../network/wire/[portal_protocol, portal_stream, portal_protocol_config],
|
|
../../network/history/
|
|
[history_network, history_content, validation/historical_hashes_accumulator],
|
|
../../database/content_db,
|
|
../../rpc/[portal_rpc_client, rpc_portal_history_api],
|
|
../test_helpers
|
|
|
|
from eth/common/eth_types_rlp import rlpHash
|
|
|
|
type HistoryNode = ref object
|
|
discoveryProtocol*: discv5_protocol.Protocol
|
|
historyNetwork*: HistoryNetwork
|
|
|
|
proc newHistoryNode(rng: ref HmacDrbgContext, port: int): HistoryNode =
|
|
let
|
|
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(port))
|
|
db = ContentDB.new(
|
|
"", uint32.high, RadiusConfig(kind: Dynamic), node.localNode.id, inMemory = true
|
|
)
|
|
streamManager = StreamManager.new(node)
|
|
historyNetwork = HistoryNetwork.new(
|
|
PortalNetwork.none, node, db, streamManager, FinishedHistoricalHashesAccumulator()
|
|
)
|
|
|
|
return HistoryNode(discoveryProtocol: node, historyNetwork: historyNetwork)
|
|
|
|
proc portalProtocol(hn: HistoryNode): PortalProtocol =
|
|
hn.historyNetwork.portalProtocol
|
|
|
|
proc localNode(hn: HistoryNode): Node =
|
|
hn.discoveryProtocol.localNode
|
|
|
|
proc start(hn: HistoryNode) =
|
|
hn.historyNetwork.start()
|
|
|
|
proc stop(hn: HistoryNode) {.async.} =
|
|
discard hn.historyNetwork.stop()
|
|
await hn.discoveryProtocol.closeWait()
|
|
|
|
proc containsId(hn: HistoryNode, contentId: ContentId): bool =
|
|
return hn.historyNetwork.contentDB.get(contentId).isSome()
|
|
|
|
proc store*(hn: HistoryNode, blockHash: Hash32, blockHeader: Header) =
|
|
let
|
|
headerRlp = rlp.encode(blockHeader)
|
|
blockHeaderWithProof = BlockHeaderWithProof(
|
|
header: ByteList[2048].init(headerRlp), proof: BlockHeaderProof.init()
|
|
)
|
|
contentKeyBytes = blockHeaderContentKey(blockHash).encode()
|
|
contentId = history_content.toContentId(contentKeyBytes)
|
|
|
|
hn.portalProtocol().storeContent(
|
|
contentKeyBytes, contentId, SSZ.encode(blockHeaderWithProof)
|
|
)
|
|
|
|
proc store*(hn: HistoryNode, blockHash: Hash32, blockBody: BlockBody) =
|
|
let
|
|
contentKeyBytes = blockBodyContentKey(blockHash).encode()
|
|
contentId = history_content.toContentId(contentKeyBytes)
|
|
|
|
hn.portalProtocol().storeContent(contentKeyBytes, contentId, blockBody.encode())
|
|
|
|
proc store*(hn: HistoryNode, blockHash: Hash32, receipts: seq[Receipt]) =
|
|
let
|
|
contentKeyBytes = receiptsContentKey(blockHash).encode()
|
|
contentId = history_content.toContentId(contentKeyBytes)
|
|
|
|
hn.portalProtocol().storeContent(contentKeyBytes, contentId, receipts.encode())
|
|
|
|
type TestCase = ref object
|
|
historyNode1: HistoryNode
|
|
historyNode2: HistoryNode
|
|
server: RpcHttpServer
|
|
client: PortalRpcClient
|
|
|
|
proc setupTest(rng: ref HmacDrbgContext): Future[TestCase] {.async.} =
|
|
let
|
|
localSrvAddress = "127.0.0.1"
|
|
localSrvPort = 0 # let the OS choose a port
|
|
ta = initTAddress(localSrvAddress, localSrvPort)
|
|
client = newRpcHttpClient()
|
|
historyNode1 = newHistoryNode(rng, 20333)
|
|
historyNode2 = newHistoryNode(rng, 20334)
|
|
|
|
historyNode1.start()
|
|
historyNode2.start()
|
|
|
|
check:
|
|
historyNode1.portalProtocol().addNode(historyNode2.localNode()) == Added
|
|
historyNode2.portalProtocol().addNode(historyNode1.localNode()) == Added
|
|
|
|
let rpcHttpServer = RpcHttpServer.new()
|
|
rpcHttpServer.addHttpServer(ta, maxRequestBodySize = 16 * 1024 * 1024)
|
|
rpcHttpServer.installPortalHistoryApiHandlers(
|
|
historyNode1.historyNetwork.portalProtocol
|
|
)
|
|
rpcHttpServer.start()
|
|
|
|
await client.connect(localSrvAddress, rpcHttpServer.localAddress[0].port, false)
|
|
|
|
return TestCase(
|
|
historyNode1: historyNode1,
|
|
historyNode2: historyNode2,
|
|
server: rpcHttpServer,
|
|
client: PortalRpcClient.init(client),
|
|
)
|
|
|
|
proc stop(testCase: TestCase) {.async.} =
|
|
await testCase.server.stop()
|
|
await testCase.server.closeWait()
|
|
await testCase.historyNode1.stop()
|
|
await testCase.historyNode2.stop()
|
|
|
|
procSuite "Portal RPC Client":
|
|
let rng = newRng()
|
|
|
|
asyncTest "Test historyGetBlockHeader with validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 100)
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let blockHeaderRes =
|
|
await tc.client.historyGetBlockHeader(blockHash, validateContent = true)
|
|
check:
|
|
blockHeaderRes.isErr()
|
|
blockHeaderRes.error() == ContentNotFound
|
|
|
|
# Test content found
|
|
block:
|
|
tc.historyNode2.store(blockHash, blockHeader)
|
|
|
|
let blockHeaderRes =
|
|
await tc.client.historyGetBlockHeader(blockHash, validateContent = true)
|
|
check:
|
|
blockHeaderRes.isOk()
|
|
blockHeaderRes.value() == blockHeader
|
|
|
|
# Test content validation failed
|
|
block:
|
|
tc.historyNode1.store(blockHash, Header()) # bad header
|
|
|
|
let blockHeaderRes =
|
|
await tc.client.historyGetBlockHeader(blockHash, validateContent = true)
|
|
check:
|
|
blockHeaderRes.isErr()
|
|
blockHeaderRes.error() == ContentValidationFailed
|
|
|
|
waitFor tc.stop()
|
|
|
|
asyncTest "Test historyGetBlockHeader without validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 200)
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let blockHeaderRes =
|
|
await tc.client.historyGetBlockHeader(blockHash, validateContent = false)
|
|
check:
|
|
blockHeaderRes.isErr()
|
|
blockHeaderRes.error() == ContentNotFound
|
|
|
|
# Test content found
|
|
block:
|
|
tc.historyNode1.store(blockHash, blockHeader)
|
|
|
|
let blockHeaderRes =
|
|
await tc.client.historyGetBlockHeader(blockHash, validateContent = false)
|
|
check:
|
|
blockHeaderRes.isOk()
|
|
blockHeaderRes.value() == blockHeader
|
|
|
|
waitFor tc.stop()
|
|
|
|
asyncTest "Test historyGetBlockBody with validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 300)
|
|
blockBody = BlockBody()
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let blockBodyRes =
|
|
await tc.client.historyGetBlockBody(blockHash, validateContent = true)
|
|
check:
|
|
blockBodyRes.isErr()
|
|
blockBodyRes.error() == ContentNotFound
|
|
|
|
# Test content validation failed
|
|
block:
|
|
tc.historyNode1.store(blockHash, blockHeader)
|
|
tc.historyNode1.store(blockHash, blockBody)
|
|
|
|
let blockBodyRes =
|
|
await tc.client.historyGetBlockBody(blockHash, validateContent = true)
|
|
check:
|
|
blockBodyRes.isErr()
|
|
blockBodyRes.error() == ContentValidationFailed
|
|
|
|
waitFor tc.stop()
|
|
|
|
asyncTest "Test historyGetBlockBody without validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 300)
|
|
blockBody = BlockBody()
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let blockBodyRes =
|
|
await tc.client.historyGetBlockBody(blockHash, validateContent = false)
|
|
check:
|
|
blockBodyRes.isErr()
|
|
blockBodyRes.error() == ContentNotFound
|
|
|
|
# Test content found
|
|
block:
|
|
tc.historyNode1.store(blockHash, blockHeader)
|
|
tc.historyNode1.store(blockHash, blockBody)
|
|
|
|
let blockBodyRes =
|
|
await tc.client.historyGetBlockBody(blockHash, validateContent = false)
|
|
check:
|
|
blockBodyRes.isOk()
|
|
blockBodyRes.value() == blockBody
|
|
|
|
waitFor tc.stop()
|
|
|
|
asyncTest "Test historyGetReceipts with validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 300)
|
|
receipts = @[Receipt()]
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let receiptsRes =
|
|
await tc.client.historyGetReceipts(blockHash, validateContent = true)
|
|
check:
|
|
receiptsRes.isErr()
|
|
receiptsRes.error() == ContentNotFound
|
|
|
|
# Test content validation failed
|
|
block:
|
|
tc.historyNode1.store(blockHash, blockHeader)
|
|
tc.historyNode1.store(blockHash, receipts)
|
|
|
|
let receiptsRes =
|
|
await tc.client.historyGetReceipts(blockHash, validateContent = true)
|
|
check:
|
|
receiptsRes.isErr()
|
|
receiptsRes.error() == ContentValidationFailed
|
|
|
|
waitFor tc.stop()
|
|
|
|
asyncTest "Test historyGetReceipts without validation":
|
|
let
|
|
tc = await setupTest(rng)
|
|
blockHeader = Header(number: 300)
|
|
receipts = @[Receipt()]
|
|
blockHash = blockHeader.rlpHash()
|
|
|
|
# Test content not found
|
|
block:
|
|
let receiptsRes =
|
|
await tc.client.historyGetReceipts(blockHash, validateContent = false)
|
|
check:
|
|
receiptsRes.isErr()
|
|
receiptsRes.error() == ContentNotFound
|
|
|
|
# Test content found
|
|
block:
|
|
tc.historyNode1.store(blockHash, blockHeader)
|
|
tc.historyNode1.store(blockHash, receipts)
|
|
|
|
let receiptsRes =
|
|
await tc.client.historyGetReceipts(blockHash, validateContent = false)
|
|
check:
|
|
receiptsRes.isOk()
|
|
receiptsRes.value() == receipts
|
|
|
|
waitFor tc.stop()
|