Update Fluffy State Network to match Portal spec addressHash change (#2548)
* Update state network to use addressHash instead of address in contract trie and contract code content keys. * Fix path calculation bug in getParent when working with extension nodes. * Bump portal spec tests repo. * Finish updating tests due to portal test vector changes. * Update Fluffy state bridge to use addressHash. * Update Fluffy book with correct commands for running portal hive tests.
This commit is contained in:
parent
d5786758b5
commit
93a160b569
|
@ -17,11 +17,11 @@ go build .
|
|||
Example commands for running test suites:
|
||||
|
||||
```sh
|
||||
# Run the history tests with the 3 different clients
|
||||
./hive --sim history --client fluffy,trin,ultralight
|
||||
# Run the portal tests with only the fluffy client
|
||||
./hive --sim portal --client fluffy
|
||||
|
||||
# Run the state tests with only the fluffy client
|
||||
./hive --sim state --client fluffy
|
||||
# Run the portal tests with the 3 different clients
|
||||
./hive --sim portal --client fluffy,trin,ultralight
|
||||
|
||||
# Access results through web-ui:
|
||||
```sh
|
||||
|
|
|
@ -23,7 +23,7 @@ export ssz_serialization, common_types, hash, results
|
|||
type
|
||||
NodeHash* = KeccakHash
|
||||
CodeHash* = KeccakHash
|
||||
Address* = EthAddress
|
||||
AddressHash* = KeccakHash
|
||||
|
||||
ContentType* = enum
|
||||
# Note: Need to add this unused value as a case object with an enum without
|
||||
|
@ -43,12 +43,12 @@ type
|
|||
nodeHash*: NodeHash
|
||||
|
||||
ContractTrieNodeKey* = object
|
||||
address*: Address
|
||||
addressHash*: AddressHash
|
||||
path*: Nibbles
|
||||
nodeHash*: NodeHash
|
||||
|
||||
ContractCodeKey* = object
|
||||
address*: Address
|
||||
addressHash*: AddressHash
|
||||
codeHash*: CodeHash
|
||||
|
||||
ContentKey* = object
|
||||
|
@ -68,12 +68,15 @@ func init*(T: type AccountTrieNodeKey, path: Nibbles, nodeHash: NodeHash): T =
|
|||
AccountTrieNodeKey(path: path, nodeHash: nodeHash)
|
||||
|
||||
func init*(
|
||||
T: type ContractTrieNodeKey, address: Address, path: Nibbles, nodeHash: NodeHash
|
||||
T: type ContractTrieNodeKey,
|
||||
addressHash: AddressHash,
|
||||
path: Nibbles,
|
||||
nodeHash: NodeHash,
|
||||
): T =
|
||||
ContractTrieNodeKey(address: address, path: path, nodeHash: nodeHash)
|
||||
ContractTrieNodeKey(addressHash: addressHash, path: path, nodeHash: nodeHash)
|
||||
|
||||
func init*(T: type ContractCodeKey, address: Address, codeHash: CodeHash): T =
|
||||
ContractCodeKey(address: address, codeHash: codeHash)
|
||||
func init*(T: type ContractCodeKey, addressHash: AddressHash, codeHash: CodeHash): T =
|
||||
ContractCodeKey(addressHash: addressHash, codeHash: codeHash)
|
||||
|
||||
func toContentKey*(key: AccountTrieNodeKey): ContentKey =
|
||||
ContentKey(contentType: accountTrieNode, accountTrieNodeKey: key)
|
||||
|
|
|
@ -67,7 +67,7 @@ proc getNextNodeHash(
|
|||
raiseAssert(e.msg)
|
||||
|
||||
proc getAccountProof(
|
||||
n: StateNetwork, stateRoot: KeccakHash, address: Address
|
||||
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
||||
): Future[Opt[TrieProof]] {.async: (raises: [CancelledError]).} =
|
||||
let nibbles = address.toPath().unpackNibbles()
|
||||
|
||||
|
@ -94,13 +94,14 @@ proc getAccountProof(
|
|||
Opt.some(proof)
|
||||
|
||||
proc getStorageProof(
|
||||
n: StateNetwork, storageRoot: KeccakHash, address: Address, storageKey: UInt256
|
||||
n: StateNetwork, storageRoot: KeccakHash, address: EthAddress, storageKey: UInt256
|
||||
): Future[Opt[TrieProof]] {.async: (raises: [CancelledError]).} =
|
||||
let nibbles = storageKey.toPath().unpackNibbles()
|
||||
|
||||
var
|
||||
addressHash = keccakHash(address)
|
||||
nibblesIdx = 0
|
||||
key = ContractTrieNodeKey.init(address, Nibbles.empty(), storageRoot)
|
||||
key = ContractTrieNodeKey.init(addressHash, Nibbles.empty(), storageRoot)
|
||||
proof = TrieProof.empty()
|
||||
|
||||
while nibblesIdx < nibbles.len():
|
||||
|
@ -116,12 +117,12 @@ proc getStorageProof(
|
|||
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
|
||||
break
|
||||
|
||||
key = ContractTrieNodeKey.init(address, nextPath, nextNodeHash)
|
||||
key = ContractTrieNodeKey.init(addressHash, nextPath, nextNodeHash)
|
||||
|
||||
Opt.some(proof)
|
||||
|
||||
proc getAccount(
|
||||
n: StateNetwork, blockHash: BlockHash, address: Address
|
||||
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
||||
): Future[Opt[Account]] {.async: (raises: [CancelledError]).} =
|
||||
let
|
||||
stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
|
||||
|
@ -138,7 +139,7 @@ proc getAccount(
|
|||
|
||||
# Used by: eth_getBalance,
|
||||
proc getBalance*(
|
||||
n: StateNetwork, blockHash: BlockHash, address: Address
|
||||
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
||||
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
||||
let account = (await n.getAccount(blockHash, address)).valueOr:
|
||||
return Opt.none(UInt256)
|
||||
|
@ -147,7 +148,7 @@ proc getBalance*(
|
|||
|
||||
# Used by: eth_getTransactionCount
|
||||
proc getTransactionCount*(
|
||||
n: StateNetwork, blockHash: BlockHash, address: Address
|
||||
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
||||
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
|
||||
let account = (await n.getAccount(blockHash, address)).valueOr:
|
||||
return Opt.none(AccountNonce)
|
||||
|
@ -156,7 +157,7 @@ proc getTransactionCount*(
|
|||
|
||||
# Used by: eth_getStorageAt
|
||||
proc getStorageAt*(
|
||||
n: StateNetwork, blockHash: BlockHash, address: Address, slotKey: UInt256
|
||||
n: StateNetwork, blockHash: BlockHash, address: EthAddress, slotKey: UInt256
|
||||
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
||||
let
|
||||
account = (await n.getAccount(blockHash, address)).valueOr:
|
||||
|
@ -172,12 +173,12 @@ proc getStorageAt*(
|
|||
|
||||
# Used by: eth_getCode
|
||||
proc getCode*(
|
||||
n: StateNetwork, blockHash: BlockHash, address: Address
|
||||
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
||||
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
|
||||
let
|
||||
account = (await n.getAccount(blockHash, address)).valueOr:
|
||||
return Opt.none(Bytecode)
|
||||
contractCodeKey = ContractCodeKey.init(address, account.codeHash)
|
||||
contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash)
|
||||
|
||||
let contractCodeRetrieval = (await n.getContractCode(contractCodeKey)).valueOr:
|
||||
warn "Failed to get contract code"
|
||||
|
|
|
@ -60,7 +60,9 @@ func getParent(p: ProofWithPath): ProofWithPath =
|
|||
# leaf or extension node so we need to remove one or more nibbles
|
||||
let (_, _, prefixNibbles) = decodePrefix(parentEndNode.listElem(0))
|
||||
|
||||
parentProof.withPath(unpackedNibbles.dropN(prefixNibbles.len()).packNibbles())
|
||||
parentProof.withPath(
|
||||
unpackedNibbles.dropN(prefixNibbles.unpackNibbles().len()).packNibbles()
|
||||
)
|
||||
except RlpError as e:
|
||||
raiseAssert(e.msg)
|
||||
|
||||
|
@ -79,7 +81,7 @@ func getParent*(offerWithKey: ContractTrieOfferWithKey): ContractTrieOfferWithKe
|
|||
(key, offer) = offerWithKey
|
||||
parent = offer.storageProof.withPath(key.path).getParent()
|
||||
parentKey = ContractTrieNodeKey.init(
|
||||
key.address, parent.path, keccakHash(parent.proof[^1].asSeq())
|
||||
key.addressHash, parent.path, keccakHash(parent.proof[^1].asSeq())
|
||||
)
|
||||
parentOffer =
|
||||
ContractTrieNodeOffer.init(parent.proof, offer.accountProof, offer.blockHash)
|
||||
|
@ -134,12 +136,12 @@ proc recursiveGossipOffer*(
|
|||
offerBytes: seq[byte],
|
||||
key: AccountTrieNodeKey,
|
||||
offer: AccountTrieNodeOffer,
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
||||
await gossipOffer(p, srcNodeId, keyBytes, offerBytes, key, offer)
|
||||
|
||||
# root node, recursive gossip is finished
|
||||
if key.path.unpackNibbles().len() == 0:
|
||||
return
|
||||
return keyBytes
|
||||
|
||||
# continue the recursive gossip by sharing the parent offer with peers
|
||||
let
|
||||
|
@ -159,12 +161,12 @@ proc recursiveGossipOffer*(
|
|||
offerBytes: seq[byte],
|
||||
key: ContractTrieNodeKey,
|
||||
offer: ContractTrieNodeOffer,
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
||||
await gossipOffer(p, srcNodeId, keyBytes, offerBytes, key, offer)
|
||||
|
||||
# root node, recursive gossip is finished
|
||||
if key.path.unpackNibbles().len() == 0:
|
||||
return
|
||||
return keyBytes
|
||||
|
||||
# continue the recursive gossip by sharing the parent offer with peers
|
||||
let
|
||||
|
|
|
@ -87,7 +87,7 @@ func removeLeafKeyEndNibbles*(
|
|||
func toPath*(hash: KeccakHash): Nibbles {.inline.} =
|
||||
Nibbles.init(hash.data, isEven = true)
|
||||
|
||||
func toPath*(address: Address): Nibbles {.inline.} =
|
||||
func toPath*(address: EthAddress): Nibbles {.inline.} =
|
||||
keccakHash(address).toPath()
|
||||
|
||||
func toPath*(slotKey: UInt256): Nibbles {.inline.} =
|
||||
|
|
|
@ -156,7 +156,7 @@ proc validateOffer*(
|
|||
): Result[void, string] =
|
||||
?validateTrieProof(
|
||||
trustedStateRoot,
|
||||
key.address.toPath(),
|
||||
key.addressHash.toPath(),
|
||||
offer.accountProof,
|
||||
allowKeyEndInPathForLeafs = true,
|
||||
)
|
||||
|
@ -172,7 +172,7 @@ proc validateOffer*(
|
|||
): Result[void, string] =
|
||||
?validateTrieProof(
|
||||
trustedStateRoot,
|
||||
key.address.toPath(),
|
||||
key.addressHash.toPath(),
|
||||
offer.accountProof,
|
||||
allowKeyEndInPathForLeafs = true,
|
||||
)
|
||||
|
|
|
@ -26,17 +26,11 @@ export yaml_utils
|
|||
const testVectorDir* = "./vendor/portal-spec-tests/tests/mainnet/state/validation/"
|
||||
|
||||
type
|
||||
YamlTrieNodeRecursiveGossipKV* = ref object
|
||||
content_key*: string
|
||||
content_value_offer*: string
|
||||
content_value_retrieval*: string
|
||||
|
||||
YamlTrieNodeKV* = object
|
||||
state_root*: string
|
||||
content_key*: string
|
||||
content_value_offer*: string
|
||||
content_value_retrieval*: string
|
||||
recursive_gossip*: YamlTrieNodeRecursiveGossipKV
|
||||
|
||||
YamlTrieNodeKVs* = seq[YamlTrieNodeKV]
|
||||
|
||||
|
@ -48,16 +42,6 @@ type
|
|||
|
||||
YamlContractBytecodeKVs* = seq[YamlContractBytecodeKV]
|
||||
|
||||
YamlRecursiveGossipKV* = object
|
||||
content_key*: string
|
||||
content_value*: string
|
||||
|
||||
YamlRecursiveGossipData* = object
|
||||
state_root*: string
|
||||
recursive_gossip*: seq[YamlRecursiveGossipKV]
|
||||
|
||||
YamlRecursiveGossipKVs* = seq[YamlRecursiveGossipData]
|
||||
|
||||
func asNibbles*(key: openArray[byte], isEven = true): Nibbles =
|
||||
Nibbles.init(key, isEven)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import
|
||||
unittest2,
|
||||
stew/byteutils,
|
||||
eth/common,
|
||||
../../network/state/state_content,
|
||||
../../eth_data/yaml_utils
|
||||
|
||||
|
@ -58,10 +59,10 @@ suite "State Content Keys":
|
|||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
packedNibbles = packNibbles(testCase.path)
|
||||
address = Address.fromHex(testCase.address)
|
||||
addressHash = EthAddress.fromHex(testCase.address).keccakHash()
|
||||
nodeHash = NodeHash.fromHex(testCase.node_hash)
|
||||
contentKey =
|
||||
ContractTrieNodeKey.init(address, packedNibbles, nodeHash).toContentKey()
|
||||
ContractTrieNodeKey.init(addressHash, packedNibbles, nodeHash).toContentKey()
|
||||
encoded = contentKey.encode()
|
||||
|
||||
check:
|
||||
|
@ -73,7 +74,9 @@ suite "State Content Keys":
|
|||
decoded.isOk()
|
||||
decoded.value().contentType == contractTrieNode
|
||||
decoded.value().contractTrieNodeKey ==
|
||||
ContractTrieNodeKey(address: address, path: packedNibbles, nodeHash: nodeHash)
|
||||
ContractTrieNodeKey(
|
||||
addressHash: addressHash, path: packedNibbles, nodeHash: nodeHash
|
||||
)
|
||||
|
||||
test "Encode/decode ContractCodeKey":
|
||||
const file = testVectorDir & "contract_bytecode_key.yaml"
|
||||
|
@ -88,9 +91,9 @@ suite "State Content Keys":
|
|||
testCase = YamlContractBytecodeKey.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
address = Address.fromHex(testCase.address)
|
||||
addressHash = EthAddress.fromHex(testCase.address).keccakHash()
|
||||
codeHash = CodeHash.fromHex(testCase.code_hash)
|
||||
contentKey = ContractCodeKey.init(address, codeHash).toContentKey()
|
||||
contentKey = ContractCodeKey.init(addressHash, codeHash).toContentKey()
|
||||
encoded = contentKey.encode()
|
||||
|
||||
check:
|
||||
|
@ -101,7 +104,7 @@ suite "State Content Keys":
|
|||
check:
|
||||
decoded.isOk()
|
||||
decoded.value().contentType == contractCode
|
||||
decoded.value().contractCodeKey.address == address
|
||||
decoded.value().contractCodeKey.addressHash == addressHash
|
||||
decoded.value().contractCodeKey.codeHash == codeHash
|
||||
|
||||
test "Invalid prefix - 0 value":
|
||||
|
|
|
@ -43,8 +43,8 @@ suite "State Endpoints - Genesis JSON Files":
|
|||
let
|
||||
proof = accountState.generateAccountProof(address)
|
||||
leafNode = proof[^1]
|
||||
addressHash = keccakHash(address).data
|
||||
path = removeLeafKeyEndNibbles(Nibbles.init(addressHash, true), leafNode)
|
||||
addressHash = keccakHash(address)
|
||||
path = removeLeafKeyEndNibbles(Nibbles.init(addressHash.data, true), leafNode)
|
||||
key = AccountTrieNodeKey.init(path, keccakHash(leafNode.asSeq()))
|
||||
offer = AccountTrieNodeOffer(proof: proof)
|
||||
|
||||
|
@ -94,8 +94,9 @@ suite "State Endpoints - Genesis JSON Files":
|
|||
block:
|
||||
# store the code
|
||||
let
|
||||
key =
|
||||
ContractCodeKey(address: address, codeHash: keccakHash(account.code))
|
||||
key = ContractCodeKey(
|
||||
addressHash: addressHash, codeHash: keccakHash(account.code)
|
||||
)
|
||||
value = ContractCodeRetrieval(code: Bytecode.init(account.code))
|
||||
|
||||
let contentKey = key.toContentKey().encode()
|
||||
|
@ -121,7 +122,9 @@ suite "State Endpoints - Genesis JSON Files":
|
|||
Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true), leafNode
|
||||
)
|
||||
key = ContractTrieNodeKey(
|
||||
address: address, path: path, nodeHash: keccakHash(leafNode.asSeq())
|
||||
addressHash: addressHash,
|
||||
path: path,
|
||||
nodeHash: keccakHash(leafNode.asSeq()),
|
||||
)
|
||||
offer =
|
||||
ContractTrieNodeOffer(storageProof: storageProof, accountProof: proof)
|
||||
|
|
|
@ -27,10 +27,10 @@ procSuite "State Endpoints":
|
|||
let rng = newRng()
|
||||
|
||||
asyncTest "Gossip then query getBalance and getTransactionCount":
|
||||
const file = testVectorDir / "recursive_gossip.yaml"
|
||||
const file = testVectorDir / "account_trie_node.yaml"
|
||||
|
||||
let
|
||||
testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||
|
@ -45,16 +45,17 @@ procSuite "State Endpoints":
|
|||
(await stateNode2.portalProtocol().ping(stateNode1.localNode())).isOk()
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
if i != 0 and i != 3:
|
||||
# only using the leaf nodes from the test data
|
||||
continue
|
||||
|
||||
let
|
||||
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
leafData = testData.recursive_gossip[0]
|
||||
leafData = testData
|
||||
contentKeyBytes = leafData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||
contentId = toContentId(contentKeyBytes)
|
||||
contentValueBytes = leafData.content_value.hexToSeqByte()
|
||||
contentValueBytes = leafData.content_value_offer.hexToSeqByte()
|
||||
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||
|
||||
# set valid state root
|
||||
|
@ -62,7 +63,7 @@ procSuite "State Endpoints":
|
|||
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||
|
||||
# offer the leaf node
|
||||
await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
let rootKeyBytes = await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
Opt.none(NodeId),
|
||||
contentKeyBytes,
|
||||
contentValueBytes,
|
||||
|
@ -70,16 +71,14 @@ procSuite "State Endpoints":
|
|||
contentValue,
|
||||
)
|
||||
|
||||
# wait for recursive gossip to complete
|
||||
for node in testData.recursive_gossip:
|
||||
let keyBytes = node.content_key.hexToSeqByte().ContentKeyByteList
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(keyBytes))
|
||||
await stateNode1.waitUntilContentAvailable(toContentId(rootKeyBytes))
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(rootKeyBytes))
|
||||
|
||||
let
|
||||
address =
|
||||
if i == 0:
|
||||
EthAddress.fromHex("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
|
||||
elif i == 2:
|
||||
elif i == 3:
|
||||
EthAddress.fromHex("0x1584a2c066b7a455dbd6ae2807a7334e83c35fa5")
|
||||
else:
|
||||
raiseAssert("Invalid test case")
|
||||
|
@ -134,11 +133,14 @@ procSuite "State Endpoints":
|
|||
|
||||
asyncTest "Gossip then query getStorageAt and getCode":
|
||||
const
|
||||
file = testVectorDir / "recursive_gossip.yaml"
|
||||
accountTrieFile = testVectorDir / "account_trie_node.yaml"
|
||||
contractTrieFile = testVectorDir / "contract_storage_trie_node.yaml"
|
||||
bytecodeFile = testVectorDir / "contract_bytecode.yaml"
|
||||
|
||||
let
|
||||
testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
accountTrieTestCase = YamlTrieNodeKVs.loadFromYaml(accountTrieFile).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
contractTrieTestCase = YamlTrieNodeKVs.loadFromYaml(contractTrieFile).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
stateNode1 = newStateNode(rng, STATE_NODE1_PORT)
|
||||
stateNode2 = newStateNode(rng, STATE_NODE2_PORT)
|
||||
|
@ -155,13 +157,13 @@ procSuite "State Endpoints":
|
|||
block:
|
||||
# seed the account data
|
||||
let
|
||||
testData = testCase[0]
|
||||
testData = accountTrieTestCase[0]
|
||||
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
leafData = testData.recursive_gossip[0]
|
||||
leafData = testData
|
||||
contentKeyBytes = leafData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||
contentId = toContentId(contentKeyBytes)
|
||||
contentValueBytes = leafData.content_value.hexToSeqByte()
|
||||
contentValueBytes = leafData.content_value_offer.hexToSeqByte()
|
||||
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||
|
||||
# set valid state root
|
||||
|
@ -169,7 +171,7 @@ procSuite "State Endpoints":
|
|||
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||
|
||||
# offer the leaf node
|
||||
await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
let rootKeyBytes = await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
Opt.none(NodeId),
|
||||
contentKeyBytes,
|
||||
contentValueBytes,
|
||||
|
@ -177,16 +179,19 @@ procSuite "State Endpoints":
|
|||
contentValue,
|
||||
)
|
||||
|
||||
# wait for gossip to complete
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(rootKeyBytes))
|
||||
|
||||
block:
|
||||
# seed the storage data
|
||||
let
|
||||
testData = testCase[1]
|
||||
testData = contractTrieTestCase[0]
|
||||
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
leafData = testData.recursive_gossip[0]
|
||||
leafData = testData
|
||||
contentKeyBytes = leafData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||
contentId = toContentId(contentKeyBytes)
|
||||
contentValueBytes = leafData.content_value.hexToSeqByte()
|
||||
contentValueBytes = leafData.content_value_offer.hexToSeqByte()
|
||||
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||
|
||||
# set valid state root
|
||||
|
@ -194,7 +199,7 @@ procSuite "State Endpoints":
|
|||
stateNode2.mockBlockHashToStateRoot(contentValue.blockHash, stateRoot)
|
||||
|
||||
# offer the leaf node
|
||||
await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
let storageRootKeyBytes = await stateNode1.portalProtocol.recursiveGossipOffer(
|
||||
Opt.none(NodeId),
|
||||
contentKeyBytes,
|
||||
contentValueBytes,
|
||||
|
@ -202,10 +207,8 @@ procSuite "State Endpoints":
|
|||
contentValue,
|
||||
)
|
||||
|
||||
# wait for recursive gossip to complete
|
||||
for node in testData.recursive_gossip:
|
||||
let keyBytes = node.content_key.hexToSeqByte().ContentKeyByteList
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(keyBytes))
|
||||
# wait for gossip to complete
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(storageRootKeyBytes))
|
||||
|
||||
let
|
||||
address = EthAddress.fromHex("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
|
||||
|
|
|
@ -68,7 +68,9 @@ suite "State Gossip getParent - Genesis JSON Files":
|
|||
(accountState, storageStates) = accounts.toState()
|
||||
|
||||
for address, account in accounts:
|
||||
let accountProof = accountState.generateAccountProof(address)
|
||||
let
|
||||
addressHash = address.keccakHash()
|
||||
accountProof = accountState.generateAccountProof(address)
|
||||
|
||||
if account.code.len() > 0:
|
||||
let storageState = storageStates[address]
|
||||
|
@ -82,7 +84,9 @@ suite "State Gossip getParent - Genesis JSON Files":
|
|||
Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true), leafNode
|
||||
)
|
||||
key = ContractTrieNodeKey(
|
||||
address: address, path: path, nodeHash: keccakHash(leafNode.asSeq())
|
||||
addressHash: addressHash,
|
||||
path: path,
|
||||
nodeHash: keccakHash(leafNode.asSeq()),
|
||||
)
|
||||
offer = ContractTrieNodeOffer(
|
||||
storageProof: storageProof, accountProof: accountProof
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
std/[os, strutils],
|
||||
std/os,
|
||||
results,
|
||||
unittest2,
|
||||
stew/byteutils,
|
||||
eth/common,
|
||||
../../common/common_utils,
|
||||
../../network/state/[state_content, state_gossip],
|
||||
./state_test_helpers
|
||||
|
||||
|
@ -23,29 +22,26 @@ suite "State Gossip getParent - Test Vectors":
|
|||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
if i == 0 or i == 3:
|
||||
let
|
||||
parentTestData = testCase[i + 1]
|
||||
key = ContentKey
|
||||
.decode(testData.content_key.hexToSeqByte().ContentKeyByteList)
|
||||
.get()
|
||||
offer = AccountTrieNodeOffer
|
||||
.decode(testData.content_value_offer.hexToSeqByte())
|
||||
.get()
|
||||
|
||||
let key =
|
||||
ContentKey.decode(testData.content_key.hexToSeqByte().ContentKeyByteList).get()
|
||||
let offer =
|
||||
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
|
||||
|
||||
if i == 1: # second test case only has root node and no recursive gossip
|
||||
doAssertRaises(AssertionDefect):
|
||||
discard offer.withKey(key.accountTrieNodeKey).getParent()
|
||||
continue
|
||||
|
||||
let (parentKey, parentOffer) = offer.withKey(key.accountTrieNodeKey).getParent()
|
||||
check:
|
||||
parentKey.path.unpackNibbles().len() <
|
||||
key.accountTrieNodeKey.path.unpackNibbles().len()
|
||||
parentOffer.proof.len() == offer.proof.len() - 1
|
||||
parentKey.toContentKey().encode() ==
|
||||
testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() ==
|
||||
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||
parentOffer.toRetrievalValue().encode() ==
|
||||
testData.recursive_gossip.content_value_retrieval.hexToSeqByte()
|
||||
let (parentKey, parentOffer) = offer.withKey(key.accountTrieNodeKey).getParent()
|
||||
check:
|
||||
parentKey.path.unpackNibbles().len() <
|
||||
key.accountTrieNodeKey.path.unpackNibbles().len()
|
||||
parentOffer.proof.len() == offer.proof.len() - 1
|
||||
parentKey.toContentKey().encode() ==
|
||||
parentTestData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() == parentTestData.content_value_offer.hexToSeqByte()
|
||||
parentOffer.toRetrievalValue().encode() ==
|
||||
parentTestData.content_value_retrieval.hexToSeqByte()
|
||||
|
||||
test "Check contract storage trie node parent matches expected recursive gossip":
|
||||
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||
|
@ -54,88 +50,24 @@ suite "State Gossip getParent - Test Vectors":
|
|||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
|
||||
let key =
|
||||
ContentKey.decode(testData.content_key.hexToSeqByte().ContentKeyByteList).get()
|
||||
let offer =
|
||||
ContractTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
|
||||
|
||||
if i == 1: # second test case only has root node and no recursive gossip
|
||||
doAssertRaises(AssertionDefect):
|
||||
discard offer.withKey(key.contractTrieNodeKey).getParent()
|
||||
continue
|
||||
|
||||
let (parentKey, parentOffer) = offer.withKey(key.contractTrieNodeKey).getParent()
|
||||
check:
|
||||
parentKey.path.unpackNibbles().len() <
|
||||
key.contractTrieNodeKey.path.unpackNibbles().len()
|
||||
parentOffer.storageProof.len() == offer.storageProof.len() - 1
|
||||
parentKey.toContentKey().encode() ==
|
||||
testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() ==
|
||||
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||
parentOffer.toRetrievalValue().encode() ==
|
||||
testData.recursive_gossip.content_value_retrieval.hexToSeqByte()
|
||||
|
||||
test "Check each account trie node parent matches expected recursive gossip":
|
||||
const file = testVectorDir / "recursive_gossip.yaml"
|
||||
|
||||
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
continue
|
||||
|
||||
for j in 0 ..< testData.recursive_gossip.high:
|
||||
if i == 0:
|
||||
let
|
||||
parentTestData = testCase[i + 1]
|
||||
key = ContentKey
|
||||
.decode(
|
||||
testData.recursive_gossip[j].content_key.hexToSeqByte().ContentKeyByteList
|
||||
)
|
||||
.get()
|
||||
offer = AccountTrieNodeOffer
|
||||
.decode(testData.recursive_gossip[j].content_value.hexToSeqByte())
|
||||
.get()
|
||||
(parentKey, parentOffer) = offer.withKey(key.accountTrieNodeKey).getParent()
|
||||
|
||||
check:
|
||||
parentKey.path.unpackNibbles().len() <
|
||||
key.accountTrieNodeKey.path.unpackNibbles().len()
|
||||
parentOffer.proof.len() == offer.proof.len() - 1
|
||||
parentKey.toContentKey().encode() ==
|
||||
testData.recursive_gossip[j + 1].content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() ==
|
||||
testData.recursive_gossip[j + 1].content_value.hexToSeqByte()
|
||||
|
||||
test "Check each contract trie node parent matches expected recursive gossip":
|
||||
const file = testVectorDir / "recursive_gossip.yaml"
|
||||
|
||||
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
if i != 1:
|
||||
continue
|
||||
|
||||
for j in 0 ..< testData.recursive_gossip.high:
|
||||
let
|
||||
key = ContentKey
|
||||
.decode(
|
||||
testData.recursive_gossip[j].content_key.hexToSeqByte().ContentKeyByteList
|
||||
)
|
||||
.decode(testData.content_key.hexToSeqByte().ContentKeyByteList)
|
||||
.get()
|
||||
offer = ContractTrieNodeOffer
|
||||
.decode(testData.recursive_gossip[j].content_value.hexToSeqByte())
|
||||
.decode(testData.content_value_offer.hexToSeqByte())
|
||||
.get()
|
||||
(parentKey, parentOffer) = offer.withKey(key.contractTrieNodeKey).getParent()
|
||||
|
||||
let (parentKey, parentOffer) =
|
||||
offer.withKey(key.contractTrieNodeKey).getParent()
|
||||
check:
|
||||
parentKey.path.unpackNibbles().len() <
|
||||
key.contractTrieNodeKey.path.unpackNibbles().len()
|
||||
parentOffer.storageProof.len() == offer.storageProof.len() - 1
|
||||
parentKey.toContentKey().encode() ==
|
||||
testData.recursive_gossip[j + 1].content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() ==
|
||||
testData.recursive_gossip[j + 1].content_value.hexToSeqByte()
|
||||
parentTestData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentOffer.encode() == parentTestData.content_value_offer.hexToSeqByte()
|
||||
parentOffer.toRetrievalValue().encode() ==
|
||||
parentTestData.content_value_retrieval.hexToSeqByte()
|
||||
|
|
|
@ -42,10 +42,11 @@ procSuite "State Gossip - Gossip Offer":
|
|||
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
continue # skip scenario with no parent
|
||||
if i != 0 and i != 3:
|
||||
continue # skip scenarios with no parent
|
||||
|
||||
let
|
||||
parentTestData = testCase[i + 1]
|
||||
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
contentKeyBytes = testData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||
|
@ -54,11 +55,10 @@ procSuite "State Gossip - Gossip Offer":
|
|||
contentValue = AccountTrieNodeOffer.decode(contentValueBytes).get()
|
||||
|
||||
parentContentKeyBytes =
|
||||
testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentTestData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentContentKey = ContentKey.decode(parentContentKeyBytes).get()
|
||||
parentContentId = toContentId(parentContentKeyBytes)
|
||||
parentContentValueBytes =
|
||||
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||
parentContentValueBytes = parentTestData.content_value_offer.hexToSeqByte()
|
||||
parentContentValue = AccountTrieNodeOffer.decode(parentContentValueBytes).get()
|
||||
|
||||
# set valid state root
|
||||
|
@ -115,10 +115,11 @@ procSuite "State Gossip - Gossip Offer":
|
|||
(await stateNode1.portalProtocol().ping(stateNode2.localNode())).isOk()
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
continue # skip scenario with no parent
|
||||
if i != 0:
|
||||
continue # skip scenarios with no parent
|
||||
|
||||
let
|
||||
parentTestData = testCase[i + 1]
|
||||
stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
contentKeyBytes = testData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
contentKey = ContentKey.decode(contentKeyBytes).get()
|
||||
|
@ -127,11 +128,10 @@ procSuite "State Gossip - Gossip Offer":
|
|||
contentValue = ContractTrieNodeOffer.decode(contentValueBytes).get()
|
||||
|
||||
parentContentKeyBytes =
|
||||
testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentTestData.content_key.hexToSeqByte().ContentKeyByteList
|
||||
parentContentKey = ContentKey.decode(parentContentKeyBytes).get()
|
||||
parentContentId = toContentId(parentContentKeyBytes)
|
||||
parentContentValueBytes =
|
||||
testData.recursive_gossip.content_value_offer.hexToSeqByte()
|
||||
parentContentValueBytes = parentTestData.content_value_offer.hexToSeqByte()
|
||||
parentContentValue = ContractTrieNodeOffer.decode(parentContentValueBytes).get()
|
||||
|
||||
# set valid state root
|
||||
|
@ -225,161 +225,3 @@ procSuite "State Gossip - Gossip Offer":
|
|||
|
||||
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().ContentKeyByteList
|
||||
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.recursiveGossipOffer(
|
||||
Opt.none(NodeId),
|
||||
contentKeyBytes,
|
||||
contentValueBytes,
|
||||
contentKey.accountTrieNodeKey,
|
||||
contentValue,
|
||||
)
|
||||
|
||||
# wait for recursive gossip to complete
|
||||
for node in testData.recursive_gossip:
|
||||
let keyBytes = node.content_key.hexToSeqByte().ContentKeyByteList
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(keyBytes))
|
||||
|
||||
# check that all nodes were received by both state instances
|
||||
for kv in testData.recursive_gossip:
|
||||
let
|
||||
expectedKeyBytes = kv.content_key.hexToSeqByte().ContentKeyByteList
|
||||
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().ContentKeyByteList
|
||||
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.recursiveGossipOffer(
|
||||
Opt.none(NodeId),
|
||||
contentKeyBytes,
|
||||
contentValueBytes,
|
||||
contentKey.contractTrieNodeKey,
|
||||
contentValue,
|
||||
)
|
||||
|
||||
# wait for recursive gossip to complete
|
||||
for node in testData.recursive_gossip:
|
||||
let keyBytes = node.content_key.hexToSeqByte().ContentKeyByteList
|
||||
await stateNode2.waitUntilContentAvailable(toContentId(keyBytes))
|
||||
|
||||
# check that all nodes were received by both state instances
|
||||
for kv in testData.recursive_gossip:
|
||||
let
|
||||
expectedKeyBytes = kv.content_key.hexToSeqByte().ContentKeyByteList
|
||||
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()
|
||||
|
|
|
@ -26,10 +26,10 @@ template checkValidProofsForExistingLeafs(
|
|||
acc.codeHash = keccakHash(account.code)
|
||||
|
||||
let
|
||||
addressHash = address.keccakHash()
|
||||
accountProof = accountState.generateAccountProof(address)
|
||||
accountPath = removeLeafKeyEndNibbles(
|
||||
Nibbles.init(keccakHash(address).data, true), accountProof[^1]
|
||||
)
|
||||
accountPath =
|
||||
removeLeafKeyEndNibbles(Nibbles.init(addressHash.data, true), accountProof[^1])
|
||||
accountTrieNodeKey = AccountTrieNodeKey(
|
||||
path: accountPath, nodeHash: keccakHash(accountProof[^1].asSeq())
|
||||
)
|
||||
|
@ -40,7 +40,8 @@ template checkValidProofsForExistingLeafs(
|
|||
check proofResult.isOk()
|
||||
|
||||
let
|
||||
contractCodeKey = ContractCodeKey(address: address, codeHash: acc.codeHash)
|
||||
contractCodeKey =
|
||||
ContractCodeKey(addressHash: addressHash, codeHash: acc.codeHash)
|
||||
contractCode =
|
||||
ContractCodeOffer(code: Bytecode.init(account.code), accountProof: accountProof)
|
||||
codeResult =
|
||||
|
@ -58,7 +59,7 @@ template checkValidProofsForExistingLeafs(
|
|||
Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true), storageProof[^1]
|
||||
)
|
||||
contractTrieNodeKey = ContractTrieNodeKey(
|
||||
address: address,
|
||||
addressHash: addressHash,
|
||||
path: slotPath,
|
||||
nodeHash: keccakHash(storageProof[^1].asSeq()),
|
||||
)
|
||||
|
@ -80,10 +81,10 @@ template checkInvalidProofsWithBadValue(
|
|||
acc.codeHash = keccakHash(account.code)
|
||||
|
||||
var
|
||||
addressHash = address.keccakHash()
|
||||
accountProof = accountState.generateAccountProof(address)
|
||||
accountPath = removeLeafKeyEndNibbles(
|
||||
Nibbles.init(keccakHash(address).data, true), accountProof[^1]
|
||||
)
|
||||
accountPath =
|
||||
removeLeafKeyEndNibbles(Nibbles.init(addressHash.data, true), accountProof[^1])
|
||||
accountTrieNodeKey = AccountTrieNodeKey(
|
||||
path: accountPath, nodeHash: keccakHash(accountProof[^1].asSeq())
|
||||
)
|
||||
|
@ -96,7 +97,8 @@ template checkInvalidProofsWithBadValue(
|
|||
check proofResult.isErr()
|
||||
|
||||
let
|
||||
contractCodeKey = ContractCodeKey(address: address, codeHash: acc.codeHash)
|
||||
contractCodeKey =
|
||||
ContractCodeKey(addressHash: addressHash, codeHash: acc.codeHash)
|
||||
contractCode = ContractCodeOffer(
|
||||
code: Bytecode.init(@[1u8, 2, 3]), # bad code value
|
||||
accountProof: accountProof,
|
||||
|
@ -116,7 +118,7 @@ template checkInvalidProofsWithBadValue(
|
|||
Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true), storageProof[^1]
|
||||
)
|
||||
contractTrieNodeKey = ContractTrieNodeKey(
|
||||
address: address,
|
||||
addressHash: addressHash,
|
||||
path: slotPath,
|
||||
nodeHash: keccakHash(storageProof[^1].asSeq()),
|
||||
)
|
||||
|
|
|
@ -150,27 +150,13 @@ suite "State Validation - Test Vectors":
|
|||
)
|
||||
.isOk()
|
||||
|
||||
if i == 1:
|
||||
continue # second test case only has root node and no recursive gossip
|
||||
|
||||
let contentKey = ContentKey
|
||||
.decode(testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList)
|
||||
.get()
|
||||
let contentValueOffer = AccountTrieNodeOffer
|
||||
.decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
|
||||
.get()
|
||||
|
||||
check:
|
||||
validateOffer(
|
||||
Opt.some(stateRoot), contentKey.accountTrieNodeKey, contentValueOffer
|
||||
)
|
||||
.isOk()
|
||||
|
||||
test "Validate invalid AccountTrieNodeOffer nodes - bad state roots":
|
||||
const file = testVectorDir / "account_trie_node.yaml"
|
||||
const stateRoots = [
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xBAD8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",
|
||||
"0xBAD8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",
|
||||
]
|
||||
|
||||
|
@ -178,16 +164,16 @@ suite "State Validation - Test Vectors":
|
|||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
|
||||
|
||||
let contentKey =
|
||||
ContentKey.decode(testData.content_key.hexToSeqByte().ContentKeyByteList).get()
|
||||
let contentValueOffer =
|
||||
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
|
||||
|
||||
let res = validateOffer(
|
||||
Opt.some(stateRoot), contentKey.accountTrieNodeKey, contentValueOffer
|
||||
)
|
||||
let
|
||||
stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
|
||||
contentKey = ContentKey
|
||||
.decode(testData.content_key.hexToSeqByte().ContentKeyByteList)
|
||||
.get()
|
||||
contentValueOffer =
|
||||
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
|
||||
res = validateOffer(
|
||||
Opt.some(stateRoot), contentKey.accountTrieNodeKey, contentValueOffer
|
||||
)
|
||||
check:
|
||||
res.isErr()
|
||||
res.error() == "hash of proof root node doesn't match the expected root hash"
|
||||
|
@ -216,7 +202,7 @@ suite "State Validation - Test Vectors":
|
|||
res.error() == "hash of proof root node doesn't match the expected root hash"
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
if i == 2:
|
||||
continue # second test case only has root node
|
||||
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
|
||||
|
@ -275,27 +261,12 @@ suite "State Validation - Test Vectors":
|
|||
)
|
||||
.isOk()
|
||||
|
||||
if i == 1:
|
||||
continue # second test case has no recursive gossip
|
||||
|
||||
let contentKey = ContentKey
|
||||
.decode(testData.recursive_gossip.content_key.hexToSeqByte().ContentKeyByteList)
|
||||
.get()
|
||||
let contentValueOffer = ContractTrieNodeOffer
|
||||
.decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
|
||||
.get()
|
||||
|
||||
check:
|
||||
validateOffer(
|
||||
Opt.some(stateRoot), contentKey.contractTrieNodeKey, contentValueOffer
|
||||
)
|
||||
.isOk()
|
||||
|
||||
test "Validate invalid ContractTrieNodeOffer nodes - bad state roots":
|
||||
const file = testVectorDir / "contract_storage_trie_node.yaml"
|
||||
const stateRoots = [
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
]
|
||||
|
||||
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
|
||||
|
@ -525,58 +496,3 @@ suite "State Validation - Test Vectors":
|
|||
res.isErr()
|
||||
res.error() ==
|
||||
"hash of bytecode doesn't match the code hash in the account proof"
|
||||
|
||||
# Recursive gossip offer validation tests
|
||||
|
||||
test "Validate valid AccountTrieNodeOffer recursive gossip nodes":
|
||||
const file = testVectorDir / "recursive_gossip.yaml"
|
||||
const stateRoots = [
|
||||
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61",
|
||||
"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",
|
||||
]
|
||||
|
||||
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
if i == 1:
|
||||
continue
|
||||
|
||||
var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
|
||||
|
||||
for kv in testData.recursive_gossip:
|
||||
let contentKey =
|
||||
ContentKey.decode(kv.content_key.hexToSeqByte().ContentKeyByteList).get()
|
||||
let contentValueOffer =
|
||||
AccountTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
|
||||
|
||||
check:
|
||||
validateOffer(
|
||||
Opt.some(stateRoot), contentKey.accountTrieNodeKey, contentValueOffer
|
||||
)
|
||||
.isOk()
|
||||
|
||||
test "Validate valid ContractTrieNodeOffer recursive gossip nodes":
|
||||
const file = testVectorDir / "recursive_gossip.yaml"
|
||||
|
||||
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
|
||||
raiseAssert "Cannot read test vector: " & error
|
||||
|
||||
for i, testData in testCase:
|
||||
if i != 1:
|
||||
continue
|
||||
|
||||
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
|
||||
|
||||
for kv in testData.recursive_gossip:
|
||||
let contentKey =
|
||||
ContentKey.decode(kv.content_key.hexToSeqByte().ContentKeyByteList).get()
|
||||
let contentValueOffer =
|
||||
ContractTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
|
||||
|
||||
check:
|
||||
validateOffer(
|
||||
Opt.some(stateRoot), contentKey.contractTrieNodeKey, contentValueOffer
|
||||
)
|
||||
.isOk()
|
||||
|
|
|
@ -152,7 +152,8 @@ type
|
|||
.}: bool
|
||||
|
||||
gossipGenesis* {.
|
||||
desc: "Enable gossip of the genesis state into the portal network",
|
||||
desc:
|
||||
"Enable gossip of the genesis state into the portal network when starting from block 1",
|
||||
defaultValue: true,
|
||||
name: "gossip-genesis"
|
||||
.}: bool
|
||||
|
|
|
@ -27,12 +27,12 @@ proc toTrieProof(proof: seq[seq[byte]]): TrieProof =
|
|||
TrieProof.init(proof.map((node) => TrieNode.init(node)))
|
||||
|
||||
proc buildAccountTrieNodeOffer(
|
||||
builder: var OffersBuilder, address: EthAddress, proof: TrieProof
|
||||
builder: var OffersBuilder, addressHash: content_keys.AddressHash, proof: TrieProof
|
||||
) =
|
||||
try:
|
||||
let
|
||||
path = removeLeafKeyEndNibbles(
|
||||
Nibbles.init(worldState.toAccountKey(address).data, isEven = true), proof[^1]
|
||||
Nibbles.init(addressHash.data, isEven = true), proof[^1]
|
||||
)
|
||||
offerKey = AccountTrieNodeKey.init(path, keccakHash(proof[^1].asSeq()))
|
||||
offerValue = AccountTrieNodeOffer.init(proof, builder.blockHash)
|
||||
|
@ -43,7 +43,7 @@ proc buildAccountTrieNodeOffer(
|
|||
|
||||
proc buildContractTrieNodeOffer(
|
||||
builder: var OffersBuilder,
|
||||
address: EthAddress,
|
||||
addressHash: content_keys.AddressHash,
|
||||
slotHash: SlotKeyHash,
|
||||
storageProof: TrieProof,
|
||||
accountProof: TrieProof,
|
||||
|
@ -53,8 +53,9 @@ proc buildContractTrieNodeOffer(
|
|||
path = removeLeafKeyEndNibbles(
|
||||
Nibbles.init(slotHash.data, isEven = true), storageProof[^1]
|
||||
)
|
||||
offerKey =
|
||||
ContractTrieNodeKey.init(address, path, keccakHash(storageProof[^1].asSeq()))
|
||||
offerKey = ContractTrieNodeKey.init(
|
||||
addressHash, path, keccakHash(storageProof[^1].asSeq())
|
||||
)
|
||||
offerValue =
|
||||
ContractTrieNodeOffer.init(storageProof, accountProof, builder.blockHash)
|
||||
|
||||
|
@ -64,30 +65,32 @@ proc buildContractTrieNodeOffer(
|
|||
|
||||
proc buildContractCodeOffer(
|
||||
builder: var OffersBuilder,
|
||||
address: EthAddress,
|
||||
addressHash: content_keys.AddressHash,
|
||||
code: seq[byte],
|
||||
accountProof: TrieProof,
|
||||
) =
|
||||
let
|
||||
#bytecode = Bytelist.init(code) # This fails to compile for some reason
|
||||
bytecode = List[byte, MAX_BYTECODE_LEN](code)
|
||||
offerKey = ContractCodeKey.init(address, keccakHash(code))
|
||||
offerKey = ContractCodeKey.init(addressHash, keccakHash(code))
|
||||
offerValue = ContractCodeOffer.init(bytecode, accountProof, builder.blockHash)
|
||||
|
||||
builder.contractCodeOffers.add(offerValue.withKey(offerKey))
|
||||
|
||||
proc buildBlockOffers*(builder: var OffersBuilder) =
|
||||
for address, proof in builder.worldState.updatedAccountProofs():
|
||||
for addressHash, proof in builder.worldState.updatedAccountProofs():
|
||||
let accountProof = toTrieProof(proof)
|
||||
builder.buildAccountTrieNodeOffer(address, accountProof)
|
||||
builder.buildAccountTrieNodeOffer(addressHash, accountProof)
|
||||
|
||||
for slotHash, sProof in builder.worldState.updatedStorageProofs(address):
|
||||
for slotHash, sProof in builder.worldState.updatedStorageProofs(addressHash):
|
||||
let storageProof = toTrieProof(sProof)
|
||||
builder.buildContractTrieNodeOffer(address, slotHash, storageProof, accountProof)
|
||||
builder.buildContractTrieNodeOffer(
|
||||
addressHash, slotHash, storageProof, accountProof
|
||||
)
|
||||
|
||||
let code = builder.worldState.getUpdatedBytecode(address)
|
||||
let code = builder.worldState.getUpdatedBytecode(addressHash)
|
||||
if code.len() > 0:
|
||||
builder.buildContractCodeOffer(address, code, accountProof)
|
||||
builder.buildContractCodeOffer(addressHash, code, accountProof)
|
||||
|
||||
proc getAccountTrieOffers*(builder: OffersBuilder): lent seq[AccountTrieOfferWithKey] =
|
||||
builder.accountTrieOffers
|
||||
|
|
|
@ -109,9 +109,7 @@ proc setAccountPreimage(
|
|||
) =
|
||||
state.preimagesDb.put(rlp.encode(accountKey), rlp.encode(address))
|
||||
|
||||
proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState =
|
||||
let accountKey = toAccountKey(address)
|
||||
|
||||
proc getAccount(state: WorldStateRef, accountKey: AddressHash): AccountState =
|
||||
try:
|
||||
if state.accountsTrie.contains(accountKey.data):
|
||||
let accountBytes = state.accountsTrie.get(accountKey.data)
|
||||
|
@ -121,6 +119,9 @@ proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState =
|
|||
except RlpError as e:
|
||||
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||
|
||||
proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState {.inline.} =
|
||||
state.getAccount(toAccountKey(address))
|
||||
|
||||
proc setAccount*(state: WorldStateRef, address: EthAddress, accState: AccountState) =
|
||||
let accountKey = toAccountKey(address)
|
||||
state.setAccountPreimage(accountKey, address)
|
||||
|
@ -166,7 +167,7 @@ proc deleteAccount*(state: WorldStateRef, address: EthAddress) =
|
|||
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||
|
||||
# Returns the account proofs for all the updated accounts from the last transaction
|
||||
iterator updatedAccountProofs*(state: WorldStateRef): (EthAddress, seq[seq[byte]]) =
|
||||
iterator updatedAccountProofs*(state: WorldStateRef): (AddressHash, seq[seq[byte]]) =
|
||||
let trie = initHexaryTrie(
|
||||
state.db.getAccountsUpdatedCache(), state.stateRoot(), isPruning = false
|
||||
)
|
||||
|
@ -175,16 +176,15 @@ iterator updatedAccountProofs*(state: WorldStateRef): (EthAddress, seq[seq[byte]
|
|||
for key in trie.keys():
|
||||
if key.len() == 0:
|
||||
continue # skip the empty node created on initialization
|
||||
let address = state.getAccountPreimage(KeccakHash.fromBytes(key))
|
||||
yield (address, trie.getBranch(key))
|
||||
yield (KeccakHash.fromBytes(key), trie.getBranch(key))
|
||||
except RlpError as e:
|
||||
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||
|
||||
# Returns the storage proofs for the updated slots for the given account from the last transaction
|
||||
iterator updatedStorageProofs*(
|
||||
state: WorldStateRef, address: EthAddress
|
||||
state: WorldStateRef, accountKey: AddressHash
|
||||
): (SlotKeyHash, seq[seq[byte]]) =
|
||||
let accState = state.getAccount(address)
|
||||
let accState = state.getAccount(accountKey)
|
||||
|
||||
let trie = initHexaryTrie(
|
||||
state.db.getStorageUpdatedCache(), accState.account.storageRoot, isPruning = false
|
||||
|
@ -199,9 +199,9 @@ iterator updatedStorageProofs*(
|
|||
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||
|
||||
proc getUpdatedBytecode*(
|
||||
state: WorldStateRef, address: EthAddress
|
||||
state: WorldStateRef, accountKey: AddressHash
|
||||
): seq[byte] {.inline.} =
|
||||
state.db.getBytecodeUpdatedCache().get(toAccountKey(address).data)
|
||||
state.db.getBytecodeUpdatedCache().get(accountKey.data)
|
||||
|
||||
# Slow: Used for testing only
|
||||
proc verifyProofs*(
|
||||
|
@ -215,11 +215,11 @@ proc verifyProofs*(
|
|||
for k, v in trie.replicate():
|
||||
memDb.put(k, v)
|
||||
|
||||
for address, proof in state.updatedAccountProofs():
|
||||
for accountKey, proof in state.updatedAccountProofs():
|
||||
doAssert isValidBranch(
|
||||
proof,
|
||||
expectedStateRoot,
|
||||
@(toAccountKey(address).data),
|
||||
@(accountKey.data),
|
||||
rlpFromBytes(proof[^1]).listElem(1).toBytes(), # pull the value out of the proof
|
||||
)
|
||||
for p in proof:
|
||||
|
@ -228,15 +228,15 @@ proc verifyProofs*(
|
|||
let memTrie = initHexaryTrie(memDb, expectedStateRoot, isPruning = false)
|
||||
doAssert(memTrie.rootHash() == expectedStateRoot)
|
||||
|
||||
for address, proof in state.updatedAccountProofs():
|
||||
for accountKey, proof in state.updatedAccountProofs():
|
||||
let
|
||||
accountBytes = memTrie.get(toAccountKey(address).data)
|
||||
accountBytes = memTrie.get(accountKey.data)
|
||||
account = rlp.decode(accountBytes, Account)
|
||||
doAssert(accountBytes.len() > 0)
|
||||
doAssert(accountBytes == rlpFromBytes(proof[^1]).listElem(1).toBytes())
|
||||
# pull the value out of the proof
|
||||
|
||||
for slotHash, sProof in state.updatedStorageProofs(address):
|
||||
for slotHash, sProof in state.updatedStorageProofs(accountKey):
|
||||
doAssert isValidBranch(
|
||||
sProof,
|
||||
account.storageRoot,
|
||||
|
@ -245,7 +245,7 @@ proc verifyProofs*(
|
|||
# pull the value out of the proof
|
||||
)
|
||||
|
||||
let updatedCode = state.getUpdatedBytecode(address)
|
||||
let updatedCode = state.getUpdatedBytecode(accountKey)
|
||||
if updatedCode.len() > 0:
|
||||
doAssert(account.codeHash == keccakHash(updatedCode))
|
||||
except RlpError as e:
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 4254dac8ce1cbe28fc4704d92aa6809c73451c20
|
||||
Subproject commit 92b5a99e748a964fcc5c59e0c6fef248ccdd88f4
|
Loading…
Reference in New Issue