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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public enum RpcMethod {
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"),
ENGINE_GET_CLIENT_VERSION_V1("engine_getClientVersionV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2("engine_getPayloadBodiesByHashV2"),
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V2("engine_getPayloadBodiesByRangeV2"),
ENGINE_EXCHANGE_CAPABILITIES("engine_exchangeCapabilities"),
ENGINE_PREPARE_PAYLOAD_DEBUG("engine_preparePayload_debug"),
ETH_ACCOUNTS("eth_accounts"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright contributors to Hyperledger 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.engine;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
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.methods.ExecutionEngineJsonRpcMethod;
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.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV2;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EngineGetPayloadBodiesByHashV2 extends ExecutionEngineJsonRpcMethod {

private static final int MAX_REQUEST_BLOCKS = 1024;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limit seems not to be in the spec, how do other clients handle that?

Copy link
Contributor Author

@mirgee mirgee Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specs define only the lower bound, it seems that it's up to clients to define the upper bound on the range:

Client software MUST support count values of at least 32 blocks. The call MUST return -38004: Too large request error if the requested range is too large.

Link to the quote.

private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadBodiesByHashV2.class);
private final BlockResultFactory blockResultFactory;

public EngineGetPayloadBodiesByHashV2(
final Vertx vertx,
final ProtocolContext protocolContext,
final BlockResultFactory blockResultFactory,
final EngineCallListener engineCallListener) {
super(vertx, protocolContext, engineCallListener);
this.blockResultFactory = blockResultFactory;
}

@Override
public String getName() {
return RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V2.getMethodName();
}

@Override
public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) {
engineCallListener.executionEngineCalled();

final Object reqId = request.getRequest().getId();

final Hash[] blockHashes;
try {
blockHashes = request.getRequiredParameter(0, Hash[].class);
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid block hash parameters (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e);
}

LOG.atTrace()
.setMessage("{} parameters: blockHashes {}")
.addArgument(this::getName)
.addArgument(blockHashes)
.log();

if (blockHashes.length > getMaxRequestBlocks()) {
return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_RANGE_REQUEST_TOO_LARGE);
}

final Blockchain blockchain = protocolContext.getBlockchain();

final List<Optional<String>> blockAccessLists =
Arrays.stream(blockHashes)
.map(blockHash -> getBlockAccessList(blockchain, blockHash))
.collect(Collectors.toList());

final List<Optional<BlockBody>> blockBodies =
Arrays.stream(blockHashes).map(blockchain::getBlockBody).collect(Collectors.toList());

final EngineGetPayloadBodiesResultV2 engineGetPayloadBodiesResultV2 =
blockResultFactory.payloadBodiesCompleteV2(blockBodies, blockAccessLists);

return new JsonRpcSuccessResponse(reqId, engineGetPayloadBodiesResultV2);
}

protected int getMaxRequestBlocks() {
return MAX_REQUEST_BLOCKS;
}

private Optional<String> getBlockAccessList(final Blockchain blockchain, final Hash blockHash) {
return blockchain
.getBlockAccessList(blockHash)
.map(EngineGetPayloadBodiesByHashV2::encodeBlockAccessList);
}

private static String encodeBlockAccessList(final BlockAccessList blockAccessList) {
final BytesValueRLPOutput output = new BytesValueRLPOutput();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are block access lists stored? It seems we are not storing them as RLP and need to encode them every single time. Why not store them as RLP directly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BAL is RLP encoded. So in this case we are decoding BAL from RLP and encode it again in order to return it. clearly not optimized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are storing them in RLP, decoding them every time on retrieval, and then sometimes re-encode them back to RLP when necessary. I agree this redundant round-trip is not ideal in those cases, but it is consistent with how block headers, block bodies and transaction receipts are currently handled.

blockAccessList.writeTo(output);
return output.encoded().toHexString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright contributors to Hyperledger 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.engine;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter;
import org.hyperledger.besu.ethereum.ProtocolContext;
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.methods.ExecutionEngineJsonRpcMethod;
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.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV2;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EngineGetPayloadBodiesByRangeV2 extends ExecutionEngineJsonRpcMethod {
private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadBodiesByRangeV2.class);
private static final int MAX_REQUEST_BLOCKS = 1024;
private final BlockResultFactory blockResultFactory;

public EngineGetPayloadBodiesByRangeV2(
final Vertx vertx,
final ProtocolContext protocolContext,
final BlockResultFactory blockResultFactory,
final EngineCallListener engineCallListener) {
super(vertx, protocolContext, engineCallListener);
this.blockResultFactory = blockResultFactory;
}

@Override
public String getName() {
return RpcMethod.ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V2.getMethodName();
}

@Override
public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) {
engineCallListener.executionEngineCalled();

final long startBlockNumber;
try {
startBlockNumber = request.getRequiredParameter(0, UnsignedLongParameter.class).getValue();
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid start block number parameter (index 0)",
RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS,
e);
}
final long count;
try {
count = request.getRequiredParameter(1, UnsignedLongParameter.class).getValue();
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid block count params (index 1)", RpcErrorType.INVALID_BLOCK_COUNT_PARAMS, e);
}
final Object reqId = request.getRequest().getId();

LOG.atTrace()
.setMessage("{} parameters: start block number {} count {}")
.addArgument(this::getName)
.addArgument(startBlockNumber)
.addArgument(count)
.log();

if (startBlockNumber < 1 || count < 1) {
return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS);
}

