diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java index 3a4ca71002b..29f60dd74a6 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java @@ -58,6 +58,7 @@ public static BlockHeader createBlockHeader( null, null, null, + null, blockHeaderFunctions); } } diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json index 74e9347df47..7405dbae026 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json @@ -27,8 +27,9 @@ "transactions": [], "withdrawals": [], "depositReceipts": [], + "exits": [], "blockNumber": "0x2", - "blockHash": "0xc8255831601171a628ef17f6601d3d1d30ff9b382e77592ed1af32354f6dafbb", + "blockHash": "0x194d190af2a85c418947fecca405eb168c832481f33f618b0c36326ba65d4767", "blobGasUsed": "0x0", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" }, diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json index b3b8166af22..1f63502a891 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json @@ -22,8 +22,9 @@ "depositReceipts" : [ {"amount":"0x773594000","index":"0x0","pubkey":"0x96a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9","signature":"0xb1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9","withdrawalCredentials":"0x003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef2"} ], + "exits": [], "blockNumber": "0x2", - "blockHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", + "blockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "receiptsRoot": "0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9", "blobGasUsed": "0x0" }, @@ -37,7 +38,7 @@ "id": 67, "result": { "status": "VALID", - "latestValidHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", + "latestValidHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "validationError": null } }, diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json index ef779c959f5..cf4554ddfdc 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json @@ -4,8 +4,8 @@ "method": "engine_forkchoiceUpdatedV3", "params": [ { - "headBlockHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", - "safeBlockHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", + "headBlockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", + "safeBlockHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" }, { @@ -24,10 +24,10 @@ "result": { "payloadStatus": { "status": "VALID", - "latestValidHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", + "latestValidHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "validationError": null }, - "payloadId": "0x282643db882670cf" + "payloadId": "0x282643f559414ecf" } }, "statusCode" : 200 diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json index 7a251d599af..7f2a1884518 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json @@ -3,7 +3,7 @@ "jsonrpc": "2.0", "method": "engine_getPayloadV4", "params": [ - "0x282643db882670cf" + "0x282643f559414ecf" ], "id": 67 }, @@ -12,7 +12,7 @@ "id": 67, "result": { "executionPayload": { - "parentHash": "0xddb65a684b9b8980b6231ee0e388566c10a9c4583bbddf16f8d68bbc0b8ed965", + "parentHash": "0x84e13dc50074ba4be5841bd7e453a87b6f77261a8907518a78f3de8c9d877ee7", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "stateRoot": "0x14208ac0e218167936e220b72d5d5887a963cb858ea2f2d268518f014a3da3fa", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -27,8 +27,9 @@ "transactions": [], "withdrawals": [], "depositReceipts": [], + "exits": [], "blockNumber": "0x3", - "blockHash": "0xf1e7093b5d229885caab11a3acb95412af80f9077b742020a8014cf81c8c75f2", + "blockHash": "0xec4741580be2d83cde0dcd6346a67a71636d915f5da592f39d4470aecef72020", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "blobGasUsed": "0x0" }, diff --git a/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java index 2f6924112b7..f0f8b571c0e 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java @@ -192,6 +192,7 @@ public void miningParametersBlockPeriodSecondsIsUpdatedOnTransition() { null, null, null, + null, getBlockHeaderFunctions()); final Block block1 = new Block(header1, BlockBody.empty()); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java index 631149516f4..6bd6f5d2ec0 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java @@ -221,6 +221,7 @@ public void miningParametersBlockPeriodSecondsIsUpdatedOnTransition() { null, null, null, + null, new CliqueBlockHeaderFunctions()); final Block block1 = new Block(header1, BlockBody.empty()); diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 321ef8a3b82..78a71be5b72 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -213,7 +213,7 @@ private void setSyncTarget() { mock(EthPeer.class), new org.hyperledger.besu.ethereum.core.BlockHeader( null, null, null, null, null, null, null, null, 1, 1, 1, 1, null, null, null, 1, null, - null, null, null, null, null)); + null, null, null, null, null, null)); } private void clearSyncTarget() { diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java index b3c462656ac..2273b5a0f5a 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java @@ -55,6 +55,7 @@ public class ProposalTest { Collections.emptyList(), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.empty(), Optional.empty())); @Test diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java index a57cb990c81..249de3f239d 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java @@ -38,5 +38,6 @@ public enum JsonRpcResponseKey { TRANSACTION_ROOT, BASEFEE, WITHDRAWALS_ROOT, - DEPOSITS_ROOT + DEPOSITS_ROOT, + EXITS_ROOT } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java index 039aac39ff5..6f5881097ae 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java @@ -18,6 +18,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.COINBASE; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.DEPOSITS_ROOT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.DIFFICULTY; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.EXITS_ROOT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.EXTRA_DATA; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.GAS_LIMIT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.GAS_USED; @@ -106,6 +107,7 @@ public JsonRpcResponse response( values.containsKey(WITHDRAWALS_ROOT) ? hash(values.get(WITHDRAWALS_ROOT)) : null; final Hash depositsRoot = values.containsKey(DEPOSITS_ROOT) ? hash(values.get(DEPOSITS_ROOT)) : null; + final Hash exitsRoot = values.containsKey(EXITS_ROOT) ? hash(values.get(EXITS_ROOT)) : null; final List ommers = new ArrayList<>(); final BlockHeader header = @@ -131,6 +133,7 @@ public JsonRpcResponse response( null, // ToDo 4844: set with the value of excess_blob_gas field null, // TODO 4788: set with the value of the parent beacon block root field depositsRoot, + exitsRoot, blockHeaderFunctions); return new JsonRpcSuccessResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java index 361f3c8f64d..4e4da9b2dd6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java @@ -21,6 +21,7 @@ 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.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.DepositsValidatorProvider.getDepositsValidator; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.ValidatorExitsValidatorProvider.getValidatorExitsValidator; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.WithdrawalsValidatorProvider.getWithdrawalsValidator; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; @@ -36,6 +37,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitParameter; 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; @@ -50,6 +52,7 @@ import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; @@ -167,6 +170,17 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) return new JsonRpcErrorResponse(reqId, new JsonRpcError(INVALID_PARAMS, "Invalid deposits")); } + final Optional> maybeExits = + Optional.ofNullable(blockParam.getExits()) + .map( + exits -> + exits.stream().map(ValidatorExitParameter::toValidatorExit).collect(toList())); + if (!getValidatorExitsValidator( + protocolSchedule.get(), blockParam.getTimestamp(), blockParam.getBlockNumber()) + .validateValidatorExitParameter(maybeExits)) { + return new JsonRpcErrorResponse(reqId, new JsonRpcError(INVALID_PARAMS, "Invalid exits")); + } + if (mergeContext.get().isSyncing()) { LOG.debug("We are syncing"); return respondWith(reqId, blockParam, null, SYNCING); @@ -235,6 +249,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) : BlobGas.fromHexString(blockParam.getExcessBlobGas()), maybeParentBeaconBlockRoot.orElse(null), maybeDeposits.map(BodyValidation::depositsRoot).orElse(null), + maybeExits.map(BodyValidation::exitsRoot).orElse(null), headerFunctions); // ensure the block hash matches the blockParam hash @@ -297,7 +312,12 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final var block = new Block( newBlockHeader, - new BlockBody(transactions, Collections.emptyList(), maybeWithdrawals, maybeDeposits)); + new BlockBody( + transactions, + Collections.emptyList(), + maybeWithdrawals, + maybeDeposits, + maybeExits)); if (maybeParentHeader.isEmpty()) { LOG.atDebug() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ValidatorExitsValidatorProvider.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ValidatorExitsValidatorProvider.java new file mode 100644 index 00000000000..8e3dd2fc736 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ValidatorExitsValidatorProvider.java @@ -0,0 +1,45 @@ +/* + * 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 org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator; + +import java.util.Optional; + +public class ValidatorExitsValidatorProvider { + + static ValidatorExitsValidator getValidatorExitsValidator( + final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { + + final BlockHeader blockHeader = + BlockHeaderBuilder.createDefault() + .timestamp(blockTimestamp) + .number(blockNumber) + .buildBlockHeader(); + return getValidatorExitsValidator(protocolSchedule.getByBlockHeader(blockHeader)); + } + + private static ValidatorExitsValidator getValidatorExitsValidator( + final ProtocolSpec protocolSchedule) { + return Optional.ofNullable(protocolSchedule) + .map(ProtocolSpec::getExitsValidator) + .orElseGet(ValidatorExitsValidator.ProhibitedExits::new); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java index 355f7b218c7..0e9795126dd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java @@ -44,6 +44,7 @@ public class EnginePayloadParameter { private final Long blobGasUsed; private final String excessBlobGas; private final List deposits; + private final List exits; /** * Creates an instance of EnginePayloadParameter. @@ -66,6 +67,7 @@ public class EnginePayloadParameter { * @param blobGasUsed QUANTITY, 64 Bits * @param excessBlobGas QUANTITY, 64 Bits * @param deposits List of deposit parameters. + * @param exits List of exits parameters. */ @JsonCreator public EnginePayloadParameter( @@ -86,7 +88,8 @@ public EnginePayloadParameter( @JsonProperty("withdrawals") final List withdrawals, @JsonProperty("blobGasUsed") final UnsignedLongParameter blobGasUsed, @JsonProperty("excessBlobGas") final String excessBlobGas, - @JsonProperty("depositReceipts") final List deposits) { + @JsonProperty("depositReceipts") final List deposits, + @JsonProperty("exits") final List exits) { this.blockHash = blockHash; this.parentHash = parentHash; this.feeRecipient = feeRecipient; @@ -105,6 +108,7 @@ public EnginePayloadParameter( this.blobGasUsed = blobGasUsed == null ? null : blobGasUsed.getValue(); this.excessBlobGas = excessBlobGas; this.deposits = deposits; + this.exits = exits; } public Hash getBlockHash() { @@ -178,4 +182,8 @@ public String getExcessBlobGas() { public List getDeposits() { return deposits; } + + public List getExits() { + return exits; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameter.java new file mode 100644 index 00000000000..efa0c809a27 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameter.java @@ -0,0 +1,92 @@ +/* + * 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.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ValidatorExit; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.vertx.core.json.JsonObject; + +public class ValidatorExitParameter { + + private final String sourceAddress; + private final String validatorPubKey; + + @JsonCreator + public ValidatorExitParameter( + @JsonProperty("sourceAddress") final String sourceAddress, + @JsonProperty("pubkey") final String validatorPubKey) { + this.sourceAddress = sourceAddress; + this.validatorPubKey = validatorPubKey; + } + + public static ValidatorExitParameter fromValidatorExit(final ValidatorExit exit) { + return new ValidatorExitParameter( + exit.getSourceAddress().toHexString(), exit.getValidatorPubKey().toHexString()); + } + + public ValidatorExit toValidatorExit() { + return new ValidatorExit( + Address.fromHexString(sourceAddress), BLSPublicKey.fromHexString(validatorPubKey)); + } + + public JsonObject asJsonObject() { + return new JsonObject() + .put("sourceAddress", sourceAddress) + .put("validatorPubKey", validatorPubKey); + } + + @JsonGetter + public String getSourceAddress() { + return sourceAddress; + } + + @JsonGetter + public String getValidatorPubKey() { + return validatorPubKey; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ValidatorExitParameter that = (ValidatorExitParameter) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(validatorPubKey, that.validatorPubKey); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, validatorPubKey); + } + + @Override + public String toString() { + return "DepositParameter{" + + "sourceAddress='" + + sourceAddress + + '\'' + + ", validatorPubKey='" + + validatorPubKey + + '\'' + + '}'; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java index 4bb3add6ac4..dc8d90bd46f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java @@ -173,6 +173,7 @@ public EngineGetPayloadResultV4 payloadTransactionCompleteV4( txs, blockWithReceipts.getBlock().getBody().getWithdrawals(), blockWithReceipts.getBlock().getBody().getDeposits(), + blockWithReceipts.getBlock().getBody().getExits(), Quantity.create(blockValue), blobsBundleV1); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java index 721438a8746..b88049bee45 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java @@ -15,9 +15,11 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import java.util.List; @@ -41,9 +43,10 @@ public EngineGetPayloadResultV4( final List transactions, final Optional> withdrawals, final Optional> deposits, + final Optional> exits, final String blockValue, final BlobsBundleV1 blobsBundle) { - this.executionPayload = new PayloadResult(header, transactions, withdrawals, deposits); + this.executionPayload = new PayloadResult(header, transactions, withdrawals, deposits, exits); this.blockValue = blockValue; this.blobsBundle = blobsBundle; this.shouldOverrideBuilder = false; @@ -91,12 +94,14 @@ public static class PayloadResult { protected final List transactions; private final List withdrawals; private final List deposits; + private final List exits; public PayloadResult( final BlockHeader header, final List transactions, final Optional> withdrawals, - final Optional> deposits) { + final Optional> deposits, + final Optional> exits) { this.blockNumber = Quantity.create(header.getNumber()); this.blockHash = header.getHash().toString(); this.parentHash = header.getParentHash().toString(); @@ -124,6 +129,14 @@ public PayloadResult( .map( ds -> ds.stream().map(DepositParameter::fromDeposit).collect(Collectors.toList())) .orElse(null); + this.exits = + exits + .map( + ds -> + ds.stream() + .map(ValidatorExitParameter::fromValidatorExit) + .collect(Collectors.toList())) + .orElse(null); this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO); this.excessBlobGas = header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO); @@ -206,6 +219,11 @@ public List getDeposits() { return deposits; } + @JsonGetter(value = "exits") + public List getExits() { + return exits; + } + @JsonGetter(value = "feeRecipient") @JsonInclude(JsonInclude.Include.NON_NULL) public String getFeeRecipient() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java index cd1e890fd41..2d08bdfe593 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java @@ -222,6 +222,7 @@ private Object createFakeBlock(final Long height) { null, null, null, + null, null), new BlockBody( List.of( @@ -259,6 +260,7 @@ private Object createEmptyBlock(final Long height) { null, null, null, + null, null), new BlockBody(List.of(), List.of()))); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java index 048bc8d9388..e34a04651a9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java @@ -146,6 +146,7 @@ public class EthGetTransactionReceiptTest { null, Optional.empty(), null, + null, true, true); private final ProtocolSpec statusTransactionTypeSpec = @@ -175,6 +176,7 @@ public class EthGetTransactionReceiptTest { null, Optional.empty(), null, + null, true, true); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java index fd2f02ba024..777ecaeefc7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java @@ -104,6 +104,7 @@ public AbstractEngineGetPayloadTest() { Collections.emptyList(), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.empty(), Optional.empty())); private static final Block mockBlockWithDeposits = new Block( @@ -112,6 +113,7 @@ public AbstractEngineGetPayloadTest() { Collections.emptyList(), Collections.emptyList(), Optional.empty(), + Optional.of(Collections.emptyList()), Optional.of(Collections.emptyList()))); protected static final BlockWithReceipts mockBlockWithReceiptsAndWithdrawals = new BlockWithReceipts(mockBlockWithWithdrawals, Collections.emptyList()); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java index 93d2f453ee5..e7c1a23ea61 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java @@ -41,6 +41,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitParameter; 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; @@ -54,6 +55,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; @@ -127,6 +129,7 @@ public void shouldReturnValid() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -140,7 +143,10 @@ public void shouldReturnValid() { public void shouldReturnInvalidOnBlockExecutionError() { BlockHeader mockHeader = setupValidPayload( - new BlockProcessingResult("error 42"), Optional.empty(), Optional.empty()); + new BlockProcessingResult("error 42"), + Optional.empty(), + Optional.empty(), + Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -155,7 +161,8 @@ public void shouldReturnInvalidOnBlockExecutionError() { @Test public void shouldReturnAcceptedOnLatestValidAncestorEmpty() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -173,7 +180,8 @@ public void shouldReturnAcceptedOnLatestValidAncestorEmpty() { @Test public void shouldReturnSuccessOnAlreadyPresent() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); Block mockBlock = new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); @@ -186,7 +194,8 @@ public void shouldReturnSuccessOnAlreadyPresent() { @Test public void shouldReturnInvalidWithLatestValidHashIsABadBlock() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); Hash latestValidHash = Hash.hash(Bytes32.fromHexStringLenient("0xcafebabe")); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); @@ -208,6 +217,7 @@ public void shouldNotReturnInvalidOnStorageException() { setupValidPayload( new BlockProcessingResult(Optional.empty(), new StorageException("database bedlam")), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -224,6 +234,7 @@ public void shouldNotReturnInvalidOnHandledMerkleTrieException() { setupValidPayload( new BlockProcessingResult(Optional.empty(), new MerkleTrieException("missing leaf")), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() @@ -238,7 +249,8 @@ public void shouldNotReturnInvalidOnHandledMerkleTrieException() { @Test public void shouldNotReturnInvalidOnThrownMerkleTrieException() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -255,7 +267,8 @@ public void shouldNotReturnInvalidOnThrownMerkleTrieException() { @Test public void shouldReturnInvalidBlockHashOnBadHashParameter() { - BlockHeader mockHeader = spy(createBlockHeader(Optional.empty(), Optional.empty())); + BlockHeader mockHeader = + spy(createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty())); lenient() .when(mergeCoordinator.getLatestValidAncestor(mockHeader.getBlockHash())) .thenReturn(Optional.empty()); @@ -272,7 +285,8 @@ public void shouldReturnInvalidBlockHashOnBadHashParameter() { @Test public void shouldCheckBlockValidityBeforeCheckingByHashForExisting() { - BlockHeader realHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader realHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); BlockHeader paramHeader = spy(realHeader); when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337")); @@ -286,7 +300,8 @@ public void shouldCheckBlockValidityBeforeCheckingByHashForExisting() { @Test public void shouldReturnInvalidOnMalformedTransactions() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeCoordinator.getLatestValidAncestor(any(Hash.class))) .thenReturn(Optional.of(mockHash)); @@ -301,7 +316,8 @@ public void shouldReturnInvalidOnMalformedTransactions() { @Test public void shouldRespondWithSyncingDuringForwardSync() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeContext.isSyncing()).thenReturn(Boolean.TRUE); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); @@ -314,7 +330,8 @@ public void shouldRespondWithSyncingDuringForwardSync() { @Test public void shouldRespondWithSyncingDuringBackwardsSync() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeCoordinator.appendNewPayloadToSync(any())) .thenReturn(CompletableFuture.completedFuture(null)); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); @@ -328,7 +345,8 @@ public void shouldRespondWithSyncingDuringBackwardsSync() { @Test public void shouldRespondWithInvalidIfExtraDataIsNull() { - BlockHeader realHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader realHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); BlockHeader paramHeader = spy(realHeader); when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337")); when(paramHeader.getExtraData().toHexString()).thenReturn(null); @@ -345,7 +363,8 @@ public void shouldRespondWithInvalidIfExtraDataIsNull() { @Test public void shouldReturnInvalidWhenBadBlock() { when(mergeCoordinator.isBadBlock(any(Hash.class))).thenReturn(true); - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); when(protocolSpec.getWithdrawalsValidator()) .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); @@ -363,6 +382,7 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -383,14 +403,15 @@ protected JsonRpcResponse resp(final EnginePayloadParameter payload) { protected EnginePayloadParameter mockEnginePayload( final BlockHeader header, final List txs) { - return mockEnginePayload(header, txs, null, null); + return mockEnginePayload(header, txs, null, null, null); } protected EnginePayloadParameter mockEnginePayload( final BlockHeader header, final List txs, final List withdrawals, - final List deposits) { + final List deposits, + final List exits) { return new EnginePayloadParameter( header.getHash(), header.getParentHash(), @@ -409,15 +430,17 @@ protected EnginePayloadParameter mockEnginePayload( withdrawals, header.getBlobGasUsed().map(UnsignedLongParameter::new).orElse(null), header.getExcessBlobGas().map(BlobGas::toHexString).orElse(null), - deposits); + deposits, + exits); } protected BlockHeader setupValidPayload( final BlockProcessingResult value, final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDeposits, + final Optional> maybeExits) { - BlockHeader mockHeader = createBlockHeader(maybeWithdrawals, maybeDeposits); + BlockHeader mockHeader = createBlockHeader(maybeWithdrawals, maybeDeposits, maybeExits); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); // when(blockchain.getBlockHeader(mockHeader.getParentHash())) // .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -450,13 +473,15 @@ protected JsonRpcError fromErrorResp(final JsonRpcResponse resp) { protected BlockHeader createBlockHeader( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { - return createBlockHeaderFixture(maybeWithdrawals, maybeDeposits).buildHeader(); + final Optional> maybeDeposits, + final Optional> maybeExits) { + return createBlockHeaderFixture(maybeWithdrawals, maybeDeposits, maybeExits).buildHeader(); } protected BlockHeaderTestFixture createBlockHeaderFixture( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDeposits, + final Optional> maybeExits) { BlockHeader parentBlockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); return new BlockHeaderTestFixture() diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java index 1617afdac30..eadea3fa05a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java @@ -254,6 +254,7 @@ private BlockHeader createBlockHeader(final Hash blockHash, final long blockNumb null, null, null, + null, new BlockHeaderFunctions() { @Override public Hash hash(final BlockHeader header) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java index 9c3cd8f45ff..154c25df45b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java @@ -181,6 +181,7 @@ public void shouldReturnWithdrawalNullWhenBlockIsPreShanghai() { List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.empty(), + Optional.empty(), Optional.empty()); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(preShanghaiBlockBody)); when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(preShanghaiBlockBody2)); @@ -212,6 +213,7 @@ public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); final BlockBody shanghaiBlockBody2 = @@ -219,6 +221,7 @@ public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal2)), + Optional.empty(), Optional.empty()); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); when(blockchain.getBlockBody(blockHash2)).thenReturn(Optional.of(shanghaiBlockBody2)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java index 16964458648..da3fca2e455 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java @@ -180,6 +180,7 @@ public void shouldReturnNullForWithdrawalsWhenBlockIsPreShanghai() { List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.empty(), + Optional.empty(), Optional.empty()); when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(preShanghaiBlockBody)); @@ -215,6 +216,7 @@ public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); final BlockBody shanghaiBlockBody2 = @@ -222,6 +224,7 @@ public void shouldReturnWithdrawalsWhenBlockIsPostShanghai() { List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal2)), + Optional.empty(), Optional.empty()); when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(130)); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); @@ -253,6 +256,7 @@ public void shouldNotContainTrailingNullForBlocksPastTheCurrentHead() { new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(123)); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); @@ -277,18 +281,21 @@ public void shouldReturnUpUntilHeadWhenStartBlockPlusCountEqualsHeadNumber() { List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); final BlockBody shanghaiBlockBody2 = new BlockBody( List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); final BlockBody shanghaiBlockBody3 = new BlockBody( List.of(new TransactionTestFixture().createTransaction(sig.generateKeyPair())), Collections.emptyList(), Optional.of(List.of(withdrawal)), + Optional.empty(), Optional.empty()); when(blockchain.getChainHeadBlockNumber()).thenReturn(Long.valueOf(125)); when(blockchain.getBlockBody(blockHash1)).thenReturn(Optional.of(shanghaiBlockBody)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java index 1e19bc3fd5d..9813502d47a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java @@ -130,6 +130,7 @@ public void shouldReturnBlockForKnownPayloadId() { List.of(blobTx), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.of(Collections.emptyList()), Optional.empty())), List.of(blobReceipt)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java index 3a0ee35e4d2..9cfa8954b98 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java @@ -132,6 +132,7 @@ public void shouldReturnBlockForKnownPayloadId() { List.of(blobTx), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.of(Collections.emptyList()), Optional.of(Collections.emptyList()))), List.of(blobReceipt)); @@ -147,6 +148,7 @@ public void shouldReturnBlockForKnownPayloadId() { final EngineGetPayloadResultV4 res = (EngineGetPayloadResultV4) r.getResult(); assertThat(res.getExecutionPayload().getWithdrawals()).isNotNull(); assertThat(res.getExecutionPayload().getDeposits()).isNotNull(); + assertThat(res.getExecutionPayload().getExits()).isNotNull(); assertThat(res.getExecutionPayload().getHash()) .isEqualTo(header.getHash().toString()); assertThat(res.getBlockValue()).isEqualTo(Quantity.create(0)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java index f7f2cba533f..b05fa5042b2 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java @@ -81,11 +81,13 @@ public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.of(withdrawals), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawalsParam, null)); + var resp = + resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawalsParam, null, null)); assertValidResponse(mockHeader, resp); } @@ -99,11 +101,13 @@ public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawals, null)); + var resp = + resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawals, null, null)); assertValidResponse(mockHeader, resp); } @@ -118,9 +122,11 @@ public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited( var resp = resp( mockEnginePayload( - createBlockHeader(Optional.of(Collections.emptyList()), Optional.empty()), + createBlockHeader( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()), Collections.emptyList(), withdrawals, + null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); @@ -132,11 +138,12 @@ public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited( public void shouldValidateBlobGasUsedCorrectly() { // V2 should return error if non-null blobGasUsed BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .blobGasUsed(100L) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); assertThat(jsonRpcError.getData()).isEqualTo("non-null BlobGasUsed pre-cancun"); @@ -147,11 +154,12 @@ public void shouldValidateBlobGasUsedCorrectly() { public void shouldValidateExcessBlobGasCorrectly() { // V2 should return error if non-null ExcessBlobGas BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(BlobGas.MAX_BLOB_GAS) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); @@ -168,9 +176,10 @@ public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() { var resp = resp( mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.empty()), + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), Collections.emptyList(), withdrawals, + null, null)); assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java index 8f319c27cf0..09dded51cab 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java @@ -47,6 +47,7 @@ import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; @@ -133,9 +134,10 @@ public void shouldValidVersionedHash_whenListIsEmpty() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); final EnginePayloadParameter payload = - mockEnginePayload(mockHeader, Collections.emptyList(), null, null); + mockEnginePayload(mockHeader, Collections.emptyList(), null, null, null); ValidationResult res = method.validateParameters( @@ -148,7 +150,8 @@ public void shouldValidVersionedHash_whenListIsEmpty() { @Override protected BlockHeader createBlockHeader( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDeposits, + final Optional> maybeExits) { BlockHeader parentBlockHeader = new BlockHeaderTestFixture() .baseFeePerGas(Wei.ONE) @@ -185,12 +188,13 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { public void shouldValidateBlobGasUsedCorrectly() { // V3 must return error if null blobGasUsed BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(BlobGas.MAX_BLOB_GAS) .blobGasUsed(null) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); @@ -203,12 +207,13 @@ public void shouldValidateBlobGasUsedCorrectly() { public void shouldValidateExcessBlobGasCorrectly() { // V3 must return error if null excessBlobGas BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(null) .blobGasUsed(100L) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); @@ -229,6 +234,7 @@ public void shouldRejectTransactionsWithFullBlobs() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); var resp = resp(mockEnginePayload(mockHeader, transactions)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java index 9943a10ba99..54dbc3d8e6a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitTestFixture.VALIDATOR_EXIT_PARAMETER_1; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; @@ -33,14 +34,17 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; +import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import java.util.Collections; @@ -93,13 +97,14 @@ public void shouldReturnValidIfDepositsIsNull_WhenDepositsProhibited() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); when(mergeCoordinator.getLatestValidAncestor(mockHeader)) .thenReturn(Optional.of(mockHeader.getHash())); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, deposits)); + var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, deposits, null)); assertValidResponse(mockHeader, resp); } @@ -114,10 +119,11 @@ public void shouldReturnInvalidIfDepositsIsNull_WhenDepositsAllowed() { var resp = resp( mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.empty()), + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), Collections.emptyList(), null, - deposits)); + deposits, + null)); assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); verify(engineCallListener, times(1)).executionEngineCalled(); @@ -133,12 +139,14 @@ public void shouldReturnValidIfDepositsIsNotNull_WhenDepositsAllowed() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), - Optional.of(deposits)); + Optional.of(deposits), + Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); when(mergeCoordinator.getLatestValidAncestor(mockHeader)) .thenReturn(Optional.of(mockHeader.getHash())); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, depositsParam)); + var resp = + resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, depositsParam, null)); assertValidResponse(mockHeader, resp); } @@ -153,10 +161,96 @@ public void shouldReturnInvalidIfDepositsIsNotNull_WhenDepositsProhibited() { var resp = resp( mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.of(Collections.emptyList())), + createBlockHeader( + Optional.empty(), Optional.of(Collections.emptyList()), Optional.empty()), + Collections.emptyList(), + null, + deposits, + null)); + + final JsonRpcError jsonRpcError = fromErrorResp(resp); + assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfExitsIsNull_WhenExitsProhibited() { + when(protocolSpec.getExitsValidator()) + .thenReturn(new ValidatorExitsValidator.ProhibitedExits()); + + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), + Optional.empty(), + Optional.empty(), + Optional.empty()); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + + var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, null, null)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void shouldReturnInvalidIfExitsIsNull_WhenExitsAllowed() { + when(protocolSpec.getExitsValidator()).thenReturn(new ValidatorExitsValidator.AllowedExits()); + + var resp = + resp( + mockEnginePayload( + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), Collections.emptyList(), null, - deposits)); + null, + null)); + + assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfExitsIsNotNull_WhenExitsAllowed() { + final List validatorExitsParams = List.of(VALIDATOR_EXIT_PARAMETER_1); + final List validatorExits = + List.of(VALIDATOR_EXIT_PARAMETER_1.toValidatorExit()); + when(protocolSpec.getExitsValidator()).thenReturn(new ValidatorExitsValidator.AllowedExits()); + + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), + Optional.empty(), + Optional.empty(), + Optional.of(validatorExits)); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + var resp = + resp( + mockEnginePayload( + mockHeader, Collections.emptyList(), null, null, validatorExitsParams)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void shouldReturnInvalidIfExitsIsNotNull_WhenExitsProhibited() { + final List validatorExits = List.of(); + when(protocolSpec.getExitsValidator()) + .thenReturn(new ValidatorExitsValidator.ProhibitedExits()); + + var resp = + resp( + mockEnginePayload( + createBlockHeader( + Optional.empty(), Optional.empty(), Optional.of(Collections.emptyList())), + Collections.emptyList(), + null, + null, + validatorExits)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); @@ -166,7 +260,8 @@ public void shouldReturnInvalidIfDepositsIsNotNull_WhenDepositsProhibited() { @Override protected BlockHeader createBlockHeader( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDeposits, + final Optional> maybeExits) { BlockHeader parentBlockHeader = new BlockHeaderTestFixture() .baseFeePerGas(Wei.ONE) @@ -185,6 +280,7 @@ protected BlockHeader createBlockHeader( .excessBlobGas(BlobGas.ZERO) .blobGasUsed(0L) .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)) + .exitsRoot(maybeExits.map(BodyValidation::exitsRoot).orElse(null)) .parentBeaconBlockRoot( maybeParentBeaconBlockRoot.isPresent() ? maybeParentBeaconBlockRoot : null) .buildHeader(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameterTest.java new file mode 100644 index 00000000000..a3a7c0d615a --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitParameterTest.java @@ -0,0 +1,51 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ValidatorExitTestFixture.VALIDATOR_EXIT_PARAMETER_1; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ValidatorExit; + +import org.junit.jupiter.api.Test; + +public class ValidatorExitParameterTest { + + @Test + public void toValidatorExit() { + ValidatorExit expected = + new ValidatorExit( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + assertThat(VALIDATOR_EXIT_PARAMETER_1.toValidatorExit()).isEqualTo(expected); + } + + @Test + public void fromValidatorExit() { + ValidatorExit validatorExit = + new ValidatorExit( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + assertThat(ValidatorExitParameter.fromValidatorExit(validatorExit)) + .isEqualTo(VALIDATOR_EXIT_PARAMETER_1); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitTestFixture.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitTestFixture.java new file mode 100644 index 00000000000..6989ef1fb3c --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ValidatorExitTestFixture.java @@ -0,0 +1,28 @@ +/* + * 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; + +public class ValidatorExitTestFixture { + + public static final ValidatorExitParameter VALIDATOR_EXIT_PARAMETER_1 = + new ValidatorExitParameter( + "0x814fae9f487206471b6b0d713cd51a2d35980000", + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"); + static final ValidatorExitParameter VALIDATOR_EXIT_PARAMETER_2 = + new ValidatorExitParameter( + "0x758b8178a9a4b7206d1f648c4a77c515cbac7000", + "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243"); +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java index 71cbc26515b..7a543093dc6 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java @@ -119,6 +119,7 @@ public void setup() { null, null, null, + null, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList()); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java index 5ff026e62ca..cc39639243a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java @@ -110,6 +110,7 @@ public void setup() throws IOException { null, null, null, + null, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.of(fakeHeader)); @@ -285,6 +286,7 @@ private BlockHeader createBlock(final long number, final Optional messag null, null, null, + null, new MainnetBlockHeaderFunctions()); testHash = fakeHeader.getHash(); when(blockchain.getBlockHeader(number)).thenReturn(Optional.of(fakeHeader)); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index 451b337b178..4da6b3970a6 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.DepositDecoder; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; @@ -49,6 +50,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.ValidatorExitsValidator; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator; @@ -251,6 +253,16 @@ protected BlockCreationResult createBlock( throwIfStopped(); + // TODO implement logic to retrieve validator exits from precompile + // https://github.com/hyperledger/besu/issues/6800 + final ValidatorExitsValidator exitsValidator = newProtocolSpec.getExitsValidator(); + Optional> maybeExits = Optional.empty(); + if (exitsValidator instanceof ValidatorExitsValidator.AllowedExits) { + maybeExits = Optional.of(List.of()); + } + + throwIfStopped(); + if (rewardCoinbase && !rewardBeneficiary( disposableWorldState, @@ -285,7 +297,8 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? BodyValidation.withdrawalsRoot(maybeWithdrawals.get()) : null) - .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)); + .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)) + .exitsRoot(maybeExits.map(BodyValidation::exitsRoot).orElse(null)); if (usage != null) { builder.blobGasUsed(usage.used.toLong()).excessBlobGas(usage.excessBlobGas); } @@ -298,7 +311,11 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? maybeWithdrawals : Optional.empty(); final BlockBody blockBody = new BlockBody( - transactionResults.getSelectedTransactions(), ommers, withdrawals, maybeDeposits); + transactionResults.getSelectedTransactions(), + ommers, + withdrawals, + maybeDeposits, + maybeExits); final Block block = new Block(blockHeader, blockBody); operationTracer.traceEndBlock(blockHeader, blockBody); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index ccf47b17f59..d69731e144f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; @@ -132,8 +133,10 @@ private static BlockBody buildBody(final GenesisConfigFile config) { isShanghaiAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); final Optional> deposits = isExperimentalEipsTimeAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); + final Optional> exits = + isPragueAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); - return new BlockBody(emptyList(), emptyList(), withdrawals, deposits); + return new BlockBody(emptyList(), emptyList(), withdrawals, deposits, exits); } public Block getBlock() { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java index 32313820cc8..4d511fd376e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java @@ -63,6 +63,7 @@ public void writeTo(final RLPOutput out) { out.writeList(body.getOmmers(), BlockHeader::writeTo); body.getWithdrawals().ifPresent(withdrawals -> out.writeList(withdrawals, Withdrawal::writeTo)); body.getDeposits().ifPresent(deposits -> out.writeList(deposits, Deposit::writeTo)); + body.getExits().ifPresent(exits -> out.writeList(exits, ValidatorExit::writeTo)); out.endList(); } @@ -76,9 +77,13 @@ public static Block readFrom(final RLPInput in, final BlockHeaderFunctions hashF in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Withdrawal::readFrom)); final Optional> deposits = in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Deposit::readFrom)); + final Optional> exits = + in.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(in.readList(ValidatorExit::readFrom)); in.leaveList(); - return new Block(header, new BlockBody(transactions, ommers, withdrawals, deposits)); + return new Block(header, new BlockBody(transactions, ommers, withdrawals, deposits, exits)); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java index 7defb8eab29..462347a6a06 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java @@ -37,23 +37,27 @@ public class BlockBody implements org.hyperledger.besu.plugin.data.BlockBody { private final List ommers; private final Optional> withdrawals; private final Optional> deposits; + private final Optional> exits; public BlockBody(final List transactions, final List ommers) { this.transactions = transactions; this.ommers = ommers; this.withdrawals = Optional.empty(); this.deposits = Optional.empty(); + this.exits = Optional.empty(); } public BlockBody( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits) { + final Optional> deposits, + final Optional> exits) { this.transactions = transactions; this.ommers = ommers; this.withdrawals = withdrawals; this.deposits = deposits; + this.exits = exits; } public static BlockBody empty() { @@ -86,6 +90,16 @@ public Optional> getWithdrawals() { return withdrawals; } + /** + * Returns the exits of the block. + * + * @return The optional list of exits included in the block. + */ + @Override + public Optional> getExits() { + return exits; + } + /** * Returns the deposits of the block. * @@ -112,6 +126,7 @@ public void writeTo(final RLPOutput output) { output.writeList(getOmmers(), BlockHeader::writeTo); withdrawals.ifPresent(withdrawals -> output.writeList(withdrawals, Withdrawal::writeTo)); deposits.ifPresent(deposits -> output.writeList(deposits, Deposit::writeTo)); + exits.ifPresent(exits -> output.writeList(exits, ValidatorExit::writeTo)); } public static BlockBody readWrappedBodyFrom( @@ -163,7 +178,10 @@ public static BlockBody readFrom( : Optional.of(input.readList(Withdrawal::readFrom)), input.isEndOfCurrentList() ? Optional.empty() - : Optional.of(input.readList(Deposit::readFrom))); + : Optional.of(input.readList(Deposit::readFrom)), + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(ValidatorExit::readFrom))); } @Override @@ -174,19 +192,21 @@ public boolean equals(final Object o) { return Objects.equals(transactions, blockBody.transactions) && Objects.equals(ommers, blockBody.ommers) && Objects.equals(withdrawals, blockBody.withdrawals) - && Objects.equals(deposits, blockBody.deposits); + && Objects.equals(deposits, blockBody.deposits) + && Objects.equals(exits, blockBody.exits); } @Override public int hashCode() { - return Objects.hash(transactions, ommers, withdrawals, deposits); + return Objects.hash(transactions, ommers, withdrawals, deposits, exits); } public boolean isEmpty() { return transactions.isEmpty() && ommers.isEmpty() && withdrawals.isEmpty() - && deposits.isEmpty(); + && deposits.isEmpty() + && exits.isEmpty(); } @Override @@ -200,6 +220,8 @@ public String toString() { + withdrawals + ", deposits=" + deposits + + ", exits=" + + exits + '}'; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index 1e40d54a5df..51a20996ce2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -65,6 +65,7 @@ public BlockHeader( final BlobGas excessBlobGas, final Bytes32 parentBeaconBlockRoot, final Hash depositsRoot, + final Hash exitsRoot, final BlockHeaderFunctions blockHeaderFunctions) { super( parentHash, @@ -86,7 +87,8 @@ public BlockHeader( blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositsRoot); + depositsRoot, + exitsRoot); this.nonce = nonce; this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this)); this.parsedExtraData = Suppliers.memoize(() -> blockHeaderFunctions.parseExtraData(this)); @@ -174,6 +176,9 @@ public void writeTo(final RLPOutput out) { if (depositsRoot != null) { out.writeBytes(depositsRoot); } + if (exitsRoot != null) { + out.writeBytes(exitsRoot); + } out.endList(); } @@ -206,6 +211,7 @@ public static BlockHeader readFrom( final Bytes32 parentBeaconBlockRoot = !input.isEndOfCurrentList() ? input.readBytes32() : null; final Hash depositHashRoot = !input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null; + final Hash exitsHashRoot = !input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null; input.leaveList(); return new BlockHeader( parentHash, @@ -229,6 +235,7 @@ public static BlockHeader readFrom( excessBlobGas, parentBeaconBlockRoot, depositHashRoot, + exitsHashRoot, blockHeaderFunctions); } @@ -282,6 +289,9 @@ public String toString() { if (depositsRoot != null) { sb.append("depositsRoot=").append(depositsRoot); } + if (exitsRoot != null) { + sb.append("exitsRoot=").append(exitsRoot); + } return sb.append("}").toString(); } @@ -316,6 +326,7 @@ public static org.hyperledger.besu.ethereum.core.BlockHeader convertPluginBlockH .getDepositsRoot() .map(h -> Hash.fromHexString(h.toHexString())) .orElse(null), + pluginBlockHeader.getExitsRoot().map(h -> Hash.fromHexString(h.toHexString())).orElse(null), blockHeaderFunctions); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java index b23c7a46c78..33c1450cd05 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java @@ -46,6 +46,7 @@ public class BlockHeaderBuilder { private Hash withdrawalsRoot = null; private Hash depositsRoot = null; + private Hash exitsRoot = null; private Hash receiptsRoot; @@ -124,7 +125,8 @@ public static BlockHeaderBuilder fromHeader(final BlockHeader header) { .blobGasUsed(header.getBlobGasUsed().orElse(null)) .excessBlobGas(header.getExcessBlobGas().orElse(null)) .parentBeaconBlockRoot(header.getParentBeaconBlockRoot().orElse(null)) - .depositsRoot(header.getDepositsRoot().orElse(null)); + .depositsRoot(header.getDepositsRoot().orElse(null)) + .exitsRoot(header.getExitsRoot().orElse(null)); } public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilder) { @@ -149,6 +151,7 @@ public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilde .excessBlobGas(fromBuilder.excessBlobGas) .parentBeaconBlockRoot(fromBuilder.parentBeaconBlockRoot) .depositsRoot(fromBuilder.depositsRoot) + .exitsRoot(fromBuilder.exitsRoot) .blockHeaderFunctions(fromBuilder.blockHeaderFunctions); toBuilder.nonce = fromBuilder.nonce; return toBuilder; @@ -179,6 +182,7 @@ public BlockHeader buildBlockHeader() { excessBlobGas, parentBeaconBlockRoot, depositsRoot, + exitsRoot, blockHeaderFunctions); } @@ -220,7 +224,8 @@ public SealableBlockHeader buildSealableBlockHeader() { blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositsRoot); + depositsRoot, + exitsRoot); } private void validateBlockHeader() { @@ -285,6 +290,7 @@ public BlockHeaderBuilder populateFrom(final SealableBlockHeader sealableBlockHe sealableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas); sealableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot); depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null)); + exitsRoot(sealableBlockHeader.getExitsRoot().orElse(null)); return this; } @@ -404,6 +410,11 @@ public BlockHeaderBuilder depositsRoot(final Hash hash) { return this; } + public BlockHeaderBuilder exitsRoot(final Hash hash) { + this.exitsRoot = hash; + return this; + } + public BlockHeaderBuilder excessBlobGas(final BlobGas excessBlobGas) { this.excessBlobGas = excessBlobGas; return this; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java index 68855cfafbd..f11c73eed74 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java @@ -45,6 +45,8 @@ public class SealableBlockHeader extends ProcessableBlockHeader { protected final Hash depositsRoot; + protected final Hash exitsRoot; + protected final Long blobGasUsed; protected final BlobGas excessBlobGas; @@ -69,7 +71,8 @@ protected SealableBlockHeader( final Long blobGasUsed, final BlobGas excessBlobGas, final Bytes32 parentBeaconBlockRoot, - final Hash depositsRoot) { + final Hash depositsRoot, + final Hash exitsRoot) { super( parentHash, coinbase, @@ -86,6 +89,7 @@ protected SealableBlockHeader( this.withdrawalsRoot = withdrawalsRoot; this.depositsRoot = depositsRoot; this.receiptsRoot = receiptsRoot; + this.exitsRoot = exitsRoot; this.logsBloom = logsBloom; this.gasUsed = gasUsed; this.extraData = extraData; @@ -174,6 +178,10 @@ public Optional getDepositsRoot() { return Optional.ofNullable(depositsRoot); } + public Optional getExitsRoot() { + return Optional.ofNullable(exitsRoot); + } + /** * Returns the blob gas used if available. * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ValidatorExit.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ValidatorExit.java new file mode 100644 index 00000000000..5ebc75a6dc8 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ValidatorExit.java @@ -0,0 +1,89 @@ +/* + * 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.core; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.ethereum.core.encoding.ValidatorExitDecoder; +import org.hyperledger.besu.ethereum.core.encoding.ValidatorExitEncoder; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.util.Objects; + +import org.apache.tuweni.bytes.Bytes; + +public class ValidatorExit implements org.hyperledger.besu.plugin.data.ValidatorExit { + + private final Address sourceAddress; + private final BLSPublicKey validatorPubKey; + + public ValidatorExit(final Address sourceAddress, final BLSPublicKey validatorPubKey) { + this.sourceAddress = sourceAddress; + this.validatorPubKey = validatorPubKey; + } + + public static ValidatorExit readFrom(final Bytes rlpBytes) { + return readFrom(RLP.input(rlpBytes)); + } + + public static ValidatorExit readFrom(final RLPInput rlpInput) { + return ValidatorExitDecoder.decode(rlpInput); + } + + public void writeTo(final RLPOutput out) { + ValidatorExitEncoder.encode(this, out); + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public PublicKey getValidatorPubKey() { + return validatorPubKey; + } + + @Override + public String toString() { + return "ValidatorExit{" + + "sourceAddress=" + + sourceAddress + + "validatorPubKey=" + + validatorPubKey + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ValidatorExit that = (ValidatorExit) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(validatorPubKey, that.validatorPubKey); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, validatorPubKey); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoder.java new file mode 100644 index 00000000000..90a80d52782 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoder.java @@ -0,0 +1,32 @@ +/* + * 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.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ValidatorExit; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +public class ValidatorExitDecoder { + + public static ValidatorExit decode(final RLPInput rlpInput) { + rlpInput.enterList(); + final Address sourceAddress = Address.readFrom(rlpInput); + final BLSPublicKey validatorPublicKey = BLSPublicKey.readFrom(rlpInput); + rlpInput.leaveList(); + + return new ValidatorExit(sourceAddress, validatorPublicKey); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoder.java new file mode 100644 index 00000000000..94903bac1ad --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoder.java @@ -0,0 +1,35 @@ +/* + * 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.core.encoding; + +import org.hyperledger.besu.ethereum.core.ValidatorExit; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public class ValidatorExitEncoder { + + public static void encode(final ValidatorExit exit, final RLPOutput rlpOutput) { + rlpOutput.startList(); + rlpOutput.writeBytes(exit.getSourceAddress()); + rlpOutput.writeBytes(exit.getValidatorPubKey()); + rlpOutput.endList(); + } + + public static Bytes encodeOpaqueBytes(final ValidatorExit exit) { + return RLP.encode(rlpOutput -> encode(exit, rlpOutput)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java index ecf206a54ad..d7a1611e659 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java @@ -21,10 +21,12 @@ import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.DepositEncoder; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; +import org.hyperledger.besu.ethereum.core.encoding.ValidatorExitEncoder; import org.hyperledger.besu.ethereum.core.encoding.WithdrawalEncoder; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.MerkleTrie; @@ -103,6 +105,21 @@ public static Hash depositsRoot(final List deposits) { return Hash.wrap(trie.getRootHash()); } + /** + * Generates the exits root for a list of exits + * + * @param exits list of exits + * @return the exits root + */ + public static Hash exitsRoot(final List exits) { + final MerkleTrie trie = trie(); + + IntStream.range(0, exits.size()) + .forEach(i -> trie.put(indexKey(i), ValidatorExitEncoder.encodeOpaqueBytes(exits.get(i)))); + + return Hash.wrap(trie.getRootHash()); + } + /** * Generates the receipt root for a list of receipts * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index cf63c8ce175..0e6dcf91eeb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -764,6 +764,7 @@ static ProtocolSpecBuilder pragueDefinition( // use prague precompiled contracts .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) .depositsValidator(new DepositsValidator.AllowedDeposits(depositContractAddress)) + .exitsValidator(new ValidatorExitsValidator.AllowedExits()) .name("Prague"); } @@ -815,9 +816,6 @@ static ProtocolSpecBuilder experimentalEipsDefinition( final EvmConfiguration evmConfiguration, final MiningParameters miningParameters) { - final Address depositContractAddress = - genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); - return futureEipsDefinition( chainId, configContractSizeLimit, @@ -830,7 +828,6 @@ static ProtocolSpecBuilder experimentalEipsDefinition( (gasCalculator, jdCacheConfig) -> MainnetEVMs.experimentalEips( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) - .depositsValidator(new DepositsValidator.AllowedDeposits(depositContractAddress)) .name("ExperimentalEips"); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java index 850b4fd0824..72eaa8622ec 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java @@ -76,6 +76,7 @@ public class ProtocolSpec { private final WithdrawalsValidator withdrawalsValidator; private final Optional withdrawalsProcessor; private final DepositsValidator depositsValidator; + private final ValidatorExitsValidator exitsValidator; private final boolean isPoS; private final boolean isReplayProtectionSupported; @@ -137,6 +138,7 @@ public ProtocolSpec( final WithdrawalsValidator withdrawalsValidator, final Optional withdrawalsProcessor, final DepositsValidator depositsValidator, + final ValidatorExitsValidator exitsValidator, final boolean isPoS, final boolean isReplayProtectionSupported) { this.name = name; @@ -164,6 +166,7 @@ public ProtocolSpec( this.withdrawalsValidator = withdrawalsValidator; this.withdrawalsProcessor = withdrawalsProcessor; this.depositsValidator = depositsValidator; + this.exitsValidator = exitsValidator; this.isPoS = isPoS; this.isReplayProtectionSupported = isReplayProtectionSupported; } @@ -369,6 +372,10 @@ public DepositsValidator getDepositsValidator() { return depositsValidator; } + public ValidatorExitsValidator getExitsValidator() { + return exitsValidator; + } + /** * Returns true if the network is running Proof of Stake * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 67a89ea653e..8f2e1583e82 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -74,8 +74,8 @@ public class ProtocolSpecBuilder { private WithdrawalsValidator withdrawalsValidator = new WithdrawalsValidator.ProhibitedWithdrawals(); private WithdrawalsProcessor withdrawalsProcessor; - private DepositsValidator depositsValidator = new DepositsValidator.ProhibitedDeposits(); + private ValidatorExitsValidator exitsValidator = new ValidatorExitsValidator.ProhibitedExits(); private FeeMarket feeMarket = FeeMarket.legacy(); private BadBlockManager badBlockManager; private PoWHasher powHasher = PoWHasher.ETHASH_LIGHT; @@ -265,6 +265,11 @@ public ProtocolSpecBuilder depositsValidator(final DepositsValidator depositsVal return this; } + public ProtocolSpecBuilder exitsValidator(final ValidatorExitsValidator exitsValidator) { + this.exitsValidator = exitsValidator; + return this; + } + public ProtocolSpecBuilder isPoS(final boolean isPoS) { this.isPoS = isPoS; return this; @@ -384,6 +389,7 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { withdrawalsValidator, Optional.ofNullable(withdrawalsProcessor), depositsValidator, + exitsValidator, isPoS, isReplayProtectionSupported); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidator.java new file mode 100644 index 00000000000..06bc803ba5e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ValidatorExitsValidator.java @@ -0,0 +1,56 @@ +/* + * 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.mainnet; + +import org.hyperledger.besu.ethereum.core.ValidatorExit; + +import java.util.List; +import java.util.Optional; + +public interface ValidatorExitsValidator { + + boolean validateValidatorExitParameter(Optional> validatorExits); + + /** Used before Prague */ + class ProhibitedExits implements ValidatorExitsValidator { + + /** + * Before Prague we do not expect to have execution layer triggered exits, so it is expected the + * optional parameter will be empty + * + * @param validatorExits Optional list of exits + * @return true, if valid, false otherwise + */ + @Override + public boolean validateValidatorExitParameter( + final Optional> validatorExits) { + return validatorExits.isEmpty(); + } + } + + /** Used after Prague */ + class AllowedExits implements ValidatorExitsValidator { + + @Override + public boolean validateValidatorExitParameter( + final Optional> validatorExits) { + // TODO implement any extra required validation (see + // https://github.com/hyperledger/besu/issues/6800) + return validatorExits.isPresent(); + } + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index 3b2b8e30958..7b88503c926 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -304,6 +304,7 @@ public BlockHeader header(final long number, final BlockBody body, final BlockOp .nonce(blockNonce) .withdrawalsRoot(options.getWithdrawalsRoot(null)) .depositsRoot(options.getDepositsRoot(null)) + .exitsRoot(options.getExitsRoot(null)) .blockHeaderFunctions( options.getBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); options.getBaseFee(Optional.of(Wei.of(uint256(2)))).ifPresent(blockHeaderBuilder::baseFee); @@ -332,7 +333,8 @@ public BlockBody body(final BlockOptions options) { options.getTransactions(defaultTxs), ommers, options.getWithdrawals(Optional.empty()), - options.getDeposits(Optional.empty())); + options.getDeposits(Optional.empty()), + options.getExits(Optional.empty())); } private BlockHeader ommer() { @@ -639,6 +641,7 @@ public static class BlockOptions { private Optional>> withdrawals = Optional.empty(); private Optional>> deposits = Optional.empty(); + private Optional>> exits = Optional.empty(); private Optional extraData = Optional.empty(); private Optional blockHeaderFunctions = Optional.empty(); private Optional receiptsRoot = Optional.empty(); @@ -655,6 +658,7 @@ public static class BlockOptions { private Optional withdrawalsRoot = Optional.empty(); private Optional depositsRoot = Optional.empty(); + private Optional exitsRoot = Optional.empty(); private Optional> maybeMaxFeePerBlobGas = Optional.empty(); @@ -731,6 +735,15 @@ public Optional> getDeposits(final Optional> default return deposits.orElse(defaultValue); } + public Hash getExitsRoot(final Hash defaultValue) { + return exitsRoot.orElse(defaultValue); + } + + public Optional> getExits( + final Optional> defaultValue) { + return exits.orElse(defaultValue); + } + public boolean hasTransactions() { return hasTransactions; } @@ -763,6 +776,11 @@ public BlockOptions setDeposits(final Optional> deposits) { return this; } + public BlockOptions setExits(final Optional> exits) { + this.exits = Optional.of(exits); + return this; + } + public BlockOptions setBlockNumber(final long blockNumber) { this.blockNumber = OptionalLong.of(blockNumber); return this; @@ -856,6 +874,11 @@ public BlockOptions setDepositsRoot(final Hash depositsRoot) { return this; } + public BlockOptions setExitsRoot(final Hash exitsRoot) { + this.exitsRoot = Optional.of(exitsRoot); + return this; + } + public Optional getMaxFeePerBlobGas(final Optional defaultValue) { return maybeMaxFeePerBlobGas.orElse(defaultValue); } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java index 0a379c9d51e..4a135b33b8c 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java @@ -51,6 +51,7 @@ public class BlockHeaderTestFixture { private long nonce = 0; private Optional withdrawalsRoot = Optional.empty(); private Optional depositsRoot = Optional.empty(); + private Optional exitsRoot = Optional.empty(); private BlockHeaderFunctions blockHeaderFunctions = new MainnetBlockHeaderFunctions(); private Optional excessBlobGas = Optional.empty(); private Optional blobGasUsed = Optional.empty(); @@ -79,6 +80,7 @@ public BlockHeader buildHeader() { excessBlobGas.ifPresent(builder::excessBlobGas); blobGasUsed.ifPresent(builder::blobGasUsed); depositsRoot.ifPresent(builder::depositsRoot); + exitsRoot.ifPresent(builder::exitsRoot); parentBeaconBlockRoot.ifPresent(builder::parentBeaconBlockRoot); builder.blockHeaderFunctions(blockHeaderFunctions); @@ -180,6 +182,11 @@ public BlockHeaderTestFixture depositsRoot(final Hash depositsRoot) { return this; } + public BlockHeaderTestFixture exitsRoot(final Hash exitsRoot) { + this.exitsRoot = Optional.ofNullable(exitsRoot); + return this; + } + public BlockHeaderTestFixture excessBlobGas(final BlobGas excessBlobGas) { this.excessBlobGas = Optional.ofNullable(excessBlobGas); return this; diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java index d0c7cb8bbe1..f818de80870 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java @@ -119,6 +119,11 @@ public Optional getDepositsRoot() { return Optional.empty(); } + @Override + public Optional getExitsRoot() { + return Optional.empty(); + } + @Override public Hash getBlockHash() { return blockHash; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoderTest.java new file mode 100644 index 00000000000..2bdb7e779de --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitDecoderTest.java @@ -0,0 +1,44 @@ +/* + * 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.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ValidatorExit; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ValidatorExitDecoderTest { + + @Test + public void shouldDecodeValidatorExit() { + final ValidatorExit expectedValidatorExit = + new ValidatorExit( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + expectedValidatorExit.writeTo(out); + + final ValidatorExit decodedValidatorExit = + ValidatorExitDecoder.decode(RLP.input(out.encoded())); + + Assertions.assertThat(decodedValidatorExit).isEqualTo(expectedValidatorExit); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoderTest.java new file mode 100644 index 00000000000..f8dbc8e4471 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ValidatorExitEncoderTest.java @@ -0,0 +1,42 @@ +/* + * 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.core.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ValidatorExit; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class ValidatorExitEncoderTest { + @Test + void shouldEncodeExit() { + final ValidatorExit exit = + new ValidatorExit( + Address.fromHexString("0x763c396673F9c391DCe3361A9A71C8E161388000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + final Bytes encoded = ValidatorExitEncoder.encodeOpaqueBytes(exit); + + assertThat(encoded) + .isEqualTo( + Bytes.fromHexString( + "0xf84694763c396673f9c391dce3361a9a71c8e161388000b0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java index 98fbaa1734f..2010f09f6a5 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput; @@ -63,7 +64,11 @@ public static BlockBody readBody(final long num) throws IOException { input.isEndOfCurrentList() ? Optional.empty() : Optional.of(input.readList(Deposit::readFrom)); - return new BlockBody(transactions, ommers, withdrawals, deposits); + final Optional> exits = + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(ValidatorExit::readFrom)); + return new BlockBody(transactions, ommers, withdrawals, deposits, exits); } public static Block readBlock(final long num) throws IOException { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java index 331c6b1afdb..e26436c4271 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java @@ -100,6 +100,7 @@ class LogRollingTests { null, null, null, + null, new MainnetBlockHeaderFunctions()); private static final BlockHeader headerTwo = new BlockHeader( @@ -124,6 +125,7 @@ class LogRollingTests { null, null, null, + null, new MainnetBlockHeaderFunctions()); @BeforeEach diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index 4cd66830251..c908837db61 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; @@ -152,32 +153,42 @@ static class BodyIdentifier { private final Bytes32 ommersHash; private final Bytes32 withdrawalsRoot; private final Bytes32 depositsRoot; + private final Bytes32 exitsRoot; public BodyIdentifier( final Bytes32 transactionsRoot, final Bytes32 ommersHash, final Bytes32 withdrawalsRoot, - final Bytes32 depositsRoot) { + final Bytes32 depositsRoot, + final Bytes32 exitsRoot) { this.transactionsRoot = transactionsRoot; this.ommersHash = ommersHash; this.withdrawalsRoot = withdrawalsRoot; this.depositsRoot = depositsRoot; + this.exitsRoot = exitsRoot; } public BodyIdentifier(final BlockBody body) { - this(body.getTransactions(), body.getOmmers(), body.getWithdrawals(), body.getDeposits()); + this( + body.getTransactions(), + body.getOmmers(), + body.getWithdrawals(), + body.getDeposits(), + body.getExits()); } public BodyIdentifier( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits) { + final Optional> deposits, + final Optional> exits) { this( BodyValidation.transactionsRoot(transactions), BodyValidation.ommersHash(ommers), withdrawals.map(BodyValidation::withdrawalsRoot).orElse(null), - deposits.map(BodyValidation::depositsRoot).orElse(null)); + deposits.map(BodyValidation::depositsRoot).orElse(null), + exits.map(BodyValidation::exitsRoot).orElse(null)); } public BodyIdentifier(final BlockHeader header) { @@ -185,7 +196,8 @@ public BodyIdentifier(final BlockHeader header) { header.getTransactionsRoot(), header.getOmmersHash(), header.getWithdrawalsRoot().orElse(null), - header.getDepositsRoot().orElse(null)); + header.getDepositsRoot().orElse(null), + header.getExitsRoot().orElse(null)); } @Override @@ -196,12 +208,13 @@ public boolean equals(final Object o) { return Objects.equals(transactionsRoot, that.transactionsRoot) && Objects.equals(ommersHash, that.ommersHash) && Objects.equals(withdrawalsRoot, that.withdrawalsRoot) - && Objects.equals(depositsRoot, that.depositsRoot); + && Objects.equals(depositsRoot, that.depositsRoot) + && Objects.equals(exitsRoot, that.exitsRoot); } @Override public int hashCode() { - return Objects.hash(transactionsRoot, ommersHash, withdrawalsRoot, depositsRoot); + return Objects.hash(transactionsRoot, ommersHash, withdrawalsRoot, depositsRoot, exitsRoot); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java index bdd59260e72..5be7bedf29d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java @@ -94,6 +94,7 @@ private BlockBody createEmptyBodyBasedOnProtocolSchedule( isWithdrawalsEnabled(protocolSchedule, header) ? Optional.of(Collections.emptyList()) : Optional.empty(), + Optional.empty(), Optional.empty()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java index dbe1ba9bc86..afadea61c0c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; @@ -78,7 +79,12 @@ public void assertBodyIdentifierUsesWithdrawalsToGenerateBodyIdentifiers() { final BlockBody emptyBodyBlock = BlockBody.empty(); // Block with no tx, no ommers, 1 withdrawal final BlockBody bodyBlockWithWithdrawal = - new BlockBody(emptyList(), emptyList(), Optional.of(List.of(withdrawal)), Optional.empty()); + new BlockBody( + emptyList(), + emptyList(), + Optional.of(List.of(withdrawal)), + Optional.empty(), + Optional.empty()); assertThat( new GetBodiesFromPeerTask.BodyIdentifier(emptyBodyBlock) @@ -103,11 +109,41 @@ public void assertBodyIdentifierUsesDepositsToGenerateBodyIdentifiers() { final BlockBody emptyBodyBlock = BlockBody.empty(); // Block with no tx, no ommers, 1 deposit final BlockBody bodyBlockWithDeposit = - new BlockBody(emptyList(), emptyList(), Optional.empty(), Optional.of(List.of(deposit))); + new BlockBody( + emptyList(), + emptyList(), + Optional.empty(), + Optional.of(List.of(deposit)), + Optional.empty()); assertThat( new GetBodiesFromPeerTask.BodyIdentifier(emptyBodyBlock) .equals(new GetBodiesFromPeerTask.BodyIdentifier(bodyBlockWithDeposit))) .isFalse(); } + + @Test + public void assertBodyIdentifierUsesExitsToGenerateBodyIdentifiers() { + final ValidatorExit validatorExit = + new ValidatorExit( + Address.fromHexString("0x763c396673F9c391DCe3361A9A71C8E161388000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + // Empty body block + final BlockBody emptyBodyBlock = BlockBody.empty(); + // Block with no tx, no ommers, 1 validator exit + final BlockBody bodyBlockWithValidatorExit = + new BlockBody( + emptyList(), + emptyList(), + Optional.empty(), + Optional.empty(), + Optional.of(List.of(validatorExit))); + + assertThat( + new GetBodiesFromPeerTask.BodyIdentifier(emptyBodyBlock) + .equals(new GetBodiesFromPeerTask.BodyIdentifier(bodyBlockWithValidatorExit))) + .isFalse(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java index 78fcbb1c085..b6a0babfa1e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java @@ -260,6 +260,7 @@ public void readFromExpectsListWrappingBodyFields() { Collections.emptyList(), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.empty(), Optional.empty())); } @@ -285,6 +286,7 @@ public void readBodyFieldsExpectsNoListWrappingBodyFields() { Collections.emptyList(), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.empty(), Optional.empty())); } @@ -400,6 +402,7 @@ public TestBlockHeader( null, null, null, + null, new MainnetBlockHeaderFunctions()); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java index a1ee46a4eea..ee816ea4ea0 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java @@ -61,6 +61,7 @@ public static BlockHeader prepareHeader(final long number, final Optional expectedBlocks = asList(block1, block2); @@ -167,6 +169,7 @@ public void shouldCompleteBlockThatOnlyContainsWithdrawals_whenWithdrawalsAreEna Collections.emptyList(), Collections.emptyList(), Optional.of(withdrawals), + Optional.empty(), Optional.empty())); final Block block3 = new Block( @@ -175,6 +178,7 @@ public void shouldCompleteBlockThatOnlyContainsWithdrawals_whenWithdrawalsAreEna Collections.emptyList(), Collections.emptyList(), Optional.of(Collections.emptyList()), + Optional.empty(), Optional.empty())); final List expected = asList(block1, block2, block3); diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java index 985c977507f..e8cd62e9f4a 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ParsedExtraData; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.ValidatorExit; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; @@ -166,6 +167,7 @@ public ReferenceTestBlockHeader( @JsonProperty("nonce") final String nonce, @JsonProperty("withdrawalsRoot") final String withdrawalsRoot, @JsonProperty("depositsRoot") final String depositsRoot, + @JsonProperty("exitsRoot") final String exitsRoot, @JsonProperty("dataGasUsed") final String dataGasUsed, // TODO: remove once reference tests have been updated @JsonProperty("excessDataGas") @@ -204,6 +206,7 @@ public ReferenceTestBlockHeader( : excessBlobGas != null ? BlobGas.fromHexString(excessBlobGas) : null, parentBeaconBlockRoot != null ? Bytes32.fromHexString(parentBeaconBlockRoot) : null, depositsRoot != null ? Hash.fromHexString(depositsRoot) : null, + exitsRoot != null ? Hash.fromHexString(exitsRoot) : null, new BlockHeaderFunctions() { @Override public Hash hash(final BlockHeader header) { @@ -291,7 +294,10 @@ public Block getBlock() { : Optional.of(input.readList(Withdrawal::readFrom)), input.isEndOfCurrentList() ? Optional.empty() - : Optional.of(input.readList(Deposit::readFrom))); + : Optional.of(input.readList(Deposit::readFrom)), + input.isEndOfCurrentList() + ? Optional.empty() + : Optional.of(input.readList(ValidatorExit::readFrom))); return new Block(header, body); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java index c7e726d03ff..8e4b3d0f9b9 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java @@ -153,6 +153,7 @@ public ReferenceTestEnv( : BlobGas.fromHexString(currentExcessBlobGas), beaconRoot == null ? null : Bytes32.fromHexString(beaconRoot), null, // depositsRoot + null, // exitsRoot new MainnetBlockHeaderFunctions()); this.parentDifficulty = parentDifficulty; this.parentBaseFee = parentBaseFee; diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java index 8bc1968dde9..dd6350bfaa1 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java @@ -89,6 +89,7 @@ public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { original.getWithdrawalsValidator(), original.getWithdrawalsProcessor(), original.getDepositsValidator(), + original.getExitsValidator(), original.isPoS(), original.isReplayProtectionSupported()); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java index 3f9f567dc2d..4fa64db2683 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java @@ -163,6 +163,8 @@ static void populateForPrague( populateForCancun(registry, gasCalculator); // TODO: add Prague precompiles here + // EIP-7002 - Execution layer triggerable exits + // (https://github.com/hyperledger/besu/issues/6800) } /** diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 423411b5630..ea352273b5a 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'YH+8rbilrhatRAh8rK8/36qxwrqkybBaaNeg+AkZ0c4=' + knownHash = 'T98TztTmKMLspdRDLKxOTH5nTxFiVijRgNN2twZu3OY=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java index 858285365ad..b227e967a07 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java @@ -57,4 +57,12 @@ public interface BlockBody { */ @Unstable Optional> getDeposits(); + + /** + * Returns the list of exits of the block. + * + * @return The list of exits of the block. + */ + @Unstable + Optional> getExits(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java index edd552e118d..ef41f85a7fb 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java @@ -130,6 +130,16 @@ public interface BlockHeader extends ProcessableBlockHeader { @Unstable Optional getDepositsRoot(); + /** + * The Keccak 256-bit hash of the root node of the trie structure populated with each exit in the + * exits list portion of the block. + * + * @return The Keccak 256-bit hash of the root node of the trie structure populated with each exit + * in the exits list portion of the block. + */ + @Unstable + Optional getExitsRoot(); + /** * The excess_blob_gas of this header. * diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ValidatorExit.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ValidatorExit.java new file mode 100644 index 00000000000..57177b08f94 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ValidatorExit.java @@ -0,0 +1,41 @@ +/* + * 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.plugin.data; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.plugin.Unstable; + +/** + * A deposit is a system-level operation to support validator exitys that are pushed from the EVM to + * beacon chain. + */ +@Unstable +public interface ValidatorExit { + + /** + * Withdrawal credential (0x01) associated with the validator to be exited + * + * @return withdrawal credential address + */ + Address getSourceAddress(); + + /** + * Public key of the validator that is going to exit + * + * @return public key of validator + */ + PublicKey getValidatorPubKey(); +}