Skip to content

Commit a06f1a3

Browse files
committed
Add tests for history content, network and endpoints
Also bump portal-spec-tests repo for new test vectors
1 parent 594a956 commit a06f1a3

File tree

8 files changed

+387
-4
lines changed

8 files changed

+387
-4
lines changed

portal/network/history/history_network.nim

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type
4545
contentRequestRetries: int
4646
contentQueueWorkers: int
4747

48-
proc defaultNoGetHeader(
48+
proc defaultNoGetHeader*(
4949
blockNumber: uint64
5050
): Future[Result[Header, string]] {.async: (raises: [CancelledError]), gcsafe.} =
5151
err(
@@ -100,6 +100,9 @@ proc new*(
100100
contentQueueWorkers: contentQueueWorkers,
101101
)
102102

103+
func localNode*(n: HistoryNetwork): Node =
104+
n.portalProtocol.localNode()
105+
103106
proc getContent*(
104107
n: HistoryNetwork, contentKey: ContentKey, V: type ContentValueType, header: Header
105108
): Future[Opt[V]] {.async: (raises: [CancelledError]).} =

portal/tests/history_network_tests/all_history_network_tests.nim

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@
77

88
{.warning[UnusedImport]: off.}
99

10-
import ./test_history_content_keys
10+
import
11+
./test_history_content_keys,
12+
./test_history_content_values,
13+
./test_history_endpoints,
14+
./test_history_network
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Nimbus
2+
# Copyright (c) 2025 Status Research & Development GmbH
3+
# Licensed and distributed under either of
4+
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
5+
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
6+
# at your option. This file may not be copied, modified, or distributed except according to those terms.
7+
8+
{.push raises: [].}
9+
10+
import
11+
chronos,
12+
eth/keys,
13+
eth/p2p/discoveryv5/protocol,
14+
../../database/content_db,
15+
../../network/wire/[portal_protocol, portal_stream],
16+
../../network/history/history_network,
17+
../test_helpers
18+
19+
type HistoryNode* = ref object
20+
discovery: protocol.Protocol
21+
historyNetwork*: HistoryNetwork
22+
23+
proc newHistoryNetwork*(
24+
rng: ref HmacDrbgContext,
25+
port: int,
26+
getHeaderCallback: GetHeaderCallback = defaultNoGetHeader,
27+
): HistoryNode =
28+
let
29+
node =
30+
try:
31+
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(port))
32+
except CatchableError as e:
33+
raiseAssert "Failed to initialize discovery node: " & e.msg
34+
db = ContentDB.new(
35+
"",
36+
uint32.high,
37+
RadiusConfig(kind: Static, logRadius: 256),
38+
node.localNode.id,
39+
inMemory = true,
40+
)
41+
streamManager = StreamManager.new(node)
42+
43+
HistoryNode(
44+
discovery: node,
45+
historyNetwork: HistoryNetwork.new(
46+
PortalNetwork.mainnet, node, db, streamManager, getHeaderCallback
47+
),
48+
)
49+
50+
proc start*(n: HistoryNode) =
51+
n.discovery.start()
52+
n.historyNetwork.start()
53+
54+
proc stop*(n: HistoryNode) {.async: (raises: []).} =
55+
await n.historyNetwork.stop()
56+
await n.discovery.closeWait()
57+
n.historyNetwork.contentDB.close()
58+
59+
func portalProtocol*(n: HistoryNode): PortalProtocol =
60+
n.historyNetwork.portalProtocol
61+
62+
func localNode*(n: HistoryNode): Node =
63+
n.historyNetwork.localNode()

portal/tests/history_network_tests/test_history_content_keys.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import unittest2, stew/byteutils, ../../network/history/history_content
1111

12-
suite "History Content Keys":
12+
suite "History Network Content Keys":
1313
test "toContentId":
1414
# Input
1515
const blockNumbers = [
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Nimbus
2+
# Copyright (c) 2025 Status Research & Development GmbH
3+
# Licensed and distributed under either of
4+
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
5+
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
6+
# at your option. This file may not be copied, modified, or distributed except according to those terms.
7+
8+
{.push raises: [].}
9+
10+
import
11+
unittest2,
12+
results,
13+
stew/byteutils,
14+
../../common/common_types,
15+
../../eth_data/yaml_utils,
16+
../../tools/eth_data_exporter/el_data_exporter,
17+
../../network/history/history_validation
18+
19+
from std/os import walkDir, splitFile, PathComponent
20+
21+
const testsPath = "./vendor/portal-spec-tests/tests/mainnet/history/block_data/"
22+
23+
suite "History Network Content Values":
24+
test "BlockBody and Receipts Encoding/Decoding and Verification":
25+
for kind, path in walkDir(testsPath):
26+
if kind == pcFile and path.splitFile.ext == ".yaml":
27+
let
28+
blockData = BlockData.loadFromYaml(path).valueOr:
29+
raiseAssert "Cannot read test vector: " & error
30+
31+
headerEncoded = blockData.header.hexToSeqByte()
32+
bodyEncoded = blockData.body.hexToSeqByte()
33+
34+
let header = decodeRlp(headerEncoded, Header).expect("Valid header")
35+
36+
let contentKey = blockBodyContentKey(header.number)
37+
check validateContent(contentKey, bodyEncoded, header).isOk()
38+
39+
let contentValue = decodeRlp(bodyEncoded, BlockBody)
40+
check contentValue.isOk()
41+
check rlp.encode(contentValue.get()) == bodyEncoded
42+
43+
test "Receipts Encoding/Decoding and Verification":
44+
for kind, path in walkDir(testsPath):
45+
if kind == pcFile and path.splitFile.ext == ".yaml":
46+
let
47+
blockData = BlockData.loadFromYaml(path).valueOr:
48+
raiseAssert "Cannot read test vector: " & error
49+
50+
headerEncoded = blockData.header.hexToSeqByte()
51+
receiptsEncoded = blockData.receipts.hexToSeqByte()
52+
53+
let header = decodeRlp(headerEncoded, Header).expect("Valid header")
54+
55+
let contentKey = receiptsContentKey(header.number)
56+
check validateContent(contentKey, receiptsEncoded, header).isOk()
57+
58+
let contentValue = decodeRlp(receiptsEncoded, StoredReceipts)
59+
check contentValue.isOk()
60+
check rlp.encode(contentValue.get()) == receiptsEncoded
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Nimbus
2+
# Copyright (c) 2025 Status Research & Development GmbH
3+
# Licensed and distributed under either of
4+
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
5+
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
6+
# at your option. This file may not be copied, modified, or distributed except according to those terms.
7+
8+
{.push raises: [].}
9+
10+
import
11+
unittest2,
12+
stew/byteutils,
13+
chronos/unittest2/asynctests,
14+
../../eth_data/yaml_utils,
15+
../../tools/eth_data_exporter/el_data_exporter,
16+
../../network/wire/portal_protocol,
17+
../../network/history/history_endpoints,
18+
./history_test_helpers
19+
20+
from std/os import walkDir, splitFile, PathComponent
21+
22+
const testsPath = "./vendor/portal-spec-tests/tests/mainnet/history/block_data/"
23+
24+
suite "History Network Endpoints":
25+
asyncTest "GetBlockBody":
26+
let
27+
rng = newRng()
28+
node1 = newHistoryNetwork(rng, 9001)
29+
node2 = newHistoryNetwork(rng, 9002)
30+
31+
node1.start()
32+
node2.start()
33+
34+
check:
35+
node1.portalProtocol().addNode(node2.localNode()) == Added
36+
node2.portalProtocol().addNode(node1.localNode()) == Added
37+
38+
(await node1.portalProtocol().ping(node2.localNode())).isOk()
39+
(await node2.portalProtocol().ping(node1.localNode())).isOk()
40+
41+
for kind, path in walkDir(testsPath):
42+
if kind == pcFile and path.splitFile.ext == ".yaml":
43+
let
44+
blockData = BlockData.loadFromYaml(path).valueOr:
45+
raiseAssert "Cannot read test vector: " & error
46+
47+
headerEncoded = blockData.header.hexToSeqByte()
48+
bodyEncoded = blockData.body.hexToSeqByte()
49+
header = decodeRlp(headerEncoded, Header).expect("Valid header")
50+
contentKey = blockBodyContentKey(header.number)
51+
52+
node1.portalProtocol().storeContent(
53+
contentKey.encode(), contentKey.toContentId(), bodyEncoded
54+
)
55+
56+
check (await node2.historyNetwork.getBlockBody(header)).isOk()
57+
58+
await node1.stop()
59+
await node2.stop()
60+
61+
asyncTest "GetReceipts":
62+
let
63+
rng = newRng()
64+
node1 = newHistoryNetwork(rng, 9001)
65+
node2 = newHistoryNetwork(rng, 9002)
66+
67+
node1.start()
68+
node2.start()
69+
70+
check:
71+
node1.portalProtocol().addNode(node2.localNode()) == Added
72+
node2.portalProtocol().addNode(node1.localNode()) == Added
73+
74+
(await node1.portalProtocol().ping(node2.localNode())).isOk()
75+
(await node2.portalProtocol().ping(node1.localNode())).isOk()
76+
77+
for kind, path in walkDir(testsPath):
78+
if kind == pcFile and path.splitFile.ext == ".yaml":
79+
let
80+
blockData = BlockData.loadFromYaml(path).valueOr:
81+
raiseAssert "Cannot read test vector: " & error
82+
83+
headerEncoded = blockData.header.hexToSeqByte()
84+
receiptsEncoded = blockData.receipts.hexToSeqByte()
85+
header = decodeRlp(headerEncoded, Header).expect("Valid header")
86+
contentKey = receiptsContentKey(header.number)
87+
88+
node1.portalProtocol().storeContent(
89+
contentKey.encode(), contentKey.toContentId(), receiptsEncoded
90+
)
91+
92+
check (await node2.historyNetwork.getReceipts(header)).isOk()
93+
94+
await node1.stop()
95+
await node2.stop()

0 commit comments

Comments
 (0)