Skip to content

Commit

Permalink
7251 - Update processWithdrawals for maxEB (Consensys#8265)
Browse files Browse the repository at this point in the history
fixes Consensys#8148

Signed-off-by: Paul Harris <[email protected]>
  • Loading branch information
rolfyone authored May 2, 2024
1 parent 1ffe414 commit feab484
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,84 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import tech.pegasys.teku.infrastructure.bytes.Bytes20;
import tech.pegasys.teku.infrastructure.ssz.SszList;
import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List;
import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.config.SpecConfig;
import tech.pegasys.teku.spec.config.SpecConfigCapella;
import tech.pegasys.teku.spec.config.SpecConfigElectra;
import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal;
import tech.pegasys.teku.spec.datastructures.execution.versions.capella.WithdrawalSchema;
import tech.pegasys.teku.spec.datastructures.state.Validator;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateCapella;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.MutableBeaconStateCapella;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra;
import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal;
import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators;
import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers;
import tech.pegasys.teku.spec.logic.common.helpers.Predicates;
import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException;
import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra;
import tech.pegasys.teku.spec.logic.versions.electra.helpers.PredicatesElectra;
import tech.pegasys.teku.spec.schemas.SchemaDefinitions;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra;

public class ExpectedWithdrawals {
public static final ExpectedWithdrawals NOOP = new ExpectedWithdrawals(List.of(), 0);
public final List<Withdrawal> withdrawalList;
private final List<Withdrawal> withdrawalList;
private Optional<SszList<Withdrawal>> maybeWithdrawalsSszList = Optional.empty();
private final int partialWithdrawalCount;

public ExpectedWithdrawals(
private ExpectedWithdrawals(
final List<Withdrawal> withdrawalList, final int partialWithdrawalCount) {
this.withdrawalList = withdrawalList;
this.partialWithdrawalCount = partialWithdrawalCount;
}

private ExpectedWithdrawals(
final List<Withdrawal> withdrawalList,
final int partialWithdrawalCount,
final SchemaDefinitionsCapella schemaDefinitions) {
this.withdrawalList = withdrawalList;
this.partialWithdrawalCount = partialWithdrawalCount;

getExpectedWithdrawalsSszList(schemaDefinitions);
}

public static ExpectedWithdrawals create(
final BeaconState preState,
final SchemaDefinitions schemaDefinitions,
final MiscHelpers miscHelpers,
final SpecConfig specConfig,
final Predicates predicates) {

if (preState.toVersionElectra().isPresent()) {
return createFromElectraState(
BeaconStateElectra.required(preState),
SchemaDefinitionsElectra.required(schemaDefinitions),
MiscHelpersElectra.required(miscHelpers),
SpecConfigElectra.required(specConfig),
PredicatesElectra.required(predicates));
} else if (preState.toVersionCapella().isPresent()) {
return createFromCapellaState(
BeaconStateCapella.required(preState),
SchemaDefinitionsCapella.required(schemaDefinitions),
miscHelpers,
SpecConfigCapella.required(specConfig),
predicates);
}

return NOOP;
}

private static ExpectedWithdrawals createFromCapellaState(
final BeaconStateCapella preState,
final SchemaDefinitionsCapella schemaDefinitionsCapella,
final MiscHelpers miscHelpers,
Expand All @@ -62,10 +109,10 @@ public static ExpectedWithdrawals create(
specConfigCapella,
predicates,
new ArrayList<>());
return new ExpectedWithdrawals(capellaWithdrawals, 0);
return new ExpectedWithdrawals(capellaWithdrawals, 0, schemaDefinitionsCapella);
}

public static ExpectedWithdrawals create(
private static ExpectedWithdrawals createFromElectraState(
final BeaconStateElectra preState,
final SchemaDefinitionsElectra schemaDefinitions,
final MiscHelpersElectra miscHelpers,
Expand All @@ -82,7 +129,7 @@ public static ExpectedWithdrawals create(
specConfig,
predicates,
partialPendingWithdrawals);
return new ExpectedWithdrawals(capellaWithdrawals, partialWithdrawalsCount);
return new ExpectedWithdrawals(capellaWithdrawals, partialWithdrawalsCount, schemaDefinitions);
}

public List<Withdrawal> getWithdrawalList() {
Expand Down Expand Up @@ -209,6 +256,115 @@ private static List<Withdrawal> getExpectedWithdrawals(
return expectedWithdrawals;
}

public void processWithdrawals(
final MutableBeaconState genericState,
final ExecutionPayloadSummary payloadSummary,
final SchemaDefinitionsCapella schemaDefinitionsCapella,
final BeaconStateMutators beaconStateMutators,
final SpecConfigCapella specConfigCapella)
throws BlockProcessingException {
final SszList<Withdrawal> expectedWithdrawals =
getExpectedWithdrawalsSszList(schemaDefinitionsCapella);

assertWithdrawalsInExecutionPayloadMatchExpected(payloadSummary, expectedWithdrawals);

processWithdrawalsUnchecked(
genericState, schemaDefinitionsCapella, beaconStateMutators, specConfigCapella);
}

void processWithdrawalsUnchecked(
final MutableBeaconState genericState,
final SchemaDefinitionsCapella schemaDefinitionsCapella,
final BeaconStateMutators beaconStateMutators,
final SpecConfigCapella specConfigCapella) {
final MutableBeaconStateCapella state = MutableBeaconStateCapella.required(genericState);
final SszList<Withdrawal> expectedWithdrawals =
getExpectedWithdrawalsSszList(schemaDefinitionsCapella);

for (int i = 0; i < expectedWithdrawals.size(); i++) {
final Withdrawal withdrawal = expectedWithdrawals.get(i);
beaconStateMutators.decreaseBalance(
state, withdrawal.getValidatorIndex().intValue(), withdrawal.getAmount());
}

if (partialWithdrawalCount > 0) {
// new in electra
reducePendingWithdrawals(MutableBeaconStateElectra.required(state));
}

final int validatorCount = genericState.getValidators().size();
final int maxWithdrawalsPerPayload = specConfigCapella.getMaxWithdrawalsPerPayload();
final int maxValidatorsPerWithdrawalsSweep =
specConfigCapella.getMaxValidatorsPerWithdrawalSweep();
if (!expectedWithdrawals.isEmpty()) {
final Withdrawal latestWithdrawal = expectedWithdrawals.get(expectedWithdrawals.size() - 1);
state.setNextWithdrawalIndex(latestWithdrawal.getIndex().increment());
}

if (expectedWithdrawals.size() == maxWithdrawalsPerPayload) {
// Update the next validator index to start the next withdrawal sweep
final Withdrawal latestWithdrawal = expectedWithdrawals.get(expectedWithdrawals.size() - 1);
final int nextWithdrawalValidatorIndex = latestWithdrawal.getValidatorIndex().intValue() + 1;
state.setNextWithdrawalValidatorIndex(
UInt64.valueOf(nextWithdrawalValidatorIndex % validatorCount));
} else {
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
final int nextWithdrawalValidatorIndex =
state.getNextWithdrawalValidatorIndex().intValue() + maxValidatorsPerWithdrawalsSweep;
state.setNextWithdrawalValidatorIndex(
UInt64.valueOf(nextWithdrawalValidatorIndex % validatorCount));
}
}

private SszList<Withdrawal> getExpectedWithdrawalsSszList(
final SchemaDefinitionsCapella schemaDefinitions) {
if (maybeWithdrawalsSszList.isEmpty()) {
maybeWithdrawalsSszList =
Optional.of(
schemaDefinitions
.getExecutionPayloadSchema()
.getWithdrawalsSchemaRequired()
.createFromElements(withdrawalList));
}
return maybeWithdrawalsSszList.get();
}

private void reducePendingWithdrawals(final MutableBeaconStateElectra state) {
final SszListSchema<PendingPartialWithdrawal, ?> schema =
state.getPendingPartialWithdrawals().getSchema();
if (state.getPendingPartialWithdrawals().size() == partialWithdrawalCount) {
state.setPendingPartialWithdrawals(schema.createFromElements(List.of()));
} else {
final List<PendingPartialWithdrawal> pendingPartialWithdrawals =
state.getPendingPartialWithdrawals().asList();
state.setPendingPartialWithdrawals(
schema.createFromElements(
pendingPartialWithdrawals.subList(
partialWithdrawalCount, pendingPartialWithdrawals.size())));
}
}

private static void assertWithdrawalsInExecutionPayloadMatchExpected(
final ExecutionPayloadSummary payloadSummary, final SszList<Withdrawal> expectedWithdrawals)
throws BlockProcessingException {
// the spec does a element-to-element comparison but Teku is comparing the hash of the tree
if (payloadSummary.getOptionalWithdrawalsRoot().isEmpty()
|| !expectedWithdrawals
.hashTreeRoot()
.equals(payloadSummary.getOptionalWithdrawalsRoot().get())) {
final String msg =
String.format(
"Withdrawals in execution payload are different from expected (expected withdrawals root is %s but was "
+ "%s)",
expectedWithdrawals.hashTreeRoot(),
payloadSummary
.getOptionalWithdrawalsRoot()
.map(Bytes::toHexString)
.orElse("MISSING"));
throw new BlockProcessingException(msg);
}
}

private static UInt64 nextWithdrawalAfter(final List<Withdrawal> partialWithdrawals) {
return partialWithdrawals.get(partialWithdrawals.size() - 1).getIndex().increment();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary;
import tech.pegasys.teku.spec.datastructures.execution.ExpectedWithdrawals;
import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal;
import tech.pegasys.teku.spec.datastructures.operations.BlsToExecutionChange;
import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange;
import tech.pegasys.teku.spec.datastructures.state.Validator;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateCapella;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.MutableBeaconStateCapella;
import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators;
import tech.pegasys.teku.spec.logic.common.helpers.Predicates;
Expand Down Expand Up @@ -185,73 +183,19 @@ public void processBlsToExecutionChangesNoValidation(
public void processWithdrawals(
final MutableBeaconState genericState, final ExecutionPayloadSummary payloadSummary)
throws BlockProcessingException {
final MutableBeaconStateCapella state = MutableBeaconStateCapella.required(genericState);
final SszList<Withdrawal> expectedWithdrawals =
schemaDefinitionsCapella
.getExecutionPayloadSchema()
.getWithdrawalsSchemaRequired()
.createFromElements(getExpectedWithdrawals(state).getWithdrawalList());

assertWithdrawalsInExecutionPayloadMatchExpected(payloadSummary, expectedWithdrawals);

for (int i = 0; i < expectedWithdrawals.size(); i++) {
final Withdrawal withdrawal = expectedWithdrawals.get(i);
beaconStateMutators.decreaseBalance(
state, withdrawal.getValidatorIndex().intValue(), withdrawal.getAmount());
}

final int validatorCount = genericState.getValidators().size();
final int maxWithdrawalsPerPayload = specConfigCapella.getMaxWithdrawalsPerPayload();
final int maxValidatorsPerWithdrawalsSweep =
specConfigCapella.getMaxValidatorsPerWithdrawalSweep();
if (expectedWithdrawals.size() != 0) {
final Withdrawal latestWithdrawal = expectedWithdrawals.get(expectedWithdrawals.size() - 1);
state.setNextWithdrawalIndex(latestWithdrawal.getIndex().increment());
}

final int nextWithdrawalValidatorIndex;
if (expectedWithdrawals.size() == maxWithdrawalsPerPayload) {
// Update the next validator index to start the next withdrawal sweep
final Withdrawal latestWithdrawal = expectedWithdrawals.get(expectedWithdrawals.size() - 1);
nextWithdrawalValidatorIndex = latestWithdrawal.getValidatorIndex().intValue() + 1;
} else {
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
nextWithdrawalValidatorIndex =
state.getNextWithdrawalValidatorIndex().intValue() + maxValidatorsPerWithdrawalsSweep;
}
state.setNextWithdrawalValidatorIndex(
UInt64.valueOf(nextWithdrawalValidatorIndex % validatorCount));
}

private static void assertWithdrawalsInExecutionPayloadMatchExpected(
final ExecutionPayloadSummary payloadSummary, final SszList<Withdrawal> expectedWithdrawals)
throws BlockProcessingException {
// the spec does a element-to-element comparison but Teku is comparing the hash of the tree
if (payloadSummary.getOptionalWithdrawalsRoot().isEmpty()
|| !expectedWithdrawals
.hashTreeRoot()
.equals(payloadSummary.getOptionalWithdrawalsRoot().get())) {
final String msg =
String.format(
"Withdrawals in execution payload are different from expected (expected withdrawals root is %s but was "
+ "%s)",
expectedWithdrawals.hashTreeRoot(),
payloadSummary
.getOptionalWithdrawalsRoot()
.map(Bytes::toHexString)
.orElse("MISSING"));
throw new BlockProcessingException(msg);
}
final ExpectedWithdrawals expectedWithdrawals = getExpectedWithdrawals(genericState);
expectedWithdrawals.processWithdrawals(
genericState,
payloadSummary,
schemaDefinitionsCapella,
beaconStateMutators,
specConfigCapella);
}

@Override
public ExpectedWithdrawals getExpectedWithdrawals(final BeaconState preState) {
return ExpectedWithdrawals.create(
BeaconStateCapella.required(preState),
schemaDefinitionsCapella,
miscHelpers,
specConfigCapella,
predicates);
preState, schemaDefinitionsCapella, miscHelpers, specConfig, predicates);
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import tech.pegasys.teku.spec.config.SpecConfigElectra;
import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary;
import tech.pegasys.teku.spec.datastructures.execution.ExpectedWithdrawals;
import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt;
import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerWithdrawalRequest;
Expand Down Expand Up @@ -64,7 +65,6 @@
import tech.pegasys.teku.spec.logic.versions.deneb.block.BlockProcessorDeneb;
import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb;
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.logic.versions.electra.helpers.PredicatesElectra;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra;

Expand Down Expand Up @@ -142,7 +142,7 @@ protected void verifyOutstandingDepositsAreProcessed(
final int expectedDepositCount =
Math.min(
specConfig.getMaxDeposits(),
eth1DepositIndexLimit.minus(state.getEth1DepositIndex()).intValue());
eth1DepositIndexLimit.minusMinZero(state.getEth1DepositIndex()).intValue());

checkArgument(
body.getDeposits().size() == expectedDepositCount,
Expand All @@ -166,6 +166,20 @@ protected void processExecutionLayerWithdrawalRequests(
validatorExitContextSupplier);
}

// process_withdrawals
@Override
public void processWithdrawals(
final MutableBeaconState genericState, final ExecutionPayloadSummary payloadSummary)
throws BlockProcessingException {
final ExpectedWithdrawals expectedWithdrawals = getExpectedWithdrawals(genericState);
expectedWithdrawals.processWithdrawals(
genericState,
payloadSummary,
schemaDefinitionsElectra,
beaconStateMutators,
specConfigElectra);
}

/**
* Implements process_execution_layer_withdrawal_request from consensus-specs (EIP-7002 &
* EIP-7251).
Expand Down Expand Up @@ -259,8 +273,8 @@ public void processExecutionLayerWithdrawalRequests(
&& hasExcessBalance) {
final UInt64 toWithdraw =
validatorBalance
.minus(minActivationBalance)
.minus(pendingBalanceToWithdraw)
.minusMinZero(minActivationBalance)
.minusMinZero(pendingBalanceToWithdraw)
.min(withdrawalRequest.getAmount());
final UInt64 exitQueueEpoch =
beaconStateMutatorsElectra.computeExitEpochAndUpdateChurn(
Expand Down Expand Up @@ -321,16 +335,6 @@ public void processDepositReceipts(
}
}

@Override
public ExpectedWithdrawals getExpectedWithdrawals(final BeaconState preState) {
return ExpectedWithdrawals.create(
BeaconStateElectra.required(preState),
schemaDefinitionsElectra,
MiscHelpersElectra.required(miscHelpers),
specConfigElectra,
predicatesElectra);
}

@Override
protected void applyDepositToValidatorIndex(
final MutableBeaconState state,
Expand Down
Loading

0 comments on commit feab484

Please sign in to comment.