diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ExecutionLayerTriggeredExitAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ExecutionLayerTriggeredExitAcceptanceTest.java index 53d6d0ed3d1..98328f7e9bd 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ExecutionLayerTriggeredExitAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ExecutionLayerTriggeredExitAcceptanceTest.java @@ -78,9 +78,9 @@ void triggerValidatorExitWithFullWithdrawal() throws Exception { tekuNode.waitForNewFinalization(); final ValidatorKeys validator = validatorKeys.getValidatorKeys().get(0); - final BLSPublicKey validatorPublicKey = validator.getValidatorKey().getPublicKey(); + final BLSPublicKey validatorPubkey = validator.getValidatorKey().getPublicKey(); - besuNode.createWithdrawalRequest(eth1PrivateKey, validatorPublicKey, UInt64.ZERO); + besuNode.createWithdrawalRequest(eth1PrivateKey, validatorPubkey, UInt64.ZERO); // Wait for validator exit confirmation tekuNode.waitForLogMessageContaining( diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ValidatorConsolidationAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ValidatorConsolidationAcceptanceTest.java index 2c803b09303..70c6f96c7d8 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ValidatorConsolidationAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/ValidatorConsolidationAcceptanceTest.java @@ -80,19 +80,19 @@ void consolidateValidator() throws Exception { tekuNode.waitForNewFinalization(); final ValidatorKeys sourceValidator = validatorKeys.getValidatorKeys().get(0); - final BLSPublicKey sourceValidatorPublicKey = sourceValidator.getValidatorKey().getPublicKey(); + final BLSPublicKey sourceValidatorPubkey = sourceValidator.getValidatorKey().getPublicKey(); final ValidatorKeys targetValidator = validatorKeys.getValidatorKeys().get(1); - final BLSPublicKey targetValidatorPublicKey = targetValidator.getValidatorKey().getPublicKey(); + final BLSPublicKey targetValidatorPubkey = targetValidator.getValidatorKey().getPublicKey(); besuNode.createConsolidationRequest( - eth1PrivateKey, sourceValidatorPublicKey, targetValidatorPublicKey); - waitForValidatorExit(tekuNode, sourceValidatorPublicKey); + eth1PrivateKey, sourceValidatorPubkey, targetValidatorPubkey); + waitForValidatorExit(tekuNode, sourceValidatorPubkey); } private void waitForValidatorExit( - final TekuBeaconNode tekuNode, final BLSPublicKey validatorPublicKey) { - final String pubKeySubstring = validatorPublicKey.toHexString().substring(2, 9); + final TekuBeaconNode tekuNode, final BLSPublicKey validatorPubkey) { + final String pubKeySubstring = validatorPubkey.toHexString().substring(2, 9); tekuNode.waitForLogMessageContaining( "Validator " + pubKeySubstring diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/executionrequests/ExecutionRequestsService.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/executionrequests/ExecutionRequestsService.java index e644fea4fae..919e8877b2c 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/executionrequests/ExecutionRequestsService.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/executionrequests/ExecutionRequestsService.java @@ -102,7 +102,7 @@ public SafeFuture createWithdrawalRequest( } public SafeFuture createConsolidationRequest( - final BLSPublicKey sourceValidatorPublicKey, final BLSPublicKey targetValidatorPublicKey) { + final BLSPublicKey sourceValidatorPubkey, final BLSPublicKey targetValidatorPubkey) { // Sanity check that we can interact with the contract Waiter.waitFor( () -> @@ -110,7 +110,7 @@ public SafeFuture createConsolidationRequest( .isEqualTo(0)); return consolidationRequestContract - .createConsolidationRequest(sourceValidatorPublicKey, targetValidatorPublicKey) + .createConsolidationRequest(sourceValidatorPubkey, targetValidatorPubkey) .thenCompose( response -> { final String txHash = response.getResult(); diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionRequests.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionRequests.java index 5b62bae5657..8d1ef190f57 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionRequests.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionRequests.java @@ -74,12 +74,12 @@ public ExecutionRequests( .toList(); final List< tech.pegasys.teku.spec.datastructures.execution.versions.electra.ConsolidationRequest> - consolidationInternal = + consolidationsInternal = consolidations.stream() .map( consolidationRequest -> consolidationRequest.asInternalConsolidationRequest(consolidationSchema)) .toList(); - return schema.create(depositsInternal, withdrawalsInternal, consolidationInternal); + return schema.create(depositsInternal, withdrawalsInternal, consolidationsInternal); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/WithdrawalRequest.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/WithdrawalRequest.java index a28cb29275b..b434fb3df31 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/WithdrawalRequest.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/WithdrawalRequest.java @@ -25,17 +25,17 @@ public class WithdrawalRequest { private final Eth1Address sourceAddress; @JsonProperty("validator_pubkey") - private final BLSPublicKey validatorPublicKey; + private final BLSPublicKey validatorPubkey; @JsonProperty("amount") private final UInt64 amount; public WithdrawalRequest( @JsonProperty("source_address") final Eth1Address sourceAddress, - @JsonProperty("validator_pubkey") final BLSPublicKey validatorPublicKey, + @JsonProperty("validator_pubkey") final BLSPublicKey validatorPubkey, @JsonProperty("amount") final UInt64 amount) { this.sourceAddress = sourceAddress; - this.validatorPublicKey = validatorPublicKey; + this.validatorPubkey = validatorPubkey; this.amount = amount; } @@ -44,12 +44,12 @@ public WithdrawalRequest( withdrawalRequest) { this.sourceAddress = Eth1Address.fromBytes(withdrawalRequest.getSourceAddress().getWrappedBytes()); - this.validatorPublicKey = withdrawalRequest.getValidatorPublicKey(); + this.validatorPubkey = withdrawalRequest.getValidatorPubkey(); this.amount = withdrawalRequest.getAmount(); } public final tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequest asInternalWithdrawalRequest(final WithdrawalRequestSchema schema) { - return schema.create(sourceAddress, validatorPublicKey, amount); + return schema.create(sourceAddress, validatorPubkey, amount); } } diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/JsonRpcErrorCodes.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/JsonRpcErrorCodes.java new file mode 100644 index 00000000000..d83f6429dfe --- /dev/null +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/JsonRpcErrorCodes.java @@ -0,0 +1,61 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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. + */ + +package tech.pegasys.teku.ethereum.executionclient.web3j; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +enum JsonRpcErrorCodes { + PARSE_ERROR(-32700, "Parse error"), + INVALID_REQUEST(-32600, "Invalid Request"), + METHOD_NOT_FOUND(-32601, "Method not found"), + INVALID_PARAMS(-32602, "Invalid params"), + INTERNAL_ERROR(-32603, "Internal error"), + SERVER_ERROR(-32000, "Server error"); + + private final int errorCode; + private final String description; + private static final Int2ObjectOpenHashMap CODE_TO_ERROR_MAP; + + static { + CODE_TO_ERROR_MAP = new Int2ObjectOpenHashMap<>(); + for (final JsonRpcErrorCodes error : values()) { + CODE_TO_ERROR_MAP.put(error.getErrorCode(), error); + } + } + + JsonRpcErrorCodes(final int errorCode, final String description) { + this.errorCode = errorCode; + this.description = description; + } + + public int getErrorCode() { + return errorCode; + } + + public String getDescription() { + return description; + } + + public static String getDescription(final int errorCode) { + return fromCode(errorCode).getDescription(); + } + + public static JsonRpcErrorCodes fromCode(final int errorCode) { + final JsonRpcErrorCodes error = CODE_TO_ERROR_MAP.get(errorCode); + if (error != null) { + return error; + } + return errorCode >= -32099 && errorCode <= -32000 ? SERVER_ERROR : INTERNAL_ERROR; + } +} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClient.java index b8cf68f6729..c1bcc331e29 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClient.java @@ -15,7 +15,6 @@ import static tech.pegasys.teku.infrastructure.exceptions.ExceptionUtil.getMessageOrSimpleName; -import java.io.IOException; import java.net.ConnectException; import java.time.Duration; import java.util.Collection; @@ -86,14 +85,9 @@ public SafeFuture> doRequest( (response, exception) -> { final boolean isCriticalRequest = isCriticalRequest(web3jRequest); if (exception != null) { - final boolean couldBeAuthError = isAuthenticationException(exception); - handleError(isCriticalRequest, exception, couldBeAuthError); - return Response.withErrorMessage(getMessageOrSimpleName(exception)); + return handleException(exception, isCriticalRequest); } else if (response.hasError()) { - final String errorMessage = - response.getError().getCode() + ": " + response.getError().getMessage(); - handleError(isCriticalRequest, new IOException(errorMessage), false); - return Response.withErrorMessage(errorMessage); + return handleJsonRpcError(response.getError(), isCriticalRequest); } else { handleSuccess(isCriticalRequest); return new Response<>(response.getResult()); @@ -101,6 +95,31 @@ public SafeFuture> doRequest( }); } + private Response handleException( + final Throwable exception, final boolean isCriticalRequest) { + final boolean couldBeAuthError = isAuthenticationException(exception); + handleError(isCriticalRequest, exception, couldBeAuthError); + return Response.withErrorMessage(getMessageOrSimpleName(exception)); + } + + private Response handleJsonRpcError( + final org.web3j.protocol.core.Response.Error error, final boolean isCriticalRequest) { + final int errorCode = error.getCode(); + final String errorType = JsonRpcErrorCodes.getDescription(errorCode); + final String formattedError = + String.format("JSON-RPC error: %s (%d): %s", errorType, errorCode, error.getMessage()); + + if (isCriticalRequest) { + logError(formattedError); + } + + return Response.withErrorMessage(formattedError); + } + + private void logError(final String errorMessage) { + eventLog.executionClientRequestFailed(new Exception(errorMessage), false); + } + private boolean isCriticalRequest(final Request request) { return !nonCriticalMethods.contains(request.getMethod()); } diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClientTest.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClientTest.java index dce5771ce05..5ef46e59e4a 100644 --- a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClientTest.java +++ b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JClientTest.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.ethereum.executionclient.web3j; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -272,6 +273,40 @@ void shouldNotUpdateAvailabilityWhenNonCriticalMethodFailsWithErrorResponse( verifyNoInteractions(executionClientEventsPublisher); } + @ParameterizedTest + @MethodSource("getClientInstances") + void shouldDecodeJsonRpcErrorCodesCorrectly(final ClientFactory clientFactory) throws Exception { + final Web3JClient client = clientFactory.create(eventLog, executionClientEventsPublisher); + Request request = createRequest(client); + + // Create a response with a specific JSON-RPC error + VoidResponse errorResponse = new VoidResponse(); + Error rpcError = + new Error( + JsonRpcErrorCodes.INVALID_PARAMS.getErrorCode(), + "engine_newPayload method has been called with invalid parameters"); + errorResponse.setError(rpcError); + + when(client.getWeb3jService().sendAsync(request, VoidResponse.class)) + .thenReturn(SafeFuture.completedFuture(errorResponse)); + + final SafeFuture> result = client.doRequest(request, DEFAULT_TIMEOUT); + Waiter.waitFor(result); + + SafeFutureAssert.assertThatSafeFuture(result).isCompleted(); + final Response response = SafeFutureAssert.safeJoin(result); + + assertThat(response.getErrorMessage()) + .isEqualTo( + String.format( + "JSON-RPC error: %s (%d): %s", + JsonRpcErrorCodes.INVALID_PARAMS.getDescription(), + JsonRpcErrorCodes.INVALID_PARAMS.getErrorCode(), + "engine_newPayload method has been called with invalid parameters")); + + verify(eventLog).executionClientRequestFailed(any(Exception.class), eq(false)); + } + private static Request createRequest(final Web3JClient client) { return new Request<>("test", new ArrayList<>(), client.getWeb3jService(), VoidResponse.class); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequest.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequest.java index 1f7d270fe8c..d4ffaaf8859 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequest.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequest.java @@ -33,12 +33,12 @@ public class WithdrawalRequest protected WithdrawalRequest( final WithdrawalRequestSchema schema, final Bytes20 sourceAddress, - final BLSPublicKey validatorPublicKey, + final BLSPublicKey validatorPubkey, final UInt64 amount) { super( schema, SszByteVector.fromBytes(sourceAddress.getWrappedBytes()), - new SszPublicKey(validatorPublicKey), + new SszPublicKey(validatorPubkey), SszUInt64.of(amount)); } @@ -50,7 +50,7 @@ public Bytes20 getSourceAddress() { return new Bytes20(getField0().getBytes()); } - public BLSPublicKey getValidatorPublicKey() { + public BLSPublicKey getValidatorPubkey() { return getField1().getBLSPublicKey(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequestSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequestSchema.java index c985b9cdda2..6aad998cd30 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequestSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/WithdrawalRequestSchema.java @@ -37,8 +37,8 @@ public WithdrawalRequestSchema() { } public WithdrawalRequest create( - final Bytes20 sourceAddress, final BLSPublicKey validatorPublicKey, final UInt64 amount) { - return new WithdrawalRequest(this, sourceAddress, validatorPublicKey, amount); + final Bytes20 sourceAddress, final BLSPublicKey validatorPubkey, final UInt64 amount) { + return new WithdrawalRequest(this, sourceAddress, validatorPubkey, amount); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java index 3223e6cca36..7815a5f46f3 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java @@ -253,11 +253,11 @@ public void processWithdrawalRequests( } final Optional maybeValidatorIndex = - validatorsUtil.getValidatorIndex(state, withdrawalRequest.getValidatorPublicKey()); + validatorsUtil.getValidatorIndex(state, withdrawalRequest.getValidatorPubkey()); if (maybeValidatorIndex.isEmpty()) { LOG.debug( "process_withdrawal_request: no matching validator for public key {}", - withdrawalRequest.getValidatorPublicKey().toAbbreviatedString()); + withdrawalRequest.getValidatorPubkey().toAbbreviatedString()); return; } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/WithdrawalRequestTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/WithdrawalRequestTest.java index 4efda405578..f43b29088eb 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/WithdrawalRequestTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/WithdrawalRequestTest.java @@ -31,15 +31,15 @@ class WithdrawalRequestTest { new DataStructureUtil(TestSpecFactory.createMinimal(SpecMilestone.ELECTRA)); private final WithdrawalRequestSchema withdrawalRequestSchema = new WithdrawalRequestSchema(); private final Bytes20 sourceAddress = dataStructureUtil.randomBytes20(); - private final BLSPublicKey validatorPublicKey = dataStructureUtil.randomPublicKey(); + private final BLSPublicKey validatorPubkey = dataStructureUtil.randomPublicKey(); private final UInt64 amount = dataStructureUtil.randomUInt64(); @Test public void objectEquality() { final WithdrawalRequest withdrawalRequest1 = - withdrawalRequestSchema.create(sourceAddress, validatorPublicKey, amount); + withdrawalRequestSchema.create(sourceAddress, validatorPubkey, amount); final WithdrawalRequest withdrawalRequest2 = - withdrawalRequestSchema.create(sourceAddress, validatorPublicKey, amount); + withdrawalRequestSchema.create(sourceAddress, validatorPubkey, amount); assertThat(withdrawalRequest1).isEqualTo(withdrawalRequest2); } @@ -47,16 +47,16 @@ public void objectEquality() { @Test public void objectAccessorMethods() { final WithdrawalRequest withdrawalRequest = - withdrawalRequestSchema.create(sourceAddress, validatorPublicKey, amount); + withdrawalRequestSchema.create(sourceAddress, validatorPubkey, amount); assertThat(withdrawalRequest.getSourceAddress()).isEqualTo(sourceAddress); - assertThat(withdrawalRequest.getValidatorPublicKey()).isEqualTo(validatorPublicKey); + assertThat(withdrawalRequest.getValidatorPubkey()).isEqualTo(validatorPubkey); } @Test public void roundTripSSZ() { final WithdrawalRequest withdrawalRequest = - withdrawalRequestSchema.create(sourceAddress, validatorPublicKey, amount); + withdrawalRequestSchema.create(sourceAddress, validatorPubkey, amount); final Bytes sszBytes = withdrawalRequest.sszSerialize(); final WithdrawalRequest deserializedObject = withdrawalRequestSchema.sszDeserialize(sszBytes); diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java index 8a33c73de79..dad9ebc47d2 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java @@ -179,7 +179,7 @@ public void processWithdrawalRequests_ExitForAbsentValidator_DoesNothing() { preState.getValidators().stream() .filter( validator -> - validator.getPublicKey().equals(withdrawalRequest.getValidatorPublicKey()))) + validator.getPublicKey().equals(withdrawalRequest.getValidatorPubkey()))) .isEmpty(); final BeaconStateElectra postState =