2022-09-06 16:14:50 +00:00
|
|
|
# light client proxy
|
|
|
|
# Copyright (c) 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.
|
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
import
|
2022-09-09 13:59:36 +00:00
|
|
|
std/[sequtils, typetraits, options],
|
2022-09-06 16:14:50 +00:00
|
|
|
stint,
|
2022-09-09 13:59:36 +00:00
|
|
|
stew/results,
|
2022-09-06 16:14:50 +00:00
|
|
|
eth/common/eth_types as etypes,
|
|
|
|
eth/common/eth_types_rlp,
|
|
|
|
eth/rlp,
|
|
|
|
eth/trie/hexary,
|
|
|
|
web3/ethtypes
|
|
|
|
|
2022-09-09 13:59:36 +00:00
|
|
|
export results
|
|
|
|
|
2022-09-06 16:14:50 +00:00
|
|
|
func toMDigest(arg: FixedBytes[32]): MDigest[256] =
|
|
|
|
MDigest[256](data: distinctBase(arg))
|
|
|
|
|
2022-09-09 13:59:36 +00:00
|
|
|
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(
|
2022-09-06 16:14:50 +00:00
|
|
|
stateRoot: FixedBytes[32],
|
|
|
|
accountAddress: Address,
|
|
|
|
accountBalance: UInt256,
|
|
|
|
accountNonce: Quantity,
|
|
|
|
accountCodeHash: CodeHash,
|
|
|
|
accountStorageRootHash: StorageHash,
|
|
|
|
mptNodes: seq[RlpEncodedBytes]
|
2022-09-09 13:59:36 +00:00
|
|
|
): Option[etypes.Account] =
|
2022-09-06 16:14:50 +00:00
|
|
|
let
|
|
|
|
mptNodesBytes = mptNodes.mapIt(distinctBase(it))
|
|
|
|
keccakStateRootHash = toMDigest(stateRoot)
|
|
|
|
acc = etypes.Account(
|
|
|
|
nonce: distinctBase(accountNonce),
|
|
|
|
balance: accountBalance,
|
|
|
|
storageRoot: toMDigest(accountStorageRootHash),
|
|
|
|
codeHash: toMDigest(accountCodeHash)
|
|
|
|
)
|
|
|
|
accountEncoded = rlp.encode(acc)
|
|
|
|
accountKey = toSeq(keccakHash(distinctBase(accountAddress)).data)
|
|
|
|
|
2022-09-09 13:59:36 +00:00
|
|
|
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")
|
2022-09-06 16:14:50 +00:00
|
|
|
|
2022-09-09 13:59:36 +00:00
|
|
|
else:
|
|
|
|
return err("invalid account proof")
|