diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/epoch_processing/EpochProcessingTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/epoch_processing/EpochProcessingTestExecutor.java index 859e693b8b5..c6d0996219f 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/epoch_processing/EpochProcessingTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/epoch_processing/EpochProcessingTestExecutor.java @@ -78,8 +78,9 @@ public class EpochProcessingTestExecutor implements TestExecutor { .put( "epoch_processing/inactivity_updates", new EpochProcessingTestExecutor(EpochOperation.INACTIVITY_UPDATES)) - // TODO re-enable consolidation tests (https://github.com/Consensys/teku/issues/8617) - .put("epoch_processing/pending_consolidations", TestExecutor.IGNORE_TESTS) + .put( + "epoch_processing/pending_consolidations", + new EpochProcessingTestExecutor(EpochOperation.PENDING_CONSOLIDATIONS)) .put( "epoch_processing/pending_deposits", new EpochProcessingTestExecutor(EpochOperation.PENDING_DEPOSITS)) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java index 805acbc1c39..aabdc8bacdc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java @@ -930,6 +930,14 @@ public void processConsolidationRequests( // No Consolidations until Electra } + @Override + public boolean isValidSwitchToCompoundingRequest( + final BeaconState beaconState, final ConsolidationRequest consolidationRequest) + throws BlockProcessingException { + // No Consolidations until Electra + return false; + } + // Catch generic errors and wrap them in a BlockProcessingException protected void safelyProcess(final BlockProcessingAction action) throws BlockProcessingException { try { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java index 43d66bcd048..8976f5e7363 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java @@ -182,6 +182,10 @@ void processConsolidationRequests( MutableBeaconState state, List consolidationRequests) throws BlockProcessingException; + boolean isValidSwitchToCompoundingRequest( + BeaconState beaconState, ConsolidationRequest consolidationRequest) + throws BlockProcessingException; + ExpectedWithdrawals getExpectedWithdrawals(BeaconState preState); default Optional toVersionAltair() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java index d895e956d63..83ce065d599 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java @@ -222,7 +222,7 @@ public void processWithdrawalRequests( if (maybeValidatorIndex.isEmpty()) { LOG.debug( "process_withdrawal_request: no matching validator for public key {}", - withdrawalRequest.getValidatorPublicKey()); + withdrawalRequest.getValidatorPublicKey().toAbbreviatedString()); return; } @@ -377,7 +377,7 @@ public void processDepositRequests( public void processConsolidationRequests( final MutableBeaconState state, final List consolidationRequests) { LOG.debug( - "process_consolidation_request: {} consolidation request to process from block at " + "process_consolidation_request: {} consolidation requests to process from block at " + "slot {}", consolidationRequests.size(), state.getSlot()); @@ -392,6 +392,27 @@ private void processConsolidationRequest( final UInt64 slot = state.getSlot(); final UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(slot); + if (isValidSwitchToCompoundingRequest(state, consolidationRequest)) { + LOG.debug( + "process_consolidation_request: switching validator {} to compounding address", + consolidationRequest.getSourcePubkey().toAbbreviatedString()); + validatorsUtil + .getValidatorIndex(state, consolidationRequest.getSourcePubkey()) + .ifPresent( + sourceValidatorIndex -> + beaconStateMutatorsElectra.switchToCompoundingValidator( + state, sourceValidatorIndex)); + return; + } + + // Verify that source != target, so a consolidation cannot be used as an exit + if (consolidationRequest.getSourcePubkey().equals(consolidationRequest.getTargetPubkey())) { + LOG.debug( + "process_consolidation_request: source_pubkey and target_pubkey must be different (pubkey = {})", + consolidationRequest.getSourcePubkey().toAbbreviatedString()); + return; + } + // If the pending consolidations queue is full, consolidation requests are ignored if (state.getPendingConsolidations().size() == specConfigElectra.getPendingConsolidationsLimit()) { @@ -414,7 +435,7 @@ private void processConsolidationRequest( if (maybeSourceValidatorIndex.isEmpty()) { LOG.debug( "process_consolidation_request: source_pubkey {} not found", - consolidationRequest.getSourcePubkey()); + consolidationRequest.getSourcePubkey().toAbbreviatedString()); return; } @@ -424,18 +445,14 @@ private void processConsolidationRequest( if (maybeTargetValidatorIndex.isEmpty()) { LOG.debug( "process_consolidation_request: target_pubkey {} not found", - consolidationRequest.getTargetPubkey()); - return; - } - - // Verify that source != target, so a consolidation cannot be used as an exit. - if (maybeSourceValidatorIndex.get().equals(maybeTargetValidatorIndex.get())) { - LOG.debug("process_consolidation_request: source_pubkey and target_pubkey must be different"); + consolidationRequest.getTargetPubkey().toAbbreviatedString()); return; } - final Validator sourceValidator = state.getValidators().get(maybeSourceValidatorIndex.get()); - final Validator targetValidator = state.getValidators().get(maybeTargetValidatorIndex.get()); + final int sourceValidatorIndex = maybeSourceValidatorIndex.get(); + final Validator sourceValidator = state.getValidators().get(sourceValidatorIndex); + final int targetValidatorIndex = maybeTargetValidatorIndex.get(); + final Validator targetValidator = state.getValidators().get(targetValidatorIndex); // Verify source withdrawal credentials final boolean sourceHasExecutionWithdrawalCredentials = @@ -459,21 +476,25 @@ private void processConsolidationRequest( // Verify the source and the target are active if (!predicatesElectra.isActiveValidator(sourceValidator, currentEpoch)) { - LOG.debug("process_consolidation_request: source validator is inactive"); + LOG.debug( + "process_consolidation_request: source validator {} is inactive", sourceValidatorIndex); return; } if (!predicatesElectra.isActiveValidator(targetValidator, currentEpoch)) { - LOG.debug("process_consolidation_request: target validator is inactive"); + LOG.debug( + "process_consolidation_request: target validator {} is inactive", targetValidatorIndex); return; } // Verify exits for source and target have not been initiated if (!sourceValidator.getExitEpoch().equals(FAR_FUTURE_EPOCH)) { - LOG.debug("process_consolidation_request: source validator is exiting"); + LOG.debug( + "process_consolidation_request: source validator {} is exiting", sourceValidatorIndex); return; } if (!targetValidator.getExitEpoch().equals(FAR_FUTURE_EPOCH)) { - LOG.debug("process_consolidation_request: target validator is exiting"); + LOG.debug( + "process_consolidation_request: target validator {} is exiting", targetValidatorIndex); return; } @@ -487,24 +508,81 @@ private void processConsolidationRequest( state .getValidators() .update( - maybeSourceValidatorIndex.get(), + sourceValidatorIndex, v -> v.withExitEpoch(exitEpoch).withWithdrawableEpoch(withdrawableEpoch)); LOG.debug( "process_consolidation_request: updated validator {} with exit_epoch = {}, withdrawable_epoch = {}", - maybeSourceValidatorIndex.get(), + sourceValidatorIndex, exitEpoch, withdrawableEpoch); final PendingConsolidation pendingConsolidation = new PendingConsolidation( schemaDefinitionsElectra.getPendingConsolidationSchema(), - SszUInt64.of(UInt64.valueOf(maybeSourceValidatorIndex.get())), - SszUInt64.of(UInt64.valueOf(maybeTargetValidatorIndex.get()))); + SszUInt64.of(UInt64.valueOf(sourceValidatorIndex)), + SszUInt64.of(UInt64.valueOf(targetValidatorIndex))); state.getPendingConsolidations().append(pendingConsolidation); + // Churn any target excess active balance of target and raise its max + if (predicatesElectra.hasEth1WithdrawalCredential(targetValidator)) { + beaconStateMutatorsElectra.switchToCompoundingValidator(state, targetValidatorIndex); + } + LOG.debug("process_consolidation_request: created {}", pendingConsolidation); } + /** + * Implements function is_valid_switch_to_compounding_request + * + * @see + */ + @Override + public boolean isValidSwitchToCompoundingRequest( + final BeaconState state, final ConsolidationRequest consolidationRequest) { + + // Switch to compounding requires source and target be equal + if (!consolidationRequest.getSourcePubkey().equals(consolidationRequest.getTargetPubkey())) { + return false; + } + + // Verify source_pubkey exists + final Optional maybeSourceValidatorIndex = + validatorsUtil.getValidatorIndex(state, consolidationRequest.getSourcePubkey()); + if (maybeSourceValidatorIndex.isEmpty()) { + return false; + } + + final int sourceValidatorIndex = maybeSourceValidatorIndex.get(); + final Validator sourceValidator = state.getValidators().get(sourceValidatorIndex); + + // Verify request has been authorized + final Eth1Address sourceValidatorExecutionAddress = + Predicates.getExecutionAddressUnchecked(sourceValidator.getWithdrawalCredentials()); + if (!sourceValidatorExecutionAddress.equals( + Eth1Address.fromBytes(consolidationRequest.getSourceAddress().getWrappedBytes()))) { + return false; + } + + // Verify source withdrawal credentials + if (!predicatesElectra.hasEth1WithdrawalCredential(sourceValidator)) { + return false; + } + + // Verify the source is active + final UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(state.getSlot()); + if (!predicatesElectra.isActiveValidator(sourceValidator, currentEpoch)) { + return false; + } + + // Verify exit for source has not been initiated + if (!sourceValidator.getExitEpoch().equals(FAR_FUTURE_EPOCH)) { + return false; + } + + return true; + } + @Override public void applyDeposit( final MutableBeaconState state, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateMutatorsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateMutatorsElectra.java index 69aa1786318..7b9e7b2a1ba 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateMutatorsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateMutatorsElectra.java @@ -184,19 +184,16 @@ public UInt64 computeConsolidationEpochAndUpdateChurn( * @param index validatorIndex */ public void switchToCompoundingValidator(final MutableBeaconStateElectra state, final int index) { - if (PredicatesElectra.isEth1WithdrawalCredential( - state.getValidators().get(index).getWithdrawalCredentials())) { - final byte[] withdrawalCredentialsUpdated = - state.getValidators().get(index).getWithdrawalCredentials().toArray(); - withdrawalCredentialsUpdated[0] = COMPOUNDING_WITHDRAWAL_BYTE; - state - .getValidators() - .update( - index, - validator -> - validator.withWithdrawalCredentials(Bytes32.wrap(withdrawalCredentialsUpdated))); - queueExcessActiveBalance(state, index); - } + final byte[] withdrawalCredentialsUpdated = + state.getValidators().get(index).getWithdrawalCredentials().toArray(); + withdrawalCredentialsUpdated[0] = COMPOUNDING_WITHDRAWAL_BYTE; + state + .getValidators() + .update( + index, + validator -> + validator.withWithdrawalCredentials(Bytes32.wrap(withdrawalCredentialsUpdated))); + queueExcessActiveBalance(state, index); } /** diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java index 439abe017a1..40c4c488339 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java @@ -54,7 +54,6 @@ import tech.pegasys.teku.spec.logic.versions.altair.helpers.BeaconStateAccessorsAltair; import tech.pegasys.teku.spec.logic.versions.capella.statetransition.epoch.EpochProcessorCapella; import tech.pegasys.teku.spec.logic.versions.electra.helpers.BeaconStateAccessorsElectra; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.BeaconStateMutatorsElectra; import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; @@ -63,7 +62,6 @@ public class EpochProcessorElectra extends EpochProcessorCapella { private final UInt64 minActivationBalance; private final BeaconStateAccessorsElectra stateAccessorsElectra; - private final BeaconStateMutatorsElectra stateMutatorsElectra; private final SchemaDefinitionsElectra schemaDefinitionsElectra; public EpochProcessorElectra( @@ -89,7 +87,6 @@ public EpochProcessorElectra( this.minActivationBalance = specConfig.toVersionElectra().orElseThrow().getMinActivationBalance(); this.stateAccessorsElectra = BeaconStateAccessorsElectra.required(beaconStateAccessors); - this.stateMutatorsElectra = BeaconStateMutatorsElectra.required(beaconStateMutators); this.schemaDefinitionsElectra = SchemaDefinitionsElectra.required(schemaDefinitions); } @@ -391,8 +388,6 @@ public void processPendingConsolidations(final MutableBeaconState state) { break; } - stateMutatorsElectra.switchToCompoundingValidator( - stateElectra, pendingConsolidation.getTargetIndex()); final UInt64 activeBalance = stateAccessorsElectra.getActiveBalance(state, pendingConsolidation.getSourceIndex()); beaconStateMutators.decreaseBalance(