Implement getStorageAt endpoint (#1216)

* Implement getStorageAt endpoint
This commit is contained in:
KonradStaniec 2022-09-09 15:59:36 +02:00 committed by GitHub
parent 621c6a31a7
commit c138a4913c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 185 additions and 23 deletions

View File

@ -9,6 +9,7 @@
import
stint,
stew/byteutils,
chronicles,
json_rpc/[rpcserver, rpcclient],
web3,
@ -25,6 +26,10 @@ logScope:
template encodeQuantity(value: UInt256): HexQuantityStr =
hexQuantityStr("0x" & value.toHex())
template encodeHexData(value: UInt256): HexDataStr =
hexDataStr("0x" & toBytesBe(value).toHex)
template encodeQuantity(value: Quantity): HexQuantityStr =
hexQuantityStr(encodeQuantity(value.uint64))
@ -33,6 +38,20 @@ type LightClientRpcProxy* = ref object
server*: RpcHttpServer
executionPayload*: Opt[ExecutionPayloadV1]
template checkPreconditions(payload: Opt[ExecutionPayloadV1], quantityTag: string) =
if payload.isNone():
raise newException(ValueError, "Syncing")
if quantityTag != "latest":
# TODO: for now we support only latest block, as its semantically most straight
# forward, i.e it is last received and a valid ExecutionPayloadV1.
# Ultimately we could keep track of n last valid payloads and support number
# queries for this set of blocks.
# `Pending` could be mapped to some optimistic header with the block
# fetched on demand.
raise newException(ValueError, "Only latest block is supported")
proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload
@ -45,16 +64,7 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
# TODO quantity tag should be better typed
lcProxy.server.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr:
if payload.isNone:
raise newException(ValueError, "Syncing")
if quantityTag != "latest":
# TODO for now we support only latest block, as its semanticly most streight
# forward i.e it is last received and valid ExecutionPayloadV1.
# Ultimatly we could keep track of n last valid payload and support number
# queries for this set of blocks
# `Pending` coud be mapped to some optimisc header with block fetched on demand
raise newException(ValueError, "Only latest block is supported")
checkPreconditions(payload, quantityTag)
# When requesting state for `latest` block number, we need to translate
# `latest` to actual block number as `latest` on proxy and on data provider
@ -82,3 +92,23 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
return encodeQuantity(proof.balance)
else:
raise newException(ValueError, "Data provided by data provider server is invalid")
lcProxy.server.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
checkPreconditions(payload, quantityTag)
let
executionPayload = payload.get
uslot = UInt256.fromHex(slot.string)
blockNumber = executionPayload.blockNumber.uint64
info "Forwarding eth_getStorageAt", executionBn = blockNumber
let proof = await lcProxy.client.eth_getProof(address, @[uslot], blockId(blockNumber))
let dataResult = getStorageData(executionPayload.stateRoot, uslot, proof)
if dataResult.isOk():
let slotValue = dataResult.get()
return encodeHexData(slotValue)
else:
raise newException(ValueError, dataResult.error)

View File

@ -47,3 +47,39 @@ suite "Merkle proof of inclusion validation":
storageRoot,
rlpNodes
)
test "Validate storage proof":
let slotValue = UInt256.fromHex("0x25a92a5853702f199bb2d805bba05d67025214a8")
let stateRoot = FixedBytes[32].fromHex("0x99b31961b56190853d6f20b17298c8e3aed0a88860f80d4662fb41712f93491d")
let proof = ProofResponse(
address: Address(hexToByteArray[20]("0x805fe47d1fe7d86496753bb4b36206953c1ae660")),
accountProof: @[
RlpEncodedBytes(hexToSeqByte("0xf90211a07399cef9c0d815f0ea8c485255564f7f749d3352f4cd13590b6354d521243433a0786151335ec66b81e6a5f8b0750d40a1440894febfae9021a246af0011dddc39a0d8be7a536ea6f5a70b6684a6bc5a954a3d87b424fe9c8d31e48b7697c2a439d6a0f270896eacce7a14d5f73c30c8d9864e18a7499a8c941872d0f543a583eed8d7a03d19b1d9c3294de123bab76b580db62e847b007ad24d7627c39513ad63ce7edea0cae2f92408fda1e856f7acb53c8f26984cc783dbb9ac8c4e93af362f458c2f0ca0fb6e6b3e8878fcd181be3b09eab73334d8ef6d89e26babf8072ba32e1ae5ce4aa016d11cb1bac71884c5057ec8afe714aa9b72125fb9ec6e2d9481869641a8e026a00d9828dddc21d4c4b5457a233ac518f508c88b3cd4e814eac2e28a2e2a632a08a00d396f8c462121f2e06a4288d668e98aafd2821d99b6dd1887aa660775318875a045c2a82a655fff0f7ab212635fff00ebf40e6918423f32c8abfeeb588a23c7c7a08be08722cc8bbd898101ce8a5299145d0964fd50eaf923f3feeb7df414c35fe5a0697c454102347fbd17fce54dc165220abac4ee992f1e19791c55ac17e14518e6a09502fa4ea94b14920ad984e6ab4d837068c5f00cfaefd1787a454a4705b4e2d5a08b9b53e865cd8910964ddfc3a0f1d87d8ea665ee9c0813d564de3744ffb147d7a0ae674b410e770b9fd691c72252a67ae461c82b91b88a9b42f1bbcc4a8733585780")),
RlpEncodedBytes(hexToSeqByte("0xf90211a09412f363cb562e21fccd9b4bb47a3b61215421eb9770b4a5730cb2cb6ea5bbc9a0ec0c28e23f6a9310c216965c08e1ec9c9fc9ec6112f12100263003be31d76e34a0378e652ba0a437830411206a126853271d599951a45d94a267ad5e31516563cca09b3a79cc886fd7dce88c60d39d8280ee625725b21e066358b8ca8c768911490ba088abe98c021c60a356abab43474f3fd9a3a852161b7933b0c8c932ba47e900cda052a527e30350d47b10cf22f43cf1ab288d8ffb723e6de54bc8f83b6189db2266a0bd5d883c317ab84c75785444ff61feae3ed9c2d4aab2017637ca8e974806e3c7a006c3451ace02de15b65cfd7c19c02c5c1040335f995dd957d3e07e8dc3079485a015af1df6ff5da1c38d32cf7a788c9764556e149c09bcfa92f020f77b155a03b4a0e0a5c318b7881c9e9a56d7f652a3532d5003565d5bd94337a180ce4ff1db8467a030d718ee799736a89d72b69189f1e27b0563d500ace86729685d056b09c6701ca0e2d4a13abc46bbe7b34290ca6ebd94dbbe228460fb6bb10413f6144459aa8463a0a7af172aa05889771cea29cdebdcd6bb7d4a6ec09b96629bbb64a99f4856fab8a07ece8ac878f91890f51a66f9ab6a145e1a6734cd2505ce8f2be7548617b065fda0c8819fca92b6ff1e3ea5624c0b4b582554ef7696a0e96fe696858739a8a5af9da0314d236293a35e001b071f1682bdc1e20c79af444b115fd0f8e697803b727d9180")),
RlpEncodedBytes(hexToSeqByte("0xf90211a0e8dd0264185a25c11978679ef7f80c8e50804286b5f3f15ac26d11f8f4fe8612a0398a219de5df7abe3f16eb8571583d262196726e74575ad86b88e2e27a14c529a0aafdffb23bdb02384873ed6d5df2beaf8ba1a3d7eb89a7a9cf145861ef3fd18fa09f7299d9c05587feee543d34f49c59401997d6a9d44e78d7a144b002e838be7ea0bc385eca8be25dcdf3b6b58a230ef917da28e568dbff4ad05b054feec92bd53da0bea9a90577955f6fa5a60150f37b9e77a6b233e0b75168cfb3aec7d8eb098163a0ca0114d558ad84b5afd4f5a0bd5f7469758a446cffbed94c996e4c57302d9b0aa0158a3d1058957bc5cd41d816bdb42a066132cde9439b7c1925af42256cfcfc07a0ec12e2d93e3f7862e5c86de6131caae8ac623ddbc9109b74ebc62a5ed919f60aa0fd900e1d33be45abd3e1ddda1447739c2eb90e3b53604a16c65b44bd96b2a999a019693693106b6a90707e1c9bdad704dc128c8a5690c126d573daaf007f2aedcba0ce308e3364196afb1988196382015c90e8d65f349b72e60ced504870c7f307d3a0677e417e01c738c8818f647984dbfb7683820dd8a50dba02eeb79f4314f6d81ba0ecd210d3a979b55b6d33ebc53434441d3cade0e96d6a101a082bda6cd4ca88baa055d90842c7606222257aa1e98d949a23f7d676512bb21395bfacebc9d718e1bda027e473f101976ee9201de7f867158e165eedf3fe472567a9178d96ca6856c01c80")),
RlpEncodedBytes(hexToSeqByte("0xf90211a09044017f2c2743fb2a70885262ce105c4d63095cc85e998b844ca5a08e2a6e14a020f6c529f0fcd791436640ba14ec77555cf06ac65cb75bd15a70270c940ad900a0c8f51f52d62882314e3b004ca0c3afdca1cb374582e484b10c371f553ff951c4a0975183fc3697fe81104fd4acc5e3a6167614786efcc52c940187cd517dc5f88fa00bf7e6698b3f1ba0963ced4ecd5f3fce6b7ae9346ee331ddfe54323a9027f944a06405c7db333d93ebad9e660b513373c974c5492012401a8ae14b5f2953c80e90a0bf9bad639ffcc1a8e047e03aace972039aa25c463c5880d1fd8907f684f82440a02cca4a3426ceabb8da2f06fe3491147a01eb0134128ec174d6836902f76ca9f2a070a886a555112f301b1545f883ed686519ae56cae46fb512e93986c37c63ec29a07df58e619bfcb18dc3ac5b49cf9eaa2817616e81e59204ccc66fd063b35aa4c6a09185c8c8e02e8a5dcfe642e776cf691208d0c79f8886585fc1bad1deebdf1dc8a0b660d946ab0ccca5467484c88789bc0baefcbb8f36b091f5758da2aa8f46a5e8a0127150d53d1b8d5aa81eeb898af87dbc55a2d0c2c6f1ef4d575e2dc1138dffa4a0a50a7266499bd407a2e60475df45b14cea1673957aca1a03740a5606117aa2bfa0434e10c0ff2e6b5ac8713a40b29bf8995e302c7d0b821c55c9f0cfc6d81fce87a0ed37d722b70122d0a5c96cd52810781b8b1a88277064d691a282d896fd301f0a80")),
RlpEncodedBytes(hexToSeqByte("0xf90211a05febd97d485ae84c4aa444320eddf9361b45ac0cd966eca4e86acadbbb9b1919a06b014b81f4328a4c92bd6639a0b7f06e55a875b5891b958e3f32aed237a60128a0878844a864828586bad11cc05427f942475e040da74a17affa41284c7c2b8a5ca0afa3cf559f694ca29166fd02462f8dc4263b25f5a6bcd012b5ea7792adf2aebba02ce34320a3ade650b5d6951fe0c6628a03ce227f06e46e2646f0dc85cb227348a01b4b6baed79a52ed6737deffe81b0f2fc55f2888639470266222946b9c85d47fa014b15187e49585497a789fa11b3499603a250f0f48f8f12313b453937239af72a0a304eac356264043417a7c9ca624d92ff08d32d3390d229a3ed96c089ac0a602a0f723b00e76886ba3206a92fa098996c87990e4231d4f08667201cc0891b778a7a0d67ede626d7500ad5d4f11bd9b9de562209627c50f3cfd967a03b41a17009a1aa04f46efe2492e2905d02802a7ff1f5d1718edfe36fcae46dd45954ee9861f13e1a042d8c5e5834755e7183313443996885b561b1ba6daa0e484b60d0becc245460da07302dec93846a62ecb50892edbc5a485775700aebf816badfa42af6e3853d16aa04b2b25079c39ad8d517a61e47141519b4b497b36c4edb81e6f34c7917823d414a01aac7bb66afad0b0e7f8ffeb928d34ab59fe428e4a77b30689956fabb94e9f56a01accc5cd467129ec35675c04625ae805dc974fef16119f49e44f27853aee58f480")),
RlpEncodedBytes(hexToSeqByte("0xf90111a0d7559f6dc085b66ea2cf7928f3aff9ca07e9518113f44678a662b8fd5c91ab60a0b584675ea78138a4fd465b08895f3e791e717c2e259bff313d922994ad1085da80a0901123278214a37f6cd1d18d67c9ad8c51a64f8cf18c7de342d59a6b072d2f25a097533fa6c344fb668de71a3aa0c30da9190d2b6ce2974b05ba6caa9c8a9c495aa0344b048b149b2c08d1aa92ba1741009c6a8a4726bf5e7db12c78f4ad521122ae80808080a092677d6f81cdcafb80add68e79d7322af8771830d7d444a7587e8296203b16a88080a0930e22b8279f903ce0046cabc3cd24f2659dcb9c08980509ee0d12cc0ddecbee80a0156aa8328726c06af81bc6219dfada3e7826f1337da178258886015e94b0501c80")),
RlpEncodedBytes(hexToSeqByte("0xf8679e20da5951bceaed385c03546f45f276b83d86bc0755e045f92f1d9071f431b846f8440180a07bb85da974b0ee4efcb379f528bcf7e947a55901d5a2c0d38bc9cc16c851e785a03b45ab254ec24f2bcb75a922f15031796bc433ea5a4514783705d185321e5f82"))
],
balance: UInt256.fromHex("0x0"),
codeHash: FixedBytes[32].fromHex("0x3b45ab254ec24f2bcb75a922f15031796bc433ea5a4514783705d185321e5f82"),
nonce: Quantity(uint64(1)),
storageHash: FixedBytes[32].fromHex("0x7bb85da974b0ee4efcb379f528bcf7e947a55901d5a2c0d38bc9cc16c851e785"),
storageProof: @[
StorageProof(
key: u256(0),
value: slotValue,
proof: @[
RlpEncodedBytes(hexToSeqByte("0xf8b18080a0a24d07e12c3fedf892f5325b16f489fc7f14d267cef99aa6b7b74da7eab7a47a80a078a183d317824c686d46f3cf0a344cd5e1f8f73e095ea29864d3f8ef599dc643808080a0925cf9d2d4b35a02377f6ef9c51f1bbab4842dff4befd4ee5d85bbc15bb8554a808080a020db4b8930daa709db04119c07ee39896da3ac30b961b7901a7bea81a5329a8680a024f50ff7b56d6a9de8a2fcfe21e1710377dddb106c39c834362bd41a9bce22518080")),
RlpEncodedBytes(hexToSeqByte("0xf7a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563959425a92a5853702f199bb2d805bba05d67025214a8"))
]
)
]
)
let validationResult = getStorageData(stateRoot, u256(0), proof)
check:
validationResult.isOk()
validationResult.get == slotValue

