New state network types update (#1976)

This commit is contained in:
Daniel Sobol 2024-01-19 20:18:57 +03:00 committed by GitHub
parent 9875eb11d6
commit 7ecd38347f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 285 additions and 760 deletions

View File

@ -68,7 +68,6 @@ TOOLS_CSV := $(subst $(SPACE),$(COMMA),$(TOOLS))
FLUFFY_TOOLS := \
portal_bridge \
beacon_lc_bridge \
state_bridge \
eth_data_exporter \
content_verifier \
blockwalk \

View File

@ -11,33 +11,17 @@
{.push raises: [].}
import
nimcrypto/[hash, sha2, keccak], stew/[objects, results], stint,
nimcrypto/[hash, sha2, keccak], stew/results, stint,
eth/common/eth_types,
ssz_serialization,
../../common/common_types
export ssz_serialization, common_types, hash, results
type JsonAccount* = object
nonce*: int
balance*: string
storage_hash*: string
code_hash*: string
type JsonProof* = object
address*: string
state*: JsonAccount
proof*: seq[string]
type JsonProofVector* = object
`block`*: int
block_hash*: string
state_root*: string
proofs*: seq[JsonProof]
type
NodeHash* = MDigest[32 * 8] # keccak256
CodeHash* = MDigest[32 * 8] # keccak256
Address* = array[20, byte]
NodeHash* = KeccakHash
CodeHash* = KeccakHash
Address* = EthAddress
ContentType* = enum
# Note: Need to add this unused value as a case object with an enum without
@ -49,52 +33,71 @@ type
# the SSZ spec is not explicit about disallowing this.
unused = 0x00
accountTrieNode = 0x20
contractStorageTrieNode = 0x21
accountTrieProof = 0x22
contractStorageTrieProof = 0x23
contractBytecode = 0x24
contractTrieNode = 0x21
contractCode = 0x22
NibblePair* = byte
Nibbles* = object
isOddLength*: bool
packedNibbles*: List[NibblePair, 32]
WitnessNode* = List[byte, 1024]
Witness* = List[WitnessNode, 1024]
StateWitness* = object
key*: Nibbles
proof*: Witness
StorageWitness* = object
key*: Nibbles
proof*: Witness
stateWitness*: StateWitness
AccountTrieNodeKey* = object
path*: ByteList
path*: Nibbles
nodeHash*: NodeHash
stateRoot*: Bytes32
ContractStorageTrieNodeKey* = object
ContractTrieNodeKey* = object
address*: Address
path*: ByteList
path*: Nibbles
nodeHash*: NodeHash
stateRoot*: Bytes32
AccountTrieProofKey* = object
address*: Address
stateRoot*: Bytes32
ContractStorageTrieProofKey* = object
address*: Address
slot*: UInt256
stateRoot*: Bytes32
ContractBytecodeKey* = object
ContractCodeKey* = object
address*: Address
codeHash*: CodeHash
WitnessNode* = ByteList
AccountTrieProof* = List[WitnessNode, 32]
ContentKey* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNodeKey*: AccountTrieNodeKey
of contractStorageTrieNode:
contractStorageTrieNodeKey*: ContractStorageTrieNodeKey
of accountTrieProof:
accountTrieProofKey*: AccountTrieProofKey
of contractStorageTrieProof:
contractStorageTrieProofKey*: ContractStorageTrieProofKey
of contractBytecode:
contractBytecodeKey*: ContractBytecodeKey
of contractTrieNode:
contractTrieNodeKey*: ContractTrieNodeKey
of contractCode:
contractCodeKey*: ContractCodeKey
AccountTrieNodeOffer* = object
proof*: StateWitness
blockHash*: BlockHash
AccountTrieNodeRetrieval* = object
node*: WitnessNode
ContractTrieNodeOffer* = object
proof*: StorageWitness
blockHash*: BlockHash
ContractTrieNodeRetrieval* = object
node*: WitnessNode
ContractCodeOffer* = object
code*: ByteList
accountProof*: StateWitness
blockHash*: BlockHash
ContractCodeRetrieval* = object
code*: ByteList
func encode*(contentKey: ContentKey): ByteList =
doAssert(contentKey.contentType != unused)
@ -115,53 +118,10 @@ func decode*(contentKey: ByteList): Opt[ContentKey] =
except SerializationError:
return Opt.none(ContentKey)
template computeContentId*(digestCtxType: type, body: untyped): ContentId =
var h {.inject.}: digestCtxType
init(h)
body
let idHash = finish(h)
func toContentId*(contentKey: ByteList): ContentId =
# TODO: Should we try to parse the content key here for invalid ones?
let idHash = sha2.sha256.digest(contentKey.asSeq())
readUintBE[256](idHash.data)
func toContentId*(contentKey: ContentKey): ContentId =
case contentKey.contentType:
of unused:
raiseAssert "Should not be used and fail at decoding"
of accountTrieNode: # sha256(path | node_hash)
let key = contentKey.accountTrieNodeKey
computeContentId sha256:
h.update(key.path.asSeq())
h.update(key.nodeHash.data)
of contractStorageTrieNode: # sha256(address | path | node_hash)
let key = contentKey.contractStorageTrieNodeKey
computeContentId sha256:
h.update(key.address)
h.update(key.path.asSeq())
h.update(key.nodeHash.data)
of accountTrieProof: # keccak(address)
let key = contentKey.accountTrieProofKey
computeContentId keccak256:
h.update(key.address)
of contractStorageTrieProof: # (keccak(address) + keccak(slot)) % 2**256
# TODO: Why is keccak run on slot, when it can be used directly?
# Also, value to LE or BE? Not mentioned in specification.
let key = contentKey.contractStorageTrieProofKey
let n1 =
block: computeContentId keccak256:
h.update(key.address)
let n2 =
block: computeContentId keccak256:
h.update(toBytesBE(key.slot))
n1 + n2 # uint256 will wrap arround, practically applying the modulo 256
of contractBytecode: # sha256(address | code_hash)
let key = contentKey.contractBytecodeKey
computeContentId sha256:
h.update(key.address)
h.update(key.codeHash.data)
func toContentId*(contentKey: ByteList): results.Opt[ContentId] =
let key = decode(contentKey)
if key.isSome():
ok(key.get().toContentId())
else:
Opt.none(ContentId)
toContentId(encode(contentKey))

View File

@ -1,80 +0,0 @@
# Nimbus
# Copyright (c) 2021-2022 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
eth/p2p/discoveryv5/routing_table,
stint
const
MID* = u256(2).pow(u256(255))
MAX* = high(UInt256)
# Custom distance function described in: https://notes.ethereum.org/h58LZcqqRRuarxx4etOnGQ#Storage-Layout
# The implementation looks different than in spec, due to the fact that in practice
# we are operating on unsigned 256bit integers instead of signed big ints.
# Thanks to this we do not need to use:
# - modulo operations
# - abs operation
# and the results are eqivalent to function described in spec.
#
# The way it works is as follows. Let say we have integers modulo 8:
# [0, 1, 2, 3, 4, 5, 6, 7]
# and we want to calculate minimal distance between 0 and 5.
# Raw difference is: 5 - 0 = 5, which is larger than mid point which is equal to 4.
# From this we know that the shorter distance is the one wraping around 0, which
# is equal to 3
func stateDistance*(node_id: UInt256, content_id: UInt256): UInt256 =
let rawDiff =
if node_id > content_id:
node_id - content_id
else:
content_id - node_id
if rawDiff > MID:
# If rawDiff is larger than mid this means that distance between node_id and
# content_id is smaller when going from max side.
MAX - rawDiff + UInt256.one
else:
rawDiff
# TODO we do not have Uint256 log2 implementation. It would be nice to implement
# it in stint library in some more performant way. This version has O(n) complexity.
func log2DistanceImpl(value: UInt256): uint16 =
# Logarithm is not defined for zero values. Implementation in stew for builtin
# types return -1 in that case, but here it is just internal function so just make sure
# 0 is never provided.
doAssert(not value.isZero())
if value == UInt256.one:
return 0'u16
var comp = value
var ret = 0'u16
while (comp > 1):
comp = comp shr 1
ret = ret + 1
return ret
func stateIdAtDistance*(id: UInt256, dist: uint16): UInt256 =
# TODO With current distance function there are always two ids at given distance
# so we might as well do: id - u256(dist), maybe it is worth discussing if every client
# should use the same id in this case.
id + u256(2).pow(dist)
func stateLogDistance*(a, b: UInt256): uint16 =
let distance = stateDistance(a, b)
if distance.isZero():
return 0
else:
return log2DistanceImpl(distance)
const stateDistanceCalculator* =
DistanceCalculator(
calculateDistance: stateDistance,
calculateLogDistance: stateLogDistance,
calculateIdAtDistance: stateIdAtDistance
)

View File

@ -13,8 +13,7 @@ import
eth/p2p/discoveryv5/[protocol, enr],
../../database/content_db,
../wire/[portal_protocol, portal_stream, portal_protocol_config],
./state_content,
./state_distance
./state_content
logScope:
topics = "portal_state"
@ -29,7 +28,7 @@ type StateNetwork* = ref object
processContentLoop: Future[void]
func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] =
toContentId(contentKey)
ok(toContentId(contentKey))
proc getContent*(n: StateNetwork, key: ContentKey):
Future[Opt[seq[byte]]] {.async.} =
@ -75,27 +74,9 @@ proc validateContent(
false
of accountTrieNode:
true
of contractStorageTrieNode:
of contractTrieNode:
true
of accountTrieProof:
let decodedProof = decodeSsz(contentValue, AccountTrieProof).valueOr:
warn "Received invalid account trie proof", error
return false
let
proof = decodedProof.asSeq().map((p: ByteList) => p.toSeq())
trieKey = keccakHash(key.accountTrieProofKey.address).data.toSeq()
value = proof[^1].decode(seq[seq[byte]])[^1]
stateRoot = MDigest[256](data: key.accountTrieProofKey.stateRoot)
verificationResult = verifyMptProof(proof, stateRoot, trieKey, value)
case verificationResult.kind:
of ValidProof:
true
else:
warn "Received invalid account trie proof"
false
of contractStorageTrieProof:
true
of contractBytecode:
of contractCode:
true
proc validateContent(
@ -131,8 +112,7 @@ proc new*(
let portalProtocol = PortalProtocol.new(
baseProtocol, stateProtocolId,
toContentIdHandler, createGetHandler(contentDB), s,
bootstrapRecords, stateDistanceCalculator,
config = portalConfig)
bootstrapRecords, config = portalConfig)
portalProtocol.dbPut = createStoreHandler(contentDB, portalConfig.radiusConfig, portalProtocol)

View File

@ -9,8 +9,6 @@
import
./test_portal_wire_protocol,
./state_network_tests/test_state_distance,
./state_network_tests/test_state_network,
./state_network_tests/test_state_content,
./test_state_proof_verification,
./test_accumulator,

View File

@ -13,5 +13,4 @@ import
./test_history_content,
./test_history_content_validation,
./test_header_content,
./test_state_content,
./test_accumulator_root

View File

@ -1,225 +0,0 @@
# Fluffy
# Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
unittest2, stew/byteutils,
../../../network/state/state_content
# According to test vectors:
# https://github.com/ethereum/portal-network-specs/blob/master/content-keys-test-vectors.md#state-network-keys
suite "State ContentKey Encodings":
# Common input
const
stateRoot = hexToByteArray[sizeof(Bytes32)](
"0xd1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d")
address = hexToByteArray[sizeof(Address)](
"0x829bd824b016326a401d083b33d092293333a830")
test "AccountTrieNode":
# Input
const
nodeHash = NodeHash.fromHex(
"0xb8be7903aee73b8f6a59cd44a1f52c62148e1f376c0dfa1f5f773a98666efc2b")
path = ByteList.init(@[byte 1, 2, 0, 1])
# Output
contentKeyHex =
"2044000000b8be7903aee73b8f6a59cd44a1f52c62148e1f376c0dfa1f5f773a98666efc2bd1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01020001"
contentId =
"41237096982860596884042712109427867048220765019203857308279863638242761605893"
# or
contentIdHexBE =
"5b2b5ea9a7384491010c1aa459a0f967dcf8b69988adbfe7e0bed513e9bb8305"
let
accountTrieNodeKey = AccountTrieNodeKey(
path: path, nodeHash: nodeHash, stateRoot: stateRoot)
contentKey = ContentKey(
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey)
let encoded = encode(contentKey)
check encoded.asSeq.toHex == contentKeyHex
let decoded = decode(encoded)
check decoded.isSome()
let contentKeyDecoded = decoded.get()
check:
contentKeyDecoded.contentType == contentKey.contentType
contentKeyDecoded.accountTrieNodeKey == contentKey.accountTrieNodeKey
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE
test "ContractStorageTrieNode":
# Input
const
nodeHash = NodeHash.fromHex(
"0x3e190b68719aecbcb28ed2271014dd25f2aa633184988eb414189ce0899cade5")
path = ByteList.init(@[byte 1, 0, 15, 14, 12, 0])
# Output
contentKeyHex =
"21829bd824b016326a401d083b33d092293333a830580000003e190b68719aecbcb28ed2271014dd25f2aa633184988eb414189ce0899cade5d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01000f0e0c00"
contentId =
"43529358882110548041037387588279806363134301284609868141745095118932570363585"
# or
contentIdHexBE =
"603cbe7902925ce359822378a4cb1b4b53e1bf19d003de2c26e55812d76956c1"
let
contractStorageTrieNodeKey = ContractStorageTrieNodeKey(
address: address, path: path, nodeHash: nodeHash, stateRoot: stateRoot)
contentKey = ContentKey(
contentType: contractStorageTrieNode,
contractStorageTrieNodeKey: contractStorageTrieNodeKey)
let encoded = encode(contentKey)
check encoded.asSeq.toHex == contentKeyHex
let decoded = decode(encoded)
check decoded.isSome()
let contentKeyDecoded = decoded.get()
check:
contentKeyDecoded.contentType == contentKey.contentType
contentKeyDecoded.contractStorageTrieNodeKey ==
contentKey.contractStorageTrieNodeKey
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE
test "AccountTrieProof":
# Output
const
contentKeyHex =
"22829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"45301550050471302973396879294932122279426162994178563319590607565171451545101"
# or
contentIdHexBE =
"6427c4c8d42db15c2aca8dfc7dff7ce2c8c835441b566424fa3377dd031cc60d"
let
accountTrieProofKey = AccountTrieProofKey(
address: address, stateRoot: stateRoot)
contentKey = ContentKey(
contentType: accountTrieProof,
accountTrieProofKey: accountTrieProofKey)
let encoded = encode(contentKey)
check encoded.asSeq.toHex == contentKeyHex
let decoded = decode(encoded)
check decoded.isSome()
let contentKeyDecoded = decoded.get()
check:
contentKeyDecoded.contentType == contentKey.contentType
contentKeyDecoded.accountTrieProofKey == contentKey.accountTrieProofKey
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE
test "ContractStorageTrieProof":
# Input
const
slot = 239304.stuint(256)
# Output
contentKeyHex =
"23829bd824b016326a401d083b33d092293333a830c8a6030000000000000000000000000000000000000000000000000000000000d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"80413803151602881485894828440259195604313253842905231566803078625935967002376"
# or
contentIdHexBE =
"b1c89984803cebd325303ba035f9c4ca0d0d91b2cbfef84d455e7a847ade1f08"
let
contractStorageTrieProofKey = ContractStorageTrieProofKey(
address: address, slot: slot, stateRoot: stateRoot)
contentKey = ContentKey(
contentType: contractStorageTrieProof,
contractStorageTrieProofKey: contractStorageTrieProofKey)
let encoded = encode(contentKey)
check encoded.asSeq.toHex == contentKeyHex
let decoded = decode(encoded)
check decoded.isSome()
let contentKeyDecoded = decoded.get()
check:
contentKeyDecoded.contentType == contentKey.contentType
contentKeyDecoded.contractStorageTrieProofKey ==
contentKey.contractStorageTrieProofKey
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE
test "ContractBytecode":
# Input
const codeHash = CodeHash.fromHex(
"0xd1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d")
# Output
const
contentKeyHex =
"24829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"9243655320250466575533858917172702581481192615849913473767356296630272634800"
# or
contentIdHexBE =
"146fb937afe42bcf11d25ad57d67734b9a7138677d59eeec3f402908f54dafb0"
let
contractBytecodeKey = ContractBytecodeKey(
address: address, codeHash: codeHash)
contentKey = ContentKey(
contentType: contractBytecode,
contractBytecodeKey: contractBytecodeKey)
let encoded = encode(contentKey)
check encoded.asSeq.toHex == contentKeyHex
let decoded = decode(encoded)
check decoded.isSome()
let contentKeyDecoded = decoded.get()
check:
contentKeyDecoded.contentType == contentKey.contentType
contentKeyDecoded.contractBytecodeKey == contentKey.contractBytecodeKey
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE
test "Invalid prefix - 0 value":
let encoded = ByteList.init(@[byte 0x00])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid prefix - before valid range":
let encoded = ByteList.init(@[byte 0x01])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid prefix - after valid range":
let encoded = ByteList.init(@[byte 0x25])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid key - empty input":
let encoded = ByteList.init(@[])
let decoded = decode(encoded)
check decoded.isNone()

View File

@ -12,33 +12,229 @@ import
eth/keys,
../../network/state/state_content
const testVectorDir =
"./vendor/portal-spec-tests/tests/mainnet/state/"
procSuite "State Content":
let rng = newRng()
test "Encode/decode accountTrieProof":
let file = testVectorDir & "/proofs.full.block.0.json"
let content = readAllFile(file).valueOr:
quit(1)
let decoded =
try:
Json.decode(content, state_content.JsonProofVector)
except SerializationError:
quit(1)
let proof = decoded.proofs[0].proof.map(hexToSeqByte)
var accountTrieProof = AccountTrieProof(@[])
for witness in proof:
let witnessNode = ByteList(witness)
discard accountTrieProof.add(witnessNode)
suite "State Content Keys":
const evenNibles = "0005000000123456789abc"
test "Encode/decode even nibbles":
const
packedNibbles = Nibbles.packedNibbles.init(@[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC])
isOddLength = false
let
encodedProof = SSZ.encode(accountTrieProof)
decodedProof = decodeSsz(encodedProof, AccountTrieProof).get()
nibbles = Nibbles(packedNibbles: packedNibbles, isOddLength: isOddLength)
encoded = SSZ.encode(nibbles)
check encoded.toHex() == evenNibles
# echo ">>>", encoded.toHex()
const oddNibbles = "0105000000123456789abc0d"
test "Encode/decode odd nibbles":
const
packedNibbles = Nibbles.packedNibbles.init(@[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0x0D])
isOddLength = true
check decodedProof == accountTrieProof
let
nibbles = Nibbles(packedNibbles: packedNibbles, isOddLength: isOddLength)
encoded = SSZ.encode(nibbles)
check encoded.toHex() == oddNibbles
# echo ">>>", encoded.toHex()
const accountTrieNodeKeyEncoded = "2024000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700005000000123456789abc"
test "Encode/decode AccountTrieNodeKey":
const
packedNibbles = Nibbles.packedNibbles.init(@[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC])
isOddLength = false
nodeHash = NodeHash.fromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
let
nibbles = Nibbles(packedNibbles: packedNibbles, isOddLength: isOddLength)
accountTrieNodeKey = AccountTrieNodeKey(path: nibbles, nodeHash: nodeHash)
contentKey = ContentKey(contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey)
encoded = contentKey.encode()
# echo ">>>", $encoded
check $encoded == accountTrieNodeKeyEncoded
let decoded = encoded.decode().valueOr:
raiseAssert "Cannot decode AccountTrieNodeKey"
check:
decoded.contentType == accountTrieNode
decoded.accountTrieNodeKey == AccountTrieNodeKey(path: nibbles, nodeHash: nodeHash)
const contractTrieNodeKeyEncoded = "21000d836201318ec6899a6754069038278074328038000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700005000000123456789abc"
test "Encode/decode ContractTrieNodeKey":
const
address = Address.fromHex("000d836201318ec6899a67540690382780743280")
packedNibbles = Nibbles.packedNibbles.init(@[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC])
isOddLength = false
nodeHash = NodeHash.fromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
let
nibbles = Nibbles(packedNibbles: (packedNibbles), isOddLength: isOddLength)
contractTrieNodeKey = ContractTrieNodeKey(address: address, path: nibbles, nodeHash: nodeHash)
contentKey = ContentKey(contentType: contractTrieNode, contractTrieNodeKey: contractTrieNodeKey)
encoded = contentKey.encode()
# echo ">>>", $encoded
check $encoded == contractTrieNodeKeyEncoded
let decoded = encoded.decode().valueOr:
raiseAssert "Cannot decode ContractTrieNodeKey"
check:
decoded.contentType == contractTrieNode
decoded.contractTrieNodeKey == ContractTrieNodeKey(address: address, path: nibbles, nodeHash: nodeHash)
const contractCodeKeyEncoded = "22000d836201318ec6899a67540690382780743280c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
test "Encode/decode ContractCodeKey":
const
address = Address.fromHex("000d836201318ec6899a67540690382780743280")
codeHash = CodeHash.fromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
let
contractCodeKey = ContractCodeKey(address: address, codeHash: codeHash)
contentKey = ContentKey(contentType: contractCode, contractCodeKey: contractCodeKey)
encoded = contentKey.encode()
# echo ">>>", $encoded
check $encoded == contractCodeKeyEncoded
let decoded = encoded.decode().valueOr:
raiseAssert "Cannot decode ContractCodeKey"
check:
decoded.contentType == contractCode
decoded.contractCodeKey.address == address
decoded.contractCodeKey.codeHash == codeHash
test "Invalid prefix - 0 value":
let encoded = ByteList.init(@[byte 0x00])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid prefix - before valid range":
let encoded = ByteList.init(@[byte 0x01])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid prefix - after valid range":
let encoded = ByteList.init(@[byte 0x25])
let decoded = decode(encoded)
check decoded.isNone()
test "Invalid key - empty input":
let encoded = ByteList.init(@[])
let decoded = decode(encoded)
check decoded.isNone()
suite "State Content Values":
const accountTrieNodeOfferEncoded = "24000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa308000000130000000005000000123456789abc04000000010203040506"
test "Encode/decode AccountTrieNodeOffer":
let
blockHash = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
packedNibbles = @[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]
isOddLength = false
key = Nibbles(packedNibbles: Nibbles.packedNibbles.init(packedNibbles), isOddLength: isOddLength)
witnessNode = WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
proof = Witness(@[witnessNode])
accountTrieNodeOffer = AccountTrieNodeOffer(blockHash: BlockHash.fromHex(blockHash), proof: StateWitness(key: key, proof: proof))
encoded = SSZ.encode(accountTrieNodeOffer)
# echo ">>>", encoded.toHex()
check encoded.toHex() == accountTrieNodeOfferEncoded
let decoded = SSZ.decode(encoded, AccountTrieNodeOffer)
check:
decoded.blockHash == BlockHash.fromHex(blockHash)
decoded.proof == StateWitness(key: key, proof: proof)
const accountTrieNodeRetrievalEncoded = "04000000010203040506"
test "Encode/decode AccountTrieNodeRetrieval":
let
witnessNode = WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
accountTrieNodeRetrieval = AccountTrieNodeRetrieval(node: witnessNode)
encoded = SSZ.encode(accountTrieNodeRetrieval)
# echo ">>>", encoded.toHex()
check encoded.toHex() == accountTrieNodeRetrievalEncoded
let decoded = SSZ.decode(encoded, AccountTrieNodeRetrieval)
check decoded.node == witnessNode
const contractTrieNodeOfferEncoded = "24000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa30c00000017000000220000000005000000123456789abc040000000102030405060708000000150000000005000000123456789abcdef104000000010203040506070809"
test "Encode/decode ContractTrieNodeOffer":
let
blockHash = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
contractWitnessKeyPackedNibbles = @[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]
contractWitnessProof = Witness(@[WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07])])
stateWitnessKeyPackedNibbles = @[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF1]
stateWitnessProof = Witness(@[WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09])])
contractTrieNodeOffer = ContractTrieNodeOffer(
blockHash: BlockHash.fromHex(blockHash),
proof: StorageWitness(
key: Nibbles(packedNibbles: Nibbles.packedNibbles.init(contractWitnessKeyPackedNibbles), isOddLength: false),
proof: contractWitnessProof,
stateWitness: StateWitness(
key: Nibbles(packedNibbles: Nibbles.packedNibbles.init(stateWitnessKeyPackedNibbles), isOddLength: false),
proof: stateWitnessProof
)))
encoded = SSZ.encode(contractTrieNodeOffer)
# echo ">>>", encoded.toHex()
check encoded.toHex() == contractTrieNodeOfferEncoded
let decoded = SSZ.decode(encoded, ContractTrieNodeOffer)
check:
decoded.blockHash == BlockHash.fromHex(blockHash)
decoded.proof == StorageWitness(
key: Nibbles(packedNibbles: Nibbles.packedNibbles.init(contractWitnessKeyPackedNibbles), isOddLength: false),
proof: contractWitnessProof,
stateWitness: StateWitness(
key: Nibbles(packedNibbles: Nibbles.packedNibbles.init(stateWitnessKeyPackedNibbles), isOddLength: false),
proof: stateWitnessProof
))
const contractTrieNodeRetrievalEncoded = "04000000010203040506"
test "Encode/decode ContractTrieNodeRetrieval":
let
witnessNode = WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
contractTrieNodeRetrieval = ContractTrieNodeRetrieval(node: witnessNode)
encoded = SSZ.encode(contractTrieNodeRetrieval)
# echo ">>>", encoded.toHex()
check encoded.toHex() == contractTrieNodeRetrievalEncoded
let decoded = SSZ.decode(encoded, ContractTrieNodeRetrieval)
check decoded.node == witnessNode
const contractCodeOfferEncoded = "2800000036000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3010203040506070809a0a1a2a3a408000000130000000005000000123456789abc04000000010203040506"
test "Encode/decode ContractCodeOffer":
let
code = @[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4]
blockHash = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
accountProofKey = Nibbles(packedNibbles: Nibbles.packedNibbles.init(@[NibblePair 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]), isOddLength: false)
accountProofWitness = Witness(@[WitnessNode(@[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])])
contractCodeOffer = ContractCodeOffer(
code: ByteList.init(code),
blockHash: BlockHash.fromHex(blockHash),
accountProof: StateWitness(key: accountProofKey, proof: accountProofWitness))
encoded = SSZ.encode(contractCodeOffer)
# echo ">>>", encoded.toHex()
check encoded.toHex() == contractCodeOfferEncoded
let decoded = SSZ.decode(encoded, ContractCodeOffer)
check:
decoded.code == ByteList.init(code)
decoded.blockHash == BlockHash.fromHex(blockHash)
decoded.accountProof == StateWitness(key: accountProofKey, proof: accountProofWitness)
const contractCodeRetrievalEncoded = "04000000010203040506070809a0a1a2a3a4"
test "Encode/decode ContractCodeRetrieval":
let
code = @[byte 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4]
contractCodeRetrieval = ContractCodeRetrieval(code: ByteList.init(code))
encoded = SSZ.encode(contractCodeRetrieval)
# echo ">>>", encoded.toHex()
check encoded.toHex() == contractCodeRetrievalEncoded
let decoded = SSZ.decode(encoded, ContractCodeRetrieval)
check decoded.code == ByteList.init(code)

