Skip to content
33 changes: 15 additions & 18 deletions execution_chain/rpc/debug.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import
std/[json, times],
json_rpc/rpcserver,
web3/[eth_api_types, conversions],
./handler_utils,
./rpc_utils,
./rpc_types,
#../tracer,
Expand Down Expand Up @@ -184,25 +185,22 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) =
badBlocks

# https://ethereum.github.io/execution-apis/api/methods/debug_getRawBlock
proc debug_getRawBlock(blockTag: BlockTag): seq[byte] {.raises: [ApplicationError].} =
proc debug_getRawBlock(blockTag: BlockTag): seq[byte] {.raises: [ApplicationError, ValueError].} =
## Returns an RLP-encoded block.
let blockFromTag = chain.blockFromTag(blockTag).valueOr:
raise invalidParams(error)
let blockFromTag = getOrRaise(chain.blockFromTag(blockTag), "Block not found")

rlp.encode(blockFromTag)

# https://ethereum.github.io/execution-apis/api/methods/debug_getRawHeader
proc debug_getRawHeader(blockTag: BlockTag): seq[byte] {.raises: [ApplicationError, RlpError].} =
proc debug_getRawHeader(blockTag: BlockTag): seq[byte] {.raises: [ApplicationError, ValueError, RlpError].} =
## Returns an RLP-encoded header.
let header = chain.headerFromTag(blockTag).valueOr:
raise invalidParams(error)
let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found")
rlp.encode(header)

# https://ethereum.github.io/execution-apis/api/methods/debug_getRawReceipts
proc debug_getRawReceipts(blockTag: BlockTag): seq[seq[byte]] {.raises: [ApplicationError, RlpError].} =
proc debug_getRawReceipts(blockTag: BlockTag): seq[seq[byte]] {.raises: [ApplicationError, ValueError, RlpError].} =
## Returns an array of EIP-2718 binary-encoded receipts.
let header = chain.headerFromTag(blockTag).valueOr:
raise invalidParams(error)
let header = getOrRaise(chain.headerFromTag(blockTag), "Header not found")
let txFrame = chain.txFrame(header)
var res: seq[seq[byte]]
for receipt in txFrame.getReceipts(header.receiptsRoot):
Expand All @@ -218,13 +216,13 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) =
# TODO: remove manual validation when upstream parsing decoding reports strict
# hex input failures .
if not txHashHex.startsWith("0x"):
raise invalidParams("invalid argument 0: hex string without 0x prefix")
invalidParams("invalid argument 0: hex string without 0x prefix")

let txHash =
try:
Hash32.fromHex(txHashHex)
except ValueError as exc:
raise invalidParams("invalid argument 0: " & exc.msg)
var txHash: Hash32
try:
txHash = Hash32.fromHex(txHashHex)
except ValueError as exc:
invalidParams("invalid argument 0: " & exc.msg)

let res = txPool.getItem(txHash)
if res.isOk:
Expand All @@ -243,10 +241,9 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) =

# Execution Witness endpoints - not specified in the Execution API

proc debug_executionWitness(quantityTag: BlockTag): ExecutionWitness {.raises: [ValueError].} =
proc debug_executionWitness(quantityTag: BlockTag): ExecutionWitness {.raises: [ApplicationError, ValueError].} =
## Returns an execution witness for the given block number.
let header = chain.headerFromTag(quantityTag).valueOr:
raise newException(ValueError, "Header not found")
let header = getOrRaise(chain.headerFromTag(quantityTag), "Header not found")

chain.getExecutionWitness(header.computeBlockHash()).valueOr:
raise newException(ValueError, error)
Expand Down
31 changes: 31 additions & 0 deletions execution_chain/rpc/handler_utils.nim
Original file line number Diff line number Diff line change
@@ -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 getOrInvalidParam*[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 getOrRaise*[T](res: Result[Opt[T], string], msg: string): T =
getOrInvalidParam(res):
raise newException(ValueError, msg)
7 changes: 5 additions & 2 deletions execution_chain/rpc/oracle.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
71 changes: 48 additions & 23 deletions execution_chain/rpc/rpc_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@

import
std/[sequtils, algorithm, strutils],
json_rpc/errors,
./rpc_types,
./params,
../db/ledger,
../db/[ledger, core_db, storage_types],
../constants, stint,
../utils/utils,
../transaction,
Expand Down Expand Up @@ -44,12 +43,6 @@ func median(prices: var openArray[GasInt]): GasInt =

prices[middle]

proc invalidParams*(msg: string): ref ApplicationError =
(ref ApplicationError)(
code: -32602,
msg: msg,
)

proc calculateMedianGasPrice*(chain: ForkedChainRef): GasInt =
const minGasPrice = 30_000_000_000.GasInt
var prices = chain.latestBlock.transactions.mapIt(it.gasPrice)
Expand Down Expand Up @@ -422,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))
Loading