From 0f79f8c55ddd7becd2d93133bda9a9ee333e47fa Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi Date: Wed, 15 Apr 2026 23:56:32 +0530 Subject: [PATCH 01/12] fix: reject non-hex block numbers in debug_getRawBlock, debug_getRawHeader, debug_getRawReceipts The Hive rpc-compat suite sends decimal strings like "2" (no 0x prefix) as block parameters and expects a -32602 INVALID_PARAMS error. Besu was silently accepting these via Long.decode() in BlockParameter, which accepts both decimal and hex strings. Add pre-validation in the blockParameter()/blockParameterOrBlockHash() overrides of each affected method: if the raw parameter is not a named block tag (earliest/latest/pending/finalized/safe) and does not start with "0x", throw InvalidJsonRpcParameters(-32602) immediately. Fixes Hive rpc-compat failures: debug_getRawBlock/get-invalid-number debug_getRawHeader/get-invalid-number debug_getRawReceipts/get-invalid-number Signed-off-by: Shridhar Panigrahi --- .../jsonrpc/internal/methods/DebugGetRawBlock.java | 13 +++++++++++++ .../jsonrpc/internal/methods/DebugGetRawHeader.java | 13 +++++++++++++ .../internal/methods/DebugGetRawReceipts.java | 12 ++++++++++++ 3 files changed, 38 insertions(+) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java index c9097583e36..c369b9aaaa7 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java @@ -24,10 +24,16 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.rlp.RLP; +import java.util.Locale; +import java.util.Set; + import com.google.common.base.Suppliers; public class DebugGetRawBlock extends AbstractBlockParameterMethod { + private static final Set NAMED_BLOCK_TAGS = + Set.of("earliest", "latest", "pending", "finalized", "safe"); + public DebugGetRawBlock(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); } @@ -40,6 +46,13 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { try { + final String rawParam = request.getRequiredParameter(0, String.class); + final String lower = rawParam.toLowerCase(Locale.ROOT); + if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0): hex string without 0x prefix", + RpcErrorType.INVALID_BLOCK_PARAMS); + } return request.getRequiredParameter(0, BlockParameter.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java index 8e4f9ce6ead..ef888e103c3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java @@ -24,10 +24,16 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.rlp.RLP; +import java.util.Locale; +import java.util.Set; + import com.google.common.base.Suppliers; public class DebugGetRawHeader extends AbstractBlockParameterMethod { + private static final Set NAMED_BLOCK_TAGS = + Set.of("earliest", "latest", "pending", "finalized", "safe"); + public DebugGetRawHeader(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); } @@ -40,6 +46,13 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { try { + final String rawParam = request.getRequiredParameter(0, String.class); + final String lower = rawParam.toLowerCase(Locale.ROOT); + if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0): hex string without 0x prefix", + RpcErrorType.INVALID_BLOCK_PARAMS); + } return request.getRequiredParameter(0, BlockParameter.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java index ca7f527aa35..f132743766a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java @@ -28,11 +28,16 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import java.util.List; +import java.util.Locale; +import java.util.Set; import com.google.common.base.Suppliers; public class DebugGetRawReceipts extends AbstractBlockParameterOrBlockHashMethod { + private static final Set NAMED_BLOCK_TAGS = + Set.of("earliest", "latest", "pending", "finalized", "safe"); + public DebugGetRawReceipts(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); } @@ -46,6 +51,13 @@ public String getName() { protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { try { + final String rawParam = request.getRequiredParameter(0, String.class); + final String lower = rawParam.toLowerCase(Locale.ROOT); + if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 0): hex string without 0x prefix", + RpcErrorType.INVALID_BLOCK_PARAMS); + } return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( From 27b0445a2e8bdc95492ac6b780499fbe050e8988 Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi Date: Thu, 23 Apr 2026 12:45:28 +0530 Subject: [PATCH 02/12] refactor: use BlockParameterOrBlockHash in debug_getRawBlock and debug_getRawHeader Switch DebugGetRawBlock and DebugGetRawHeader from AbstractBlockParameterMethod to AbstractBlockParameterOrBlockHashMethod so they accept block hashes as well as block numbers, matching the pattern already used by DebugGetRawReceipts. Move the hex-prefix validation into BlockParameterOrBlockHash itself so it applies to all methods using that parameter type rather than being duplicated per method. Update DebugSetHeadTest to pass hex block numbers accordingly. Signed-off-by: Shridhar Panigrahi --- .../internal/methods/DebugGetRawBlock.java | 31 +++++------------ .../internal/methods/DebugGetRawHeader.java | 33 ++++++------------- .../parameters/BlockParameterOrBlockHash.java | 3 ++ .../internal/methods/DebugSetHeadTest.java | 2 +- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java index c369b9aaaa7..a3d61f4eebd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java @@ -14,25 +14,20 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.rlp.RLP; -import java.util.Locale; -import java.util.Set; - import com.google.common.base.Suppliers; -public class DebugGetRawBlock extends AbstractBlockParameterMethod { - - private static final Set NAMED_BLOCK_TAGS = - Set.of("earliest", "latest", "pending", "finalized", "safe"); +public class DebugGetRawBlock extends AbstractBlockParameterOrBlockHashMethod { public DebugGetRawBlock(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); @@ -44,29 +39,21 @@ public String getName() { } @Override - protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + protected BlockParameterOrBlockHash blockParameterOrBlockHash( + final JsonRpcRequestContext request) { try { - final String rawParam = request.getRequiredParameter(0, String.class); - final String lower = rawParam.toLowerCase(Locale.ROOT); - if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { - throw new InvalidJsonRpcParameters( - "Invalid block parameter (index 0): hex string without 0x prefix", - RpcErrorType.INVALID_BLOCK_PARAMS); - } - return request.getRequiredParameter(0, BlockParameter.class); + return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( - "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } } @Override - protected Object resultByBlockNumber( - final JsonRpcRequestContext request, final long blockNumber) { - + protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { return getBlockchainQueries() .getBlockchain() - .getBlockByNumber(blockNumber) + .getBlockByHash(blockHash) .map(block -> RLP.encode(block::writeTo).toString()) .orElseGet( () -> diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java index ef888e103c3..75c4fcc552e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java @@ -14,25 +14,20 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.rlp.RLP; -import java.util.Locale; -import java.util.Set; - import com.google.common.base.Suppliers; -public class DebugGetRawHeader extends AbstractBlockParameterMethod { - - private static final Set NAMED_BLOCK_TAGS = - Set.of("earliest", "latest", "pending", "finalized", "safe"); +public class DebugGetRawHeader extends AbstractBlockParameterOrBlockHashMethod { public DebugGetRawHeader(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); @@ -44,29 +39,21 @@ public String getName() { } @Override - protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + protected BlockParameterOrBlockHash blockParameterOrBlockHash( + final JsonRpcRequestContext request) { try { - final String rawParam = request.getRequiredParameter(0, String.class); - final String lower = rawParam.toLowerCase(Locale.ROOT); - if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { - throw new InvalidJsonRpcParameters( - "Invalid block parameter (index 0): hex string without 0x prefix", - RpcErrorType.INVALID_BLOCK_PARAMS); - } - return request.getRequiredParameter(0, BlockParameter.class); + return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( - "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } } @Override - protected Object resultByBlockNumber( - final JsonRpcRequestContext request, final long blockNumber) { - + protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { return getBlockchainQueries() - .blockByNumber(blockNumber) - .map(block -> RLP.encode(block.getHeader()::writeTo).toString()) + .getBlockHeaderByHash(blockHash) + .map(header -> RLP.encode(header::writeTo).toString()) .orElseGet( () -> new JsonRpcErrorResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index 6cc537e6922..5e67b53d9b5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -79,6 +79,9 @@ public BlockParameterOrBlockHash(final Object value) throws JsonProcessingExcept requireCanonical = false; } else if (normalizedValue.length() > 16) { throw new IllegalArgumentException("hex number > 64 bits"); + } else if (!normalizedValue.startsWith("0x")) { + throw new IllegalArgumentException( + "Invalid block number: must be a hex string with 0x prefix"); } else { type = BlockParameterType.NUMERIC; number = OptionalLong.of(Long.decode(value.toString())); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java index 7a0ebd2ac01..e4c70adc5a0 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java @@ -152,7 +152,7 @@ public void assertNullWhenBlockNotFound() { // move the head to number just after chain head var resp = - debugSetHead.response(debugSetHead("" + chainTip.getNumber() + 1, Optional.of(TRUE))); + debugSetHead.response(debugSetHead("0x" + Long.toHexString(chainTip.getNumber() + 1), Optional.of(TRUE))); // success with null result if block not found assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); assertThat(((JsonRpcSuccessResponse) resp).getResult()).isNull(); From 64f6436e87046c757a56dcd5c863db95c14c368f Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi Date: Thu, 23 Apr 2026 12:52:15 +0530 Subject: [PATCH 03/12] refactor: remove redundant hex validation from DebugGetRawReceipts The per-method check in blockParameterOrBlockHash was already superseded by the validation added to BlockParameterOrBlockHash itself. Signed-off-by: Shridhar Panigrahi --- .../internal/methods/DebugGetRawReceipts.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java index f132743766a..ca7f527aa35 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java @@ -28,16 +28,11 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import java.util.List; -import java.util.Locale; -import java.util.Set; import com.google.common.base.Suppliers; public class DebugGetRawReceipts extends AbstractBlockParameterOrBlockHashMethod { - private static final Set NAMED_BLOCK_TAGS = - Set.of("earliest", "latest", "pending", "finalized", "safe"); - public DebugGetRawReceipts(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); } @@ -51,13 +46,6 @@ public String getName() { protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { try { - final String rawParam = request.getRequiredParameter(0, String.class); - final String lower = rawParam.toLowerCase(Locale.ROOT); - if (!NAMED_BLOCK_TAGS.contains(lower) && !lower.startsWith("0x")) { - throw new InvalidJsonRpcParameters( - "Invalid block or block hash parameter (index 0): hex string without 0x prefix", - RpcErrorType.INVALID_BLOCK_PARAMS); - } return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( From c7ea8963215920091209837b6c0f841de10e1fe8 Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi Date: Fri, 24 Apr 2026 12:20:34 +0530 Subject: [PATCH 04/12] chore: fix spotless formatting and add changelog entry Signed-off-by: Shridhar Panigrahi --- CHANGELOG.md | 1 + .../api/jsonrpc/internal/methods/DebugSetHeadTest.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940cd3695b6..f164930ac11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Bug fixes - Fix `engine_forkchoiceUpdatedV1` now returns `-38003 INVALID_PAYLOAD_ATTRIBUTES` for invalid payload attribute timestamps (zero or not greater than head). [#10353](https://github.com/besu-eth/besu/pull/10353) - Fix `debug_trace*` `storage` field to emit only for SLOAD/SSTORE opcodes showing the single slot touched, matching the execution-apis spec and geth behaviour [#10176](https://github.com/besu-eth/besu/pull/10176) +- `debug_getRawBlock`, `debug_getRawHeader`, and `debug_getRawReceipts` now reject non-hex block number parameters (e.g. decimal integers) with an `INVALID_BLOCK_PARAMS` error, consistent with the JSON-RPC spec. Validation is centralised in `BlockParameterOrBlockHash`. [#10240](https://github.com/besu-eth/besu/pull/10240) ### Additions and Improvements - Add `eth_getStorageValues` JSON-RPC method for batched reads of multiple storage slots across multiple accounts in a single call [#10259](https://github.com/besu-eth/besu/pull/10259) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java index e4c70adc5a0..5238955bc69 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java @@ -152,7 +152,8 @@ public void assertNullWhenBlockNotFound() { // move the head to number just after chain head var resp = - debugSetHead.response(debugSetHead("0x" + Long.toHexString(chainTip.getNumber() + 1), Optional.of(TRUE))); + debugSetHead.response( + debugSetHead("0x" + Long.toHexString(chainTip.getNumber() + 1), Optional.of(TRUE))); // success with null result if block not found assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); assertThat(((JsonRpcSuccessResponse) resp).getResult()).isNull(); From 7a322ba650308481219663f4b8820bda09d2cbe4 Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Thu, 14 May 2026 13:10:40 +0530 Subject: [PATCH 05/12] fix: add eth_getProof + debug_getRawTransaction hex validation per maintainer review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix EthGetProofTest: replace decimal block numbers (String.valueOf(500/501)) with hex equivalents ("0x1f4" / "0x1f5") — needed because BlockParameterOrBlockHash now rejects non-0x-prefixed numbers - Add 0x prefix check to DebugGetRawTransaction for the transaction hash parameter, fixing the hive rpc-compat debug_getRawTransaction/get-invalid-hash test failure - CHANGELOG: add eth_getProof and debug_getRawTransaction to the affected-methods list; move the block-number-hex note from Upcoming Breaking Changes to Breaking Changes Signed-off-by: Sridhar Panigrahi --- CHANGELOG.md | 7 +++---- .../jsonrpc/internal/methods/DebugGetRawTransaction.java | 6 ++++++ .../api/jsonrpc/internal/methods/EthGetProofTest.java | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70fb7cf4b48..037776c15fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,10 @@ ## Unreleased ### Breaking Changes +- RPC changes to enhance compatibility with other ELs + - Block number parameter in RPCs will only support hex values. Non-hex (decimal) block number parameters are now rejected. This affects `debug_getRawBlock`, `debug_getRawHeader`, `debug_getRawReceipts`, and `eth_getProof`. Transaction hash parameters in `debug_getRawTransaction` must also use a `0x` prefix. ### Upcoming Breaking Changes -- RPC changes to enhance compatibility with other ELs - - Block number parameter in RPCs will only support hex values. Support for non-hex (decimal) block number parameters is deprecated. - - This affects several RPCs, including `admin_logsRemoveCache`, `debug_getRawHeader`, `eth_call`, `eth_simulateV1`, `trace_call` and more. - Sunsetting features - for more context on the reasoning behind the deprecation of these features, including alternative options, read [this blog post](https://www.lfdecentralizedtrust.org/blog/sunsetting-tessera-and-simplifying-hyperledger-besu) - Proof of Work consensus (PoW) - `--min-block-occupancy-ratio` is deprecated and will be removed in a future release @@ -21,7 +20,7 @@ - Fix `engine_forkchoiceUpdatedV1` now returns `-38003 INVALID_PAYLOAD_ATTRIBUTES` for invalid payload attribute timestamps (zero or not greater than head). [#10353](https://github.com/besu-eth/besu/pull/10353) - Fix `engine_newPayloadV4`/`V5` now returns `-32602 INVALID_PARAMS` instead of `INVALID` payload status when execution requests contain an unknown request type. [#10484](https://github.com/besu-eth/besu/pull/10484) - Fix `debug_trace*` `storage` field to emit only for SLOAD/SSTORE opcodes showing the single slot touched, matching the execution-apis spec and geth behaviour [#10176](https://github.com/besu-eth/besu/pull/10176) -- `debug_getRawBlock`, `debug_getRawHeader`, and `debug_getRawReceipts` now reject non-hex block number parameters (e.g. decimal integers) with an `INVALID_BLOCK_PARAMS` error, consistent with the JSON-RPC spec. Validation is centralised in `BlockParameterOrBlockHash`. [#10240](https://github.com/besu-eth/besu/pull/10240) +- `debug_getRawBlock`, `debug_getRawHeader`, `debug_getRawReceipts`, and `eth_getProof` now reject non-hex block number parameters (e.g. decimal integers) with an `INVALID_BLOCK_PARAMS` error, consistent with the JSON-RPC spec. Validation is centralised in `BlockParameterOrBlockHash`. `debug_getRawTransaction` now rejects transaction hash parameters without a `0x` prefix. [#10240](https://github.com/besu-eth/besu/pull/10240) ### Additions and Improvements - Add `eth_getStorageValues` JSON-RPC method for batched reads of multiple storage slots across multiple accounts in a single call [#10259](https://github.com/besu-eth/besu/pull/10259) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java index 2b1e756d22a..df81974ea10 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java @@ -43,6 +43,12 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Hash txHash; try { + final String rawHash = requestContext.getRequiredParameter(0, String.class); + if (!rawHash.startsWith("0x") && !rawHash.startsWith("0X")) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS); + } txHash = requestContext.getRequiredParameter(0, Hash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java index d3adb5f1c78..7b19ce2a117 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java @@ -124,7 +124,7 @@ void shouldReturnNullWhenWorldStateUnavailable() { requestWithParams( Address.fromHexString("0x0000000000000000000000000000000000000000"), new String[] {storageKey.toString()}, - String.valueOf(501)); + "0x1f5"); final JsonRpcResponse response = method.response(request); @@ -139,7 +139,7 @@ void getProofWithAccount() { final JsonRpcRequestContext request = requestWithParams( - address.toString(), new String[] {storageKey.toString()}, String.valueOf(blockNumber)); + address.toString(), new String[] {storageKey.toString()}, "0x1f4"); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); final GetProofResult result = (GetProofResult) response.getResult(); @@ -225,7 +225,7 @@ void getProofWithoutAccount() { final JsonRpcRequestContext request = requestWithParams( - address.toString(), new String[] {storageKey.toString()}, String.valueOf(blockNumber)); + address.toString(), new String[] {storageKey.toString()}, "0x1f4"); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); final GetProofResult result = (GetProofResult) response.getResult(); From 1fe7f6b0bf77ca546c0a1b27b317e6b992035847 Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Fri, 15 May 2026 21:08:17 +0530 Subject: [PATCH 06/12] fix: revert DebugGetRawTransaction change and consolidate CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per maintainer feedback, keep this PR focused on block param hex validation only. Reverted the 0x prefix check added to DebugGetRawTransaction and removed the duplicate bug-fixes entry from CHANGELOG — the breaking change entry already covers it. Signed-off-by: Sridhar Panigrahi --- CHANGELOG.md | 3 +-- .../jsonrpc/internal/methods/DebugGetRawTransaction.java | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 037776c15fd..f29025b4fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Breaking Changes - RPC changes to enhance compatibility with other ELs - - Block number parameter in RPCs will only support hex values. Non-hex (decimal) block number parameters are now rejected. This affects `debug_getRawBlock`, `debug_getRawHeader`, `debug_getRawReceipts`, and `eth_getProof`. Transaction hash parameters in `debug_getRawTransaction` must also use a `0x` prefix. + - Block number parameter in RPCs will only support hex values. Non-hex (decimal) block number parameters are now rejected. This affects `debug_getRawBlock`, `debug_getRawHeader`, `debug_getRawReceipts`, and `eth_getProof`. [#10240](https://github.com/besu-eth/besu/pull/10240) ### Upcoming Breaking Changes - Sunsetting features - for more context on the reasoning behind the deprecation of these features, including alternative options, read [this blog post](https://www.lfdecentralizedtrust.org/blog/sunsetting-tessera-and-simplifying-hyperledger-besu) @@ -20,7 +20,6 @@ - Fix `engine_forkchoiceUpdatedV1` now returns `-38003 INVALID_PAYLOAD_ATTRIBUTES` for invalid payload attribute timestamps (zero or not greater than head). [#10353](https://github.com/besu-eth/besu/pull/10353) - Fix `engine_newPayloadV4`/`V5` now returns `-32602 INVALID_PARAMS` instead of `INVALID` payload status when execution requests contain an unknown request type. [#10484](https://github.com/besu-eth/besu/pull/10484) - Fix `debug_trace*` `storage` field to emit only for SLOAD/SSTORE opcodes showing the single slot touched, matching the execution-apis spec and geth behaviour [#10176](https://github.com/besu-eth/besu/pull/10176) -- `debug_getRawBlock`, `debug_getRawHeader`, `debug_getRawReceipts`, and `eth_getProof` now reject non-hex block number parameters (e.g. decimal integers) with an `INVALID_BLOCK_PARAMS` error, consistent with the JSON-RPC spec. Validation is centralised in `BlockParameterOrBlockHash`. `debug_getRawTransaction` now rejects transaction hash parameters without a `0x` prefix. [#10240](https://github.com/besu-eth/besu/pull/10240) ### Additions and Improvements - Add `eth_getStorageValues` JSON-RPC method for batched reads of multiple storage slots across multiple accounts in a single call [#10259](https://github.com/besu-eth/besu/pull/10259) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java index df81974ea10..2b1e756d22a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java @@ -43,12 +43,6 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Hash txHash; try { - final String rawHash = requestContext.getRequiredParameter(0, String.class); - if (!rawHash.startsWith("0x") && !rawHash.startsWith("0X")) { - throw new InvalidJsonRpcParameters( - "Invalid transaction hash parameter (index 0)", - RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS); - } txHash = requestContext.getRequiredParameter(0, Hash.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( From 684af43292a340d2b68f68d9853dda3dbaf94ca2 Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Mon, 18 May 2026 11:36:59 +0530 Subject: [PATCH 07/12] test: derive hex block numbers from blockNumber field in EthGetProofTest Replace hardcoded "0x1f4" / "0x1f5" with "0x" + Long.toHexString(blockNumber) and "0x" + Long.toHexString(blockNumber + 1) so the strings stay in sync with the blockNumber field if it ever changes. Signed-off-by: Sridhar Panigrahi --- .../api/jsonrpc/internal/methods/EthGetProofTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java index 7b19ce2a117..7c5d2014125 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java @@ -124,7 +124,7 @@ void shouldReturnNullWhenWorldStateUnavailable() { requestWithParams( Address.fromHexString("0x0000000000000000000000000000000000000000"), new String[] {storageKey.toString()}, - "0x1f5"); + "0x" + Long.toHexString(blockNumber + 1)); final JsonRpcResponse response = method.response(request); @@ -139,7 +139,9 @@ void getProofWithAccount() { final JsonRpcRequestContext request = requestWithParams( - address.toString(), new String[] {storageKey.toString()}, "0x1f4"); + address.toString(), + new String[] {storageKey.toString()}, + "0x" + Long.toHexString(blockNumber)); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); final GetProofResult result = (GetProofResult) response.getResult(); @@ -225,7 +227,9 @@ void getProofWithoutAccount() { final JsonRpcRequestContext request = requestWithParams( - address.toString(), new String[] {storageKey.toString()}, "0x1f4"); + address.toString(), + new String[] {storageKey.toString()}, + "0x" + Long.toHexString(blockNumber)); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); final GetProofResult result = (GetProofResult) response.getResult(); From e8d3421fd765e9788a359ccd81af6b26d374aafa Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Tue, 19 May 2026 20:29:32 +0530 Subject: [PATCH 08/12] fix: allow negative hex block params to flow to downstream check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hex-prefix check in BlockParameterOrBlockHash was rejecting inputs like "-0x10" upfront with a generic IllegalArgumentException, which methods mapped to INVALID_BLOCK_PARAMS ("Invalid block param (block not found)"). The negative-number check already lives downstream in AbstractBlockParameterOrBlockHashMethod and returns the more accurate INVALID_BLOCK_NUMBER_PARAMS ("Invalid block number params") — accept an optional leading minus so that path is reached. Also update JsonRpcHttpServiceTest.ethGetStorageAtBlockNumber to pass "0x0" instead of decimal "0" — the new contract is hex-only and this test was the only remaining decimal usage in the api module. Signed-off-by: Sridhar Panigrahi --- .../internal/parameters/BlockParameterOrBlockHash.java | 2 +- .../besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index 5e67b53d9b5..d9059e32bc4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -79,7 +79,7 @@ public BlockParameterOrBlockHash(final Object value) throws JsonProcessingExcept requireCanonical = false; } else if (normalizedValue.length() > 16) { throw new IllegalArgumentException("hex number > 64 bits"); - } else if (!normalizedValue.startsWith("0x")) { + } else if (!normalizedValue.startsWith("0x") && !normalizedValue.startsWith("-0x")) { throw new IllegalArgumentException( "Invalid block number: must be a hex string with 0x prefix"); } else { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java index 518fbb558a5..81acb4b6189 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java @@ -1994,9 +1994,7 @@ public void ethGetStorageAtBlockNumber() throws Exception { + address + "\",\"" + UInt256.ZERO - + "\",\"" - + 0L - + "\"]}", + + "\",\"0x0\"]}", JSON); try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { From 5fdec8a17b16c886eb575ff695e322881b4be168 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Wed, 20 May 2026 08:00:58 +1000 Subject: [PATCH 09/12] remove -0x carve out and update relevant tests Signed-off-by: Sally MacFarlane --- .../jsonrpc/internal/parameters/BlockParameterOrBlockHash.java | 2 +- .../api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json | 2 +- .../api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json | 2 +- .../api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json | 2 +- .../api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index d9059e32bc4..5e67b53d9b5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -79,7 +79,7 @@ public BlockParameterOrBlockHash(final Object value) throws JsonProcessingExcept requireCanonical = false; } else if (normalizedValue.length() > 16) { throw new IllegalArgumentException("hex number > 64 bits"); - } else if (!normalizedValue.startsWith("0x") && !normalizedValue.startsWith("-0x")) { + } else if (!normalizedValue.startsWith("0x")) { throw new IllegalArgumentException( "Invalid block number: must be a hex string with 0x prefix"); } else { diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json index 2a59465e88f..3447b185ecb 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json @@ -13,7 +13,7 @@ "id": 28, "error": { "code": -32602, - "message": "Invalid block number params" + "message": "Invalid block param (block not found)" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json index e2011bb4d05..28d1b81b2f5 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json @@ -13,7 +13,7 @@ "id": 13, "error": { "code": -32602, - "message": "Invalid block number params" + "message": "Invalid block param (block not found)" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json index f0cf31f79dd..1f2c912df91 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json @@ -14,7 +14,7 @@ "id": 28, "error": { "code": -32602, - "message": "Invalid block number params" + "message": "Invalid block param (block not found)" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json index 1f46003c8f6..a6c7cec29fd 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json @@ -14,7 +14,7 @@ "id": 337, "error": { "code": -32602, - "message": "Invalid block number params" + "message": "Invalid block param (block not found)" } }, "statusCode": 200 From cc5553646aef2885cb50a34e3537851f506c2c84 Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Mon, 25 May 2026 20:08:34 +0530 Subject: [PATCH 10/12] fix: align debug_getRaw* methods with execution-apis BlockNumberOrTag spec debug_getRawBlock, debug_getRawHeader and debug_getRawReceipts now use BlockParameter (BlockNumberOrTag) instead of BlockParameterOrBlockHash, matching the execution-apis spec. Resolves the remaining debug_getRawReceipts/get-invalid-number hive failure. CHANGELOG breaking-changes list now explicitly names these three methods and eth_getProof (which keeps BlockParameterOrBlockHash per its spec). Signed-off-by: Sridhar Panigrahi --- CHANGELOG.md | 2 +- .../internal/methods/DebugGetRawBlock.java | 17 +++++++------- .../internal/methods/DebugGetRawHeader.java | 17 +++++++------- .../internal/methods/DebugGetRawReceipts.java | 22 +++++++++---------- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92965437038..fa9138b0d43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Breaking Changes - Besu now requires JDK 25 to build and run. - RPC changes to enhance compatibility with other ELs - - Block number parameter in RPCs now only supports hex values. Non-hex (decimal) block number parameters are now rejected. Affected RPCs including but not limited to: `admin_logsRemoveCache`, `admin_generateLogBloomCache`, `eth_estimateGas`, `eth_getBlockByNumber`, `eth_getBlockTransactionCountByNumber`, `eth_getTransactionByBlockNumberAndIndex`, `eth_getUncleByBlockNumberAndIndex`, `eth_getUncleCountByBlockNumber`, `eth_feeHistory`, `trace_block`, `trace_call`, `trace_callMany`, `trace_replayBlockTransactions`, `debug_traceBlockByNumber`, `debug_traceCall`, and `debug_replayBlock` [#10515](https://github.com/besu-eth/besu/pull/10515), [#10240](https://github.com/besu-eth/besu/pull/10240) + - Block number parameter in RPCs now only supports hex values. Non-hex (decimal) block number parameters are now rejected. Affected RPCs including but not limited to: `admin_logsRemoveCache`, `admin_generateLogBloomCache`, `eth_estimateGas`, `eth_getBlockByNumber`, `eth_getBlockTransactionCountByNumber`, `eth_getTransactionByBlockNumberAndIndex`, `eth_getUncleByBlockNumberAndIndex`, `eth_getUncleCountByBlockNumber`, `eth_feeHistory`, `eth_getProof`, `trace_block`, `trace_call`, `trace_callMany`, `trace_replayBlockTransactions`, `debug_traceBlockByNumber`, `debug_traceCall`, `debug_replayBlock`, `debug_getRawBlock`, `debug_getRawHeader`, and `debug_getRawReceipts` [#10515](https://github.com/besu-eth/besu/pull/10515), [#10240](https://github.com/besu-eth/besu/pull/10240) - Hash parameter in RPCs now only supports hex values. Hash values without a `0x` prefix are now rejected with `-32602 INVALID_PARAMS`. Affected RPCs including but not limited to: `debug_getRawTransaction`, `eth_getTransactionByHash`, `eth_getTransactionReceipt` [#10505](https://github.com/besu-eth/besu/pull/10505) ### Upcoming Breaking Changes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java index a3d61f4eebd..eed5fda38a9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java @@ -14,11 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; @@ -27,7 +26,7 @@ import com.google.common.base.Suppliers; -public class DebugGetRawBlock extends AbstractBlockParameterOrBlockHashMethod { +public class DebugGetRawBlock extends AbstractBlockParameterMethod { public DebugGetRawBlock(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); @@ -39,21 +38,21 @@ public String getName() { } @Override - protected BlockParameterOrBlockHash blockParameterOrBlockHash( - final JsonRpcRequestContext request) { + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { try { - return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + return request.getRequiredParameter(0, BlockParameter.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( - "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } } @Override - protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { + protected Object resultByBlockNumber( + final JsonRpcRequestContext request, final long blockNumber) { return getBlockchainQueries() .getBlockchain() - .getBlockByHash(blockHash) + .getBlockByNumber(blockNumber) .map(block -> RLP.encode(block::writeTo).toString()) .orElseGet( () -> diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java index 75c4fcc552e..e125f8a2532 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java @@ -14,11 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; @@ -27,7 +26,7 @@ import com.google.common.base.Suppliers; -public class DebugGetRawHeader extends AbstractBlockParameterOrBlockHashMethod { +public class DebugGetRawHeader extends AbstractBlockParameterMethod { public DebugGetRawHeader(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); @@ -39,20 +38,20 @@ public String getName() { } @Override - protected BlockParameterOrBlockHash blockParameterOrBlockHash( - final JsonRpcRequestContext request) { + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { try { - return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + return request.getRequiredParameter(0, BlockParameter.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( - "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } } @Override - protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { + protected Object resultByBlockNumber( + final JsonRpcRequestContext request, final long blockNumber) { return getBlockchainQueries() - .getBlockHeaderByHash(blockHash) + .getBlockHeaderByNumber(blockNumber) .map(header -> RLP.encode(header::writeTo).toString()) .orElseGet( () -> diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java index ca7f527aa35..0e10d81f3d1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java @@ -14,11 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -31,7 +30,7 @@ import com.google.common.base.Suppliers; -public class DebugGetRawReceipts extends AbstractBlockParameterOrBlockHashMethod { +public class DebugGetRawReceipts extends AbstractBlockParameterMethod { public DebugGetRawReceipts(final BlockchainQueries blockchain) { super(Suppliers.ofInstance(blockchain)); @@ -43,22 +42,21 @@ public String getName() { } @Override - protected BlockParameterOrBlockHash blockParameterOrBlockHash( - final JsonRpcRequestContext request) { + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { try { - return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + return request.getRequiredParameter(0, BlockParameter.class); } catch (JsonRpcParameterException e) { throw new InvalidJsonRpcParameters( - "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } } @Override - protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { - return blockchainQueries - .get() - .getBlockchain() - .getTxReceipts(blockHash) + protected Object resultByBlockNumber( + final JsonRpcRequestContext request, final long blockNumber) { + return getBlockchainQueries() + .getBlockHashByNumber(blockNumber) + .flatMap(blockHash -> getBlockchainQueries().getBlockchain().getTxReceipts(blockHash)) .map(this::toRLP) .orElseGet(() -> new String[0]); } From 803e08bb9fec22f3da5a4dc6af895d313b830444 Mon Sep 17 00:00:00 2001 From: Sridhar Panigrahi Date: Tue, 26 May 2026 11:56:20 +0530 Subject: [PATCH 11/12] revert: drop DebugGetRawBlock/DebugGetRawHeader changes per maintainer review Reverts both files to origin/main so this PR stays focused on the block-parameter hex-prefix validation change. Signed-off-by: Sridhar Panigrahi --- .../api/jsonrpc/internal/methods/DebugGetRawBlock.java | 1 + .../api/jsonrpc/internal/methods/DebugGetRawHeader.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java index eed5fda38a9..c9097583e36 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java @@ -50,6 +50,7 @@ protected BlockParameter blockParameter(final JsonRpcRequestContext request) { @Override protected Object resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { + return getBlockchainQueries() .getBlockchain() .getBlockByNumber(blockNumber) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java index e125f8a2532..8e4f9ce6ead 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java @@ -50,9 +50,10 @@ protected BlockParameter blockParameter(final JsonRpcRequestContext request) { @Override protected Object resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { + return getBlockchainQueries() - .getBlockHeaderByNumber(blockNumber) - .map(header -> RLP.encode(header::writeTo).toString()) + .blockByNumber(blockNumber) + .map(block -> RLP.encode(block.getHeader()::writeTo).toString()) .orElseGet( () -> new JsonRpcErrorResponse( From 46ed14d0f67241f9bc1a3861d21c31e27434b301 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 28 May 2026 20:52:43 +1000 Subject: [PATCH 12/12] review comments Signed-off-by: Sally MacFarlane --- .../internal/methods/DebugGetRawReceipts.java | 2 +- .../parameters/BlockParameterOrBlockHash.java | 7 +- .../methods/DebugGetRawReceiptsTest.java | 96 +++++++++++++++++++ .../BlockParameterOrBlockHashTest.java | 53 ++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceiptsTest.java create mode 100644 ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHashTest.java diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java index 0e10d81f3d1..c7969fdd7d6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java @@ -58,7 +58,7 @@ protected Object resultByBlockNumber( .getBlockHashByNumber(blockNumber) .flatMap(blockHash -> getBlockchainQueries().getBlockchain().getTxReceipts(blockHash)) .map(this::toRLP) - .orElseGet(() -> new String[0]); + .orElse(null); } private String[] toRLP(final List receipts) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index 5e67b53d9b5..0e767442cb3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -100,8 +100,13 @@ public BlockParameterOrBlockHash(final Object value) throws JsonProcessingExcept requireCanonical = false; } } else { + final String blockNumberText = jsonNode.get("blockNumber").asText(); + if (!blockNumberText.toLowerCase(Locale.ROOT).startsWith("0x")) { + throw new IllegalArgumentException( + "Invalid block number: must be a hex string with 0x prefix"); + } type = BlockParameterType.NUMERIC; - number = OptionalLong.of(Long.decode(jsonNode.get("blockNumber").asText())); + number = OptionalLong.of(Long.decode(blockNumberText)); blockHash = Optional.empty(); requireCanonical = false; } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceiptsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceiptsTest.java new file mode 100644 index 00000000000..b6f7390273f --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceiptsTest.java @@ -0,0 +1,96 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.Blockchain; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DebugGetRawReceiptsTest { + + private BlockchainQueries blockchainQueries; + private Blockchain blockchain; + private DebugGetRawReceipts method; + + @BeforeEach + public void setUp() { + blockchainQueries = mock(BlockchainQueries.class); + blockchain = mock(Blockchain.class); + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + method = new DebugGetRawReceipts(blockchainQueries); + } + + @Test + public void returnsNullForMissingBlock() { + final long missingBlockNumber = 999_999_999L; + when(blockchainQueries.getBlockHashByNumber(missingBlockNumber)).thenReturn(Optional.empty()); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + "debug_getRawReceipts", + new Object[] {"0x" + Long.toHexString(missingBlockNumber)})); + + final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); + assertThat(response.getResult()).isNull(); + } + + @Test + public void returnsNullForFutureBlock() { + final long futureBlockNumber = Long.MAX_VALUE; + when(blockchainQueries.getBlockHashByNumber(futureBlockNumber)).thenReturn(Optional.empty()); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + "debug_getRawReceipts", + new Object[] {"0x" + Long.toHexString(futureBlockNumber)})); + + final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); + assertThat(response.getResult()).isNull(); + } + + @Test + public void returnsEmptyArrayForBlockWithNoReceipts() { + final long blockNumber = 42L; + final Hash blockHash = Hash.fromHexStringLenient("0x1234"); + when(blockchainQueries.getBlockHashByNumber(blockNumber)).thenReturn(Optional.of(blockHash)); + when(blockchain.getTxReceipts(blockHash)).thenReturn(Optional.of(java.util.List.of())); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + "debug_getRawReceipts", + new Object[] {"0x" + Long.toHexString(blockNumber)})); + + final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(request); + assertThat((String[]) response.getResult()).isEmpty(); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHashTest.java new file mode 100644 index 00000000000..759548bda23 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHashTest.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.jupiter.api.Test; + +public class BlockParameterOrBlockHashTest { + + @Test + public void hexBlockNumberStringIsAccepted() throws JsonProcessingException { + final BlockParameterOrBlockHash param = new BlockParameterOrBlockHash("0x64"); + assertThat(param.getNumber()).hasValue(100); + } + + @Test + public void decimalBlockNumberStringIsRejected() { + assertThatThrownBy(() -> new BlockParameterOrBlockHash("100")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("0x prefix"); + } + + @Test + public void hexBlockNumberInEip1898ObjectFormIsAccepted() throws JsonProcessingException { + // EIP-1898 object form: {"blockNumber": "0x64"} + final BlockParameterOrBlockHash param = + new BlockParameterOrBlockHash(java.util.Map.of("blockNumber", "0x64")); + assertThat(param.getNumber()).hasValue(100); + } + + @Test + public void decimalBlockNumberInEip1898ObjectFormIsRejected() { + // EIP-1898 object form with decimal: {"blockNumber": "100"} must be rejected + assertThatThrownBy(() -> new BlockParameterOrBlockHash(java.util.Map.of("blockNumber", "100"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("0x prefix"); + } +}