View File

@ -1,57 +0,0 @@
# Nimbus - Portal Network
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
std/sequtils,
stint, unittest2,
../../network/state/state_distance
suite "State network custom distance function":
test "Calculate distance according to spec":
check:
# Test cases from spec
stateDistance(u256(10), u256(10)) == 0
stateDistance(u256(5), high(UInt256)) == 6
stateDistance(high(UInt256), u256(6)) == 7
stateDistance(u256(5), u256(1)) == 4
stateDistance(u256(1), u256(5)) == 4
stateDistance(UInt256.zero, MID) == MID
stateDistance(UInt256.zero, MID + UInt256.one) == MID - UInt256.one
# Additional test cases to check some basic properties
stateDistance(UInt256.zero, MID + MID) == UInt256.zero
stateDistance(UInt256.zero, UInt256.one) == stateDistance(UInt256.zero, high(UInt256))
test "Calculate logarithimic distance":
check:
stateLogDistance(u256(0), u256(0)) == 0
stateLogDistance(u256(0), u256(1)) == 0
stateLogDistance(u256(0), u256(2)) == 1
stateLogDistance(u256(0), u256(4)) == 2
stateLogDistance(u256(0), u256(8)) == 3
stateLogDistance(u256(8), u256(0)) == 3
stateLogDistance(UInt256.zero, MID) == 255
stateLogDistance(UInt256.zero, MID + UInt256.one) == 254
test "Calculate id at log distance":
let logDistances = @[
0'u16, 1, 2, 3, 4, 5, 6, 7, 8
]
# for each log distance, calulate node-id at given distance from node zero, and then
# log distance from calculate node-id to node zero. The results should equal
# starting log distances
let logCalculated = logDistances.map(
proc (x: uint16): uint16 =
let nodeAtDist = stateIdAtDistance(UInt256.zero, x)
return stateLogDistance(UInt256.zero, nodeAtDist)
)
check:
logDistances == logCalculated

