diff --git a/nimbus_verified_proxy/engine/accounts.nim b/nimbus_verified_proxy/engine/accounts.nim index 0dd22b9a6d..06483ada71 100644 --- a/nimbus_verified_proxy/engine/accounts.nim +++ b/nimbus_verified_proxy/engine/accounts.nim @@ -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( @@ -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) @@ -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) @@ -103,12 +105,12 @@ 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 @@ -116,16 +118,16 @@ proc getAccount*( 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 @@ -134,36 +136,38 @@ 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, @@ -171,12 +175,12 @@ proc getStorageAt*( 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 @@ -184,13 +188,13 @@ proc getStorageAt*( 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 @@ -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) @@ -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) diff --git a/nimbus_verified_proxy/engine/blocks.nim b/nimbus_verified_proxy/engine/blocks.nim index 332386fc17..d44e0ba36e 100644 --- a/nimbus_verified_proxy/engine/blocks.nim +++ b/nimbus_verified_proxy/engine/blocks.nim @@ -23,26 +23,34 @@ import proc resolveBlockTag*( engine: RpcVerificationEngine, blockTag: BlockTag -): Result[BlockTag, string] = +): BlockTag {.raises: [UnavailableDataError].} = if blockTag.kind == bidAlias: let tag = blockTag.alias.toLowerAscii() case tag of "latest": let hLatest = engine.headerStore.latest.valueOr: - return err("Couldn't get the latest block number from header store") - ok(BlockTag(kind: bidNumber, number: Quantity(hLatest.number))) + raise newException( + UnavailableDataError, "Couldn't get the latest block number from header store" + ) + BlockTag(kind: bidNumber, number: Quantity(hLatest.number)) of "finalized": let hFinalized = engine.headerStore.finalized.valueOr: - return err("Couldn't get the latest block number from header store") - ok(BlockTag(kind: bidNumber, number: Quantity(hFinalized.number))) + raise newException( + UnavailableDataError, + "Couldn't get the latest finalized block number from header store", + ) + BlockTag(kind: bidNumber, number: Quantity(hFinalized.number)) of "earliest": let hEarliest = engine.headerStore.earliest.valueOr: - return err("Couldn't get the latest block number from header store") - ok(BlockTag(kind: bidNumber, number: Quantity(hEarliest.number))) + raise newException( + UnavailableDataError, + "Couldn't get the earliest block number from header store", + ) + BlockTag(kind: bidNumber, number: Quantity(hEarliest.number)) else: - err("No support for block tag " & $blockTag) + raise newException(UnavailableDataError, "No support for block tag " & $blockTag) else: - ok(blockTag) + blockTag func convHeader*(blk: eth_api_types.BlockObject): Header = let nonce = blk.nonce.valueOr: @@ -78,15 +86,16 @@ proc walkBlocks( targetNum: base.BlockNumber, sourceHash: Hash32, targetHash: Hash32, -): Future[Result[void, string]] {.async: (raises: []).} = +) {.async: (raises: [CancelledError, EngineError]).} = var nextHash = sourceHash info "Starting block walk to verify requested block", blockHash = targetHash let numBlocks = sourceNum - targetNum if numBlocks > engine.maxBlockWalk: - return err( + raise newException( + VerificationError, "Cannot query more than " & $engine.maxBlockWalk & - " to verify the chain for the requested block" + " to verify the chain for the requested block", ) for i in 0 ..< numBlocks: @@ -97,10 +106,11 @@ proc walkBlocks( let blk = try: await engine.backend.eth_getBlockByHash(nextHash, false) - except CatchableError as e: - return err( + except EthBackendError as e: + # re-raise with extra information + e.msg = "Couldn't get block " & $nextHash & " during the chain traversal: " & e.msg - ) + raise e trace "getting next block", hash = nextHash, @@ -110,152 +120,172 @@ proc walkBlocks( let header = convHeader(blk) if header.computeBlockHash != nextHash: - return err("Encountered an invalid block header while walking the chain") + raise newException( + VerificationError, + "Encountered an invalid block header while walking the chain", + ) header if nextHeader.parentHash == targetHash: - return ok() + return nextHash = nextHeader.parentHash - err("the requested block is not part of the canonical chain") + raise newException( + VerificationError, "the requested block is not part of the canonical chain" + ) proc verifyHeader( engine: RpcVerificationEngine, header: Header, hash: Hash32 -): Future[Result[void, string]] {.async: (raises: []).} = +) {.async: (raises: [CancelledError, EngineError]).} = # verify calculated hash with the requested hash if header.computeBlockHash != hash: - return err("hashed block header doesn't match with blk.hash(downloaded)") + raise newException( + VerificationError, "hashed block header doesn't match with blk.hash(downloaded)" + ) if not engine.headerStore.contains(hash): let latestHeader = engine.headerStore.latest.valueOr: - return err("Couldn't get the latest header, syncing in progress") + raise newException( + UnavailableDataError, + "Couldn't get the latest header for starting block walk (probably still syncing)", + ) # walk blocks backwards(time) from source to target - ?( - await engine.walkBlocks( - latestHeader.number, header.number, latestHeader.parentHash, hash - ) + await engine.walkBlocks( + latestHeader.number, header.number, latestHeader.parentHash, hash ) - ok() - proc verifyBlock( engine: RpcVerificationEngine, blk: BlockObject, fullTransactions: bool -): Future[Result[void, string]] {.async: (raises: []).} = +) {.async: (raises: [CancelledError, EngineError]).} = let header = convHeader(blk) - ?(await engine.verifyHeader(header, blk.hash)) + await engine.verifyHeader(header, blk.hash) # verify transactions if fullTransactions: - ?verifyTransactions(header.transactionsRoot, blk.transactions) + verifyTransactions(header.transactionsRoot, blk.transactions) # verify withdrawals if blk.withdrawalsRoot.isSome(): if blk.withdrawalsRoot.get() != orderedTrieRoot(blk.withdrawals.get(@[])): - return err("Withdrawals within the block do not yield the same withdrawals root") + raise newException( + VerificationError, + "Withdrawals within the block do not yield the same withdrawals root", + ) else: if blk.withdrawals.isSome(): - return err("Block contains withdrawals but no withdrawalsRoot") - - ok() + raise newException( + UnavailableDataError, "Block contains withdrawals but no withdrawalsRoot" + ) proc getBlock*( engine: RpcVerificationEngine, blockHash: Hash32, fullTransactions: bool -): Future[Result[BlockObject, string]] {.async: (raises: []).} = +): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} = # get the target block let blk = try: await engine.backend.eth_getBlockByHash(blockHash, fullTransactions) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Block fetch by hash failed: " & e.msg + raise e # verify requested hash with the downloaded hash if blockHash != blk.hash: - return err("the downloaded block hash doesn't match with the requested hash") + raise newException( + VerificationError, + "the downloaded block hash doesn't match with the requested hash", + ) # verify the block - ?(await engine.verifyBlock(blk, fullTransactions)) + await engine.verifyBlock(blk, fullTransactions) - ok(blk) + blk proc getBlock*( engine: RpcVerificationEngine, blockTag: BlockTag, fullTransactions: bool -): Future[Result[BlockObject, string]] {.async: (raises: []).} = - let numberTag = engine.resolveBlockTag(blockTag).valueOr: - return err(error) +): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} = + let numberTag = engine.resolveBlockTag(blockTag) # get the target block let blk = try: await engine.backend.eth_getBlockByNumber(numberTag, fullTransactions) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Block fetch by number failed: " & e.msg + raise e if numberTag.number != blk.number: - return - err("the downloaded block number doesn't match with the requested block number") + raise newException( + VerificationError, + "the downloaded block number doesn't match with the requested block number", + ) # verify the block - ?(await engine.verifyBlock(blk, fullTransactions)) + await engine.verifyBlock(blk, fullTransactions) - ok(blk) + blk proc getHeader*( engine: RpcVerificationEngine, blockHash: Hash32 -): Future[Result[Header, string]] {.async: (raises: []).} = +): Future[Header] {.async: (raises: [CancelledError, EngineError]).} = let cachedHeader = engine.headerStore.get(blockHash) if cachedHeader.isNone(): debug "did not find the header in the cache", blockHash = blockHash else: - return ok(cachedHeader.get()) + return cachedHeader.get() # get the target block let blk = try: await engine.backend.eth_getBlockByHash(blockHash, false) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Header fetch by hash failed: " & e.msg + raise e let header = convHeader(blk) if blockHash != blk.hash: - return err("the blk.hash(downloaded) doesn't match with the provided hash") + raise newException( + VerificationError, "the blk.hash(downloaded) doesn't match with the provided hash" + ) - ?(await engine.verifyHeader(header, blockHash)) + await engine.verifyHeader(header, blockHash) - ok(header) + header proc getHeader*( engine: RpcVerificationEngine, blockTag: BlockTag -): Future[Result[Header, string]] {.async: (raises: []).} = +): Future[Header] {.async: (raises: [CancelledError, EngineError]).} = let - numberTag = engine.resolveBlockTag(blockTag).valueOr: - return err(error) + numberTag = engine.resolveBlockTag(blockTag) n = distinctBase(numberTag.number) cachedHeader = engine.headerStore.get(n) if cachedHeader.isNone(): debug "did not find the header in the cache", blockTag = blockTag else: - return ok(cachedHeader.get()) + return cachedHeader.get() # get the target block let blk = try: await engine.backend.eth_getBlockByNumber(numberTag, false) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Header fetch by number failed: " & e.msg + raise e let header = convHeader(blk) if n != header.number: - return - err("the downloaded block number doesn't match with the requested block number") + raise newException( + VerificationError, + "the downloaded block number doesn't match with the requested block number", + ) - ?(await engine.verifyHeader(header, blk.hash)) + await engine.verifyHeader(header, blk.hash) - ok(header) + header diff --git a/nimbus_verified_proxy/engine/engine.nim b/nimbus_verified_proxy/engine/engine.nim index 5888282262..d659caf860 100644 --- a/nimbus_verified_proxy/engine/engine.nim +++ b/nimbus_verified_proxy/engine/engine.nim @@ -11,7 +11,7 @@ import ./types, ./utils, ./rpc_frontend, ./header_store, ./evm proc init*( T: type RpcVerificationEngine, config: RpcVerificationEngineConf -): T {.raises: [ValueError].} = +): T {.raises: [EngineError].} = let engine = RpcVerificationEngine( chainId: config.chainId, maxBlockWalk: config.maxBlockWalk, @@ -24,7 +24,7 @@ proc init*( engine.registerDefaultFrontend() let networkId = chainIdToNetworkId(config.chainId).valueOr: - raise newException(ValueError, error) + raise newException(EngineError, error) # since AsyncEvm requires a few transport methods (getStorage, getCode etc.) for initialization, we initialize the proxy first then the evm within it engine.evm = AsyncEvm.init(engine.toAsyncEvmStateBackend(), networkId) diff --git a/nimbus_verified_proxy/engine/evm.nim b/nimbus_verified_proxy/engine/evm.nim index a3dd79ccc1..f94c48c406 100644 --- a/nimbus_verified_proxy/engine/evm.nim +++ b/nimbus_verified_proxy/engine/evm.nim @@ -27,42 +27,35 @@ proc toAsyncEvmStateBackend*(engine: RpcVerificationEngine): AsyncEvmStateBacken ): Future[Opt[Account]] {.async: (raises: [CancelledError]).} = let account = try: - (await engine.getAccount(address, header.number, header.stateRoot)) - except CatchableError as e: - raise newException(CancelledError, e.msg) + Opt.some(await engine.getAccount(address, header.number, header.stateRoot)) + except EngineError as e: + Opt.none(Account) - if account.isOk(): - return Opt.some(account.get()) - - Opt.none(Account) + account storageProc = proc( header: Header, address: Address, slotKey: UInt256 ): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} = - let storageSlot = + let slot = try: - (await engine.getStorageAt(address, slotKey, header.number, header.stateRoot)) - except CatchableError as e: - raise newException(CancelledError, e.msg) - - if storageSlot.isOk(): - return Opt.some(storageSlot.get()) + Opt.some( + await engine.getStorageAt(address, slotKey, header.number, header.stateRoot) + ) + except EngineError as e: + Opt.none(UInt256) - Opt.none(UInt256) + slot codeProc = proc( header: Header, address: Address ): Future[Opt[seq[byte]]] {.async: (raises: [CancelledError]).} = let code = try: - (await engine.getCode(address, header.number, header.stateRoot)) - except CatchableError as e: - raise newException(CancelledError, e.msg) - - if code.isOk(): - return Opt.some(code.get()) + Opt.some(await engine.getCode(address, header.number, header.stateRoot)) + except EngineError as e: + Opt.none(seq[byte]) - Opt.none(seq[byte]) + code blockHashProc = proc( header: Header, number: BlockNumber diff --git a/nimbus_verified_proxy/engine/fees.nim b/nimbus_verified_proxy/engine/fees.nim index 8119d96ab3..619137c7ef 100644 --- a/nimbus_verified_proxy/engine/fees.nim +++ b/nimbus_verified_proxy/engine/fees.nim @@ -36,29 +36,25 @@ func median(prices: var openArray[GasInt]): GasInt = proc suggestGasPrice*( engine: RpcVerificationEngine -): Future[Result[GasInt, string]] {.async: (raises: []).} = +): Future[GasInt] {.async: (raises: [CancelledError, EngineError]).} = const minGasPrice = 30_000_000_000.GasInt let - blk = (await engine.getBlock(blockId("latest"), true)).valueOr: - return err(error) - txs = blk.transactions.toTransactions().valueOr: - return err(error) + blk = await engine.getBlock(blockId("latest"), true) + txs = blk.transactions.toTransactions() var prices = newSeqOfCap[GasInt](64) for tx in txs: if tx.gasPrice > GasInt(0): prices.add(tx.gasPrice) - ok(max(minGasPrice, median(prices))) + max(minGasPrice, median(prices)) proc suggestMaxPriorityGasPrice*( engine: RpcVerificationEngine -): Future[Result[GasInt, string]] {.async: (raises: []).} = +): Future[GasInt] {.async: (raises: [CancelledError, EngineError]).} = let - blk = (await engine.getBlock(blockId("latest"), true)).valueOr: - return err(error) - txs = blk.transactions.toTransactions().valueOr: - return err(error) + blk = await engine.getBlock(blockId("latest"), true) + txs = blk.transactions.toTransactions() var prices = newSeqOfCap[GasInt](64) @@ -66,4 +62,4 @@ proc suggestMaxPriorityGasPrice*( if tx.maxPriorityFeePerGas > GasInt(0): prices.add(tx.maxPriorityFeePerGas) - ok(median(prices)) + median(prices) diff --git a/nimbus_verified_proxy/engine/receipts.nim b/nimbus_verified_proxy/engine/receipts.nim index 6d618aab40..253f8ff76f 100644 --- a/nimbus_verified_proxy/engine/receipts.nim +++ b/nimbus_verified_proxy/engine/receipts.nim @@ -44,27 +44,30 @@ func toReceipts(recs: openArray[ReceiptObject]): seq[Receipt] = proc getReceipts( engine: RpcVerificationEngine, header: Header, blockTag: types.BlockTag -): Future[Result[seq[ReceiptObject], string]] {.async: (raises: []).} = +): Future[seq[ReceiptObject]] {.async: (raises: [CancelledError, EngineError]).} = let rxs = try: await engine.backend.eth_getBlockReceipts(blockTag) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Receipts fetch failed: " & e.msg + raise e + if rxs.isSome(): if orderedTrieRoot(toReceipts(rxs.get())) != header.receiptsRoot: - return - err("downloaded receipts do not evaluate to the receipts root of the block") + raise newException( + VerificationError, + "downloaded receipts do not evaluate to the receipts root of the block", + ) else: - return err("error downloading the receipts") + raise newException(UnavailableDataError, "error downloading the receipts") - return ok(rxs.get()) + rxs.get() proc getReceipts*( engine: RpcVerificationEngine, blockTag: types.BlockTag -): Future[Result[seq[ReceiptObject], string]] {.async: (raises: []).} = +): Future[seq[ReceiptObject]] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(blockTag)).valueOr: - return err(error) + header = await engine.getHeader(blockTag) # all other tags are automatically resolved while getting the header numberTag = types.BlockTag( kind: BlockIdentifierKind.bidNumber, number: Quantity(header.number) @@ -74,10 +77,9 @@ proc getReceipts*( proc getReceipts*( engine: RpcVerificationEngine, blockHash: Hash32 -): Future[Result[seq[ReceiptObject], string]] {.async: (raises: []).} = +): Future[seq[ReceiptObject]] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(blockHash)).valueOr: - return err(error) + header = await engine.getHeader(blockHash) numberTag = types.BlockTag( kind: BlockIdentifierKind.bidNumber, number: Quantity(header.number) ) @@ -86,30 +88,27 @@ proc getReceipts*( proc resolveFilterTags*( engine: RpcVerificationEngine, filter: FilterOptions -): Result[FilterOptions, string] = +): FilterOptions {.raises: [UnavailableDataError].} = if filter.blockHash.isSome(): - return ok(filter) + return filter + let fromBlock = filter.fromBlock.get(types.BlockTag(kind: bidAlias, alias: "latest")) toBlock = filter.toBlock.get(types.BlockTag(kind: bidAlias, alias: "latest")) - fromBlockNumberTag = engine.resolveBlockTag(fromBlock).valueOr: - return err(error) - toBlockNumberTag = engine.resolveBlockTag(toBlock).valueOr: - return err(error) - - return ok( - FilterOptions( - fromBlock: Opt.some(fromBlockNumberTag), - toBlock: Opt.some(toBlockNumberTag), - address: filter.address, - topics: filter.topics, - blockHash: filter.blockHash, - ) + fromBlockNumberTag = engine.resolveBlockTag(fromBlock) + toBlockNumberTag = engine.resolveBlockTag(toBlock) + + FilterOptions( + fromBlock: Opt.some(fromBlockNumberTag), + toBlock: Opt.some(toBlockNumberTag), + address: filter.address, + topics: filter.topics, + blockHash: filter.blockHash, ) proc verifyLogs*( engine: RpcVerificationEngine, filter: FilterOptions, logObjs: seq[LogObject] -): Future[Result[void, string]] {.async: (raises: []).} = +) {.async: (raises: [CancelledError, EngineError]).} = # store block hashes contains the logs so that we can batch receipt requests var prevBlockHash: Hash32 @@ -121,8 +120,7 @@ proc verifyLogs*( # exploit sequentiality of logs if prevBlockHash != lg.blockHash.get(): # TODO: a cache will solve downloading the same block receipts for multiple logs - rxs = (await engine.getReceipts(lg.blockHash.get())).valueOr: - return err(error) + rxs = await engine.getReceipts(lg.blockHash.get()) prevBlockHash = lg.blockHash.get() let txIdx = distinctBase(lg.transactionIndex.get()) @@ -136,22 +134,20 @@ proc verifyLogs*( lg.blockNumber.get() < filter.fromBlock.get().number or lg.blockNumber.get() > filter.toBlock.get().number or (not match(toLog(lg), filter.address, filter.topics)): - return err("one of the returned logs is invalid") - - ok() + raise newException(VerificationError, "one of the returned logs is invalid") proc getLogs*( engine: RpcVerificationEngine, filter: FilterOptions -): Future[Result[seq[LogObject], string]] {.async: (raises: []).} = +): Future[seq[LogObject]] {.async: (raises: [CancelledError, EngineError]).} = let - resolvedFilter = engine.resolveFilterTags(filter).valueOr: - return err(error) + resolvedFilter = engine.resolveFilterTags(filter) logObjs = try: await engine.backend.eth_getLogs(resolvedFilter) - except CatchableError as e: - return err(e.msg) + except EthBackendError as e: + e.msg = "Logs fetch failed: " & e.msg + raise e - ?(await engine.verifyLogs(resolvedFilter, logObjs)) + await engine.verifyLogs(resolvedFilter, logObjs) - return ok(logObjs) + logObjs diff --git a/nimbus_verified_proxy/engine/rpc_frontend.nim b/nimbus_verified_proxy/engine/rpc_frontend.nim index 87839762eb..462d25b303 100644 --- a/nimbus_verified_proxy/engine/rpc_frontend.nim +++ b/nimbus_verified_proxy/engine/rpc_frontend.nim @@ -27,117 +27,101 @@ import proc registerDefaultFrontend*(engine: RpcVerificationEngine) = engine.frontend.eth_chainId = proc(): Future[UInt256] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} = engine.chainId engine.frontend.eth_blockNumber = proc(): Future[uint64] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} = ## Returns the number of the most recent block. let latest = engine.headerStore.latest.valueOr: - raise newException(ValueError, "Syncing") + raise newException(EngineError, "Syncing") latest.number.uint64 engine.frontend.eth_getBalance = proc( address: Address, quantityTag: BlockTag - ): Future[UInt256] {.async: (raises: [ValueError]).} = + ): Future[UInt256] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(quantityTag)).valueOr: - raise newException(ValueError, error) - account = (await engine.getAccount(address, header.number, header.stateRoot)).valueOr: - raise newException(ValueError, error) + header = await engine.getHeader(quantityTag) + account = await engine.getAccount(address, header.number, header.stateRoot) account.balance engine.frontend.eth_getStorageAt = proc( address: Address, slot: UInt256, quantityTag: BlockTag - ): Future[FixedBytes[32]] {.async: (raises: [ValueError]).} = + ): Future[FixedBytes[32]] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(quantityTag)).valueOr: - raise newException(ValueError, error) - storage = ( + header = await engine.getHeader(quantityTag) + storage = await engine.getStorageAt(address, slot, header.number, header.stateRoot) - ).valueOr: - raise newException(ValueError, error) storage.to(Bytes32) engine.frontend.eth_getTransactionCount = proc( address: Address, quantityTag: BlockTag - ): Future[Quantity] {.async: (raises: [ValueError]).} = + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(quantityTag)).valueOr: - raise newException(ValueError, error) - account = (await engine.getAccount(address, header.number, header.stateRoot)).valueOr: - raise newException(ValueError, error) + header = await engine.getHeader(quantityTag) + account = await engine.getAccount(address, header.number, header.stateRoot) Quantity(account.nonce) engine.frontend.eth_getCode = proc( address: Address, quantityTag: BlockTag - ): Future[seq[byte]] {.async: (raises: [ValueError]).} = + ): Future[seq[byte]] {.async: (raises: [CancelledError, EngineError]).} = let - header = (await engine.getHeader(quantityTag)).valueOr: - raise newException(ValueError, error) - code = (await engine.getCode(address, header.number, header.stateRoot)).valueOr: - raise newException(ValueError, error) + header = await engine.getHeader(quantityTag) + code = await engine.getCode(address, header.number, header.stateRoot) code engine.frontend.eth_getBlockByHash = proc( blockHash: Hash32, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [ValueError]).} = - (await engine.getBlock(blockHash, fullTransactions)).valueOr: - raise newException(ValueError, error) + ): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} = + await engine.getBlock(blockHash, fullTransactions) engine.frontend.eth_getBlockByNumber = proc( blockTag: BlockTag, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [ValueError]).} = - (await engine.getBlock(blockTag, fullTransactions)).valueOr: - raise newException(ValueError, error) + ): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} = + await engine.getBlock(blockTag, fullTransactions) engine.frontend.eth_getUncleCountByBlockNumber = proc( blockTag: BlockTag - ): Future[Quantity] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockTag, false)).valueOr: - raise newException(ValueError, error) + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockTag, false) Quantity(blk.uncles.len()) engine.frontend.eth_getUncleCountByBlockHash = proc( blockHash: Hash32 - ): Future[Quantity] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockHash, false)).valueOr: - raise newException(ValueError, error) + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockHash, false) Quantity(blk.uncles.len()) engine.frontend.eth_getBlockTransactionCountByNumber = proc( blockTag: BlockTag - ): Future[Quantity] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockTag, true)).valueOr: - raise newException(ValueError, error) + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockTag, true) Quantity(blk.transactions.len) engine.frontend.eth_getBlockTransactionCountByHash = proc( blockHash: Hash32 - ): Future[Quantity] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockHash, true)).valueOr: - raise newException(ValueError, error) + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockHash, true) Quantity(blk.transactions.len) engine.frontend.eth_getTransactionByBlockNumberAndIndex = proc( blockTag: BlockTag, index: Quantity - ): Future[TransactionObject] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockTag, true)).valueOr: - raise newException(ValueError, error) + ): Future[TransactionObject] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockTag, true) if distinctBase(index) >= uint64(blk.transactions.len): - raise newException(ValueError, "provided transaction index is outside bounds") + raise newException(EngineError, "provided transaction index is outside bounds") let x = blk.transactions[distinctBase(index)] doAssert x.kind == tohTx @@ -146,12 +130,12 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = engine.frontend.eth_getTransactionByBlockHashAndIndex = proc( blockHash: Hash32, index: Quantity - ): Future[TransactionObject] {.async: (raises: [ValueError]).} = - let blk = (await engine.getBlock(blockHash, true)).valueOr: - raise newException(ValueError, error) + ): Future[TransactionObject] {.async: (raises: [CancelledError, EngineError]).} = + let blk = await engine.getBlock(blockHash, true) if distinctBase(index) >= uint64(blk.transactions.len): - raise newException(ValueError, "provided transaction index is outside bounds") + raise + newException(VerificationError, "provided transaction index is outside bounds") let x = blk.transactions[distinctBase(index)] doAssert x.kind == tohTx @@ -160,12 +144,11 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = engine.frontend.eth_call = proc( tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: bool = true - ): Future[seq[byte]] {.async: (raises: [CancelledError, ValueError]).} = + ): Future[seq[byte]] {.async: (raises: [CancelledError, EngineError]).} = if tx.to.isNone(): - raise newException(ValueError, "to address is required") + raise newException(EngineError, "to address is required") - let header = (await engine.getHeader(blockTag)).valueOr: - raise newException(ValueError, error) + let header = await engine.getHeader(blockTag) # Start fetching code to get it in the code cache discard engine.getCode(tx.to.get(), header.number, header.stateRoot) @@ -173,25 +156,23 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = # As a performance optimisation we concurrently pre-fetch the state needed # for the call by calling eth_createAccessList and then using the returned # access list keys to fetch the required state using eth_getProof. - (await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx)).isOkOr: - raise newException(ValueError, error) + await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx) let callResult = (await engine.evm.call(header, tx, optimisticStateFetch)).valueOr: - raise newException(ValueError, error) + raise newException(EngineError, error) if callResult.error.len() > 0: - raise newException(ValueError, callResult.error) + raise newException(EngineError, callResult.error) return callResult.output engine.frontend.eth_createAccessList = proc( tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: bool = true - ): Future[AccessListResult] {.async: (raises: [CancelledError, ValueError]).} = + ): Future[AccessListResult] {.async: (raises: [CancelledError, EngineError]).} = if tx.to.isNone(): - raise newException(ValueError, "to address is required") + raise newException(EngineError, "to address is required") - let header = (await engine.getHeader(blockTag)).valueOr: - raise newException(ValueError, error) + let header = await engine.getHeader(blockTag) # Start fetching code to get it in the code cache discard engine.getCode(tx.to.get(), header.number, header.stateRoot) @@ -199,25 +180,23 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = # As a performance optimisation we concurrently pre-fetch the state needed # for the call by calling eth_createAccessList and then using the returned # access list keys to fetch the required state using eth_getProof. - (await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx)).isOkOr: - raise newException(ValueError, error) + await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx) let (accessList, error, gasUsed) = ( await engine.evm.createAccessList(header, tx, optimisticStateFetch) ).valueOr: - raise newException(ValueError, error) + raise newException(EngineError, error) return AccessListResult(accessList: accessList, error: error, gasUsed: gasUsed.Quantity) engine.frontend.eth_estimateGas = proc( tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: bool = true - ): Future[Quantity] {.async: (raises: [CancelledError, ValueError]).} = + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} = if tx.to.isNone(): - raise newException(ValueError, "to address is required") + raise newException(EngineError, "to address is required") - let header = (await engine.getHeader(blockTag)).valueOr: - raise newException(ValueError, error) + let header = await engine.getHeader(blockTag) # Start fetching code to get it in the code cache discard engine.getCode(tx.to.get(), header.number, header.stateRoot) @@ -225,71 +204,72 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = # As a performance optimisation we concurrently pre-fetch the state needed # for the call by calling eth_createAccessList and then using the returned # access list keys to fetch the required state using eth_getProof. - (await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx)).isOkOr: - raise newException(ValueError, error) + await engine.populateCachesUsingAccessList(header.number, header.stateRoot, tx) let gasEstimate = (await engine.evm.estimateGas(header, tx, optimisticStateFetch)).valueOr: - raise newException(ValueError, error) + raise newException(EngineError, error) return gasEstimate.Quantity engine.frontend.eth_getTransactionByHash = proc( txHash: Hash32 - ): Future[TransactionObject] {.async: (raises: [ValueError]).} = + ): Future[TransactionObject] {.async: (raises: [CancelledError, EngineError]).} = let tx = try: await engine.backend.eth_getTransactionByHash(txHash) - except CatchableError as e: - raise newException(ValueError, e.msg) + except EthBackendError as e: + e.msg = "Transaction fetch failed: " & e.msg + raise e if tx.hash != txHash: raise newException( - ValueError, + VerificationError, "the downloaded transaction hash doesn't match the requested transaction hash", ) if not checkTxHash(tx, txHash): - raise - newException(ValueError, "the transaction doesn't hash to the provided hash") + raise newException( + VerificationError, "the transaction doesn't hash to the provided hash" + ) return tx engine.frontend.eth_getBlockReceipts = proc( blockTag: BlockTag - ): Future[Opt[seq[ReceiptObject]]] {.async: (raises: [ValueError]).} = - let rxs = (await engine.getReceipts(blockTag)).valueOr: - raise newException(ValueError, error) + ): Future[Opt[seq[ReceiptObject]]] {.async: (raises: [CancelledError, EngineError]).} = + let rxs = await engine.getReceipts(blockTag) + return Opt.some(rxs) engine.frontend.eth_getTransactionReceipt = proc( txHash: Hash32 - ): Future[ReceiptObject] {.async: (raises: [ValueError]).} = + ): Future[ReceiptObject] {.async: (raises: [CancelledError, EngineError]).} = let rx = try: await engine.backend.eth_getTransactionReceipt(txHash) - except CatchableError as e: - raise newException(ValueError, e.msg) - rxs = (await engine.getReceipts(rx.blockHash)).valueOr: - raise newException(ValueError, error) + except EthBackendError as e: + e.msg = "Receipt fetch failed: " & e.msg + raise e + + rxs = await engine.getReceipts(rx.blockHash) for r in rxs: if r.transactionHash == txHash: return r - raise newException(ValueError, "receipt couldn't be verified") + raise newException(VerificationError, "receipt couldn't be verified") engine.frontend.eth_getLogs = proc( filterOptions: FilterOptions - ): Future[seq[LogObject]] {.async: (raises: [ValueError]).} = - (await engine.getLogs(filterOptions)).valueOr: - raise newException(ValueError, error) + ): Future[seq[LogObject]] {.async: (raises: [CancelledError, EngineError]).} = + await engine.getLogs(filterOptions) engine.frontend.eth_newFilter = proc( filterOptions: FilterOptions - ): Future[string] {.async: (raises: [ValueError]).} = + ): Future[string] {.async: (raises: [CancelledError, EngineError]).} = if engine.filterStore.len >= MAX_FILTERS: - raise newException(ValueError, "FilterStore already full") + raise newException(EngineError, "FilterStore already full") var id: array[8, byte] # 64bits @@ -298,7 +278,7 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = for i in 0 .. (MAX_ID_TRIES + 1): if randomBytes(id) != len(id): raise newException( - ValueError, "Couldn't generate a random identifier for the filter" + EngineError, "Couldn't generate a random identifier for the filter" ) strId = toHex(id) @@ -307,8 +287,9 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = break if i >= MAX_ID_TRIES: - raise - newException(ValueError, "Couldn't create a unique identifier for the filter") + raise newException( + EngineError, "Couldn't create a unique identifier for the filter" + ) engine.filterStore[strId] = FilterStoreItem(filter: filterOptions, blockMarker: Opt.none(Quantity)) @@ -317,7 +298,7 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = engine.frontend.eth_uninstallFilter = proc( filterId: string - ): Future[bool] {.async: (raises: [ValueError]).} = + ): Future[bool] {.async: (raises: [CancelledError, EngineError]).} = if filterId in engine.filterStore: engine.filterStore.del(filterId) return true @@ -326,28 +307,31 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = engine.frontend.eth_getFilterLogs = proc( filterId: string - ): Future[seq[LogObject]] {.async: (raises: [ValueError]).} = - if filterId notin engine.filterStore: - raise newException(ValueError, "Filter doesn't exist") + ): Future[seq[LogObject]] {.async: (raises: [CancelledError, EngineError]).} = + let filterItem = + try: + engine.filterStore[filterId] + except KeyError as e: + raise newException(UnavailableDataError, "Filter doesn't exist") - (await engine.getLogs(engine.filterStore[filterId].filter)).valueOr: - raise newException(ValueError, error) + await engine.getLogs(filterItem.filter) engine.frontend.eth_getFilterChanges = proc( filterId: string - ): Future[seq[LogObject]] {.async: (raises: [ValueError]).} = - if filterId notin engine.filterStore: - raise newException(ValueError, "Filter doesn't exist") - + ): Future[seq[LogObject]] {.async: (raises: [CancelledError, EngineError]).} = let - filterItem = engine.filterStore[filterId] - filter = engine.resolveFilterTags(filterItem.filter).valueOr: - raise newException(ValueError, error) + filterItem = + try: + engine.filterStore[filterId] + except KeyError as e: + raise newException(UnavailableDataError, "Filter doesn't exist") + + filter = engine.resolveFilterTags(filterItem.filter) # after resolving toBlock is always some and a number tag toBlock = filter.toBlock.get().number if filterItem.blockMarker.isSome() and toBlock <= filterItem.blockMarker.get(): - raise newException(ValueError, "No changes for the filter since the last query") + raise newException(EngineError, "No changes for the filter since the last query") let fromBlock = @@ -365,16 +349,19 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = topics: filter.topics, blockHash: filter.blockHash, ) - logObjs = (await engine.getLogs(changesFilter)).valueOr: - raise newException(ValueError, error) + logObjs = await engine.getLogs(changesFilter) # all logs verified so we can update blockMarker - engine.filterStore[filterId].blockMarker = Opt.some(toBlock) + try: + engine.filterStore[filterId].blockMarker = Opt.some(toBlock) + except KeyError as e: + raise + newException(UnavailableDataError, "Filter removed before it could be updated") return logObjs engine.frontend.eth_blobBaseFee = proc(): Future[UInt256] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} = let com = CommonRef.new( DefaultDbMemory.newCoreDbRef(), @@ -384,30 +371,27 @@ proc registerDefaultFrontend*(engine: RpcVerificationEngine) = statelessProviderEnabled = true, # Enables collection of witness keys ) - let header = (await engine.getHeader(blockId("latest"))).valueOr: - raise newException(ValueError, error) + let header = await engine.getHeader(blockId("latest")) if header.blobGasUsed.isNone(): - raise newException(ValueError, "blobGasUsed missing from latest header") + raise newException(VerificationError, "blobGasUsed missing from latest header") if header.excessBlobGas.isNone(): - raise newException(ValueError, "excessBlobGas missing from latest header") + raise newException(VerificationError, "excessBlobGas missing from latest header") let blobBaseFee = getBlobBaseFee(header.excessBlobGas.get, com, com.toEVMFork(header)) * header.blobGasUsed.get.u256 return blobBaseFee engine.frontend.eth_gasPrice = proc(): Future[Quantity] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} = - let suggestedPrice = (await engine.suggestGasPrice()).valueOr: - raise newException(ValueError, error) + let suggestedPrice = await engine.suggestGasPrice() Quantity(suggestedPrice.uint64) engine.frontend.eth_maxPriorityFeePerGas = proc(): Future[Quantity] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} = - let suggestedPrice = (await engine.suggestMaxPriorityGasPrice()).valueOr: - raise newException(ValueError, error) + let suggestedPrice = await engine.suggestMaxPriorityGasPrice() Quantity(suggestedPrice.uint64) diff --git a/nimbus_verified_proxy/engine/transactions.nim b/nimbus_verified_proxy/engine/transactions.nim index 880802163b..c6be569d91 100644 --- a/nimbus_verified_proxy/engine/transactions.nim +++ b/nimbus_verified_proxy/engine/transactions.nim @@ -13,6 +13,7 @@ import eth/common/eth_types_rlp, eth/trie/[ordered_trie, trie_defs], web3/[eth_api_types, eth_api], + ./types, ../../execution_chain/beacon/web3_eth_conv proc toTransaction(tx: TransactionObject): Transaction = @@ -36,28 +37,32 @@ proc toTransaction(tx: TransactionObject): Transaction = authorizationList: tx.authorizationList.get(@[]), ) -proc toTransactions*(txs: openArray[TxOrHash]): Result[seq[Transaction], string] = +proc toTransactions*( + txs: openArray[TxOrHash] +): seq[Transaction] {.raises: [UnavailableDataError].} = var convertedTxs = newSeqOfCap[Transaction](txs.len) for x in txs: if x.kind == tohTx: convertedTxs.add toTransaction(x.tx) else: - return err("cannot construct a transaction trie using only txhashes") + raise newException( + UnavailableDataError, "cannot construct a transaction trie using only txhashes" + ) - return ok(convertedTxs) + convertedTxs proc checkTxHash*(txObj: TransactionObject, txHash: Hash32): bool = toTransaction(txObj).computeRlpHash() == txHash proc verifyTransactions*( txRoot: Hash32, transactions: seq[TxOrHash] -): Result[void, string] = +) {.raises: [EngineError].} = let - txs = toTransactions(transactions).valueOr: - return err(error) + txs = toTransactions(transactions) rootHash = orderedTrieRoot(txs) - if rootHash == txRoot: - return ok() - - err("calculated tx trie root doesn't match the provided tx trie root") + if rootHash != txRoot: + raise newException( + VerificationError, + "calculated tx trie root doesn't match the provided tx trie root", + ) diff --git a/nimbus_verified_proxy/engine/types.nim b/nimbus_verified_proxy/engine/types.nim index 9ace401ee6..907ff8b7c7 100644 --- a/nimbus_verified_proxy/engine/types.nim +++ b/nimbus_verified_proxy/engine/types.nim @@ -34,119 +34,154 @@ type BlockTag* = eth_api_types.RtBlockIdentifier + # generic engine error + # All EngineError's are propagated back to the application. + # Anything that need not be propagated must either be translated + # or absorbed. + EngineError* = object of CatchableError + + # these errors are abstracted to support a simple architecture + # (encode -> fetch -> decode) that is adaptable for different + # kinds of backends. These errors help in scoring endpoints too. + EthBackendError* = object of EngineError + EthBackendEncodingError* = object of EthBackendError + EthBackendFetchError* = object of EthBackendError + EthBackendDecodingError* = object of EthBackendError + + # besides backend errors the other errors that can occur + # There is not much use to differentiating these and are done + # to this extent just for the sake of it. + UnavailableDataError* = object of EngineError + VerificationError* = object of EngineError + # Backend API EthApiBackend* = object - eth_chainId*: proc(): Future[UInt256] {.async: (raises: [CancelledError]).} + eth_chainId*: + proc(): Future[UInt256] {.async: (raises: [CancelledError, EthBackendError]).} eth_getBlockByHash*: proc( blkHash: Hash32, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} eth_getBlockByNumber*: proc( blkNum: BlockTag, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} eth_getProof*: proc( address: Address, slots: seq[UInt256], blockId: BlockTag - ): Future[ProofResponse] {.async: (raises: [CancelledError]).} + ): Future[ProofResponse] {.async: (raises: [CancelledError, EthBackendError]).} eth_createAccessList*: proc( args: TransactionArgs, blockId: BlockTag - ): Future[AccessListResult] {.async: (raises: [CancelledError]).} + ): Future[AccessListResult] {.async: (raises: [CancelledError, EthBackendError]).} eth_getCode*: proc(address: Address, blockId: BlockTag): Future[seq[byte]] {. - async: (raises: [CancelledError]) + async: (raises: [CancelledError, EthBackendError]) .} eth_getBlockReceipts*: proc(blockId: BlockTag): Future[Opt[seq[ReceiptObject]]] {. - async: (raises: [CancelledError]) + async: (raises: [CancelledError, EthBackendError]) + .} + eth_getTransactionReceipt*: proc(txHash: Hash32): Future[ReceiptObject] {. + async: (raises: [CancelledError, EthBackendError]) .} - eth_getTransactionReceipt*: - proc(txHash: Hash32): Future[ReceiptObject] {.async: (raises: [CancelledError]).} eth_getTransactionByHash*: proc(txHash: Hash32): Future[TransactionObject] {. - async: (raises: [CancelledError]) + async: (raises: [CancelledError, EthBackendError]) .} eth_getLogs*: proc(filterOptions: FilterOptions): Future[seq[LogObject]] {. - async: (raises: [CancelledError]) + async: (raises: [CancelledError, EthBackendError]) .} # Frontend API EthApiFrontend* = object # Chain - eth_chainId*: proc(): Future[UInt256] {.async: (raises: [ValueError]).} - eth_blockNumber*: proc(): Future[uint64] {.async: (raises: [ValueError]).} + eth_chainId*: + proc(): Future[UInt256] {.async: (raises: [CancelledError, EngineError]).} + eth_blockNumber*: + proc(): Future[uint64] {.async: (raises: [CancelledError, EngineError]).} # State eth_getBalance*: proc(address: Address, blockId: BlockTag): Future[UInt256] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} eth_getStorageAt*: proc( address: Address, slot: UInt256, blockId: BlockTag - ): Future[FixedBytes[32]] {.async: (raises: [ValueError]).} + ): Future[FixedBytes[32]] {.async: (raises: [CancelledError, EngineError]).} eth_getTransactionCount*: proc( address: Address, blockId: BlockTag - ): Future[Quantity] {.async: (raises: [ValueError]).} + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} eth_getCode*: proc(address: Address, blockId: BlockTag): Future[seq[byte]] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} eth_getProof*: proc( address: Address, slots: seq[UInt256], blockId: BlockTag - ): Future[ProofResponse] {.async: (raises: [ValueError]).} + ): Future[ProofResponse] {.async: (raises: [CancelledError, EngineError]).} # Block eth_getBlockByHash*: proc( blkHash: Hash32, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [ValueError]).} + ): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} eth_getBlockByNumber*: proc( blkNum: BlockTag, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [ValueError]).} - eth_getUncleCountByBlockHash*: - proc(blkHash: Hash32): Future[Quantity] {.async: (raises: [ValueError]).} - eth_getUncleCountByBlockNumber*: - proc(blkNum: BlockTag): Future[Quantity] {.async: (raises: [ValueError]).} - eth_getBlockTransactionCountByHash*: - proc(blkHash: Hash32): Future[Quantity] {.async: (raises: [ValueError]).} - eth_getBlockTransactionCountByNumber*: - proc(blkNum: BlockTag): Future[Quantity] {.async: (raises: [ValueError]).} + ): Future[BlockObject] {.async: (raises: [CancelledError, EngineError]).} + eth_getUncleCountByBlockHash*: proc(blkHash: Hash32): Future[Quantity] {. + async: (raises: [CancelledError, EngineError]) + .} + eth_getUncleCountByBlockNumber*: proc(blkNum: BlockTag): Future[Quantity] {. + async: (raises: [CancelledError, EngineError]) + .} + eth_getBlockTransactionCountByHash*: proc(blkHash: Hash32): Future[Quantity] {. + async: (raises: [CancelledError, EngineError]) + .} + eth_getBlockTransactionCountByNumber*: proc(blkNum: BlockTag): Future[Quantity] {. + async: (raises: [CancelledError, EngineError]) + .} # Transaction eth_getTransactionByBlockHashAndIndex*: proc( blkHash: Hash32, index: Quantity - ): Future[TransactionObject] {.async: (raises: [ValueError]).} + ): Future[TransactionObject] {.async: (raises: [CancelledError, EngineError]).} eth_getTransactionByBlockNumberAndIndex*: proc( blkNum: BlockTag, index: Quantity - ): Future[TransactionObject] {.async: (raises: [ValueError]).} - eth_getTransactionByHash*: - proc(txHash: Hash32): Future[TransactionObject] {.async: (raises: [ValueError]).} + ): Future[TransactionObject] {.async: (raises: [CancelledError, EngineError]).} + eth_getTransactionByHash*: proc(txHash: Hash32): Future[TransactionObject] {. + async: (raises: [CancelledError, EngineError]) + .} # EVM eth_call*: proc( args: TransactionArgs, blockId: BlockTag, optimisticFetch: bool = true - ): Future[seq[byte]] {.async: (raises: [CancelledError, ValueError]).} + ): Future[seq[byte]] {.async: (raises: [CancelledError, EngineError]).} eth_createAccessList*: proc( args: TransactionArgs, blockId: BlockTag, optimisticFetch: bool = true - ): Future[AccessListResult] {.async: (raises: [CancelledError, ValueError]).} + ): Future[AccessListResult] {.async: (raises: [CancelledError, EngineError]).} eth_estimateGas*: proc( args: TransactionArgs, blockId: BlockTag, optimisticFetch: bool = true - ): Future[Quantity] {.async: (raises: [CancelledError, ValueError]).} + ): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} # Receipts eth_getBlockReceipts*: proc(blockId: BlockTag): Future[Opt[seq[ReceiptObject]]] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) + .} + eth_getTransactionReceipt*: proc(txHash: Hash32): Future[ReceiptObject] {. + async: (raises: [CancelledError, EngineError]) .} - eth_getTransactionReceipt*: - proc(txHash: Hash32): Future[ReceiptObject] {.async: (raises: [ValueError]).} eth_getLogs*: proc(filterOptions: FilterOptions): Future[seq[LogObject]] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) .} eth_newFilter*: proc(filterOptions: FilterOptions): Future[string] {. - async: (raises: [ValueError]) + async: (raises: [CancelledError, EngineError]) + .} + eth_uninstallFilter*: proc(filterId: string): Future[bool] {. + async: (raises: [CancelledError, EngineError]) + .} + eth_getFilterLogs*: proc(filterId: string): Future[seq[LogObject]] {. + async: (raises: [CancelledError, EngineError]) + .} + eth_getFilterChanges*: proc(filterId: string): Future[seq[LogObject]] {. + async: (raises: [CancelledError, EngineError]) .} - eth_uninstallFilter*: - proc(filterId: string): Future[bool] {.async: (raises: [ValueError]).} - eth_getFilterLogs*: - proc(filterId: string): Future[seq[LogObject]] {.async: (raises: [ValueError]).} - eth_getFilterChanges*: - proc(filterId: string): Future[seq[LogObject]] {.async: (raises: [ValueError]).} # Fee-based - eth_blobBaseFee*: proc(): Future[UInt256] {.async: (raises: [ValueError]).} - eth_gasPrice*: proc(): Future[Quantity] {.async: (raises: [ValueError]).} + eth_blobBaseFee*: + proc(): Future[UInt256] {.async: (raises: [CancelledError, EngineError]).} + eth_gasPrice*: + proc(): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} eth_maxPriorityFeePerGas*: - proc(): Future[Quantity] {.async: (raises: [ValueError]).} + proc(): Future[Quantity] {.async: (raises: [CancelledError, EngineError]).} FilterStoreItem* = object filter*: FilterOptions diff --git a/nimbus_verified_proxy/json_rpc_backend.nim b/nimbus_verified_proxy/json_rpc_backend.nim index e19e80d9ba..9f7fdf3215 100644 --- a/nimbus_verified_proxy/json_rpc_backend.nim +++ b/nimbus_verified_proxy/json_rpc_backend.nim @@ -31,102 +31,96 @@ proc init*(T: type JsonRpcClient, url: Web3Url): JsonRpcClient = proc start*( client: JsonRpcClient -): Future[Result[void, string]] {.async: (raises: []).} = +) {.async: (raises: [CancelledError, EthBackendError]).} = try: case client.kind of Http: await client.httpClient.connect(client.url) of WebSocket: await client.wsClient.connect(uri = client.url, compression = false, flags = {}) + except CancelledError as e: + raise e except CatchableError as e: - return err(e.msg) - - ok() + raise newException(EthBackendError, e.msg) template getClient(client: JsonRpcClient): RpcClient = case client.kind of Http: client.httpClient of WebSocket: client.wsClient +template handleBackendCall(callBackend: untyped): auto = + try: + await callBackend + except CancelledError as e: + raise e + except RpcPostError as e: + raise newException(EthBackendEncodingError, e.msg) + except ErrorResponse as e: + raise newException(EthBackendFetchError, e.msg) + except JsonRpcError as e: + raise newException(EthBackendDecodingError, e.msg) + except InvalidResponse as e: + raise newException(EthBackendDecodingError, e.msg) + except FailedHttpResponse as e: + raise newException(EthBackendDecodingError, e.msg) + except CatchableError as e: + raise newException(EthBackendError, e.msg) + proc getEthApiBackend*(client: JsonRpcClient): EthApiBackend = let - ethChainIdProc = proc(): Future[UInt256] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_chainId() - except CatchableError as e: - raise newException(CancelledError, e.msg) + ethChainIdProc = proc(): Future[UInt256] {. + async: (raises: [CancelledError, EthBackendError]) + .} = + handleBackendCall client.getClient().eth_chainId() getBlockByHashProc = proc( blkHash: Hash32, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getBlockByHash(blkHash, fullTransactions) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getBlockByHash(blkHash, fullTransactions) getBlockByNumberProc = proc( blkNum: BlockTag, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getBlockByNumber(blkNum, fullTransactions) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getBlockByNumber( + blkNum, fullTransactions + ) getProofProc = proc( address: Address, slots: seq[UInt256], blockId: BlockTag - ): Future[ProofResponse] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getProof(address, slots, blockId) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[ProofResponse] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getProof(address, slots, blockId) createAccessListProc = proc( args: TransactionArgs, blockId: BlockTag - ): Future[AccessListResult] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_createAccessList(args, blockId) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[AccessListResult] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_createAccessList(args, blockId) getCodeProc = proc( address: Address, blockId: BlockTag - ): Future[seq[byte]] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getCode(address, blockId) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[seq[byte]] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getCode(address, blockId) getTransactionByHashProc = proc( txHash: Hash32 - ): Future[TransactionObject] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getTransactionByHash(txHash) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[TransactionObject] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getTransactionByHash(txHash) getTransactionReceiptProc = proc( txHash: Hash32 - ): Future[ReceiptObject] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getTransactionReceipt(txHash) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[ReceiptObject] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getTransactionReceipt(txHash) getBlockReceiptsProc = proc( blockId: BlockTag - ): Future[Opt[seq[ReceiptObject]]] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getBlockReceipts(blockId) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[Opt[seq[ReceiptObject]]] {. + async: (raises: [CancelledError, EthBackendError]) + .} = + handleBackendCall client.getClient().eth_getBlockReceipts(blockId) getLogsProc = proc( filterOptions: FilterOptions - ): Future[seq[LogObject]] {.async: (raises: [CancelledError]).} = - try: - await client.getClient().eth_getLogs(filterOptions) - except CatchableError as e: - raise newException(CancelledError, e.msg) + ): Future[seq[LogObject]] {.async: (raises: [CancelledError, EthBackendError]).} = + handleBackendCall client.getClient().eth_getLogs(filterOptions) EthApiBackend( eth_chainId: ethChainIdProc, @@ -141,8 +135,12 @@ proc getEthApiBackend*(client: JsonRpcClient): EthApiBackend = eth_getTransactionReceipt: getTransactionReceiptProc, ) -proc stop*(client: JsonRpcClient) {.async: (raises: [CancelledError]).} = +proc stop*( + client: JsonRpcClient +) {.async: (raises: [CancelledError, EthBackendError]).} = try: await client.getClient().close() - except CatchableError: - raise newException(CancelledError, "coudln't close the json rpc client") + except CancelledError as e: + raise e + except CatchableError as e: + raise newException(EthBackendError, e.msg) diff --git a/nimbus_verified_proxy/json_rpc_frontend.nim b/nimbus_verified_proxy/json_rpc_frontend.nim index 0daad6cba0..ee21204cf1 100644 --- a/nimbus_verified_proxy/json_rpc_frontend.nim +++ b/nimbus_verified_proxy/json_rpc_frontend.nim @@ -25,46 +25,57 @@ type JsonRpcServer* = ref object proc init*( T: type JsonRpcServer, url: Web3Url -): JsonRpcServer {.raises: [JsonRpcError, ValueError, TransportAddressError].} = +): JsonRpcServer {.raises: [EngineError].} = let auth = @[httpCors(@[])] # TODO: for now we serve all cross origin requests parsedUrl = parseUri(url.web3Url) hostname = if parsedUrl.hostname == "": "127.0.0.1" else: parsedUrl.hostname + port = if parsedUrl.port == "": 8545 else: - parseInt(parsedUrl.port) - listenAddress = initTAddress(hostname, port) + try: + parseInt(parsedUrl.port) + except ValueError: + raise newException(EngineError, "Could not parse the port number") - case url.kind - of HttpUrl: - JsonRpcServer( - kind: Http, httpServer: newRpcHttpServer([listenAddress], RpcRouter.init(), auth) - ) - of WsUrl: - let server = - JsonRpcServer(kind: WebSocket, wsServer: newRpcWebSocketServer(listenAddress)) + listenAddress = + try: + initTAddress(hostname, port) + except TransportAddressError as e: + raise newException(EngineError, e.msg) - server.wsServer.router = RpcRouter.init() - server + try: + case url.kind + of HttpUrl: + JsonRpcServer( + kind: Http, + httpServer: newRpcHttpServer([listenAddress], RpcRouter.init(), auth), + ) + of WsUrl: + let server = + JsonRpcServer(kind: WebSocket, wsServer: newRpcWebSocketServer(listenAddress)) + + server.wsServer.router = RpcRouter.init() + server + except JsonRpcError as e: + raise newException(EngineError, e.msg) func getServer(server: JsonRpcServer): RpcServer = case server.kind of Http: server.httpServer of WebSocket: server.wsServer -proc start*(server: JsonRpcServer): Result[void, string] = +proc start*(server: JsonRpcServer) {.raises: [EngineError].} = try: case server.kind of Http: server.httpServer.start() of WebSocket: server.wsServer.start() - except CatchableError as e: - return err(e.msg) - - ok() + except JsonRpcError as e: + raise newException(EngineError, e.msg) proc injectEngineFrontend*(server: JsonRpcServer, frontend: EthApiFrontend) = server.getServer().rpc("eth_blockNumber") do() -> uint64: @@ -186,7 +197,7 @@ proc injectEngineFrontend*(server: JsonRpcServer, frontend: EthApiFrontend) = server.getServer().rpc("eth_maxPriorityFeePerGas") do() -> Quantity: await frontend.eth_maxPriorityFeePerGas() -proc stop*(server: JsonRpcServer) {.async: (raises: [CancelledError]).} = +proc stop*(server: JsonRpcServer) {.async: (raises: [EngineError]).} = try: case server.kind of Http: @@ -194,4 +205,4 @@ proc stop*(server: JsonRpcServer) {.async: (raises: [CancelledError]).} = of WebSocket: await server.wsServer.closeWait() except CatchableError as e: - raise newException(CancelledError, e.msg) + raise newException(EngineError, e.msg) diff --git a/nimbus_verified_proxy/nimbus_verified_proxy.nim b/nimbus_verified_proxy/nimbus_verified_proxy.nim index 43460dc519..b7ad5ed79f 100644 --- a/nimbus_verified_proxy/nimbus_verified_proxy.nim +++ b/nimbus_verified_proxy/nimbus_verified_proxy.nim @@ -42,11 +42,11 @@ proc cleanup*(ctx: ptr Context) = proc verifyChaindId( engine: RpcVerificationEngine -): Future[void] {.async: (raises: []).} = +): Future[void] {.async: (raises: [CancelledError]).} = let providerId = try: await engine.backend.eth_chainId() - except CatchableError: + except EthBackendError: 0.u256 # This is a chain/network mismatch error between the Nimbus verified proxy and @@ -105,13 +105,8 @@ proc run*( jsonRpcServer.injectEngineFrontend(engine.frontend) # start frontend and backend - var status = waitFor jsonRpcClient.start() - if status.isErr(): - raise newException(ValueError, status.error) - - status = jsonRpcServer.start() - if status.isErr(): - raise newException(ValueError, status.error) + waitFor jsonRpcClient.start() + jsonRpcServer.start() # just for short hand convenience template cfg(): auto = @@ -304,8 +299,9 @@ proc run*( if ctx != nil and ctx.stop: # Cleanup waitFor network.stop() - waitFor jsonRpcClient.stop() waitFor jsonRpcServer.stop() + waitFor jsonRpcClient.stop() + ctx.cleanup() # Notify client that cleanup is finished ctx.onHeader(nil, 2) diff --git a/nimbus_verified_proxy/tests/test_api_backend.nim b/nimbus_verified_proxy/tests/test_api_backend.nim index 84211371ea..0b45598e82 100644 --- a/nimbus_verified_proxy/tests/test_api_backend.nim +++ b/nimbus_verified_proxy/tests/test_api_backend.nim @@ -186,23 +186,29 @@ func convToPartialBlock(blk: BlockObject): BlockObject = proc initTestApiBackend*(t: TestApiState): EthApiBackend = let - ethChainIdProc = proc(): Future[UInt256] {.async: (raises: [CancelledError]).} = - return t.chainId + ethChainIdProc = proc(): Future[UInt256] {. + async: (raises: [CancelledError, EthBackendError]) + .} = + if t.chainId == u256(0): + raise newException( + EthBackendDecodingError, "chainId not set in test backend or is set to 0" + ) getBlockByHashProc = proc( blkHash: Hash32, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} = + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} = try: if fullTransactions: return t.blocks[blkHash] else: + # TODO: check if this proc can throw error in the future return convToPartialBlock(t.blocks[blkHash]) - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getBlockByNumberProc = proc( blkNum: BlockTag, fullTransactions: bool - ): Future[BlockObject] {.async: (raises: [CancelledError]).} = + ): Future[BlockObject] {.async: (raises: [CancelledError, EthBackendError]).} = try: # we directly use number here because the verified proxy should never use aliases let blkHash = t.nums[blkNum.number] @@ -211,72 +217,74 @@ proc initTestApiBackend*(t: TestApiState): EthApiBackend = return t.blocks[blkHash] else: return convToPartialBlock(t.blocks[blkHash]) - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getProofProc = proc( address: Address, slots: seq[UInt256], blkNum: BlockTag - ): Future[ProofResponse] {.async: (raises: [CancelledError]).} = + ): Future[ProofResponse] {.async: (raises: [CancelledError, EthBackendError]).} = try: # we directly use number here because the verified proxy should never use aliases let blkHash = t.nums[blkNum.number] t.proofs[(address, slots, blkHash)] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) createAccessListProc = proc( args: TransactionArgs, blkNum: BlockTag - ): Future[AccessListResult] {.async: (raises: [CancelledError]).} = + ): Future[AccessListResult] {.async: (raises: [CancelledError, EthBackendError]).} = try: # we directly use number here because the verified proxy should never use aliases let blkHash = t.nums[blkNum.number] t.accessLists[(args, blkHash)] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getCodeProc = proc( address: Address, blkNum: BlockTag - ): Future[seq[byte]] {.async: (raises: [CancelledError]).} = + ): Future[seq[byte]] {.async: (raises: [CancelledError, EthBackendError]).} = try: # we directly use number here because the verified proxy should never use aliases let blkHash = t.nums[blkNum.number] t.codes[(address, blkHash)] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getBlockReceiptsProc = proc( blockId: BlockTag - ): Future[Opt[seq[ReceiptObject]]] {.async: (raises: [CancelledError]).} = + ): Future[Opt[seq[ReceiptObject]]] {. + async: (raises: [CancelledError, EthBackendError]) + .} = try: # we directly use number here because the verified proxy should never use aliases let blkHash = t.nums[blockId.number] Opt.some(t.blockReceipts[blkHash]) - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getLogsProc = proc( filterOptions: FilterOptions - ): Future[seq[LogObject]] {.async: (raises: [CancelledError]).} = + ): Future[seq[LogObject]] {.async: (raises: [CancelledError, EthBackendError]).} = try: t.logs[filterOptions] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getTransactionByHashProc = proc( txHash: Hash32 - ): Future[TransactionObject] {.async: (raises: [CancelledError]).} = + ): Future[TransactionObject] {.async: (raises: [CancelledError, EthBackendError]).} = try: t.transactions[txHash] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) getTransactionReceiptProc = proc( txHash: Hash32 - ): Future[ReceiptObject] {.async: (raises: [CancelledError]).} = + ): Future[ReceiptObject] {.async: (raises: [CancelledError, EthBackendError]).} = try: t.receipts[txHash] - except CatchableError as e: - raise newException(CancelledError, e.msg) + except KeyError as e: + raise newException(EthBackendFetchError, e.msg) EthApiBackend( eth_chainId: ethChainIdProc, diff --git a/nimbus_verified_proxy/tests/test_blocks.nim b/nimbus_verified_proxy/tests/test_blocks.nim index 2c149a800e..6d21691675 100644 --- a/nimbus_verified_proxy/tests/test_blocks.nim +++ b/nimbus_verified_proxy/tests/test_blocks.nim @@ -97,20 +97,18 @@ suite "test verified blocks": kind: BlockIdentifierKind.bidNumber, number: Quantity(targetBlockNum + 1) ) - # TODO: catch the exact error try: let verifiedBlk = waitFor engine.frontend.eth_getBlockByNumber(unreachableTargetTag, true) check(false) - except CatchableError: + except EngineError: check(true) - # TODO: catch the exact error try: let verifiedBlk = waitFor engine.frontend.eth_getBlockByNumber(reachableTargetTag, true) check(true) - except CatchableError: + except EngineError: check(false) test "check block related API methods": diff --git a/nimbus_verified_proxy/tests/test_fees.nim b/nimbus_verified_proxy/tests/test_fees.nim index 323be0c09b..2ac39eb461 100644 --- a/nimbus_verified_proxy/tests/test_fees.nim +++ b/nimbus_verified_proxy/tests/test_fees.nim @@ -42,8 +42,7 @@ suite "test fees verification": let blobFee = waitFor engine.frontend.eth_blobBaseFee() # blobs weren't enables on paris check false - except CatchableError: - # TODO: change this to an appropriate error whenever refactoring is done + except EngineError: check true ts.clear() diff --git a/nimbus_verified_proxy/tests/test_proof_validation.nim b/nimbus_verified_proxy/tests/test_proof_validation.nim index d4a4a0f7cc..48de86380d 100644 --- a/nimbus_verified_proxy/tests/test_proof_validation.nim +++ b/nimbus_verified_proxy/tests/test_proof_validation.nim @@ -9,7 +9,7 @@ {.push raises: [], gcsafe.} -import unittest2, stint, stew/byteutils, web3, ../engine/accounts +import unittest2, stint, stew/byteutils, web3, ../engine/accounts, ./test_utils suite "Merkle proof of inclusion validation": test "Validate account proof": @@ -66,11 +66,10 @@ suite "Merkle proof of inclusion validation": ), ] - check: - getAccountFromProof( + checkNoEngineError: + discard getAccountFromProof( stateRoot, address, balance, nonce, codeHash, storageRoot, rlpNodes ) - .isOk() test "Validate storage proof": let slotValue = UInt256.fromHex("0x25a92a5853702f199bb2d805bba05d67025214a8") @@ -148,5 +147,4 @@ suite "Merkle proof of inclusion validation": let validationResult = getStorageFromProof(stateRoot, u256(0), proof) check: - validationResult.isOk() - validationResult.get == slotValue + validationResult == slotValue diff --git a/nimbus_verified_proxy/tests/test_receipts.nim b/nimbus_verified_proxy/tests/test_receipts.nim index 54e7a326b0..502ab46794 100644 --- a/nimbus_verified_proxy/tests/test_receipts.nim +++ b/nimbus_verified_proxy/tests/test_receipts.nim @@ -166,7 +166,7 @@ suite "test receipts verification": try: let againFilterChanges = waitFor engine.frontend.eth_getFilterChanges(newFilter) check false - except CatchableError: + except EngineError: check true ts.clear() diff --git a/nimbus_verified_proxy/tests/test_transactions.nim b/nimbus_verified_proxy/tests/test_transactions.nim index 10866f793c..d80abc5139 100644 --- a/nimbus_verified_proxy/tests/test_transactions.nim +++ b/nimbus_verified_proxy/tests/test_transactions.nim @@ -25,11 +25,10 @@ suite "test transaction verification": check checkTxHash(tx.tx, tx.tx.hash) test "check tx trie root": - let - blk = getBlockFromJson("nimbus_verified_proxy/tests/data/Istanbul.json") - res = verifyTransactions(blk.transactionsRoot, blk.transactions) + let blk = getBlockFromJson("nimbus_verified_proxy/tests/data/Istanbul.json") - check res.isOk() + checkNoEngineError: + verifyTransactions(blk.transactionsRoot, blk.transactions) test "check eth api methods": let diff --git a/nimbus_verified_proxy/tests/test_utils.nim b/nimbus_verified_proxy/tests/test_utils.nim index f64f8ade8f..589366cfde 100644 --- a/nimbus_verified_proxy/tests/test_utils.nim +++ b/nimbus_verified_proxy/tests/test_utils.nim @@ -9,6 +9,7 @@ {.push raises: [], gcsafe.} import + unittest2, stint, chronos, json_rpc/jsonmarshal, @@ -20,6 +21,16 @@ import ../engine/engine, ./test_api_backend +template checkNoEngineError*(body: untyped) = + let status = + try: + body + true + except EngineError as e: + false + + check(status) + proc getBlockFromJson*(filepath: string): BlockObject {.raises: [SerializationError].} = let blkBytes = readAllBytes(filepath) JrpcConv.decode(blkBytes.get, BlockObject) @@ -71,7 +82,7 @@ template `==`*(logs1: seq[LogObject], logs2: seq[LogObject]): bool = proc initTestEngine*( testState: TestApiState, headerCacheLen: int, maxBlockWalk: uint64 -): RpcVerificationEngine {.raises: [CatchableError].} = +): RpcVerificationEngine {.raises: [EngineError].} = let engineConf = RpcVerificationEngineConf( chainId: 1.u256,