Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 59 additions & 60 deletions nimbus_verified_proxy/engine/accounts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ proc getAccountFromProof*(
accountCodeHash: Hash32,
accountStorageRoot: Hash32,
mptNodes: seq[RlpEncodedBytes],
): Result[Account, string] =
): Account {.raises: [VerificationError].} =
let
mptNodesBytes = mptNodes.mapIt(distinctBase(it))
acc = Account(
Expand All @@ -44,15 +44,15 @@ proc getAccountFromProof*(

case proofResult.kind
of MissingKey:
return ok(EMPTY_ACCOUNT)
return EMPTY_ACCOUNT
of ValidProof:
return ok(acc)
return acc
of InvalidProof:
return err(proofResult.errorMsg)
raise newException(VerificationError, proofResult.errorMsg)

proc getStorageFromProof(
account: Account, storageProof: StorageProof
): Result[UInt256, string] =
): UInt256 {.raises: [VerificationError].} =
let
storageMptNodes = storageProof.proof.mapIt(distinctBase(it))
key = toSeq(keccak256(toBytesBE(storageProof.key)).data)
Expand All @@ -62,39 +62,41 @@ proc getStorageFromProof(

case proofResult.kind
of MissingKey:
return ok(UInt256.zero)
return UInt256.zero
of ValidProof:
return ok(storageProof.value)
return storageProof.value
of InvalidProof:
return err(proofResult.errorMsg)
raise newException(VerificationError, proofResult.errorMsg)

proc getStorageFromProof*(
stateRoot: Hash32,
requestedSlot: UInt256,
proof: ProofResponse,
storageProofIndex = 0,
): Result[UInt256, string] =
let account =
?getAccountFromProof(
stateRoot, proof.address, proof.balance, proof.nonce, proof.codeHash,
proof.storageHash, proof.accountProof,
)
): UInt256 {.raises: [VerificationError].} =
let account = getAccountFromProof(
stateRoot, proof.address, proof.balance, proof.nonce, proof.codeHash,
proof.storageHash, proof.accountProof,
)

if account.storageRoot == EMPTY_ROOT_HASH:
# valid account with empty storage, in that case getStorageAt
# return 0 value
return ok(u256(0))
return u256(0)

if proof.storageProof.len() <= storageProofIndex:
return err("no storage proof for requested slot")
# this is not an UnavailableDataError because the unavailable data is in the proof
raise newException(VerificationError, "no storage proof for requested slot")

let storageProof = proof.storageProof[storageProofIndex]

if len(storageProof.proof) == 0:
return err("empty mpt proof for account with not empty storage")
raise newException(
VerificationError, "empty mpt proof for account with not empty storage"
)

if storageProof.key != requestedSlot:
return err("received proof for invalid slot")
raise newException(VerificationError, "received proof for invalid slot")

getStorageFromProof(account, storageProof)

Expand All @@ -103,29 +105,29 @@ proc getAccount*(
address: Address,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[Account, string]] {.async: (raises: []).} =
): Future[Account] {.async: (raises: [CancelledError, EngineError]).} =
let
cacheKey = (stateRoot, address)
cachedAcc = engine.accountsCache.get(cacheKey)
if cachedAcc.isSome():
return ok(cachedAcc.get())
return cachedAcc.get()

info "Forwarding eth_getAccount", blockNumber

let
proof =
try:
await engine.backend.eth_getProof(address, @[], blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
except EthBackendError as e:
e.msg = "getProof failed for account: " & e.msg
raise e

account = getAccountFromProof(
stateRoot, proof.address, proof.balance, proof.nonce, proof.codeHash,
proof.storageHash, proof.accountProof,
)

if account.isOk():
engine.accountsCache.put(cacheKey, account.get())
engine.accountsCache.put(cacheKey, account)

return account

Expand All @@ -134,63 +136,65 @@ proc getCode*(
address: Address,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[seq[byte], string]] {.async: (raises: []).} =
): Future[seq[byte]] {.async: (raises: [CancelledError, EngineError]).} =
# get verified account details for the address at blockNumber
let account = (await engine.getAccount(address, blockNumber, stateRoot)).valueOr:
return err(error)
let account = await engine.getAccount(address, blockNumber, stateRoot)