View File

@ -40,81 +40,6 @@ proc genesisToTrie(filePath: string): CoreDbMptRef =
procSuite "State Network":
let rng = newRng()
test "Test account state proof":
let file = testVectorDir & "/proofs.full.block.0.json"
let content = readAllFile(file).valueOr:
quit(1)
let decoded =
try:
Json.decode(content, state_content.JsonProofVector)
except SerializationError:
quit(1)
let
proof = decoded.proofs[0].proof.map(hexToSeqByte)
stateRoot = MDigest[256].fromHex(decoded.state_root)
address = hexToByteArray[20](decoded.proofs[0].address)
key = keccakHash(address).data.toSeq()
value = proof[^1].decode(seq[seq[byte]])[^1]
proofResult = verifyMptProof(proof, stateRoot, key, value)
check proofResult.kind == ValidProof
asyncTest "Decode and use proofs":
let file = testVectorDir & "/proofs.full.block.0.json"
let content = readAllFile(file).valueOr:
quit(1)
let decoded =
try:
Json.decode(content, state_content.JsonProofVector)
except SerializationError:
quit(1)
let
node1 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20302))
sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20303))
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)
state_root = hexToByteArray[sizeof(state_content.AccountTrieProofKey.stateRoot)](decoded.state_root)
check proto2.portalProtocol.addNode(node1.localNode) == Added
for proof in decoded.proofs:
let
address = hexToByteArray[sizeof(state_content.Address)](proof.address)
key = AccountTrieProofKey(
address: address,
stateRoot: state_root)
contentKey = ContentKey(
contentType: state_content.ContentType.accountTrieProof,
accountTrieProofKey: key)
var accountTrieProof = AccountTrieProof(@[])
for witness in proof.proof:
let witnessNode = ByteList(hexToSeqByte(witness))
discard accountTrieProof.add(witnessNode)
let encodedValue = SSZ.encode(accountTrieProof)
discard proto1.contentDB.put(contentKey.toContentId(), encodedValue, proto1.portalProtocol.localNode.id)
let foundContent = await proto2.getContent(contentKey)
check foundContent.isSome()
check decodeSsz(foundContent.get(), AccountTrieProof).isOk()
await node1.closeWait()
await node2.closeWait()
asyncTest "Test Share Full State":
let
trie = genesisToTrie("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
@ -235,36 +160,3 @@ procSuite "State Network":
await node1.closeWait()
await node2.closeWait()
await node3.closeWait()
asyncTest "Find other nodes in state network with correct custom distance":
let
node1 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20302))
sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(
rng, PrivateKey.random(rng[]), localAddress(20303))
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 (await node1.ping(node2.localNode)).isOk()
check (await node2.ping(node1.localNode)).isOk()
proto2.portalProtocol.seedTable()
let distance = proto1.portalProtocol.routingTable.logDistance(
node1.localNode.id, node2.localNode.id)
let nodes = await proto1.portalProtocol.findNodes(
proto2.portalProtocol.localNode, @[distance])
# TODO: This gives an error because of the custom distances issues that
# need to be resolved first.
skip()
# check:
# nodes.isOk()
# nodes.get().len() == 1
await node1.closeWait()
await node2.closeWait()

