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 6b049b221a4..69f61ef0442 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 @@ -55,6 +55,7 @@ public enum RpcMethod { ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"), ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"), ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"), + ENGINE_EXCHANGE_CAPABILITIES("engine_exchangeCapabilities"), GOQUORUM_ETH_GET_QUORUM_PAYLOAD("eth_getQuorumPayload"), GOQUORUM_STORE_RAW("goquorum_storeRaw"), 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 new file mode 100644 index 00000000000..4d62e62cdab --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java @@ -0,0 +1,77 @@ +/* + * 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.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ENGINE_EXCHANGE_CAPABILITIES; +import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda; + +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.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.vertx.core.Vertx; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EngineExchangeCapabilities extends ExecutionEngineJsonRpcMethod { + private static final Logger LOG = LoggerFactory.getLogger(EngineExchangeCapabilities.class); + + public EngineExchangeCapabilities( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener) { + super(vertx, protocolContext, engineCallListener); + } + + @Override + public String getName() { + return ENGINE_EXCHANGE_CAPABILITIES.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + engineCallListener.executionEngineCalled(); + + final List remoteCapabilities = + Arrays.stream(requestContext.getRequest().getParams()) + .map(String::valueOf) + .collect(Collectors.toList()); + final Object reqId = requestContext.getRequest().getId(); + + traceLambda(LOG, "received remote capabilities: {}", () -> remoteCapabilities); + + final List localCapabilities = + Stream.of(RpcMethod.values()) + .filter(e -> e.getMethodName().startsWith("engine_")) + .filter(e -> !e.equals(ENGINE_EXCHANGE_CAPABILITIES)) + .map(RpcMethod::getMethodName) + .collect(Collectors.toList()); + + return respondWith(reqId, localCapabilities); + } + + private JsonRpcResponse respondWith( + final Object requestId, final List localCapabilities) { + return new JsonRpcSuccessResponse(requestId, localCapabilities); + } +} 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 60c02722dec..0de76d5b620 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 @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineExchangeCapabilities; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineExchangeTransitionConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2; @@ -112,7 +113,8 @@ protected Map create() { mergeCoordinator.get(), engineQosTimer), new EngineExchangeTransitionConfiguration( - consensusEngineServer, protocolContext, engineQosTimer)); + consensusEngineServer, protocolContext, engineQosTimer), + new EngineExchangeCapabilities(consensusEngineServer, protocolContext, engineQosTimer)); } else { return mapOf( new EngineExchangeTransitionConfiguration( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java new file mode 100644 index 00000000000..b9817f63dfa --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java @@ -0,0 +1,93 @@ +/* + * 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.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ENGINE_EXCHANGE_CAPABILITIES; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.ethereum.ProtocolContext; +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.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; + +import java.util.Collections; +import java.util.List; +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.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class EngineExchangeCapabilitiesTest { + private EngineExchangeCapabilities method; + private static final Vertx vertx = Vertx.vertx(); + + @Mock private ProtocolContext protocolContext; + + @Mock private EngineCallListener engineCallListener; + + @Before + public void setUp() { + this.method = new EngineExchangeCapabilities(vertx, protocolContext, engineCallListener); + } + + @Test + public void shouldReturnExpectedMethodName() { + assertThat(method.getName()).isEqualTo("engine_exchangeCapabilities"); + } + + @Test + public void shouldReturnAllSupportedEngineApiRpcNames() { + var response = resp(List.of("engine_newPayloadV1", "engine_newPayloadV2", "nonsense")); + + var result = fromSuccessResp(response); + assertThat(result).allMatch(name -> name.startsWith("engine_")); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldNotReturnSelf() { + var response = resp(Collections.emptyList()); + + var result = fromSuccessResp(response); + assertThat(result).allMatch(name -> !ENGINE_EXCHANGE_CAPABILITIES.getMethodName().equals(name)); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + private JsonRpcResponse resp(final List params) { + return method.response( + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", ENGINE_EXCHANGE_CAPABILITIES.getMethodName(), params.toArray()))); + } + + @SuppressWarnings("unchecked") + private List fromSuccessResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + return Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .map(JsonRpcSuccessResponse::getResult) + .map(List.class::cast) + .get(); + } +}