# if the account does not have any code, return empty hex data
if account.codeHash == EMPTY_CODE_HASH:
return ok(newSeq[byte]())
return newSeq[byte]()

let
cacheKey = (stateRoot, address)
cachedCode = engine.codeCache.get(cacheKey)
if cachedCode.isSome():
return ok(cachedCode.get())
return cachedCode.get()

info "Forwarding eth_getCode", blockNumber

let code =
try:
await engine.backend.eth_getCode(address, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
except EthBackendError as e:
e.msg = "Code fetch failed: " & e.msg
raise e

# verify the byte code. since we verified the account against
# the state root we just need to verify the code hash
if account.codeHash == keccak256(code):
engine.codeCache.put(cacheKey, code)
return ok(code)
return code
else:
return err("received code doesn't match the account code hash")
raise newException(
VerificationError, "received code doesn't match the account code hash"
)

proc getStorageAt*(
engine: RpcVerificationEngine,
address: Address,
slot: UInt256,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[UInt256, string]] {.async: (raises: []).} =
): Future[UInt256] {.async: (raises: [CancelledError, EngineError]).} =
let
cacheKey = (stateRoot, address, slot)
cachedSlotValue = engine.storageCache.get(cacheKey)
if cachedSlotValue.isSome():
return ok(cachedSlotValue.get())
return cachedSlotValue.get()

info "Forwarding eth_getStorageAt", blockNumber

let
proof =
try:
await engine.backend.eth_getProof(address, @[slot], blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
except EthBackendError as e:
e.msg = "getProof failed for account: " & e.msg
raise e

slotValue = getStorageFromProof(stateRoot, slot, proof)

if slotValue.isOk():
engine.storageCache.put(cacheKey, slotValue.get())
engine.storageCache.put(cacheKey, slotValue)

return slotValue

Expand All @@ -200,7 +204,7 @@ proc populateCachesForAccountAndSlots(
slots: seq[UInt256],
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[void, string]] {.async: (raises: []).} =
) {.async: (raises: [CancelledError, EngineError]).} =
var slotsToFetch: seq[UInt256]
for s in slots:
let storageCacheKey = (stateRoot, address, s)
Expand All @@ -214,47 +218,42 @@ proc populateCachesForAccountAndSlots(
proof =
try:
await engine.backend.eth_getProof(address, slotsToFetch, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
except EthBackendError as e:
e.msg = "getProof failed for populating accounts and slots cache: " & e.msg
raise e

account = getAccountFromProof(
stateRoot, proof.address, proof.balance, proof.nonce, proof.codeHash,
proof.storageHash, proof.accountProof,
)

if account.isOk():
engine.accountsCache.put(accountCacheKey, account.get())
engine.accountsCache.put(accountCacheKey, account)

for i, s in slotsToFetch:
let slotValue = getStorageFromProof(stateRoot, s, proof, i)
let
slotValue = getStorageFromProof(stateRoot, s, proof, i)
storageCacheKey = (stateRoot, address, s)

if slotValue.isOk():
let storageCacheKey = (stateRoot, address, s)
engine.storageCache.put(storageCacheKey, slotValue.get())

ok()
engine.storageCache.put(storageCacheKey, slotValue)

proc populateCachesUsingAccessList*(
engine: RpcVerificationEngine,
blockNumber: base.BlockNumber,
stateRoot: Root,
tx: TransactionArgs,
): Future[Result[void, string]] {.async: (raises: []).} =
) {.async: (raises: [CancelledError, EngineError]).} =
let accessListRes: AccessListResult =
try:
await engine.backend.eth_createAccessList(tx, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
except EthBackendError as e:
e.msg = "AccessList fetch failed for populating cache: " & e.msg
raise e

var futs = newSeqOfCap[Future[Result[void, string]]](accessListRes.accessList.len())
var futs = newSeqOfCap[Future[void]](accessListRes.accessList.len())
for accessPair in accessListRes.accessList:
let slots = accessPair.storageKeys.mapIt(UInt256.fromBytesBE(it.data))
futs.add engine.populateCachesForAccountAndSlots(
accessPair.address, slots, blockNumber, stateRoot
)

try:
await allFutures(futs)
except CatchableError as e:
return err(e.msg)

ok()
await allFutures(futs)
Loading