-
Notifications
You must be signed in to change notification settings - Fork 1k
Add engine_getPayloadBodiesBy*V2 methods
#9674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; | ||
| 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(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those methods are duplicated and are the same in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😇
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:
Link to the quote.