diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java index fd3d0f9d3b0..d388e3d86c4 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java @@ -78,7 +78,7 @@ public ExecutionPayloadResult initiateBlockProduction( if (!isBlind) { final SafeFuture getPayloadResponseFuture = executionLayerChannel - .engineGetPayload(context, blockSlotState.getSlot()) + .engineGetPayload(context, blockSlotState) .thenPeek(__ -> blockProductionPerformance.engineGetPayload()); final SafeFuture executionPayloadFuture = getPayloadResponseFuture.thenApply(GetPayloadResponse::getExecutionPayload); @@ -111,7 +111,7 @@ public ExecutionPayloadResult initiateBlockAndBlobsProduction( if (!isBlind) { final SafeFuture getPayloadResponseFuture = executionLayerChannel - .engineGetPayload(context, blockSlotState.getSlot()) + .engineGetPayload(context, blockSlotState) .thenPeek(__ -> blockProductionPerformance.engineGetPayload()); final SafeFuture executionPayloadFuture = getPayloadResponseFuture.thenApply(GetPayloadResponse::getExecutionPayload); diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImpl.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImpl.java index b494f608255..75847ebd7f9 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImpl.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImpl.java @@ -195,8 +195,8 @@ public SafeFuture engineForkChoiceUpdated( @Override public SafeFuture engineGetPayload( - final ExecutionPayloadContext executionPayloadContext, final UInt64 slot) { - return engineGetPayload(executionPayloadContext, slot, false) + final ExecutionPayloadContext executionPayloadContext, final BeaconState state) { + return engineGetPayload(executionPayloadContext, state.getSlot(), false) .thenPeek(__ -> recordExecutionPayloadFallbackSource(Source.LOCAL_EL, FallbackReason.NONE)); } diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerStub.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerStub.java index 7fedfb01991..2792225f45c 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerStub.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerStub.java @@ -37,9 +37,9 @@ public class ExecutionLayerManagerStub extends ExecutionLayerChannelStub private final BuilderCircuitBreaker builderCircuitBreaker; public ExecutionLayerManagerStub( - Spec spec, - TimeProvider timeProvider, - boolean enableTransitionEmulation, + final Spec spec, + final TimeProvider timeProvider, + final boolean enableTransitionEmulation, final Optional terminalBlockHashInTTDMode, final BuilderCircuitBreaker builderCircuitBreaker) { super(spec, timeProvider, enableTransitionEmulation, terminalBlockHashInTTDMode); @@ -58,7 +58,7 @@ public SafeFuture builderGetHeader( final SafeFuture payloadValueResult, final Optional requestedBuilderBoostFactor, final BlockProductionPerformance blockProductionPerformance) { - boolean builderCircuitBreakerEngaged = builderCircuitBreaker.isEngaged(state); + final boolean builderCircuitBreakerEngaged = builderCircuitBreaker.isEngaged(state); LOG.info("Builder Circuit Breaker isEngaged: " + builderCircuitBreakerEngaged); return super.builderGetHeader( @@ -70,9 +70,7 @@ public SafeFuture builderGetHeader( .thenCompose( headerWithFallbackData -> { if (builderCircuitBreakerEngaged) { - return engineGetPayload( - executionPayloadContext, - executionPayloadContext.getPayloadBuildingAttributes().getProposalSlot()) + return engineGetPayload(executionPayloadContext, state) .thenApply( payload -> HeaderWithFallbackData.create( diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImplTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImplTest.java index 1423547e425..f6cc7ac0c7d 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImplTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerManagerImplTest.java @@ -166,11 +166,12 @@ public void engineGetPayload_shouldReturnGetPayloadResponseViaEngine() { final ExecutionPayloadContext executionPayloadContext = dataStructureUtil.randomPayloadExecutionContext(false, true); final UInt64 slot = executionPayloadContext.getForkChoiceState().getHeadBlockSlot(); + final BeaconState state = dataStructureUtil.randomBeaconState(slot); final GetPayloadResponse getPayloadResponse = prepareEngineGetPayloadResponse(executionPayloadContext, localExecutionPayloadValue, slot); - assertThat(executionLayerManager.engineGetPayload(executionPayloadContext, slot)) + assertThat(executionLayerManager.engineGetPayload(executionPayloadContext, state)) .isCompletedWithValue(getPayloadResponse); // we expect no calls to builder @@ -187,11 +188,12 @@ public void engineGetPayloadV2_shouldReturnPayloadViaEngine() { dataStructureUtil.randomPayloadExecutionContext(false, true); executionLayerManager = createExecutionLayerChannelImpl(false, false); final UInt64 slot = executionPayloadContext.getForkChoiceState().getHeadBlockSlot(); + final BeaconState state = dataStructureUtil.randomBeaconState(slot); final GetPayloadResponse getPayloadResponse = prepareEngineGetPayloadResponse(executionPayloadContext, localExecutionPayloadValue, slot); - assertThat(executionLayerManager.engineGetPayload(executionPayloadContext, slot)) + assertThat(executionLayerManager.engineGetPayload(executionPayloadContext, state)) .isCompletedWithValue(getPayloadResponse); // we expect no calls to builder diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java new file mode 100644 index 00000000000..36a98bfeacd --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java @@ -0,0 +1,73 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.util; + +import java.security.SecureRandom; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.constants.Domain; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; +import tech.pegasys.teku.spec.datastructures.operations.DepositMessage; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class DepositReceiptsUtil { + + private static final int MAX_NUMBER_OF_DEPOSITS_PER_BLOCK = 3; + + private final Spec spec; + + @SuppressWarnings("DoNotCreateSecureRandomDirectly") + private final SecureRandom random = new SecureRandom(); + + public DepositReceiptsUtil(final Spec spec) { + this.spec = spec; + } + + public List generateDepositReceipts(final BeaconState state) { + final UInt64 nextDepositReceiptIndex = UInt64.valueOf(state.getValidators().size()); + return IntStream.range(0, getNumberOfDepositReceiptsToGenerate()) + .mapToObj(i -> createDepositReceipt(state.getSlot(), nextDepositReceiptIndex.plus(i))) + .toList(); + } + + private int getNumberOfDepositReceiptsToGenerate() { + return random.nextInt(MAX_NUMBER_OF_DEPOSITS_PER_BLOCK + 1); + } + + private DepositReceipt createDepositReceipt(final UInt64 slot, final UInt64 index) { + final BLSKeyPair validatorKeyPair = BLSKeyPair.random(random); + final BLSPublicKey publicKey = validatorKeyPair.getPublicKey(); + final UInt64 depositAmount = UInt64.THIRTY_TWO_ETH; + final DepositMessage depositMessage = + new DepositMessage(publicKey, Bytes32.ZERO, depositAmount); + final MiscHelpers miscHelpers = spec.atSlot(slot).miscHelpers(); + final Bytes32 depositDomain = miscHelpers.computeDomain(Domain.DEPOSIT); + final BLSSignature signature = + BLS.sign( + validatorKeyPair.getSecretKey(), + miscHelpers.computeSigningRoot(depositMessage, depositDomain)); + return SchemaDefinitionsElectra.required(spec.atSlot(slot).getSchemaDefinitions()) + .getDepositReceiptSchema() + .create(publicKey, Bytes32.ZERO, depositAmount, signature, index); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannel.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannel.java index 336ddf3141e..3199e384002 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannel.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannel.java @@ -60,7 +60,7 @@ public SafeFuture engineForkChoiceUpdated( @Override public SafeFuture engineGetPayload( - final ExecutionPayloadContext executionPayloadContext, final UInt64 slot) { + final ExecutionPayloadContext executionPayloadContext, final BeaconState state) { return SafeFuture.completedFuture(null); } @@ -123,7 +123,7 @@ SafeFuture engineForkChoiceUpdated( * BeaconState, boolean, Optional, BlockProductionPerformance)} instead */ SafeFuture engineGetPayload( - ExecutionPayloadContext executionPayloadContext, UInt64 slot); + ExecutionPayloadContext executionPayloadContext, BeaconState state); // builder namespace SafeFuture builderRegisterValidators( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java index 10418ea516e..8d1decb640f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java @@ -61,9 +61,11 @@ import tech.pegasys.teku.spec.datastructures.execution.HeaderWithFallbackData; import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; import tech.pegasys.teku.spec.datastructures.execution.PowBlock; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.util.BlobsUtil; +import tech.pegasys.teku.spec.datastructures.util.DepositReceiptsUtil; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; @@ -73,6 +75,8 @@ public class ExecutionLayerChannelStub implements ExecutionLayerChannel { private static final ClientVersion STUB_CLIENT_VERSION = new ClientVersion("SB", ExecutionLayerChannel.STUB_ENDPOINT_PREFIX, "0.0.0", Bytes4.ZERO); + private static final boolean GENERATE_DEPOSIT_RECEIPTS = false; + private final TimeProvider timeProvider; private final Map knownBlocks = new ConcurrentHashMap<>(); private final Map knownPosBlocks = new ConcurrentHashMap<>(); @@ -81,6 +85,7 @@ public class ExecutionLayerChannelStub implements ExecutionLayerChannel { private final Set requestedPowBlocks = new HashSet<>(); private final Spec spec; private final BlobsUtil blobsUtil; + private final DepositReceiptsUtil depositReceiptsUtil; private final Random random = new Random(); private PayloadStatus payloadStatus = PayloadStatus.VALID; @@ -122,6 +127,7 @@ public ExecutionLayerChannelStub( kzg = KZG.NOOP; } this.blobsUtil = new BlobsUtil(spec, kzg); + this.depositReceiptsUtil = new DepositReceiptsUtil(spec); } public ExecutionLayerChannelStub( @@ -222,7 +228,7 @@ public SafeFuture engineForkChoiceUpdated( @Override public SafeFuture engineGetPayload( - final ExecutionPayloadContext executionPayloadContext, final UInt64 slot) { + final ExecutionPayloadContext executionPayloadContext, final BeaconState state) { if (!bellatrixActivationDetected) { LOG.info( "getPayload received before terminalBlock has been sent. Assuming transition already happened"); @@ -230,6 +236,7 @@ public SafeFuture engineGetPayload( // do the activation check to be able to respond to terminal block verification checkBellatrixActivation(); } + final UInt64 slot = state.getSlot(); final Optional schemaDefinitionsBellatrix = spec.atSlot(slot).getSchemaDefinitions().toVersionBellatrix(); @@ -246,9 +253,7 @@ public SafeFuture engineGetPayload( final List transactions = generateTransactions(slot, headAndAttrs); final ExecutionPayload executionPayload = - spec.atSlot(slot) - .getSchemaDefinitions() - .toVersionBellatrix() + schemaDefinitionsBellatrix .orElseThrow() .getExecutionPayloadSchema() .createExecutionPayload( @@ -271,7 +276,7 @@ public SafeFuture engineGetPayload( .withdrawals(() -> payloadAttributes.getWithdrawals().orElse(List.of())) .blobGasUsed(() -> UInt64.ZERO) .excessBlobGas(() -> UInt64.ZERO) - .depositReceipts(List::of) + .depositReceipts(() -> generateDepositReceipts(state)) .exits(List::of)); // we assume all blocks are produced locally @@ -288,7 +293,7 @@ public SafeFuture engineGetPayload( LOG.info( "getPayload: payloadId: {} slot: {} -> executionPayload blockHash: {}", executionPayloadContext.getPayloadId(), - slot, + state.getSlot(), executionPayload.getBlockHash()); final GetPayloadResponse getPayloadResponse = @@ -346,7 +351,7 @@ public SafeFuture builderGetHeader( final SchemaDefinitions schemaDefinitions = spec.atSlot(slot).getSchemaDefinitions(); - return engineGetPayload(executionPayloadContext, slot) + return engineGetPayload(executionPayloadContext, state) .thenPeek(__ -> blockProductionPerformance.engineGetPayload()) .thenApply( getPayloadResponse -> { @@ -554,4 +559,18 @@ private Bytes generateBlobsAndTransaction( return blobsUtil.generateRawBlobTransactionFromKzgCommitments(commitments); } + + private List generateDepositReceipts(final BeaconState state) { + return spec.atSlot(state.getSlot()) + .getConfig() + .toVersionElectra() + .map( + __ -> { + if (GENERATE_DEPOSIT_RECEIPTS) { + return depositReceiptsUtil.generateDepositReceipts(state); + } + return List.of(); + }) + .orElse(List.of()); + } }