View File

@ -1 +0,0 @@
-d:"chronicles_sinks=textlines[dynamic],json[dynamic]"

View File

@ -1,82 +0,0 @@
# Nimbus
# 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.
# This is a fake bridge that reads state from a directory and backfills it to the portal state network.
{.push raises: [].}
import
std/[os, sugar],
confutils, confutils/std/net, chronicles, chronicles/topics_registry,
json_rpc/clients/httpclient,
chronos,
stew/[byteutils, io2],
eth/async_utils,
eth/common/eth_types,
../../network/state/state_content,
../../rpc/portal_rpc_client,
../../logging,
../eth_data_exporter/cl_data_exporter,
./state_bridge_conf
proc run(config: StateBridgeConf) {.raises: [CatchableError].} =
setupLogging(config.logLevel, config.logStdout)
notice "Launching Fluffy fake state bridge",
cmdParams = commandLineParams()
let portalRpcClient = newRpcHttpClient()
proc backfill(rpcAddress: string, rpcPort: Port) {.async raises: [OSError].} =
# info "Backfilling...", config.rpcAddress, ":", config.rpcPort
await portalRpcClient.connect(config.rpcAddress, Port(config.rpcPort), false)
let files = collect(for f in walkDir(config.dataDir): f.path)
for file in files:
let
content = readAllFile(file).valueOr:
warn "Skipping file because of error \n", file=file, error=($error)
continue
decoded =
try:
Json.decode(content, JsonProofVector)
except SerializationError as e:
warn "Skipping file because of error \n", file=file, error = e.msg
continue
state_root = hexToByteArray[sizeof(Bytes32)](decoded.state_root)
for proof in decoded.proofs:
let
address = hexToByteArray[sizeof(state_content.Address)](proof.address)
key = AccountTrieProofKey(
address: address,
stateRoot: state_root)
contentKey = ContentKey(
contentType: ContentType.accountTrieProof,
accountTrieProofKey: key)
encodedKey = encode(contentKey)
var accountTrieProof = AccountTrieProof(@[])
for witness in proof.proof:
let witnessNode = ByteList(hexToSeqByte(witness))
discard accountTrieProof.add(witnessNode)
discard await portalRpcClient.portal_stateGossip(encodedKey.asSeq().toHex(), SSZ.encode(accountTrieProof).toHex())
await portalRpcClient.close()
notice "Backfill done..."
waitFor backfill(config.rpcAddress, Port(config.rpcPort))
while true:
poll()
when isMainModule:
{.pop.}
let config = StateBridgeConf.load()
{.push raises: [].}
case config.cmd
of StateBridgeCmd.noCommand:
run(config)

