diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/NoopSyncService.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/NoopSyncService.java index 4e342290802..aa64d8b7555 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/NoopSyncService.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/NoopSyncService.java @@ -145,6 +145,11 @@ public boolean isRunning() { return false; } + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + // No-op + } + @Override public void onBlockValidated(final SignedBeaconBlock block) { // No-op diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/gossip/blocks/RecentBlocksFetchService.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/gossip/blocks/RecentBlocksFetchService.java index 1fe3354dc28..c06c0be2145 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/gossip/blocks/RecentBlocksFetchService.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/gossip/blocks/RecentBlocksFetchService.java @@ -128,6 +128,9 @@ public void processFetchedResult(final FetchBlockTask task, final SignedBeaconBl removeTask(task); } + @Override + public void onBlockSeen(final SignedBeaconBlock block) {} + @Override public void onBlockValidated(final SignedBeaconBlock block) {} diff --git a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java index 93d2d3f72c6..739aaab00d1 100644 --- a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java +++ b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java @@ -47,8 +47,10 @@ import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationManager; +import tech.pegasys.teku.statetransition.attestation.PayloadAttestationManager; import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTrackersPool; import tech.pegasys.teku.statetransition.block.BlockImportChannel; +import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceTrigger; import tech.pegasys.teku.statetransition.forkchoice.ProposersDataManager; import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; @@ -74,6 +76,8 @@ public class ValidatorApiHandlerIntegrationTest { private final BlockFactory blockFactory = mock(BlockFactory.class); private final AggregatingAttestationPool attestationPool = mock(AggregatingAttestationPool.class); private final AttestationManager attestationManager = mock(AttestationManager.class); + private final PayloadAttestationManager payloadAttestationManager = + mock(PayloadAttestationManager.class); private final AttestationTopicSubscriber attestationTopicSubscriber = mock(AttestationTopicSubscriber.class); private final ActiveValidatorTracker activeValidatorTracker = mock(ActiveValidatorTracker.class); @@ -114,6 +118,7 @@ public class ValidatorApiHandlerIntegrationTest { blobSidecarGossipChannel, attestationPool, attestationManager, + payloadAttestationManager, attestationTopicSubscriber, activeValidatorTracker, dutyMetrics, @@ -125,7 +130,8 @@ public class ValidatorApiHandlerIntegrationTest { syncCommitteeContributionPool, syncCommitteeSubscriptionManager, new BlockProductionAndPublishingPerformanceFactory( - new SystemTimeProvider(), __ -> UInt64.ZERO, true, 0, 0, 0, 0)); + new SystemTimeProvider(), __ -> UInt64.ZERO, true, 0, 0, 0, 0), + ReceivedBlockEventsChannel.NOOP); @BeforeEach public void setup() { diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 6fbfe51dd7b..81da90b8595 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -53,6 +53,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuty; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; @@ -77,10 +78,12 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -93,8 +96,10 @@ import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationManager; +import tech.pegasys.teku.statetransition.attestation.PayloadAttestationManager; import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTrackersPool; import tech.pegasys.teku.statetransition.block.BlockImportChannel; +import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceTrigger; import tech.pegasys.teku.statetransition.forkchoice.ProposersDataManager; import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; @@ -135,6 +140,7 @@ public class ValidatorApiHandler implements ValidatorApiChannel { private final BlockFactory blockFactory; private final AggregatingAttestationPool attestationPool; private final AttestationManager attestationManager; + private final PayloadAttestationManager payloadAttestationManager; private final AttestationTopicSubscriber attestationTopicSubscriber; private final ActiveValidatorTracker activeValidatorTracker; private final DutyMetrics dutyMetrics; @@ -145,8 +151,8 @@ public class ValidatorApiHandler implements ValidatorApiChannel { private final SyncCommitteeSubscriptionManager syncCommitteeSubscriptionManager; private final SyncCommitteeContributionPool syncCommitteeContributionPool; private final ProposersDataManager proposersDataManager; + private final ReceivedBlockEventsChannel receivedBlockEventsChannel; private final BlockPublisher blockPublisher; - private final AttesterDutiesGenerator attesterDutiesGenerator; public ValidatorApiHandler( @@ -162,6 +168,7 @@ public ValidatorApiHandler( final BlobSidecarGossipChannel blobSidecarGossipChannel, final AggregatingAttestationPool attestationPool, final AttestationManager attestationManager, + final PayloadAttestationManager payloadAttestationManager, final AttestationTopicSubscriber attestationTopicSubscriber, final ActiveValidatorTracker activeValidatorTracker, final DutyMetrics dutyMetrics, @@ -173,7 +180,8 @@ public ValidatorApiHandler( final SyncCommitteeContributionPool syncCommitteeContributionPool, final SyncCommitteeSubscriptionManager syncCommitteeSubscriptionManager, final BlockProductionAndPublishingPerformanceFactory - blockProductionAndPublishingPerformanceFactory) { + blockProductionAndPublishingPerformanceFactory, + final ReceivedBlockEventsChannel receivedBlockEventsChannel) { this.blockProductionAndPublishingPerformanceFactory = blockProductionAndPublishingPerformanceFactory; this.chainDataProvider = chainDataProvider; @@ -184,6 +192,7 @@ public ValidatorApiHandler( this.blockFactory = blockFactory; this.attestationPool = attestationPool; this.attestationManager = attestationManager; + this.payloadAttestationManager = payloadAttestationManager; this.attestationTopicSubscriber = attestationTopicSubscriber; this.activeValidatorTracker = activeValidatorTracker; this.dutyMetrics = dutyMetrics; @@ -194,6 +203,7 @@ public ValidatorApiHandler( this.syncCommitteeContributionPool = syncCommitteeContributionPool; this.syncCommitteeSubscriptionManager = syncCommitteeSubscriptionManager; this.proposersDataManager = proposersDataManager; + this.receivedBlockEventsChannel = receivedBlockEventsChannel; this.blockPublisher = new MilestoneBasedBlockPublisher( spec, @@ -268,6 +278,41 @@ public SafeFuture> getAttestationDuties( combinedChainDataClient.isChainHeadOptimistic()))); } + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + if (isSyncActive()) { + return NodeSyncingException.failedFuture(); + } + if (epoch.isGreaterThan( + combinedChainDataClient + .getCurrentEpoch() + .plus(spec.getSpecConfig(epoch).getMinSeedLookahead() + DUTY_EPOCH_TOLERANCE))) { + return SafeFuture.failedFuture( + new IllegalArgumentException( + String.format( + "Payload attestation duties were requested %s epochs ahead, only 1 epoch in future is supported.", + epoch.minus(combinedChainDataClient.getCurrentEpoch()).toString()))); + } + // what state can we use? If the current or next epoch, we can use the best state, + // which would guarantee no state regeneration + final UInt64 slot = spec.getEarliestQueryableSlotForBeaconCommitteeInTargetEpoch(epoch); + + LOG.trace( + "Retrieving payload attestation duties from epoch {} using state at slot {}", epoch, slot); + return combinedChainDataClient + .getStateAtSlotExact(slot) + .thenApply( + optionalState -> + optionalState.map( + state -> + attesterDutiesGenerator.getPayloadAttesterDutiesFromIndicesAndState( + state, + epoch, + validatorIndices, + combinedChainDataClient.isChainHeadOptimistic()))); + } + @Override public SafeFuture> getSyncCommitteeDuties( final UInt64 epoch, final IntCollection validatorIndices) { @@ -477,6 +522,61 @@ public SafeFuture> createAttestationData( } } + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + if (isSyncActive()) { + return NodeSyncingException.failedFuture(); + } + + return combinedChainDataClient + .getTimelyExecutionPayload(slot) + .thenApply( + executionPayloadEnvelope -> + executionPayloadEnvelope.map( + payloadStatusAndBeaconBlockRoot -> + PayloadAttestationData.SSZ_SCHEMA.create( + payloadStatusAndBeaconBlockRoot.beaconBlockRoot(), + slot, + payloadStatusAndBeaconBlockRoot.payloadStatus().getCode()))); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return SafeFuture.collectAll(attestations.stream().map(this::processPayloadAttestation)) + .thenApply(this::convertAttestationProcessingResultsToErrorList); + } + + private SafeFuture processPayloadAttestation( + final PayloadAttestationMessage attestation) { + return payloadAttestationManager + .addPayloadAttestation(attestation, Optional.empty()) + .thenPeek( + result -> { + if (!result.isReject()) { + // TODO update dutyMetrics and performanceTracker + } else { + VALIDATOR_LOGGER.producedInvalidPayloadAttestation( + attestation.getData().getSlot(), + result.getDescription().orElse("Unknown reason")); + } + }) + .exceptionally( + error -> { + LOG.error( + "Failed to send signed payload attestation for slot {}, block {}", + attestation.getData().getSlot(), + attestation.getData().getBeaconBlockRoot(), + error); + return InternalValidationResult.reject( + "Failed to send signed payload attestation for slot %s, block %s: %s", + attestation.getData().getSlot(), + attestation.getData().getBeaconBlockRoot(), + getMessageOrSimpleName(error)); + }); + } + private AttestationData createAttestationData( final BeaconBlock block, final BeaconState state, @@ -653,6 +753,7 @@ private SafeFuture processAggregateAndProof( public SafeFuture sendSignedBlock( final SignedBlockContainer maybeBlindedBlockContainer, final BroadcastValidationLevel broadcastValidationLevel) { + receivedBlockEventsChannel.onBlockSeen(maybeBlindedBlockContainer.getSignedBlock()); final BlockPublishingPerformance blockPublishingPerformance = blockProductionAndPublishingPerformanceFactory.createForPublishing( maybeBlindedBlockContainer.getSlot()); diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/duties/AttesterDutiesGenerator.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/duties/AttesterDutiesGenerator.java index a978a647edf..a8092412e1c 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/duties/AttesterDutiesGenerator.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/duties/AttesterDutiesGenerator.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.validator.coordinator.duties; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntCollection; import java.util.ArrayList; import java.util.List; @@ -21,6 +22,8 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuty; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuty; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.state.CommitteeAssignment; @@ -47,6 +50,20 @@ public AttesterDuties getAttesterDutiesFromIndicesAndState( return new AttesterDuties(isChainHeadOptimistic, dependentRoot, duties); } + public PayloadAttesterDuties getPayloadAttesterDutiesFromIndicesAndState( + final BeaconState state, + final UInt64 epoch, + final IntCollection validatorIndices, + final boolean isChainHeadOptimistic) { + final Bytes32 dependentRoot = + epoch.isGreaterThan(spec.getCurrentEpoch(state)) + ? spec.atEpoch(epoch).getBeaconStateUtil().getCurrentDutyDependentRoot(state) + : spec.atEpoch(epoch).getBeaconStateUtil().getPreviousDutyDependentRoot(state); + final List duties = + createPayloadAttesterDuties(state, epoch, validatorIndices); + return new PayloadAttesterDuties(isChainHeadOptimistic, dependentRoot, duties); + } + private List createAttesterDuties( final BeaconState state, final UInt64 epoch, final IntCollection validatorIndices) { final List> maybeAttesterDutyList = new ArrayList<>(); @@ -84,4 +101,26 @@ private Optional attesterDutyFromCommitteeAssignment( committeeAssignment.getCommittee().indexOf(validatorIndex), committeeAssignment.getSlot())); } + + private List createPayloadAttesterDuties( + final BeaconState state, final UInt64 epoch, final IntCollection validatorIndices) { + final List payloadAttesterDutyList = new ArrayList<>(); + + final Int2ObjectMap validatorIndexToCommitteeAssignmentMap = + spec.getValidatorIndexToPctAssignmentMap(state, epoch); + for (final int validatorIndex : validatorIndices) { + final UInt64 slot = validatorIndexToCommitteeAssignmentMap.get(validatorIndex); + if (slot != null) { + payloadAttesterDutyFromCommitteeAssignment(slot, validatorIndex, state) + .ifPresent(payloadAttesterDutyList::add); + } + } + return payloadAttesterDutyList; + } + + private Optional payloadAttesterDutyFromCommitteeAssignment( + final UInt64 slot, final int validatorIndex, final BeaconState state) { + return spec.getValidatorPubKey(state, UInt64.valueOf(validatorIndex)) + .map(publicKey -> new PayloadAttesterDuty(publicKey, validatorIndex, slot)); + } } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java index 548d254130b..f8b2a40dcff 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java @@ -68,9 +68,11 @@ import tech.pegasys.teku.spec.datastructures.execution.FallbackData; import tech.pegasys.teku.spec.datastructures.execution.FallbackReason; import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -1296,6 +1298,18 @@ public BeaconBlockBodyBuilder blobKzgCommitments( return this; } + @Override + public BeaconBlockBodyBuilder signedExecutionPayloadHeader( + final SignedExecutionPayloadHeader signedExecutionPayloadHeader) { + return this; + } + + @Override + public BeaconBlockBodyBuilder payloadAttestations( + final SszList payloadAttestations) { + return this; + } + @Override public BeaconBlockBody build() { return null; diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index 3afc7b5f86d..3e0c658ca84 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -119,9 +119,11 @@ import tech.pegasys.teku.spec.util.DataStructureUtil; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationManager; +import tech.pegasys.teku.statetransition.attestation.PayloadAttestationManager; import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTrackersPool; import tech.pegasys.teku.statetransition.block.BlockImportChannel; import tech.pegasys.teku.statetransition.block.BlockImportChannel.BlockImportAndBroadcastValidationResults; +import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceTrigger; import tech.pegasys.teku.statetransition.forkchoice.ProposersDataManager; import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; @@ -145,6 +147,8 @@ class ValidatorApiHandlerTest { private final BlockFactory blockFactory = mock(BlockFactory.class); private final AggregatingAttestationPool attestationPool = mock(AggregatingAttestationPool.class); private final AttestationManager attestationManager = mock(AttestationManager.class); + private final PayloadAttestationManager payloadAttestationManager = + mock(PayloadAttestationManager.class); private final AttestationTopicSubscriber attestationTopicSubscriptions = mock(AttestationTopicSubscriber.class); private final ActiveValidatorTracker activeValidatorTracker = mock(ActiveValidatorTracker.class); @@ -210,6 +214,7 @@ public void setUp() { blobSidecarGossipChannel, attestationPool, attestationManager, + payloadAttestationManager, attestationTopicSubscriptions, activeValidatorTracker, dutyMetrics, @@ -220,7 +225,8 @@ public void setUp() { syncCommitteeMessagePool, syncCommitteeContributionPool, syncCommitteeSubscriptionManager, - blockProductionPerformanceFactory); + blockProductionPerformanceFactory, + ReceivedBlockEventsChannel.NOOP); when(syncStateProvider.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC); when(forkChoiceTrigger.prepareForBlockProduction(any(), any())).thenReturn(SafeFuture.COMPLETE); @@ -465,6 +471,7 @@ void getSyncCommitteeDuties_shouldNotUseEpochPriorToFork() { blobSidecarGossipChannel, attestationPool, attestationManager, + payloadAttestationManager, attestationTopicSubscriptions, activeValidatorTracker, dutyMetrics, @@ -475,7 +482,8 @@ void getSyncCommitteeDuties_shouldNotUseEpochPriorToFork() { syncCommitteeMessagePool, syncCommitteeContributionPool, syncCommitteeSubscriptionManager, - blockProductionPerformanceFactory); + blockProductionPerformanceFactory, + ReceivedBlockEventsChannel.NOOP); // Best state is still in Phase0 final BeaconState state = dataStructureUtil.stateBuilderPhase0().slot(previousEpochStartSlot.minus(1)).build(); @@ -1376,6 +1384,7 @@ private void setupDeneb() { blobSidecarGossipChannel, attestationPool, attestationManager, + payloadAttestationManager, attestationTopicSubscriptions, activeValidatorTracker, dutyMetrics, @@ -1386,7 +1395,8 @@ private void setupDeneb() { syncCommitteeMessagePool, syncCommitteeContributionPool, syncCommitteeSubscriptionManager, - blockProductionPerformanceFactory); + blockProductionPerformanceFactory, + ReceivedBlockEventsChannel.NOOP); // BlobSidecar builder doAnswer( diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java index 5fd4833b78e..97218a551e1 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java @@ -170,6 +170,11 @@ public void onNewFinalizedCheckpoint( notifySubscribersOfEvent(EventType.finalized_checkpoint, event); } + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + // No-op + } + @Override public void onBlockValidated(final SignedBeaconBlock block) { onNewBlockGossip(block); diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java index 776eafc71fc..05d9db3241c 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java @@ -124,6 +124,7 @@ public BeaconBlock getBlindedBlock( block.getParentRoot(), block.getStateRoot(), getBlindedBlockBodyElectra(block.getBody())); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } @@ -167,6 +168,7 @@ public BeaconBlock getBeaconBlock( block.getParentRoot(), block.getStateRoot(), getBeaconBlockBodyElectra(block.getBody())); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } @@ -243,6 +245,7 @@ public BeaconState getBeaconState( case CAPELLA -> new BeaconStateCapella(state); case DENEB -> new BeaconStateDeneb(state); case ELECTRA -> new BeaconStateElectra(state); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } } diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java index f079edb1c81..fd4adf0bf09 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java @@ -178,6 +178,7 @@ public SignedBeaconBlock parseBlock(final JsonProvider jsonProvider, final Strin case CAPELLA -> mapper.treeToValue(jsonNode, SignedBeaconBlockCapella.class); case DENEB -> mapper.treeToValue(jsonNode, SignedBeaconBlockDeneb.class); case ELECTRA -> mapper.treeToValue(jsonNode, SignedBeaconBlockElectra.class); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } @@ -194,6 +195,7 @@ public SignedBeaconBlock parseBlindedBlock( case CAPELLA -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockCapella.class); case DENEB -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockDeneb.class); case ELECTRA -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockElectra.class); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java index 85ac4b86670..1fd5ac2fc18 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java @@ -22,7 +22,8 @@ public enum Version { bellatrix, capella, deneb, - electra; + electra, + eip7732; public static Version fromMilestone(final SpecMilestone milestone) { return switch (milestone) { @@ -32,6 +33,7 @@ public static Version fromMilestone(final SpecMilestone milestone) { case CAPELLA -> capella; case DENEB -> deneb; case ELECTRA -> electra; + case EIP7732 -> eip7732; }; } } diff --git a/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java b/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java index 424e6bc0400..bb25ad9eb9b 100644 --- a/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java +++ b/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java @@ -42,6 +42,7 @@ public void shouldConvertToInternalObject(final SpecContext ctx) { case CAPELLA -> new BeaconStateCapella(beaconStateInternal); case DENEB -> new BeaconStateDeneb(beaconStateInternal); case ELECTRA -> new BeaconStateElectra(beaconStateInternal); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; assertThat(beaconState.asInternalBeaconState(spec)).isEqualTo(beaconStateInternal); diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java index 22b8d6aab57..3a2ddd11fe9 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java @@ -61,8 +61,7 @@ public MilestoneBasedEngineJsonRpcMethodsResolver( .forEach( milestone -> { switch (milestone) { - case PHASE0: - case ALTAIR: + case PHASE0, ALTAIR: break; case BELLATRIX: methodsByMilestone.put(milestone, bellatrixSupportedMethods()); @@ -76,6 +75,9 @@ public MilestoneBasedEngineJsonRpcMethodsResolver( case ELECTRA: methodsByMilestone.put(milestone, electraSupportedMethods()); break; + case EIP7732: + // EIP7732 TODO: implement + break; } }); } diff --git a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuties.java b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuties.java new file mode 100644 index 00000000000..e10465f30cc --- /dev/null +++ b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuties.java @@ -0,0 +1,20 @@ +/* + * 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.ethereum.json.types.validator; + +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; + +public record PayloadAttesterDuties( + boolean executionOptimistic, Bytes32 dependentRoot, List duties) {} diff --git a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutiesBuilder.java b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutiesBuilder.java new file mode 100644 index 00000000000..787fad2ac62 --- /dev/null +++ b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutiesBuilder.java @@ -0,0 +1,72 @@ +/* + * 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.ethereum.json.types.validator; + +import static tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDutyBuilder.PAYLOAD_ATTESTER_DUTY_TYPE; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EXECUTION_OPTIMISTIC; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.BOOLEAN_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.BYTES32_TYPE; + +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; + +public class PayloadAttesterDutiesBuilder { + public static final DeserializableTypeDefinition + PAYLOAD_ATTESTER_DUTIES_RESPONSE_TYPE = + DeserializableTypeDefinition.object( + PayloadAttesterDuties.class, PayloadAttesterDutiesBuilder.class) + .name("GetPayloadAttesterDutiesResponse") + .initializer(PayloadAttesterDutiesBuilder::new) + .finisher(PayloadAttesterDutiesBuilder::build) + .withField( + "dependent_root", + BYTES32_TYPE, + PayloadAttesterDuties::dependentRoot, + PayloadAttesterDutiesBuilder::dependentRoot) + .withField( + EXECUTION_OPTIMISTIC, + BOOLEAN_TYPE, + PayloadAttesterDuties::executionOptimistic, + PayloadAttesterDutiesBuilder::executionOptimistic) + .withField( + "data", + DeserializableTypeDefinition.listOf(PAYLOAD_ATTESTER_DUTY_TYPE), + PayloadAttesterDuties::duties, + PayloadAttesterDutiesBuilder::duties) + .build(); + + private boolean executionOptimistic; + private Bytes32 dependentRoot; + private List duties; + + public PayloadAttesterDutiesBuilder executionOptimistic(final boolean executionOptimistic) { + this.executionOptimistic = executionOptimistic; + return this; + } + + public PayloadAttesterDutiesBuilder dependentRoot(final Bytes32 dependentRoot) { + this.dependentRoot = dependentRoot; + return this; + } + + public PayloadAttesterDutiesBuilder duties(final List duties) { + this.duties = duties; + return this; + } + + public PayloadAttesterDuties build() { + return new PayloadAttesterDuties(executionOptimistic, dependentRoot, duties); + } +} diff --git a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuty.java b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuty.java new file mode 100644 index 00000000000..c583db5ad88 --- /dev/null +++ b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDuty.java @@ -0,0 +1,19 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.ethereum.json.types.validator; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public record PayloadAttesterDuty(BLSPublicKey publicKey, int validatorIndex, UInt64 slot) {} diff --git a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutyBuilder.java b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutyBuilder.java new file mode 100644 index 00000000000..51be44a8b94 --- /dev/null +++ b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/validator/PayloadAttesterDutyBuilder.java @@ -0,0 +1,67 @@ +/* + * 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.ethereum.json.types.validator; + +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.PUBLIC_KEY_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.INTEGER_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.UINT64_TYPE; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PayloadAttesterDutyBuilder { + public static final DeserializableTypeDefinition PAYLOAD_ATTESTER_DUTY_TYPE = + DeserializableTypeDefinition.object( + PayloadAttesterDuty.class, PayloadAttesterDutyBuilder.class) + .name("AttesterDuty") + .initializer(PayloadAttesterDutyBuilder::new) + .finisher(PayloadAttesterDutyBuilder::build) + .withField( + "pubkey", + PUBLIC_KEY_TYPE, + PayloadAttesterDuty::publicKey, + PayloadAttesterDutyBuilder::publicKey) + .withField( + "validator_index", + INTEGER_TYPE, + PayloadAttesterDuty::validatorIndex, + PayloadAttesterDutyBuilder::validatorIndex) + .withField( + "slot", UINT64_TYPE, PayloadAttesterDuty::slot, PayloadAttesterDutyBuilder::slot) + .build(); + + private BLSPublicKey publicKey; + private int validatorIndex; + private UInt64 slot; + + public PayloadAttesterDutyBuilder publicKey(final BLSPublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public PayloadAttesterDutyBuilder validatorIndex(final int validatorIndex) { + this.validatorIndex = validatorIndex; + return this; + } + + public PayloadAttesterDutyBuilder slot(final UInt64 slot) { + this.slot = slot; + return this; + } + + public PayloadAttesterDuty build() { + return new PayloadAttesterDuty(publicKey, validatorIndex, slot); + } +} diff --git a/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java b/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java index 15582467bb2..a33994996dd 100644 --- a/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java +++ b/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java @@ -91,6 +91,7 @@ public class Eth2NetworkConfiguration { private final Optional capellaForkEpoch; private final Optional denebForkEpoch; private final Optional electraForkEpoch; + private final Optional eip7732ForkEpoch; private final Eth1Address eth1DepositContractAddress; private final Optional eth1DepositContractDeployBlock; private final Optional trustedSetup; @@ -121,6 +122,7 @@ private Eth2NetworkConfiguration( final Optional capellaForkEpoch, final Optional denebForkEpoch, final Optional electraForkEpoch, + final Optional eip7732ForkEpoch, final Optional terminalBlockHashOverride, final Optional totalTerminalDifficultyOverride, final Optional terminalBlockHashEpochOverride, @@ -143,6 +145,7 @@ private Eth2NetworkConfiguration( this.capellaForkEpoch = capellaForkEpoch; this.denebForkEpoch = denebForkEpoch; this.electraForkEpoch = electraForkEpoch; + this.eip7732ForkEpoch = eip7732ForkEpoch; this.eth1DepositContractAddress = eth1DepositContractAddress == null ? spec.getGenesisSpecConfig().getDepositContractAddress() @@ -230,6 +233,7 @@ public Optional getForkEpoch(final SpecMilestone specMilestone) { case CAPELLA -> capellaForkEpoch; case DENEB -> denebForkEpoch; case ELECTRA -> electraForkEpoch; + case EIP7732 -> eip7732ForkEpoch; default -> Optional.empty(); }; } @@ -310,6 +314,7 @@ public boolean equals(final Object o) { && Objects.equals(capellaForkEpoch, that.capellaForkEpoch) && Objects.equals(denebForkEpoch, that.denebForkEpoch) && Objects.equals(electraForkEpoch, that.electraForkEpoch) + && Objects.equals(eip7732ForkEpoch, that.eip7732ForkEpoch) && Objects.equals(eth1DepositContractAddress, that.eth1DepositContractAddress) && Objects.equals(eth1DepositContractDeployBlock, that.eth1DepositContractDeployBlock) && Objects.equals(trustedSetup, that.trustedSetup) @@ -334,6 +339,7 @@ public int hashCode() { capellaForkEpoch, denebForkEpoch, electraForkEpoch, + eip7732ForkEpoch, eth1DepositContractAddress, eth1DepositContractDeployBlock, trustedSetup, @@ -374,6 +380,7 @@ public static class Builder { private Optional capellaForkEpoch = Optional.empty(); private Optional denebForkEpoch = Optional.empty(); private Optional electraForkEpoch = Optional.empty(); + private Optional eip7732ForkEpoch = Optional.empty(); private Optional terminalBlockHashOverride = Optional.empty(); private Optional totalTerminalDifficultyOverride = Optional.empty(); private Optional terminalBlockHashEpochOverride = Optional.empty(); @@ -427,6 +434,9 @@ public Eth2NetworkConfiguration build() { builder.electraBuilder( electraBuilder -> electraForkEpoch.ifPresent(electraBuilder::electraForkEpoch)); + builder.eip7732Builder( + eip7732Builder -> + eip7732ForkEpoch.ifPresent(eip7732Builder::eip7732ForkEpoch)); }); } if (spec.getForkSchedule().getSupportedMilestones().contains(SpecMilestone.DENEB) @@ -459,6 +469,7 @@ public Eth2NetworkConfiguration build() { capellaForkEpoch, denebForkEpoch, electraForkEpoch, + eip7732ForkEpoch, terminalBlockHashOverride, totalTerminalDifficultyOverride, terminalBlockHashEpochOverride, @@ -670,6 +681,11 @@ public Builder electraForkEpoch(final UInt64 electraForkEpoch) { return this; } + public Builder eip7732ForkEpoch(final UInt64 eip7732ForkEpoch) { + this.eip7732ForkEpoch = Optional.of(eip7732ForkEpoch); + return this; + } + public Builder safeSlotsToImportOptimistically(final int safeSlotsToImportOptimistically) { if (safeSlotsToImportOptimistically < 0) { throw new InvalidConfigurationException( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 0f8b0fa2a14..12100d2f3b6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntList; import java.io.File; import java.io.IOException; @@ -65,8 +66,11 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockUnblinder; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; import tech.pegasys.teku.spec.datastructures.forkchoice.MutableStore; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyStore; @@ -100,6 +104,7 @@ import tech.pegasys.teku.spec.logic.common.util.LightClientUtil; import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; public class Spec { @@ -448,6 +453,26 @@ public Bytes computeSigningRoot(final AggregateAndProof proof, final Bytes32 dom .computeSigningRoot(proof, domain); } + public Bytes computeSigningRoot( + final ExecutionPayloadHeader executionPayloadHeader, final Bytes32 domain) { + return atSlot(ExecutionPayloadHeaderEip7732.required(executionPayloadHeader).getSlot()) + .miscHelpers() + .computeSigningRoot(executionPayloadHeader, domain); + } + + public Bytes computeSigningRoot(final ExecutionPayloadEnvelope envelope, final Bytes32 domain) { + return forMilestone(envelope.getPayload().getMilestone()) + .miscHelpers() + .computeSigningRoot(envelope, domain); + } + + public Bytes computeSigningRoot( + final PayloadAttestationData payloadAttestationData, final Bytes32 domain) { + return atSlot(payloadAttestationData.getSlot()) + .miscHelpers() + .computeSigningRoot(payloadAttestationData, domain); + } + public Bytes computeSigningRoot(final UInt64 slot, final Bytes32 domain) { return atSlot(slot).miscHelpers().computeSigningRoot(slot, domain); } @@ -856,6 +881,11 @@ public Int2IntMap getBeaconCommitteesSize(final BeaconState state, final UInt64 return atState(state).beaconStateAccessors().getBeaconCommitteesSize(state, slot); } + public IntList getPtc(final BeaconState state, final UInt64 slot) { + return BeaconStateAccessorsEip7732.required(atState(state).beaconStateAccessors()) + .getPtc(state, slot); + } + public Optional getValidatorPubKey( final BeaconState state, final UInt64 validatorIndex) { return atState(state).beaconStateAccessors().getValidatorPubKey(state, validatorIndex); @@ -876,6 +906,11 @@ public Optional getCommitteeAssignment( return atEpoch(epoch).getValidatorsUtil().getCommitteeAssignment(state, epoch, validatorIndex); } + public Optional getPtcAssignment( + final BeaconState state, final UInt64 epoch, final int validatorIndex) { + return atEpoch(epoch).getValidatorsUtil().getCommitteeAssignment(state, epoch, validatorIndex); + } + public Map getValidatorIndexToCommitteeAssignmentMap( final BeaconState state, final UInt64 epoch) { return atEpoch(epoch) @@ -883,6 +918,11 @@ public Map getValidatorIndexToCommitteeAssignmentM .getValidatorIndexToCommitteeAssignmentMap(state, epoch); } + public Int2ObjectMap getValidatorIndexToPctAssignmentMap( + final BeaconState state, final UInt64 epoch) { + return atEpoch(epoch).getValidatorsUtil().getValidatorIndexToPctAssignmentMap(state, epoch); + } + // Attestation helpers public IntList getAttestingIndices(final BeaconState state, final Attestation attestation) { return atSlot(attestation.getData().getSlot()) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java index 4c06267bcab..72f3c8c6d76 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java @@ -17,6 +17,7 @@ import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; import static tech.pegasys.teku.spec.SpecMilestone.DENEB; +import static tech.pegasys.teku.spec.SpecMilestone.EIP7732; import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; @@ -28,6 +29,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; @@ -63,9 +65,16 @@ public static Spec create(final SpecConfig config) { .toVersionElectra() .map(SpecConfigElectra::getElectraForkEpoch) .orElse(FAR_FUTURE_EPOCH); + final UInt64 eip7732ForkEpoch = + config + .toVersionEip7732() + .map(SpecConfigEip7732::getEip7732ForkEpoch) + .orElse(FAR_FUTURE_EPOCH); final SpecMilestone highestMilestoneSupported; - if (!electraForkEpoch.equals(FAR_FUTURE_EPOCH)) { + if (!eip7732ForkEpoch.equals(FAR_FUTURE_EPOCH)) { + highestMilestoneSupported = EIP7732; + } else if (!electraForkEpoch.equals(FAR_FUTURE_EPOCH)) { highestMilestoneSupported = ELECTRA; } else if (!denebForkEpoch.equals(FAR_FUTURE_EPOCH)) { highestMilestoneSupported = DENEB; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java index 543bc2f6cad..2edba414c05 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java @@ -26,6 +26,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; import tech.pegasys.teku.spec.config.SpecConfigElectra; public enum SpecMilestone { @@ -34,7 +35,8 @@ public enum SpecMilestone { BELLATRIX, CAPELLA, DENEB, - ELECTRA; + ELECTRA, + EIP7732; /** * Returns true if this milestone is at or after the supplied milestone ({@code other}) @@ -114,13 +116,15 @@ static Optional getForkVersion( case CAPELLA -> specConfig.toVersionCapella().map(SpecConfigCapella::getCapellaForkVersion); case DENEB -> specConfig.toVersionDeneb().map(SpecConfigDeneb::getDenebForkVersion); case ELECTRA -> specConfig.toVersionElectra().map(SpecConfigElectra::getElectraForkVersion); + case EIP7732 -> specConfig.toVersionEip7732().map(SpecConfigEip7732::getEip7732ForkVersion); }; } static Optional getForkEpoch(final SpecConfig specConfig, final SpecMilestone milestone) { return switch (milestone) { case PHASE0 -> - // Phase0 can only ever start at epoch 0 - no non-zero slot is valid. However, another fork + // Phase0 can only ever start at epoch 0 - no non-zero slot is valid. However, another + // fork // may also be configured to start at epoch 0, effectively overriding phase0 Optional.of(UInt64.ZERO); case ALTAIR -> specConfig.toVersionAltair().map(SpecConfigAltair::getAltairForkEpoch); @@ -130,6 +134,7 @@ static Optional getForkEpoch(final SpecConfig specConfig, final SpecMile case CAPELLA -> specConfig.toVersionCapella().map(SpecConfigCapella::getCapellaForkEpoch); case DENEB -> specConfig.toVersionDeneb().map(SpecConfigDeneb::getDenebForkEpoch); case ELECTRA -> specConfig.toVersionElectra().map(SpecConfigElectra::getElectraForkEpoch); + case EIP7732 -> specConfig.toVersionEip7732().map(SpecConfigEip7732::getEip7732ForkEpoch); }; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java index 029d0b247e8..69ba5aa0626 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.logic.DelegatingSpecLogic; import tech.pegasys.teku.spec.logic.SpecLogic; @@ -28,6 +29,7 @@ import tech.pegasys.teku.spec.logic.versions.bellatrix.SpecLogicBellatrix; import tech.pegasys.teku.spec.logic.versions.capella.SpecLogicCapella; import tech.pegasys.teku.spec.logic.versions.deneb.SpecLogicDeneb; +import tech.pegasys.teku.spec.logic.versions.eip7732.SpecLogicEip7732; import tech.pegasys.teku.spec.logic.versions.electra.SpecLogicElectra; import tech.pegasys.teku.spec.logic.versions.phase0.SpecLogicPhase0; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; @@ -35,6 +37,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsPhase0; @@ -63,6 +66,7 @@ public static Optional create( case CAPELLA -> specConfig.toVersionCapella().map(SpecVersion::createCapella); case DENEB -> specConfig.toVersionDeneb().map(SpecVersion::createDeneb); case ELECTRA -> specConfig.toVersionElectra().map(SpecVersion::createElectra); + case EIP7732 -> specConfig.toVersionEip7732().map(SpecVersion::createEip7732); }; } @@ -108,6 +112,13 @@ static SpecVersion createElectra(final SpecConfigElectra specConfig) { return new SpecVersion(SpecMilestone.ELECTRA, specConfig, schemaDefinitions, specLogic); } + static SpecVersion createEip7732(final SpecConfigEip7732 specConfig) { + final SchemaDefinitionsEip7732 schemaDefinitions = new SchemaDefinitionsEip7732(specConfig); + final SpecLogicEip7732 specLogic = + SpecLogicEip7732.create(specConfig, schemaDefinitions, SYSTEM_TIME_PROVIDER); + return new SpecVersion(SpecMilestone.EIP7732, specConfig, schemaDefinitions, specLogic); + } + public SpecMilestone getMilestone() { return milestone; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfigElectra.java new file mode 100644 index 00000000000..0077a4c9ca4 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfigElectra.java @@ -0,0 +1,113 @@ +/* + * 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.config; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class DelegatingSpecConfigElectra extends DelegatingSpecConfigDeneb + implements SpecConfigElectra { + private final SpecConfigElectra specConfigElectra; + + public DelegatingSpecConfigElectra(final SpecConfigElectra specConfig) { + super(specConfig); + this.specConfigElectra = SpecConfigElectra.required(specConfig); + } + + @Override + public Bytes4 getElectraForkVersion() { + return specConfigElectra.getElectraForkVersion(); + } + + @Override + public UInt64 getElectraForkEpoch() { + return specConfigElectra.getElectraForkEpoch(); + } + + @Override + public UInt64 getMinPerEpochChurnLimitElectra() { + return specConfigElectra.getMinPerEpochChurnLimitElectra(); + } + + @Override + public UInt64 getMinActivationBalance() { + return specConfigElectra.getMinActivationBalance(); + } + + @Override + public UInt64 getMaxEffectiveBalanceElectra() { + return specConfigElectra.getMaxEffectiveBalanceElectra(); + } + + @Override + public int getPendingBalanceDepositsLimit() { + return specConfigElectra.getPendingBalanceDepositsLimit(); + } + + @Override + public int getPendingPartialWithdrawalsLimit() { + return specConfigElectra.getPendingPartialWithdrawalsLimit(); + } + + @Override + public int getPendingConsolidationsLimit() { + return specConfigElectra.getPendingConsolidationsLimit(); + } + + @Override + public int getMinSlashingPenaltyQuotientElectra() { + return specConfigElectra.getMinSlashingPenaltyQuotientElectra(); + } + + @Override + public int getWhistleblowerRewardQuotientElectra() { + return specConfigElectra.getWhistleblowerRewardQuotientElectra(); + } + + @Override + public int getMaxAttesterSlashingsElectra() { + return specConfigElectra.getMaxAttesterSlashingsElectra(); + } + + @Override + public int getMaxAttestationsElectra() { + return specConfigElectra.getMaxAttestationsElectra(); + } + + @Override + public int getMaxConsolidationRequestsPerPayload() { + return specConfigElectra.getMaxConsolidationRequestsPerPayload(); + } + + @Override + public int getMaxDepositRequestsPerPayload() { + return specConfigElectra.getMaxDepositRequestsPerPayload(); + } + + @Override + public int getMaxWithdrawalRequestsPerPayload() { + return specConfigElectra.getMaxWithdrawalRequestsPerPayload(); + } + + @Override + public int getMaxPendingPartialsPerWithdrawalsSweep() { + return specConfigElectra.getMaxPendingPartialsPerWithdrawalsSweep(); + } + + @Override + public Optional toVersionElectra() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7732.java new file mode 100644 index 00000000000..12878fcd6e9 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7732.java @@ -0,0 +1,19 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.config; + +public interface NetworkingSpecConfigEip7732 extends NetworkingSpecConfigDeneb { + + int getMaxRequestPayloads(); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java index 54802ae2332..0af7a4652a8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java @@ -188,4 +188,8 @@ default Optional toVersionDeneb() { default Optional toVersionElectra() { return Optional.empty(); } + + default Optional toVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732.java new file mode 100644 index 00000000000..95199ac5632 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732.java @@ -0,0 +1,44 @@ +/* + * 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.config; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public interface SpecConfigEip7732 extends SpecConfigElectra, NetworkingSpecConfigEip7732 { + + static SpecConfigEip7732 required(final SpecConfig specConfig) { + return specConfig + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 spec config but got: " + + specConfig.getClass().getSimpleName())); + } + + Bytes4 getEip7732ForkVersion(); + + UInt64 getEip7732ForkEpoch(); + + int getPtcSize(); + + int getMaxPayloadAttestations(); + + int getKzgCommitmentInclusionProofDepthEip7732(); + + @Override + Optional toVersionEip7732(); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732Impl.java new file mode 100644 index 00000000000..57075661f25 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7732Impl.java @@ -0,0 +1,113 @@ +/* + * 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.config; + +import java.util.Objects; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class SpecConfigEip7732Impl extends DelegatingSpecConfigElectra + implements SpecConfigEip7732 { + + private final Bytes4 eip7732ForkVersion; + private final UInt64 eip7732ForkEpoch; + + private final int ptcSize; + private final int maxPayloadAttestations; + private final int kzgCommitmentInclusionProofDepthEip7732; + private final int maxRequestPayloads; + + public SpecConfigEip7732Impl( + final SpecConfigElectra specConfig, + final Bytes4 eip7732ForkVersion, + final UInt64 eip7732ForkEpoch, + final int ptcSize, + final int maxPayloadAttestations, + final int kzgCommitmentInclusionProofDepthEip7732, + final int maxRequestPayloads) { + super(specConfig); + this.eip7732ForkVersion = eip7732ForkVersion; + this.eip7732ForkEpoch = eip7732ForkEpoch; + this.ptcSize = ptcSize; + this.maxPayloadAttestations = maxPayloadAttestations; + this.kzgCommitmentInclusionProofDepthEip7732 = kzgCommitmentInclusionProofDepthEip7732; + this.maxRequestPayloads = maxRequestPayloads; + } + + @Override + public Bytes4 getEip7732ForkVersion() { + return eip7732ForkVersion; + } + + @Override + public UInt64 getEip7732ForkEpoch() { + return eip7732ForkEpoch; + } + + @Override + public int getPtcSize() { + return ptcSize; + } + + @Override + public int getMaxPayloadAttestations() { + return maxPayloadAttestations; + } + + @Override + public int getKzgCommitmentInclusionProofDepthEip7732() { + return kzgCommitmentInclusionProofDepthEip7732; + } + + @Override + public int getMaxRequestPayloads() { + return maxRequestPayloads; + } + + @Override + public Optional toVersionEip7732() { + return Optional.of(this); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final SpecConfigEip7732Impl that = (SpecConfigEip7732Impl) o; + return Objects.equals(specConfig, that.specConfig) + && Objects.equals(eip7732ForkVersion, that.eip7732ForkVersion) + && Objects.equals(eip7732ForkEpoch, that.eip7732ForkEpoch) + && ptcSize == that.ptcSize + && maxPayloadAttestations == that.maxPayloadAttestations + && kzgCommitmentInclusionProofDepthEip7732 == that.kzgCommitmentInclusionProofDepthEip7732 + && maxRequestPayloads == that.maxRequestPayloads; + } + + @Override + public int hashCode() { + return Objects.hash( + specConfig, + eip7732ForkVersion, + eip7732ForkEpoch, + ptcSize, + maxPayloadAttestations, + kzgCommitmentInclusionProofDepthEip7732, + maxRequestPayloads); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java index 6bfb9297322..ce4db41b4a6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java @@ -48,6 +48,7 @@ import tech.pegasys.teku.spec.config.builder.BellatrixBuilder; import tech.pegasys.teku.spec.config.builder.CapellaBuilder; import tech.pegasys.teku.spec.config.builder.DenebBuilder; +import tech.pegasys.teku.spec.config.builder.Eip7732Builder; import tech.pegasys.teku.spec.config.builder.ElectraBuilder; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; @@ -207,6 +208,16 @@ public void loadFromMap( unprocessedConfig.remove(constantKey); }); + // Process eip7732 config + streamConfigSetters(Eip7732Builder.class) + .forEach( + setter -> { + final String constantKey = camelToSnakeCase(setter.getName()); + final Object rawValue = unprocessedConfig.get(constantKey); + invokeSetter(setter, configBuilder::eip7732Builder, constantKey, rawValue); + unprocessedConfig.remove(constantKey); + }); + // Check any constants that have been configured and then ignore final Set configuredConstants = Sets.intersection(CONSTANT_KEYS, unprocessedConfig.keySet()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7732Builder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7732Builder.java new file mode 100644 index 00000000000..b36d1971ea7 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7732Builder.java @@ -0,0 +1,124 @@ +/* + * 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.config.builder; + +import static com.google.common.base.Preconditions.checkNotNull; +import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.config.SpecConfigEip7732Impl; +import tech.pegasys.teku.spec.config.SpecConfigElectra; + +public class Eip7732Builder implements ForkConfigBuilder { + + private Bytes4 eip7732ForkVersion; + private UInt64 eip7732ForkEpoch; + + private Integer ptcSize; + private Integer maxPayloadAttestations; + private Integer kzgCommitmentInclusionProofDepthEip7732; + private Integer maxRequestPayloads; + + Eip7732Builder() {} + + @Override + public SpecConfigEip7732 build(final SpecConfigElectra specConfig) { + return new SpecConfigEip7732Impl( + specConfig, + eip7732ForkVersion, + eip7732ForkEpoch, + ptcSize, + maxPayloadAttestations, + kzgCommitmentInclusionProofDepthEip7732, + maxRequestPayloads); + } + + public Eip7732Builder eip7732ForkEpoch(final UInt64 eip7732ForkEpoch) { + checkNotNull(eip7732ForkEpoch); + this.eip7732ForkEpoch = eip7732ForkEpoch; + return this; + } + + public Eip7732Builder eip7732ForkVersion(final Bytes4 eip7732ForkVersion) { + checkNotNull(eip7732ForkVersion); + this.eip7732ForkVersion = eip7732ForkVersion; + return this; + } + + public Eip7732Builder ptcSize(final Integer ptcSize) { + checkNotNull(ptcSize); + this.ptcSize = ptcSize; + return this; + } + + public Eip7732Builder maxPayloadAttestations(final Integer maxPayloadAttestations) { + checkNotNull(maxPayloadAttestations); + this.maxPayloadAttestations = maxPayloadAttestations; + return this; + } + + public Eip7732Builder kzgCommitmentInclusionProofDepthEip7732( + final Integer kzgCommitmentInclusionProofDepthEip7732) { + checkNotNull(kzgCommitmentInclusionProofDepthEip7732); + this.kzgCommitmentInclusionProofDepthEip7732 = kzgCommitmentInclusionProofDepthEip7732; + return this; + } + + public Eip7732Builder maxRequestPayloads(final Integer maxRequestPayloads) { + checkNotNull(maxRequestPayloads); + this.maxRequestPayloads = maxRequestPayloads; + return this; + } + + @Override + public void validate() { + if (eip7732ForkEpoch == null) { + eip7732ForkEpoch = SpecConfig.FAR_FUTURE_EPOCH; + eip7732ForkVersion = SpecBuilderUtil.PLACEHOLDER_FORK_VERSION; + } + + // Fill default zeros if fork is unsupported + if (eip7732ForkEpoch.equals(FAR_FUTURE_EPOCH)) { + SpecBuilderUtil.fillMissingValuesWithZeros(this); + } + + validateConstants(); + } + + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + + constants.put("eip7732ForkEpoch", eip7732ForkEpoch); + constants.put("eip7732ForkVersion", eip7732ForkVersion); + constants.put("ptcSize", ptcSize); + constants.put("maxPayloadAttestations", maxPayloadAttestations); + constants.put( + "kzgCommitmentInclusionProofDepthEip7732", kzgCommitmentInclusionProofDepthEip7732); + constants.put("maxRequestPayloads", maxRequestPayloads); + + return constants; + } + + @Override + public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) { + rawConfig.accept("EIP7732_FORK_EPOCH", eip7732ForkEpoch); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java index d79d7723be4..c6bc574e93c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java @@ -28,7 +28,7 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; import tech.pegasys.teku.spec.config.SpecConfigPhase0; @SuppressWarnings({"UnusedReturnValue", "unused"}) @@ -131,12 +131,13 @@ public class SpecConfigBuilder { private Integer reorgParentWeightThreshold = 160; private UInt64 maxPerEpochActivationExitChurnLimit = UInt64.valueOf(256000000000L); - private final BuilderChain builderChain = + private final BuilderChain builderChain = BuilderChain.create(new AltairBuilder()) .appendBuilder(new BellatrixBuilder()) .appendBuilder(new CapellaBuilder()) .appendBuilder(new DenebBuilder()) - .appendBuilder(new ElectraBuilder()); + .appendBuilder(new ElectraBuilder()) + .appendBuilder(new Eip7732Builder()); public SpecConfig build() { builderChain.addOverridableItemsToRawConfig( @@ -740,4 +741,9 @@ public SpecConfigBuilder electraBuilder(final Consumer consumer) builderChain.withBuilder(ElectraBuilder.class, consumer); return this; } + + public SpecConfigBuilder eip7732Builder(final Consumer consumer) { + builderChain.withBuilder(Eip7732Builder.class, consumer); + return this; + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/Domain.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/Domain.java index 9666cb38f7e..ac42dece1b6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/Domain.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/Domain.java @@ -36,4 +36,8 @@ public class Domain { // Electra public static final Bytes4 DOMAIN_CONSOLIDATION = Bytes4.fromHexString("0x0B000000"); + + // EIP-7732 + public static final Bytes4 BEACON_BUILDER = Bytes4.fromHexString("0x1B000000"); + public static final Bytes4 PTC_ATTESTER = Bytes4.fromHexString("0x0C000000"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java index 2bbbf82f076..5d31a694ea1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java @@ -17,6 +17,7 @@ public class NetworkConstants { public static final int SYNC_COMMITTEE_SUBNET_COUNT = 4; public static final int INTERVALS_PER_SLOT = 3; + public static final int INTERVALS_PER_SLOT_EIP7732 = 4; public static final int DEFAULT_SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY = 128; public static final int NODE_ID_BITS = 256; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/PayloadStatus.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/PayloadStatus.java new file mode 100644 index 00000000000..c05a98089e6 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/PayloadStatus.java @@ -0,0 +1,31 @@ +/* + * 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.constants; + +public enum PayloadStatus { + PAYLOAD_ABSENT((byte) 0), + PAYLOAD_PRESENT((byte) 1), + PAYLOAD_WITHHELD((byte) 2), + PAYLOAD_INVALID_STATUS((byte) 3); + + private final byte code; + + PayloadStatus(final byte code) { + this.code = code; + } + + public byte getCode() { + return code; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java index 2240f7549e7..afc0f320e08 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BlindedBeaconBlockBodyCapella; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BlindedBeaconBlockBodyDeneb; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodyEip7732; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodyElectra; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; @@ -36,6 +37,7 @@ import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -87,6 +89,10 @@ default Optional> getOptionalBlobKzgCommitments() { return Optional.empty(); } + default Optional> getOptionalPayloadAttestations() { + return Optional.empty(); + } + default boolean isBlinded() { return false; } @@ -122,6 +128,10 @@ default Optional toVersionElectra() { return Optional.empty(); } + default Optional toVersionEip7732() { + return Optional.empty(); + } + default Optional toBlindedVersionDeneb() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodyBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodyBuilder.java index abaf77057b9..30fcc4a8a46 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodyBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodyBuilder.java @@ -20,9 +20,11 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -73,5 +75,10 @@ default Boolean supportsKzgCommitments() { BeaconBlockBodyBuilder blobKzgCommitments(SszList blobKzgCommitments); + BeaconBlockBodyBuilder signedExecutionPayloadHeader( + SignedExecutionPayloadHeader signedExecutionPayloadHeader); + + BeaconBlockBodyBuilder payloadAttestations(SszList payloadAttestations); + BeaconBlockBody build(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java index 633678af8b7..cfd8f1f6773 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.bellatrix.BeaconBlockBodySchemaBellatrix; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BeaconBlockBodySchemaCapella; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodySchemaDeneb; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodySchemaEip7732; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectra; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -70,6 +71,10 @@ default Optional> toVersionElectra() { return Optional.empty(); } + default Optional> toVersionEip7732() { + return Optional.empty(); + } + /** * getBlindedNodeGeneralizedIndices * diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/common/BlockBodyFields.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/common/BlockBodyFields.java index a4d05877fa0..b7ea354ac4e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/common/BlockBodyFields.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/common/BlockBodyFields.java @@ -30,7 +30,8 @@ public enum BlockBodyFields implements SszFieldName { EXECUTION_PAYLOAD_HEADER, BLS_TO_EXECUTION_CHANGES, BLOB_KZG_COMMITMENTS, - CONSOLIDATIONS; + SIGNED_EXECUTION_PAYLOAD_HEADER, + PAYLOAD_ATTESTATIONS; private final String sszFieldName; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyBuilderEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyBuilderEip7732.java new file mode 100644 index 00000000000..4dde72829bd --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyBuilderEip7732.java @@ -0,0 +1,82 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732; + +import static com.google.common.base.Preconditions.checkNotNull; + +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyBuilderElectra; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class BeaconBlockBodyBuilderEip7732 extends BeaconBlockBodyBuilderElectra { + + private SignedExecutionPayloadHeader signedExecutionPayloadHeader; + private SszList payloadAttestations; + + public BeaconBlockBodyBuilderEip7732( + final BeaconBlockBodySchema schema) { + super(schema, null); + } + + @Override + public BeaconBlockBodyBuilder signedExecutionPayloadHeader( + final SignedExecutionPayloadHeader signedExecutionPayloadHeader) { + this.signedExecutionPayloadHeader = signedExecutionPayloadHeader; + return this; + } + + @Override + public BeaconBlockBodyBuilder payloadAttestations( + final SszList payloadAttestations) { + this.payloadAttestations = payloadAttestations; + return this; + } + + @Override + protected void validate() { + // EIP7732 TODO: hacky skip of super.validate() to avoid blobKzgCommitments and executionPayload + // checks + // super.validate(); + checkNotNull(signedExecutionPayloadHeader, "signedExecutionPayloadHeader must be specified"); + checkNotNull(payloadAttestations, "payloadAttestations must be specified"); + } + + @Override + public BeaconBlockBody build() { + validate(); + + final BeaconBlockBodySchemaEip7732Impl schema = + getAndValidateSchema(false, BeaconBlockBodySchemaEip7732Impl.class); + return new BeaconBlockBodyEip7732Impl( + schema, + new SszSignature(randaoReveal), + eth1Data, + SszBytes32.of(graffiti), + proposerSlashings, + attesterSlashings, + attestations, + deposits, + voluntaryExits, + syncAggregate, + getBlsToExecutionChanges(), + signedExecutionPayloadHeader, + payloadAttestations); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732.java new file mode 100644 index 00000000000..4fa222cc042 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732.java @@ -0,0 +1,60 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; + +public interface BeaconBlockBodyEip7732 extends BeaconBlockBodyElectra { + static BeaconBlockBodyEip7732 required(final BeaconBlockBody body) { + return body.toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 block body but got " + body.getClass().getSimpleName())); + } + + @Override + BeaconBlockBodySchemaEip7732 getSchema(); + + SignedExecutionPayloadHeader getSignedExecutionPayloadHeader(); + + SszList getPayloadAttestations(); + + @Override + default Optional> getOptionalPayloadAttestations() { + return Optional.of(getPayloadAttestations()); + } + + @Override + default ExecutionPayloadElectra getExecutionPayload() { + throw new UnsupportedOperationException("ExecutionPayload removed in Eip7732"); + } + + @Override + default SszList getBlobKzgCommitments() { + throw new UnsupportedOperationException("BlobKzgCommitments removed in Eip7732"); + } + + @Override + default Optional toVersionEip7732() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732Impl.java new file mode 100644 index 00000000000..fe508536f3b --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodyEip7732Impl.java @@ -0,0 +1,175 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732; + +import static com.google.common.base.Preconditions.checkArgument; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.Container12; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; +import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; +import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class BeaconBlockBodyEip7732Impl + extends Container12< + BeaconBlockBodyEip7732Impl, + SszSignature, + Eth1Data, + SszBytes32, + SszList, + SszList, + SszList, + SszList, + SszList, + SyncAggregate, + SszList, + SignedExecutionPayloadHeader, + SszList> + implements BeaconBlockBodyEip7732 { + + BeaconBlockBodyEip7732Impl( + final BeaconBlockBodySchemaEip7732Impl type, + final SszSignature randaoReveal, + final Eth1Data eth1Data, + final SszBytes32 graffiti, + final SszList proposerSlashings, + final SszList attesterSlashings, + final SszList attestations, + final SszList deposits, + final SszList voluntaryExits, + final SyncAggregate syncAggregate, + final SszList blsToExecutionChanges, + final SignedExecutionPayloadHeader signedExecutionPayloadHeader, + final SszList payloadAttestations) { + super( + type, + randaoReveal, + eth1Data, + graffiti, + proposerSlashings, + attesterSlashings, + attestations, + deposits, + voluntaryExits, + syncAggregate, + blsToExecutionChanges, + signedExecutionPayloadHeader, + payloadAttestations); + } + + BeaconBlockBodyEip7732Impl(final BeaconBlockBodySchemaEip7732Impl type) { + super(type); + } + + BeaconBlockBodyEip7732Impl( + final BeaconBlockBodySchemaEip7732Impl type, final TreeNode backingNode) { + super(type, backingNode); + } + + public static BeaconBlockBodyEip7732Impl required(final BeaconBlockBody body) { + checkArgument( + body instanceof BeaconBlockBodyEip7732Impl, + "Expected Eip7732 block body but got %s", + body.getClass()); + return (BeaconBlockBodyEip7732Impl) body; + } + + @Override + public BLSSignature getRandaoReveal() { + return getField0().getSignature(); + } + + @Override + public SszSignature getRandaoRevealSsz() { + return getField0(); + } + + @Override + public Eth1Data getEth1Data() { + return getField1(); + } + + @Override + public Bytes32 getGraffiti() { + return getField2().get(); + } + + @Override + public SszBytes32 getGraffitiSsz() { + return getField2(); + } + + @Override + public SszList getProposerSlashings() { + return getField3(); + } + + @Override + public SszList getAttesterSlashings() { + return getField4(); + } + + @Override + public SszList getAttestations() { + return getField5(); + } + + @Override + public SszList getDeposits() { + return getField6(); + } + + @Override + public SszList getVoluntaryExits() { + return getField7(); + } + + @Override + public SyncAggregate getSyncAggregate() { + return getField8(); + } + + @Override + public SszList getBlsToExecutionChanges() { + return getField9(); + } + + @Override + public SignedExecutionPayloadHeader getSignedExecutionPayloadHeader() { + return getField10(); + } + + @Override + public SszList getPayloadAttestations() { + return getField11(); + } + + @Override + public BeaconBlockBodySchemaEip7732Impl getSchema() { + return (BeaconBlockBodySchemaEip7732Impl) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732.java new file mode 100644 index 00000000000..ad140eabd00 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732.java @@ -0,0 +1,46 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectra; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeaderSchema; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; + +public interface BeaconBlockBodySchemaEip7732 + extends BeaconBlockBodySchemaElectra { + + static BeaconBlockBodySchemaEip7732 required(final BeaconBlockBodySchema schema) { + checkArgument( + schema instanceof BeaconBlockBodySchemaEip7732, + "Expected a BeaconBlockBodySchemaEip7732 but was %s", + schema.getClass()); + return (BeaconBlockBodySchemaEip7732) schema; + } + + SignedExecutionPayloadHeaderSchema getSignedExecutionPayloadHeaderSchema(); + + SszListSchema getPayloadAttestationsSchema(); + + long getBlobKzgCommitmentsRootGeneralizedIndex(); + + @Override + default Optional> toVersionEip7732() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732Impl.java new file mode 100644 index 00000000000..8485dbcd1f7 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7732/BeaconBlockBodySchemaEip7732Impl.java @@ -0,0 +1,256 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732; + +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.function.Function; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema12; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.GIndexUtil; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.common.BlockBodyFields; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregateSchema; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeaderSchema; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing.AttesterSlashingSchema; +import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; +import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; +import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChangeSchema; +import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; +import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class BeaconBlockBodySchemaEip7732Impl + extends ContainerSchema12< + BeaconBlockBodyEip7732Impl, + SszSignature, + Eth1Data, + SszBytes32, + SszList, + SszList, + SszList, + SszList, + SszList, + SyncAggregate, + SszList, + SignedExecutionPayloadHeader, + SszList> + implements BeaconBlockBodySchemaEip7732 { + + protected BeaconBlockBodySchemaEip7732Impl( + final String containerName, + final NamedSchema randaoRevealSchema, + final NamedSchema eth1DataSchema, + final NamedSchema graffitiSchema, + final NamedSchema> proposerSlashingsSchema, + final NamedSchema> attesterSlashingsSchema, + final NamedSchema> attestationsSchema, + final NamedSchema> depositsSchema, + final NamedSchema> voluntaryExitsSchema, + final NamedSchema syncAggregateSchema, + final NamedSchema> blsToExecutionChange, + final NamedSchema signedExecutionPayloadHeader, + final NamedSchema> payloadAttestations) { + super( + containerName, + randaoRevealSchema, + eth1DataSchema, + graffitiSchema, + proposerSlashingsSchema, + attesterSlashingsSchema, + attestationsSchema, + depositsSchema, + voluntaryExitsSchema, + syncAggregateSchema, + blsToExecutionChange, + signedExecutionPayloadHeader, + payloadAttestations); + } + + public static BeaconBlockBodySchemaEip7732Impl create( + final SpecConfigEip7732 specConfig, + final AttesterSlashingSchema attesterSlashingSchema, + final SignedBlsToExecutionChangeSchema blsToExecutionChangeSchema, + final long maxValidatorsPerAttestation, + final ExecutionPayloadHeaderSchema executionPayloadHeaderSchema, + final PayloadAttestationSchema payloadAttestationSchema, + final String containerName) { + return new BeaconBlockBodySchemaEip7732Impl( + containerName, + namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), + namedSchema(BlockBodyFields.ETH1_DATA, Eth1Data.SSZ_SCHEMA), + namedSchema(BlockBodyFields.GRAFFITI, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema( + BlockBodyFields.PROPOSER_SLASHINGS, + SszListSchema.create( + ProposerSlashing.SSZ_SCHEMA, specConfig.getMaxProposerSlashings())), + namedSchema( + BlockBodyFields.ATTESTER_SLASHINGS, + SszListSchema.create( + attesterSlashingSchema, specConfig.getMaxAttesterSlashingsElectra())), + namedSchema( + BlockBodyFields.ATTESTATIONS, + SszListSchema.create( + new AttestationElectraSchema( + maxValidatorsPerAttestation, specConfig.getMaxCommitteesPerSlot()) + .castTypeToAttestationSchema(), + specConfig.getMaxAttestationsElectra())), + namedSchema( + BlockBodyFields.DEPOSITS, + SszListSchema.create(Deposit.SSZ_SCHEMA, specConfig.getMaxDeposits())), + namedSchema( + BlockBodyFields.VOLUNTARY_EXITS, + SszListSchema.create( + SignedVoluntaryExit.SSZ_SCHEMA, specConfig.getMaxVoluntaryExits())), + namedSchema( + BlockBodyFields.SYNC_AGGREGATE, + SyncAggregateSchema.create(specConfig.getSyncCommitteeSize())), + namedSchema( + BlockBodyFields.BLS_TO_EXECUTION_CHANGES, + SszListSchema.create( + blsToExecutionChangeSchema, specConfig.getMaxBlsToExecutionChanges())), + namedSchema( + BlockBodyFields.SIGNED_EXECUTION_PAYLOAD_HEADER, + new SignedExecutionPayloadHeaderSchema(executionPayloadHeaderSchema)), + namedSchema( + BlockBodyFields.PAYLOAD_ATTESTATIONS, + SszListSchema.create( + payloadAttestationSchema, specConfig.getMaxPayloadAttestations()))); + } + + @Override + public SafeFuture createBlockBody( + final Function> bodyBuilder) { + final BeaconBlockBodyBuilderEip7732 builder = new BeaconBlockBodyBuilderEip7732(this); + return bodyBuilder.apply(builder).thenApply(__ -> builder.build()); + } + + @Override + public BeaconBlockBody createEmpty() { + return new BeaconBlockBodyEip7732Impl(this); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getProposerSlashingsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.PROPOSER_SLASHINGS)); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getAttesterSlashingsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.ATTESTER_SLASHINGS)); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getAttestationsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.ATTESTATIONS)); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getDepositsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(BlockBodyFields.DEPOSITS)); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getVoluntaryExitsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.VOLUNTARY_EXITS)); + } + + @Override + public SyncAggregateSchema getSyncAggregateSchema() { + return (SyncAggregateSchema) getChildSchema(getFieldIndex(BlockBodyFields.SYNC_AGGREGATE)); + } + + @Override + public BeaconBlockBodyEip7732Impl createFromBackingNode(final TreeNode node) { + return new BeaconBlockBodyEip7732Impl(this, node); + } + + @Override + public ExecutionPayloadSchema getExecutionPayloadSchema() { + throw new UnsupportedOperationException("ExecutionPayload removed in Eip7732"); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getBlsToExecutionChangesSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.BLS_TO_EXECUTION_CHANGES)); + } + + @Override + public SszListSchema getBlobKzgCommitmentsSchema() { + throw new UnsupportedOperationException("BlobKzgCommitments removed in Eip7732"); + } + + @Override + public long getBlobKzgCommitmentsGeneralizedIndex() { + throw new UnsupportedOperationException("BlobKzgCommitments removed in Eip7732"); + } + + // TODO: not sure what to do here + @Override + public LongList getBlindedNodeGeneralizedIndices() { + return GIndexUtil.gIdxComposeAll( + getChildGeneralizedIndex(getFieldIndex(BlockBodyFields.EXECUTION_PAYLOAD)), + getExecutionPayloadSchema().getBlindedNodeGeneralizedIndices()); + } + + @Override + public long getBlobKzgCommitmentsRootGeneralizedIndex() { + return getSignedExecutionPayloadHeaderSchema() + .getMessageSchema() + .getChildGeneralizedIndex(getFieldIndex(ExecutionPayloadFields.BLOB_KZG_COMMITMENTS_ROOT)); + } + + @Override + public SignedExecutionPayloadHeaderSchema getSignedExecutionPayloadHeaderSchema() { + return (SignedExecutionPayloadHeaderSchema) + getChildSchema(getFieldIndex(BlockBodyFields.SIGNED_EXECUTION_PAYLOAD_HEADER)); + } + + @SuppressWarnings("unchecked") + @Override + public SszListSchema getPayloadAttestationsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BlockBodyFields.PAYLOAD_ATTESTATIONS)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodyBuilderPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodyBuilderPhase0.java index f7600483e82..0b7b8fbbc80 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodyBuilderPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodyBuilderPhase0.java @@ -27,9 +27,11 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -135,6 +137,20 @@ public BeaconBlockBodyBuilder blobKzgCommitments( return this; } + @Override + public BeaconBlockBodyBuilder signedExecutionPayloadHeader( + final SignedExecutionPayloadHeader signedExecutionPayloadHeader) { + // No SignedExecutionPayloadHeader in phase 0 + return null; + } + + @Override + public BeaconBlockBodyBuilder payloadAttestations( + final SszList payloadAttestations) { + // No PayloadAttestation in phase 0 + return null; + } + protected void validate() { checkNotNull(randaoReveal, "randaoReveal must be specified"); checkNotNull(eth1Data, "eth1Data must be specified"); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java index 848ca04357a..3e2a869d7b6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java @@ -25,6 +25,7 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.capella.ExecutionPayloadCapella; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadDeneb; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadEip7732; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; public interface ExecutionPayload extends ExecutionPayloadSummary, SszContainer, BuilderPayload { @@ -61,6 +62,10 @@ default Optional toVersionElectra() { return Optional.empty(); } + default Optional toVersionEip7732() { + return Optional.empty(); + } + @Override default ExecutionPayload getExecutionPayload() { return this; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelope.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelope.java new file mode 100644 index 00000000000..f2b7a158bcc --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelope.java @@ -0,0 +1,86 @@ +/* + * 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.execution; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.Container6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBit; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; + +public class ExecutionPayloadEnvelope + extends Container6< + ExecutionPayloadEnvelope, + ExecutionPayload, + SszUInt64, + SszBytes32, + SszList, + SszBit, + SszBytes32> { + + ExecutionPayloadEnvelope( + final ExecutionPayloadEnvelopeSchema schema, + final ExecutionPayload payload, + final UInt64 builderIndex, + final Bytes32 beaconBlockRoot, + final SszList blobKzgCommitments, + final boolean payloadWithheld, + final Bytes32 stateRoot) { + super( + schema, + payload, + SszUInt64.of(builderIndex), + SszBytes32.of(beaconBlockRoot), + blobKzgCommitments, + SszBit.of(payloadWithheld), + SszBytes32.of(stateRoot)); + } + + ExecutionPayloadEnvelope(final ExecutionPayloadEnvelopeSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public ExecutionPayload getPayload() { + return getField0(); + } + + public UInt64 getBuilderIndex() { + return getField1().get(); + } + + public Bytes32 getBeaconBlockRoot() { + return getField2().get(); + } + + public SszList getBlobKzgCommitments() { + return getField3(); + } + + public boolean isPayloadWithheld() { + return getField4().get(); + } + + public Bytes32 getStateRoot() { + return getField5().get(); + } + + @Override + public ExecutionPayloadEnvelopeSchema getSchema() { + return (ExecutionPayloadEnvelopeSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelopeSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelopeSchema.java new file mode 100644 index 00000000000..4b1d4e75ecb --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadEnvelopeSchema.java @@ -0,0 +1,80 @@ +/* + * 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.execution; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBit; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobKzgCommitmentsSchema; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; + +public class ExecutionPayloadEnvelopeSchema + extends ContainerSchema6< + ExecutionPayloadEnvelope, + ExecutionPayload, + SszUInt64, + SszBytes32, + SszList, + SszBit, + SszBytes32> { + + public ExecutionPayloadEnvelopeSchema( + final ExecutionPayloadSchema executionPayloadSchema, + final BlobKzgCommitmentsSchema blobKzgCommitmentsSchema) { + super( + "ExecutionPayloadEnvelope", + namedSchema("payload", SszSchema.as(ExecutionPayload.class, executionPayloadSchema)), + namedSchema("builder_index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("beacon_block_root", SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema("blob_kzg_commitments", blobKzgCommitmentsSchema), + namedSchema("payload_withheld", SszPrimitiveSchemas.BIT_SCHEMA), + namedSchema("root", SszPrimitiveSchemas.BYTES32_SCHEMA)); + } + + public ExecutionPayloadEnvelope create( + final ExecutionPayload payload, + final UInt64 builderIndex, + final Bytes32 beaconBlockRoot, + final SszList blobKzgCommitments, + final boolean payloadWithheld, + final Bytes32 stateRoot) { + return new ExecutionPayloadEnvelope( + this, + payload, + builderIndex, + beaconBlockRoot, + blobKzgCommitments, + payloadWithheld, + stateRoot); + } + + @SuppressWarnings("unchecked") + public SszListSchema getBlobKzgCommitmentsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex("blob_kzg_commitments")); + } + + @Override + public ExecutionPayloadEnvelope createFromBackingNode(final TreeNode node) { + return new ExecutionPayloadEnvelope(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java index e63cca43f19..22271df8709 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java @@ -41,7 +41,14 @@ public enum ExecutionPayloadFields implements SszFieldName { WITHDRAWAL_REQUESTS, WITHDRAWAL_REQUESTS_ROOT, CONSOLIDATION_REQUESTS, - CONSOLIDATION_REQUESTS_ROOT; + CONSOLIDATION_REQUESTS_ROOT, + // EIP7732 + PARENT_BLOCK_HASH, + PARENT_BLOCK_ROOT, + BUILDER_INDEX, + SLOT, + VALUE, + BLOB_KZG_COMMITMENTS_ROOT; private final String sszFieldName; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java index 964692b03f4..8ec70ea59e0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java @@ -19,6 +19,7 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.bellatrix.ExecutionPayloadHeaderBellatrix; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.ExecutionPayloadHeaderCapella; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadHeaderDeneb; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectra; public interface ExecutionPayloadHeader extends ExecutionPayloadSummary, SszContainer { @@ -42,4 +43,8 @@ default Optional toVersionDeneb() { default Optional toVersionElectra() { return Optional.empty(); } + + default Optional toVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java index 90c404df0a2..e58c20a3ca4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java @@ -63,5 +63,18 @@ ExecutionPayloadHeaderBuilder withdrawalRequestsRoot( ExecutionPayloadHeaderBuilder consolidationRequestsRoot( Supplier consolidationRequestsRootSupplier); + ExecutionPayloadHeaderBuilder parentBlockHash(Supplier parentBlockHashSupplier); + + ExecutionPayloadHeaderBuilder parentBlockRoot(Supplier parentBlockRootSupplier); + + ExecutionPayloadHeaderBuilder builderIndex(Supplier builderIndexSupplier); + + ExecutionPayloadHeaderBuilder slot(Supplier slotSupplier); + + ExecutionPayloadHeaderBuilder value(Supplier valueSupplier); + + ExecutionPayloadHeaderBuilder blobKzgCommitmentsRoot( + Supplier blobKzgCommitmentsRootSupplier); + ExecutionPayloadHeader build(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationData.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationData.java new file mode 100644 index 00000000000..828428ea8c5 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationData.java @@ -0,0 +1,57 @@ +/* + * 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.execution; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PayloadAttestationData + extends Container3 { + + PayloadAttestationData( + final PayloadAttestationDataSchema schema, + final Bytes32 beaconBlockRoot, + final UInt64 slot, + final Byte payloadStatus) { + super(schema, SszBytes32.of(beaconBlockRoot), SszUInt64.of(slot), SszByte.of(payloadStatus)); + } + + PayloadAttestationData(final PayloadAttestationDataSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public static final PayloadAttestationDataSchema SSZ_SCHEMA = new PayloadAttestationDataSchema(); + + public Bytes32 getBeaconBlockRoot() { + return getField0().get(); + } + + public UInt64 getSlot() { + return getField1().get(); + } + + public Byte getPayloadStatus() { + return getField2().get(); + } + + @Override + public PayloadAttestationDataSchema getSchema() { + return (PayloadAttestationDataSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationDataSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationDataSchema.java new file mode 100644 index 00000000000..399d97c0223 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/PayloadAttestationDataSchema.java @@ -0,0 +1,45 @@ +/* + * 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.execution; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PayloadAttestationDataSchema + extends ContainerSchema3 { + + public PayloadAttestationDataSchema() { + super( + "PayloadAttestationData", + namedSchema("beacon_block_root", SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema("slot", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("payload_status", SszPrimitiveSchemas.BYTE_SCHEMA)); + } + + public PayloadAttestationData create( + final Bytes32 beaconBlockRoot, final UInt64 slot, final Byte payloadStatus) { + return new PayloadAttestationData(this, beaconBlockRoot, slot, payloadStatus); + } + + @Override + public PayloadAttestationData createFromBackingNode(final TreeNode node) { + return new PayloadAttestationData(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelope.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelope.java new file mode 100644 index 00000000000..71b972e4f8d --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelope.java @@ -0,0 +1,48 @@ +/* + * 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.execution; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class SignedExecutionPayloadEnvelope + extends Container2 { + + SignedExecutionPayloadEnvelope( + final SignedExecutionPayloadEnvelopeSchema schema, + final ExecutionPayloadEnvelope message, + final BLSSignature signature) { + super(schema, message, new SszSignature(signature)); + } + + SignedExecutionPayloadEnvelope( + final SignedExecutionPayloadEnvelopeSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public ExecutionPayloadEnvelope getMessage() { + return getField0(); + } + + public BLSSignature getSignature() { + return getField1().getSignature(); + } + + @Override + public SignedExecutionPayloadEnvelopeSchema getSchema() { + return (SignedExecutionPayloadEnvelopeSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelopeSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelopeSchema.java new file mode 100644 index 00000000000..a9262ca32ba --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadEnvelopeSchema.java @@ -0,0 +1,43 @@ +/* + * 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.execution; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class SignedExecutionPayloadEnvelopeSchema + extends ContainerSchema2< + SignedExecutionPayloadEnvelope, ExecutionPayloadEnvelope, SszSignature> { + + public SignedExecutionPayloadEnvelopeSchema( + final ExecutionPayloadEnvelopeSchema executionPayloadEnvelopeSchema) { + super( + "SignedExecutionPayloadEnvelope", + namedSchema("message", executionPayloadEnvelopeSchema), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + public SignedExecutionPayloadEnvelope create( + final ExecutionPayloadEnvelope message, final BLSSignature signature) { + return new SignedExecutionPayloadEnvelope(this, message, signature); + } + + @Override + public SignedExecutionPayloadEnvelope createFromBackingNode(final TreeNode node) { + return new SignedExecutionPayloadEnvelope(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeader.java new file mode 100644 index 00000000000..428180cdb5f --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeader.java @@ -0,0 +1,48 @@ +/* + * 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.execution; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class SignedExecutionPayloadHeader + extends Container2 { + + SignedExecutionPayloadHeader( + final SignedExecutionPayloadHeaderSchema schema, + final ExecutionPayloadHeader message, + final BLSSignature signature) { + super(schema, message, new SszSignature(signature)); + } + + SignedExecutionPayloadHeader( + final SignedExecutionPayloadHeaderSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public ExecutionPayloadHeader getMessage() { + return getField0(); + } + + public BLSSignature getSignature() { + return getField1().getSignature(); + } + + @Override + public SignedExecutionPayloadHeaderSchema getSchema() { + return (SignedExecutionPayloadHeaderSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeaderSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeaderSchema.java new file mode 100644 index 00000000000..94e21bfd759 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/SignedExecutionPayloadHeaderSchema.java @@ -0,0 +1,48 @@ +/* + * 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.execution; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class SignedExecutionPayloadHeaderSchema + extends ContainerSchema2 { + + public SignedExecutionPayloadHeaderSchema( + final ExecutionPayloadHeaderSchema executionPayloadHeaderSchema) { + super( + "SignedExecutionPayloadHeader", + namedSchema( + "message", SszSchema.as(ExecutionPayloadHeader.class, executionPayloadHeaderSchema)), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + public SignedExecutionPayloadHeader create( + final ExecutionPayloadHeader message, final BLSSignature signature) { + return new SignedExecutionPayloadHeader(this, message, signature); + } + + public ExecutionPayloadHeaderSchema getMessageSchema() { + return (ExecutionPayloadHeaderSchema) getChildSchema(getFieldIndex("message")); + } + + @Override + public SignedExecutionPayloadHeader createFromBackingNode(final TreeNode node) { + return new SignedExecutionPayloadHeader(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java index 8b9e80a74ea..2e10e4f2f86 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java @@ -170,6 +170,39 @@ public ExecutionPayloadHeaderBuilder consolidationRequestsRoot( return this; } + @Override + public ExecutionPayloadHeaderBuilder parentBlockHash( + final Supplier parentBlockHashSupplier) { + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder parentBlockRoot( + final Supplier parentBlockRootSupplier) { + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder builderIndex(final Supplier builderIndexSupplier) { + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder slot(final Supplier slotSupplier) { + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder value(final Supplier valueSupplier) { + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder blobKzgCommitmentsRoot( + final Supplier blobKzgCommitmentsRootSupplier) { + return this; + } + protected void validateSchema() { checkNotNull(schema, "schema must be specified"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadBuilderEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadBuilderEip7732.java new file mode 100644 index 00000000000..c0a7e764324 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadBuilderEip7732.java @@ -0,0 +1,71 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import static com.google.common.base.Preconditions.checkNotNull; + +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadBuilderElectra; + +public class ExecutionPayloadBuilderEip7732 extends ExecutionPayloadBuilderElectra { + private ExecutionPayloadSchemaEip7732 schema; + + public ExecutionPayloadBuilderEip7732 schema(final ExecutionPayloadSchemaEip7732 schema) { + this.schema = schema; + return this; + } + + @Override + protected void validateSchema() { + checkNotNull(schema, "schema must be specified"); + } + + @Override + protected void validate() { + super.validate(); + } + + @Override + public ExecutionPayload build() { + validate(); + return new ExecutionPayloadEip7732Impl( + schema, + SszBytes32.of(parentHash), + SszByteVector.fromBytes(feeRecipient.getWrappedBytes()), + SszBytes32.of(stateRoot), + SszBytes32.of(receiptsRoot), + SszByteVector.fromBytes(logsBloom), + SszBytes32.of(prevRandao), + SszUInt64.of(blockNumber), + SszUInt64.of(gasLimit), + SszUInt64.of(gasUsed), + SszUInt64.of(timestamp), + schema.getExtraDataSchema().fromBytes(extraData), + SszUInt256.of(baseFeePerGas), + SszBytes32.of(blockHash), + transactions.stream() + .map(schema.getTransactionSchema()::fromBytes) + .collect(schema.getTransactionsSchema().collector()), + schema.getWithdrawalsSchema().createFromElements(withdrawals), + SszUInt64.of(blobGasUsed), + SszUInt64.of(excessBlobGas), + schema.getDepositRequestsSchema().createFromElements(depositRequests), + schema.getWithdrawalRequestsSchema().createFromElements(withdrawalRequests), + schema.getConsolidationRequestsSchema().createFromElements(consolidationRequests)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732.java new file mode 100644 index 00000000000..7d54ea0d750 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732.java @@ -0,0 +1,42 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import java.util.Optional; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; + +public interface ExecutionPayloadEip7732 extends ExecutionPayload, ExecutionPayloadElectra { + + static ExecutionPayloadEip7732 required(final ExecutionPayload payload) { + return payload + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 execution payload but got " + + payload.getClass().getSimpleName())); + } + + @Override + default Optional toVersionEip7732() { + return Optional.of(this); + } + + @Override + default SpecMilestone getMilestone() { + return SpecMilestone.EIP7732; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732Impl.java new file mode 100644 index 00000000000..2c2c8f293e0 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadEip7732Impl.java @@ -0,0 +1,266 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import java.util.List; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; +import tech.pegasys.teku.infrastructure.ssz.containers.Container20; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema20; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.Transaction; +import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ConsolidationRequest; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequest; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequest; + +public class ExecutionPayloadEip7732Impl + extends Container20< + ExecutionPayloadEip7732Impl, + SszBytes32, + SszByteVector, + SszBytes32, + SszBytes32, + SszByteVector, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszByteList, + SszUInt256, + SszBytes32, + SszList, + SszList, + SszUInt64, + SszUInt64, + SszList, + SszList, + SszList> + implements ExecutionPayloadEip7732 { + + public ExecutionPayloadEip7732Impl( + final ContainerSchema20< + ExecutionPayloadEip7732Impl, + SszBytes32, + SszByteVector, + SszBytes32, + SszBytes32, + SszByteVector, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszByteList, + SszUInt256, + SszBytes32, + SszList, + SszList, + SszUInt64, + SszUInt64, + SszList, + SszList, + SszList> + schema, + final TreeNode backingNode) { + super(schema, backingNode); + } + + public ExecutionPayloadEip7732Impl( + final ExecutionPayloadSchemaEip7732 schema, + final SszBytes32 parentHash, + final SszByteVector feeRecipient, + final SszBytes32 stateRoot, + final SszBytes32 receiptsRoot, + final SszByteVector logsBloom, + final SszBytes32 prevRandao, + final SszUInt64 blockNumber, + final SszUInt64 gasLimit, + final SszUInt64 gasUsed, + final SszUInt64 timestamp, + final SszByteList extraData, + final SszUInt256 baseFeePerGas, + final SszBytes32 blockHash, + final SszList transactions, + final SszList withdrawals, + final SszUInt64 blobGasUsed, + final SszUInt64 excessBlobGas, + final SszList depositRequests, + final SszList withdrawalRequests, + final SszList consolidationRequests) { + super( + schema, + parentHash, + feeRecipient, + stateRoot, + receiptsRoot, + logsBloom, + prevRandao, + blockNumber, + gasLimit, + gasUsed, + timestamp, + extraData, + baseFeePerGas, + blockHash, + transactions, + withdrawals, + blobGasUsed, + excessBlobGas, + depositRequests, + withdrawalRequests, + consolidationRequests); + } + + @Override + public boolean isDefaultPayload() { + return super.isDefault(); + } + + @Override + public Optional getOptionalWithdrawalsRoot() { + return Optional.of(getWithdrawals().hashTreeRoot()); + } + + @Override + public ExecutionPayloadSchemaEip7732 getSchema() { + return (ExecutionPayloadSchemaEip7732) super.getSchema(); + } + + @Override + public Bytes32 getParentHash() { + return getField0().get(); + } + + @Override + public Bytes20 getFeeRecipient() { + return Bytes20.leftPad(getField1().getBytes()); + } + + @Override + public Bytes32 getStateRoot() { + return getField2().get(); + } + + @Override + public Bytes32 getReceiptsRoot() { + return getField3().get(); + } + + @Override + public Bytes getLogsBloom() { + return getField4().getBytes(); + } + + @Override + public Bytes32 getPrevRandao() { + return getField5().get(); + } + + @Override + public UInt64 getBlockNumber() { + return getField6().get(); + } + + @Override + public UInt64 getGasLimit() { + return getField7().get(); + } + + @Override + public UInt64 getGasUsed() { + return getField8().get(); + } + + @Override + public UInt64 getTimestamp() { + return getField9().get(); + } + + @Override + public Bytes getExtraData() { + return getField10().getBytes(); + } + + @Override + public UInt256 getBaseFeePerGas() { + return getField11().get(); + } + + @Override + public Bytes32 getBlockHash() { + return getField12().get(); + } + + @Override + public Bytes32 getPayloadHash() { + return hashTreeRoot(); + } + + @Override + public SszList getTransactions() { + return getField13(); + } + + @Override + public SszList getWithdrawals() { + return getField14(); + } + + @Override + public UInt64 getBlobGasUsed() { + return getField15().get(); + } + + @Override + public UInt64 getExcessBlobGas() { + return getField16().get(); + } + + @Override + public SszList getDepositRequests() { + return getField17(); + } + + @Override + public SszList getWithdrawalRequests() { + return getField18(); + } + + @Override + public SszList getConsolidationRequests() { + return getField19(); + } + + @Override + public List getUnblindedTreeNodes() { + return List.of( + getTransactions().getBackingNode(), + getWithdrawals().getBackingNode(), + getDepositRequests().getBackingNode(), + getWithdrawalRequests().getBackingNode(), + getConsolidationRequests().getBackingNode()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderBuilderEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderBuilderEip7732.java new file mode 100644 index 00000000000..5101d919814 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderBuilderEip7732.java @@ -0,0 +1,113 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.function.Supplier; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderBuilder; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderBuilderElectra; + +public class ExecutionPayloadHeaderBuilderEip7732 extends ExecutionPayloadHeaderBuilderElectra { + private ExecutionPayloadHeaderSchemaEip7732 schema; + + protected Bytes32 parentBlockHash; + protected Bytes32 parentBlockRoot; + protected UInt64 builderIndex; + protected UInt64 slot; + protected UInt64 value; + protected Bytes32 blobKzgCommitmentsRoot; + + public ExecutionPayloadHeaderBuilderEip7732 schema( + final ExecutionPayloadHeaderSchemaEip7732 schema) { + this.schema = schema; + return this; + } + + @Override + protected void validateSchema() { + checkNotNull(schema, "schema must be specified"); + } + + @Override + protected void validate() { + // EIP7732 TODO: hacky skip validation + // super.validate(); + checkNotNull(parentBlockHash, "parentBlockHash must be specified"); + checkNotNull(parentBlockRoot, "parentBlockRoot must be specified"); + checkNotNull(builderIndex, "builderIndex must be specified"); + checkNotNull(slot, "slot must be specified"); + checkNotNull(value, "value must be specified"); + checkNotNull(blobKzgCommitmentsRoot, "blobKzgCommitmentsRoot must be specified"); + } + + @Override + public ExecutionPayloadHeaderBuilder parentBlockHash( + final Supplier parentBlockHashSupplier) { + this.parentBlockHash = parentBlockHashSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder parentBlockRoot( + final Supplier parentBlockRootSupplier) { + this.parentBlockRoot = parentBlockRootSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder builderIndex(final Supplier builderIndexSupplier) { + this.builderIndex = builderIndexSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder slot(final Supplier slotSupplier) { + this.slot = slotSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder value(final Supplier valueSupplier) { + this.value = valueSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeaderBuilder blobKzgCommitmentsRoot( + final Supplier blobKzgCommitmentsRootSupplier) { + this.blobKzgCommitmentsRoot = blobKzgCommitmentsRootSupplier.get(); + return this; + } + + @Override + public ExecutionPayloadHeader build() { + validate(); + return new ExecutionPayloadHeaderEip7732Impl( + schema, + SszBytes32.of(parentBlockHash), + SszBytes32.of(parentBlockRoot), + SszBytes32.of(blockHash), + SszUInt64.of(gasLimit), + SszUInt64.of(builderIndex), + SszUInt64.of(slot), + SszUInt64.of(value), + SszBytes32.of(blobKzgCommitmentsRoot)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732.java new file mode 100644 index 00000000000..babce1a323d --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732.java @@ -0,0 +1,148 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectra; + +public interface ExecutionPayloadHeaderEip7732 extends ExecutionPayloadHeaderElectra { + + static ExecutionPayloadHeaderEip7732 required(final ExecutionPayloadHeader payload) { + return payload + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 execution payload header but got " + + payload.getClass().getSimpleName())); + } + + Bytes32 getParentBlockHash(); + + Bytes32 getParentBlockRoot(); + + UInt64 getBuilderIndex(); + + UInt64 getSlot(); + + UInt64 getValue(); + + Bytes32 getBlobKzgCommitmentsRoot(); + + @Override + default UInt64 getGasUsed() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default UInt64 getBlockNumber() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default UInt256 getBaseFeePerGas() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default UInt64 getTimestamp() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getPrevRandao() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getReceiptsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getStateRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes20 getFeeRecipient() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getParentHash() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes getLogsBloom() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes getExtraData() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getPayloadHash() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getTransactionsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getWithdrawalsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default UInt64 getBlobGasUsed() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default UInt64 getExcessBlobGas() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getDepositRequestsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getWithdrawalRequestsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Bytes32 getConsolidationRequestsRoot() { + throw new UnsupportedOperationException("Not supported in Eip7732"); + } + + @Override + default Optional toVersionEip7732() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732Impl.java new file mode 100644 index 00000000000..8ebb4cedb5d --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderEip7732Impl.java @@ -0,0 +1,129 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.containers.Container8; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema8; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class ExecutionPayloadHeaderEip7732Impl + extends Container8< + ExecutionPayloadHeaderEip7732Impl, + SszBytes32, + SszBytes32, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszBytes32> + implements ExecutionPayloadHeaderEip7732 { + + protected ExecutionPayloadHeaderEip7732Impl( + final ContainerSchema8< + ExecutionPayloadHeaderEip7732Impl, + SszBytes32, + SszBytes32, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszBytes32> + schema, + final TreeNode backingTree) { + super(schema, backingTree); + } + + public ExecutionPayloadHeaderEip7732Impl( + final ExecutionPayloadHeaderSchemaEip7732 schema, + final SszBytes32 parentBlockHash, + final SszBytes32 parentBlockRoot, + final SszBytes32 blockHash, + final SszUInt64 gasLimit, + final SszUInt64 builderIndex, + final SszUInt64 slot, + final SszUInt64 value, + final SszBytes32 blobKzgCommitmentsRoot) { + super( + schema, + parentBlockHash, + parentBlockRoot, + blockHash, + gasLimit, + builderIndex, + slot, + value, + blobKzgCommitmentsRoot); + } + + @Override + public UInt64 getGasLimit() { + return getField3().get(); + } + + @Override + public Bytes32 getBlockHash() { + return getField2().get(); + } + + @Override + public Bytes32 getParentBlockHash() { + return getField0().get(); + } + + @Override + public Bytes32 getParentBlockRoot() { + return getField1().get(); + } + + @Override + public UInt64 getBuilderIndex() { + return getField4().get(); + } + + @Override + public UInt64 getSlot() { + return getField5().get(); + } + + @Override + public UInt64 getValue() { + return getField6().get(); + } + + @Override + public Bytes32 getBlobKzgCommitmentsRoot() { + return getField7().get(); + } + + @Override + public boolean isDefaultPayload() { + return isHeaderOfDefaultPayload(); + } + + @Override + public ExecutionPayloadHeaderSchemaEip7732 getSchema() { + return (ExecutionPayloadHeaderSchemaEip7732) super.getSchema(); + } + + @Override + public boolean isHeaderOfDefaultPayload() { + return equals(getSchema().getHeaderOfDefaultPayload()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderSchemaEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderSchemaEip7732.java new file mode 100644 index 00000000000..3af1b6b6792 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadHeaderSchemaEip7732.java @@ -0,0 +1,119 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOB_KZG_COMMITMENTS_ROOT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_HASH; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BUILDER_INDEX; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.GAS_LIMIT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.PARENT_BLOCK_HASH; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.PARENT_BLOCK_ROOT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.SLOT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.VALUE; + +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.function.Consumer; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema8; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderBuilder; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; + +public class ExecutionPayloadHeaderSchemaEip7732 + extends ContainerSchema8< + ExecutionPayloadHeaderEip7732Impl, + SszBytes32, + SszBytes32, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszBytes32> + implements ExecutionPayloadHeaderSchema { + + private final ExecutionPayloadHeaderEip7732Impl defaultExecutionPayloadHeader; + private final ExecutionPayloadHeaderEip7732Impl executionPayloadHeaderOfDefaultPayload; + + public ExecutionPayloadHeaderSchemaEip7732(final SpecConfigEip7732 specConfig) { + super( + "ExecutionPayloadHeaderEip7732", + namedSchema(PARENT_BLOCK_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(PARENT_BLOCK_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(BLOCK_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(GAS_LIMIT, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(BUILDER_INDEX, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(SLOT, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(VALUE, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(BLOB_KZG_COMMITMENTS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA)); + + final ExecutionPayloadEip7732Impl defaultExecutionPayload = + new ExecutionPayloadSchemaEip7732(specConfig).getDefault(); + + this.executionPayloadHeaderOfDefaultPayload = + createFromExecutionPayload(defaultExecutionPayload); + this.defaultExecutionPayloadHeader = createFromBackingNode(getDefaultTree()); + } + + @Override + public LongList getBlindedNodeGeneralizedIndices() { + return LongList.of(getChildGeneralizedIndex(getFieldIndex(BLOB_KZG_COMMITMENTS_ROOT))); + } + + @Override + public ExecutionPayloadHeader createExecutionPayloadHeader( + final Consumer builderConsumer) { + final ExecutionPayloadHeaderBuilderEip7732 builder = + new ExecutionPayloadHeaderBuilderEip7732().schema(this); + builderConsumer.accept(builder); + return builder.build(); + } + + @Override + public ExecutionPayloadHeaderEip7732Impl getDefault() { + return defaultExecutionPayloadHeader; + } + + @Override + public ExecutionPayloadHeaderEip7732 getHeaderOfDefaultPayload() { + return executionPayloadHeaderOfDefaultPayload; + } + + @Override + public ExecutionPayloadHeaderEip7732Impl createFromBackingNode(final TreeNode node) { + return new ExecutionPayloadHeaderEip7732Impl(this, node); + } + + @Override + public ExecutionPayloadHeaderEip7732Impl createFromExecutionPayload( + final ExecutionPayload payload) { + return new ExecutionPayloadHeaderEip7732Impl( + this, + SszBytes32.of(Bytes32.ZERO), + SszBytes32.of(Bytes32.ZERO), + SszBytes32.of(payload.getBlockHash()), + SszUInt64.of(payload.getGasLimit()), + SszUInt64.of(UInt64.ZERO), + SszUInt64.of(UInt64.ZERO), + SszUInt64.of(UInt64.ZERO), + SszBytes32.of(Bytes32.ZERO)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadSchemaEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadSchemaEip7732.java new file mode 100644 index 00000000000..c04294f5667 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7732/ExecutionPayloadSchemaEip7732.java @@ -0,0 +1,257 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7732; + +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BASE_FEE_PER_GAS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOB_GAS_USED; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_HASH; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_NUMBER; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.CONSOLIDATION_REQUESTS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.DEPOSIT_REQUESTS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXCESS_BLOB_GAS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXTRA_DATA; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.FEE_RECIPIENT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.GAS_LIMIT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.GAS_USED; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.LOGS_BLOOM; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.PARENT_HASH; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.PREV_RANDAO; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.RECEIPTS_ROOT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.STATE_ROOT; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.TIMESTAMP; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.TRANSACTIONS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.WITHDRAWALS; +import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.WITHDRAWAL_REQUESTS; + +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.function.Consumer; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema20; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteVectorSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; +import tech.pegasys.teku.spec.datastructures.execution.Transaction; +import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; +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.execution.versions.electra.ConsolidationRequest; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ConsolidationRequestSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequest; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequestSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequest; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequestSchema; + +public class ExecutionPayloadSchemaEip7732 + extends ContainerSchema20< + ExecutionPayloadEip7732Impl, + SszBytes32, + SszByteVector, + SszBytes32, + SszBytes32, + SszByteVector, + SszBytes32, + SszUInt64, + SszUInt64, + SszUInt64, + SszUInt64, + SszByteList, + SszUInt256, + SszBytes32, + SszList, + SszList, + SszUInt64, + SszUInt64, + SszList, + SszList, + SszList> + implements ExecutionPayloadSchema { + + private final ExecutionPayloadEip7732Impl defaultExecutionPayload; + + public ExecutionPayloadSchemaEip7732(final SpecConfigEip7732 specConfig) { + super( + "ExecutionPayloadEip7732", + namedSchema(PARENT_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(FEE_RECIPIENT, SszByteVectorSchema.create(Bytes20.SIZE)), + namedSchema(STATE_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(RECEIPTS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(LOGS_BLOOM, SszByteVectorSchema.create(specConfig.getBytesPerLogsBloom())), + namedSchema(PREV_RANDAO, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema(BLOCK_NUMBER, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(GAS_LIMIT, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(GAS_USED, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(TIMESTAMP, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(EXTRA_DATA, SszByteListSchema.create(specConfig.getMaxExtraDataBytes())), + namedSchema(BASE_FEE_PER_GAS, SszPrimitiveSchemas.UINT256_SCHEMA), + namedSchema(BLOCK_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema( + TRANSACTIONS, + SszListSchema.create( + new TransactionSchema(specConfig), specConfig.getMaxTransactionsPerPayload())), + namedSchema( + WITHDRAWALS, + SszListSchema.create(Withdrawal.SSZ_SCHEMA, specConfig.getMaxWithdrawalsPerPayload())), + namedSchema(BLOB_GAS_USED, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(EXCESS_BLOB_GAS, SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema( + DEPOSIT_REQUESTS, + SszListSchema.create( + DepositRequest.SSZ_SCHEMA, specConfig.getMaxDepositRequestsPerPayload())), + namedSchema( + WITHDRAWAL_REQUESTS, + SszListSchema.create( + WithdrawalRequest.SSZ_SCHEMA, specConfig.getMaxWithdrawalRequestsPerPayload())), + namedSchema( + CONSOLIDATION_REQUESTS, + SszListSchema.create( + ConsolidationRequest.SSZ_SCHEMA, + specConfig.getMaxConsolidationRequestsPerPayload()))); + this.defaultExecutionPayload = createFromBackingNode(getDefaultTree()); + } + + @Override + public ExecutionPayloadEip7732Impl getDefault() { + return defaultExecutionPayload; + } + + @Override + public TransactionSchema getTransactionSchema() { + return (TransactionSchema) getTransactionsSchema().getElementSchema(); + } + + @Override + public SszListSchema> getWithdrawalsSchemaRequired() { + return getWithdrawalsSchema(); + } + + @Override + public WithdrawalSchema getWithdrawalSchemaRequired() { + return getWithdrawalSchema(); + } + + @Override + public SszListSchema> + getDepositRequestsSchemaRequired() { + return getDepositRequestsSchema(); + } + + @Override + public DepositRequestSchema getDepositRequestSchemaRequired() { + return getDepositRequestSchema(); + } + + @Override + public SszListSchema> + getWithdrawalRequestsSchemaRequired() { + return getWithdrawalRequestsSchema(); + } + + @Override + public WithdrawalRequestSchema getWithdrawalRequestSchemaRequired() { + return getWithdrawalRequestSchema(); + } + + @Override + public ConsolidationRequestSchema getConsolidationRequestSchemaRequired() { + return getConsolidationRequestSchema(); + } + + @Override + public SszListSchema> + getConsolidationRequestsSchemaRequired() { + return getConsolidationRequestsSchema(); + } + + public WithdrawalSchema getWithdrawalSchema() { + return (WithdrawalSchema) getWithdrawalsSchema().getElementSchema(); + } + + public DepositRequestSchema getDepositRequestSchema() { + return (DepositRequestSchema) getDepositRequestsSchema().getElementSchema(); + } + + public WithdrawalRequestSchema getWithdrawalRequestSchema() { + return (WithdrawalRequestSchema) getWithdrawalRequestsSchema().getElementSchema(); + } + + public ConsolidationRequestSchema getConsolidationRequestSchema() { + return (ConsolidationRequestSchema) getConsolidationRequestsSchema().getElementSchema(); + } + + @Override + public LongList getBlindedNodeGeneralizedIndices() { + return LongList.of( + getChildGeneralizedIndex(getFieldIndex(TRANSACTIONS)), + getChildGeneralizedIndex(getFieldIndex(WITHDRAWALS)), + getChildGeneralizedIndex(getFieldIndex(DEPOSIT_REQUESTS)), + getChildGeneralizedIndex(getFieldIndex(WITHDRAWAL_REQUESTS)), + getChildGeneralizedIndex(getFieldIndex(CONSOLIDATION_REQUESTS))); + } + + @Override + public ExecutionPayload createExecutionPayload( + final Consumer builderConsumer) { + final ExecutionPayloadBuilderEip7732 builder = + new ExecutionPayloadBuilderEip7732().schema(this); + builderConsumer.accept(builder); + return builder.build(); + } + + @Override + public ExecutionPayloadEip7732Impl createFromBackingNode(final TreeNode node) { + return new ExecutionPayloadEip7732Impl(this, node); + } + + public SszByteListSchema getExtraDataSchema() { + return (SszByteListSchema) getChildSchema(getFieldIndex(EXTRA_DATA)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getTransactionsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(TRANSACTIONS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getWithdrawalsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(WITHDRAWALS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getDepositRequestsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(DEPOSIT_REQUESTS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getWithdrawalRequestsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(WITHDRAWAL_REQUESTS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getConsolidationRequestsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(CONSOLIDATION_REQUESTS)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/ExecutionPayloadEnvelopesByRootRequestMessage.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/ExecutionPayloadEnvelopesByRootRequestMessage.java new file mode 100644 index 00000000000..01610e4524d --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/ExecutionPayloadEnvelopesByRootRequestMessage.java @@ -0,0 +1,62 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc; + +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.impl.SszListImpl; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; + +public class ExecutionPayloadEnvelopesByRootRequestMessage extends SszListImpl + implements SszList, RpcRequest { + + public static class ExecutionPayloadEnvelopesByRootRequestMessageSchema + extends AbstractSszListSchema { + + public ExecutionPayloadEnvelopesByRootRequestMessageSchema(final SpecConfigEip7732 specConfig) { + super(SszPrimitiveSchemas.BYTES32_SCHEMA, specConfig.getMaxRequestPayloads()); + } + + @Override + public ExecutionPayloadEnvelopesByRootRequestMessage createFromBackingNode( + final TreeNode node) { + return new ExecutionPayloadEnvelopesByRootRequestMessage(this, node); + } + } + + public ExecutionPayloadEnvelopesByRootRequestMessage( + final ExecutionPayloadEnvelopesByRootRequestMessageSchema schema, final List roots) { + super(schema, schema.createTreeFromElements(roots.stream().map(SszBytes32::of).toList())); + } + + private ExecutionPayloadEnvelopesByRootRequestMessage( + final ExecutionPayloadEnvelopesByRootRequestMessageSchema schema, final TreeNode node) { + super(schema, node); + } + + @Override + public int getMaximumResponseChunks() { + return size(); + } + + @Override + public String toString() { + return "ExecutionPayloadEnvelopesByRootRequestMessage{" + super.toString() + "}"; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestation.java new file mode 100644 index 00000000000..c8602d7594f --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestation.java @@ -0,0 +1,56 @@ +/* + * 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.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class IndexedPayloadAttestation + extends Container3< + IndexedPayloadAttestation, SszUInt64List, PayloadAttestationData, SszSignature> { + + IndexedPayloadAttestation( + final IndexedPayloadAttestationSchema schema, + final SszUInt64List attestingIndices, + final PayloadAttestationData data, + final BLSSignature signature) { + super(schema, attestingIndices, data, new SszSignature(signature)); + } + + IndexedPayloadAttestation( + final IndexedPayloadAttestationSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public SszUInt64List getAttestingIndices() { + return getField0(); + } + + public PayloadAttestationData getData() { + return getField1(); + } + + public BLSSignature getSignature() { + return getField2().getSignature(); + } + + @Override + public IndexedPayloadAttestationSchema getSchema() { + return (IndexedPayloadAttestationSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestationSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestationSchema.java new file mode 100644 index 00000000000..e47d80c2e48 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedPayloadAttestationSchema.java @@ -0,0 +1,53 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class IndexedPayloadAttestationSchema + extends ContainerSchema3< + IndexedPayloadAttestation, SszUInt64List, PayloadAttestationData, SszSignature> { + + public IndexedPayloadAttestationSchema(final long ptcSize) { + super( + "IndexedPayloadAttestation", + namedSchema("attesting_indices", SszUInt64ListSchema.create(ptcSize)), + namedSchema("data", PayloadAttestationData.SSZ_SCHEMA), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + public IndexedPayloadAttestation create( + final SszUInt64List attestingIndices, + final PayloadAttestationData data, + final BLSSignature signature) { + return new IndexedPayloadAttestation(this, attestingIndices, data, signature); + } + + @SuppressWarnings("unchecked") + public SszUInt64ListSchema getAttestingIndicesSchema() { + return (SszUInt64ListSchema) getChildSchema(getFieldIndex("attesting_indices")); + } + + @Override + public IndexedPayloadAttestation createFromBackingNode(final TreeNode node) { + return new IndexedPayloadAttestation(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestation.java new file mode 100644 index 00000000000..725d3e36591 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestation.java @@ -0,0 +1,54 @@ +/* + * 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.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class PayloadAttestation + extends Container3 { + + PayloadAttestation( + final PayloadAttestationSchema schema, + final SszBitvector aggregationBits, + final PayloadAttestationData data, + final BLSSignature signature) { + super(schema, aggregationBits, data, new SszSignature(signature)); + } + + PayloadAttestation(final PayloadAttestationSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public SszBitvector getAggregationBits() { + return getField0(); + } + + public PayloadAttestationData getData() { + return getField1(); + } + + public BLSSignature getSignature() { + return getField2().getSignature(); + } + + @Override + public PayloadAttestationSchema getSchema() { + return (PayloadAttestationSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessage.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessage.java new file mode 100644 index 00000000000..8a3a152ff79 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessage.java @@ -0,0 +1,59 @@ +/* + * 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.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class PayloadAttestationMessage + extends Container3 { + + PayloadAttestationMessage( + final PayloadAttestationMessageSchema schema, + final UInt64 validatorIndex, + final PayloadAttestationData data, + final BLSSignature signature) { + super(schema, SszUInt64.of(validatorIndex), data, new SszSignature(signature)); + } + + PayloadAttestationMessage( + final PayloadAttestationMessageSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public static final PayloadAttestationMessageSchema SSZ_SCHEMA = + new PayloadAttestationMessageSchema(); + + public UInt64 getValidatorIndex() { + return getField0().get(); + } + + public PayloadAttestationData getData() { + return getField1(); + } + + public BLSSignature getSignature() { + return getField2().getSignature(); + } + + @Override + public PayloadAttestationMessageSchema getSchema() { + return (PayloadAttestationMessageSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessageSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessageSchema.java new file mode 100644 index 00000000000..20a2bf88d85 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationMessageSchema.java @@ -0,0 +1,49 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class PayloadAttestationMessageSchema + extends ContainerSchema3< + PayloadAttestationMessage, SszUInt64, PayloadAttestationData, SszSignature> { + + public PayloadAttestationMessageSchema() { + super( + "PayloadAttestationMessage", + namedSchema("validator_index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("data", PayloadAttestationData.SSZ_SCHEMA), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + public PayloadAttestationMessage create( + final UInt64 validatorIndex, + final PayloadAttestationData data, + final BLSSignature signature) { + return new PayloadAttestationMessage(this, validatorIndex, data, signature); + } + + @Override + public PayloadAttestationMessage createFromBackingNode(final TreeNode node) { + return new PayloadAttestationMessage(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationSchema.java new file mode 100644 index 00000000000..a83f60dac8a --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/PayloadAttestationSchema.java @@ -0,0 +1,48 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class PayloadAttestationSchema + extends ContainerSchema3< + PayloadAttestation, SszBitvector, PayloadAttestationData, SszSignature> { + + public PayloadAttestationSchema(final long ptcSize) { + super( + "PayloadAttestation", + namedSchema("aggregation_bits", SszBitvectorSchema.create(ptcSize)), + namedSchema("data", PayloadAttestationData.SSZ_SCHEMA), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + public PayloadAttestation create( + final SszBitvector aggregationBits, + final PayloadAttestationData data, + final BLSSignature signature) { + return new PayloadAttestation(this, aggregationBits, data, signature); + } + + @Override + public PayloadAttestation createFromBackingNode(final TreeNode node) { + return new PayloadAttestation(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java index 25d9e6545d6..75fa4204f3d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java @@ -39,6 +39,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateBellatrix; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateCapella; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateEip7732; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.BeaconStatePhase0; @@ -192,4 +193,8 @@ default Optional toVersionDeneb() { default Optional toVersionElectra() { return Optional.empty(); } + + default Optional toVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java index f8168fafe37..30b06a96f5b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java @@ -40,6 +40,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.MutableBeaconStateBellatrix; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.MutableBeaconStateCapella; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.MutableBeaconStateDeneb; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.MutableBeaconStateEip7732; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.MutableBeaconStatePhase0; @@ -223,4 +224,8 @@ default Optional toMutableVersionDeneb() { default Optional toMutableVersionElectra() { return Optional.empty(); } + + default Optional toMutableVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java index c85ecbc30f9..451aef0482a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java @@ -78,7 +78,11 @@ public enum BeaconStateFields implements SszFieldName { EARLIEST_CONSOLIDATION_EPOCH, PENDING_BALANCE_DEPOSITS, PENDING_PARTIAL_WITHDRAWALS, - PENDING_CONSOLIDATIONS; + PENDING_CONSOLIDATIONS, + // PBS fields + LATEST_BLOCK_HASH, + LATEST_FULL_SLOT, + LATEST_WITHDRAWALS_ROOT; private final String sszFieldName; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/bellatrix/BeaconStateSchemaBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/bellatrix/BeaconStateSchemaBellatrix.java index f848034befb..143315c0fbb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/bellatrix/BeaconStateSchemaBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/bellatrix/BeaconStateSchemaBellatrix.java @@ -47,6 +47,7 @@ public static BeaconStateSchemaBellatrix create(final SpecConfig specConfig) { } public static List getUniqueFields(final SpecConfig specConfig) { + // New final SszField latestExecutionPayloadHeaderField = new SszField( LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/capella/BeaconStateSchemaCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/capella/BeaconStateSchemaCapella.java index 31d7dd899fa..94ef2e709c7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/capella/BeaconStateSchemaCapella.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/capella/BeaconStateSchemaCapella.java @@ -50,11 +50,13 @@ public class BeaconStateSchemaCapella private static List getUniqueFields(final SpecConfig specConfig) { final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = new HistoricalSummary.HistoricalSummarySchema(); + // Bellatrix final SszField latestExecutionPayloadHeaderField = new SszField( LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, () -> new ExecutionPayloadHeaderSchemaCapella(SpecConfigCapella.required(specConfig))); + // New final SszField nextWithdrawalIndexField = new SszField( NEXT_WITHDRAWAL_INDEX, @@ -65,7 +67,6 @@ private static List getUniqueFields(final SpecConfig specConfig) { NEXT_WITHDRAWAL_VALIDATOR_INDEX, BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField historicalSummariesField = new SszField( HISTORICAL_SUMMARIES_INDEX, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/deneb/BeaconStateSchemaDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/deneb/BeaconStateSchemaDeneb.java index 51d7642b7ff..1d353fa7e30 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/deneb/BeaconStateSchemaDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/deneb/BeaconStateSchemaDeneb.java @@ -50,11 +50,13 @@ public class BeaconStateSchemaDeneb private static List getUniqueFields(final SpecConfig specConfig) { final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = new HistoricalSummary.HistoricalSummarySchema(); + // Bellatrix final SszField latestExecutionPayloadHeaderField = new SszField( LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, () -> new ExecutionPayloadHeaderSchemaDeneb(SpecConfigDeneb.required(specConfig))); + // Capella final SszField nextWithdrawalIndexField = new SszField( NEXT_WITHDRAWAL_INDEX, @@ -65,7 +67,6 @@ private static List getUniqueFields(final SpecConfig specConfig) { NEXT_WITHDRAWAL_VALIDATOR_INDEX, BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField historicalSummariesField = new SszField( HISTORICAL_SUMMARIES_INDEX, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732.java new file mode 100644 index 00000000000..e31d4dc6f2c --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732.java @@ -0,0 +1,77 @@ +/* + * 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.state.beaconstate.versions.eip7732; + +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.LATEST_BLOCK_HASH; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.LATEST_FULL_SLOT; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.LATEST_WITHDRAWALS_ROOT; + +import com.google.common.base.MoreObjects; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; + +public interface BeaconStateEip7732 extends BeaconStateElectra { + static BeaconStateEip7732 required(final BeaconState state) { + return state + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected an Eip7732 state but got: " + state.getClass().getSimpleName())); + } + + static void describeCustomEip7732Fields( + final MoreObjects.ToStringHelper stringBuilder, final BeaconStateEip7732 state) { + BeaconStateElectra.describeCustomElectraFields(stringBuilder, state); + stringBuilder.add("latest_block_hash", state.getLatestBlockHash()); + stringBuilder.add("latest_full_slot", state.getLatestFullSlot()); + stringBuilder.add("latest_withdrawals_root", state.getLatestWithdrawalsRoot()); + } + + @Override + MutableBeaconStateEip7732 createWritableCopy(); + + default + BeaconStateEip7732 updatedEip7732( + final Mutator mutator) throws E1, E2, E3 { + MutableBeaconStateEip7732 writableCopy = createWritableCopy(); + mutator.mutate(writableCopy); + return writableCopy.commitChanges(); + } + + @Override + default Optional toVersionEip7732() { + return Optional.of(this); + } + + default Bytes32 getLatestBlockHash() { + final int index = getSchema().getFieldIndex(LATEST_BLOCK_HASH); + return ((SszBytes32) get(index)).get(); + } + + default UInt64 getLatestFullSlot() { + final int index = getSchema().getFieldIndex(LATEST_FULL_SLOT); + return ((SszUInt64) get(index)).get(); + } + + default Bytes32 getLatestWithdrawalsRoot() { + final int index = getSchema().getFieldIndex(LATEST_WITHDRAWALS_ROOT); + return ((SszBytes32) get(index)).get(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732Impl.java new file mode 100644 index 00000000000..19ad33ca7ed --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateEip7732Impl.java @@ -0,0 +1,66 @@ +/* + * 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.state.beaconstate.versions.eip7732; + +import com.google.common.base.MoreObjects; +import tech.pegasys.teku.infrastructure.ssz.SszContainer; +import tech.pegasys.teku.infrastructure.ssz.SszData; +import tech.pegasys.teku.infrastructure.ssz.cache.IntCache; +import tech.pegasys.teku.infrastructure.ssz.schema.SszCompositeSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszContainerSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.AbstractBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.SlotCaches; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.TransitionCaches; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.ValidatorStatsAltair; + +public class BeaconStateEip7732Impl extends AbstractBeaconState + implements BeaconStateEip7732, BeaconStateCache, ValidatorStatsAltair { + + BeaconStateEip7732Impl( + final BeaconStateSchema schema) { + super(schema); + } + + BeaconStateEip7732Impl( + final SszCompositeSchema type, + final TreeNode backingNode, + final IntCache cache, + final TransitionCaches transitionCaches, + final SlotCaches slotCaches) { + super(type, backingNode, cache, transitionCaches, slotCaches); + } + + BeaconStateEip7732Impl( + final AbstractSszContainerSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + @Override + public BeaconStateSchemaEip7732 getBeaconStateSchema() { + return (BeaconStateSchemaEip7732) getSchema(); + } + + @Override + public MutableBeaconStateEip7732 createWritableCopy() { + return new MutableBeaconStateEip7732Impl(this); + } + + @Override + protected void describeCustomFields(final MoreObjects.ToStringHelper stringBuilder) { + BeaconStateEip7732.describeCustomEip7732Fields(stringBuilder, this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateSchemaEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateSchemaEip7732.java new file mode 100644 index 00000000000..7032588c029 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/BeaconStateSchemaEip7732.java @@ -0,0 +1,272 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732; + +import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateSchemaBellatrix.LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.HISTORICAL_SUMMARIES_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_VALIDATOR_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.CONSOLIDATION_BALANCE_TO_CONSUME_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.DEPOSIT_BALANCE_TO_CONSUME_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.DEPOSIT_REQUESTS_START_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.EARLIEST_CONSOLIDATION_EPOCH_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.EARLIEST_EXIT_EPOCH_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.EXIT_BALANCE_TO_CONSUME_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.PENDING_BALANCE_DEPOSITS_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.PENDING_CONSOLIDATIONS_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra.PENDING_PARTIAL_WITHDRAWALS_INDEX; + +import com.google.common.annotations.VisibleForTesting; +import java.util.List; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszPrimitiveListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; +import tech.pegasys.teku.infrastructure.ssz.sos.SszField; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderSchemaEip7732; +import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.AbstractBeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateSchemaAltair; +import tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; + +public class BeaconStateSchemaEip7732 + extends AbstractBeaconStateSchema { + public static final int LATEST_BLOCK_HASH_INDEX = 37; + public static final int LATEST_FULL_SLOT_INDEX = 38; + public static final int LATEST_WITHDRAWALS_ROOT_INDEX = 39; + + @VisibleForTesting + BeaconStateSchemaEip7732(final SpecConfig specConfig) { + super("BeaconStateEip7732", getUniqueFields(specConfig), specConfig); + } + + private static List getUniqueFields(final SpecConfig specConfig) { + final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = + new HistoricalSummary.HistoricalSummarySchema(); + final PendingBalanceDeposit.PendingBalanceDepositSchema pendingBalanceDepositSchema = + new PendingBalanceDeposit.PendingBalanceDepositSchema(); + final PendingPartialWithdrawal.PendingPartialWithdrawalSchema pendingPartialWithdrawalSchema = + new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); + final SpecConfigEip7732 specConfigEip7732 = SpecConfigEip7732.required(specConfig); + final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema = + new PendingConsolidation.PendingConsolidationSchema(); + // Bellatrix + final SszField latestExecutionPayloadHeaderField = + new SszField( + LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, + BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, + () -> new ExecutionPayloadHeaderSchemaEip7732(specConfigEip7732)); + // Capella + final SszField nextWithdrawalIndexField = + new SszField( + NEXT_WITHDRAWAL_INDEX, + BeaconStateFields.NEXT_WITHDRAWAL_INDEX, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField nextWithdrawalValidatorIndexField = + new SszField( + NEXT_WITHDRAWAL_VALIDATOR_INDEX, + BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField historicalSummariesField = + new SszField( + HISTORICAL_SUMMARIES_INDEX, + BeaconStateFields.HISTORICAL_SUMMARIES, + () -> + SszListSchema.create( + historicalSummarySchema, specConfig.getHistoricalRootsLimit())); + // Electra + final SszField depositRequestsStartIndexField = + new SszField( + DEPOSIT_REQUESTS_START_INDEX, + BeaconStateFields.DEPOSIT_REQUESTS_START_INDEX, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField depositBalanceToConsumeField = + new SszField( + DEPOSIT_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField exitBalanceToConsumeField = + new SszField( + EXIT_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.EXIT_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField earliestExitEpochField = + new SszField( + EARLIEST_EXIT_EPOCH_INDEX, + BeaconStateFields.EARLIEST_EXIT_EPOCH, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField consolidationBalanceToConsumeField = + new SszField( + CONSOLIDATION_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField earliestConsolidationEpochField = + new SszField( + EARLIEST_CONSOLIDATION_EPOCH_INDEX, + BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField pendingBalanceDepositsField = + new SszField( + PENDING_BALANCE_DEPOSITS_INDEX, + BeaconStateFields.PENDING_BALANCE_DEPOSITS, + () -> + SszListSchema.create( + pendingBalanceDepositSchema, + specConfigEip7732.getPendingBalanceDepositsLimit())); + final SszField pendingPartialWithdrawalsField = + new SszField( + PENDING_PARTIAL_WITHDRAWALS_INDEX, + BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS, + () -> + SszListSchema.create( + pendingPartialWithdrawalSchema, + specConfigEip7732.getPendingPartialWithdrawalsLimit())); + final SszField pendingConsolidationsField = + new SszField( + PENDING_CONSOLIDATIONS_INDEX, + BeaconStateFields.PENDING_CONSOLIDATIONS, + () -> + SszListSchema.create( + pendingConsolidationSchema, specConfigEip7732.getPendingConsolidationsLimit())); + // New + final SszField latestBlockHashField = + new SszField( + LATEST_BLOCK_HASH_INDEX, + BeaconStateFields.LATEST_BLOCK_HASH, + () -> SszPrimitiveSchemas.BYTES32_SCHEMA); + final SszField latestFullSlotField = + new SszField( + LATEST_FULL_SLOT_INDEX, + BeaconStateFields.LATEST_FULL_SLOT, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField latestWithdrawalsRootField = + new SszField( + LATEST_WITHDRAWALS_ROOT_INDEX, + BeaconStateFields.LATEST_WITHDRAWALS_ROOT, + () -> SszPrimitiveSchemas.BYTES32_SCHEMA); + return Stream.concat( + BeaconStateSchemaAltair.getUniqueFields(specConfig).stream(), + Stream.of( + latestExecutionPayloadHeaderField, + nextWithdrawalIndexField, + nextWithdrawalValidatorIndexField, + historicalSummariesField, + depositRequestsStartIndexField, + depositBalanceToConsumeField, + exitBalanceToConsumeField, + earliestExitEpochField, + consolidationBalanceToConsumeField, + earliestConsolidationEpochField, + pendingBalanceDepositsField, + pendingPartialWithdrawalsField, + pendingConsolidationsField, + latestBlockHashField, + latestFullSlotField, + latestWithdrawalsRootField)) + .toList(); + } + + @SuppressWarnings("unchecked") + public SszPrimitiveListSchema getPreviousEpochParticipationSchema() { + return (SszPrimitiveListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PREVIOUS_EPOCH_PARTICIPATION)); + } + + @SuppressWarnings("unchecked") + public SszPrimitiveListSchema getCurrentEpochParticipationSchema() { + return (SszPrimitiveListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_EPOCH_PARTICIPATION)); + } + + public SszUInt64ListSchema getInactivityScoresSchema() { + return (SszUInt64ListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.INACTIVITY_SCORES)); + } + + public SyncCommittee.SyncCommitteeSchema getCurrentSyncCommitteeSchema() { + return (SyncCommittee.SyncCommitteeSchema) + getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_SYNC_COMMITTEE)); + } + + public ExecutionPayloadHeaderSchemaEip7732 getLastExecutionPayloadHeaderSchema() { + return (ExecutionPayloadHeaderSchemaEip7732) + getChildSchema(getFieldIndex(BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER)); + } + + @Override + public MutableBeaconStateEip7732 createBuilder() { + return new MutableBeaconStateEip7732Impl(createEmptyBeaconStateImpl(), true); + } + + public static BeaconStateSchemaEip7732 create(final SpecConfig specConfig) { + return new BeaconStateSchemaEip7732(specConfig); + } + + public static BeaconStateSchemaEip7732 required(final BeaconStateSchema schema) { + checkArgument( + schema instanceof BeaconStateSchemaEip7732, + "Expected a BeaconStateSchemaEip7732 but was %s", + schema.getClass()); + return (BeaconStateSchemaEip7732) schema; + } + + @SuppressWarnings("unchecked") + public SszListSchema getHistoricalSummariesSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.HISTORICAL_SUMMARIES)); + } + + @Override + public BeaconStateEip7732 createEmpty() { + return createEmptyBeaconStateImpl(); + } + + private BeaconStateEip7732Impl createEmptyBeaconStateImpl() { + return new BeaconStateEip7732Impl(this); + } + + @Override + public BeaconStateEip7732Impl createFromBackingNode(final TreeNode node) { + return new BeaconStateEip7732Impl(this, node); + } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingBalanceDepositsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_BALANCE_DEPOSITS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingPartialWithdrawalsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingConsolidationsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_CONSOLIDATIONS)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732.java new file mode 100644 index 00000000000..250d9030b72 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732.java @@ -0,0 +1,57 @@ +/* + * 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.state.beaconstate.versions.eip7732; + +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; + +public interface MutableBeaconStateEip7732 extends MutableBeaconStateElectra, BeaconStateEip7732 { + static MutableBeaconStateEip7732 required(final MutableBeaconState state) { + return state + .toMutableVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected an Eip7732 state but got: " + state.getClass().getSimpleName())); + } + + @Override + BeaconStateEip7732 commitChanges(); + + @Override + default Optional toMutableVersionEip7732() { + return Optional.of(this); + } + + default void setLatestBlockHash(final Bytes32 latestBlockHash) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.LATEST_BLOCK_HASH); + set(fieldIndex, SszBytes32.of(latestBlockHash)); + } + + default void setLatestFullSlot(final UInt64 latestFullSlot) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.LATEST_FULL_SLOT); + set(fieldIndex, SszUInt64.of(latestFullSlot)); + } + + default void setLatestWithdrawalsRoot(final Bytes32 latestWithdrawalsRoot) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.LATEST_WITHDRAWALS_ROOT); + set(fieldIndex, SszBytes32.of(latestWithdrawalsRoot)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732Impl.java new file mode 100644 index 00000000000..ebfa65488d2 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7732/MutableBeaconStateEip7732Impl.java @@ -0,0 +1,63 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732; + +import com.google.common.base.MoreObjects; +import tech.pegasys.teku.infrastructure.ssz.SszData; +import tech.pegasys.teku.infrastructure.ssz.cache.IntCache; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.AbstractMutableBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.SlotCaches; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.TransitionCaches; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.ValidatorStatsAltair; + +public class MutableBeaconStateEip7732Impl + extends AbstractMutableBeaconState + implements MutableBeaconStateEip7732, BeaconStateCache, ValidatorStatsAltair { + + MutableBeaconStateEip7732Impl(final BeaconStateEip7732Impl backingImmutableView) { + super(backingImmutableView); + } + + MutableBeaconStateEip7732Impl( + final BeaconStateEip7732Impl backingImmutableView, final boolean builder) { + super(backingImmutableView, builder); + } + + @Override + protected BeaconStateEip7732Impl createImmutableBeaconState( + final TreeNode backingNode, + final IntCache viewCache, + final TransitionCaches transitionCaches, + final SlotCaches slotCaches) { + return new BeaconStateEip7732Impl( + getSchema(), backingNode, viewCache, transitionCaches, slotCaches); + } + + @Override + protected void addCustomFields(final MoreObjects.ToStringHelper stringBuilder) { + BeaconStateEip7732.describeCustomEip7732Fields(stringBuilder, this); + } + + @Override + public BeaconStateEip7732 commitChanges() { + return (BeaconStateEip7732) super.commitChanges(); + } + + @Override + public MutableBeaconStateEip7732 createWritableCopy() { + return (MutableBeaconStateEip7732) super.createWritableCopy(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java index 8c88129ad30..2e229900f0d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java @@ -69,11 +69,13 @@ private static List getUniqueFields(final SpecConfig specConfig) { final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(specConfig); final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); + // Bellatrix final SszField latestExecutionPayloadHeaderField = new SszField( LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, () -> new ExecutionPayloadHeaderSchemaElectra(specConfigElectra)); + // Capella final SszField nextWithdrawalIndexField = new SszField( NEXT_WITHDRAWAL_INDEX, @@ -84,7 +86,6 @@ private static List getUniqueFields(final SpecConfig specConfig) { NEXT_WITHDRAWAL_VALIDATOR_INDEX, BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField historicalSummariesField = new SszField( HISTORICAL_SUMMARIES_INDEX, @@ -92,6 +93,7 @@ private static List getUniqueFields(final SpecConfig specConfig) { () -> SszListSchema.create( historicalSummarySchema, specConfig.getHistoricalRootsLimit())); + // New final SszField depositRequestsStartIndexField = new SszField( DEPOSIT_REQUESTS_START_INDEX, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/DelegatingSpecLogic.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/DelegatingSpecLogic.java index 0b873f0ed80..74043354002 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/DelegatingSpecLogic.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/DelegatingSpecLogic.java @@ -15,6 +15,7 @@ import java.util.Optional; import tech.pegasys.teku.spec.logic.common.block.BlockProcessor; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.forktransition.StateUpgrade; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; @@ -111,6 +112,11 @@ public Optional getBellatrixTransitionHelpers() { return specLogic.getBellatrixTransitionHelpers(); } + @Override + public Optional getExecutionPayloadProcessor() { + return specLogic.getExecutionPayloadProcessor(); + } + @Override public Predicates predicates() { return specLogic.predicates(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/SpecLogic.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/SpecLogic.java index 12a57a1a6c0..7acb60b90fe 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/SpecLogic.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/SpecLogic.java @@ -15,6 +15,7 @@ import java.util.Optional; import tech.pegasys.teku.spec.logic.common.block.BlockProcessor; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.forktransition.StateUpgrade; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; @@ -72,4 +73,6 @@ public interface SpecLogic { OperationSignatureVerifier operationSignatureVerifier(); Optional getBellatrixTransitionHelpers(); + + Optional getExecutionPayloadProcessor(); } 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 24d20bb7aee..355504f1c2b 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 @@ -449,8 +449,10 @@ protected void processOperationsNoValidation( processDeposits(state, body.getDeposits()); processVoluntaryExitsNoValidation( state, body.getVoluntaryExits(), validatorExitContextSupplier); + processDepositRequests(state, body.getOptionalExecutionPayload()); processWithdrawalRequests( state, body.getOptionalExecutionPayload(), validatorExitContextSupplier); + processConsolidationRequests(state, body.getOptionalExecutionPayload()); }); } @@ -900,21 +902,32 @@ protected BlockValidationResult verifyVoluntaryExits( return BlockValidationResult.SUCCESSFUL; } - protected void processWithdrawalRequests( - final MutableBeaconState state, - final Optional executionPayload, - final Supplier validatorExitContextSupplier) + @Override + public void processExecutionPayloadHeader(final MutableBeaconState state, final BeaconBlock block) throws BlockProcessingException { - // No WithdrawalRequests until Electra + // No processExecutionPayloadHeader until EIP-7732 + } + + protected void processDepositRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + // No DepositRequests until Electra } @Override public void processDepositRequests( - final MutableBeaconState state, final SszList depositRequests) - throws BlockProcessingException { + final MutableBeaconState state, final SszList depositRequests) { // No DepositRequests until Electra } + protected void processWithdrawalRequests( + final MutableBeaconState state, + final Optional executionPayload, + final Supplier validatorExitContextSupplier) + throws BlockProcessingException { + // No WithdrawalRequests until Electra + } + @Override public void processWithdrawalRequests( final MutableBeaconState state, @@ -924,6 +937,12 @@ public void processWithdrawalRequests( // No WithdrawalRequests until Electra } + protected void processConsolidationRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + // No Consolidations until Electra + } + @Override public void processConsolidationRequests( final MutableBeaconState state, final SszList consolidationRequests) 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 5bb086645bb..5fad610a11f 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 @@ -168,6 +168,9 @@ void processBlsToExecutionChanges( void processWithdrawals(MutableBeaconState state, ExecutionPayloadSummary payloadSummary) throws BlockProcessingException; + void processExecutionPayloadHeader(MutableBeaconState state, BeaconBlock block) + throws BlockProcessingException; + void processDepositRequests(MutableBeaconState state, SszList depositRequests) throws BlockProcessingException; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/AbstractExecutionPayloadProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/AbstractExecutionPayloadProcessor.java new file mode 100644 index 00000000000..f983a5c0105 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/AbstractExecutionPayloadProcessor.java @@ -0,0 +1,74 @@ +/* + * 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.logic.common.execution; + +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.ExecutionPayloadProcessingException; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; + +public abstract class AbstractExecutionPayloadProcessor implements ExecutionPayloadProcessor { + + private static final Logger LOG = LogManager.getLogger(); + + @Override + public BeaconState processAndVerifyExecutionPayload( + final BeaconState preState, + final SignedExecutionPayloadEnvelope signedEnvelope, + final Optional payloadExecutor) + throws StateTransitionException { + if (!verifyExecutionPayloadEnvelopeSignature(preState, signedEnvelope)) { + throw new StateTransitionException( + "Signature verification failed for signed envelope with beacon block root " + + signedEnvelope.getMessage().getBeaconBlockRoot()); + } + final BeaconState postState = + preState.updated( + state -> { + try { + processExecutionPayload(state, signedEnvelope.getMessage(), payloadExecutor); + } catch (ExecutionPayloadProcessingException ex) { + LOG.warn( + "State transition error while importing execution payload with beacon block root " + + signedEnvelope.getMessage().getBeaconBlockRoot(), + ex); + throw new StateTransitionException(ex); + } + }); + if (signedEnvelope.getMessage().getStateRoot().equals(postState.hashTreeRoot())) { + throw new StateTransitionException( + "State root of the signed envelope does not match the post-processing state root"); + } + return postState; + } + + // Catch generic errors and wrap them in an ExecutionPayloadProcessingException + protected void safelyProcess(final ExecutionPayloadProcessingAction action) + throws ExecutionPayloadProcessingException { + try { + action.run(); + } catch (Exception ex) { + LOG.warn("Failed to process execution payload", ex); + throw new ExecutionPayloadProcessingException(ex); + } + } + + protected interface ExecutionPayloadProcessingAction { + void run() throws ExecutionPayloadProcessingException; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/ExecutionPayloadProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/ExecutionPayloadProcessor.java new file mode 100644 index 00000000000..189f919891c --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/execution/ExecutionPayloadProcessor.java @@ -0,0 +1,44 @@ +/* + * 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.logic.common.execution; + +import java.util.Optional; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.ExecutionPayloadProcessingException; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; + +public interface ExecutionPayloadProcessor { + + BeaconState processAndVerifyExecutionPayload( + BeaconState preState, + SignedExecutionPayloadEnvelope signedEnvelope, + Optional payloadExecutor) + throws StateTransitionException; + + boolean verifyExecutionPayloadEnvelopeSignature( + BeaconState state, SignedExecutionPayloadEnvelope signedEnvelope); + + void processExecutionPayload( + MutableBeaconState state, + ExecutionPayloadEnvelope envelope, + Optional payloadExecutor) + throws ExecutionPayloadProcessingException; + + NewPayloadRequest computeNewPayloadRequest(BeaconState state, ExecutionPayloadEnvelope envelope); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java index 0d47860fffa..a0a32f98374 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java @@ -134,4 +134,12 @@ static Bytes32 uintToBytes32(final UInt64 value) { public static UInt64 bytesToUInt64(final Bytes data) { return UInt64.fromLongBits(data.toLong(ByteOrder.LITTLE_ENDIAN)); } + + /** if ``n`` is not zero, returns the largest power of `2` that is not greater than `n`. */ + public static UInt64 bitFloor(final UInt64 n) { + if (n.isZero()) { + return UInt64.ZERO; + } + return UInt64.valueOf(1L << (n.bigIntegerValue().bitLength() - 1)); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 3ba1a0c6355..f93546e18b7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -46,6 +46,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.MiscHelpersEip7732; import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; public class MiscHelpers { @@ -397,4 +398,8 @@ public Optional toVersionDeneb() { public Optional toVersionElectra() { return Optional.empty(); } + + public Optional toVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java index 528564150be..d6cd45e0ee4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.PredicatesEip7732; import tech.pegasys.teku.spec.logic.versions.electra.helpers.PredicatesElectra; public class Predicates { @@ -175,4 +176,8 @@ public UInt64 getValidatorMaxEffectiveBalance(final Validator validator) { public Optional toVersionElectra() { return Optional.empty(); } + + public Optional toVersionEip7732() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/statetransition/exceptions/ExecutionPayloadProcessingException.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/statetransition/exceptions/ExecutionPayloadProcessingException.java new file mode 100644 index 00000000000..686cf26726b --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/statetransition/exceptions/ExecutionPayloadProcessingException.java @@ -0,0 +1,25 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.common.statetransition.exceptions; + +public final class ExecutionPayloadProcessingException extends Exception { + + public ExecutionPayloadProcessingException(final String message) { + super(message); + } + + public ExecutionPayloadProcessingException(final Exception ex) { + super(ex); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java index 028a0acd3b3..218555aafa8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java @@ -37,6 +37,7 @@ import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; @@ -300,6 +301,13 @@ public AttestationData getGenericAttestationData( performSlotInclusionGossipValidation( Attestation attestation, UInt64 genesisTime, UInt64 currentTimeMillis); + public Optional performSlotInclusionGossipValidation( + final PayloadAttestationData payloadAttestationData, + final UInt64 genesisTime, + final UInt64 currentTimeMillis) { + return Optional.empty(); + } + public enum SlotInclusionGossipValidationResult { IGNORE, SAVE_FOR_FUTURE diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ValidatorsUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ValidatorsUtil.java index 63241c6da29..8a70c9bb1e9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ValidatorsUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ValidatorsUtil.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.bytesToUInt64; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntList; import java.util.HashMap; import java.util.Map; @@ -32,7 +34,9 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; +import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; public class ValidatorsUtil { private final SpecConfig specConfig; @@ -98,6 +102,33 @@ public Map getValidatorIndexToCommitteeAssignmentM return assignmentMap; } + public Int2ObjectMap getValidatorIndexToPctAssignmentMap( + final BeaconState state, final UInt64 epoch) { + final Int2ObjectMap assignmentMap = new Int2ObjectArrayMap<>(); + + final int slotsPerEpoch = specConfig.getSlotsPerEpoch(); + final int committeeCountPerSlot = getPtCommitteesPerSlot(state, epoch).intValue(); + final BeaconStateAccessorsEip7732 beaconStateAccessorsEip7732 = + BeaconStateAccessorsEip7732.required(beaconStateAccessors); + + final UInt64 startSlot = miscHelpers.computeStartSlotAtEpoch(epoch); + for (int slotOffset = 0; slotOffset < slotsPerEpoch; slotOffset++) { + final UInt64 slot = startSlot.plus(slotOffset); + for (int i = 0; i < committeeCountPerSlot; i++) { + final IntList committee = beaconStateAccessorsEip7732.getPtc(state, slot); + committee.forEach(j -> assignmentMap.put(j, slot)); + } + } + return assignmentMap; + } + + private UInt64 getPtCommitteesPerSlot(final BeaconState state, final UInt64 epoch) { + return MathHelpers.bitFloor( + BeaconStateAccessorsEip7732.required(beaconStateAccessors) + .getCommitteeCountPerSlot(state, epoch) + .min(specConfig.toVersionEip7732().orElseThrow().getPtcSize())); + } + /** * Return the committee assignment in the ``epoch`` for ``validator_index``. ``assignment`` * returned is a tuple of the following form: ``assignment[0]`` is the list of validators in the diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/SpecLogicAltair.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/SpecLogicAltair.java index a216b593035..75e99132c6b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/SpecLogicAltair.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/SpecLogicAltair.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfigAltair; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; @@ -205,4 +206,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return Optional.empty(); } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/SpecLogicBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/SpecLogicBellatrix.java index a68522b0496..6dfae5a9816 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/SpecLogicBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/SpecLogicBellatrix.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; import tech.pegasys.teku.spec.logic.common.operations.validation.AttestationDataValidator; @@ -216,4 +217,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return bellatrixTransitionHelpers; } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java index d5b0c3f1a29..7556f0c4f8e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java @@ -97,7 +97,7 @@ public void processBlock( processBlockHeader(state, block); if (miscHelpersBellatrix.isExecutionEnabled(genericState, block)) { - executionProcessing(genericState, block.getBody(), payloadExecutor); + executionProcessing(genericState, block, payloadExecutor); } processRandaoNoValidation(state, block.getBody()); processEth1Data(state, block.getBody()); @@ -108,10 +108,10 @@ public void processBlock( public void executionProcessing( final MutableBeaconState genericState, - final BeaconBlockBody beaconBlockBody, + final BeaconBlock block, final Optional payloadExecutor) throws BlockProcessingException { - processExecutionPayload(genericState, beaconBlockBody, payloadExecutor); + processExecutionPayload(genericState, block.getBody(), payloadExecutor); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/SpecLogicCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/SpecLogicCapella.java index fd7aa210f36..4bc38281dcf 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/SpecLogicCapella.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/SpecLogicCapella.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; import tech.pegasys.teku.spec.logic.common.operations.validation.AttestationDataValidator; @@ -216,4 +217,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return Optional.of(bellatrixTransitionHelpers); } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java index 7a5d88f95de..9be7037a583 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.cache.IndexedAttestationCache; import tech.pegasys.teku.spec.config.SpecConfigCapella; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; @@ -96,13 +97,13 @@ public BlockProcessorCapella( @Override public void executionProcessing( final MutableBeaconState genericState, - final BeaconBlockBody beaconBlockBody, + final BeaconBlock block, final Optional payloadExecutor) throws BlockProcessingException { final ExecutionPayloadHeader executionPayloadHeader = - extractExecutionPayloadHeader(beaconBlockBody); + extractExecutionPayloadHeader(block.getBody()); processWithdrawals(genericState, executionPayloadHeader); - super.executionProcessing(genericState, beaconBlockBody, payloadExecutor); + super.executionProcessing(genericState, block, payloadExecutor); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/SpecLogicDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/SpecLogicDeneb.java index 92c6112f75b..95cc74b39dd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/SpecLogicDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/SpecLogicDeneb.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; import tech.pegasys.teku.spec.logic.common.operations.validation.AttestationDataValidator; @@ -212,4 +213,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return Optional.empty(); } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java index 0c49d887a1c..c2bba7c01a4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java @@ -48,8 +48,8 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; public class MiscHelpersDeneb extends MiscHelpersCapella { - private final Predicates predicates; - private final BeaconBlockBodySchemaDeneb beaconBlockBodySchema; + protected final Predicates predicates; + protected final BeaconBlockBodySchemaDeneb beaconBlockBodySchema; private final BlobSidecarSchema blobSidecarSchema; public static MiscHelpersDeneb required(final MiscHelpers miscHelpers) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/SpecLogicEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/SpecLogicEip7732.java new file mode 100644 index 00000000000..8ae6cfbab57 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/SpecLogicEip7732.java @@ -0,0 +1,251 @@ +/* + * 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.logic.versions.eip7732; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; +import tech.pegasys.teku.spec.logic.common.helpers.Predicates; +import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; +import tech.pegasys.teku.spec.logic.common.operations.validation.AttestationDataValidator; +import tech.pegasys.teku.spec.logic.common.operations.validation.OperationValidator; +import tech.pegasys.teku.spec.logic.common.util.AttestationUtil; +import tech.pegasys.teku.spec.logic.common.util.BeaconStateUtil; +import tech.pegasys.teku.spec.logic.common.util.BlindBlockUtil; +import tech.pegasys.teku.spec.logic.common.util.BlockProposalUtil; +import tech.pegasys.teku.spec.logic.common.util.ForkChoiceUtil; +import tech.pegasys.teku.spec.logic.common.util.LightClientUtil; +import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; +import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; +import tech.pegasys.teku.spec.logic.versions.altair.statetransition.epoch.ValidatorStatusFactoryAltair; +import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.BeaconStateMutatorsBellatrix; +import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.BellatrixTransitionHelpers; +import tech.pegasys.teku.spec.logic.versions.bellatrix.util.BlindBlockUtilBellatrix; +import tech.pegasys.teku.spec.logic.versions.capella.operations.validation.OperationValidatorCapella; +import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; +import tech.pegasys.teku.spec.logic.versions.deneb.util.ForkChoiceUtilDeneb; +import tech.pegasys.teku.spec.logic.versions.eip7732.block.BlockProcessorEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.execution.ExecutionPayloadProcessorEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.forktransition.Eip7732StateUpgrade; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.MiscHelpersEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.PredicatesEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.util.AttestationUtilEip7732; +import tech.pegasys.teku.spec.logic.versions.electra.block.BlockProcessorElectra; +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.operations.validation.AttestationDataValidatorElectra; +import tech.pegasys.teku.spec.logic.versions.electra.operations.validation.VoluntaryExitValidatorElectra; +import tech.pegasys.teku.spec.logic.versions.electra.statetransition.epoch.EpochProcessorElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; + +public class SpecLogicEip7732 extends AbstractSpecLogic { + private final Optional syncCommitteeUtil; + private final Optional lightClientUtil; + private final ExecutionPayloadProcessor executionPayloadProcessor; + + private SpecLogicEip7732( + final Predicates predicates, + final MiscHelpersDeneb miscHelpers, + final BeaconStateAccessorsElectra beaconStateAccessors, + final BeaconStateMutatorsBellatrix beaconStateMutators, + final OperationSignatureVerifier operationSignatureVerifier, + final ValidatorsUtil validatorsUtil, + final BeaconStateUtil beaconStateUtil, + final AttestationUtil attestationUtil, + final OperationValidator operationValidator, + final ValidatorStatusFactoryAltair validatorStatusFactory, + final EpochProcessorElectra epochProcessor, + final BlockProcessorElectra blockProcessor, + final ForkChoiceUtil forkChoiceUtil, + final BlockProposalUtil blockProposalUtil, + final BlindBlockUtil blindBlockUtil, + final SyncCommitteeUtil syncCommitteeUtil, + final LightClientUtil lightClientUtil, + final Eip7732StateUpgrade stateUpgrade, + final ExecutionPayloadProcessorEip7732 executionPayloadProcessor) { + super( + predicates, + miscHelpers, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + validatorsUtil, + beaconStateUtil, + attestationUtil, + operationValidator, + validatorStatusFactory, + epochProcessor, + blockProcessor, + forkChoiceUtil, + blockProposalUtil, + Optional.of(blindBlockUtil), + Optional.of(stateUpgrade)); + this.syncCommitteeUtil = Optional.of(syncCommitteeUtil); + this.lightClientUtil = Optional.of(lightClientUtil); + this.executionPayloadProcessor = executionPayloadProcessor; + } + + public static SpecLogicEip7732 create( + final SpecConfigEip7732 config, + final SchemaDefinitionsEip7732 schemaDefinitions, + final TimeProvider timeProvider) { + // Helpers + final PredicatesEip7732 predicates = new PredicatesEip7732(config); + final MiscHelpersEip7732 miscHelpers = + new MiscHelpersEip7732(config, predicates, schemaDefinitions); + final BeaconStateAccessorsEip7732 beaconStateAccessors = + new BeaconStateAccessorsEip7732(config, predicates, miscHelpers, schemaDefinitions); + final BeaconStateMutatorsElectra beaconStateMutators = + new BeaconStateMutatorsElectra( + config, miscHelpers, beaconStateAccessors, schemaDefinitions); + + // Operation validation + final OperationSignatureVerifier operationSignatureVerifier = + new OperationSignatureVerifier(miscHelpers, beaconStateAccessors); + + // Util + final ValidatorsUtil validatorsUtil = + new ValidatorsUtil(config, miscHelpers, beaconStateAccessors); + final BeaconStateUtil beaconStateUtil = + new BeaconStateUtil( + config, schemaDefinitions, predicates, miscHelpers, beaconStateAccessors); + final AttestationUtilEip7732 attestationUtil = + new AttestationUtilEip7732(config, schemaDefinitions, beaconStateAccessors, miscHelpers); + final AttestationDataValidator attestationDataValidator = + new AttestationDataValidatorElectra(config, miscHelpers, beaconStateAccessors); + final VoluntaryExitValidatorElectra voluntaryExitValidatorElectra = + new VoluntaryExitValidatorElectra(config, predicates, beaconStateAccessors); + final OperationValidator operationValidator = + new OperationValidatorCapella( + predicates, + beaconStateAccessors, + attestationDataValidator, + attestationUtil, + voluntaryExitValidatorElectra); + final ValidatorStatusFactoryAltair validatorStatusFactory = + new ValidatorStatusFactoryAltair( + config, + beaconStateUtil, + attestationUtil, + predicates, + miscHelpers, + beaconStateAccessors); + final EpochProcessorElectra epochProcessor = + new EpochProcessorElectra( + config, + miscHelpers, + beaconStateAccessors, + beaconStateMutators, + validatorsUtil, + beaconStateUtil, + validatorStatusFactory, + schemaDefinitions, + timeProvider); + final SyncCommitteeUtil syncCommitteeUtil = + new SyncCommitteeUtil( + beaconStateAccessors, validatorsUtil, config, miscHelpers, schemaDefinitions); + final LightClientUtil lightClientUtil = + new LightClientUtil(beaconStateAccessors, syncCommitteeUtil, schemaDefinitions); + final BlockProcessorEip7732 blockProcessor = + new BlockProcessorEip7732( + config, + predicates, + miscHelpers, + syncCommitteeUtil, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + beaconStateUtil, + attestationUtil, + validatorsUtil, + operationValidator, + schemaDefinitions); + final ForkChoiceUtil forkChoiceUtil = + new ForkChoiceUtilDeneb( + config, beaconStateAccessors, epochProcessor, attestationUtil, miscHelpers); + final BlockProposalUtil blockProposalUtil = + new BlockProposalUtil(schemaDefinitions, blockProcessor); + + final BlindBlockUtilBellatrix blindBlockUtil = new BlindBlockUtilBellatrix(schemaDefinitions); + + // State upgrade + final Eip7732StateUpgrade stateUpgrade = + new Eip7732StateUpgrade( + config, schemaDefinitions, beaconStateAccessors, beaconStateMutators); + + // Execution payload processing + // EIP7732 TODO: dirty way to leverage Electra operations + final BlockProcessorElectra blockProcessorElectra = + new BlockProcessorElectra( + config, + predicates, + miscHelpers, + syncCommitteeUtil, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + beaconStateUtil, + attestationUtil, + validatorsUtil, + operationValidator, + schemaDefinitions); + final ExecutionPayloadProcessorEip7732 executionPayloadProcessor = + new ExecutionPayloadProcessorEip7732( + config, miscHelpers, beaconStateAccessors, beaconStateMutators, blockProcessorElectra); + + return new SpecLogicEip7732( + predicates, + miscHelpers, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + validatorsUtil, + beaconStateUtil, + attestationUtil, + operationValidator, + validatorStatusFactory, + epochProcessor, + blockProcessor, + forkChoiceUtil, + blockProposalUtil, + blindBlockUtil, + syncCommitteeUtil, + lightClientUtil, + stateUpgrade, + executionPayloadProcessor); + } + + @Override + public Optional getSyncCommitteeUtil() { + return syncCommitteeUtil; + } + + @Override + public Optional getLightClientUtil() { + return lightClientUtil; + } + + @Override + public Optional getBellatrixTransitionHelpers() { + return Optional.empty(); + } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.of(executionPayloadProcessor); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/block/BlockProcessorEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/block/BlockProcessorEip7732.java new file mode 100644 index 00000000000..7ff0ce73e4a --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/block/BlockProcessorEip7732.java @@ -0,0 +1,307 @@ +/* + * 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.logic.versions.eip7732.block; + +import static tech.pegasys.teku.spec.constants.IncentivizationWeights.PROPOSER_WEIGHT; +import static tech.pegasys.teku.spec.constants.IncentivizationWeights.WEIGHT_DENOMINATOR; +import static tech.pegasys.teku.spec.logic.versions.altair.helpers.MiscHelpersAltair.PARTICIPATION_FLAG_WEIGHTS; + +import java.util.Optional; +import java.util.function.Supplier; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.SszMutableList; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.cache.IndexedAttestationCache; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.constants.Domain; +import tech.pegasys.teku.spec.constants.PayloadStatus; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodyEip7732; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; +import tech.pegasys.teku.spec.datastructures.operations.IndexedPayloadAttestation; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.MutableBeaconStateEip7732; +import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; +import tech.pegasys.teku.spec.logic.common.helpers.Predicates; +import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; +import tech.pegasys.teku.spec.logic.common.operations.validation.OperationValidator; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; +import tech.pegasys.teku.spec.logic.common.util.BeaconStateUtil; +import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; +import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; +import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.MiscHelpersEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.util.AttestationUtilEip7732; +import tech.pegasys.teku.spec.logic.versions.electra.block.BlockProcessorElectra; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.BeaconStateMutatorsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; + +public class BlockProcessorEip7732 extends BlockProcessorElectra { + + private final MiscHelpersEip7732 miscHelpersEip7732; + private final BeaconStateAccessorsEip7732 beaconStateAccessorsEip7732; + private final AttestationUtilEip7732 attestationUtilEip7732; + + public BlockProcessorEip7732( + final SpecConfigEip7732 specConfig, + final Predicates predicates, + final MiscHelpersEip7732 miscHelpers, + final SyncCommitteeUtil syncCommitteeUtil, + final BeaconStateAccessorsEip7732 beaconStateAccessors, + final BeaconStateMutatorsElectra beaconStateMutators, + final OperationSignatureVerifier operationSignatureVerifier, + final BeaconStateUtil beaconStateUtil, + final AttestationUtilEip7732 attestationUtil, + final ValidatorsUtil validatorsUtil, + final OperationValidator operationValidator, + final SchemaDefinitionsEip7732 schemaDefinitions) { + super( + specConfig, + predicates, + miscHelpers, + syncCommitteeUtil, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + beaconStateUtil, + attestationUtil, + validatorsUtil, + operationValidator, + schemaDefinitions); + this.miscHelpersEip7732 = miscHelpers; + this.beaconStateAccessorsEip7732 = beaconStateAccessors; + this.attestationUtilEip7732 = attestationUtil; + } + + @Override + public void executionProcessing( + final MutableBeaconState genericState, + final BeaconBlock block, + final Optional payloadExecutor) + throws BlockProcessingException { + super.executionProcessing(genericState, block, payloadExecutor); + processExecutionPayloadHeader(genericState, block); + } + + @Override + protected void processOperationsNoValidation( + final MutableBeaconState state, + final BeaconBlockBody body, + final IndexedAttestationCache indexedAttestationCache) + throws BlockProcessingException { + super.processOperationsNoValidation(state, body, indexedAttestationCache); + + safelyProcess( + () -> + processPayloadAttestations( + MutableBeaconStateEip7732.required(state), + body.getOptionalPayloadAttestations() + .orElseThrow( + () -> + new BlockProcessingException( + "PayloadAttestations was not found during block processing.")))); + } + + // process_execution_payload_header + @Override + public void processExecutionPayloadHeader(final MutableBeaconState state, final BeaconBlock block) + throws BlockProcessingException { + final SignedExecutionPayloadHeader signedHeader = + BeaconBlockBodyEip7732.required(block.getBody()).getSignedExecutionPayloadHeader(); + + if (!verifyExecutionPayloadHeaderSignature(state, signedHeader)) { + throw new BlockProcessingException("Header signature is not valid"); + } + + final ExecutionPayloadHeaderEip7732 header = + ExecutionPayloadHeaderEip7732.required(signedHeader.getMessage()); + + final int builderIndex = header.getBuilderIndex().intValue(); + final UInt64 amount = header.getValue(); + + if (!state.getBalances().get(builderIndex).get().isGreaterThanOrEqualTo(amount)) { + throw new BlockProcessingException("Builder does not have the funds to cover the bid"); + } + + if (!header.getSlot().equals(state.getSlot())) { + throw new BlockProcessingException("Bid is not for the current slot"); + } + + final MutableBeaconStateEip7732 stateEip7732 = MutableBeaconStateEip7732.required(state); + + if (!header.getParentBlockHash().equals(stateEip7732.getLatestBlockHash()) + || !header.getParentBlockRoot().equals(block.getParentRoot())) { + throw new BlockProcessingException("Bid is not for the right parent block"); + } + + beaconStateMutators.decreaseBalance(state, builderIndex, amount); + beaconStateMutators.increaseBalance(state, block.getProposerIndex().intValue(), amount); + + stateEip7732.setLatestExecutionPayloadHeader(header); + } + + public boolean verifyExecutionPayloadHeaderSignature( + final MutableBeaconState state, final SignedExecutionPayloadHeader signedHeader) { + final Validator builder = + state + .getValidators() + .get( + ExecutionPayloadHeaderEip7732.required(signedHeader.getMessage()) + .getBuilderIndex() + .intValue()); + final Bytes signingRoot = + miscHelpers.computeSigningRoot( + signedHeader.getMessage(), + beaconStateAccessors.getDomain( + state.getForkInfo(), + Domain.BEACON_BUILDER, + miscHelpers.computeEpochAtSlot(state.getSlot()))); + return BLS.verify(builder.getPublicKey(), signingRoot, signedHeader.getSignature()); + } + + public void processPayloadAttestations( + final MutableBeaconStateEip7732 state, final SszList payloadAttestations) + throws BlockProcessingException { + // process_payload_attestation + for (PayloadAttestation payloadAttestation : payloadAttestations) { + final PayloadAttestationData data = payloadAttestation.getData(); + if (!data.getBeaconBlockRoot().equals(state.getLatestBlockHeader().getParentRoot())) { + throw new BlockProcessingException("Attestation is not for the parent beacon block"); + } + if (!data.getSlot().increment().equals(state.getSlot())) { + throw new BlockProcessingException("Attestation is not for the previous slot"); + } + final IndexedPayloadAttestation indexedPayloadAttestation = + beaconStateAccessorsEip7732.getIndexedPayloadAttestation( + state, data.getSlot(), payloadAttestation); + if (!attestationUtilEip7732.isValidIndexedPayloadAttestation( + state, indexedPayloadAttestation)) { + throw new BlockProcessingException("Invalid indexed payload attestation"); + } + + final SszMutableList epochParticipation; + + if (state.getSlot().mod(specConfig.getSlotsPerEpoch()).isZero()) { + epochParticipation = state.getPreviousEpochParticipation(); + } else { + epochParticipation = state.getCurrentEpochParticipation(); + } + + final boolean payloadWasPresent = data.getSlot().equals(state.getLatestFullSlot()); + final boolean votedPresent = data.getPayloadStatus().equals(PayloadStatus.PAYLOAD_PRESENT); + + final UInt64 proposerRewardDenominator = + WEIGHT_DENOMINATOR + .minusMinZero(PROPOSER_WEIGHT) + .times(WEIGHT_DENOMINATOR.dividedBy(PROPOSER_WEIGHT)); + + final int proposerIndex = beaconStateAccessors.getBeaconProposerIndex(state); + + // Return early if the attestation is for the wrong payload status + if (votedPresent != payloadWasPresent) { + // Unset the flags in case they were set by an equivocating ptc attestation + UInt64 proposerPenaltyNumerator = UInt64.ZERO; + + for (SszUInt64 attestingIndex : indexedPayloadAttestation.getAttestingIndices()) { + final int index = attestingIndex.get().intValue(); + for (int flagIndex = 0; flagIndex < PARTICIPATION_FLAG_WEIGHTS.size(); flagIndex++) { + if (miscHelpersEip7732.hasFlag(epochParticipation.get(index).get(), flagIndex)) { + epochParticipation.set( + index, + SszByte.of( + miscHelpersEip7732.removeFlag( + epochParticipation.get(index).get(), flagIndex))); + proposerPenaltyNumerator = + proposerPenaltyNumerator.plus( + beaconStateAccessorsEip7732 + .getBaseReward(state, index) + .times(PARTICIPATION_FLAG_WEIGHTS.get(flagIndex))); + } + } + } + // Penalize the proposer + final UInt64 proposerPenalty = + proposerPenaltyNumerator.times(2).dividedBy(proposerRewardDenominator); + + beaconStateMutators.decreaseBalance(state, proposerIndex, proposerPenalty); + return; + } + + // Reward the proposer and set all the participation flags in case of correct attestations + UInt64 proposerRewardNumerator = UInt64.ZERO; + + for (SszUInt64 attestingIndex : indexedPayloadAttestation.getAttestingIndices()) { + final int index = attestingIndex.get().intValue(); + for (int flagIndex = 0; flagIndex < PARTICIPATION_FLAG_WEIGHTS.size(); flagIndex++) { + if (!miscHelpersEip7732.hasFlag(epochParticipation.get(index).get(), flagIndex)) { + epochParticipation.set( + index, + SszByte.of( + miscHelpersEip7732.addFlag(epochParticipation.get(index).get(), flagIndex))); + proposerRewardNumerator = + proposerRewardNumerator.plus( + beaconStateAccessorsEip7732 + .getBaseReward(state, index) + .times(PARTICIPATION_FLAG_WEIGHTS.get(flagIndex))); + } + } + } + + // Reward proposer + final UInt64 proposerReward = proposerRewardNumerator.dividedBy(proposerRewardDenominator); + beaconStateMutators.increaseBalance(state, proposerIndex, proposerReward); + } + } + + @Override + public void processExecutionPayload( + final MutableBeaconState genericState, + final BeaconBlockBody beaconBlockBody, + final Optional payloadExecutor) { + // Removed in EIP-7732 + } + + @Override + protected void processDepositRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + // Removed in EIP-7732 + } + + @Override + protected void processWithdrawalRequests( + final MutableBeaconState state, + final Optional executionPayload, + final Supplier validatorExitContextSupplier) { + // Removed in EIP-7732 + } + + @Override + protected void processConsolidationRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + // Removed in EIP-7732 + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/execution/ExecutionPayloadProcessorEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/execution/ExecutionPayloadProcessorEip7732.java new file mode 100644 index 00000000000..13af9e08022 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/execution/ExecutionPayloadProcessorEip7732.java @@ -0,0 +1,224 @@ +/* + * 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.logic.versions.eip7732.execution; + +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.constants.Domain; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadEip7732; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; +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.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.MutableBeaconStateEip7732; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.logic.common.execution.AbstractExecutionPayloadProcessor; +import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; +import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.ExecutionPayloadProcessingException; +import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; +import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.MiscHelpersEip7732; +import tech.pegasys.teku.spec.logic.versions.electra.block.BlockProcessorElectra; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.BeaconStateMutatorsElectra; + +public class ExecutionPayloadProcessorEip7732 extends AbstractExecutionPayloadProcessor { + + protected final SpecConfigEip7732 specConfig; + protected final MiscHelpersEip7732 miscHelpers; + protected final BeaconStateAccessorsEip7732 beaconStateAccessors; + protected final BeaconStateMutatorsElectra beaconStateMutators; + protected final BlockProcessorElectra blockProcessorElectra; + + public ExecutionPayloadProcessorEip7732( + final SpecConfigEip7732 specConfig, + final MiscHelpersEip7732 miscHelpers, + final BeaconStateAccessorsEip7732 beaconStateAccessors, + final BeaconStateMutatorsElectra beaconStateMutators, + final BlockProcessorElectra blockProcessorElectra) { + this.specConfig = specConfig; + this.miscHelpers = miscHelpers; + this.beaconStateAccessors = beaconStateAccessors; + this.beaconStateMutators = beaconStateMutators; + this.blockProcessorElectra = blockProcessorElectra; + } + + @Override + public boolean verifyExecutionPayloadEnvelopeSignature( + final BeaconState state, final SignedExecutionPayloadEnvelope signedEnvelope) { + final Validator builder = + state.getValidators().get(signedEnvelope.getMessage().getBuilderIndex().intValue()); + final Bytes signingRoot = + miscHelpers.computeSigningRoot( + signedEnvelope.getMessage(), + beaconStateAccessors.getDomain( + state.getForkInfo(), + Domain.BEACON_BUILDER, + miscHelpers.computeEpochAtSlot(state.getSlot()))); + return BLS.verify(builder.getPublicKey(), signingRoot, signedEnvelope.getSignature()); + } + + @Override + public void processExecutionPayload( + final MutableBeaconState state, + final ExecutionPayloadEnvelope envelope, + final Optional payloadExecutor) + throws ExecutionPayloadProcessingException { + final ExecutionPayloadEip7732 payload = ExecutionPayloadEip7732.required(envelope.getPayload()); + final Bytes32 previousStateRoot = state.hashTreeRoot(); + + final BeaconBlockHeader latestBlockHeader = state.getLatestBlockHeader(); + if (latestBlockHeader.getStateRoot().isZero()) { + state.setLatestBlockHeader( + new BeaconBlockHeader( + latestBlockHeader.getSlot(), + latestBlockHeader.getProposerIndex(), + latestBlockHeader.getParentRoot(), + // Cache latest block header state root + previousStateRoot, + latestBlockHeader.getBodyRoot())); + } + + // Verify consistency with the beacon block + if (!envelope.getBeaconBlockRoot().equals(state.getLatestBlockHeader().hashTreeRoot())) { + throw new ExecutionPayloadProcessingException( + "Execution payload is not consistent with the beacon block"); + } + + // Verify consistency with the committed header + final ExecutionPayloadHeaderEip7732 committedHeader = + ExecutionPayloadHeaderEip7732.required( + BeaconStateEip7732.required(state).getLatestExecutionPayloadHeader()); + + if (!envelope.getBuilderIndex().equals(committedHeader.getBuilderIndex()) + || !committedHeader + .getBlobKzgCommitmentsRoot() + .equals(envelope.getBlobKzgCommitments().hashTreeRoot())) { + throw new ExecutionPayloadProcessingException( + "Execution payload is not consistent with the committed header"); + } + + if (envelope.isPayloadWithheld()) { + return; + } + + // Verify the withdrawals root + if (!payload + .getWithdrawals() + .hashTreeRoot() + .equals(BeaconStateEip7732.required(state).getLatestWithdrawalsRoot())) { + throw new ExecutionPayloadProcessingException( + "Execution payload withdrawals root is not consistent with the state latest withdrawals root"); + } + + // Verify the gas limit + if (!committedHeader.getGasLimit().equals(payload.getGasLimit()) + || !committedHeader.getBlockHash().equals(payload.getBlockHash())) { + throw new ExecutionPayloadProcessingException( + "Execution payload gas limit is not consistent with the gas limit of the committed header"); + } + + // Verify consistency of the parent hash with respect to the previous execution payload + if (!payload.getParentHash().equals(BeaconStateEip7732.required(state).getLatestBlockHash())) { + throw new ExecutionPayloadProcessingException( + "Execution payload parent hash is not consistent with the latest block hash from state"); + } + + // Verify prev_randao + if (!payload + .getPrevRandao() + .equals( + beaconStateAccessors.getRandaoMix( + state, miscHelpers.computeEpochAtSlot(state.getSlot())))) { + throw new ExecutionPayloadProcessingException( + "Execution payload prev randao is not as expected"); + } + + // Verify timestamp + if (!payload + .getTimestamp() + .equals(miscHelpers.computeTimeAtSlot(state.getGenesisTime(), state.getSlot()))) { + throw new ExecutionPayloadProcessingException( + "Execution payload timestamp is not as expected"); + } + + // Verify commitments are under limit + if (envelope.getBlobKzgCommitments().size() > specConfig.getMaxBlobCommitmentsPerBlock()) { + throw new ExecutionPayloadProcessingException( + "Execution payload blob kzg commitments are over the limit"); + } + + // Verify the execution payload is valid + if (payloadExecutor.isPresent()) { + final NewPayloadRequest payloadToExecute = computeNewPayloadRequest(state, envelope); + final boolean optimisticallyAccept = + payloadExecutor.get().optimisticallyExecute(committedHeader, payloadToExecute); + if (!optimisticallyAccept) { + throw new ExecutionPayloadProcessingException( + "Execution payload was not optimistically accepted"); + } + } + + processOperationsNoValidation(state, payload); + + // Cache the execution payload header and proposer + MutableBeaconStateEip7732.required(state).setLatestBlockHash(payload.getBlockHash()); + MutableBeaconStateEip7732.required(state).setLatestFullSlot(state.getSlot()); + } + + @Override + public NewPayloadRequest computeNewPayloadRequest( + final BeaconState state, final ExecutionPayloadEnvelope envelope) { + final SszList blobKzgCommitments = envelope.getBlobKzgCommitments(); + final List versionedHashes = + blobKzgCommitments.stream() + .map(SszKZGCommitment::getKZGCommitment) + .map(miscHelpers::kzgCommitmentToVersionedHash) + .toList(); + final Bytes32 parentBeaconBlockRoot = state.getLatestBlockHeader().getParentRoot(); + return new NewPayloadRequest(envelope.getPayload(), versionedHashes, parentBeaconBlockRoot); + } + + protected void processOperationsNoValidation( + final MutableBeaconState state, final ExecutionPayload executionPayload) + throws ExecutionPayloadProcessingException { + safelyProcess( + () -> { + final Supplier validatorExitContextSupplier = + beaconStateMutators.createValidatorExitContextSupplier(state); + + final ExecutionPayloadEip7732 executionPayloadEip7732 = + ExecutionPayloadEip7732.required(executionPayload); + // EIP7732 TODO: dirty way to leverage Electra operations + blockProcessorElectra.processDepositRequests( + state, executionPayloadEip7732.getDepositRequests()); + blockProcessorElectra.processWithdrawalRequests( + state, executionPayloadEip7732.getWithdrawalRequests(), validatorExitContextSupplier); + blockProcessorElectra.processConsolidationRequests( + state, executionPayloadEip7732.getConsolidationRequests()); + }); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/forktransition/Eip7732StateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/forktransition/Eip7732StateUpgrade.java new file mode 100644 index 00000000000..c451841fa5c --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/forktransition/Eip7732StateUpgrade.java @@ -0,0 +1,153 @@ +/* + * 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.logic.versions.eip7732.forktransition; + +import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; + +import java.util.Comparator; +import java.util.stream.IntStream; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.SszMutableList; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.state.Fork; +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.common.BeaconStateFields; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.logic.common.forktransition.StateUpgrade; +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.logic.versions.electra.helpers.PredicatesElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; + +public class Eip7732StateUpgrade implements StateUpgrade { + + private final SpecConfigEip7732 specConfig; + private final SchemaDefinitionsEip7732 schemaDefinitions; + private final BeaconStateAccessorsElectra beaconStateAccessors; + private final BeaconStateMutatorsElectra beaconStateMutators; + + public Eip7732StateUpgrade( + final SpecConfigEip7732 specConfig, + final SchemaDefinitionsEip7732 schemaDefinitions, + final BeaconStateAccessorsElectra beaconStateAccessors, + final BeaconStateMutatorsElectra beaconStateMutators) { + this.specConfig = specConfig; + this.schemaDefinitions = schemaDefinitions; + this.beaconStateAccessors = beaconStateAccessors; + this.beaconStateMutators = beaconStateMutators; + } + + @Override + public BeaconStateEip7732 upgrade(final BeaconState preState) { + final UInt64 epoch = beaconStateAccessors.getCurrentEpoch(preState); + final BeaconStateElectra preStateElectra = BeaconStateElectra.required(preState); + final PredicatesElectra predicatesElectra = new PredicatesElectra(specConfig); + final MiscHelpersElectra miscHelpersElectra = + new MiscHelpersElectra(specConfig, predicatesElectra, schemaDefinitions); + return schemaDefinitions + .getBeaconStateSchema() + .createEmpty() + .updatedEip7732( + state -> { + BeaconStateFields.copyCommonFieldsFromSource(state, preState); + + state.setCurrentEpochParticipation(preStateElectra.getCurrentEpochParticipation()); + state.setPreviousEpochParticipation(preStateElectra.getPreviousEpochParticipation()); + state.setCurrentSyncCommittee(preStateElectra.getCurrentSyncCommittee()); + state.setNextSyncCommittee(preStateElectra.getNextSyncCommittee()); + state.setInactivityScores(preStateElectra.getInactivityScores()); + + state.setFork( + new Fork( + preState.getFork().getCurrentVersion(), + specConfig.getEip7732ForkVersion(), + epoch)); + + final ExecutionPayloadHeader upgradedExecutionPayloadHeader = + schemaDefinitions + .getExecutionPayloadHeaderSchema() + .createExecutionPayloadHeader( + builder -> + builder + .parentBlockHash(() -> Bytes32.ZERO) + .parentBlockRoot(() -> Bytes32.ZERO) + .blockHash(Bytes32.ZERO) + .gasLimit(UInt64.ZERO) + .builderIndex(() -> UInt64.ZERO) + .slot(() -> UInt64.ZERO) + .value(() -> UInt64.ZERO) + .blobKzgCommitmentsRoot(() -> Bytes32.ZERO)); + + state.setLatestExecutionPayloadHeader(upgradedExecutionPayloadHeader); + + state.setNextWithdrawalValidatorIndex( + preStateElectra.getNextWithdrawalValidatorIndex()); + state.setNextWithdrawalIndex(preStateElectra.getNextWithdrawalIndex()); + state.setHistoricalSummaries(preStateElectra.getHistoricalSummaries()); + state.setDepositRequestsStartIndex( + SpecConfigElectra.UNSET_DEPOSIT_REQUESTS_START_INDEX); + state.setDepositBalanceToConsume(UInt64.ZERO); + state.setExitBalanceToConsume( + beaconStateAccessors.getActivationExitChurnLimit(state)); + state.setEarliestExitEpoch(findEarliestExitEpoch(state, epoch)); + state.setConsolidationBalanceToConsume( + beaconStateAccessors.getConsolidationChurnLimit(state)); + state.setEarliestConsolidationEpoch( + miscHelpersElectra.computeActivationExitEpoch(epoch)); + + final SszMutableList validators = state.getValidators(); + + // Add validators that are not yet active to pending balance deposits + IntStream.range(0, validators.size()) + .filter( + index -> validators.get(index).getActivationEpoch().equals(FAR_FUTURE_EPOCH)) + .boxed() + .sorted( + Comparator.comparing( + index -> validators.get(index).getActivationEligibilityEpoch())) + .forEach( + index -> + beaconStateMutators.queueEntireBalanceAndResetValidator(state, index)); + + // Ensure early adopters of compounding credentials go through the activation churn + IntStream.range(0, validators.size()) + .forEach( + index -> { + if (predicatesElectra.hasCompoundingWithdrawalCredential( + validators.get(index))) { + beaconStateMutators.queueExcessActiveBalance(state, index); + } + }); + + state.setLatestBlockHash(Bytes32.ZERO); + state.setLatestFullSlot(UInt64.ZERO); + state.setLatestWithdrawalsRoot(Bytes32.ZERO); + }); + } + + private UInt64 findEarliestExitEpoch(final BeaconState state, final UInt64 currentEpoch) { + return state.getValidators().stream() + .map(Validator::getExitEpoch) + .filter(exitEpoch -> !exitEpoch.equals(FAR_FUTURE_EPOCH)) + .max(UInt64::compareTo) + .orElse(currentEpoch) + .increment(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/BeaconStateAccessorsEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/BeaconStateAccessorsEip7732.java new file mode 100644 index 00000000000..4ac25944049 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/BeaconStateAccessorsEip7732.java @@ -0,0 +1,119 @@ +/* + * 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.logic.versions.eip7732.helpers; + +import static com.google.common.base.Preconditions.checkArgument; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.operations.IndexedPayloadAttestation; +import tech.pegasys.teku.spec.datastructures.operations.IndexedPayloadAttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; +import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.BeaconStateAccessorsElectra; +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.SchemaDefinitionsEip7732; + +public class BeaconStateAccessorsEip7732 extends BeaconStateAccessorsElectra { + + private final SpecConfigEip7732 configEip7732; + private final SchemaDefinitionsEip7732 schemaDefinitionsEip7732; + + public BeaconStateAccessorsEip7732( + final SpecConfig config, + final PredicatesElectra predicatesElectra, + final MiscHelpersElectra miscHelpers, + final SchemaDefinitionsEip7732 schemaDefinitionsEip7732) { + super(SpecConfigElectra.required(config), predicatesElectra, miscHelpers); + this.configEip7732 = config.toVersionEip7732().orElseThrow(); + this.schemaDefinitionsEip7732 = schemaDefinitionsEip7732; + } + + public static BeaconStateAccessorsEip7732 required( + final BeaconStateAccessors beaconStateAccessors) { + checkArgument( + beaconStateAccessors instanceof BeaconStateAccessorsEip7732, + "Expected %s but it was %s", + BeaconStateAccessorsEip7732.class, + beaconStateAccessors.getClass()); + return (BeaconStateAccessorsEip7732) beaconStateAccessors; + } + + /** Get the payload timeliness committee for the given ``slot`` */ + public IntList getPtc(final BeaconState state, final UInt64 slot) { + final UInt64 epoch = miscHelpers.computeEpochAtSlot(slot); + + final UInt64 committeesPerSlot = + MathHelpers.bitFloor( + getCommitteeCountPerSlot(state, epoch).min(configEip7732.getPtcSize())); + + final long membersPerCommittee = + Math.floorDiv(configEip7732.getPtcSize(), committeesPerSlot.longValue()); + + final IntList validatorIndices = new IntArrayList(configEip7732.getPtcSize()); + + UInt64.range(UInt64.ZERO, committeesPerSlot) + .forEach( + idx -> { + final IntList beaconCommittee = getBeaconCommittee(state, slot, idx); + for (int i = 0; i < Math.min(membersPerCommittee, beaconCommittee.size()); i++) { + validatorIndices.addLast(beaconCommittee.getInt(i)); + } + }); + + return validatorIndices; + } + + /** Return the set of attesting indices corresponding to ``payload_attestation``. */ + public IntList getPayloadAttestingIndices( + final BeaconState state, final UInt64 slot, final PayloadAttestation payloadAttestation) { + final IntList ptc = getPtc(state, slot); + final SszBitvector aggregationBits = payloadAttestation.getAggregationBits(); + final IntList attestingIndices = new IntArrayList(); + for (int i = 0; i < ptc.size(); i++) { + if (aggregationBits.isSet(i)) { + final int index = ptc.getInt(i); + attestingIndices.addLast(index); + } + } + return attestingIndices; + } + + /** Return the indexed payload attestation corresponding to ``payload_attestation``. */ + public IndexedPayloadAttestation getIndexedPayloadAttestation( + final BeaconState state, final UInt64 slot, final PayloadAttestation payloadAttestation) { + final IntList payloadAttestingIndices = + getPayloadAttestingIndices(state, slot, payloadAttestation); + final IndexedPayloadAttestationSchema indexedPayloadAttestationSchema = + schemaDefinitionsEip7732.getIndexedPayloadAttestationSchema(); + final SszUInt64List attestingIndices = + payloadAttestingIndices + .intStream() + .sorted() + .mapToObj(idx -> SszUInt64.of(UInt64.valueOf(idx))) + .collect(indexedPayloadAttestationSchema.getAttestingIndicesSchema().collector()); + return indexedPayloadAttestationSchema.create( + attestingIndices, payloadAttestation.getData(), payloadAttestation.getSignature()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/MiscHelpersEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/MiscHelpersEip7732.java new file mode 100644 index 00000000000..85c11793c28 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/MiscHelpersEip7732.java @@ -0,0 +1,117 @@ +/* + * 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.logic.versions.eip7732.helpers; + +import java.util.List; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.tree.GIndexUtil; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodySchemaEip7732; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class MiscHelpersEip7732 extends MiscHelpersElectra { + private final SchemaDefinitionsEip7732 schemaDefinitions; + + public MiscHelpersEip7732( + final SpecConfigEip7732 specConfig, + final PredicatesEip7732 predicates, + final SchemaDefinitionsEip7732 schemaDefinitions) { + super( + SpecConfigElectra.required(specConfig), + predicates, + SchemaDefinitionsElectra.required(schemaDefinitions)); + this.schemaDefinitions = schemaDefinitions; + } + + public static MiscHelpersEip7732 required(final MiscHelpers miscHelpers) { + return miscHelpers + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 misc helpers but got: " + + miscHelpers.getClass().getSimpleName())); + } + + public byte removeFlag(final byte participationFlags, final int flagIndex) { + final byte flag = (byte) (1 << flagIndex); + return (byte) (participationFlags & ~flag); + } + + // add the blob kzg commitments root for an empty list + @Override + public boolean isMergeTransitionComplete(final BeaconState genericState) { + final ExecutionPayloadHeader header = + schemaDefinitions + .getExecutionPayloadHeaderSchema() + .createExecutionPayloadHeader( + builder -> + builder + .parentBlockHash(() -> Bytes32.ZERO) + .parentBlockRoot(() -> Bytes32.ZERO) + .blockHash(Bytes32.ZERO) + .gasLimit(UInt64.ZERO) + .builderIndex(() -> UInt64.ZERO) + .slot(() -> UInt64.ZERO) + .value(() -> UInt64.ZERO) + .blobKzgCommitmentsRoot( + () -> + schemaDefinitions + .getBlobKzgCommitmentsSchema() + .createFromElements(List.of()) + .hashTreeRoot())); + return !BeaconStateEip7732.required(genericState) + .getLatestExecutionPayloadHeader() + .equals(header); + } + + @Override + public int getBlobSidecarKzgCommitmentGeneralizedIndex(final UInt64 blobSidecarIndex) { + final long blobKzgCommitmentsRootGeneralizedIndex = + BeaconBlockBodySchemaEip7732.required(beaconBlockBodySchema) + .getBlobKzgCommitmentsRootGeneralizedIndex(); + final long commitmentGeneralizedIndex = + schemaDefinitions + .getExecutionPayloadEnvelopeSchema() + .getBlobKzgCommitmentsSchema() + .getChildGeneralizedIndex(blobSidecarIndex.longValue()); + return (int) + GIndexUtil.gIdxCompose(blobKzgCommitmentsRootGeneralizedIndex, commitmentGeneralizedIndex); + } + + @Override + public boolean verifyBlobSidecarMerkleProof(final BlobSidecar blobSidecar) { + return predicates.isValidMerkleBranch( + blobSidecar.getSszKZGCommitment().hashTreeRoot(), + blobSidecar.getKzgCommitmentInclusionProof(), + SpecConfigEip7732.required(specConfig).getKzgCommitmentInclusionProofDepthEip7732(), + getBlobSidecarKzgCommitmentGeneralizedIndex(blobSidecar.getIndex()), + blobSidecar.getBlockBodyRoot()); + } + + @Override + public Optional toVersionEip7732() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/PredicatesEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/PredicatesEip7732.java new file mode 100644 index 00000000000..37473c766b7 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/helpers/PredicatesEip7732.java @@ -0,0 +1,50 @@ +/* + * 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.logic.versions.eip7732.helpers; + +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateBellatrix; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.spec.logic.common.helpers.Predicates; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.PredicatesElectra; + +public class PredicatesEip7732 extends PredicatesElectra { + + public PredicatesEip7732(final SpecConfig specConfig) { + super(specConfig); + } + + public static PredicatesEip7732 required(final Predicates predicates) { + return predicates + .toVersionEip7732() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Eip7732 predicates but got " + + predicates.getClass().getSimpleName())); + } + + /** + * This function returns true if the last committed payload header was fulfilled with a payload, + * this can only happen when both beacon block and payload were present. This function must be + * called on a beacon state before processing the execution payload header in the block. + */ + public boolean isParentBlockFull(final BeaconState state) { + return BeaconStateBellatrix.required(state) + .getLatestExecutionPayloadHeader() + .getBlockHash() + .equals(BeaconStateEip7732.required(state).getLatestBlockHash()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/util/AttestationUtilEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/util/AttestationUtilEip7732.java new file mode 100644 index 00000000000..9c878c4acea --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7732/util/AttestationUtilEip7732.java @@ -0,0 +1,121 @@ +/* + * 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.logic.versions.eip7732.util; + +import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; +import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; + +import it.unimi.dsi.fastutil.ints.IntList; +import java.util.List; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.constants.Domain; +import tech.pegasys.teku.spec.constants.PayloadStatus; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.IndexedPayloadAttestation; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; +import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; +import tech.pegasys.teku.spec.logic.versions.eip7732.helpers.BeaconStateAccessorsEip7732; +import tech.pegasys.teku.spec.logic.versions.electra.util.AttestationUtilElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitions; + +public class AttestationUtilEip7732 extends AttestationUtilElectra { + public AttestationUtilEip7732( + final SpecConfig specConfig, + final SchemaDefinitions schemaDefinitions, + final BeaconStateAccessors beaconStateAccessors, + final MiscHelpers miscHelpers) { + super(specConfig, schemaDefinitions, beaconStateAccessors, miscHelpers); + } + + @Override + public Optional performSlotInclusionGossipValidation( + final PayloadAttestationData payloadAttestationData, + final UInt64 genesisTime, + final UInt64 currentTimeMillis) { + final UInt64 slot = payloadAttestationData.getSlot(); + final UInt64 minimumAllowedTime = + secondsToMillis(genesisTime.plus(slot.times(specConfig.getSecondsPerSlot()))) + .minusMinZero(specConfig.getMaximumGossipClockDisparity()); + final UInt64 lastAllowedTime = + secondsToMillis(genesisTime.plus(slot.plus(ONE).times(specConfig.getSecondsPerSlot()))) + .plus(specConfig.getMaximumGossipClockDisparity()); + if (currentTimeMillis.isGreaterThan(lastAllowedTime)) { + return Optional.of(SlotInclusionGossipValidationResult.IGNORE); + } + if (currentTimeMillis.isLessThan(minimumAllowedTime)) { + return Optional.of(SlotInclusionGossipValidationResult.SAVE_FOR_FUTURE); + } + return Optional.empty(); + } + + /** get_attesting_indices is modified to ignore PTC votes */ + @Override + public IntList getAttestingIndices(final BeaconState state, final Attestation attestation) { + final IntList attestingIndices = super.getAttestingIndices(state, attestation); + final IntList ptc = + BeaconStateAccessorsEip7732.required(beaconStateAccessors) + .getPtc(state, attestation.getData().getSlot()); + return IntList.of(attestingIndices.intStream().filter(i -> !ptc.contains(i)).toArray()); + } + + /** + * Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has a + * valid aggregate signature. + */ + public boolean isValidIndexedPayloadAttestation( + final BeaconState state, final IndexedPayloadAttestation indexedPayloadAttestation) { + // Verify the data is valid + if (indexedPayloadAttestation.getData().getPayloadStatus() + >= PayloadStatus.PAYLOAD_INVALID_STATUS) { + return false; + } + + final SszUInt64List indices = indexedPayloadAttestation.getAttestingIndices(); + + // Verify indices are sorted and unique + if (indices.isEmpty() + || !indices + .asListUnboxed() + .equals(indices.asListUnboxed().stream().sorted().distinct().toList())) { + return false; + } + + // Verify aggregate signature + final List publicKeys = + indices.stream() + .map(idx -> state.getValidators().get(idx.get().intValue()).getPublicKey()) + .toList(); + + final Bytes32 domain = + beaconStateAccessors.getDomain( + state.getForkInfo(), + Domain.PTC_ATTESTER, + miscHelpers.computeEpochAtSlot(state.getSlot())); + + final Bytes signingRoot = + miscHelpers.computeSigningRoot(indexedPayloadAttestation.getData(), domain); + + return BLS.fastAggregateVerify( + publicKeys, signingRoot, indexedPayloadAttestation.getSignature()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java index 3dce3a95d9a..57f03a7c84b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; import tech.pegasys.teku.spec.logic.common.operations.validation.AttestationDataValidator; @@ -215,4 +216,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return Optional.empty(); } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } 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 63677a1bc05..242b3d89e56 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 @@ -34,10 +34,8 @@ import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.cache.IndexedAttestationCache; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary; import tech.pegasys.teku.spec.datastructures.execution.ExpectedWithdrawals; @@ -113,33 +111,6 @@ public BlockProcessorElectra( this.schemaDefinitionsElectra = schemaDefinitions; } - @Override - protected void processOperationsNoValidation( - final MutableBeaconState state, - final BeaconBlockBody body, - final IndexedAttestationCache indexedAttestationCache) - throws BlockProcessingException { - super.processOperationsNoValidation(state, body, indexedAttestationCache); - - safelyProcess( - () -> { - processDepositRequests( - state, - body.getOptionalExecutionPayload() - .flatMap(ExecutionPayload::toVersionElectra) - .map(ExecutionPayloadElectra::getDepositRequests) - .orElseThrow( - () -> - new BlockProcessingException( - "Deposit requests were not found during block processing."))); - this.processConsolidationRequests( - state, - BeaconBlockBodyElectra.required(body) - .getExecutionPayload() - .getConsolidationRequests()); - }); - } - @Override protected void verifyOutstandingDepositsAreProcessed( final BeaconState state, final BeaconBlockBody body) { @@ -171,7 +142,7 @@ protected void processWithdrawalRequests( final Optional executionPayload, final Supplier validatorExitContextSupplier) throws BlockProcessingException { - this.processWithdrawalRequests( + processWithdrawalRequests( state, getWithdrawalRequestsFromBlock(executionPayload), validatorExitContextSupplier); } @@ -350,13 +321,19 @@ private SszList getWithdrawalRequestsFromBlock( "Withdrawal requests were not found during block processing.")); } + @Override + protected void processDepositRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + processDepositRequests(state, getDepositRequestsFromBlock(executionPayload)); + } + /* Implements process_deposit_request from consensus-specs (EIP-6110) */ @Override public void processDepositRequests( - final MutableBeaconState state, final SszList depositRequests) - throws BlockProcessingException { + final MutableBeaconState state, final SszList depositRequests) { final MutableBeaconStateElectra electraState = MutableBeaconStateElectra.required(state); for (DepositRequest depositRequest : depositRequests) { // process_deposit_request @@ -376,6 +353,24 @@ public void processDepositRequests( } } + private SszList getDepositRequestsFromBlock( + final Optional maybeExecutionPayload) throws BlockProcessingException { + return maybeExecutionPayload + .flatMap(ExecutionPayload::toVersionElectra) + .map(ExecutionPayloadElectra::getDepositRequests) + .orElseThrow( + () -> + new BlockProcessingException( + "Deposit requests were not found during block processing.")); + } + + @Override + protected void processConsolidationRequests( + final MutableBeaconState state, final Optional executionPayload) + throws BlockProcessingException { + processConsolidationRequests(state, getConsolidationRequestsFromBlock(executionPayload)); + } + /** * Implements process_consolidation_request from consensus-spec (EIP-7251) * @@ -514,6 +509,17 @@ private void processConsolidationRequest( LOG.debug("process_consolidation_request: created {}", pendingConsolidation); } + private SszList getConsolidationRequestsFromBlock( + final Optional maybeExecutionPayload) throws BlockProcessingException { + return maybeExecutionPayload + .flatMap(ExecutionPayload::toVersionElectra) + .map(ExecutionPayloadElectra::getConsolidationRequests) + .orElseThrow( + () -> + new BlockProcessingException( + "Consolidation requests were not found during block processing.")); + } + @Override protected void applyDepositToValidatorIndex( final MutableBeaconState state, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/SpecLogicPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/SpecLogicPhase0.java index 09525caeb62..a919b009926 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/SpecLogicPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/SpecLogicPhase0.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; +import tech.pegasys.teku.spec.logic.common.execution.ExecutionPayloadProcessor; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; @@ -176,4 +177,9 @@ public Optional getLightClientUtil() { public Optional getBellatrixTransitionHelpers() { return Optional.empty(); } + + @Override + public Optional getExecutionPayloadProcessor() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java index 2c63b63a456..70c011b2242 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java @@ -111,6 +111,11 @@ default Optional toVersionElectra() { return Optional.empty(); } + @NonSchema + default Optional toVersionEip7732() { + return Optional.empty(); + } + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface NonSchema {} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7732.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7732.java new file mode 100644 index 00000000000..1bfa1c10d66 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7732.java @@ -0,0 +1,346 @@ +/* + * 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.schemas; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Optional; +import tech.pegasys.teku.spec.config.SpecConfigEip7732; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecarSchema; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSchema; +import tech.pegasys.teku.spec.datastructures.blocks.BlockContainer; +import tech.pegasys.teku.spec.datastructures.blocks.BlockContainerSchema; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockSchema; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainerSchema; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodySchemaEip7732Impl; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyBuilderElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodySchemaElectraImpl; +import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlockContentsSchema; +import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.SignedBlockContentsSchema; +import tech.pegasys.teku.spec.datastructures.builder.BlobsBundleSchema; +import tech.pegasys.teku.spec.datastructures.builder.BuilderBidSchema; +import tech.pegasys.teku.spec.datastructures.builder.BuilderPayloadSchema; +import tech.pegasys.teku.spec.datastructures.builder.ExecutionPayloadAndBlobsBundleSchema; +import tech.pegasys.teku.spec.datastructures.builder.SignedBuilderBidSchema; +import tech.pegasys.teku.spec.datastructures.builder.versions.deneb.BuilderBidSchemaDeneb; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelopeSchema; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelopeSchema; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeaderSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderSchemaEip7732; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadSchemaEip7732; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage.ExecutionPayloadEnvelopesByRootRequestMessageSchema; +import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof.AggregateAndProofSchema; +import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.IndexedPayloadAttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessageSchema; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; +import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.BeaconStateSchemaEip7732; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7732.MutableBeaconStateEip7732; + +public class SchemaDefinitionsEip7732 extends SchemaDefinitionsElectra { + private final AttestationSchema attestationSchema; + private final SignedAggregateAndProofSchema signedAggregateAndProofSchema; + private final AggregateAndProofSchema aggregateAndProofSchema; + + private final BeaconStateSchemaEip7732 beaconStateSchema; + + private final ExecutionPayloadSchemaEip7732 executionPayloadSchemaEip7732; + private final ExecutionPayloadHeaderSchemaEip7732 executionPayloadHeaderSchemaEip7732; + + private final BeaconBlockBodySchemaEip7732Impl beaconBlockBodySchema; + private final BlindedBeaconBlockBodySchemaElectraImpl blindedBeaconBlockBodySchema; + + private final BeaconBlockSchema beaconBlockSchema; + private final BeaconBlockSchema blindedBeaconBlockSchema; + private final SignedBeaconBlockSchema signedBeaconBlockSchema; + private final SignedBeaconBlockSchema signedBlindedBeaconBlockSchema; + + private final BuilderBidSchema builderBidSchemaElectra; + private final SignedBuilderBidSchema signedBuilderBidSchemaElectra; + + private final BlobSidecarSchema blobSidecarSchema; + private final BlockContentsSchema blockContentsSchema; + private final SignedBlockContentsSchema signedBlockContentsSchema; + private final BlobsBundleSchema blobsBundleSchema; + private final ExecutionPayloadAndBlobsBundleSchema executionPayloadAndBlobsBundleSchema; + + private final PayloadAttestationSchema payloadAttestationSchema; + private final IndexedPayloadAttestationSchema indexedPayloadAttestationSchema; + private final SignedExecutionPayloadHeaderSchema signedExecutionPayloadHeaderSchema; + private final ExecutionPayloadEnvelopeSchema executionPayloadEnvelopeSchema; + private final SignedExecutionPayloadEnvelopeSchema signedExecutionPayloadEnvelopeSchema; + private final ExecutionPayloadEnvelopesByRootRequestMessageSchema + executionPayloadEnvelopesByRootRequestMessageSchema; + private final PayloadAttestationMessageSchema payloadAttestationMessageSchema; + + public SchemaDefinitionsEip7732(final SpecConfigEip7732 specConfig) { + super(specConfig); + + final long maxValidatorsPerAttestation = getMaxValidatorPerAttestation(specConfig); + + this.attestationSchema = + new AttestationElectraSchema( + maxValidatorsPerAttestation, specConfig.getMaxCommitteesPerSlot()); + this.aggregateAndProofSchema = new AggregateAndProofSchema(attestationSchema); + this.signedAggregateAndProofSchema = new SignedAggregateAndProofSchema(aggregateAndProofSchema); + + this.executionPayloadSchemaEip7732 = new ExecutionPayloadSchemaEip7732(specConfig); + + this.beaconStateSchema = BeaconStateSchemaEip7732.create(specConfig); + this.executionPayloadHeaderSchemaEip7732 = + beaconStateSchema.getLastExecutionPayloadHeaderSchema(); + this.payloadAttestationSchema = new PayloadAttestationSchema(specConfig.getPtcSize()); + this.payloadAttestationMessageSchema = new PayloadAttestationMessageSchema(); + this.beaconBlockBodySchema = + BeaconBlockBodySchemaEip7732Impl.create( + specConfig, + getAttesterSlashingSchema(), + getSignedBlsToExecutionChangeSchema(), + maxValidatorsPerAttestation, + executionPayloadHeaderSchemaEip7732, + payloadAttestationSchema, + "BeaconBlockBodyEip7732"); + // EIP7732 TODO: this schema needs changing + this.blindedBeaconBlockBodySchema = + BlindedBeaconBlockBodySchemaElectraImpl.create( + specConfig, + getAttesterSlashingSchema(), + getSignedBlsToExecutionChangeSchema(), + getBlobKzgCommitmentsSchema(), + maxValidatorsPerAttestation, + "BlindedBlockBodyEip7732"); + this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockEip7732"); + this.blindedBeaconBlockSchema = + new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockEip7732"); + this.signedBeaconBlockSchema = + new SignedBeaconBlockSchema(beaconBlockSchema, "SignedBeaconBlockEip7732"); + this.signedBlindedBeaconBlockSchema = + new SignedBeaconBlockSchema(blindedBeaconBlockSchema, "SignedBlindedBlockEip7732"); + this.builderBidSchemaElectra = + new BuilderBidSchemaDeneb( + "BuilderBidEip7732", + executionPayloadHeaderSchemaEip7732, + getBlobKzgCommitmentsSchema()); + this.signedBuilderBidSchemaElectra = + new SignedBuilderBidSchema("SignedBuilderBidEip7732", builderBidSchemaElectra); + + this.blobSidecarSchema = + BlobSidecarSchema.create( + SignedBeaconBlockHeader.SSZ_SCHEMA, + getBlobSchema(), + specConfig.getKzgCommitmentInclusionProofDepthEip7732()); + this.blockContentsSchema = + BlockContentsSchema.create( + specConfig, beaconBlockSchema, getBlobSchema(), "BlockContentsEip7732"); + this.signedBlockContentsSchema = + SignedBlockContentsSchema.create( + specConfig, signedBeaconBlockSchema, getBlobSchema(), "SignedBlockContentsEip7732"); + this.blobsBundleSchema = + new BlobsBundleSchema( + "BlobsBundleEip7732", getBlobSchema(), getBlobKzgCommitmentsSchema(), specConfig); + this.executionPayloadAndBlobsBundleSchema = + new ExecutionPayloadAndBlobsBundleSchema(executionPayloadSchemaEip7732, blobsBundleSchema); + + this.indexedPayloadAttestationSchema = + new IndexedPayloadAttestationSchema(specConfig.getPtcSize()); + this.signedExecutionPayloadHeaderSchema = + new SignedExecutionPayloadHeaderSchema(executionPayloadHeaderSchemaEip7732); + this.executionPayloadEnvelopeSchema = + new ExecutionPayloadEnvelopeSchema( + executionPayloadSchemaEip7732, getBlobKzgCommitmentsSchema()); + this.signedExecutionPayloadEnvelopeSchema = + new SignedExecutionPayloadEnvelopeSchema(executionPayloadEnvelopeSchema); + this.executionPayloadEnvelopesByRootRequestMessageSchema = + new ExecutionPayloadEnvelopesByRootRequestMessageSchema(specConfig); + } + + public static SchemaDefinitionsEip7732 required(final SchemaDefinitions schemaDefinitions) { + checkArgument( + schemaDefinitions instanceof SchemaDefinitionsEip7732, + "Expected definitions of type %s but got %s", + SchemaDefinitionsEip7732.class, + schemaDefinitions.getClass()); + return (SchemaDefinitionsEip7732) schemaDefinitions; + } + + @Override + public SignedAggregateAndProofSchema getSignedAggregateAndProofSchema() { + return signedAggregateAndProofSchema; + } + + @Override + public BlobSidecarSchema getBlobSidecarSchema() { + return blobSidecarSchema; + } + + @Override + public AggregateAndProofSchema getAggregateAndProofSchema() { + return aggregateAndProofSchema; + } + + @Override + public AttestationSchema getAttestationSchema() { + return attestationSchema; + } + + @Override + public BeaconStateSchema + getBeaconStateSchema() { + return beaconStateSchema; + } + + @Override + public BeaconBlockBodySchema getBeaconBlockBodySchema() { + return beaconBlockBodySchema; + } + + @Override + public BeaconBlockBodySchema getBlindedBeaconBlockBodySchema() { + return blindedBeaconBlockBodySchema; + } + + @Override + public BeaconBlockSchema getBeaconBlockSchema() { + return beaconBlockSchema; + } + + @Override + public BeaconBlockSchema getBlindedBeaconBlockSchema() { + return blindedBeaconBlockSchema; + } + + @Override + public SignedBeaconBlockSchema getSignedBeaconBlockSchema() { + return signedBeaconBlockSchema; + } + + @Override + public SignedBeaconBlockSchema getSignedBlindedBeaconBlockSchema() { + return signedBlindedBeaconBlockSchema; + } + + @Override + public BlockContainerSchema getBlockContainerSchema() { + return getBlockContentsSchema().castTypeToBlockContainer(); + } + + @Override + public BlockContainerSchema getBlindedBlockContainerSchema() { + return getBlindedBeaconBlockSchema().castTypeToBlockContainer(); + } + + @Override + public SignedBlockContainerSchema getSignedBlockContainerSchema() { + return getSignedBlockContentsSchema().castTypeToSignedBlockContainer(); + } + + @Override + public SignedBlockContainerSchema getSignedBlindedBlockContainerSchema() { + return getSignedBlindedBeaconBlockSchema().castTypeToSignedBlockContainer(); + } + + @Override + public ExecutionPayloadSchema getExecutionPayloadSchema() { + return executionPayloadSchemaEip7732; + } + + @Override + public ExecutionPayloadHeaderSchema getExecutionPayloadHeaderSchema() { + return executionPayloadHeaderSchemaEip7732; + } + + @Override + public BuilderBidSchema getBuilderBidSchema() { + return builderBidSchemaElectra; + } + + @Override + public SignedBuilderBidSchema getSignedBuilderBidSchema() { + return signedBuilderBidSchemaElectra; + } + + @Override + public BuilderPayloadSchema getBuilderPayloadSchema() { + return getExecutionPayloadAndBlobsBundleSchema(); + } + + @Override + public BeaconBlockBodyBuilder createBeaconBlockBodyBuilder() { + return new BeaconBlockBodyBuilderElectra(beaconBlockBodySchema, blindedBeaconBlockBodySchema); + } + + @Override + public BlockContentsSchema getBlockContentsSchema() { + return blockContentsSchema; + } + + @Override + public SignedBlockContentsSchema getSignedBlockContentsSchema() { + return signedBlockContentsSchema; + } + + @Override + public BlobsBundleSchema getBlobsBundleSchema() { + return blobsBundleSchema; + } + + @Override + public ExecutionPayloadAndBlobsBundleSchema getExecutionPayloadAndBlobsBundleSchema() { + return executionPayloadAndBlobsBundleSchema; + } + + public PayloadAttestationSchema getPayloadAttestationSchema() { + return payloadAttestationSchema; + } + + public IndexedPayloadAttestationSchema getIndexedPayloadAttestationSchema() { + return indexedPayloadAttestationSchema; + } + + public SignedExecutionPayloadHeaderSchema getSignedExecutionPayloadHeaderSchema() { + return signedExecutionPayloadHeaderSchema; + } + + public ExecutionPayloadEnvelopeSchema getExecutionPayloadEnvelopeSchema() { + return executionPayloadEnvelopeSchema; + } + + public SignedExecutionPayloadEnvelopeSchema getSignedExecutionPayloadEnvelopeSchema() { + return signedExecutionPayloadEnvelopeSchema; + } + + public ExecutionPayloadEnvelopesByRootRequestMessageSchema + getExecutionPayloadEnvelopesByRootRequestMessageSchema() { + return executionPayloadEnvelopesByRootRequestMessageSchema; + } + + public PayloadAttestationMessageSchema getPayloadAttestationMessageSchema() { + return payloadAttestationMessageSchema; + } + + @Override + public Optional toVersionEip7732() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/DeletableSigner.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/DeletableSigner.java index 30bc8d78f54..4eb4a46823f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/DeletableSigner.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/DeletableSigner.java @@ -25,6 +25,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -69,6 +70,12 @@ public SafeFuture signAttestationData( return sign(() -> delegate.signAttestationData(attestationData, forkInfo)); } + @Override + public SafeFuture signPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + return sign(() -> delegate.signPayloadAttestationData(payloadAttestationData, forkInfo)); + } + @Override public SafeFuture signAggregationSlot(final UInt64 slot, final ForkInfo forkInfo) { return sign(() -> delegate.signAggregationSlot(slot, forkInfo)); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/LocalSigner.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/LocalSigner.java index df5a1beed7f..211d94dee02 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/LocalSigner.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/LocalSigner.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -70,6 +71,13 @@ public SafeFuture signAttestationData( return sign(signingRootUtil.signingRootForSignAttestationData(attestationData, forkInfo)); } + @Override + public SafeFuture signPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + return sign( + signingRootUtil.signingRootForPayloadAttestationData(payloadAttestationData, forkInfo)); + } + @Override public SafeFuture signAggregationSlot(final UInt64 slot, final ForkInfo forkInfo) { return sign(signingRootUtil.signingRootForSignAggregationSlot(slot, forkInfo)); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/Signer.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/Signer.java index 89246a2375f..23fa1e3a5d6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/Signer.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/Signer.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -38,6 +39,9 @@ public interface Signer { SafeFuture signAttestationData(AttestationData attestationData, ForkInfo forkInfo); + SafeFuture signPayloadAttestationData( + PayloadAttestationData payloadAttestationData, ForkInfo forkInfo); + SafeFuture signAggregationSlot(UInt64 slot, ForkInfo forkInfo); SafeFuture signAggregateAndProof( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SigningRootUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SigningRootUtil.java index 4923887d55f..9f51f6f5493 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SigningRootUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SigningRootUtil.java @@ -22,6 +22,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -73,6 +74,18 @@ public Bytes signingRootForSignAttestationData( return specVersion.miscHelpers().computeSigningRoot(attestationData, domain); } + public Bytes signingRootForPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + final SpecVersion specVersion = spec.atSlot(payloadAttestationData.getSlot()); + final Bytes32 domain = + spec.getDomain( + Domain.BEACON_ATTESTER, + spec.computeEpochAtSlot(payloadAttestationData.getSlot()), + forkInfo.getFork(), + forkInfo.getGenesisValidatorsRoot()); + return specVersion.miscHelpers().computeSigningRoot(payloadAttestationData, domain); + } + public Bytes signingRootForSignAggregationSlot(final UInt64 slot, final ForkInfo forkInfo) { final SpecVersion specVersion = spec.atSlot(slot); final Bytes32 domain = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SlashingProtectedSigner.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SlashingProtectedSigner.java index 635dcc9eb58..d4180a7eaa5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SlashingProtectedSigner.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/signatures/SlashingProtectedSigner.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -76,6 +77,12 @@ public SafeFuture signAttestationData( .thenCompose(__ -> delegate.signAttestationData(attestationData, forkInfo)); } + @Override + public SafeFuture signPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + return delegate.signPayloadAttestationData(payloadAttestationData, forkInfo); + } + private Supplier slashableBlockMessage(final BeaconBlock block) { return () -> "Refusing to sign block at slot " diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 01e569037e6..7476f86a96c 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -53,6 +53,9 @@ DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC # Electra ELECTRA_FORK_VERSION: 0x05000000 ELECTRA_FORK_EPOCH: 18446744073709551615 +# EIP7732 +EIP7732_FORK_VERSION: 0x09000000 # temporary stub +EIP7732_FORK_EPOCH: 18446744073709551615 # Time parameters @@ -145,4 +148,7 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in Electra:EIP7251] MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index 73eb9889463..91266eabc3a 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -52,6 +52,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # Electra ELECTRA_FORK_VERSION: 0x05000001 ELECTRA_FORK_EPOCH: 18446744073709551615 +# EIP7732 +EIP7732_FORK_VERSION: 0x09000001 +EIP7732_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -145,4 +148,7 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in Electra:EIP7251] MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) \ No newline at end of file +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7732.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7732.yaml new file mode 100644 index 00000000000..721c0cb7ac0 --- /dev/null +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7732.yaml @@ -0,0 +1,9 @@ +# Mainnet preset - EIP7732 + +# Execution +# --------------------------------------------------------------- +# 2**9 (= 512) +PTC_SIZE: 512 +# 2**2 (= 4) +MAX_PAYLOAD_ATTESTATIONS: 4 +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 13 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7732.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7732.yaml new file mode 100644 index 00000000000..5aaa69caf5d --- /dev/null +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7732.yaml @@ -0,0 +1,9 @@ +# Minimal preset - EIP7732 + +# Execution +# --------------------------------------------------------------- +# 2**1(= 2) +PTC_SIZE: 2 +# 2**2 (= 4) +MAX_PAYLOAD_ATTESTATIONS: 4 +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 13 \ No newline at end of file diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java index 8697dbc4121..f38ec1d0ca6 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java @@ -93,6 +93,7 @@ public void setup(final SpecContext specContext) { afterBeaconStateClass = BeaconStateElectra.class; yield TestSpecFactory.createMinimalWithElectraForkEpoch(milestoneTransitionEpoch); } + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; genesis = createGenesis(spec); diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java index d62091db538..40fd93d8ad9 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java @@ -46,6 +46,7 @@ public static Spec createMinimal(final SpecMilestone specMilestone) { case CAPELLA -> createMinimalCapella(); case DENEB -> createMinimalDeneb(); case ELECTRA -> createMinimalElectra(); + case EIP7732 -> createMinimalEip7732(); }; } @@ -57,6 +58,7 @@ public static Spec createMainnet(final SpecMilestone specMilestone) { case CAPELLA -> createMainnetCapella(); case DENEB -> createMainnetDeneb(); case ELECTRA -> createMainnetElectra(); + case EIP7732 -> createMainnetEip7732(); }; } @@ -113,6 +115,11 @@ public static Spec createMinimalElectra(final Consumer config return create(specConfig, SpecMilestone.ELECTRA); } + public static Spec createMinimalEip7732() { + final SpecConfigElectra specConfig = getElectraSpecConfig(Eth2Network.MINIMAL); + return create(specConfig, SpecMilestone.EIP7732); + } + /** * Create a spec that forks to altair at the provided slot * @@ -207,6 +214,11 @@ public static Spec createMainnetElectra() { return create(specConfig, SpecMilestone.ELECTRA); } + public static Spec createMainnetEip7732() { + final SpecConfigElectra specConfig = getElectraSpecConfig(Eth2Network.MAINNET); + return create(specConfig, SpecMilestone.EIP7732); + } + public static Spec createPhase0(final SpecConfig config) { return create(config, SpecMilestone.PHASE0); } @@ -253,6 +265,14 @@ public static Spec create( .capellaBuilder(c -> c.capellaForkEpoch(UInt64.ZERO)) .denebBuilder(d -> d.denebForkEpoch(UInt64.ZERO)) .electraBuilder(e -> e.electraForkEpoch(UInt64.ZERO)); + case EIP7732 -> builder -> + builder + .altairBuilder(a -> a.altairForkEpoch(UInt64.ZERO)) + .bellatrixBuilder(b -> b.bellatrixForkEpoch(UInt64.ZERO)) + .capellaBuilder(c -> c.capellaForkEpoch(UInt64.ZERO)) + .denebBuilder(d -> d.denebForkEpoch(UInt64.ZERO)) + .electraBuilder(e -> e.electraForkEpoch(UInt64.ZERO)) + .eip7732Builder(e -> e.eip7732ForkEpoch(UInt64.ZERO)); }; return create( SpecConfigLoader.loadConfig(network.configName(), defaultModifier.andThen(configModifier)), diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/signatures/NoOpSigner.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/signatures/NoOpSigner.java index 05f6de611f2..f6655ada198 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/signatures/NoOpSigner.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/signatures/NoOpSigner.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -93,6 +94,12 @@ public SafeFuture signValidatorRegistration( return new SafeFuture<>(); } + @Override + public SafeFuture signPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + return new SafeFuture<>(); + } + @Override public abstract Optional getSigningServiceUrl(); } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 2977f0f3d59..f1a745ab2d7 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -32,7 +32,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; @@ -1646,7 +1645,7 @@ public List randomDeposits(final int num) { } public List randomDepositsWithIndex(final int num) { - return Stream.generate(this::randomDepositWithIndex).limit(num).collect(Collectors.toList()); + return Stream.generate(this::randomDepositWithIndex).limit(num).collect(toList()); } public SszList randomSszDeposits(final int num) { @@ -1858,6 +1857,7 @@ public BeaconState randomBeaconState( case CAPELLA -> stateBuilderCapella(validatorCount, numItemsInSszLists); case DENEB -> stateBuilderDeneb(validatorCount, numItemsInSszLists); case ELECTRA -> stateBuilderElectra(validatorCount, numItemsInSszLists); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/SingleItemOperationPool.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/SingleItemOperationPool.java new file mode 100644 index 00000000000..a36ededb095 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/SingleItemOperationPool.java @@ -0,0 +1,46 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * 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.statetransition; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.ssz.SszData; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; + +public interface SingleItemOperationPool { + + void subscribeOperationAdded(OperationAddedSubscriber subscriber); + + T getItemForBlock(BeaconState stateAtBlockSlot, Predicate filter); + + SafeFuture addLocal(T item); + + SafeFuture addRemote(T item, Optional arrivalTimestamp); + + void remove(T item); + + Set getAll(); + + default Set getLocallySubmitted() { + return Set.of(); + } + + @VisibleForTesting + int size(); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AttestationManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AttestationManager.java index 950311468fc..72fe786de4e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AttestationManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AttestationManager.java @@ -196,6 +196,11 @@ private void applyFutureAttestations(final UInt64 slot) { }); } + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + // No-op + } + @Override public void onBlockValidated(final SignedBeaconBlock block) { // No-op diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationManager.java new file mode 100644 index 00000000000..9d9e63b5ba4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationManager.java @@ -0,0 +1,170 @@ +/* + * 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.statetransition.attestation; + +import java.util.List; +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.ethereum.events.SlotEventsChannel; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; +import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; +import tech.pegasys.teku.statetransition.util.FutureItems; +import tech.pegasys.teku.statetransition.util.PendingPool; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; +import tech.pegasys.teku.statetransition.validation.PayloadAttestationValidator; +import tech.pegasys.teku.statetransition.validation.ValidationResultCode; + +public class PayloadAttestationManager implements SlotEventsChannel, ReceivedBlockEventsChannel { + private static final Logger LOG = LogManager.getLogger(); + + private final PayloadAttestationValidator payloadAttestationValidator; + private final ForkChoice forkChoice; + + private final PayloadAttestationPool payloadAttestationPool; + private final PendingPool pendingAttestations; + private final FutureItems futureAttestations; + + public PayloadAttestationManager( + final PayloadAttestationValidator payloadAttestationValidator, + final ForkChoice forkChoice, + final PayloadAttestationPool payloadAttestationPool, + final PendingPool pendingAttestations, + final FutureItems futureAttestations) { + this.payloadAttestationValidator = payloadAttestationValidator; + this.forkChoice = forkChoice; + this.payloadAttestationPool = payloadAttestationPool; + this.pendingAttestations = pendingAttestations; + this.futureAttestations = futureAttestations; + } + + public SafeFuture addPayloadAttestation( + final PayloadAttestationMessage payloadAttestationMessage, + final Optional arrivalTimestamp) { + arrivalTimestamp.ifPresent( + timestamp -> + LOG.trace( + "Processing payload attestation for slot {} at {}", + payloadAttestationMessage.getData().getSlot(), + timestamp)); + final SafeFuture validationResult = + payloadAttestationValidator.validate(payloadAttestationMessage); + processInternallyPayloadAttestation(validationResult, payloadAttestationMessage); + return validationResult; + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void processInternallyPayloadAttestation( + final SafeFuture validationResult, + final PayloadAttestationMessage payloadAttestationMessage) { + validationResult.thenAccept( + internalValidationResult -> { + if (internalValidationResult.code().equals(ValidationResultCode.ACCEPT) + || internalValidationResult.code().equals(ValidationResultCode.SAVE_FOR_FUTURE)) { + onPayloadAttestationMessage(payloadAttestationMessage) + .finish( + result -> + result.ifInvalid( + reason -> + LOG.debug("Rejected received payload attestation: " + reason)), + err -> LOG.error("Failed to process received payload attestation.", err)); + } + }); + } + + private SafeFuture onPayloadAttestationMessage( + final PayloadAttestationMessage payloadAttestationMessage) { + return forkChoice + .onPayloadAttestationMessage(payloadAttestationMessage) + .thenApply( + result -> { + switch (result.getStatus()) { + case SUCCESSFUL: + LOG.trace( + "Processed payload attestation {} successfully", + payloadAttestationMessage::hashTreeRoot); + payloadAttestationPool.add(payloadAttestationMessage); + break; + case UNKNOWN_BLOCK: + LOG.trace( + "Deferring payload attestation {} as required block is not yet present", + payloadAttestationMessage::hashTreeRoot); + pendingAttestations.add(payloadAttestationMessage); + break; + case SAVED_FOR_FUTURE: + LOG.trace( + "Deferring payload attestation {} until a future slot", + payloadAttestationMessage::hashTreeRoot); + payloadAttestationPool.add(payloadAttestationMessage); + futureAttestations.add(payloadAttestationMessage); + break; + case DEFER_FORK_CHOICE_PROCESSING, INVALID: + break; + default: + throw new UnsupportedOperationException( + "AttestationProcessingResult is unrecognizable"); + } + return result; + }); + } + + @Override + public void onSlot(final UInt64 slot) { + pendingAttestations.onSlot(slot); + applyFutureAttestations(slot); + } + + private void applyFutureAttestations(final UInt64 slot) { + futureAttestations.onSlot(slot); + final List payloadAttestations = futureAttestations.prune(slot); + if (payloadAttestations.isEmpty()) { + return; + } + forkChoice.applyPayloadAttestationMessages(payloadAttestations); + } + + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + // No-op + } + + @Override + public void onBlockValidated(final SignedBeaconBlock block) { + // No-op + } + + @Override + public void onBlockImported(final SignedBeaconBlock block, final boolean executionOptimistic) { + final Bytes32 blockRoot = block.getMessage().hashTreeRoot(); + pendingAttestations + .getItemsDependingOn(blockRoot, false) + .forEach( + payloadAttestationMessage -> { + pendingAttestations.remove(payloadAttestationMessage); + onPayloadAttestationMessage(payloadAttestationMessage) + .finish( + err -> + LOG.error( + "Failed to process pending payload attestation dependent on " + + blockRoot, + err)); + }); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationPool.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationPool.java new file mode 100644 index 00000000000..440fcde4ca4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PayloadAttestationPool.java @@ -0,0 +1,72 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.attestation; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import tech.pegasys.teku.infrastructure.metrics.SettableGauge; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7732.BeaconBlockBodySchemaEip7732; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.schemas.SchemaDefinitions; + +public class PayloadAttestationPool { + + private final Spec spec; + private final SettableGauge sizeGauge; + + private final AtomicInteger size = new AtomicInteger(0); + + public PayloadAttestationPool(final Spec spec, final MetricsSystem metricsSystem) { + this.spec = spec; + this.sizeGauge = + SettableGauge.create( + metricsSystem, + TekuMetricCategory.BEACON, + "payload_attestation_pool_size", + "The number of payload attestations available to be included in proposed blocks"); + } + + // EIP7732 TODO: implement + @SuppressWarnings("unused") + public synchronized void add(final PayloadAttestationMessage payloadAttestationMessage) { + updateSize(1); + } + + private void updateSize(final int delta) { + final int currentSize = size.addAndGet(delta); + sizeGauge.set(currentSize); + } + + // EIP7732 TODO: implement + @SuppressWarnings("unused") + public synchronized SszList getPayloadAttestationsForBlock( + final BeaconState stateAtBlockSlot) { + + final SchemaDefinitions schemaDefinitions = + spec.atSlot(stateAtBlockSlot.getSlot()).getSchemaDefinitions(); + + final SszListSchema attestationsSchema = + BeaconBlockBodySchemaEip7732.required(schemaDefinitions.getBeaconBlockBodySchema()) + .getPayloadAttestationsSchema(); + + return attestationsSchema.createFromElements(List.of()); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockManager.java index aa51fbd841b..0a3edb14a0e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockManager.java @@ -174,6 +174,11 @@ public void subscribeFailedPayloadExecution(final FailedPayloadExecutionSubscrib failedPayloadExecutionSubscribers.subscribe(subscriber); } + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + // No-op + } + @Override public void onBlockValidated(final SignedBeaconBlock block) { // No-op diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/ReceivedBlockEventsChannel.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/ReceivedBlockEventsChannel.java index f72046d84fc..fa45ea2503e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/ReceivedBlockEventsChannel.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/ReceivedBlockEventsChannel.java @@ -19,6 +19,22 @@ /** Used to notify subscribers for events related to blocks received from P2P or API */ public interface ReceivedBlockEventsChannel extends VoidReturningChannelInterface { + ReceivedBlockEventsChannel NOOP = + new ReceivedBlockEventsChannel() { + @Override + public void onBlockSeen(final SignedBeaconBlock block) {} + + @Override + public void onBlockValidated(final SignedBeaconBlock block) {} + + @Override + public void onBlockImported( + final SignedBeaconBlock block, final boolean executionOptimistic) {} + }; + + /** Block has been seen from P2P or API */ + void onBlockSeen(SignedBeaconBlock block); + /** Block passes validation rules of the `beacon_block` topic */ void onBlockValidated(SignedBeaconBlock block); diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadHeaderPool.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadHeaderPool.java new file mode 100644 index 00000000000..5b7f3f9eeaa --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadHeaderPool.java @@ -0,0 +1,124 @@ +/* + * 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.statetransition.execution; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedSet; +import tech.pegasys.teku.infrastructure.subscribers.Subscribers; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.statetransition.OperationAddedSubscriber; +import tech.pegasys.teku.statetransition.SingleItemOperationPool; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; +import tech.pegasys.teku.statetransition.validation.OperationValidator; +import tech.pegasys.teku.statetransition.validation.ValidationResultCode; + +public class ExecutionPayloadHeaderPool + implements SingleItemOperationPool { + + private static final int DEFAULT_SIGNED_BIDS_POOL_SIZE = 1000; + + private final Set signedBids = + LimitedSet.createSynchronizedIterable(DEFAULT_SIGNED_BIDS_POOL_SIZE); + + private final OperationValidator operationValidator; + private final Optional> priorityOrderComparator; + + private final Subscribers> subscribers = + Subscribers.create(true); + + public ExecutionPayloadHeaderPool( + final OperationValidator operationValidator, + final Optional> priorityOrderComparator) { + this.operationValidator = operationValidator; + this.priorityOrderComparator = priorityOrderComparator; + } + + @Override + public void subscribeOperationAdded( + final OperationAddedSubscriber subscriber) { + this.subscribers.subscribe(subscriber); + } + + @Override + public SignedExecutionPayloadHeader getItemForBlock( + final BeaconState stateAtBlockSlot, final Predicate filter) { + final List sortedBids = + priorityOrderComparator + .map(comparator -> signedBids.stream().sorted(comparator)) + .orElseGet(signedBids::stream) + .toList(); + for (SignedExecutionPayloadHeader bid : sortedBids) { + if (!filter.test(bid)) { + continue; + } + if (operationValidator.validateForBlockInclusion(stateAtBlockSlot, bid).isEmpty()) { + return bid; + } else { + // The item is no longer valid to be included in a block so remove it from the pool. + remove(bid); + } + } + throw new IllegalStateException( + "Couldn't find a valid signed bid in the pool for inclusion in a block"); + } + + @Override + public SafeFuture addLocal(final SignedExecutionPayloadHeader item) { + return add(item, false); + } + + @Override + public SafeFuture addRemote( + final SignedExecutionPayloadHeader item, final Optional arrivalTimestamp) { + return add(item, true); + } + + @Override + public void remove(final SignedExecutionPayloadHeader item) { + signedBids.remove(item); + } + + @Override + public Set getAll() { + return Collections.unmodifiableSet(signedBids); + } + + @Override + public int size() { + return signedBids.size(); + } + + private SafeFuture add( + final SignedExecutionPayloadHeader signedBid, final boolean fromNetwork) { + return operationValidator + .validateForGossip(signedBid) + .thenApply( + result -> { + if (result.code().equals(ValidationResultCode.ACCEPT) + || result.code().equals(ValidationResultCode.SAVE_FOR_FUTURE)) { + signedBids.add(signedBid); + subscribers.forEach(s -> s.onOperationAdded(signedBid, result, fromNetwork)); + } + return result; + }); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadManager.java new file mode 100644 index 00000000000..3f3651cf534 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/execution/ExecutionPayloadManager.java @@ -0,0 +1,71 @@ +/* + * 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.statetransition.execution; + +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel; +import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; +import tech.pegasys.teku.statetransition.validation.ExecutionPayloadValidator; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class ExecutionPayloadManager { + private static final Logger LOG = LogManager.getLogger(); + + private final ExecutionPayloadValidator executionPayloadValidator; + private final ForkChoice forkChoice; + private final RecentChainData recentChainData; + private final ExecutionLayerChannel executionLayerChannel; + + public ExecutionPayloadManager( + final ExecutionPayloadValidator executionPayloadValidator, + final ForkChoice forkChoice, + final RecentChainData recentChainData, + final ExecutionLayerChannel executionLayerChannel) { + this.executionPayloadValidator = executionPayloadValidator; + this.forkChoice = forkChoice; + this.recentChainData = recentChainData; + this.executionLayerChannel = executionLayerChannel; + } + + public SafeFuture validateAndImportExecutionPayload( + final SignedExecutionPayloadEnvelope signedExecutionPayloadEnvelope, + final Optional arrivalTimestamp) { + arrivalTimestamp.ifPresent( + timestamp -> LOG.trace("Processing payload attestation at {}", timestamp)); + final SafeFuture validationResult = + executionPayloadValidator.validate(signedExecutionPayloadEnvelope); + validationResult.thenAccept( + result -> { + switch (result.code()) { + case ACCEPT, SAVE_FOR_FUTURE -> { + arrivalTimestamp.ifPresentOrElse( + timestamp -> + recentChainData.onExecutionPayload(signedExecutionPayloadEnvelope, timestamp), + () -> LOG.error("arrivalTimestamp tracking must be enabled to support Eip7732")); + forkChoice + .onExecutionPayload(signedExecutionPayloadEnvelope, executionLayerChannel) + .finish(err -> LOG.error("Failed to process received execution payload.", err)); + } + case IGNORE, REJECT -> {} + } + }); + return validationResult; + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java index 601d05a0f36..71e3920173e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java @@ -17,6 +17,7 @@ import static tech.pegasys.teku.infrastructure.logging.P2PLogger.P2P_LOG; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT_EIP7732; import static tech.pegasys.teku.spec.logic.versions.deneb.blobs.BlobSidecarsValidationResult.INVALID; import static tech.pegasys.teku.statetransition.forkchoice.StateRootCollector.addParentStateRoots; @@ -44,6 +45,7 @@ import tech.pegasys.teku.infrastructure.subscribers.Subscribers; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.cache.CapturingIndexedAttestationCache; import tech.pegasys.teku.spec.cache.IndexedAttestationCache; @@ -52,6 +54,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.forkchoice.InvalidCheckpointException; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyStore; @@ -59,6 +62,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteUpdater; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; @@ -230,6 +234,15 @@ public SafeFuture onBlock( executionLayer)); } + /** Import an execution payload to the store. */ + // EIP7732 TODO: implement + @SuppressWarnings("unused") + public SafeFuture onExecutionPayload( + final SignedExecutionPayloadEnvelope signedEnvelope, + final ExecutionLayerChannel executionLayer) { + return SafeFuture.COMPLETE; + } + public SafeFuture onAttestation( final ValidatableAttestation attestation) { return attestationStateSelector @@ -280,6 +293,18 @@ public void applyIndexedAttestations(final List attestat .ifExceptionGetsHereRaiseABug(); } + // EIP7732 TODO: implement + @SuppressWarnings("unused") + public SafeFuture onPayloadAttestationMessage( + final PayloadAttestationMessage payloadAttestationMessage) { + return SafeFuture.completedFuture(AttestationProcessingResult.SUCCESSFUL); + } + + // EIP7732 TODO: implement + @SuppressWarnings("unused") + public void applyPayloadAttestationMessages( + final List payloadAttestationMessages) {} + public void onAttesterSlashing( final AttesterSlashing slashing, final InternalValidationResult validationStatus, @@ -631,14 +656,17 @@ private BlockImportResult importBlockAndState( // from consensus-specs/fork-choice: private boolean shouldApplyProposerBoost( final SignedBeaconBlock block, final StoreTransaction transaction) { + final SpecVersion specVersion = + spec.atTime(transaction.getGenesisTime(), transaction.getTimeInSeconds()); + // get_current_slot(store) == block.slot - if (!spec.getCurrentSlot(transaction).equals(block.getSlot())) { + if (!specVersion.getForkChoiceUtil().getCurrentSlot(transaction).equals(block.getSlot())) { return false; } // is_before_attesting_interval final UInt64 millisPerSlot = spec.getMillisPerSlot(block.getSlot()); final UInt64 timeIntoSlotMillis = getMillisIntoSlot(transaction, millisPerSlot); - if (!isBeforeAttestingInterval(millisPerSlot, timeIntoSlotMillis)) { + if (!isBeforeAttestingInterval(specVersion, millisPerSlot, timeIntoSlotMillis)) { return false; } // is_first_block @@ -689,9 +717,13 @@ private UInt64 getMillisIntoSlot(final StoreTransaction transaction, final UInt6 } private boolean isBeforeAttestingInterval( - final UInt64 millisPerSlot, final UInt64 timeIntoSlotMillis) { - UInt64 oneThirdSlot = millisPerSlot.dividedBy(INTERVALS_PER_SLOT); - return timeIntoSlotMillis.isLessThan(oneThirdSlot); + final SpecVersion specVersion, final UInt64 millisPerSlot, final UInt64 timeIntoSlotMillis) { + final UInt64 attestationDueOffset = + millisPerSlot.dividedBy( + specVersion.getMilestone().isGreaterThanOrEqualTo(SpecMilestone.EIP7732) + ? INTERVALS_PER_SLOT_EIP7732 + : INTERVALS_PER_SLOT); + return timeIntoSlotMillis.isLessThan(attestationDueOffset); } private void onExecutionPayloadResult( diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java index fe6c43a345b..9449bc659d9 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java @@ -14,6 +14,8 @@ package tech.pegasys.teku.statetransition.forkchoice; import static com.google.common.base.Preconditions.checkState; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT_EIP7732; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; @@ -36,6 +38,8 @@ import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCommitment; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -381,6 +385,11 @@ static Supplier> createLazyKzgCommitmentsSupplier( } static Duration calculateCompletionTimeout(final Spec spec, final UInt64 slot) { - return Duration.ofMillis((spec.atSlot(slot).getConfig().getSecondsPerSlot() * 1000L) / 3); + final SpecVersion specVersion = spec.atSlot(slot); + final int slotInterval = + specVersion.getMilestone().isGreaterThanOrEqualTo(SpecMilestone.EIP7732) + ? INTERVALS_PER_SLOT_EIP7732 + : INTERVALS_PER_SLOT; + return Duration.ofMillis((specVersion.getConfig().getSecondsPerSlot() * 1000L) / slotInterval); } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/PoolFactory.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/PoolFactory.java index 2978ddce72c..164fdf8d9da 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/PoolFactory.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/PoolFactory.java @@ -15,6 +15,7 @@ import com.google.common.annotations.VisibleForTesting; import java.util.Collections; +import java.util.List; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; @@ -26,6 +27,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTrackerFactory; import tech.pegasys.teku.statetransition.block.BlockImportChannel; import tech.pegasys.teku.storage.client.RecentChainData; @@ -107,6 +109,21 @@ public PendingPool createPendingPoolForAttestations(fina ValidatableAttestation::getEarliestSlotForForkChoiceProcessing); } + public PendingPool createPendingPoolForPayloadAttestations( + final Spec spec) { + return new PendingPool<>( + pendingPoolsSizeGauge, + "payload_attestations", + spec, + DEFAULT_HISTORICAL_SLOT_TOLERANCE, + FutureItems.DEFAULT_FUTURE_SLOT_TOLERANCE, + DEFAULT_MAX_ATTESTATIONS, + PayloadAttestationMessage::hashTreeRoot, + payloadAttestationMessage -> + List.of(payloadAttestationMessage.getData().getBeaconBlockRoot()), + payloadAttestationMessage -> payloadAttestationMessage.getData().getSlot()); + } + public BlockBlobSidecarsTrackersPoolImpl createPoolForBlockBlobSidecarsTrackers( final BlockImportChannel blockImportChannel, final Spec spec, diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadHeaderValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadHeaderValidator.java new file mode 100644 index 00000000000..9150487a666 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadHeaderValidator.java @@ -0,0 +1,201 @@ +/* + * 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.statetransition.validation; + +import static tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture; +import static tech.pegasys.teku.spec.config.Constants.VALID_BLOCK_SET_SIZE; +import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.reject; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedMap; +import tech.pegasys.teku.infrastructure.collections.LimitedSet; +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.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; +import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.operations.validation.OperationInvalidReason; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class ExecutionPayloadHeaderValidator + implements OperationValidator { + private static final Logger LOG = LogManager.getLogger(); + + private final Spec spec; + private final GossipValidationHelper gossipValidationHelper; + private final RecentChainData recentChainData; + + private final Set receivedValidExecutionPayloadHeaderInfoSet = + LimitedSet.createSynchronized(VALID_BLOCK_SET_SIZE); + private final Map highestBidValue = + LimitedMap.createSynchronizedLRU(32); + + public ExecutionPayloadHeaderValidator( + final Spec spec, + final GossipValidationHelper gossipValidationHelper, + final RecentChainData recentChainData) { + this.spec = spec; + this.gossipValidationHelper = gossipValidationHelper; + this.recentChainData = recentChainData; + } + + @Override + public SafeFuture validateForGossip( + final SignedExecutionPayloadHeader signedHeader) { + final ExecutionPayloadHeaderEip7732 header = + ExecutionPayloadHeaderEip7732.required(signedHeader.getMessage()); + final UInt64 builderIndex = header.getBuilderIndex(); + final SlotAndBuilderIndex slotAndBuilderIndex = + new SlotAndBuilderIndex(header.getSlot(), builderIndex); + /* + * [IGNORE] this is the first signed bid seen with a valid signature from the given builder for + * this slot. + */ + if (receivedValidExecutionPayloadHeaderInfoSet.contains(slotAndBuilderIndex)) { + return completedFuture(InternalValidationResult.IGNORE); + } + + final SlotAndParentBlockHash slotAndParentBlockHash = + new SlotAndParentBlockHash(header.getSlot(), header.getParentBlockHash()); + /* + * [IGNORE] this bid is the highest value bid seen for the pair of the corresponding slot and the given parent block hash. + */ + final UInt64 currentHighestBidValue = + highestBidValue.computeIfAbsent(slotAndParentBlockHash, k -> UInt64.ZERO); + if (header.getValue().isLessThanOrEqualTo(currentHighestBidValue)) { + return completedFuture(InternalValidationResult.IGNORE); + } + + final Optional maybeParentBlockSlot = + gossipValidationHelper.getSlotForBlockRoot(header.getParentBlockRoot()); + if (maybeParentBlockSlot.isEmpty()) { + LOG.trace( + "ExecutionPayloadHeaderValidator: Parent block root does not exist. It will be saved for future processing"); + return completedFuture(InternalValidationResult.SAVE_FOR_FUTURE); + } + final UInt64 parentBlockSlot = maybeParentBlockSlot.get(); + + return gossipValidationHelper + .getParentStateInBlockEpoch(parentBlockSlot, header.getParentBlockRoot(), header.getSlot()) + .thenApply( + maybeState -> { + if (maybeState.isEmpty()) { + LOG.trace( + "State wasn't available for parent block root {}", header.getParentBlockRoot()); + return InternalValidationResult.IGNORE; + } + final BeaconState state = maybeState.get(); + /* + * [REJECT] The signed builder bid, header.builder_index is a valid, active, and non-slashed builder index in state. + */ + final Validator builder = state.getValidators().get(builderIndex.intValue()); + final UInt64 epoch = spec.computeEpochAtSlot(state.getSlot()); + final boolean builderIsActive = + builder.getActivationEpoch().isLessThanOrEqualTo(epoch) + && epoch.isLessThan(builder.getExitEpoch()); + if (builderIsActive || builder.isSlashed()) { + return reject("Builder is not active or is slashed"); + } + /* + * [IGNORE] The signed builder bid value, header.value, is less or equal than the builder's balance in state. i.e. MIN_BUILDER_BALANCE + header.value < state.builder_balances[header.builder_index]. + */ + if (header + .getValue() + .plus(spec.atSlot(header.getSlot()).getConfig().getEjectionBalance()) + .isGreaterThan(state.getBalances().get(builderIndex.intValue()).get())) { + return InternalValidationResult.IGNORE; + } + /* + * [IGNORE] header.parent_block_hash is the block hash of a known execution payload in fork choice. + */ + final Optional parentBlockHash = + recentChainData.getExecutionBlockHashForBlockRoot(header.getParentBlockRoot()); + if (parentBlockHash.isEmpty() + || header.getParentBlockHash().equals(parentBlockHash.get())) { + return InternalValidationResult.IGNORE; + } + /* + * [IGNORE] header.parent_block_root is the hash tree root of a known beacon block in fork choice. + */ + final Optional parentBlockRootIsKnown = + recentChainData + .getForkChoiceStrategy() + .map(forkChoice -> forkChoice.contains(header.getParentBlockRoot())); + if (parentBlockRootIsKnown.isEmpty() || !parentBlockRootIsKnown.get()) { + return InternalValidationResult.IGNORE; + } + /* + * [IGNORE] header.slot is the current slot or the next slot. + */ + final Optional currentSlot = recentChainData.getCurrentSlot(); + if (currentSlot.isEmpty() + || (!header.getSlot().equals(currentSlot.get()) + && !header.getSlot().equals(currentSlot.get().increment()))) { + return InternalValidationResult.IGNORE; + } + /* + * [REJECT] The builder signature, signed_execution_payload_header_envelope.signature, is valid with respect to the header_envelope.builder_index. + */ + if (!verifyBuilderSignature(builder.getPublicKey(), signedHeader, state)) { + return reject( + "The builder signature is not valid for a builder with public key %s", + builder.getPublicKey()); + } + + // cache the valid header and the bid value + receivedValidExecutionPayloadHeaderInfoSet.add(slotAndBuilderIndex); + highestBidValue.put(slotAndParentBlockHash, header.getValue()); + + return InternalValidationResult.ACCEPT; + }); + } + + // EIP7732 TODO: implement + @Override + public Optional validateForBlockInclusion( + final BeaconState stateAtBlockSlot, final SignedExecutionPayloadHeader operation) { + return Optional.empty(); + } + + private boolean verifyBuilderSignature( + final BLSPublicKey publicKey, + final SignedExecutionPayloadHeader signedHeader, + final BeaconState state) { + final Bytes32 domain = + spec.getDomain( + Domain.BEACON_BUILDER, + spec.getCurrentEpoch(state), + state.getFork(), + state.getGenesisValidatorsRoot()); + final ExecutionPayloadHeaderEip7732 header = + ExecutionPayloadHeaderEip7732.required(signedHeader.getMessage()); + final Bytes signingRoot = spec.computeSigningRoot(header, domain); + return BLS.verify(publicKey, signingRoot, signedHeader.getSignature()); + } + + record SlotAndBuilderIndex(UInt64 slot, UInt64 builderIndex) {} + + record SlotAndParentBlockHash(UInt64 slot, Bytes32 parentBlockHash) {} +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadValidator.java new file mode 100644 index 00000000000..64f449ad33a --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/ExecutionPayloadValidator.java @@ -0,0 +1,160 @@ +/* + * 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.statetransition.validation; + +import static tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture; +import static tech.pegasys.teku.spec.config.Constants.VALID_BLOCK_SET_SIZE; + +import java.util.Set; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedSet; +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.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadEip7732; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; +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.versions.eip7732.BeaconStateEip7732; +import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class ExecutionPayloadValidator implements ReceivedBlockEventsChannel { + + private final Spec spec; + private final RecentChainData recentChainData; + + private final Set seenBlockRootInfoSet = + LimitedSet.createSynchronized(VALID_BLOCK_SET_SIZE); + private final Set receivedValidEnvelopeInfoSet = + LimitedSet.createSynchronized(VALID_BLOCK_SET_SIZE); + + public ExecutionPayloadValidator( + final Spec spec, + final GossipValidationHelper gossipValidationHelper, + final RecentChainData recentChainData) { + this.spec = spec; + this.recentChainData = recentChainData; + } + + public SafeFuture validate( + final SignedExecutionPayloadEnvelope signedExecutionPayloadEnvelope) { + final ExecutionPayloadEnvelope envelope = signedExecutionPayloadEnvelope.getMessage(); + /* + * [IGNORE] The envelope's block root envelope.block_root has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). + */ + if (seenBlockRootInfoSet.add(envelope.getBeaconBlockRoot())) { + return SafeFuture.completedFuture(InternalValidationResult.IGNORE); + } + /* + * [IGNORE] The node has not seen another valid SignedExecutionPayloadEnvelope for this block root from this builder. + */ + final BlockRootAndBuilderIndex blockRootAndBuilderIndex = + new BlockRootAndBuilderIndex(envelope.getBeaconBlockRoot(), envelope.getBuilderIndex()); + if (receivedValidEnvelopeInfoSet.contains(blockRootAndBuilderIndex)) { + return completedFuture(InternalValidationResult.IGNORE); + } + + return recentChainData + .retrieveBlockState(envelope.getBeaconBlockRoot()) + .thenApply( + maybeState -> { + /* + * [REJECT] block passes validation. + * If state is not available for a block root, block has failed validation + */ + if (maybeState.isEmpty()) { + return InternalValidationResult.reject( + "Block with root %s hasn't been imported", envelope.getBeaconBlockRoot()); + } + final BeaconState state = maybeState.get(); + final ExecutionPayloadHeaderEip7732 header = + ExecutionPayloadHeaderEip7732.required( + BeaconStateEip7732.required(state).getLatestExecutionPayloadHeader()); + /* + * [REJECT] envelope.builder_index == header.builder_index + */ + if (!envelope.getBuilderIndex().equals(header.getBuilderIndex())) { + return InternalValidationResult.reject( + "Envelope builder index does not match the committed builder index"); + } + final ExecutionPayloadEip7732 payload = + ExecutionPayloadEip7732.required(envelope.getPayload()); + /* + * if envelope.payload_withheld == False then + * [REJECT] payload.block_hash == header.block_hash + */ + if (envelope.isPayloadWithheld() + && !payload.getBlockHash().equals(header.getBlockHash())) { + return InternalValidationResult.reject( + "Payload block hash does not match the committed block hash"); + } + /* + * [REJECT] The builder signature, signed_execution_payload_envelope.signature, is valid with respect to the builder's public key. + */ + final Validator builder = + state.getValidators().get(envelope.getBuilderIndex().intValue()); + if (!verifyBuilderSignature( + builder.getPublicKey(), signedExecutionPayloadEnvelope, state)) { + return InternalValidationResult.reject( + "The builder signature is not valid for a builder with public key %s", + builder.getPublicKey()); + } + + // cache the valid envelope + receivedValidEnvelopeInfoSet.add(blockRootAndBuilderIndex); + + return InternalValidationResult.ACCEPT; + }); + } + + private boolean verifyBuilderSignature( + final BLSPublicKey publicKey, + final SignedExecutionPayloadEnvelope signedExecutionPayloadEnvelope, + final BeaconState state) { + final Bytes32 domain = + spec.getDomain( + Domain.BEACON_BUILDER, + spec.getCurrentEpoch(state), + state.getFork(), + state.getGenesisValidatorsRoot()); + final Bytes signingRoot = + spec.computeSigningRoot(signedExecutionPayloadEnvelope.getMessage(), domain); + return BLS.verify(publicKey, signingRoot, signedExecutionPayloadEnvelope.getSignature()); + } + + @Override + public void onBlockSeen(final SignedBeaconBlock block) { + seenBlockRootInfoSet.add(block.getRoot()); + } + + @Override + public void onBlockValidated(final SignedBeaconBlock block) { + // NO-OP + } + + @Override + public void onBlockImported(final SignedBeaconBlock block, final boolean executionOptimistic) { + // NO-OP + } + + record BlockRootAndBuilderIndex(Bytes32 blockRoot, UInt64 builderIndex) {} +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/PayloadAttestationValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/PayloadAttestationValidator.java new file mode 100644 index 00000000000..280a96bd159 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/PayloadAttestationValidator.java @@ -0,0 +1,142 @@ +/* + * 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.statetransition.validation; + +import static tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture; +import static tech.pegasys.teku.spec.config.Constants.VALID_BLOCK_SET_SIZE; + +import it.unimi.dsi.fastutil.ints.IntList; +import java.util.Optional; +import java.util.Set; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedSet; +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.constants.PayloadStatus; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.util.AttestationUtil.SlotInclusionGossipValidationResult; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class PayloadAttestationValidator { + + private final Spec spec; + private final RecentChainData recentChainData; + + private final Set receivedValidPayloadAttestationInfoSet = + LimitedSet.createSynchronized(VALID_BLOCK_SET_SIZE); + + public PayloadAttestationValidator(final Spec spec, final RecentChainData recentChainData) { + this.spec = spec; + this.recentChainData = recentChainData; + } + + public SafeFuture validate( + final PayloadAttestationMessage payloadAttestationMessage) { + final PayloadAttestationData data = payloadAttestationMessage.getData(); + /* + * [IGNORE] The message's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. data.slot == current_slot. + */ + final UInt64 genesisTime = recentChainData.getGenesisTime(); + final UInt64 currentTimeMillis = recentChainData.getStore().getTimeInMillis(); + final Optional slotInclusionGossipValidationResult = + spec.atSlot(data.getSlot()) + .getAttestationUtil() + .performSlotInclusionGossipValidation( + payloadAttestationMessage.getData(), genesisTime, currentTimeMillis); + if (slotInclusionGossipValidationResult.isPresent()) { + return switch (slotInclusionGossipValidationResult.get()) { + case IGNORE -> completedFuture(InternalValidationResult.IGNORE); + case SAVE_FOR_FUTURE -> completedFuture(InternalValidationResult.SAVE_FOR_FUTURE); + }; + } + /* + * [REJECT] The message's payload status is a valid status, i.e. data.payload_status < PAYLOAD_INVALID_STATUS. + */ + if (data.getPayloadStatus().compareTo(PayloadStatus.PAYLOAD_INVALID_STATUS) >= 0) { + return completedFuture( + InternalValidationResult.reject("The message's payload status is invalid")); + } + /* + * [IGNORE] The payload_attestation_message is the first valid message received from the validator with index payload_attestation_message.validate_index. + */ + final UInt64 validatorIndex = payloadAttestationMessage.getValidatorIndex(); + if (receivedValidPayloadAttestationInfoSet.contains(validatorIndex)) { + return completedFuture(InternalValidationResult.IGNORE); + } + + // The block being voted for (data.beacon_block_root) passes validation. + // It must pass validation to be in the store. + // If it's not in the store, it may not have been processed yet so save for future. + if (!recentChainData.containsBlock(data.getBeaconBlockRoot())) { + return completedFuture(InternalValidationResult.SAVE_FOR_FUTURE); + } + + return recentChainData + .retrieveBlockState(data.getBeaconBlockRoot()) + .thenApply( + maybeState -> { + if (maybeState.isEmpty()) { + // We know the block is imported but now don't have a state to validate against + // Must have got pruned between checks + return InternalValidationResult.IGNORE; + } + final BeaconState state = maybeState.get(); + /* + * [REJECT] The message's validator index is within the payload committee in get_ptc(state, data.slot). The state is the head state corresponding to processing the block up to the current slot as determined by the fork choice. + */ + final IntList payloadCommittee = spec.getPtc(state, data.getSlot()); + if (!payloadCommittee.contains(validatorIndex.intValue())) { + return InternalValidationResult.reject( + "Message's validator index is not within the payload committee"); + } + /* + * [REJECT] The message's signature of payload_attestation_message.signature is valid with respect to the validator index. + */ + final Validator validator = state.getValidators().get(validatorIndex.intValue()); + if (!verifyPayloadAttestationSignature( + validator.getPublicKey(), payloadAttestationMessage, state)) { + return InternalValidationResult.reject( + "The validator signature is not valid for a validator with public key %s", + validator.getPublicKey()); + } + + // cache valid payload attestation + receivedValidPayloadAttestationInfoSet.add(validatorIndex); + + return InternalValidationResult.ACCEPT; + }); + } + + private boolean verifyPayloadAttestationSignature( + final BLSPublicKey publicKey, + final PayloadAttestationMessage payloadAttestationMessage, + final BeaconState state) { + final Bytes32 domain = + spec.getDomain( + Domain.PTC_ATTESTER, + spec.getCurrentEpoch(state), + state.getFork(), + state.getGenesisValidatorsRoot()); + final Bytes signingRoot = spec.computeSigningRoot(payloadAttestationMessage.getData(), domain); + return BLS.verify(publicKey, signingRoot, payloadAttestationMessage.getSignature()); + } +} diff --git a/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskScheduler.java b/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskScheduler.java index 6d5e36a4179..1582ba0ebd5 100644 --- a/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskScheduler.java +++ b/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskScheduler.java @@ -14,9 +14,11 @@ package tech.pegasys.teku.infrastructure.async.timed; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import java.time.Duration; +import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import tech.pegasys.teku.infrastructure.async.AsyncRunner; @@ -55,29 +57,69 @@ private void scheduleRepeatingEvent( final UInt64 initialInvocationTime, final UInt64 repeatingPeriod, final boolean useMillis, - final RepeatingTask task) { - scheduleEvent(new TimedEvent(initialInvocationTime, repeatingPeriod, useMillis, task)); + final RepeatingTask task, + final Optional expiration, + final Optional expirationAction) { + scheduleEvent( + new TimedEvent( + initialInvocationTime, repeatingPeriod, useMillis, task, expiration, expirationAction)); } public void scheduleRepeatingEvent( final UInt64 initialInvocationTime, final UInt64 repeatingPeriod, final RepeatingTask task) { - scheduleRepeatingEvent(initialInvocationTime, repeatingPeriod, false, task); + scheduleRepeatingEvent( + initialInvocationTime, repeatingPeriod, false, task, Optional.empty(), Optional.empty()); } public void scheduleRepeatingEventInMillis( final UInt64 initialInvocationTime, final UInt64 repeatingPeriod, final RepeatingTask task) { - scheduleRepeatingEvent(initialInvocationTime, repeatingPeriod, true, task); + scheduleRepeatingEvent( + initialInvocationTime, repeatingPeriod, true, task, Optional.empty(), Optional.empty()); + } + + public void scheduleRepeatingEvent( + final UInt64 initialInvocationTime, + final UInt64 repeatingPeriod, + final RepeatingTask task, + final UInt64 expirationTime, + final ExpirationTask expirationTask) { + scheduleRepeatingEvent( + initialInvocationTime, + repeatingPeriod, + false, + task, + Optional.of(expirationTime), + Optional.of(expirationTask)); + } + + public void scheduleRepeatingEventInMillis( + final UInt64 initialInvocationTime, + final UInt64 repeatingPeriod, + final RepeatingTask task, + final UInt64 expirationTime, + final ExpirationTask expirationTask) { + scheduleRepeatingEvent( + initialInvocationTime, + repeatingPeriod, + true, + task, + Optional.of(expirationTime), + Optional.of(expirationTask)); } private void scheduleEvent(final TimedEvent event) { UInt64 nowMs = timeProvider.getTimeInMillis(); - UInt64 dueMs = getDueMs(event); + UInt64 dueMs = event.getNextDueMs(); // First execute any already due executions while (nowMs.isGreaterThanOrEqualTo(dueMs)) { executeEvent(event); + if (event.isExpired) { + expireEvent(event); + return; + } // Update both now and due in case another repeat because due while we were executing nowMs = timeProvider.getTimeInMillis(); - dueMs = getDueMs(event); + dueMs = event.getNextDueMs(); } asyncRunner .runAfterDelay( @@ -93,10 +135,6 @@ private void scheduleEvent(final TimedEvent event) { }); } - private UInt64 getDueMs(final TimedEvent event) { - return event.useMillis ? event.getNextDue() : secondsToMillis(event.getNextDue()); - } - private void executeEvent(final TimedEvent event) { try { UInt64 actualTime = getActualTime(event); @@ -108,35 +146,60 @@ private void executeEvent(final TimedEvent event) { } } + private void expireEvent(final TimedEvent event) { + try { + UInt64 actualTime = getActualTime(event); + event.expire(actualTime); + } catch (final Throwable t) { + Thread.currentThread() + .getUncaughtExceptionHandler() + .uncaughtException(Thread.currentThread(), t); + } + } + private UInt64 getActualTime(final TimedEvent event) { return event.useMillis ? timeProvider.getTimeInMillis() : timeProvider.getTimeInSeconds(); } private static class TimedEvent { private UInt64 nextDue; + private boolean isExpired; private final UInt64 repeatPeriod; private final boolean useMillis; private final RepeatingTask action; + private final Optional expiration; + private final Optional expirationAction; private TimedEvent( final UInt64 nextDue, final UInt64 repeatPeriod, final boolean useMillis, - final RepeatingTask action) { + final RepeatingTask action, + final Optional expiration, + final Optional expirationAction) { + checkArgument( + expiration.isPresent() == expirationAction.isPresent(), + "expiration and expirationAction must be both specified"); this.nextDue = nextDue; + this.isExpired = false; this.repeatPeriod = repeatPeriod; this.useMillis = useMillis; this.action = action; + this.expiration = expiration; + this.expirationAction = expirationAction; } - public UInt64 getNextDue() { - return nextDue; + public UInt64 getNextDueMs() { + return useMillis ? nextDue : secondsToMillis(nextDue); } public void execute(final UInt64 actualTime) { checkArgument( actualTime.isGreaterThanOrEqualTo(nextDue), "Executing task before it is due. Scheduled " + nextDue + " currently " + actualTime); + checkState( + !isExpired, + "Executing expired task. Expiration " + expiration + " currently " + actualTime); try { action.execute(nextDue, actualTime); } finally { @@ -144,12 +207,23 @@ public void execute(final UInt64 actualTime) { } } + public void expire(final UInt64 actualTime) { + checkArgument( + isExpired, "Task is not expired. Expiration " + expiration + " currently " + actualTime); + expirationAction.orElseThrow().execute(expiration.orElseThrow(), actualTime); + } + public void moveToNextScheduledTime() { nextDue = nextDue.plus(repeatPeriod); + expiration.ifPresent(exp -> isExpired = exp.isLessThan(nextDue)); } } public interface RepeatingTask { void execute(UInt64 scheduledTime, UInt64 actualTime); } + + public interface ExpirationTask { + void execute(UInt64 expirationTime, UInt64 actualTime); + } } diff --git a/infrastructure/async/src/test/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskSchedulerTest.java b/infrastructure/async/src/test/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskSchedulerTest.java index e5538f18c52..4913b1104b5 100644 --- a/infrastructure/async/src/test/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskSchedulerTest.java +++ b/infrastructure/async/src/test/java/tech/pegasys/teku/infrastructure/async/timed/RepeatingTaskSchedulerTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import tech.pegasys.teku.infrastructure.async.StubAsyncRunner; +import tech.pegasys.teku.infrastructure.async.timed.RepeatingTaskScheduler.ExpirationTask; import tech.pegasys.teku.infrastructure.async.timed.RepeatingTaskScheduler.RepeatingTask; import tech.pegasys.teku.infrastructure.time.StubTimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -29,6 +30,7 @@ class RepeatingTaskSchedulerTest { private final StubTimeProvider timeProvider = StubTimeProvider.withTimeInSeconds(500); private final StubAsyncRunner asyncRunner = new StubAsyncRunner(timeProvider); private final RepeatingTask action = mock(RepeatingTask.class); + private final ExpirationTask expirationAction = mock(ExpirationTask.class); private final RepeatingTaskScheduler eventQueue = new RepeatingTaskScheduler(asyncRunner, timeProvider); @@ -122,6 +124,51 @@ void shouldReportScheduledAndActualExecutionTimeWhenTaskIsDelayed(final Schedule verify(action).execute(scheduledTime, getTime(type)); } + @ParameterizedTest + @EnumSource(SchedulerType.class) + void shouldExecuteEventAndExpireImmediatelyWhenAlreadyDueAndAlreadyExpired( + final SchedulerType type) { + scheduleRepeatingEventWithExpiration( + type, getTime(type), UInt64.valueOf(12), action, getTime(type), expirationAction); + verify(action).execute(getTime(type), getTime(type)); + verify(expirationAction).execute(getTime(type), getTime(type)); + + advanceTimeBy(type, 24); + + verifyNoMoreInteractions(action); + verifyNoMoreInteractions(expirationAction); + } + + @ParameterizedTest + @EnumSource(SchedulerType.class) + void shouldExecuteEventAndExpire(final SchedulerType type) { + final UInt64 initialSchedule = getTime(type).plus(1); + final UInt64 expirationTime = getTime(type).plus(13); + scheduleRepeatingEventWithExpiration( + type, initialSchedule, UInt64.valueOf(12), action, expirationTime, expirationAction); + + asyncRunner.executeDueActionsRepeatedly(); + verifyNoInteractions(action); + verifyNoInteractions(expirationAction); + + advanceTimeBy(type, 1); + asyncRunner.executeDueActionsRepeatedly(); + + verify(action).execute(getTime(type), getTime(type)); + verifyNoInteractions(expirationAction); + + advanceTimeBy(type, 12); + asyncRunner.executeDueActionsRepeatedly(); + + verify(action).execute(getTime(type), getTime(type)); + verify(expirationAction).execute(expirationTime, getTime(type)); + + advanceTimeBy(type, 24); + + verifyNoMoreInteractions(action); + verifyNoMoreInteractions(expirationAction); + } + private void scheduleRepeatingEvent( final SchedulerType schedulerType, final UInt64 initialInvocationTime, @@ -134,6 +181,22 @@ private void scheduleRepeatingEvent( } } + private void scheduleRepeatingEventWithExpiration( + final SchedulerType schedulerType, + final UInt64 initialInvocationTime, + final UInt64 repeatingPeriod, + final RepeatingTask task, + final UInt64 expirationTime, + final ExpirationTask expirationTask) { + if (schedulerType == SchedulerType.SECONDS) { + eventQueue.scheduleRepeatingEvent( + initialInvocationTime, repeatingPeriod, task, expirationTime, expirationTask); + } else { + eventQueue.scheduleRepeatingEventInMillis( + initialInvocationTime, repeatingPeriod, task, expirationTime, expirationTask); + } + } + private UInt64 getTime(final SchedulerType schedulerType) { return schedulerType == SchedulerType.SECONDS ? timeProvider.getTimeInSeconds() diff --git a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/ValidatorLogger.java b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/ValidatorLogger.java index 31ef97f3e52..a464eef3536 100644 --- a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/ValidatorLogger.java +++ b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/ValidatorLogger.java @@ -203,6 +203,17 @@ public void producedInvalidAttestation(final UInt64 slot, final String reason) { Color.RED)); } + public void producedInvalidPayloadAttestation(final UInt64 slot, final String reason) { + log.error( + ColorConsolePrinter.print( + PREFIX + + "Produced invalid payload attestation for slot " + + slot + + ". Invalid reason: " + + reason, + Color.RED)); + } + public void producedInvalidAggregate(final UInt64 slot, final String reason) { log.error( ColorConsolePrinter.print( diff --git a/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/Validator/DutyType.java b/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/Validator/DutyType.java index 068bb277c1b..2329933405b 100644 --- a/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/Validator/DutyType.java +++ b/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/Validator/DutyType.java @@ -16,7 +16,8 @@ public enum DutyType { ATTESTATION_AGGREGATION("attestation_aggregation"), ATTESTATION_PRODUCTION("attestation_production"), - BLOCK_PRODUCTION("block_production"); + BLOCK_PRODUCTION("block_production"), + PAYLOAD_ATTESTATION_PRODUCTION("payload_attestation_production"); private final String name; diff --git a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java index f4a615b0ab2..ad2c9cf070d 100644 --- a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java +++ b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java @@ -85,6 +85,7 @@ private void setUpNextSpec(final SpecMilestone nextSpecMilestone) { nextSpec = Optional.of(TestSpecFactory.createMinimalWithElectraForkEpoch(nextSpecEpoch)); } case ELECTRA -> throw new RuntimeException("Base spec is already latest supported milestone"); + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); } nextSpecSlot = nextSpec.orElseThrow().computeStartSlotAtEpoch(nextSpecEpoch); } @@ -262,6 +263,7 @@ protected static Class milestoneToBeaconBlockBodyClass(final SpecMilestone mi case CAPELLA -> BeaconBlockBodyCapella.class; case DENEB -> BeaconBlockBodyDeneb.class; case ELECTRA -> BeaconBlockBodyElectra.class; + case EIP7732 -> throw new UnsupportedOperationException("EIP7732 TODO"); }; } } diff --git a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java index e21b79749d6..9c710e26076 100644 --- a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java +++ b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java @@ -147,7 +147,7 @@ public void requestMetadata_withDisparateVersionsEnabled( private static Class milestoneToMetadataClass(final SpecMilestone milestone) { return switch (milestone) { case PHASE0 -> MetadataMessagePhase0.class; - case ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA -> MetadataMessageAltair.class; + case ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, EIP7732 -> MetadataMessageAltair.class; }; } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index aee3b80cfad..9d7111eaf2b 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -38,6 +38,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsBellatrix; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsCapella; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsDeneb; +import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsEip7732; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; @@ -71,7 +72,10 @@ import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -106,6 +110,10 @@ public class Eth2P2PNetworkBuilder { protected OperationProcessor gossipedVoluntaryExitConsumer; protected OperationProcessor gossipedSignedBlsToExecutionChangeProcessor; + protected OperationProcessor gossipedExecutionPayloadProcessor; + protected OperationProcessor gossipedPayloadAttestationProcessor; + protected OperationProcessor + gossipedExecutionPayloadHeaderProcessor; protected ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; protected MetricsSystem metricsSystem; protected final List> rpcMethods = new ArrayList<>(); @@ -313,6 +321,28 @@ private GossipForkSubscriptions createSubscriptions( gossipedSyncCommitteeMessageProcessor, gossipedSignedBlsToExecutionChangeProcessor, debugDataDumper); + case EIP7732 -> new GossipForkSubscriptionsEip7732( + forkAndSpecMilestone.getFork(), + spec, + asyncRunner, + metricsSystem, + network, + combinedChainDataClient.getRecentChainData(), + gossipEncoding, + gossipedBlockProcessor, + gossipedBlobSidecarProcessor, + gossipedAttestationConsumer, + gossipedAggregateProcessor, + gossipedAttesterSlashingConsumer, + gossipedProposerSlashingConsumer, + gossipedVoluntaryExitConsumer, + gossipedSignedContributionAndProofProcessor, + gossipedSyncCommitteeMessageProcessor, + gossipedSignedBlsToExecutionChangeProcessor, + gossipedExecutionPayloadProcessor, + gossipedPayloadAttestationProcessor, + gossipedExecutionPayloadHeaderProcessor, + debugDataDumper); }; } @@ -424,6 +454,10 @@ private void validate() { assertNotNull("gossipedSyncCommitteeMessageProcessor", gossipedSyncCommitteeMessageProcessor); assertNotNull( "gossipedSignedBlsToExecutionChangeProcessor", gossipedSignedBlsToExecutionChangeProcessor); + assertNotNull("gossipedExecutionPayloadProcessor", gossipedExecutionPayloadProcessor); + assertNotNull("gossipedPayloadAttestationProcessor", gossipedPayloadAttestationProcessor); + assertNotNull( + "gossipedExecutionPayloadHeaderProcessor", gossipedExecutionPayloadHeaderProcessor); } private void assertNotNull(final String fieldName, final Object fieldValue) { @@ -535,6 +569,28 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } + public Eth2P2PNetworkBuilder gossipedExecutionPayload( + final OperationProcessor gossipedExecutionPayloadProcessor) { + checkNotNull(gossipedExecutionPayloadProcessor); + this.gossipedExecutionPayloadProcessor = gossipedExecutionPayloadProcessor; + return this; + } + + public Eth2P2PNetworkBuilder gossipedPayloadAttestationProcessor( + final OperationProcessor gossipedPayloadAttestationProcessor) { + checkNotNull(gossipedPayloadAttestationProcessor); + this.gossipedPayloadAttestationProcessor = gossipedPayloadAttestationProcessor; + return this; + } + + public Eth2P2PNetworkBuilder gossipedExecutionPayloadHeaderProcessor( + final OperationProcessor + gossipedExecutionPayloadHeaderProcessor) { + checkNotNull(gossipedExecutionPayloadHeaderProcessor); + this.gossipedExecutionPayloadHeaderProcessor = gossipedExecutionPayloadHeaderProcessor; + return this; + } + public Eth2P2PNetworkBuilder metricsSystem(final MetricsSystem metricsSystem) { checkNotNull(metricsSystem); this.metricsSystem = metricsSystem; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadHeaderManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadHeaderManager.java new file mode 100644 index 00000000000..2b3e6fbc2fb --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadHeaderManager.java @@ -0,0 +1,69 @@ +/* + * 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.networking.eth2.gossip; + +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7732.ExecutionPayloadHeaderEip7732; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; +import tech.pegasys.teku.statetransition.util.DebugDataDumper; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class ExecutionPayloadHeaderManager + extends AbstractGossipManager { + + public ExecutionPayloadHeaderManager( + final RecentChainData recentChainData, + final Spec spec, + final AsyncRunner asyncRunner, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final ForkInfo forkInfo, + final OperationProcessor processor, + final DebugDataDumper debugDataDumper) { + super( + recentChainData, + GossipTopicName.EXECUTION_PAYLOAD_HEADER, + asyncRunner, + gossipNetwork, + gossipEncoding, + forkInfo, + processor, + SchemaDefinitionsEip7732.required( + spec.atEpoch(forkInfo.getFork().getEpoch()).getSchemaDefinitions()) + .getSignedExecutionPayloadHeaderSchema(), + executionPayloadHeader -> { + final UInt64 slot = + executionPayloadHeader + .getMessage() + .toVersionEip7732() + .map(ExecutionPayloadHeaderEip7732::getSlot) + .orElse(UInt64.ZERO); + return spec.computeEpochAtSlot(slot); + }, + spec.getNetworkingConfig(), + debugDataDumper); + } + + public void publishExecutionPayloadHeader(final SignedExecutionPayloadHeader message) { + publishMessage(message); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadManager.java new file mode 100644 index 00000000000..7ce5af5c4eb --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/ExecutionPayloadManager.java @@ -0,0 +1,61 @@ +/* + * 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.networking.eth2.gossip; + +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; +import tech.pegasys.teku.statetransition.util.DebugDataDumper; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class ExecutionPayloadManager extends AbstractGossipManager { + + public ExecutionPayloadManager( + final RecentChainData recentChainData, + final Spec spec, + final AsyncRunner asyncRunner, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final ForkInfo forkInfo, + final OperationProcessor processor, + final DebugDataDumper debugDataDumper) { + super( + recentChainData, + GossipTopicName.EXECUTION_PAYLOAD, + asyncRunner, + gossipNetwork, + gossipEncoding, + forkInfo, + processor, + SchemaDefinitionsEip7732.required( + spec.atEpoch(forkInfo.getFork().getEpoch()).getSchemaDefinitions()) + .getSignedExecutionPayloadEnvelopeSchema(), + executionPayloadEnvelope -> + spec.getForkSchedule() + .getFork(executionPayloadEnvelope.getMessage().getPayload().getMilestone()) + .getEpoch(), + spec.getNetworkingConfig(), + debugDataDumper); + } + + public void publishExecutionPayload(final SignedExecutionPayloadEnvelope message) { + publishMessage(message); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/PayloadAttestationManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/PayloadAttestationManager.java new file mode 100644 index 00000000000..cf76f64b714 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/PayloadAttestationManager.java @@ -0,0 +1,55 @@ +/* + * 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.networking.eth2.gossip; + +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.statetransition.util.DebugDataDumper; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class PayloadAttestationManager extends AbstractGossipManager { + + public PayloadAttestationManager( + final RecentChainData recentChainData, + final Spec spec, + final AsyncRunner asyncRunner, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final ForkInfo forkInfo, + final OperationProcessor processor, + final DebugDataDumper debugDataDumper) { + super( + recentChainData, + GossipTopicName.PAYLOAD_ATTESTATION_MESSAGE, + asyncRunner, + gossipNetwork, + gossipEncoding, + forkInfo, + processor, + PayloadAttestationMessage.SSZ_SCHEMA, + payloadAttestation -> spec.computeEpochAtSlot(payloadAttestation.getData().getSlot()), + spec.getNetworkingConfig(), + debugDataDumper); + } + + public void publishAttestationPayload(final PayloadAttestationMessage message) { + publishMessage(message); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java index e8fd1110a95..2e116379782 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java @@ -18,7 +18,10 @@ import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -70,4 +73,16 @@ default void unsubscribeFromSyncCommitteeSubnet(final int subnetId) { } default void publishSignedBlsToExecutionChangeMessage(final SignedBlsToExecutionChange message) {} + + default void publishExecutionPayloadMessage(final SignedExecutionPayloadEnvelope message) { + // since EIP-7732 + } + + default void publishPayloadAttestation(final PayloadAttestationMessage payloadAttestation) { + // since EIP-7732 + } + + default void publishExecutionPayloadHeaderMessage(final SignedExecutionPayloadHeader message) { + // since EIP-7732 + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7732.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7732.java new file mode 100644 index 00000000000..990f678a892 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7732.java @@ -0,0 +1,165 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip.forks.versions; + +import org.hyperledger.besu.plugin.services.MetricsSystem; +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.networking.eth2.gossip.ExecutionPayloadHeaderManager; +import tech.pegasys.teku.networking.eth2.gossip.ExecutionPayloadManager; +import tech.pegasys.teku.networking.eth2.gossip.PayloadAttestationManager; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; +import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; +import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.ValidatableSyncCommitteeMessage; +import tech.pegasys.teku.spec.datastructures.state.Fork; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.statetransition.util.DebugDataDumper; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class GossipForkSubscriptionsEip7732 extends GossipForkSubscriptionsElectra { + + private final OperationProcessor executionPayloadProcessor; + private final OperationProcessor payloadAttestationProcessor; + private final OperationProcessor executionPayloadHeaderProcessor; + + private ExecutionPayloadManager executionPayloadManager; + private PayloadAttestationManager payloadAttestationManager; + private ExecutionPayloadHeaderManager executionPayloadHeaderManager; + + public GossipForkSubscriptionsEip7732( + final Fork fork, + final Spec spec, + final AsyncRunner asyncRunner, + final MetricsSystem metricsSystem, + final DiscoveryNetwork discoveryNetwork, + final RecentChainData recentChainData, + final GossipEncoding gossipEncoding, + final OperationProcessor blockProcessor, + final OperationProcessor blobSidecarProcessor, + final OperationProcessor attestationProcessor, + final OperationProcessor aggregateProcessor, + final OperationProcessor attesterSlashingProcessor, + final OperationProcessor proposerSlashingProcessor, + final OperationProcessor voluntaryExitProcessor, + final OperationProcessor + signedContributionAndProofOperationProcessor, + final OperationProcessor + syncCommitteeMessageOperationProcessor, + final OperationProcessor + signedBlsToExecutionChangeOperationProcessor, + final OperationProcessor executionPayloadProcessor, + final OperationProcessor payloadAttestationProcessor, + final OperationProcessor executionPayloadHeaderProcessor, + final DebugDataDumper debugDataDumper) { + super( + fork, + spec, + asyncRunner, + metricsSystem, + discoveryNetwork, + recentChainData, + gossipEncoding, + blockProcessor, + blobSidecarProcessor, + attestationProcessor, + aggregateProcessor, + attesterSlashingProcessor, + proposerSlashingProcessor, + voluntaryExitProcessor, + signedContributionAndProofOperationProcessor, + syncCommitteeMessageOperationProcessor, + signedBlsToExecutionChangeOperationProcessor, + debugDataDumper); + this.executionPayloadProcessor = executionPayloadProcessor; + this.payloadAttestationProcessor = payloadAttestationProcessor; + this.executionPayloadHeaderProcessor = executionPayloadHeaderProcessor; + } + + @Override + protected void addGossipManagers(final ForkInfo forkInfo) { + super.addGossipManagers(forkInfo); + addExecutionPayloadManager(forkInfo); + addPayloadAttestationManager(forkInfo); + addExecutionPayloadHeaderManager(forkInfo); + } + + void addExecutionPayloadManager(final ForkInfo forkInfo) { + executionPayloadManager = + new ExecutionPayloadManager( + recentChainData, + spec, + asyncRunner, + discoveryNetwork, + gossipEncoding, + forkInfo, + executionPayloadProcessor, + debugDataDumper); + addGossipManager(executionPayloadManager); + } + + void addPayloadAttestationManager(final ForkInfo forkInfo) { + payloadAttestationManager = + new PayloadAttestationManager( + recentChainData, + spec, + asyncRunner, + discoveryNetwork, + gossipEncoding, + forkInfo, + payloadAttestationProcessor, + debugDataDumper); + addGossipManager(payloadAttestationManager); + } + + void addExecutionPayloadHeaderManager(final ForkInfo forkInfo) { + executionPayloadHeaderManager = + new ExecutionPayloadHeaderManager( + recentChainData, + spec, + asyncRunner, + discoveryNetwork, + gossipEncoding, + forkInfo, + executionPayloadHeaderProcessor, + debugDataDumper); + addGossipManager(executionPayloadHeaderManager); + } + + @Override + public void publishExecutionPayloadMessage(final SignedExecutionPayloadEnvelope message) { + executionPayloadManager.publishExecutionPayload(message); + } + + @Override + public void publishPayloadAttestation(final PayloadAttestationMessage payloadAttestationMessage) { + payloadAttestationManager.publishAttestationPayload(payloadAttestationMessage); + } + + @Override + public void publishExecutionPayloadHeaderMessage(final SignedExecutionPayloadHeader message) { + executionPayloadHeaderManager.publishExecutionPayloadHeader(message); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java index 87b48b507fb..e7817f79a78 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java @@ -22,7 +22,10 @@ public enum GossipTopicName { PROPOSER_SLASHING, VOLUNTARY_EXIT, SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF, - BLS_TO_EXECUTION_CHANGE; + BLS_TO_EXECUTION_CHANGE, + EXECUTION_PAYLOAD, + PAYLOAD_ATTESTATION_MESSAGE, + EXECUTION_PAYLOAD_HEADER; @Override public String toString() { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index c1e714a86c9..332b4f0a9bc 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -53,6 +53,7 @@ import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; @@ -60,6 +61,8 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage.ExecutionPayloadEnvelopesByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.GoodbyeMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.PingMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.RpcRequest; @@ -67,6 +70,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private static final Logger LOG = LogManager.getLogger(); @@ -90,6 +94,8 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private final Supplier firstSlotSupportingBlobSidecarsByRange; private final Supplier blobSidecarsByRootRequestMessageSchema; + private final Supplier + executionPayloadEnvelopesByRootRequestMessageSchema; private final Supplier maxBlobsPerBlock; DefaultEth2Peer( @@ -125,6 +131,12 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { SchemaDefinitionsDeneb.required( spec.forMilestone(SpecMilestone.DENEB).getSchemaDefinitions()) .getBlobSidecarsByRootRequestMessageSchema()); + this.executionPayloadEnvelopesByRootRequestMessageSchema = + Suppliers.memoize( + () -> + SchemaDefinitionsEip7732.required( + spec.forMilestone(SpecMilestone.EIP7732).getSchemaDefinitions()) + .getExecutionPayloadEnvelopesByRootRequestMessageSchema()); this.maxBlobsPerBlock = Suppliers.memoize(() -> getSpecConfigDeneb().getMaxBlobsPerBlock()); } @@ -253,6 +265,22 @@ public SafeFuture requestBlobSidecarsByRoot( .orElse(failWithUnsupportedMethodException("BlobSidecarsByRoot")); } + @Override + public SafeFuture requestExecutionPayloadEnvelopesByRoot( + final List blockRoots, + final RpcResponseListener listener) { + return rpcMethods + .executionPayloadEnvelopesByRoot() + .map( + method -> + requestStream( + method, + new ExecutionPayloadEnvelopesByRootRequestMessage( + executionPayloadEnvelopesByRootRequestMessageSchema.get(), blockRoots), + listener)) + .orElse(failWithUnsupportedMethodException("ExecutionPayloadEnvelopesByRoot")); + } + @Override public SafeFuture> requestBlockBySlot(final UInt64 slot) { final Eth2RpcMethod blocksByRange = @@ -299,6 +327,21 @@ public SafeFuture> requestBlobSidecarByRoot( .orElse(failWithUnsupportedMethodException("BlobSidecarsByRoot")); } + @Override + public SafeFuture> requestExecutionPayloadEnvelopeByRoot( + final Bytes32 blockRoot) { + return rpcMethods + .executionPayloadEnvelopesByRoot() + .map( + method -> + requestOptionalItem( + method, + new ExecutionPayloadEnvelopesByRootRequestMessage( + executionPayloadEnvelopesByRootRequestMessageSchema.get(), + List.of(blockRoot)))) + .orElse(failWithUnsupportedMethodException("ExecutionPayloadEnvelopesByRoot")); + } + @Override public SafeFuture requestBlocksByRange( final UInt64 startSlot, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java index 5480b26df89..89daf61144a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java @@ -32,6 +32,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.RpcRequest; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; @@ -93,12 +94,18 @@ SafeFuture requestBlocksByRoot( SafeFuture requestBlobSidecarsByRoot( List blobIdentifiers, RpcResponseListener listener); + SafeFuture requestExecutionPayloadEnvelopesByRoot( + List blockRoots, RpcResponseListener listener); + SafeFuture> requestBlockBySlot(UInt64 slot); SafeFuture> requestBlockByRoot(Bytes32 blockRoot); SafeFuture> requestBlobSidecarByRoot(BlobIdentifier blobIdentifier); + SafeFuture> requestExecutionPayloadEnvelopeByRoot( + Bytes32 blockRoot); + SafeFuture requestMetadata(); SafeFuture requestSingleItem( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java index ec2da65a97e..2b05f2a451c 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java @@ -26,6 +26,9 @@ public class BeaconChainMethodIds { static final String BLOB_SIDECARS_BY_ROOT = "/eth2/beacon_chain/req/blob_sidecars_by_root"; static final String BLOB_SIDECARS_BY_RANGE = "/eth2/beacon_chain/req/blob_sidecars_by_range"; + static final String EXECUTION_PAYLOAD_ENVELOPES_BY_ROOT = + "/eth2/beacon_chain/req/execution_payload_envelopes_by_root"; + static final String GET_METADATA = "/eth2/beacon_chain/req/metadata"; static final String PING = "/eth2/beacon_chain/req/ping"; @@ -52,6 +55,11 @@ public static String getBlobSidecarsByRangeMethodId( return getMethodId(BLOB_SIDECARS_BY_RANGE, version, encoding); } + public static String getExecutionPayloadEnvelopeByRootMethodId( + final int version, final RpcEncoding encoding) { + return getMethodId(EXECUTION_PAYLOAD_ENVELOPES_BY_ROOT, version, encoding); + } + public static String getStatusMethodId(final int version, final RpcEncoding encoding) { return getMethodId(STATUS, version, encoding); } @@ -64,6 +72,10 @@ public static int extractBeaconBlocksByRangeVersion(final String methodId) { return extractVersion(methodId, BEACON_BLOCKS_BY_RANGE); } + public static int extractExecutionPayloadEnvelopeByRootVersion(final String methodId) { + return extractVersion(methodId, EXECUTION_PAYLOAD_ENVELOPES_BY_ROOT); + } + public static int extractGetMetadataVersion(final String methodId) { return extractVersion(methodId, GET_METADATA); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java index 631730f8ef9..5797f152249 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BeaconBlocksByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.ExecutionPayloadEnvelopesByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.GoodbyeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; @@ -45,6 +46,7 @@ import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage.BeaconBlocksByRangeRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage; @@ -54,11 +56,14 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage.EmptyMessageSchema; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage.ExecutionPayloadEnvelopesByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.GoodbyeMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.PingMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.StatusMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7732; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.client.RecentChainData; @@ -74,6 +79,10 @@ public class BeaconChainMethods { blobSidecarsByRoot; private final Optional> blobSidecarsByRange; + private final Optional< + Eth2RpcMethod< + ExecutionPayloadEnvelopesByRootRequestMessage, SignedExecutionPayloadEnvelope>> + executionPayloadEnvelopesByRoot; private final Eth2RpcMethod getMetadata; private final Eth2RpcMethod ping; @@ -88,6 +97,10 @@ private BeaconChainMethods( blobSidecarsByRoot, final Optional> blobSidecarsByRange, + final Optional< + Eth2RpcMethod< + ExecutionPayloadEnvelopesByRootRequestMessage, SignedExecutionPayloadEnvelope>> + executionPayloadEnvelopesByRoot, final Eth2RpcMethod getMetadata, final Eth2RpcMethod ping) { this.status = status; @@ -96,6 +109,7 @@ private BeaconChainMethods( this.beaconBlocksByRange = beaconBlocksByRange; this.blobSidecarsByRoot = blobSidecarsByRoot; this.blobSidecarsByRange = blobSidecarsByRange; + this.executionPayloadEnvelopesByRoot = executionPayloadEnvelopesByRoot; this.getMetadata = getMetadata; this.ping = ping; this.allMethods = @@ -103,6 +117,7 @@ private BeaconChainMethods( List.of(status, goodBye, beaconBlocksByRoot, beaconBlocksByRange, getMetadata, ping)); blobSidecarsByRoot.ifPresent(allMethods::add); blobSidecarsByRange.ifPresent(allMethods::add); + executionPayloadEnvelopesByRoot.ifPresent(allMethods::add); } public static BeaconChainMethods create( @@ -144,6 +159,8 @@ public static BeaconChainMethods create( peerLookup, rpcEncoding, recentChainData), + createExecutionPayloadEnvelopesByRoot( + spec, metricsSystem, asyncRunner, peerLookup, rpcEncoding, recentChainData), createMetadata(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding), createPing(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding)); } @@ -343,6 +360,47 @@ private static Eth2RpcMethod createGoodBye( spec.getNetworkingConfig())); } + private static Optional< + Eth2RpcMethod< + ExecutionPayloadEnvelopesByRootRequestMessage, SignedExecutionPayloadEnvelope>> + createExecutionPayloadEnvelopesByRoot( + final Spec spec, + final MetricsSystem metricsSystem, + final AsyncRunner asyncRunner, + final PeerLookup peerLookup, + final RpcEncoding rpcEncoding, + final RecentChainData recentChainData) { + if (!spec.isMilestoneSupported(SpecMilestone.EIP7732)) { + return Optional.empty(); + } + + final RpcContextCodec forkDigestContextCodec = + RpcContextCodec.forkDigest( + spec, recentChainData, ForkDigestPayloadContext.EXECUTION_PAYLOAD_ENVELOPE); + + final ExecutionPayloadEnvelopesByRootMessageHandler + executionPayloadEnvelopesByRootMessageHandler = + new ExecutionPayloadEnvelopesByRootMessageHandler(spec, metricsSystem, recentChainData); + final ExecutionPayloadEnvelopesByRootRequestMessageSchema + executionPayloadEnvelopesByRootRequestMessageSchema = + SchemaDefinitionsEip7732.required( + spec.forMilestone(SpecMilestone.EIP7732).getSchemaDefinitions()) + .getExecutionPayloadEnvelopesByRootRequestMessageSchema(); + + return Optional.of( + new SingleProtocolEth2RpcMethod<>( + asyncRunner, + BeaconChainMethodIds.EXECUTION_PAYLOAD_ENVELOPES_BY_ROOT, + 1, + rpcEncoding, + executionPayloadEnvelopesByRootRequestMessageSchema, + true, + forkDigestContextCodec, + executionPayloadEnvelopesByRootMessageHandler, + peerLookup, + spec.getNetworkingConfig())); + } + private static Eth2RpcMethod createMetadata( final Spec spec, final AsyncRunner asyncRunner, @@ -460,6 +518,13 @@ public Eth2RpcMethod beaco return blobSidecarsByRange; } + public Optional< + Eth2RpcMethod< + ExecutionPayloadEnvelopesByRootRequestMessage, SignedExecutionPayloadEnvelope>> + executionPayloadEnvelopesByRoot() { + return executionPayloadEnvelopesByRoot; + } + public Eth2RpcMethod getMetadata() { return getMetadata; } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/ExecutionPayloadEnvelopesByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/ExecutionPayloadEnvelopesByRootMessageHandler.java new file mode 100644 index 00000000000..cef90898c6b --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/ExecutionPayloadEnvelopesByRootMessageHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.core.RpcResponseStatus.INVALID_REQUEST_CODE; + +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.peers.Eth2Peer; +import tech.pegasys.teku.networking.eth2.rpc.core.PeerRequiredLocalMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.core.ResponseCallback; +import tech.pegasys.teku.networking.eth2.rpc.core.RpcException; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.ExecutionPayloadEnvelopesByRootRequestMessage; +import tech.pegasys.teku.storage.client.RecentChainData; + +@SuppressWarnings("unused") +public class ExecutionPayloadEnvelopesByRootMessageHandler + extends PeerRequiredLocalMessageHandler< + ExecutionPayloadEnvelopesByRootRequestMessage, SignedExecutionPayloadEnvelope> { + private static final Logger LOG = LogManager.getLogger(); + + private final Spec spec; + private final RecentChainData recentChainData; + private final Counter totalExecutionPayloadEnvelopesRequestedCounter; + + private final LabelledMetric requestCounter; + + public ExecutionPayloadEnvelopesByRootMessageHandler( + final Spec spec, final MetricsSystem metricsSystem, final RecentChainData recentChainData) { + this.spec = spec; + this.recentChainData = recentChainData; + requestCounter = + metricsSystem.createLabelledCounter( + TekuMetricCategory.NETWORK, + "rpc_execution_payload_envelopes_by_root_requests_total", + "Total number of execution payload envelopes by root requests received", + "status"); + totalExecutionPayloadEnvelopesRequestedCounter = + metricsSystem.createCounter( + TekuMetricCategory.NETWORK, + "rpc_execution_payload_envelopes_by_root_requested_envelopes_total", + "Total number of execution payload envelopes requested in accepted execution payload envelopes by root requests from peers"); + } + + @Override + public Optional validateRequest( + final String protocolId, final ExecutionPayloadEnvelopesByRootRequestMessage request) { + final UInt64 maxRequestPayloads = getMaxRequestPayloads(); + + if (request.size() > maxRequestPayloads.intValue()) { + requestCounter.labels("count_too_big").inc(); + return Optional.of( + new RpcException( + INVALID_REQUEST_CODE, + "Only a maximum of " + + maxRequestPayloads + + " payloads can be requested per request")); + } + + return Optional.empty(); + } + + @Override + public void onIncomingMessage( + final String protocolId, + final Eth2Peer peer, + final ExecutionPayloadEnvelopesByRootRequestMessage message, + final ResponseCallback callback) { + LOG.trace( + "Peer {} requested {} ExecutionPayloadEnvelopes with roots: {}", + peer.getId(), + message.size(), + message); + + // EIP7732 TODO: implement + } + + // EIP7732 TODO: implement + private UInt64 getMaxRequestPayloads() { + return UInt64.ZERO; + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java index f755cb0314e..88b404c688e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; public interface ForkDigestPayloadContext { @@ -50,6 +51,24 @@ public SszSchema getSchemaFromSchemaDefinitions( } }; + ForkDigestPayloadContext EXECUTION_PAYLOAD_ENVELOPE = + new ForkDigestPayloadContext<>() { + // EIP7732 TODO: not sure here + @Override + public UInt64 getSlotFromPayload(final SignedExecutionPayloadEnvelope responsePayload) { + return UInt64.ZERO; + } + + @Override + public SszSchema getSchemaFromSchemaDefinitions( + final SchemaDefinitions schemaDefinitions) { + return schemaDefinitions + .toVersionEip7732() + .orElseThrow() + .getSignedExecutionPayloadEnvelopeSchema(); + } + }; + UInt64 getSlotFromPayload(final TPayload responsePayload); SszSchema getSchemaFromSchemaDefinitions(final SchemaDefinitions schemaDefinitions); diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index ba78ef8dec8..bc0b02cff44 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -54,6 +54,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsBellatrix; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsCapella; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsDeneb; +import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsEip7732; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; @@ -86,8 +87,11 @@ import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -142,6 +146,9 @@ public class Eth2P2PNetworkBuilder { protected OperationProcessor voluntaryExitProcessor; protected OperationProcessor signedContributionAndProofProcessor; protected OperationProcessor syncCommitteeMessageProcessor; + protected OperationProcessor executionPayloadProcessor; + protected OperationProcessor payloadAttestationProcessor; + protected OperationProcessor executionPayloadHeaderProcessor; protected OperationProcessor signedBlsToExecutionChangeProcessor; protected ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; protected VerifiedBlockAttestationsSubscriptionProvider @@ -449,6 +456,28 @@ private GossipForkSubscriptions createSubscriptions( syncCommitteeMessageProcessor, signedBlsToExecutionChangeProcessor, debugDataDumper); + case EIP7732 -> new GossipForkSubscriptionsEip7732( + forkAndSpecMilestone.getFork(), + spec, + asyncRunner, + metricsSystem, + network, + recentChainData, + gossipEncoding, + gossipedBlockProcessor, + gossipedBlobSidecarProcessor, + gossipedAttestationProcessor, + gossipedAggregateProcessor, + attesterSlashingProcessor, + proposerSlashingProcessor, + voluntaryExitProcessor, + signedContributionAndProofProcessor, + syncCommitteeMessageProcessor, + signedBlsToExecutionChangeProcessor, + executionPayloadProcessor, + payloadAttestationProcessor, + executionPayloadHeaderProcessor, + debugDataDumper); }; } @@ -670,6 +699,29 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } + public Eth2P2PNetworkBuilder gossipedExecutionPayload( + final OperationProcessor + gossipedExecutionPayloadProcessor) { + checkNotNull(gossipedExecutionPayloadProcessor); + this.executionPayloadProcessor = gossipedExecutionPayloadProcessor; + return this; + } + + public Eth2P2PNetworkBuilder gossipedPayloadAttestationProcessor( + final OperationProcessor gossipedPayloadAttestationProcessor) { + checkNotNull(gossipedPayloadAttestationProcessor); + this.payloadAttestationProcessor = gossipedPayloadAttestationProcessor; + return this; + } + + public Eth2P2PNetworkBuilder gossipedExecutionPayloadHeaderProcessor( + final OperationProcessor + gossipedExecutionPayloadHeaderProcessor) { + checkNotNull(gossipedExecutionPayloadHeaderProcessor); + this.executionPayloadHeaderProcessor = gossipedExecutionPayloadHeaderProcessor; + return this; + } + public Eth2P2PNetworkBuilder processedAttestationSubscriptionProvider( final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider) { checkNotNull(processedAttestationSubscriptionProvider); diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java index d160d854c14..31de0116c25 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java @@ -52,6 +52,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.RpcRequest; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; @@ -257,6 +258,13 @@ public SafeFuture requestBlobSidecarsByRoot( return createPendingBlobSidecarRequest(handler); } + @Override + public SafeFuture requestExecutionPayloadEnvelopesByRoot( + final List blockRoots, + final RpcResponseListener listener) { + throw new UnsupportedOperationException("EIP7732 TODO"); + } + @Override public SafeFuture> requestBlockBySlot(final UInt64 slot) { final PendingRequestHandler, SignedBeaconBlock> handler = @@ -284,6 +292,12 @@ public SafeFuture> requestBlobSidecarByRoot( return createPendingBlobSidecarRequest(handler); } + @Override + public SafeFuture> requestExecutionPayloadEnvelopeByRoot( + final Bytes32 blockRoot) { + throw new UnsupportedOperationException("EIP7732 TODO"); + } + private SafeFuture createPendingBlockRequest( final PendingRequestHandler handler) { final PendingRequestHandler filteredHandler = diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index b50b90f9877..fc35c7bb871 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -104,6 +104,7 @@ import tech.pegasys.teku.spec.datastructures.interop.GenesisStateBuilder; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; @@ -122,6 +123,8 @@ import tech.pegasys.teku.statetransition.SimpleOperationPool; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationManager; +import tech.pegasys.teku.statetransition.attestation.PayloadAttestationManager; +import tech.pegasys.teku.statetransition.attestation.PayloadAttestationPool; import tech.pegasys.teku.statetransition.blobs.BlobSidecarManager; import tech.pegasys.teku.statetransition.blobs.BlobSidecarManager.RemoteOrigin; import tech.pegasys.teku.statetransition.blobs.BlobSidecarManagerImpl; @@ -133,6 +136,8 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.statetransition.execution.ExecutionPayloadHeaderPool; +import tech.pegasys.teku.statetransition.execution.ExecutionPayloadManager; import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifier; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifierImpl; @@ -161,8 +166,11 @@ import tech.pegasys.teku.statetransition.validation.BlobSidecarGossipValidator; import tech.pegasys.teku.statetransition.validation.BlockGossipValidator; import tech.pegasys.teku.statetransition.validation.BlockValidator; +import tech.pegasys.teku.statetransition.validation.ExecutionPayloadHeaderValidator; +import tech.pegasys.teku.statetransition.validation.ExecutionPayloadValidator; import tech.pegasys.teku.statetransition.validation.GossipValidationHelper; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; +import tech.pegasys.teku.statetransition.validation.PayloadAttestationValidator; import tech.pegasys.teku.statetransition.validation.ProposerSlashingValidator; import tech.pegasys.teku.statetransition.validation.SignedBlsToExecutionChangeValidator; import tech.pegasys.teku.statetransition.validation.VoluntaryExitValidator; @@ -249,6 +257,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile Eth2P2PNetwork p2pNetwork; protected volatile Optional beaconRestAPI = Optional.empty(); protected volatile AggregatingAttestationPool attestationPool; + protected volatile PayloadAttestationPool payloadAttestationPool; protected volatile DepositProvider depositProvider; protected volatile SyncService syncService; protected volatile AttestationManager attestationManager; @@ -262,6 +271,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile MappedOperationPool blsToExecutionChangePool; protected volatile SyncCommitteeContributionPool syncCommitteeContributionPool; protected volatile SyncCommitteeMessagePool syncCommitteeMessagePool; + protected volatile ExecutionPayloadHeaderPool executionPayloadHeaderPool; protected volatile WeakSubjectivityValidator weakSubjectivityValidator; protected volatile PerformanceTracker performanceTracker; protected volatile PendingPool pendingBlocks; @@ -276,6 +286,8 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile GossipValidationHelper gossipValidationHelper; protected volatile KZG kzg; protected volatile BlobSidecarManager blobSidecarManager; + protected volatile ExecutionPayloadManager executionPayloadManager; + protected volatile PayloadAttestationManager payloadAttestationManager; protected volatile Optional terminalPowBlockMonitor = Optional.empty(); protected volatile ProposersDataManager proposersDataManager; protected volatile KeyValueStore keyValueStore; @@ -507,15 +519,19 @@ public void initAll() { initCombinedChainDataClient(); initSignatureVerificationService(); initAttestationPool(); + initPayloadAttestationPool(); initAttesterSlashingPool(); initProposerSlashingPool(); initVoluntaryExitPool(); initSignedBlsToExecutionChangePool(); + initExecutionPayloadHeaderPool(); initEth1DataCache(); initDepositProvider(); initGenesisHandler(); initAttestationManager(); initBlockManager(); + initExecutionPayloadManager(); + initPayloadAttestationManager(); initSyncCommitteePools(); initP2PNetwork(); initSyncService(); @@ -735,6 +751,15 @@ protected void initSignedBlsToExecutionChangePool() { blsToExecutionChangePool::removeAll); } + protected void initExecutionPayloadHeaderPool() { + LOG.debug("BeaconChainController.initExecutionPayloadHeaderPool()"); + + final ExecutionPayloadHeaderValidator validator = + new ExecutionPayloadHeaderValidator(spec, gossipValidationHelper, recentChainData); + + executionPayloadHeaderPool = new ExecutionPayloadHeaderPool(validator, Optional.empty()); + } + protected void initDataProvider() { dataProvider = DataProvider.builder() @@ -961,6 +986,7 @@ public void initValidatorApiHandler() { blobSidecarGossipChannel, attestationPool, attestationManager, + payloadAttestationManager, attestationTopicSubscriber, activeValidatorTracker, DutyMetrics.create(metricsSystem, timeProvider, recentChainData, spec), @@ -971,7 +997,8 @@ public void initValidatorApiHandler() { syncCommitteeMessagePool, syncCommitteeContributionPool, syncCommitteeSubscriptionManager, - blockProductionPerformanceFactory); + blockProductionPerformanceFactory, + receivedBlockEventsChannelPublisher); eventChannels .subscribe(SlotEventsChannel.class, activeValidatorTracker) .subscribe(ExecutionClientEventsChannel.class, executionClientVersionProvider) @@ -1100,7 +1127,11 @@ protected void initP2PNetwork() { .config(beaconConfig.p2pConfig()) .eventChannels(eventChannels) .combinedChainDataClient(combinedChainDataClient) - .gossipedBlockProcessor(blockManager::validateAndImportBlock) + .gossipedBlockProcessor( + (block, arrivalTimestamp) -> { + receivedBlockEventsChannelPublisher.onBlockSeen(block); + return blockManager.validateAndImportBlock(block, arrivalTimestamp); + }) .gossipedBlobSidecarProcessor(blobSidecarManager::validateAndPrepareForBlockImport) .gossipedAttestationProcessor(attestationManager::addAttestation) .gossipedAggregateProcessor(attestationManager::addAggregate) @@ -1110,6 +1141,9 @@ protected void initP2PNetwork() { .gossipedSignedContributionAndProofProcessor(syncCommitteeContributionPool::addRemote) .gossipedSyncCommitteeMessageProcessor(syncCommitteeMessagePool::addRemote) .gossipedSignedBlsToExecutionChangeProcessor(blsToExecutionChangePool::addRemote) + .gossipedExecutionPayload(executionPayloadManager::validateAndImportExecutionPayload) + .gossipedPayloadAttestationProcessor(payloadAttestationManager::addPayloadAttestation) + .gossipedExecutionPayloadHeaderProcessor(executionPayloadHeaderPool::addRemote) .processedAttestationSubscriptionProvider( attestationManager::subscribeToAttestationsToSend) .metricsSystem(metricsSystem) @@ -1164,6 +1198,11 @@ public void initAttestationPool() { attestationPool::onAttestationsIncludedInBlock); } + public void initPayloadAttestationPool() { + LOG.debug("BeaconChainController.initPayloadAttestationPool()"); + payloadAttestationPool = new PayloadAttestationPool(spec, metricsSystem); + } + public void initRestAPI() { LOG.debug("BeaconChainController.initRestAPI()"); if (!beaconConfig.beaconRestApiConfig().isRestApiEnabled()) { @@ -1265,6 +1304,40 @@ protected SyncServiceFactory createSyncServiceFactory() { spec); } + public void initExecutionPayloadManager() { + LOG.debug("BeaconChainController.initExecutionPayloadManager()"); + final ExecutionPayloadValidator executionPayloadValidator = + new ExecutionPayloadValidator(spec, gossipValidationHelper, recentChainData); + executionPayloadManager = + new ExecutionPayloadManager( + executionPayloadValidator, forkChoice, recentChainData, executionLayer); + eventChannels.subscribe(ReceivedBlockEventsChannel.class, executionPayloadValidator); + } + + public void initPayloadAttestationManager() { + LOG.debug("BeaconChainController.initPayloadAttestationManager()"); + final PayloadAttestationValidator payloadAttestationValidator = + new PayloadAttestationValidator(spec, recentChainData); + final PendingPool pendingAttestations = + poolFactory.createPendingPoolForPayloadAttestations(spec); + final FutureItems futureAttestations = + FutureItems.create( + payloadAttestationMessage -> payloadAttestationMessage.getData().getSlot(), + UInt64.valueOf(3), + futureItemsMetric, + "payload_attestations"); + payloadAttestationManager = + new PayloadAttestationManager( + payloadAttestationValidator, + forkChoice, + payloadAttestationPool, + pendingAttestations, + futureAttestations); + eventChannels + .subscribe(SlotEventsChannel.class, payloadAttestationManager) + .subscribe(ReceivedBlockEventsChannel.class, payloadAttestationManager); + } + public void initSyncService() { LOG.debug("BeaconChainController.initSyncService()"); syncService = createSyncServiceFactory().create(eventChannels); diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/SlotProcessor.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/SlotProcessor.java index 752ddba91fb..303d5c8d7ac 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/SlotProcessor.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/SlotProcessor.java @@ -16,6 +16,7 @@ import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT_EIP7732; import com.google.common.annotations.VisibleForTesting; import java.util.Optional; @@ -26,6 +27,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blocks.NodeSlot; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.statetransition.EpochCachePrimer; @@ -180,13 +182,13 @@ boolean isSlotStartDue(final UInt64 calculatedSlot) { return isProcessingDueForSlot(calculatedSlot, onTickSlotStart); } - // Attestations are due 1/3 of the way through the slots time period + // Attestations are due 1/3 (1/4 in Eip7732) of the way through the slots time period boolean isSlotAttestationDue( final UInt64 calculatedSlot, final UInt64 currentTimeMillis, final UInt64 nodeSlotStartTimeMillis) { final UInt64 earliestTimeInMillis = - nodeSlotStartTimeMillis.plus(oneThirdSlotMillis(calculatedSlot)); + nodeSlotStartTimeMillis.plus(attestationDueMillis(calculatedSlot)); final boolean processingDueForSlot = isProcessingDueForSlot(calculatedSlot, onTickSlotAttestation); return processingDueForSlot && isTimeReached(currentTimeMillis, earliestTimeInMillis); @@ -204,14 +206,17 @@ boolean isEpochPrecalculationDue( final UInt64 nextEpochStartTimeMillis = spec.getSlotStartTimeMillis(firstSlotOfNextEpoch, genesisTimeMillis); final UInt64 earliestTimeInMillis = - nextEpochStartTimeMillis.minusMinZero(oneThirdSlotMillis(firstSlotOfNextEpoch)); + nextEpochStartTimeMillis.minusMinZero(attestationDueMillis(firstSlotOfNextEpoch)); final boolean processingDueForSlot = isProcessingDueForSlot(firstSlotOfNextEpoch, onTickEpochPrecompute); final boolean timeReached = isTimeReached(currentTimeMillis, earliestTimeInMillis); return processingDueForSlot && timeReached; } - private UInt64 oneThirdSlotMillis(final UInt64 slot) { + private UInt64 attestationDueMillis(final UInt64 slot) { + if (spec.atSlot(slot).getMilestone().isGreaterThanOrEqualTo(SpecMilestone.EIP7732)) { + return spec.getMillisPerSlot(slot).dividedBy(INTERVALS_PER_SLOT_EIP7732); + } return spec.getMillisPerSlot(slot).dividedBy(INTERVALS_PER_SLOT); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java index 7836c60745c..46b3d4b2809 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java @@ -52,6 +52,7 @@ import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; import tech.pegasys.teku.storage.api.StorageQueryChannel; +import tech.pegasys.teku.storage.client.PayloadTimelinessProvider.PayloadStatusAndBeaconBlockRoot; import tech.pegasys.teku.storage.store.UpdatableStore; public class CombinedChainDataClient { @@ -823,4 +824,9 @@ private boolean isOptimistic( public SafeFuture> getInitialAnchor() { return historicalChainData.getAnchor(); } + + public SafeFuture> getTimelyExecutionPayload( + final UInt64 slot) { + return recentChainData.getTimelyExecutionPayload(slot); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/PayloadTimelinessProvider.java b/storage/src/main/java/tech/pegasys/teku/storage/client/PayloadTimelinessProvider.java new file mode 100644 index 00000000000..713d584e1f9 --- /dev/null +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/PayloadTimelinessProvider.java @@ -0,0 +1,116 @@ +/* + * 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.storage.client; + +import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT_EIP7732; + +import java.util.Map; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedMap; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.constants.PayloadStatus; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadEnvelope; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; + +public class PayloadTimelinessProvider { + private final RecentChainData recentChainData; + private final Spec spec; + private final Map payloadByBeaconBlockRoot; + + public PayloadTimelinessProvider(final RecentChainData recentChainData, final Spec spec) { + this.recentChainData = recentChainData; + this.spec = spec; + this.payloadByBeaconBlockRoot = LimitedMap.createSynchronizedNatural(3); + } + + public void onPayload( + final SignedExecutionPayloadEnvelope payload, final UInt64 arrivalTimeMillis) { + payloadByBeaconBlockRoot.put( + payload.getMessage().getBeaconBlockRoot(), + new PayloadAndArrivalTime(payload.getMessage(), arrivalTimeMillis)); + } + + public SafeFuture> getTimelyExecutionPayload( + final UInt64 slot) { + + return getBeaconBlockRootAtSlot(slot) + .thenApply( + maybeBlockRoot -> { + if (maybeBlockRoot.isEmpty()) { + return Optional.empty(); + } + + return Optional.of( + maybeBlockRoot + .flatMap( + beaconBlockRoot -> + Optional.ofNullable(payloadByBeaconBlockRoot.get(beaconBlockRoot))) + .filter( + payloadAndArrivalTime -> + payloadAndArrivalTime.arrivalTimeMillis.isLessThanOrEqualTo( + getMillisCutoff(slot))) + .map( + payloadAndArrivalTime -> + new PayloadStatusAndBeaconBlockRoot( + payloadAndArrivalTime.payload.isPayloadWithheld() + ? PayloadStatus.PAYLOAD_WITHHELD + : PayloadStatus.PAYLOAD_PRESENT, + payloadAndArrivalTime.payload.getBeaconBlockRoot())) + .orElse( + new PayloadStatusAndBeaconBlockRoot( + PayloadStatus.PAYLOAD_ABSENT, Bytes32.ZERO))); + }); + } + + private SafeFuture> getBeaconBlockRootAtSlot(final UInt64 slot) { + final Optional maybeBlockRootInEffect = + recentChainData + .getChainHead() + .flatMap(head -> recentChainData.getBlockRootInEffectBySlot(slot, head.getRoot())); + + if (maybeBlockRootInEffect.isEmpty()) { + return SafeFuture.completedFuture(Optional.empty()); + } + + return recentChainData + .retrieveSignedBlockByRoot(maybeBlockRootInEffect.get()) + .thenApply( + maybeBlock -> + maybeBlock + .filter(block -> block.getSlot().equals(slot)) + .map(SignedBeaconBlock::getRoot)); + } + + private UInt64 getMillisCutoff(final UInt64 slot) { + final UInt64 slotStart = + spec.getSlotStartTimeMillis(slot, recentChainData.getGenesisTimeMillis()); + final UInt64 payloadAttestationDueOffset = + secondsToMillis(UInt64.valueOf(spec.getSecondsPerSlot(slot))) + .times(3) + .dividedBy(INTERVALS_PER_SLOT_EIP7732); + + return slotStart.plus(payloadAttestationDueOffset); + } + + private record PayloadAndArrivalTime( + ExecutionPayloadEnvelope payload, UInt64 arrivalTimeMillis) {} + + public record PayloadStatusAndBeaconBlockRoot( + PayloadStatus payloadStatus, Bytes32 beaconBlockRoot) {} +} diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java b/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java index 4b274001d9a..74a50503911 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java @@ -47,6 +47,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; +import tech.pegasys.teku.spec.datastructures.execution.SignedExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.forkchoice.ProtoNodeData; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy; import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyStore; @@ -64,6 +65,7 @@ import tech.pegasys.teku.storage.api.ReorgContext; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; +import tech.pegasys.teku.storage.client.PayloadTimelinessProvider.PayloadStatusAndBeaconBlockRoot; import tech.pegasys.teku.storage.protoarray.ForkChoiceStrategy; import tech.pegasys.teku.storage.store.EmptyStoreResults; import tech.pegasys.teku.storage.store.StoreBuilder; @@ -105,6 +107,7 @@ public abstract class RecentChainData implements StoreUpdateHandler { private final SingleBlobSidecarProvider validatedBlobSidecarProvider; private final LateBlockReorgLogic lateBlockReorgLogic; + private final PayloadTimelinessProvider payloadTimelinessProvider; private final ValidatorIsConnectedProvider validatorIsConnectedProvider; @@ -136,6 +139,7 @@ public abstract class RecentChainData implements StoreUpdateHandler { this.storageUpdateChannel = storageUpdateChannel; this.finalizedCheckpointChannel = finalizedCheckpointChannel; this.lateBlockReorgLogic = new LateBlockReorgLogic(spec, this); + this.payloadTimelinessProvider = new PayloadTimelinessProvider(this, spec); reorgCounter = metricsSystem.createCounter( TekuMetricCategory.BEACON, @@ -666,4 +670,14 @@ public void setBlockTimelinessFromArrivalTime( public void setBlockTimelinessIfEmpty(final SignedBeaconBlock block) { lateBlockReorgLogic.setBlockTimelinessFromArrivalTime(block, store.getTimeInMillis()); } + + public void onExecutionPayload( + final SignedExecutionPayloadEnvelope payload, final UInt64 arrivalTimeMillis) { + payloadTimelinessProvider.onPayload(payload, arrivalTimeMillis); + } + + public SafeFuture> getTimelyExecutionPayload( + final UInt64 slot) { + return payloadTimelinessProvider.getTimelyExecutionPayload(slot); + } } diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java index 01113b0212f..64fcbe90647 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java @@ -158,6 +158,14 @@ public class Eth2NetworkOptions { arity = "1") private UInt64 electraForkEpoch; + @Option( + names = {"--Xnetwork-eip7732-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the eip7732 fork activation epoch.", + arity = "1") + private UInt64 eip7732ForkEpoch; + @Option( names = {"--Xnetwork-total-terminal-difficulty-override"}, hidden = true, @@ -331,6 +339,9 @@ private void configureEth2Network(final Eth2NetworkConfiguration.Builder builder if (electraForkEpoch != null) { builder.electraForkEpoch(electraForkEpoch); } + if (eip7732ForkEpoch != null) { + builder.eip7732ForkEpoch(eip7732ForkEpoch); + } if (totalTerminalDifficultyOverride != null) { builder.totalTerminalDifficultyOverride(totalTerminalDifficultyOverride); } diff --git a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java index 6aa0e2ee340..7f5e6eedc1b 100644 --- a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java +++ b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; @@ -37,10 +38,12 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -197,6 +200,24 @@ public SafeFuture>> getValidatorsLivenes getSyncCommitteeSelectionProof(final List requests) { return SafeFuture.completedFuture(Optional.of(requests)); } + + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + return SafeFuture.completedFuture(Optional.empty()); + } + + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + return SafeFuture.completedFuture(Optional.empty()); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return SafeFuture.completedFuture(List.of()); + } }; int UNKNOWN_VALIDATOR_ID = -1; @@ -211,6 +232,9 @@ SafeFuture>> getValidatorStatuses( SafeFuture> getAttestationDuties( UInt64 epoch, IntCollection validatorIndices); + SafeFuture> getPayloadAttestationDuties( + UInt64 epoch, IntCollection validatorIndices); + SafeFuture> getSyncCommitteeDuties( UInt64 epoch, IntCollection validatorIndices); @@ -231,6 +255,11 @@ SafeFuture> createUnsignedBlock( SafeFuture> createAttestationData(UInt64 slot, int committeeIndex); + SafeFuture> createPayloadAttestationData(UInt64 slot); + + SafeFuture> sendSignedPayloadAttestations( + List attestations); + SafeFuture> createAggregate( UInt64 slot, Bytes32 attestationHashTreeRoot, Optional committeeIndex); diff --git a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorTimingChannel.java b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorTimingChannel.java index 2e1d713c173..47cb4e7f1ed 100644 --- a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorTimingChannel.java +++ b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorTimingChannel.java @@ -41,6 +41,8 @@ void onHeadUpdate( void onAttestationAggregationDue(UInt64 slot); + void onPayloadAttestationDue(UInt64 slot); + void onAttesterSlashing(AttesterSlashing attesterSlashing); void onProposerSlashing(ProposerSlashing proposerSlashing); diff --git a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/TimeBasedEventAdapter.java b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/TimeBasedEventAdapter.java index 7a19eaa71b3..3ffa835b1a9 100644 --- a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/TimeBasedEventAdapter.java +++ b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/TimeBasedEventAdapter.java @@ -16,14 +16,17 @@ import static tech.pegasys.teku.infrastructure.time.TimeUtilities.millisToSeconds; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT; +import static tech.pegasys.teku.spec.constants.NetworkConstants.INTERVALS_PER_SLOT_EIP7732; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.async.timed.RepeatingTaskScheduler; +import tech.pegasys.teku.infrastructure.async.timed.RepeatingTaskScheduler.RepeatingTask; import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.validator.api.ValidatorTimingChannel; public class TimeBasedEventAdapter implements BeaconChainEventAdapter { @@ -65,13 +68,115 @@ void start(final UInt64 genesisTime) { final UInt64 nextSlotStartTimeMillis = secondsToMillis(nextSlotStartTime); final UInt64 millisPerSlot = secondsToMillis(secondsPerSlot); - final UInt64 oneThirdSlot = millisPerSlot.dividedBy(INTERVALS_PER_SLOT); - final UInt64 twoThirdsSlot = millisPerSlot.times(2).dividedBy(INTERVALS_PER_SLOT); + final SpecMilestone currentMilestone = spec.atSlot(currentSlot).getMilestone(); + + if (currentMilestone.equals(SpecMilestone.EIP7732)) { + final UInt64 attestationDueSlotTimeOffset = + millisPerSlot.dividedBy(INTERVALS_PER_SLOT_EIP7732); + final UInt64 aggregationDueSlotTimeOffset = + millisPerSlot.times(2).dividedBy(INTERVALS_PER_SLOT_EIP7732); + final UInt64 timelinessAttestationDueSlotTimeOffset = + millisPerSlot.times(3).dividedBy(INTERVALS_PER_SLOT_EIP7732); + + scheduleDuty( + nextSlotStartTimeMillis, + millisPerSlot, + attestationDueSlotTimeOffset, + this::onAttestationCreationDue); + scheduleDuty( + nextSlotStartTimeMillis, + millisPerSlot, + aggregationDueSlotTimeOffset, + this::onAggregationDue); + scheduleDuty( + nextSlotStartTimeMillis, + millisPerSlot, + timelinessAttestationDueSlotTimeOffset, + this::onPayloadTimelinessAttestationDue); + return; + } + + if (spec.getForkSchedule() + .getHighestSupportedMilestone() + .isGreaterThanOrEqualTo(SpecMilestone.EIP7732)) { + final UInt64 eip7732StartTimeMillis = + secondsToMillis( + spec.getSlotStartTime( + spec.computeStartSlotAtEpoch( + spec.getForkSchedule().getFork(SpecMilestone.EIP7732).getEpoch()), + genesisTime)); + + final UInt64 attestationDueSlotTimeOffset = millisPerSlot.dividedBy(INTERVALS_PER_SLOT); + final UInt64 aggregationDueSlotTimeOffset = + millisPerSlot.times(2).dividedBy(INTERVALS_PER_SLOT); + + final UInt64 newAttestationDueSlotTimeOffset = + millisPerSlot.dividedBy(INTERVALS_PER_SLOT_EIP7732); + final UInt64 newAggregationDueSlotTimeOffset = + millisPerSlot.times(2).dividedBy(INTERVALS_PER_SLOT_EIP7732); + final UInt64 timelinessAttestationDueSlotTimeOffset = + millisPerSlot.times(3).dividedBy(INTERVALS_PER_SLOT_EIP7732); + + taskScheduler.scheduleRepeatingEventInMillis( + nextSlotStartTimeMillis.plus(attestationDueSlotTimeOffset), + millisPerSlot, + this::onAttestationCreationDue, + eip7732StartTimeMillis, + (__, ___) -> + scheduleDuty( + millisPerSlot, newAttestationDueSlotTimeOffset, this::onAttestationCreationDue)); + taskScheduler.scheduleRepeatingEventInMillis( + nextSlotStartTimeMillis.plus(aggregationDueSlotTimeOffset), + millisPerSlot, + this::onAggregationDue, + eip7732StartTimeMillis, + (__, ___) -> { + scheduleDuty(millisPerSlot, newAggregationDueSlotTimeOffset, this::onAggregationDue); + scheduleDuty( + millisPerSlot, + timelinessAttestationDueSlotTimeOffset, + this::onPayloadTimelinessAttestationDue); + }); + return; + } + + // no EIP7732 support + + final UInt64 attestationDueSlotTimeOffset = millisPerSlot.dividedBy(INTERVALS_PER_SLOT); + final UInt64 aggregationDueSlotTimeOffset = + millisPerSlot.times(2).dividedBy(INTERVALS_PER_SLOT); + + scheduleDuty( + nextSlotStartTimeMillis, + millisPerSlot, + attestationDueSlotTimeOffset, + this::onAttestationCreationDue); + scheduleDuty( + nextSlotStartTimeMillis, + millisPerSlot, + aggregationDueSlotTimeOffset, + this::onAggregationDue); + } + + private void scheduleDuty( + final UInt64 period, final UInt64 offset, final RepeatingTask dutyTask) { + scheduleDuty(getNextSlotStartMillis(), period, offset, dutyTask); + } + + private void scheduleDuty( + final UInt64 nextSlotStartTimeMillis, + final UInt64 period, + final UInt64 offset, + final RepeatingTask dutyTask) { taskScheduler.scheduleRepeatingEventInMillis( - nextSlotStartTimeMillis.plus(oneThirdSlot), millisPerSlot, this::onAttestationCreationDue); - taskScheduler.scheduleRepeatingEventInMillis( - nextSlotStartTimeMillis.plus(twoThirdsSlot), millisPerSlot, this::onAggregationDue); + nextSlotStartTimeMillis.plus(offset), period, dutyTask); + } + + private UInt64 getNextSlotStartMillis() { + final UInt64 currentSlot = getCurrentSlot(); + final UInt64 nextSlotStartTime = spec.getSlotStartTime(currentSlot.plus(1), genesisTime); + return secondsToMillis(nextSlotStartTime); } private UInt64 getCurrentSlot() { @@ -89,6 +194,7 @@ private void onStartSlot(final UInt64 scheduledTime, final UInt64 actualTime) { "Skipping block creation for slot {} due to unexpected delay in slot processing", slot); return; } + LOG.info("EVENT *** onStartSlot"); validatorTimingChannel.onSlot(slot); validatorTimingChannel.onBlockProductionDue(slot); } @@ -100,6 +206,7 @@ private void onAttestationCreationDue( LOG.warn("Skipping attestation for slot {} due to unexpected delay in slot processing", slot); return; } + LOG.info("EVENT *** onAttestationCreationDue"); validatorTimingChannel.onAttestationCreationDue(slot); } @@ -110,9 +217,23 @@ private void onAggregationDue( LOG.warn("Skipping aggregation for slot {} due to unexpected delay in slot processing", slot); return; } + LOG.info("EVENT *** onAggregationDue"); validatorTimingChannel.onAttestationAggregationDue(slot); } + private void onPayloadTimelinessAttestationDue( + final UInt64 scheduledTimeInMillis, final UInt64 actualTimeInMillis) { + final UInt64 slot = getCurrentSlotForMillis(scheduledTimeInMillis); + if (isTooLateInMillis(scheduledTimeInMillis, actualTimeInMillis)) { + LOG.warn( + "Skipping timeliness attestation for slot {} due to unexpected delay in slot processing", + slot); + return; + } + LOG.info("EVENT *** onPayloadTimelinessAttestationDue"); + validatorTimingChannel.onPayloadAttestationDue(slot); + } + private UInt64 getCurrentSlotForMillis(final UInt64 millis) { return spec.getCurrentSlot(millisToSeconds(millis), genesisTime); } diff --git a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/BeaconNodeRequestLabels.java b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/BeaconNodeRequestLabels.java index 9e9253e5ac3..5d18b7b4c5c 100644 --- a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/BeaconNodeRequestLabels.java +++ b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/BeaconNodeRequestLabels.java @@ -42,4 +42,8 @@ public class BeaconNodeRequestLabels { public static final String GET_VALIDATORS_LIVENESS = "get_validators_liveness"; public static final String BEACON_COMMITTEE_SELECTIONS = "beacon_committee_selections"; public static final String SYNC_COMMITTEE_SELECTIONS = "sync_committee_selections"; + public static final String GET_PAYLOAD_ATTESTATION_DUTIES_METHOD = + "get_payload_attestation_duties"; + public static final String CREATE_PAYLOAD_ATTESTATION_METHOD = "create_payload_attestation"; + public static final String PUBLISH_PAYLOAD_ATTESTATION_METHOD = "publish_payload_attestation"; } diff --git a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java index b29110d09ec..d68e987e83f 100644 --- a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java +++ b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java @@ -35,6 +35,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; @@ -46,10 +47,12 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -281,6 +284,30 @@ public SafeFuture>> getSyncCommitteeS BeaconNodeRequestLabels.SYNC_COMMITTEE_SELECTIONS); } + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + return countDataRequest( + delegate.getPayloadAttestationDuties(epoch, validatorIndices), + BeaconNodeRequestLabels.GET_PAYLOAD_ATTESTATION_DUTIES_METHOD); + } + + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + return countDataRequest( + delegate.createPayloadAttestationData(slot), + BeaconNodeRequestLabels.CREATE_PAYLOAD_ATTESTATION_METHOD); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return countDataRequest( + delegate.sendSignedPayloadAttestations(attestations), + BeaconNodeRequestLabels.PUBLISH_PAYLOAD_ATTESTATION_METHOD); + } + private SafeFuture countDataRequest( final SafeFuture request, final String requestName) { return request diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/AbstractDutyScheduler.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/AbstractDutyScheduler.java index 8dc4ff72e6d..d266f152ae0 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/AbstractDutyScheduler.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/AbstractDutyScheduler.java @@ -190,4 +190,7 @@ public void onAttestationAggregationDue(final UInt64 slot) { notifyEpochDuties(PendingDuties::onAggregationDue, slot); } + + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} } diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/BeaconProposerPreparer.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/BeaconProposerPreparer.java index 2aba20e4c83..3a46e4c5647 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/BeaconProposerPreparer.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/BeaconProposerPreparer.java @@ -101,6 +101,9 @@ public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} @Override public void onProposerSlashing(final ProposerSlashing proposerSlashing) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onUpdatedValidatorStatuses( final Map newValidatorStatuses, diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/OwnedValidatorStatusProvider.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/OwnedValidatorStatusProvider.java index b99e8815a06..d6ed46a4d14 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/OwnedValidatorStatusProvider.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/OwnedValidatorStatusProvider.java @@ -123,6 +123,9 @@ public void onAttestationCreationDue(final UInt64 slot) {} @Override public void onAttestationAggregationDue(final UInt64 slot) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyLoader.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyLoader.java new file mode 100644 index 00000000000..76088613405 --- /dev/null +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyLoader.java @@ -0,0 +1,78 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.client; + +import it.unimi.dsi.fastutil.ints.IntCollection; +import java.util.Optional; +import java.util.function.Function; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuty; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.validator.api.ValidatorApiChannel; +import tech.pegasys.teku.validator.client.duties.Duty; +import tech.pegasys.teku.validator.client.duties.SlotBasedScheduledDuties; +import tech.pegasys.teku.validator.client.duties.attestations.PayloadAttestationDuty; +import tech.pegasys.teku.validator.client.loader.OwnedValidators; + +public class PayloadAttestationDutyLoader + extends AbstractDutyLoader> { + + private final ValidatorApiChannel validatorApiChannel; + private final Function> + scheduledDutiesFactory; + + public PayloadAttestationDutyLoader( + final ValidatorApiChannel validatorApiChannel, + final Function> + scheduledDutiesFactory, + final OwnedValidators validators, + final ValidatorIndexProvider validatorIndexProvider) { + super(validators, validatorIndexProvider); + this.validatorApiChannel = validatorApiChannel; + this.scheduledDutiesFactory = scheduledDutiesFactory; + } + + @Override + protected SafeFuture> requestDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + if (validatorIndices.isEmpty()) { + return SafeFuture.completedFuture(Optional.empty()); + } + return validatorApiChannel.getPayloadAttestationDuties(epoch, validatorIndices); + } + + @Override + protected SafeFuture> scheduleAllDuties( + final UInt64 epoch, final PayloadAttesterDuties duties) { + final SlotBasedScheduledDuties scheduledDuties = + scheduledDutiesFactory.apply(duties.dependentRoot()); + + duties.duties().forEach(duty -> scheduleDuty(scheduledDuties, duty)); + + return SafeFuture.completedFuture(scheduledDuties); + } + + private void scheduleDuty( + final SlotBasedScheduledDuties scheduledDuties, + final PayloadAttesterDuty duty) { + validators + .getValidator(duty.publicKey()) + .ifPresent( + validator -> + scheduledDuties.scheduleProductionOrAdd( + duty.slot(), validator, d -> d.addValidator(validator, duty.validatorIndex()))); + } +} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyScheduler.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyScheduler.java new file mode 100644 index 00000000000..d6eeed72f56 --- /dev/null +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/PayloadAttestationDutyScheduler.java @@ -0,0 +1,86 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.client; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; +import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; + +public class PayloadAttestationDutyScheduler extends AbstractDutyScheduler { + private static final Logger LOG = LogManager.getLogger(); + static final int LOOKAHEAD_EPOCHS = 1; + + public PayloadAttestationDutyScheduler( + final MetricsSystem metricsSystem, final DutyLoader dutyLoader, final Spec spec) { + super(metricsSystem, "payload_attestation", dutyLoader, LOOKAHEAD_EPOCHS, spec); + + metricsSystem.createIntegerGauge( + TekuMetricCategory.VALIDATOR, + "scheduled_payload_attestation_duties_current", + "Current number of pending payload attestation duties that have been scheduled", + () -> dutiesByEpoch.values().stream().mapToInt(PendingDuties::countDuties).sum()); + } + + @Override + public void onPayloadAttestationDue(final UInt64 slot) { + onProductionDue(slot); + } + + @Override + public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} + + @Override + public void onProposerSlashing(final ProposerSlashing proposerSlashing) {} + + @Override + public void onUpdatedValidatorStatuses( + final Map newValidatorStatuses, + final boolean possibleMissingEvents) {} + + @Override + protected Bytes32 getExpectedDependentRoot( + final Bytes32 headBlockRoot, + final Bytes32 previousDutyDependentRoot, + final Bytes32 currentDutyDependentRoot, + final UInt64 headEpoch, + final UInt64 dutyEpoch) { + checkArgument( + dutyEpoch.isGreaterThanOrEqualTo(headEpoch), + "Attempting to calculate dependent root for duty epoch %s that is before the updated head epoch %s", + dutyEpoch, + headEpoch); + if (headEpoch.equals(dutyEpoch)) { + LOG.debug("headEpoch {} - returning previousDutyDependentRoot", () -> headEpoch); + return previousDutyDependentRoot; + } else if (headEpoch.increment().equals(dutyEpoch)) { + LOG.debug("dutyEpoch (next epoch) {} - returning currentDutyDependentRoot", () -> dutyEpoch); + return currentDutyDependentRoot; + } else { + LOG.debug( + "headBlockRoot returned - dutyEpoch {}, headEpoch {}", () -> dutyEpoch, () -> headEpoch); + return headBlockRoot; + } + } +} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/SyncCommitteeScheduler.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/SyncCommitteeScheduler.java index fdd64d43001..3f513644791 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/SyncCommitteeScheduler.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/SyncCommitteeScheduler.java @@ -195,6 +195,9 @@ public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} @Override public void onProposerSlashing(final ProposerSlashing proposerSlashing) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onUpdatedValidatorStatuses( final Map newValidatorStatuses, diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorClientService.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorClientService.java index 2916633f3b1..40af91c8ab6 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorClientService.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorClientService.java @@ -62,6 +62,7 @@ import tech.pegasys.teku.validator.client.duties.SlotBasedScheduledDuties; import tech.pegasys.teku.validator.client.duties.ValidatorDutyMetrics; import tech.pegasys.teku.validator.client.duties.attestations.AttestationDutyFactory; +import tech.pegasys.teku.validator.client.duties.attestations.PayloadAttestationDutyFactory; import tech.pegasys.teku.validator.client.duties.synccommittee.ChainHeadTracker; import tech.pegasys.teku.validator.client.duties.synccommittee.SyncCommitteeScheduledDuties; import tech.pegasys.teku.validator.client.loader.HttpClientExternalSignerFactory; @@ -467,6 +468,9 @@ private void scheduleValidatorsDuties( validatorDutyMetrics); final AttestationDutyFactory attestationDutyFactory = new AttestationDutyFactory(spec, forkProvider, validatorApiChannel, validatorDutyMetrics); + final PayloadAttestationDutyFactory payloadAttestationDutyFactory = + new PayloadAttestationDutyFactory( + spec, forkProvider, validatorApiChannel, validatorDutyMetrics); final BeaconCommitteeSubscriptions beaconCommitteeSubscriptions = new BeaconCommitteeSubscriptions(validatorApiChannel); final boolean dvtSelectionsEndpointEnabled = @@ -499,9 +503,24 @@ private void scheduleValidatorsDuties( validatorDutyMetrics::performDutyWithMetrics), validators, validatorIndexProvider)); + + final DutyLoader payloadDutyLoader = + new RetryingDutyLoader<>( + asyncRunner, + new PayloadAttestationDutyLoader( + validatorApiChannel, + dependentRoot -> + new SlotBasedScheduledDuties<>( + payloadAttestationDutyFactory, + dependentRoot, + validatorDutyMetrics::performDutyWithMetrics), + validators, + validatorIndexProvider)); validatorTimingChannels.add(new BlockDutyScheduler(metricsSystem, blockDutyLoader, spec)); validatorTimingChannels.add( new AttestationDutyScheduler(metricsSystem, attestationDutyLoader, spec)); + validatorTimingChannels.add( + new PayloadAttestationDutyScheduler(metricsSystem, payloadDutyLoader, spec)); validatorTimingChannels.add(validatorLoader.getSlashingProtectionLogger()); if (spec.isMilestoneSupported(SpecMilestone.ALTAIR)) { diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorRegistrator.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorRegistrator.java index 9c8feac6da8..bd522c66729 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorRegistrator.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorRegistrator.java @@ -128,6 +128,9 @@ public void onAttestationCreationDue(final UInt64 slot) {} @Override public void onAttestationAggregationDue(final UInt64 slot) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorTimingActions.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorTimingActions.java index 085842a2c72..0ce38996490 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorTimingActions.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/ValidatorTimingActions.java @@ -108,6 +108,11 @@ public void onAttestationAggregationDue(final UInt64 slot) { delegates.forEach(delegates -> delegates.onAttestationAggregationDue(slot)); } + @Override + public void onPayloadAttestationDue(final UInt64 slot) { + delegates.forEach(delegates -> delegates.onPayloadAttestationDue(slot)); + } + @Override public void onAttesterSlashing(final AttesterSlashing attesterSlashing) { delegates.forEach(delegates -> delegates.onAttesterSlashing(attesterSlashing)); diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/SlotBasedScheduledDuties.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/SlotBasedScheduledDuties.java index d7086b1c388..004fd37677c 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/SlotBasedScheduledDuties.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/SlotBasedScheduledDuties.java @@ -51,6 +51,11 @@ public synchronized void scheduleProduction(final UInt64 slot, final Validator v scheduleProduction(slot, validator, duty -> null); } + public synchronized void scheduleProductionOrAdd( + final UInt64 slot, final Validator validator, final Consumer

addToDuty) { + scheduleProduction(slot, validator, duty -> addToDuty); + } + public synchronized T scheduleProduction( final UInt64 slot, final Validator validator, final Function addToDuty) { final P duty = diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDuty.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDuty.java new file mode 100644 index 00000000000..86f599b7967 --- /dev/null +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDuty.java @@ -0,0 +1,185 @@ +/* + * 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.validator.client.duties.attestations; + +import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricsSteps.CREATE_TOTAL; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.metrics.Validator.DutyType; +import tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricsSteps; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessageSchema; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.validator.api.ValidatorApiChannel; +import tech.pegasys.teku.validator.client.ForkProvider; +import tech.pegasys.teku.validator.client.Validator; +import tech.pegasys.teku.validator.client.duties.Duty; +import tech.pegasys.teku.validator.client.duties.DutyResult; +import tech.pegasys.teku.validator.client.duties.ProductionResult; +import tech.pegasys.teku.validator.client.duties.ValidatorDutyMetrics; + +public class PayloadAttestationDuty implements Duty { + private static final Logger LOG = LogManager.getLogger(); + + private final Spec spec; + private final UInt64 slot; + private final ForkProvider forkProvider; + private final ValidatorApiChannel validatorApiChannel; + private final SendingStrategy sendingStrategy; + private final ValidatorDutyMetrics validatorDutyMetrics; + + private final List validators = new ArrayList<>(); + + public PayloadAttestationDuty( + final Spec spec, + final UInt64 slot, + final ForkProvider forkProvider, + final ValidatorApiChannel validatorApiChannel, + final SendingStrategy sendingStrategy, + final ValidatorDutyMetrics validatorDutyMetrics) { + this.spec = spec; + this.slot = slot; + this.forkProvider = forkProvider; + this.validatorApiChannel = validatorApiChannel; + this.sendingStrategy = sendingStrategy; + this.validatorDutyMetrics = validatorDutyMetrics; + } + + public void addValidator(final Validator validator, final int index) { + validators.add(new ValidatorWithIndex(validator, index)); + } + + @Override + public DutyType getType() { + return DutyType.PAYLOAD_ATTESTATION_PRODUCTION; + } + + @Override + public SafeFuture performDuty() { + LOG.info("Creating payload attestation at slot {}", slot); + + if (validators.isEmpty()) { + return SafeFuture.completedFuture(DutyResult.NO_OP); + } + + return forkProvider + .getForkInfo(slot) + .thenCompose( + forkInfo -> sendingStrategy.send(produceAllAttestations(slot, forkInfo, validators))); + } + + private Stream>> produceAllAttestations( + final UInt64 slot, + final ForkInfo forkInfo, + final List validatorsWithIndex) { + + final SafeFuture> unsignedAttestationFuture = + validatorDutyMetrics.record( + () -> validatorApiChannel.createPayloadAttestationData(slot), this, CREATE_TOTAL); + + return validatorsWithIndex.stream() + .map( + validatorWithIndex -> + signAttestationForValidator( + slot, forkInfo, validatorWithIndex, unsignedAttestationFuture)); + } + + private SafeFuture> signAttestationForValidator( + final UInt64 slot, + final ForkInfo forkInfo, + final ValidatorWithIndex validatorWithIndex, + final SafeFuture> attestationDataFuture) { + return attestationDataFuture + .thenCompose( + maybeUnsignedAttestation -> + maybeUnsignedAttestation + .map( + attestationData -> { + validateAttestationData(slot, attestationData); + return validatorDutyMetrics.record( + () -> + signAttestationForValidator( + forkInfo, attestationData, validatorWithIndex), + this, + ValidatorDutyMetricsSteps.SIGN); + }) + .orElseGet( + () -> + SafeFuture.completedFuture( + ProductionResult.failure( + validatorWithIndex.validator.getPublicKey(), + new IllegalStateException( + "Unable to produce payload attestation for slot " + + slot + + " because chain data was unavailable"))))) + .exceptionally( + error -> ProductionResult.failure(validatorWithIndex.validator.getPublicKey(), error)); + } + + private static void validateAttestationData( + final UInt64 slot, final PayloadAttestationData attestationData) { + checkArgument( + attestationData.getSlot().equals(slot), + "Unsigned payload attestation slot (%s) does not match expected slot %s", + attestationData.getSlot(), + slot); + } + + private SafeFuture> signAttestationForValidator( + final ForkInfo forkInfo, + final PayloadAttestationData attestationData, + final ValidatorWithIndex validatorWithIndex) { + return validatorWithIndex + .validator + .getSigner() + .signPayloadAttestationData(attestationData, forkInfo) + .thenApply( + signature -> + createSignedPayloadAttestation(attestationData, validatorWithIndex, signature)) + .thenApply( + attestation -> + ProductionResult.success( + validatorWithIndex.validator.getPublicKey(), + attestationData.getBeaconBlockRoot(), + attestation)); + } + + private PayloadAttestationMessage createSignedPayloadAttestation( + final PayloadAttestationData attestationData, + final ValidatorWithIndex validatorWithIndex, + final BLSSignature signature) { + final PayloadAttestationMessageSchema payloadAttestationMessageSchema = + spec.atSlot(attestationData.getSlot()) + .getSchemaDefinitions() + .toVersionEip7732() + .orElseThrow() + .getPayloadAttestationMessageSchema(); + + return payloadAttestationMessageSchema.create( + UInt64.valueOf(validatorWithIndex.index), attestationData, signature); + } + + private record ValidatorWithIndex(Validator validator, int index) {} +} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDutyFactory.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDutyFactory.java new file mode 100644 index 00000000000..523f5f70aec --- /dev/null +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/attestations/PayloadAttestationDutyFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.client.duties.attestations; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.validator.api.ValidatorApiChannel; +import tech.pegasys.teku.validator.client.ForkProvider; +import tech.pegasys.teku.validator.client.Validator; +import tech.pegasys.teku.validator.client.duties.Duty; +import tech.pegasys.teku.validator.client.duties.DutyFactory; +import tech.pegasys.teku.validator.client.duties.ValidatorDutyMetrics; + +public class PayloadAttestationDutyFactory implements DutyFactory { + + private final Spec spec; + private final ForkProvider forkProvider; + private final ValidatorApiChannel validatorApiChannel; + + private final ValidatorDutyMetrics validatorDutyMetrics; + + public PayloadAttestationDutyFactory( + final Spec spec, + final ForkProvider forkProvider, + final ValidatorApiChannel validatorApiChannel, + final ValidatorDutyMetrics validatorDutyMetrics) { + this.spec = spec; + this.forkProvider = forkProvider; + this.validatorApiChannel = validatorApiChannel; + this.validatorDutyMetrics = validatorDutyMetrics; + } + + @Override + public PayloadAttestationDuty createProductionDuty(final UInt64 slot, final Validator validator) { + return new PayloadAttestationDuty( + spec, + slot, + forkProvider, + validatorApiChannel, + new BatchAttestationSendingStrategy<>(validatorApiChannel::sendSignedPayloadAttestations), + validatorDutyMetrics); + } + + @Override + public AggregationDuty createAggregationDuty(final UInt64 slot, final Validator validator) { + throw new UnsupportedOperationException("Aggregation not supported for payload attestations"); + } + + @Override + public String getProductionType() { + return "payload_attestation"; + } + + @Override + public String getAggregationType() { + // not used + return ""; + } +} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/synccommittee/ChainHeadTracker.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/synccommittee/ChainHeadTracker.java index 93f80da3d02..2dd91445482 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/synccommittee/ChainHeadTracker.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/synccommittee/ChainHeadTracker.java @@ -69,6 +69,9 @@ public void onAttestationCreationDue(final UInt64 slot) {} @Override public void onAttestationAggregationDue(final UInt64 slot) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/loader/SlashingProtectionLogger.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/loader/SlashingProtectionLogger.java index 6556631cf2d..3e2442f8a9a 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/loader/SlashingProtectionLogger.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/loader/SlashingProtectionLogger.java @@ -188,6 +188,9 @@ public void onAttestationCreationDue(final UInt64 slot) {} @Override public void onAttestationAggregationDue(final UInt64 slot) {} + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onValidatorsAdded() {} diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/signer/ExternalSigner.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/signer/ExternalSigner.java index b0334dee62b..b1f46e95f39 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/signer/ExternalSigner.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/signer/ExternalSigner.java @@ -48,6 +48,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.VoluntaryExit; @@ -139,6 +140,12 @@ public SafeFuture signAttestationData( slashableAttestationMessage(attestationData)); } + @Override + public SafeFuture signPayloadAttestationData( + final PayloadAttestationData payloadAttestationData, final ForkInfo forkInfo) { + return SafeFuture.failedFuture(new UnsupportedOperationException("Not Yet Implemented")); + } + private void recordMetrics(final BLSSignature result, final Throwable error) { if (error != null) { if (Throwables.getRootCause(error) instanceof HttpTimeoutException) { diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/BeaconNodeReadinessManager.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/BeaconNodeReadinessManager.java index 124a982e3f5..6475e708160 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/BeaconNodeReadinessManager.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/BeaconNodeReadinessManager.java @@ -131,6 +131,9 @@ public void onAttestationAggregationDue(final UInt64 slot) { performReadinessCheckAgainstAllNodes().ifExceptionGetsHereRaiseABug(); } + @Override + public void onPayloadAttestationDue(final UInt64 slot) {} + @Override public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {} diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java index 087b6bc8d1f..76d01ee38c4 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java @@ -36,6 +36,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; @@ -48,10 +49,12 @@ import tech.pegasys.teku.spec.datastructures.blocks.BlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -328,6 +331,31 @@ public SafeFuture>> getSyncCommitteeS BeaconNodeRequestLabels.SYNC_COMMITTEE_SELECTIONS); } + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + return tryRequestUntilSuccess( + apiChannel -> apiChannel.getPayloadAttestationDuties(epoch, validatorIndices), + BeaconNodeRequestLabels.GET_PAYLOAD_ATTESTATION_DUTIES_METHOD); + } + + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + return tryRequestUntilSuccess( + apiChannel -> apiChannel.createPayloadAttestationData(slot), + BeaconNodeRequestLabels.CREATE_PAYLOAD_ATTESTATION_METHOD); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return relayRequest( + apiChannel -> apiChannel.sendSignedPayloadAttestations(attestations), + BeaconNodeRequestLabels.PUBLISH_PAYLOAD_ATTESTATION_METHOD, + failoversPublishSignedDuties); + } + private SafeFuture relayRequest( final ValidatorApiChannelRequest request, final String method) { return relayRequest(request, method, true); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java index 5a6f4010f5e..621d3502ce5 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java @@ -44,6 +44,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; @@ -57,10 +58,12 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -385,6 +388,24 @@ public SafeFuture>> getSyncCommitteeS return sendRequest(() -> typeDefClient.getSyncCommitteeSelectionProof(requests)); } + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + return SafeFuture.failedFuture(new UnsupportedOperationException("not implemented")); + } + + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + return SafeFuture.failedFuture(new UnsupportedOperationException("not implemented")); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return SafeFuture.failedFuture(new UnsupportedOperationException("not implemented")); + } + private List responseToValidatorsLivenessResult( final PostValidatorLivenessResponse response) { return response.data.stream() diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java index c2d54cb7cd2..3654a89dab5 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.ethereum.json.types.node.PeerCount; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.json.types.validator.PayloadAttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; @@ -36,10 +37,12 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.execution.PayloadAttestationData; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationMessage; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; @@ -241,4 +244,24 @@ public SafeFuture>> getSyncCommitteeS final List requests) { return dutiesProviderChannel.getSyncCommitteeSelectionProof(requests); } + + @Override + public SafeFuture> getPayloadAttestationDuties( + final UInt64 epoch, final IntCollection validatorIndices) { + return dutiesProviderChannel.getPayloadAttestationDuties(epoch, validatorIndices); + } + + @Override + public SafeFuture> createPayloadAttestationData( + final UInt64 slot) { + return dutiesProviderChannel.createPayloadAttestationData(slot); + } + + @Override + public SafeFuture> sendSignedPayloadAttestations( + final List attestations) { + return attestationPublisherChannel + .orElse(dutiesProviderChannel) + .sendSignedPayloadAttestations(attestations); + } }