diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 31f1e99c20f..bdecee7c9e9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -58,7 +58,7 @@ public enum RpcMethod { ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"), ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"), ENGINE_EXCHANGE_CAPABILITIES("engine_exchangeCapabilities"), - + ENGINE_PREPARE_PAYLOAD_DEBUG("engine_preparePayload_debug"), PRIV_CALL("priv_call"), PRIV_GET_PRIVATE_TRANSACTION("priv_getPrivateTransaction"), PRIV_GET_TRANSACTION_COUNT("priv_getTransactionCount"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java index 3e4ca2d0a99..f59681a3854 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ENGINE_EXCHANGE_CAPABILITIES; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ENGINE_PREPARE_PAYLOAD_DEBUG; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -61,6 +62,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) Stream.of(RpcMethod.values()) .filter(e -> e.getMethodName().startsWith("engine_")) .filter(e -> !e.equals(ENGINE_EXCHANGE_CAPABILITIES)) + .filter(e -> !e.equals(ENGINE_PREPARE_PAYLOAD_DEBUG)) .map(RpcMethod::getMethodName) .collect(Collectors.toList()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java new file mode 100644 index 00000000000..fe8f377d91b --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java @@ -0,0 +1,104 @@ +/* + * 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 static java.util.stream.Collectors.toList; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; + +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +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.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePreparePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +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.results.EnginePreparePayloadResult; +import org.hyperledger.besu.ethereum.core.Withdrawal; + +import java.util.List; +import java.util.Optional; + +import graphql.VisibleForTesting; +import io.vertx.core.Vertx; + +public class EnginePreparePayloadDebug extends ExecutionEngineJsonRpcMethod { + private final MergeMiningCoordinator mergeCoordinator; + + public EnginePreparePayloadDebug( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener, + final MergeMiningCoordinator mergeCoordinator) { + super(vertx, protocolContext, engineCallListener); + this.mergeCoordinator = mergeCoordinator; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_PREPARE_PAYLOAD_DEBUG.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + final EnginePreparePayloadParameter enginePreparePayloadParameter = + requestContext + .getOptionalParameter(0, EnginePreparePayloadParameter.class) + .orElse( + new EnginePreparePayloadParameter( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + final var requestId = requestContext.getRequest().getId(); + + if (mergeContext.get().isSyncing()) { + return new JsonRpcSuccessResponse(requestId, new EnginePreparePayloadResult(SYNCING, null)); + } + + return generatePayload(enginePreparePayloadParameter) + .map( + payloadIdentifier -> + new JsonRpcSuccessResponse( + requestId, new EnginePreparePayloadResult(VALID, payloadIdentifier))) + .orElseGet(() -> new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PARAMS)); + } + + @VisibleForTesting + Optional generatePayload(final EnginePreparePayloadParameter param) { + final List withdrawals = + param.getWithdrawals().stream().map(WithdrawalParameter::toWithdrawal).collect(toList()); + + return param + .getParentHash() + .map(header -> protocolContext.getBlockchain().getBlockHeader(header)) + .orElseGet(() -> Optional.of(protocolContext.getBlockchain().getChainHeadHeader())) + .map( + parentHeader -> + mergeCoordinator.preparePayload( + parentHeader, + param.getTimestamp().orElse(parentHeader.getTimestamp() + 1L), + param.getPrevRandao(), + param.getFeeRecipient(), + Optional.of(withdrawals))); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java new file mode 100644 index 00000000000..698d3888ed4 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java @@ -0,0 +1,68 @@ +/* + * 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.parameters; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public class EnginePreparePayloadParameter { + private final Optional parentHash; + private final Address feeRecipient; + private final Bytes32 prevRandao; + private final Optional timestamp; + final List withdrawals; + + @JsonCreator + public EnginePreparePayloadParameter( + @JsonProperty("parentHash") final Optional parentHash, + @JsonProperty("feeRecipient") final Optional
feeRecipient, + @JsonProperty("timestamp") final Optional timestamp, + @JsonProperty("prevRandao") final Optional prevRandao, + @JsonProperty("withdrawals") final Optional> withdrawals) { + this.parentHash = parentHash; + this.feeRecipient = feeRecipient.orElse(Address.ZERO); + this.timestamp = timestamp.map(UnsignedLongParameter::getValue); + this.prevRandao = Bytes32.fromHexStringLenient(prevRandao.orElse("deadbeef")); + this.withdrawals = withdrawals.orElse(Collections.emptyList()); + } + + public Optional getParentHash() { + return parentHash; + } + + public Address getFeeRecipient() { + return feeRecipient; + } + + public Optional getTimestamp() { + return timestamp; + } + + public Bytes32 getPrevRandao() { + return prevRandao; + } + + public List getWithdrawals() { + return withdrawals; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java new file mode 100644 index 00000000000..650ad973875 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java @@ -0,0 +1,44 @@ +/* + * 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.results; + +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"status", "payloadId"}) +public class EnginePreparePayloadResult { + private final EngineStatus status; + private final PayloadIdentifier payloadId; + + public EnginePreparePayloadResult(final EngineStatus status, final PayloadIdentifier payloadId) { + this.status = status; + this.payloadId = payloadId; + } + + @JsonGetter(value = "status") + public String getStatus() { + return status.name(); + } + + @JsonGetter(value = "payloadId") + public String getPayloadId() { + return Optional.ofNullable(payloadId).map(PayloadIdentifier::toShortHexString).orElse(""); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index ccf50334784..abe82b8e399 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV2; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EnginePreparePayloadDebug; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineQosTimer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; @@ -120,7 +121,9 @@ protected Map create() { consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer), new EngineGetPayloadBodiesByRangeV1( consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer), - new EngineExchangeCapabilities(consensusEngineServer, protocolContext, engineQosTimer)); + new EngineExchangeCapabilities(consensusEngineServer, protocolContext, engineQosTimer), + new EnginePreparePayloadDebug( + consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get())); } else { return mapOf( new EngineExchangeTransitionConfiguration( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java new file mode 100644 index 00000000000..c89df2fff80 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java @@ -0,0 +1,105 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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 static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.merge.MergeContext; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePreparePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePreparePayloadResult; + +import java.util.Optional; + +import io.vertx.core.Vertx; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class EnginePreparePayloadDebugTest { + private static final Vertx vertx = Vertx.vertx(); + EnginePreparePayloadDebug method; + @Mock private ProtocolContext protocolContext; + @Mock private EngineCallListener engineCallListener; + @Mock private MergeMiningCoordinator mergeCoordinator; + @Mock private MergeContext mergeContext; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private JsonRpcRequestContext requestContext; + + @Mock private EnginePreparePayloadParameter param; + + @Before + public void setUp() { + when(protocolContext.safeConsensusContext(MergeContext.class)) + .thenReturn(Optional.of(mergeContext)); + when(requestContext.getOptionalParameter(0, EnginePreparePayloadParameter.class)) + .thenReturn(Optional.of(param)); + method = + spy( + new EnginePreparePayloadDebug( + vertx, protocolContext, engineCallListener, mergeCoordinator)); + } + + @Test + public void shouldReturnSyncing() { + when(mergeContext.isSyncing()).thenReturn(true); + var resp = method.syncResponse(requestContext); + assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); + var result = ((JsonRpcSuccessResponse) resp).getResult(); + assertThat(result).isInstanceOf(EnginePreparePayloadResult.class); + var payloadResult = (EnginePreparePayloadResult) result; + assertThat(payloadResult.getStatus()).isEqualTo(SYNCING.name()); + } + + @Test + public void shouldReturnPayloadId() { + checkForPayloadId(); + } + + @Test + public void shouldReturnPayloadIdWhenNoParams() { + when(requestContext.getOptionalParameter(0, EnginePreparePayloadParameter.class)) + .thenReturn(Optional.empty()); + checkForPayloadId(); + } + + private void checkForPayloadId() { + doAnswer(__ -> Optional.of(new PayloadIdentifier(0xdeadbeefL))) + .when(method) + .generatePayload(any()); + var resp = method.syncResponse(requestContext); + assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); + var result = ((JsonRpcSuccessResponse) resp).getResult(); + assertThat(result).isInstanceOf(EnginePreparePayloadResult.class); + var payloadResult = (EnginePreparePayloadResult) result; + assertThat(payloadResult.getStatus()).isEqualTo(VALID.name()); + assertThat(payloadResult.getPayloadId()).isEqualTo("0xdeadbeef"); + } +}