Fluffy state network tests (#2245)
* Add state network get content test. * Completed state network get content tests. * Completed state gossip offer content test. * Improve state network offer content test. * Completed state gossip offer test. * Rename tests.
This commit is contained in:
parent
4b8219a0dd
commit
1eb170e686
|
@ -122,17 +122,6 @@ proc getContractCode*(
|
||||||
): Future[Opt[ContractCodeRetrieval]] {.inline.} =
|
): Future[Opt[ContractCodeRetrieval]] {.inline.} =
|
||||||
n.getContent(key, ContractCodeRetrieval)
|
n.getContent(key, ContractCodeRetrieval)
|
||||||
|
|
||||||
# High level endpoints
|
|
||||||
# eth_getBalance
|
|
||||||
# eth_getStorageAt
|
|
||||||
# eth_getCode
|
|
||||||
|
|
||||||
func decodeKey(contentKey: ByteList): Opt[ContentKey] =
|
|
||||||
let key = ContentKey.decode(contentKey).valueOr:
|
|
||||||
return Opt.none(ContentKey)
|
|
||||||
|
|
||||||
Opt.some(key)
|
|
||||||
|
|
||||||
proc getStateRootByBlockHash(
|
proc getStateRootByBlockHash(
|
||||||
n: StateNetwork, hash: BlockHash
|
n: StateNetwork, hash: BlockHash
|
||||||
): Future[Opt[KeccakHash]] {.async.} =
|
): Future[Opt[KeccakHash]] {.async.} =
|
||||||
|
@ -146,7 +135,7 @@ proc getStateRootByBlockHash(
|
||||||
|
|
||||||
Opt.some(header.stateRoot)
|
Opt.some(header.stateRoot)
|
||||||
|
|
||||||
proc processOffer(
|
proc processOffer*(
|
||||||
n: StateNetwork,
|
n: StateNetwork,
|
||||||
maybeSrcNodeId: Opt[NodeId],
|
maybeSrcNodeId: Opt[NodeId],
|
||||||
contentKeyBytes: ByteList,
|
contentKeyBytes: ByteList,
|
||||||
|
@ -183,10 +172,11 @@ proc processContentLoop(n: StateNetwork) {.async.} =
|
||||||
try:
|
try:
|
||||||
while true:
|
while true:
|
||||||
let (srcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
let (srcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
||||||
|
|
||||||
for i, contentValueBytes in contentValues:
|
for i, contentValueBytes in contentValues:
|
||||||
let
|
let
|
||||||
contentKeyBytes = contentKeys[i]
|
contentKeyBytes = contentKeys[i]
|
||||||
contentKey = decodeKey(contentKeyBytes).valueOr:
|
contentKey = ContentKey.decode(contentKeyBytes).valueOr:
|
||||||
error "Unable to decode offered content key", contentKeyBytes
|
error "Unable to decode offered content key", contentKeyBytes
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -219,8 +209,7 @@ proc processContentLoop(n: StateNetwork) {.async.} =
|
||||||
trace "processContentLoop canceled"
|
trace "processContentLoop canceled"
|
||||||
|
|
||||||
proc start*(n: StateNetwork) =
|
proc start*(n: StateNetwork) =
|
||||||
info "Starting Portal execution state network",
|
info "Starting Portal State Network", protocolId = n.portalProtocol.protocolId
|
||||||
protocolId = n.portalProtocol.protocolId
|
|
||||||
n.portalProtocol.start()
|
n.portalProtocol.start()
|
||||||
|
|
||||||
n.processContentLoop = processContentLoop(n)
|
n.processContentLoop = processContentLoop(n)
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
{.warning[UnusedImport]: off.}
|
{.warning[UnusedImport]: off.}
|
||||||
|
|
||||||
import
|
import
|
||||||
./test_state_content_keys,
|
./test_state_content_keys_vectors,
|
||||||
./test_state_content_nibbles,
|
./test_state_content_nibbles,
|
||||||
./test_state_content_values,
|
./test_state_content_values_vectors,
|
||||||
#./test_state_network_gossip,
|
./test_state_gossip_getparent_genesis,
|
||||||
./test_state_network,
|
./test_state_gossip_getparent_vectors,
|
||||||
./test_state_recursivegossip_genesis,
|
./test_state_gossip_gossipoffer_vectors,
|
||||||
./test_state_recursivegossip_vectors,
|
./test_state_network_getcontent_vectors,
|
||||||
|
./test_state_network_offercontent_vectors,
|
||||||
./test_state_validation_genesis,
|
./test_state_validation_genesis,
|
||||||
./test_state_validation_trieproof,
|
./test_state_validation_trieproof,
|
||||||
./test_state_validation_vectors
|
./test_state_validation_vectors
|
||||||
|
|
|
@ -9,10 +9,17 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[sugar, sequtils],
|
std/[sugar, sequtils],
|
||||||
|
chronos,
|
||||||
eth/[common, trie, trie/db],
|
eth/[common, trie, trie/db],
|
||||||
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||||
|
eth/p2p/discoveryv5/routing_table,
|
||||||
|
../../network/wire/[portal_protocol, portal_stream, portal_protocol_config],
|
||||||
../../nimbus/common/chain_config,
|
../../nimbus/common/chain_config,
|
||||||
../../network/state/[state_content, state_utils],
|
../../network/history/[history_content, history_network],
|
||||||
../../eth_data/yaml_utils
|
../../network/state/[state_content, state_utils, state_network],
|
||||||
|
../../eth_data/yaml_utils,
|
||||||
|
../../database/content_db,
|
||||||
|
../test_helpers
|
||||||
|
|
||||||
export yaml_utils
|
export yaml_utils
|
||||||
|
|
||||||
|
@ -129,3 +136,53 @@ proc toState*(
|
||||||
accountTrie.put(key, value)
|
accountTrie.put(key, value)
|
||||||
|
|
||||||
(accountTrie, storageStates)
|
(accountTrie, storageStates)
|
||||||
|
|
||||||
|
type StateNode* = ref object
|
||||||
|
discoveryProtocol*: discv5_protocol.Protocol
|
||||||
|
stateNetwork*: StateNetwork
|
||||||
|
|
||||||
|
proc newStateNode*(
|
||||||
|
rng: ref HmacDrbgContext, port: int
|
||||||
|
): StateNode {.raises: [CatchableError].} =
|
||||||
|
let
|
||||||
|
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(port))
|
||||||
|
db = ContentDB.new("", uint32.high, inMemory = true)
|
||||||
|
sm = StreamManager.new(node)
|
||||||
|
hn = HistoryNetwork.new(node, db, sm, FinishedAccumulator())
|
||||||
|
sn = StateNetwork.new(node, db, sm, historyNetwork = Opt.some(hn))
|
||||||
|
|
||||||
|
return StateNode(discoveryProtocol: node, stateNetwork: sn)
|
||||||
|
|
||||||
|
proc portalProtocol*(sn: StateNode): PortalProtocol =
|
||||||
|
sn.stateNetwork.portalProtocol
|
||||||
|
|
||||||
|
proc localNode*(sn: StateNode): Node =
|
||||||
|
sn.discoveryProtocol.localNode
|
||||||
|
|
||||||
|
proc start*(sn: StateNode) =
|
||||||
|
sn.stateNetwork.start()
|
||||||
|
|
||||||
|
proc stop*(sn: StateNode) {.async.} =
|
||||||
|
sn.stateNetwork.stop()
|
||||||
|
await sn.discoveryProtocol.closeWait()
|
||||||
|
|
||||||
|
proc containsId*(sn: StateNode, contentId: ContentId): bool =
|
||||||
|
return sn.stateNetwork.contentDB.get(contentId).isSome()
|
||||||
|
|
||||||
|
proc mockBlockHashToStateRoot*(
|
||||||
|
sn: StateNode, blockHash: BlockHash, stateRoot: KeccakHash
|
||||||
|
) =
|
||||||
|
let
|
||||||
|
blockHeader = BlockHeader(stateRoot: stateRoot)
|
||||||
|
headerRlp = rlp.encode(blockHeader)
|
||||||
|
blockHeaderWithProof = BlockHeaderWithProof(
|
||||||
|
header: ByteList.init(headerRlp), proof: BlockHeaderProof.init()
|
||||||
|
)
|
||||||
|
contentKeyBytes = history_content.ContentKey
|
||||||
|
.init(history_content.ContentType.blockHeader, blockHash)
|
||||||
|
.encode()
|
||||||
|
contentId = history_content.toContentId(contentKeyBytes)
|
||||||
|
|
||||||
|
sn.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, SSZ.encode(blockHeaderWithProof)
|
||||||
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import
|
||||||
../../network/state/[state_content, state_validation, state_gossip, state_utils],
|
../../network/state/[state_content, state_validation, state_gossip, state_utils],
|
||||||
./state_test_helpers
|
./state_test_helpers
|
||||||
|
|
||||||
suite "State Recursive Gossip - Genesis JSON Files":
|
suite "State Gossip getParent - Genesis JSON Files":
|
||||||
let genesisFiles = [
|
let genesisFiles = [
|
||||||
"berlin2000.json", "calaveras.json", "chainid1.json", "chainid7.json",
|
"berlin2000.json", "calaveras.json", "chainid1.json", "chainid7.json",
|
||||||
"devnet4.json", "devnet5.json", "holesky.json", "mainshadow1.json", "merge.json",
|
"devnet4.json", "devnet5.json", "holesky.json", "mainshadow1.json", "merge.json",
|
|
@ -15,7 +15,7 @@ import
|
||||||
../../network/state/[state_content, state_gossip],
|
../../network/state/[state_content, state_gossip],
|
||||||
./state_test_helpers
|
./state_test_helpers
|
||||||
|
|
||||||
suite "State Recursive Gossip - Test Vectors":
|
suite "State Gossip getParent - Test Vectors":
|
||||||
test "Check account trie node parent matches expected recursive gossip":
|
test "Check account trie node parent matches expected recursive gossip":
|
||||||
const file = testVectorDir / "account_trie_node.yaml"
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
|
@ -0,0 +1,389 @@
|
||||||
|
# Fluffy
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import
|
||||||
|
std/os,
|
||||||
|
testutils/unittests,
|
||||||
|
chronos,
|
||||||
|
stew/byteutils,
|
||||||
|
eth/common,
|
||||||
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||||
|
eth/p2p/discoveryv5/routing_table,
|
||||||
|
../../common/common_utils,
|
||||||
|
../../network/wire/[portal_protocol, portal_stream],
|
||||||
|
../../network/state/[state_content, state_network, state_gossip],
|
||||||
|
../../database/content_db,
|
||||||
|
./state_test_helpers
|
||||||
|
|
||||||
|
procSuite "State Gossip - Gossip Offer":
|
||||||
|
const STATE_NODE1_PORT = 20602
|
||||||
|
const STATE_NODE2_PORT = 20603
|
||||||
|
|
||||||
|
let rng = newRng()
|
||||||
|
|
||||||
|
asyncTest "Gossip account trie nodes":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
for i, testData in testCase:
|
||||||
|
if i == 1:
|
||||||
|
continue # skip scenario with no parent
|
||||||
|
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
parentContentKeyBytes =
|
||||||
|
testData.recursive_gossip.content_key.hexToSeqByte().ByteList
|
||||||
|
parentContentKey = ContentKey.decode(parentContentKeyBytes).get()
|
||||||
|
parentContentId = toContentId(parentContentKeyBytes)
|
||||||
|
parentContentValueBytes =
|
||||||
|
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||||
|
parentContentValue = AccountTrieNodeOffer.decode(parentContentValueBytes).get()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.accountTrieNodeKey,
|
||||||
|
contentValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
# check that both the offer and parent were received by the second state instance
|
||||||
|
let res1 =
|
||||||
|
await stateNode2.stateNetwork.getAccountTrieNode(contentKey.accountTrieNodeKey)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == contentValue.toRetrievalValue()
|
||||||
|
res1.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
let res2 = await stateNode2.stateNetwork.getAccountTrieNode(
|
||||||
|
parentContentKey.accountTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(parentContentId)
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == parentContentValue.toRetrievalValue()
|
||||||
|
res2.get().node == parentContentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Gossip contract trie nodes":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
for i, testData in testCase:
|
||||||
|
if i == 1:
|
||||||
|
continue # skip scenario with no parent
|
||||||
|
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
parentContentKeyBytes =
|
||||||
|
testData.recursive_gossip.content_key.hexToSeqByte().ByteList
|
||||||
|
parentContentKey = ContentKey.decode(parentContentKeyBytes).get()
|
||||||
|
parentContentId = toContentId(parentContentKeyBytes)
|
||||||
|
parentContentValueBytes =
|
||||||
|
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||||
|
parentContentValue = ContractTrieNodeOffer.decode(parentContentValueBytes).get()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractTrieNodeKey,
|
||||||
|
contentValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
# check that both the offer and parent were received by the second state instance
|
||||||
|
let res1 = await stateNode2.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == contentValue.toRetrievalValue()
|
||||||
|
res1.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
let res2 = await stateNode2.stateNetwork.getContractTrieNode(
|
||||||
|
parentContentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(parentContentId)
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == parentContentValue.toRetrievalValue()
|
||||||
|
res2.get().node == parentContentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Gossip contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
for i, testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractCodeOffer.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractCodeKey,
|
||||||
|
contentValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
# check that both the offer and parent were received by the second state instance
|
||||||
|
let res1 =
|
||||||
|
await stateNode2.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == contentValue.toRetrievalValue()
|
||||||
|
res1.get().code == contentValue.toRetrievalValue().code
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Recursive gossip account trie nodes":
|
||||||
|
const file = testVectorDir / "recursive_gossip.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
stateNode2.portalProtocol().addNode(stateNode1.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||||
|
|
||||||
|
for i, testData in testCase:
|
||||||
|
if i == 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
leafData = testData.recursive_gossip[0]
|
||||||
|
contentKeyBytes = leafData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = leafData.content_value.hexToSeqByte()
|
||||||
|
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode1.containsId(contentId)
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
# offer the leaf node
|
||||||
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.accountTrieNodeKey,
|
||||||
|
contentValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for recursive gossip to complete
|
||||||
|
await sleepAsync(1000.milliseconds)
|
||||||
|
|
||||||
|
# check that all nodes were received by both state instances
|
||||||
|
for kv in testData.recursive_gossip:
|
||||||
|
let
|
||||||
|
expectedKeyBytes = kv.content_key.hexToSeqByte().ByteList
|
||||||
|
expectedKey = ContentKey.decode(expectedKeyBytes).get()
|
||||||
|
expectedId = toContentId(expectedKeyBytes)
|
||||||
|
expectedValue =
|
||||||
|
AccountTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
|
||||||
|
res1 = await stateNode1.stateNetwork.getAccountTrieNode(
|
||||||
|
expectedKey.accountTrieNodeKey
|
||||||
|
)
|
||||||
|
res2 = await stateNode2.stateNetwork.getAccountTrieNode(
|
||||||
|
expectedKey.accountTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(expectedId)
|
||||||
|
stateNode2.containsId(expectedId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == expectedValue.toRetrievalValue()
|
||||||
|
res1.get().node == expectedValue.toRetrievalValue().node
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == expectedValue.toRetrievalValue()
|
||||||
|
res2.get().node == expectedValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Recursive gossip contract trie nodes":
|
||||||
|
const file = testVectorDir / "recursive_gossip.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
stateNode2.portalProtocol().addNode(stateNode1.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||||
|
|
||||||
|
for i, testData in testCase:
|
||||||
|
if i != 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
leafData = testData.recursive_gossip[0]
|
||||||
|
contentKeyBytes = leafData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = leafData.content_value.hexToSeqByte()
|
||||||
|
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode1.containsId(contentId)
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
# offer the leaf node
|
||||||
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractTrieNodeKey,
|
||||||
|
contentValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for recursive gossip to complete
|
||||||
|
await sleepAsync(1000.milliseconds)
|
||||||
|
|
||||||
|
# check that all nodes were received by both state instances
|
||||||
|
for kv in testData.recursive_gossip:
|
||||||
|
let
|
||||||
|
expectedKeyBytes = kv.content_key.hexToSeqByte().ByteList
|
||||||
|
expectedKey = ContentKey.decode(expectedKeyBytes).get()
|
||||||
|
expectedId = toContentId(expectedKeyBytes)
|
||||||
|
expectedValue =
|
||||||
|
ContractTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
|
||||||
|
res1 = await stateNode1.stateNetwork.getContractTrieNode(
|
||||||
|
expectedKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
res2 = await stateNode2.stateNetwork.getContractTrieNode(
|
||||||
|
expectedKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(expectedId)
|
||||||
|
stateNode2.containsId(expectedId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == expectedValue.toRetrievalValue()
|
||||||
|
res1.get().node == expectedValue.toRetrievalValue().node
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == expectedValue.toRetrievalValue()
|
||||||
|
res2.get().node == expectedValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
|
@ -1,158 +0,0 @@
|
||||||
# Fluffy
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import
|
|
||||||
std/os,
|
|
||||||
nimcrypto/hash,
|
|
||||||
testutils/unittests,
|
|
||||||
chronos,
|
|
||||||
eth/keys,
|
|
||||||
eth/trie,
|
|
||||||
eth/common/[eth_types, eth_hash],
|
|
||||||
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
|
||||||
eth/p2p/discoveryv5/routing_table,
|
|
||||||
../../network/wire/[portal_protocol, portal_stream],
|
|
||||||
../../network/state/[state_content, state_network],
|
|
||||||
../../database/content_db,
|
|
||||||
../test_helpers,
|
|
||||||
./state_test_helpers
|
|
||||||
|
|
||||||
procSuite "State Network":
|
|
||||||
let rng = newRng()
|
|
||||||
|
|
||||||
asyncTest "Test Share Full State":
|
|
||||||
let
|
|
||||||
accounts =
|
|
||||||
getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
|
|
||||||
(trie, _) = accounts.toState()
|
|
||||||
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
|
|
||||||
sm1 = StreamManager.new(node1)
|
|
||||||
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
|
|
||||||
sm2 = StreamManager.new(node2)
|
|
||||||
proto1 =
|
|
||||||
StateNetwork.new(node1, ContentDB.new("", uint32.high, inMemory = true), sm1)
|
|
||||||
proto2 =
|
|
||||||
StateNetwork.new(node2, ContentDB.new("", uint32.high, inMemory = true), sm2)
|
|
||||||
|
|
||||||
check proto2.portalProtocol.addNode(node1.localNode) == Added
|
|
||||||
|
|
||||||
var keys: seq[seq[byte]]
|
|
||||||
for k, v in trie.replicate:
|
|
||||||
keys.add(k)
|
|
||||||
|
|
||||||
var nodeHash: NodeHash
|
|
||||||
copyMem(nodeHash.data.addr, unsafeAddr k[0], sizeof(nodeHash.data))
|
|
||||||
|
|
||||||
let
|
|
||||||
# TODO: add stateRoot, and path eventually
|
|
||||||
accountTrieNodeKey = AccountTrieNodeKey(nodeHash: nodeHash)
|
|
||||||
contentKey = ContentKey(
|
|
||||||
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
|
|
||||||
)
|
|
||||||
contentId = toContentId(contentKey.encode())
|
|
||||||
value = AccountTrieNodeRetrieval(node: TrieNode.init(v))
|
|
||||||
|
|
||||||
discard proto1.contentDB.put(
|
|
||||||
contentId, value.encode(), proto1.portalProtocol.localNode.id
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
var nodeHash: NodeHash
|
|
||||||
copyMem(nodeHash.data.addr, unsafeAddr key[0], sizeof(nodeHash.data))
|
|
||||||
|
|
||||||
let
|
|
||||||
accountTrieNodeKey = AccountTrieNodeKey(nodeHash: nodeHash)
|
|
||||||
contentKey = ContentKey(
|
|
||||||
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
|
|
||||||
)
|
|
||||||
contentId = toContentId(contentKey.encode())
|
|
||||||
|
|
||||||
# Note: GetContent and thus the lookup here is not really needed, as we
|
|
||||||
# only have to request data to one node.
|
|
||||||
let accTrieNode = await proto2.getAccountTrieNode(accountTrieNodeKey)
|
|
||||||
check accTrieNode.isSome()
|
|
||||||
|
|
||||||
let hash = keccakHash(accTrieNode.get().node.asSeq())
|
|
||||||
check hash.data == key
|
|
||||||
|
|
||||||
proto1.stop()
|
|
||||||
proto2.stop()
|
|
||||||
await node1.closeWait()
|
|
||||||
await node2.closeWait()
|
|
||||||
|
|
||||||
asyncTest "Find content in the network via content lookup":
|
|
||||||
# TODO: Improve this test so it actually need to go through several
|
|
||||||
# findNodes request, to properly test the lookup call.
|
|
||||||
let
|
|
||||||
accounts =
|
|
||||||
getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
|
|
||||||
(trie, _) = accounts.toState()
|
|
||||||
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
|
|
||||||
sm1 = StreamManager.new(node1)
|
|
||||||
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
|
|
||||||
sm2 = StreamManager.new(node2)
|
|
||||||
node3 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20314))
|
|
||||||
sm3 = StreamManager.new(node3)
|
|
||||||
|
|
||||||
proto1 =
|
|
||||||
StateNetwork.new(node1, ContentDB.new("", uint32.high, inMemory = true), sm1)
|
|
||||||
proto2 =
|
|
||||||
StateNetwork.new(node2, ContentDB.new("", uint32.high, inMemory = true), sm2)
|
|
||||||
proto3 =
|
|
||||||
StateNetwork.new(node3, ContentDB.new("", uint32.high, inMemory = true), sm3)
|
|
||||||
|
|
||||||
# Node1 knows about Node2, and Node2 knows about Node3 which hold all content
|
|
||||||
check proto1.portalProtocol.addNode(node2.localNode) == Added
|
|
||||||
check proto2.portalProtocol.addNode(node3.localNode) == Added
|
|
||||||
|
|
||||||
check (await proto2.portalProtocol.ping(node3.localNode)).isOk()
|
|
||||||
|
|
||||||
var keys: seq[seq[byte]]
|
|
||||||
for k, v in trie.replicate:
|
|
||||||
keys.add(k)
|
|
||||||
|
|
||||||
var nodeHash: NodeHash
|
|
||||||
copyMem(nodeHash.data.addr, unsafeAddr k[0], sizeof(nodeHash.data))
|
|
||||||
|
|
||||||
let
|
|
||||||
accountTrieNodeKey = AccountTrieNodeKey(nodeHash: nodeHash)
|
|
||||||
contentKey = ContentKey(
|
|
||||||
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
|
|
||||||
)
|
|
||||||
contentId = toContentId(contentKey.encode())
|
|
||||||
value = AccountTrieNodeRetrieval(node: TrieNode.init(v))
|
|
||||||
|
|
||||||
discard proto2.contentDB.put(
|
|
||||||
contentId, value.encode(), proto2.portalProtocol.localNode.id
|
|
||||||
)
|
|
||||||
# Not needed right now as 1 node is enough considering node 1 is connected
|
|
||||||
# to both.
|
|
||||||
discard proto3.contentDB.put(
|
|
||||||
contentId, value.encode(), proto3.portalProtocol.localNode.id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get first key
|
|
||||||
var nodeHash: NodeHash
|
|
||||||
let firstKey = keys[0]
|
|
||||||
copyMem(nodeHash.data.addr, unsafeAddr firstKey[0], sizeof(nodeHash.data))
|
|
||||||
|
|
||||||
let
|
|
||||||
accountTrieNodeKey = AccountTrieNodeKey(nodeHash: nodeHash)
|
|
||||||
contentKey =
|
|
||||||
ContentKey(contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey)
|
|
||||||
|
|
||||||
let accTrieNode = await proto1.getAccountTrieNode(accountTrieNodeKey)
|
|
||||||
check accTrieNode.isSome()
|
|
||||||
|
|
||||||
let hash = keccakHash(accTrieNode.get().node.asSeq())
|
|
||||||
check hash.data == firstKey
|
|
||||||
|
|
||||||
proto1.stop()
|
|
||||||
proto2.stop()
|
|
||||||
await node1.closeWait()
|
|
||||||
await node2.closeWait()
|
|
||||||
await node3.closeWait()
|
|
|
@ -0,0 +1,350 @@
|
||||||
|
# Fluffy
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import
|
||||||
|
std/os,
|
||||||
|
testutils/unittests,
|
||||||
|
chronos,
|
||||||
|
stew/byteutils,
|
||||||
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||||
|
eth/p2p/discoveryv5/routing_table,
|
||||||
|
../../network/wire/[portal_protocol, portal_stream],
|
||||||
|
../../network/state/[state_content, state_network],
|
||||||
|
../../database/content_db,
|
||||||
|
./state_test_helpers
|
||||||
|
|
||||||
|
procSuite "State Network - Get Content":
|
||||||
|
const STATE_NODE1_PORT = 20402
|
||||||
|
const STATE_NODE2_PORT = 20403
|
||||||
|
|
||||||
|
let rng = newRng()
|
||||||
|
|
||||||
|
# Single state instance tests
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get existing account trie node":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = AccountTrieNodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
|
||||||
|
let res =
|
||||||
|
await stateNode1.stateNetwork.getAccountTrieNode(contentKey.accountTrieNodeKey)
|
||||||
|
check:
|
||||||
|
res.isOk()
|
||||||
|
res.get() == expectedContentValue
|
||||||
|
res.get().node == expectedContentValue.node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get missing account trie node":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
|
||||||
|
let res =
|
||||||
|
await stateNode1.stateNetwork.getAccountTrieNode(contentKey.accountTrieNodeKey)
|
||||||
|
check:
|
||||||
|
res.isNone()
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get existing contract trie node":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = ContractTrieNodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
|
||||||
|
let res = await stateNode1.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
res.isOk()
|
||||||
|
res.get() == expectedContentValue
|
||||||
|
res.get().node == expectedContentValue.node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get missing contract trie node":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
|
||||||
|
let res = await stateNode1.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
res.isNone()
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get existing contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = ContractCodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
|
||||||
|
let res =
|
||||||
|
await stateNode1.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
res.isOk()
|
||||||
|
res.get() == expectedContentValue
|
||||||
|
res.get().code == expectedContentValue.code
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Get missing contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
|
||||||
|
let res =
|
||||||
|
await stateNode1.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
res.isNone()
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
# Two state instances tests
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Get existing account trie node":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
stateNode2.portalProtocol().addNode(stateNode1.localNode()) == Added
|
||||||
|
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = AccountTrieNodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# only store the content in the first state instance
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let
|
||||||
|
res1 = await stateNode1.stateNetwork.getAccountTrieNode(
|
||||||
|
contentKey.accountTrieNodeKey
|
||||||
|
)
|
||||||
|
res2 = await stateNode2.stateNetwork.getAccountTrieNode(
|
||||||
|
contentKey.accountTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == expectedContentValue
|
||||||
|
res1.get().node == expectedContentValue.node
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == expectedContentValue
|
||||||
|
res2.get().node == expectedContentValue.node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Get existing contract trie node":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
stateNode2.portalProtocol().addNode(stateNode1.localNode()) == Added
|
||||||
|
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = ContractTrieNodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# only store the content in the first state instance
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let
|
||||||
|
res1 = await stateNode1.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
res2 = await stateNode2.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == expectedContentValue
|
||||||
|
res1.get().node == expectedContentValue.node
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == expectedContentValue
|
||||||
|
res2.get().node == expectedContentValue.node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Get existing contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let
|
||||||
|
testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
stateNode2.portalProtocol().addNode(stateNode1.localNode()) == Added
|
||||||
|
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_retrieval.hexToSeqByte()
|
||||||
|
expectedContentValue = ContractCodeRetrieval.decode(contentValueBytes).get()
|
||||||
|
|
||||||
|
# only store the content in the first state instance
|
||||||
|
stateNode1.portalProtocol().storeContent(
|
||||||
|
contentKeyBytes, contentId, contentValueBytes
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let
|
||||||
|
res1 = await stateNode1.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
res2 = await stateNode2.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
res1.isOk()
|
||||||
|
res1.get() == expectedContentValue
|
||||||
|
res1.get().code == expectedContentValue.code
|
||||||
|
res2.isOk()
|
||||||
|
res2.get() == expectedContentValue
|
||||||
|
res2.get().code == expectedContentValue.code
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
|
@ -1,118 +0,0 @@
|
||||||
# Fluffy
|
|
||||||
# Copyright (c) 2023-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.
|
|
||||||
|
|
||||||
import
|
|
||||||
std/os,
|
|
||||||
chronos,
|
|
||||||
testutils/unittests,
|
|
||||||
stew/[byteutils, results],
|
|
||||||
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
|
||||||
../../network/wire/[portal_protocol, portal_stream],
|
|
||||||
../../network/history/[history_content, history_network],
|
|
||||||
../../network/state/[state_network, state_gossip],
|
|
||||||
../../database/content_db,
|
|
||||||
.././test_helpers,
|
|
||||||
../../eth_data/yaml_utils
|
|
||||||
|
|
||||||
const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/validation/"
|
|
||||||
|
|
||||||
procSuite "State Network Gossip":
|
|
||||||
let rng = newRng()
|
|
||||||
|
|
||||||
asyncTest "Test Gossip of Account Trie Node Offer":
|
|
||||||
const file = testVectorDir / "recursive_gossip.yaml"
|
|
||||||
|
|
||||||
type YamlRecursiveGossipKV = object
|
|
||||||
content_key: string
|
|
||||||
content_value: string
|
|
||||||
|
|
||||||
type YamlRecursiveGossipData = object
|
|
||||||
state_root: string
|
|
||||||
recursive_gossip: seq[YamlRecursiveGossipKV]
|
|
||||||
|
|
||||||
type YamlRecursiveGossipKVs = seq[YamlRecursiveGossipData]
|
|
||||||
|
|
||||||
let
|
|
||||||
testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
|
||||||
raiseAssert "Cannot read test vector: " & error
|
|
||||||
recursiveGossipSteps = testCase[0].recursive_gossip
|
|
||||||
numOfClients = recursiveGossipSteps.len() - 1
|
|
||||||
|
|
||||||
var clients: seq[StateNetwork]
|
|
||||||
|
|
||||||
for i in 0 .. numOfClients:
|
|
||||||
let
|
|
||||||
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20400 + i))
|
|
||||||
db = ContentDB.new("", uint32.high, inMemory = true)
|
|
||||||
sm = StreamManager.new(node)
|
|
||||||
hn = HistoryNetwork.new(node, db, sm, FinishedAccumulator())
|
|
||||||
proto = StateNetwork.new(node, db, sm, historyNetwork = Opt.some(hn))
|
|
||||||
proto.start()
|
|
||||||
clients.add(proto)
|
|
||||||
|
|
||||||
for i in 0 .. numOfClients - 1:
|
|
||||||
let
|
|
||||||
currentNode = clients[i]
|
|
||||||
nextNode = clients[i + 1]
|
|
||||||
|
|
||||||
check:
|
|
||||||
currentNode.portalProtocol.addNode(nextNode.portalProtocol.localNode) == Added
|
|
||||||
(await currentNode.portalProtocol.ping(nextNode.portalProtocol.localNode)).isOk()
|
|
||||||
|
|
||||||
let
|
|
||||||
blockHeader = BlockHeader(
|
|
||||||
stateRoot: Hash256.fromHex(
|
|
||||||
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
headerRlp = rlp.encode(blockHeader)
|
|
||||||
blockHeaderWithProof = BlockHeaderWithProof(
|
|
||||||
header: ByteList.init(headerRlp), proof: BlockHeaderProof.init()
|
|
||||||
)
|
|
||||||
value = recursiveGossipSteps[0].content_value.hexToSeqByte()
|
|
||||||
decodedValue = AccountTrieNodeOffer.decode(value).get()
|
|
||||||
contentKey = history_content.ContentKey
|
|
||||||
.init(history_content.ContentType.blockHeader, decodedValue.blockHash)
|
|
||||||
.encode()
|
|
||||||
contentId = history_content.toContentId(contentKey)
|
|
||||||
|
|
||||||
clients[i].contentDB.put(contentId, SSZ.encode(blockHeaderWithProof))
|
|
||||||
|
|
||||||
for i in 0 .. numOfClients - 1:
|
|
||||||
let
|
|
||||||
pair = recursiveGossipSteps[i]
|
|
||||||
currentNode = clients[i]
|
|
||||||
nextNode = clients[i + 1]
|
|
||||||
|
|
||||||
key = ByteList.init(pair.content_key.hexToSeqByte())
|
|
||||||
decodedKey = state_content.ContentKey.decode(key).valueOr:
|
|
||||||
raiseAssert "Cannot decode key"
|
|
||||||
|
|
||||||
nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte())
|
|
||||||
decodedNextKey = state_content.ContentKey.decode(nextKey).valueOr:
|
|
||||||
raiseAssert "Cannot decode key"
|
|
||||||
|
|
||||||
value = pair.content_value.hexToSeqByte()
|
|
||||||
decodedValue = AccountTrieNodeOffer.decode(value).get()
|
|
||||||
nextValue = recursiveGossipSteps[1].content_value.hexToSeqByte()
|
|
||||||
nextDecodedValue = AccountTrieNodeOffer.decode(nextValue).get()
|
|
||||||
nextRetrievalValue = nextDecodedValue.toRetrievalValue()
|
|
||||||
|
|
||||||
if i == 0:
|
|
||||||
await currentNode.portalProtocol.gossipOffer(
|
|
||||||
Opt.none(NodeId), key, value, decodedKey.accountTrieNodeKey, decodedValue
|
|
||||||
)
|
|
||||||
|
|
||||||
await sleepAsync(100.milliseconds) #TODO figure out how to get rid of this sleep
|
|
||||||
|
|
||||||
check (await nextNode.getAccountTrieNode(decodedNextKey.accountTrieNodeKey)) ==
|
|
||||||
Opt.some(nextRetrievalValue)
|
|
||||||
|
|
||||||
for i in 0 .. numOfClients:
|
|
||||||
await clients[i].portalProtocol.baseProtocol.closeWait()
|
|
||||||
|
|
||||||
# TODO Add tests for Contract Trie Node Offer & Contract Code Offer
|
|
|
@ -0,0 +1,390 @@
|
||||||
|
# Fluffy
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import
|
||||||
|
std/os,
|
||||||
|
testutils/unittests,
|
||||||
|
chronos,
|
||||||
|
stew/byteutils,
|
||||||
|
eth/common,
|
||||||
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||||
|
eth/p2p/discoveryv5/routing_table,
|
||||||
|
../../common/common_utils,
|
||||||
|
../../network/wire/[portal_protocol, portal_stream],
|
||||||
|
../../network/state/[state_content, state_network],
|
||||||
|
../../database/content_db,
|
||||||
|
./state_test_helpers
|
||||||
|
|
||||||
|
procSuite "State Network - Offer Content":
|
||||||
|
const
|
||||||
|
STATE_NODE1_PORT = 20502
|
||||||
|
STATE_NODE2_PORT = 20503
|
||||||
|
|
||||||
|
let rng = newRng()
|
||||||
|
|
||||||
|
# Single state instance tests
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Offer account trie nodes":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
# no state root yet
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.accountTrieNodeKey,
|
||||||
|
AccountTrieNodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set bad state root
|
||||||
|
let badStateRoot = KeccakHash.fromBytes(
|
||||||
|
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
|
||||||
|
)
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, badStateRoot)
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.accountTrieNodeKey,
|
||||||
|
AccountTrieNodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode1.containsId(contentId)
|
||||||
|
|
||||||
|
let processRes = await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.accountTrieNodeKey,
|
||||||
|
AccountTrieNodeOffer,
|
||||||
|
)
|
||||||
|
check processRes.isOk()
|
||||||
|
|
||||||
|
let getRes =
|
||||||
|
await stateNode1.stateNetwork.getAccountTrieNode(contentKey.accountTrieNodeKey)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Offer contract trie nodes":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
# no state root yet
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractTrieNodeKey,
|
||||||
|
ContractTrieNodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set bad state root
|
||||||
|
let badStateRoot = KeccakHash.fromBytes(
|
||||||
|
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
|
||||||
|
)
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, badStateRoot)
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractTrieNodeKey,
|
||||||
|
ContractTrieNodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode1.containsId(contentId)
|
||||||
|
|
||||||
|
let processRes = await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractTrieNodeKey,
|
||||||
|
ContractTrieNodeOffer,
|
||||||
|
)
|
||||||
|
check processRes.isOk()
|
||||||
|
|
||||||
|
let getRes = await stateNode1.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
asyncTest "Single state instance - Offer contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractCodeOffer.decode(contentValueBytes).get()
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
|
||||||
|
# no state root yet
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractCodeKey,
|
||||||
|
ContractCodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set bad state root
|
||||||
|
let badStateRoot = KeccakHash.fromBytes(
|
||||||
|
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
|
||||||
|
)
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, badStateRoot)
|
||||||
|
check (
|
||||||
|
await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractCodeKey,
|
||||||
|
ContractCodeOffer,
|
||||||
|
)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode1.containsId(contentId)
|
||||||
|
|
||||||
|
let processRes = await stateNode1.stateNetwork.processOffer(
|
||||||
|
Opt.none(NodeId),
|
||||||
|
contentKeyBytes,
|
||||||
|
contentValueBytes,
|
||||||
|
contentKey.contractCodeKey,
|
||||||
|
ContractCodeOffer,
|
||||||
|
)
|
||||||
|
check processRes.isOk()
|
||||||
|
|
||||||
|
let getRes =
|
||||||
|
await stateNode1.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
stateNode1.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().code == contentValue.toRetrievalValue().code
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
|
||||||
|
# Two state instances tests - State node 1 offers content to state node 2
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Offer account trie nodes":
|
||||||
|
const file = testVectorDir / "account_trie_node.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
contentKV = ContentKV(contentKey: contentKeyBytes, content: contentValueBytes)
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let offerResult =
|
||||||
|
await stateNode1.portalProtocol.offer(stateNode2.localNode(), @[contentKV])
|
||||||
|
check offerResult.isOk()
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
let getRes =
|
||||||
|
await stateNode2.stateNetwork.getAccountTrieNode(contentKey.accountTrieNodeKey)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Offer contract trie nodes":
|
||||||
|
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||||
|
contentKV = ContentKV(contentKey: contentKeyBytes, content: contentValueBytes)
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let offerResult =
|
||||||
|
await stateNode1.portalProtocol.offer(stateNode2.localNode(), @[contentKV])
|
||||||
|
check offerResult.isOk()
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
let getRes = await stateNode2.stateNetwork.getContractTrieNode(
|
||||||
|
contentKey.contractTrieNodeKey
|
||||||
|
)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().node == contentValue.toRetrievalValue().node
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
||||||
|
|
||||||
|
asyncTest "Two state instances - Offer contract bytecode":
|
||||||
|
const file = testVectorDir / "contract_bytecode.yaml"
|
||||||
|
|
||||||
|
let testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
|
||||||
|
for testData in testCase:
|
||||||
|
let
|
||||||
|
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||||
|
contentKeyBytes = testData.content_key.hexToSeqByte().ByteList
|
||||||
|
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||||
|
contentId = toContentId(contentKeyBytes)
|
||||||
|
contentValueBytes = testData.content_value_offer.hexToSeqByte()
|
||||||
|
contentValue = ContractCodeOffer.decode(contentValueBytes).get()
|
||||||
|
contentKV = ContentKV(contentKey: contentKeyBytes, content: contentValueBytes)
|
||||||
|
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||||
|
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||||
|
|
||||||
|
stateNode1.start()
|
||||||
|
stateNode2.start()
|
||||||
|
|
||||||
|
check:
|
||||||
|
stateNode1.portalProtocol().addNode(stateNode2.localNode()) == Added
|
||||||
|
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||||
|
|
||||||
|
# set valid state root
|
||||||
|
stateNode1.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
|
let offerResult =
|
||||||
|
await stateNode1.portalProtocol.offer(stateNode2.localNode(), @[contentKV])
|
||||||
|
check offerResult.isOk()
|
||||||
|
|
||||||
|
# wait for offer to be processed by state node 2
|
||||||
|
while not stateNode2.stateNetwork.contentQueue.empty():
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
let getRes =
|
||||||
|
await stateNode2.stateNetwork.getContractCode(contentKey.contractCodeKey)
|
||||||
|
check:
|
||||||
|
stateNode2.containsId(contentId)
|
||||||
|
getRes.isOk()
|
||||||
|
getRes.get() == contentValue.toRetrievalValue()
|
||||||
|
getRes.get().code == contentValue.toRetrievalValue().code
|
||||||
|
|
||||||
|
await stateNode1.stop()
|
||||||
|
await stateNode2.stop()
|
Loading…
Reference in New Issue