From 4fc229e919851c6f8605bc7109714c17c6996efd Mon Sep 17 00:00:00 2001 From: Samyxandz Date: Mon, 20 Apr 2026 22:13:33 +0530 Subject: [PATCH 1/4] feat:fix rpc error-handling --- execution_chain/rpc/debug.nim | 13 ++--- execution_chain/rpc/handler_utils.nim | 31 ++++++++++ execution_chain/rpc/oracle.nim | 7 ++- execution_chain/rpc/rpc_utils.nim | 64 +++++++++++++++----- execution_chain/rpc/server_api.nim | 84 ++++++++++++--------------- 5 files changed, 126 insertions(+), 73 deletions(-) create mode 100644 execution_chain/rpc/handler_utils.nim diff --git a/execution_chain/rpc/debug.nim b/execution_chain/rpc/debug.nim index 3d7145fb09..c191ccba71 100644 --- a/execution_chain/rpc/debug.nim +++ b/execution_chain/rpc/debug.nim @@ -13,6 +13,7 @@ import std/[json, times], json_rpc/rpcserver, web3/[eth_api_types, conversions], + ./handler_utils, ./rpc_utils, ./rpc_types, #../tracer, @@ -190,23 +191,20 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = # https://ethereum.github.io/execution-apis/api/methods/debug_getRawBlock server.rpc("debug_getRawBlock") do(blockTag: BlockTag) -> seq[byte]: ## Returns an RLP-encoded block. - let blockFromTag = chain.blockFromTag(blockTag).valueOr: - raise newException(ValueError, error) + let blockFromTag = requireFound(chain.blockFromTag(blockTag), "Block not found") rlp.encode(blockFromTag) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawHeader server.rpc("debug_getRawHeader") do(blockTag: BlockTag) -> seq[byte]: ## Returns an RLP-encoded header. - let header = chain.headerFromTag(blockTag).valueOr: - raise newException(ValueError, error) + let header = requireFound(chain.headerFromTag(blockTag), "Header not found") rlp.encode(header) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawReceipts server.rpc("debug_getRawReceipts") do(blockTag: BlockTag) -> seq[seq[byte]]: ## Returns an array of EIP-2718 binary-encoded receipts. - let header = chain.headerFromTag(blockTag).valueOr: - raise newException(ValueError, error) + let header = requireFound(chain.headerFromTag(blockTag), "Header not found") var res: seq[seq[byte]] for receipt in chain.baseTxFrame.getReceipts(header.receiptsRoot): res.add rlp.encode(receipt) @@ -235,8 +233,7 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = server.rpc("debug_executionWitness") do(quantityTag: BlockTag) -> ExecutionWitness: ## Returns an execution witness for the given block number. - let header = chain.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Header not found") + let header = requireFound(chain.headerFromTag(quantityTag), "Header not found") chain.getExecutionWitness(header.computeBlockHash()).valueOr: raise newException(ValueError, error) diff --git a/execution_chain/rpc/handler_utils.nim b/execution_chain/rpc/handler_utils.nim new file mode 100644 index 0000000000..c9ca170aa5 --- /dev/null +++ b/execution_chain/rpc/handler_utils.nim @@ -0,0 +1,31 @@ +# Nimbus +# Copyright (c) 2026 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +{.push raises: [].} + +import + json_rpc/rpcserver, + results + +proc invalidParams*(msg: string) {.raises: [ref ApplicationError].} = + raise (ref ApplicationError)(code: -32602, msg: msg) + +template lookupOr*[T](res: Result[Opt[T], string], onMissing: untyped): T = + let resolved = res + if resolved.isErr: + let errMsg = resolved.error + invalidParams(errMsg) + let found = resolved.get() + if found.isNone: + onMissing + found.get() + +template requireFound*[T](res: Result[Opt[T], string], msg: string): T = + lookupOr(res): + raise newException(ValueError, msg) diff --git a/execution_chain/rpc/oracle.nim b/execution_chain/rpc/oracle.nim index 484b9f019f..fcd57e504d 100644 --- a/execution_chain/rpc/oracle.nim +++ b/execution_chain/rpc/oracle.nim @@ -179,11 +179,14 @@ proc resolveBlockRange(oracle: Oracle, blockId: BlockTag, numBlocks: uint64): Re if head < reqEnd: return err("RequestBeyondHead: requested " & $reqEnd & ", head " & $head) else: - let resolved = oracle.chain.headerFromTag(blockId).valueOr: + let resolvedOpt = oracle.chain.headerFromTag(blockId).valueOr: return err(error) + if resolvedOpt.isNone: + return err("Block not found") + # Absolute number resolved. - reqEnd = resolved.number + reqEnd = resolvedOpt.get().number # If there are no blocks to return, short circuit. if blocks == 0: diff --git a/execution_chain/rpc/rpc_utils.nim b/execution_chain/rpc/rpc_utils.nim index a1bf771810..08f945d40a 100644 --- a/execution_chain/rpc/rpc_utils.nim +++ b/execution_chain/rpc/rpc_utils.nim @@ -13,7 +13,7 @@ import std/[sequtils, algorithm, strutils], ./rpc_types, ./params, - ../db/ledger, + ../db/[ledger, core_db, storage_types], ../constants, stint, ../utils/utils, ../transaction, @@ -415,47 +415,79 @@ proc getTotalDifficulty*(chain: ForkedChainRef, blockHash: Hash32, header: Heade # Note: It's ok to use baseTxFrame for TD as this is for historical blocks chain.baseTxFrame().getScore(blockHash) -proc headerFromTag*(chain: ForkedChainRef, blockTag: BlockTag): Result[Header, string] = +proc baseHasBlockNumber(chain: ForkedChainRef, number: base.BlockNumber): bool = + chain.baseTxFrame().hasKey(blockNumberToHashKey(number).toOpenArray) + +proc baseHasBlockHash(chain: ForkedChainRef, blockHash: Hash32): bool = + chain.baseTxFrame().hasKey(genericHashKey(blockHash).toOpenArray) + +proc earliestBlockNumber(chain: ForkedChainRef): base.BlockNumber = + chain.baseTxFrame().getChainTail() + +proc headerFromTag*(chain: ForkedChainRef, blockTag: BlockTag): Result[Opt[Header], string] = case blockTag.kind of bidAlias: let tag = blockTag.alias.toLowerAscii case tag of "latest": - ok(chain.latestHeader) + ok(Opt.some(chain.latestHeader)) of "finalized": - ok(chain.finalizedHeader) + ok(Opt.some(chain.finalizedHeader)) of "safe": - ok(chain.safeHeader) + ok(Opt.some(chain.safeHeader)) of "earliest": - chain.headerByNumber(base.BlockNumber(0)) + let header = chain.headerByNumber(chain.earliestBlockNumber()).valueOr: + return err(error) + ok(Opt.some(header)) else: err("Unsupported block tag " & tag) of bidNumber: let blockNum = base.BlockNumber blockTag.number - chain.headerByNumber(blockNum) + if blockNum > chain.latestNumber: + return ok(Opt.none(Header)) + if blockNum < chain.baseNumber and not chain.baseHasBlockNumber(blockNum): + return ok(Opt.none(Header)) + let header = chain.headerByNumber(blockNum).valueOr: + return err(error) + ok(Opt.some(header)) of bidHash: - chain.headerByHash(blockTag.hash) + if not chain.isInMemory(blockTag.hash) and not chain.baseHasBlockHash(blockTag.hash): + return ok(Opt.none(Header)) + let header = chain.headerByHash(blockTag.hash).valueOr: + return err(error) + ok(Opt.some(header)) -proc blockFromTag*(chain: ForkedChainRef, blockTag: BlockTag, noHash: bool = false): Result[Block, string] = +proc blockFromTag*(chain: ForkedChainRef, blockTag: BlockTag, noHash: bool = false): Result[Opt[Block], string] = case blockTag.kind of bidAlias: let tag = blockTag.alias.toLowerAscii case tag of "latest": - ok(chain.latestBlock) + ok(Opt.some(chain.latestBlock)) of "finalized": - ok(chain.finalizedBlock) + ok(Opt.some(chain.finalizedBlock)) of "safe": - ok(chain.safeBlock) - # wait till pruner pr is merged for tail semantics to be available, which is the appropriate way to resolve this tag + ok(Opt.some(chain.safeBlock)) of "earliest": - chain.blockByNumber(base.BlockNumber(0)) + let blk = chain.blockByNumber(chain.earliestBlockNumber()).valueOr: + return err(error) + ok(Opt.some(blk)) else: err("Unsupported block tag " & tag) of bidNumber: let blockNum = base.BlockNumber blockTag.number - chain.blockByNumber(blockNum) + if blockNum > chain.latestNumber: + return ok(Opt.none(Block)) + if blockNum < chain.baseNumber and not chain.baseHasBlockNumber(blockNum): + return ok(Opt.none(Block)) + let blk = chain.blockByNumber(blockNum).valueOr: + return err(error) + ok(Opt.some(blk)) of bidHash: if noHash: return err("query by hash not supported for this function") - chain.blockByHash(blockTag.hash) + if not chain.isInMemory(blockTag.hash) and not chain.baseHasBlockHash(blockTag.hash): + return ok(Opt.none(Block)) + let blk = chain.blockByHash(blockTag.hash).valueOr: + return err(error) + ok(Opt.some(blk)) diff --git a/execution_chain/rpc/server_api.nim b/execution_chain/rpc/server_api.nim index 4f3842cc5c..0d7b43ee47 100644 --- a/execution_chain/rpc/server_api.nim +++ b/execution_chain/rpc/server_api.nim @@ -30,6 +30,7 @@ import ./oracle, ./rpc_types, ./rpc_utils, + ./handler_utils, ./filters logScope: @@ -98,34 +99,38 @@ proc getProof*( storageProof: storage, ) -proc headerFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Header, string] = +proc headerFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Opt[Header], string] = api.chain.headerFromTag(blockTag) -proc headerFromTag(api: ServerAPIRef, blockTag: Opt[BlockTag]): Result[Header, string] = +proc headerFromTag(api: ServerAPIRef, blockTag: Opt[BlockTag]): Result[Opt[Header], string] = let blockId = blockTag.get(defaultTag) api.headerFromTag(blockId) -proc frameFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[CoreDbTxRef, string] = +proc frameFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Opt[CoreDbTxRef], string] = # TODO avoid loading full header if hash is given - let - header = ?api.headerFromTag(blockTag) + let headerOpt = api.headerFromTag(blockTag).valueOr: + return err(error) + + if headerOpt.isNone: + return ok(Opt.none(CoreDbTxRef)) + + let header = headerOpt.get() if header.number < api.chain.baseNumber: return err("Historical data not available") # TODO maybe use a new frame derived from txFrame, to protect against abuse? - ok api.chain.txFrame(header) + ok Opt.some(api.chain.txFrame(header)) -proc blockFromTag(api: ServerAPIRef, blockTag: BlockTag, noHash: bool = false): Result[Block, string] = +proc blockFromTag(api: ServerAPIRef, blockTag: BlockTag, noHash: bool = false): Result[Opt[Block], string] = api.chain.blockFromTag(blockTag, noHash) proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManager) = server.rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: ## Returns the balance of the account of given address. let - txFrame = api.frameFromTag(blockTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") address = data acc = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) acc.balance @@ -135,8 +140,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> FixedBytes[32]: ## Returns the value from a storage position at a given address. let - txFrame = api.frameFromTag(blockTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath slotKey = computeSlotKey(slot) @@ -148,8 +152,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> Quantity: ## Returns the number of transactions ak.s. nonce sent from an address. let - txFrame = api.frameFromTag(blockTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -170,8 +173,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - txFrame = api.frameFromTag(blockTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -203,9 +205,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## blockTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. ## Returns BlockObject or nil when no block was found. - let blk = api.blockFromTag(blockTag, noHash = true).valueOr: + let blk = lookupOr(api.blockFromTag(blockTag, noHash = true)): return nil - let blockHash = blk.header.computeBlockHash return populateBlockObject( blockHash, blk, api.getTotalDifficulty(blockHash, blk.header), fullTransactions @@ -293,10 +294,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag # would operate on this enum instead of raw strings. This change would need # to be done on every endpoint to be consistent. let - blockFrom = api.headerFromTag(filterOptions.fromBlock).valueOr: - raise newException(ValueError, "Block not found") - blockTo = api.headerFromTag(filterOptions.toBlock).valueOr: - raise newException(ValueError, "Block not found") + blockFrom = requireFound(api.headerFromTag(filterOptions.fromBlock), "Block not found") + blockTo = requireFound(api.headerFromTag(filterOptions.toBlock), "Block not found") # Note: if fromHeader.number > toHeader.number, no logs will be # returned. This is consistent with, what other ethereum clients return @@ -325,8 +324,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the return value of executed contract. let - header = api.headerFromTag(blockTag).valueOr: - raise newException(ValueError, "Block not found") + header = requireFound(api.headerFromTag(blockTag), "Block not found") headerHash = header.computeBlockHash txFrame = api.chain.txFrame(headerHash) res = rpcCallEvm(args, header, headerHash, api.com, txFrame).valueOr: @@ -364,8 +362,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the amount of gas used. let - header = api.headerFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Block not found") + header = requireFound(api.headerFromTag(blockId("latest")), "Block not found") headerHash = header.computeBlockHash txFrame = api.chain.txFrame(headerHash) # TODO: change 0 to configureable gas cap @@ -405,8 +402,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## ## blockTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the number of transactions in this block. - let blk = api.blockFromTag(blockTag, noHash = true).valueOr: - raise newException(ValueError, "Block not found: " & error) + let blk = requireFound(api.blockFromTag(blockTag, noHash = true), "Block not found") Quantity(blk.transactions.len) @@ -425,8 +421,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## ## blockTag: integer of a block number, or the string "latest", see the default block parameter. ## Returns integer of the number of uncles in this block. - let blk = api.blockFromTag(blockTag, noHash = true).valueOr: - raise newException(ValueError, "Block not found: " & error) + let blk = requireFound(api.blockFromTag(blockTag, noHash = true), "Block not found") Quantity(blk.uncles.len) @@ -463,8 +458,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = api.frameFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Latest Block not found") + txFrame = requireFound(api.frameFromTag(blockId("latest")), "Latest Block not found") accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) eip155 = api.com.isEIP155(api.chain.latestNumber) @@ -485,8 +479,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = api.frameFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Latest Block not found") + txFrame = requireFound(api.frameFromTag(blockId("latest")), "Latest Block not found") accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) @@ -580,7 +573,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantity: the transaction index position. ## NOTE : "pending" blockTag is not supported. let index = uint64(quantity) - let blk = api.blockFromTag(quantityTag, noHash = true).valueOr: + let blk = lookupOr(api.blockFromTag(quantityTag, noHash = true)): return nil if index >= uint64(blk.transactions.len): @@ -607,8 +600,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns: the proof response containing the account, account proof and storage proof let - txFrame = api.frameFromTag(quantityTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(quantityTag), "Block not found") getProof(txFrame, data, slots) server.rpc("eth_getBlockReceipts") do( @@ -619,9 +611,10 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "requireCanonical is a pre-merge concept and is not supported") + let blk = lookupOr(api.blockFromTag(quantityTag)): + return Opt.none(seq[ReceiptObject]) + let - blk = api.blockFromTag(quantityTag).valueOr: - return Opt.none(seq[ReceiptObject]) blkHash = blk.header.computeBlockHash receipts = api.chain.receiptsByBlockHash(blkHash).valueOr: return Opt.none(seq[ReceiptObject]) @@ -648,17 +641,15 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag args: TransactionArgs, quantityTag: BlockTag ) -> AccessListResult: ## Generates an access list for a transaction. + let header = requireFound(api.headerFromTag(quantityTag), "Block not found") try: - let header = api.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Block not found") return createAccessList(header, api.com, api.chain, args) except CatchableError as exc: return AccessListResult(error: Opt.some("createAccessList error: " & exc.msg)) server.rpc("eth_blobBaseFee") do() -> Quantity: ## Returns the base fee per blob gas in wei. - let header = api.headerFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Block not found") + let header = requireFound(api.headerFromTag(blockId("latest")), "Block not found") if header.excessBlobGas.isNone: raise newException(ValueError, "excessBlobGas missing from latest header") let blobBaseFee = @@ -700,7 +691,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantity: the uncle's index position. ## Returns BlockObject or nil when no block was found. let index = uint64(quantity) - let blk = api.blockFromTag(quantityTag).valueOr: + let blk = lookupOr(api.blockFromTag(quantityTag)): return nil if index < 0 or index >= blk.uncles.len.uint64: @@ -734,8 +725,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## Returns the block access list by block number, tag or block hash. ## - let header = api.chain.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, error) + let header = lookupOr(api.headerFromTag(quantityTag)): + return Opt.none(BlockAccessList) if not api.com.isAmsterdamOrLater(header.timestamp): raise newException(ValueError, "Block access list not available for pre-Amsterdam blocks") @@ -760,8 +751,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_getStorageValues") do(request: StorageValuesRequest, blockTag: BlockTag) -> StorageValuesResponse: let - txFrame = api.frameFromTag(blockTag).valueOr: - raise newException(ValueError, error) + txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") var res: StorageObject for req in request.list: From c238228bb1ca3c86e3daeb23bd09e2d4801a95d5 Mon Sep 17 00:00:00 2001 From: Samyxandz Date: Thu, 23 Apr 2026 23:23:46 +0530 Subject: [PATCH 2/4] rename functions --- execution_chain/rpc/debug.nim | 8 ++--- execution_chain/rpc/handler_utils.nim | 6 ++-- execution_chain/rpc/server_api.nim | 42 +++++++++++++-------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/execution_chain/rpc/debug.nim b/execution_chain/rpc/debug.nim index c191ccba71..c9d2c02336 100644 --- a/execution_chain/rpc/debug.nim +++ b/execution_chain/rpc/debug.nim @@ -191,20 +191,20 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = # https://ethereum.github.io/execution-apis/api/methods/debug_getRawBlock server.rpc("debug_getRawBlock") do(blockTag: BlockTag) -> seq[byte]: ## Returns an RLP-encoded block. - let blockFromTag = requireFound(chain.blockFromTag(blockTag), "Block not found") + let blockFromTag = getOrRaise(chain.blockFromTag(blockTag), "Block not found") rlp.encode(blockFromTag) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawHeader server.rpc("debug_getRawHeader") do(blockTag: BlockTag) -> seq[byte]: ## Returns an RLP-encoded header. - let header = requireFound(chain.headerFromTag(blockTag), "Header not found") + let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found") rlp.encode(header) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawReceipts server.rpc("debug_getRawReceipts") do(blockTag: BlockTag) -> seq[seq[byte]]: ## Returns an array of EIP-2718 binary-encoded receipts. - let header = requireFound(chain.headerFromTag(blockTag), "Header not found") + let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found") var res: seq[seq[byte]] for receipt in chain.baseTxFrame.getReceipts(header.receiptsRoot): res.add rlp.encode(receipt) @@ -233,7 +233,7 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = server.rpc("debug_executionWitness") do(quantityTag: BlockTag) -> ExecutionWitness: ## Returns an execution witness for the given block number. - let header = requireFound(chain.headerFromTag(quantityTag), "Header not found") + let header = getOrRaise(chain.headerFromTag(quantityTag), "Header not found") chain.getExecutionWitness(header.computeBlockHash()).valueOr: raise newException(ValueError, error) diff --git a/execution_chain/rpc/handler_utils.nim b/execution_chain/rpc/handler_utils.nim index c9ca170aa5..e38a829117 100644 --- a/execution_chain/rpc/handler_utils.nim +++ b/execution_chain/rpc/handler_utils.nim @@ -16,7 +16,7 @@ import proc invalidParams*(msg: string) {.raises: [ref ApplicationError].} = raise (ref ApplicationError)(code: -32602, msg: msg) -template lookupOr*[T](res: Result[Opt[T], string], onMissing: untyped): T = +template getOrInvalidParam*[T](res: Result[Opt[T], string], onMissing: untyped): T = let resolved = res if resolved.isErr: let errMsg = resolved.error @@ -26,6 +26,6 @@ template lookupOr*[T](res: Result[Opt[T], string], onMissing: untyped): T = onMissing found.get() -template requireFound*[T](res: Result[Opt[T], string], msg: string): T = - lookupOr(res): +template getOrRaise*[T](res: Result[Opt[T], string], msg: string): T = + getOrInvalidParam(res): raise newException(ValueError, msg) diff --git a/execution_chain/rpc/server_api.nim b/execution_chain/rpc/server_api.nim index 0d7b43ee47..a237292a16 100644 --- a/execution_chain/rpc/server_api.nim +++ b/execution_chain/rpc/server_api.nim @@ -130,7 +130,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: ## Returns the balance of the account of given address. let - txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") address = data acc = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) acc.balance @@ -140,7 +140,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> FixedBytes[32]: ## Returns the value from a storage position at a given address. let - txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath slotKey = computeSlotKey(slot) @@ -152,7 +152,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> Quantity: ## Returns the number of transactions ak.s. nonce sent from an address. let - txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -173,7 +173,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -205,7 +205,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## blockTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. ## Returns BlockObject or nil when no block was found. - let blk = lookupOr(api.blockFromTag(blockTag, noHash = true)): + let blk = getOrInvalidParam(api.blockFromTag(blockTag, noHash = true)): return nil let blockHash = blk.header.computeBlockHash return populateBlockObject( @@ -294,8 +294,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag # would operate on this enum instead of raw strings. This change would need # to be done on every endpoint to be consistent. let - blockFrom = requireFound(api.headerFromTag(filterOptions.fromBlock), "Block not found") - blockTo = requireFound(api.headerFromTag(filterOptions.toBlock), "Block not found") + blockFrom = getOrRaise(api.headerFromTag(filterOptions.fromBlock), "Block not found") + blockTo = getOrRaise(api.headerFromTag(filterOptions.toBlock), "Block not found") # Note: if fromHeader.number > toHeader.number, no logs will be # returned. This is consistent with, what other ethereum clients return @@ -324,7 +324,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the return value of executed contract. let - header = requireFound(api.headerFromTag(blockTag), "Block not found") + header = getOrRaise(api.headerFromTag(blockTag), "Block not found") headerHash = header.computeBlockHash txFrame = api.chain.txFrame(headerHash) res = rpcCallEvm(args, header, headerHash, api.com, txFrame).valueOr: @@ -362,7 +362,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the amount of gas used. let - header = requireFound(api.headerFromTag(blockId("latest")), "Block not found") + header = getOrRaise(api.headerFromTag(blockId("latest")), "Block not found") headerHash = header.computeBlockHash txFrame = api.chain.txFrame(headerHash) # TODO: change 0 to configureable gas cap @@ -402,7 +402,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## ## blockTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the number of transactions in this block. - let blk = requireFound(api.blockFromTag(blockTag, noHash = true), "Block not found") + let blk = getOrRaise(api.blockFromTag(blockTag, noHash = true), "Block not found") Quantity(blk.transactions.len) @@ -421,7 +421,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## ## blockTag: integer of a block number, or the string "latest", see the default block parameter. ## Returns integer of the number of uncles in this block. - let blk = requireFound(api.blockFromTag(blockTag, noHash = true), "Block not found") + let blk = getOrRaise(api.blockFromTag(blockTag, noHash = true), "Block not found") Quantity(blk.uncles.len) @@ -458,7 +458,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = requireFound(api.frameFromTag(blockId("latest")), "Latest Block not found") + txFrame = getOrRaise(api.frameFromTag(blockId("latest")), "Latest Block not found") accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) eip155 = api.com.isEIP155(api.chain.latestNumber) @@ -479,7 +479,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = requireFound(api.frameFromTag(blockId("latest")), "Latest Block not found") + txFrame = getOrRaise(api.frameFromTag(blockId("latest")), "Latest Block not found") accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) @@ -573,7 +573,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantity: the transaction index position. ## NOTE : "pending" blockTag is not supported. let index = uint64(quantity) - let blk = lookupOr(api.blockFromTag(quantityTag, noHash = true)): + let blk = getOrInvalidParam(api.blockFromTag(quantityTag, noHash = true)): return nil if index >= uint64(blk.transactions.len): @@ -600,7 +600,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns: the proof response containing the account, account proof and storage proof let - txFrame = requireFound(api.frameFromTag(quantityTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(quantityTag), "Block not found") getProof(txFrame, data, slots) server.rpc("eth_getBlockReceipts") do( @@ -611,7 +611,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "requireCanonical is a pre-merge concept and is not supported") - let blk = lookupOr(api.blockFromTag(quantityTag)): + let blk = getOrInvalidParam(api.blockFromTag(quantityTag)): return Opt.none(seq[ReceiptObject]) let @@ -641,7 +641,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag args: TransactionArgs, quantityTag: BlockTag ) -> AccessListResult: ## Generates an access list for a transaction. - let header = requireFound(api.headerFromTag(quantityTag), "Block not found") + let header = getOrRaise(api.headerFromTag(quantityTag), "Block not found") try: return createAccessList(header, api.com, api.chain, args) except CatchableError as exc: @@ -649,7 +649,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_blobBaseFee") do() -> Quantity: ## Returns the base fee per blob gas in wei. - let header = requireFound(api.headerFromTag(blockId("latest")), "Block not found") + let header = getOrRaise(api.headerFromTag(blockId("latest")), "Block not found") if header.excessBlobGas.isNone: raise newException(ValueError, "excessBlobGas missing from latest header") let blobBaseFee = @@ -691,7 +691,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantity: the uncle's index position. ## Returns BlockObject or nil when no block was found. let index = uint64(quantity) - let blk = lookupOr(api.blockFromTag(quantityTag)): + let blk = getOrInvalidParam(api.blockFromTag(quantityTag)): return nil if index < 0 or index >= blk.uncles.len.uint64: @@ -725,7 +725,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## Returns the block access list by block number, tag or block hash. ## - let header = lookupOr(api.headerFromTag(quantityTag)): + let header = getOrInvalidParam(api.headerFromTag(quantityTag)): return Opt.none(BlockAccessList) if not api.com.isAmsterdamOrLater(header.timestamp): @@ -751,7 +751,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_getStorageValues") do(request: StorageValuesRequest, blockTag: BlockTag) -> StorageValuesResponse: let - txFrame = requireFound(api.frameFromTag(blockTag), "Block not found") + txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") var res: StorageObject for req in request.list: From be3563c3b0b6d69f8dc05af92f934e4ede236597 Mon Sep 17 00:00:00 2001 From: Samyxandz Date: Fri, 24 Apr 2026 00:19:04 +0530 Subject: [PATCH 3/4] remove proc frameFromTag redundant opt --- execution_chain/rpc/server_api.nim | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/execution_chain/rpc/server_api.nim b/execution_chain/rpc/server_api.nim index a237292a16..030dbf9f95 100644 --- a/execution_chain/rpc/server_api.nim +++ b/execution_chain/rpc/server_api.nim @@ -106,14 +106,14 @@ proc headerFromTag(api: ServerAPIRef, blockTag: Opt[BlockTag]): Result[Opt[Heade let blockId = blockTag.get(defaultTag) api.headerFromTag(blockId) -proc frameFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Opt[CoreDbTxRef], string] = +proc frameFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[CoreDbTxRef, string] = # TODO avoid loading full header if hash is given let headerOpt = api.headerFromTag(blockTag).valueOr: return err(error) if headerOpt.isNone: - return ok(Opt.none(CoreDbTxRef)) + return err("Block not found") let header = headerOpt.get() @@ -121,7 +121,7 @@ proc frameFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Opt[CoreDbTxRef return err("Historical data not available") # TODO maybe use a new frame derived from txFrame, to protect against abuse? - ok Opt.some(api.chain.txFrame(header)) + ok api.chain.txFrame(header) proc blockFromTag(api: ServerAPIRef, blockTag: BlockTag, noHash: bool = false): Result[Opt[Block], string] = api.chain.blockFromTag(blockTag, noHash) @@ -130,7 +130,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: ## Returns the balance of the account of given address. let - txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") + txFrame = api.frameFromTag(blockTag).valueOr: + raise newException(ValueError, error) address = data acc = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) acc.balance @@ -140,7 +141,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> FixedBytes[32]: ## Returns the value from a storage position at a given address. let - txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") + txFrame = api.frameFromTag(blockTag).valueOr: + raise newException(ValueError, error) address = data accPath = address.computeAccPath slotKey = computeSlotKey(slot) @@ -152,7 +154,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ) -> Quantity: ## Returns the number of transactions ak.s. nonce sent from an address. let - txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") + txFrame = api.frameFromTag(blockTag).valueOr: + raise newException(ValueError, error) address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -173,7 +176,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") + txFrame = api.frameFromTag(blockTag).valueOr: + raise newException(ValueError, error) address = data accPath = address.computeAccPath acc = txFrame.fetchAccount(accPath).valueOr(emptyDbAccount) @@ -458,7 +462,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = getOrRaise(api.frameFromTag(blockId("latest")), "Latest Block not found") + txFrame = api.frameFromTag(blockId("latest")).valueOr: + raise newException(ValueError, error) accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) eip155 = api.com.isEIP155(api.chain.latestNumber) @@ -479,7 +484,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag raise newException(ValueError, "Account locked, please unlock it first") let - txFrame = getOrRaise(api.frameFromTag(blockId("latest")), "Latest Block not found") + txFrame = api.frameFromTag(blockId("latest")).valueOr: + raise newException(ValueError, error) accRec = txFrame.fetchAccount(address.computeAccPath).valueOr(emptyDbAccount) tx = unsignedTx(data, api.chain, accRec.nonce + 1, api.com.chainId) @@ -600,7 +606,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns: the proof response containing the account, account proof and storage proof let - txFrame = getOrRaise(api.frameFromTag(quantityTag), "Block not found") + txFrame = api.frameFromTag(quantityTag).valueOr: + raise newException(ValueError, error) getProof(txFrame, data, slots) server.rpc("eth_getBlockReceipts") do( @@ -751,7 +758,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManag server.rpc("eth_getStorageValues") do(request: StorageValuesRequest, blockTag: BlockTag) -> StorageValuesResponse: let - txFrame = getOrRaise(api.frameFromTag(blockTag), "Block not found") + txFrame = api.frameFromTag(blockTag).valueOr: + raise newException(ValueError, error) var res: StorageObject for req in request.list: From b1a861a8c4177301895cc1a924c953d94a555c07 Mon Sep 17 00:00:00 2001 From: Samyxandz Date: Tue, 28 Apr 2026 22:59:40 +0530 Subject: [PATCH 4/4] fix debug_* block tag decoding error handling to align with spec --- execution_chain/rpc/debug.nim | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/execution_chain/rpc/debug.nim b/execution_chain/rpc/debug.nim index 4c625c40c3..a972137b78 100644 --- a/execution_chain/rpc/debug.nim +++ b/execution_chain/rpc/debug.nim @@ -184,20 +184,35 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = badBlocks # https://ethereum.github.io/execution-apis/api/methods/debug_getRawBlock - server.rpc("debug_getRawBlock") do(blockTag: BlockTag) -> seq[byte]: + server.rpc("debug_getRawBlock") do(blockTagJson: JsonNode) -> seq[byte]: ## Returns an RLP-encoded block. + let blockTag = + try: + JrpcConv.decode($blockTagJson, BlockTag) + except SerializationError as exc: + invalidParams(exc.msg) let blockFromTag = getOrRaise(chain.blockFromTag(blockTag), "Block not found") rlp.encode(blockFromTag) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawHeader - server.rpc("debug_getRawHeader") do(blockTag: BlockTag) -> seq[byte]: + server.rpc("debug_getRawHeader") do(blockTagJson: JsonNode) -> seq[byte]: ## Returns an RLP-encoded header. + let blockTag = + try: + JrpcConv.decode($blockTagJson, BlockTag) + except SerializationError as exc: + invalidParams(exc.msg) let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found") rlp.encode(header) # https://ethereum.github.io/execution-apis/api/methods/debug_getRawReceipts - server.rpc("debug_getRawReceipts") do(blockTag: BlockTag) -> seq[seq[byte]]: + server.rpc("debug_getRawReceipts") do(blockTagJson: JsonNode) -> seq[seq[byte]]: ## Returns an array of EIP-2718 binary-encoded receipts. + let blockTag = + try: + JrpcConv.decode($blockTagJson, BlockTag) + except SerializationError as exc: + invalidParams(exc.msg) let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found") frame = chain.txFrame(header)