View File

@ -8,18 +8,33 @@
{.push raises: [Defect].}
import
std/[sequtils, typetraits],
std/[sequtils, typetraits, options],
stint,
stew/results,
eth/common/eth_types as etypes,
eth/common/eth_types_rlp,
eth/rlp,
eth/trie/hexary,
web3/ethtypes
export results
func toMDigest(arg: FixedBytes[32]): MDigest[256] =
MDigest[256](data: distinctBase(arg))
proc isAccountProofValid*(
proc isValidProof(
branch: seq[seq[byte]],
rootHash: KeccakHash,
key, value: seq[byte]): bool =
try:
# TODO: Investigate if this handles proof of non-existence.
# Probably not as bool is not expressive enough to say if proof is valid, but
# key actually does not exists in MPT
return isValidBranch(branch, rootHash, key, value)
except RlpError:
return false
proc isAccountProofValidInternal(
stateRoot: FixedBytes[32],
accountAddress: Address,
accountBalance: UInt256,
@ -27,7 +42,7 @@ proc isAccountProofValid*(
accountCodeHash: CodeHash,
accountStorageRootHash: StorageHash,
mptNodes: seq[RlpEncodedBytes]
): bool =
): Option[etypes.Account] =
let
mptNodesBytes = mptNodes.mapIt(distinctBase(it))
keccakStateRootHash = toMDigest(stateRoot)
@ -40,13 +55,94 @@ proc isAccountProofValid*(
accountEncoded = rlp.encode(acc)
accountKey = toSeq(keccakHash(distinctBase(accountAddress)).data)
try:
return isValidBranch(
mptNodesBytes,
keccakStateRootHash,
accountKey,
accountEncoded
)
except RlpError:
return false
let validProof = isValidProof(
mptNodesBytes,
keccakStateRootHash,
accountKey,
accountEncoded
)
if validProof:
return some(acc)
else:
return none(etypes.Account)
proc isAccountProofValid*(
stateRoot: FixedBytes[32],
accountAddress: Address,
accountBalance: UInt256,
accountNonce: Quantity,
accountCodeHash: CodeHash,
accountStorageRootHash: StorageHash,
mptNodes: seq[RlpEncodedBytes]
): bool =
let maybeAccount = isAccountProofValidInternal(
stateRoot,
accountAddress,
accountBalance,
accountNonce,
accountCodeHash,
accountStorageRootHash,
mptNodes
)
return maybeAccount.isSome()
proc isStorageProofValid(
account: etypes.Account,
storageProof: StorageProof): bool =
let
storageMptNodes = storageProof.proof.mapIt(distinctBase(it))
key = toSeq(keccakHash(toBytesBE(storageProof.key)).data)
encodedValue = rlp.encode(storageProof.value)
return isValidProof(storageMptNodes, account.storageRoot, key, encodedValue)
proc getStorageData*(
stateRoot: FixedBytes[32],
requestedSlot: UInt256,
proof: ProofResponse): Result[UInt256, string] =
let maybeAccount = isAccountProofValidInternal(
stateRoot,
proof.address,
proof.balance,
proof.nonce,
proof.codeHash,
proof.storageHash,
proof.accountProof
)
if maybeAccount.isSome():
let account = maybeAccount.unsafeGet()
if account.storageRoot == etypes.EMPTY_ROOT_HASH:
# valid account with empty storage, in that case getStorageAt
# return 0 value
return ok(u256(0))
if len(proof.storageProof) != 1:
return err("no storage proof for requested slot")
let sproof = proof.storageProof[0]
if len(sproof.proof) == 0:
return err("empty mpt proof for account with not empty storage")
if sproof.key != requestedSlot:
return err("received proof for invalid slot")
if sproof.value == UInt256.zero:
# TODO: zero value means that storage is empty. We need to verify proof of
# no existance. As we currenctly do not have that ability just, return zero
# value
return ok(sproof.value)
if isStorageProofValid(account, sproof):
return ok(sproof.value)
else:
return err("invalid storage proof")
else:
return err("invalid account proof")

2
vendor/nimbus-eth2 vendored

@ -1 +1 @@
Subproject commit d9ceb61dbd9e9763ba073b564eac7478e072f367
Subproject commit a770fadd013c11c174eb9c37ddeccc2ceb5411d4