View File

@ -1,54 +0,0 @@
# Nimbus
# 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.
{.push raises: [].}
import
confutils, confutils/std/net,
nimcrypto/hash,
../../logging
export net
type
StateBridgeCmd* = enum
noCommand
StateBridgeConf* = object
# Logging
logLevel* {.
desc: "Sets the log level"
defaultValue: "INFO"
name: "log-level" .}: string
logStdout* {.
hidden
desc: "Specifies what kind of logs should be written to stdout (auto, colors, nocolors, json)"
defaultValueDesc: "auto"
defaultValue: StdoutLogKind.Auto
name: "log-format" .}: StdoutLogKind
# Portal JSON-RPC API server to connect to
rpcAddress* {.
desc: "Listening address of the Portal JSON-RPC server"
defaultValue: "127.0.0.1"
name: "rpc-address" .}: string
rpcPort* {.
desc: "Listening port of the Portal JSON-RPC server"
defaultValue: 8545
name: "rpc-port" .}: Port
dataDir* {.
desc: "Data directory to lookup state data. Should point to the directory with json files generated by https://github.com/morph-dev/young-ethereum e.g. ./vendor/portal-spec-tests/tests/mainnet/state/"
name: "data-dir".}: string
case cmd* {.
command
defaultValue: noCommand .}: StateBridgeCmd
of noCommand:
discard