if (count > getMaxRequestBlocks()) {
return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_RANGE_REQUEST_TOO_LARGE);
}

final Blockchain blockchain = protocolContext.getBlockchain();
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber();

// request startBlockNumber is beyond head of chain
if (chainHeadBlockNumber < startBlockNumber) {
// Empty List of payloadBodies
return new JsonRpcSuccessResponse(reqId, new EngineGetPayloadBodiesResultV2());
}

final long upperBound = startBlockNumber + count;

// if we've received request from blocks beyond the head we exclude those from the query
final long endExclusiveBlockNumber =
chainHeadBlockNumber < upperBound ? chainHeadBlockNumber + 1 : upperBound;

final List<Optional<Hash>> blockHashes =
LongStream.range(startBlockNumber, endExclusiveBlockNumber)
.mapToObj(blockchain::getBlockHashByNumber)
.collect(Collectors.toList());

final List<Optional<String>> blockAccessLists =
blockHashes.stream()
.map(
maybeHash ->
maybeHash.flatMap(blockHash -> getBlockAccessList(blockchain, blockHash)))
.collect(Collectors.toList());

final List<Optional<BlockBody>> blockBodies =
blockHashes.stream()
.map(maybeHash -> maybeHash.flatMap(blockchain::getBlockBody))
.collect(Collectors.toList());

EngineGetPayloadBodiesResultV2 engineGetPayloadBodiesResultV2 =
blockResultFactory.payloadBodiesCompleteV2(blockBodies, blockAccessLists);

return new JsonRpcSuccessResponse(reqId, engineGetPayloadBodiesResultV2);
}

protected int getMaxRequestBlocks() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those methods are duplicated and are the same in EngineGetPayloadBodiesByHashV2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree there is a lot of duplication overall. Happy to clean it up in a follow-up PR 😇

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create an issue for that @mirgee . I will merge the branch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return MAX_REQUEST_BLOCKS;
}

private Optional<String> getBlockAccessList(final Blockchain blockchain, final Hash blockHash) {
return blockchain
.getBlockAccessList(blockHash)
.map(EngineGetPayloadBodiesByRangeV2::encodeBlockAccessList);
}

private static String encodeBlockAccessList(final BlockAccessList blockAccessList) {
final BytesValueRLPOutput output = new BytesValueRLPOutput();
blockAccessList.writeTo(output);
return output.encoded().toHexString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import org.hyperledger.besu.consensus.merge.PayloadWrapper;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1.PayloadBody;
import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.Block;
Expand All @@ -33,6 +32,7 @@
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
Expand Down Expand Up @@ -252,12 +252,31 @@ private static Optional<List<String>> requestsAsHex(final PayloadWrapper payload

public EngineGetPayloadBodiesResultV1 payloadBodiesCompleteV1(
final List<Optional<BlockBody>> blockBodies) {
final List<PayloadBody> payloadBodies =
final List<EngineGetPayloadBodiesResultV1.PayloadBody> payloadBodies =
blockBodies.stream()
.map(maybeBody -> maybeBody.map(PayloadBody::new).orElse(null))
.map(
maybeBody ->
maybeBody.map(EngineGetPayloadBodiesResultV1.PayloadBody::new).orElse(null))
.collect(Collectors.toList());
return new EngineGetPayloadBodiesResultV1(payloadBodies);
}

public EngineGetPayloadBodiesResultV2 payloadBodiesCompleteV2(
final List<Optional<BlockBody>> blockBodies, final List<Optional<String>> blockAccessLists) {
final List<EngineGetPayloadBodiesResultV2.PayloadBody> payloadBodies =
IntStream.range(0, blockBodies.size())
.mapToObj(
index ->
blockBodies
.get(index)
.map(
body ->
new EngineGetPayloadBodiesResultV2.PayloadBody(
body, blockAccessLists.get(index).orElse(null)))
.orElse(null))
.collect(Collectors.toList());
return new EngineGetPayloadBodiesResultV2(payloadBodies);
}

// endregion EngineGetPayloadBodiesResult
}
Loading