diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/altair/fork/TransitionTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/altair/fork/TransitionTestExecutor.java index ff0c63f0657..471e88b2730 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/altair/fork/TransitionTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/altair/fork/TransitionTestExecutor.java @@ -30,8 +30,8 @@ import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.executionengine.StubExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public class TransitionTestExecutor implements TestExecutor { @@ -79,7 +79,7 @@ private void processAltairUpgrade(final TestDefinition testDefinition, final Met metadata.blsSetting == 2 ? BLSSignatureVerifier.NO_OP : BLSSignatureVerifier.SIMPLE; result = spec.processBlock( - result, block, signatureVerifier, new StubExecutionEngineChannel(spec)); + result, block, signatureVerifier, OptimisticExecutionPayloadExecutor.NOOP); } catch (final StateTransitionException e) { Assertions.fail( "Failed to process block " + i + " at slot " + block.getSlot() + ": " + e.getMessage(), diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java index 0cfb4fef275..cabd366caf2 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java @@ -25,8 +25,8 @@ import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public class DefaultOperationProcessor implements OperationProcessor { private final Spec spec; @@ -104,9 +104,9 @@ public void processSyncCommittee(final MutableBeaconState state, final SyncAggre public void processExecutionPayload( final MutableBeaconState state, final ExecutionPayload executionPayload, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { spec.getBlockProcessor(state.getSlot()) - .processExecutionPayload(state, executionPayload, executionEngine); + .processExecutionPayload(state, executionPayload, payloadExecutor); } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java index 9b406dcd940..61cceae3c5f 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java @@ -22,8 +22,8 @@ import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public interface OperationProcessor { @@ -50,6 +50,6 @@ void processSyncCommittee(MutableBeaconState state, SyncAggregate aggregate) void processExecutionPayload( MutableBeaconState state, ExecutionPayload executionPayload, - ExecutionEngineChannel executionEngine) + OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException; } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java index 516ccef00f6..61ff82dce70 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java @@ -15,17 +15,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static tech.pegasys.teku.reference.TestDataUtils.loadSsz; import static tech.pegasys.teku.reference.TestDataUtils.loadStateFromSsz; import static tech.pegasys.teku.reference.TestDataUtils.loadYaml; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; -import java.util.Optional; import tech.pegasys.teku.ethtests.finder.TestDefinition; -import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.reference.TestExecutor; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.BeaconBlockBodySchemaAltair; @@ -38,9 +34,6 @@ import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutePayloadResult; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; -import tech.pegasys.teku.spec.executionengine.ExecutionPayloadStatus; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; import tech.pegasys.teku.ssz.SszData; @@ -200,7 +193,6 @@ private void processOperation( processor.processSyncCommittee(state, syncAggregate); break; case EXECUTION_PAYLOAD: - final ExecutionEngineChannel executionEngine = mock(ExecutionEngineChannel.class); final ExecutionMeta executionMeta = loadYaml(testDefinition, "execution.yaml", ExecutionMeta.class); final ExecutionPayload payload = @@ -213,16 +205,8 @@ private void processOperation( .toVersionMerge() .orElseThrow() .getExecutionPayloadSchema()); - when(executionEngine.executePayload(payload)) - .thenReturn( - SafeFuture.completedFuture( - new ExecutePayloadResult( - executionMeta.executionValid - ? ExecutionPayloadStatus.VALID - : ExecutionPayloadStatus.INVALID, - Optional.empty(), - Optional.empty()))); - processor.processExecutionPayload(state, payload, executionEngine); + processor.processExecutionPayload( + state, payload, payloadToExecute -> executionMeta.executionValid); break; } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/sanity/SanityBlocksTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/sanity/SanityBlocksTestExecutor.java index 606a56f3ec1..f47e30d1d01 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/sanity/SanityBlocksTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/sanity/SanityBlocksTestExecutor.java @@ -32,8 +32,8 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.executionengine.StubExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public class SanityBlocksTestExecutor implements TestExecutor { @@ -95,7 +95,7 @@ private BeaconState applyBlocks( metaData.getBlsSetting() == IGNORED ? BLSSignatureVerifier.NO_OP : BLSSignatureVerifier.SIMPLE, - new StubExecutionEngineChannel(spec)); + OptimisticExecutionPayloadExecutor.NOOP); } return result; } catch (StateTransitionException e) { 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 49ffd6eafb1..a136c727a2d 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 @@ -56,7 +56,6 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateInvariants; import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; import tech.pegasys.teku.spec.datastructures.util.ForkAndSpecMilestone; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.genesis.GenesisGenerator; import tech.pegasys.teku.spec.logic.StateTransition; import tech.pegasys.teku.spec.logic.common.block.BlockProcessor; @@ -70,6 +69,7 @@ import tech.pegasys.teku.spec.logic.common.util.AsyncBLSSignatureVerifier; import tech.pegasys.teku.spec.logic.common.util.BeaconStateUtil; import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.ssz.collections.SszBitlist; import tech.pegasys.teku.ssz.type.Bytes4; @@ -463,10 +463,10 @@ public BlockImportResult onBlock( final SignedBeaconBlock signedBlock, final BeaconState blockSlotState, final IndexedAttestationCache indexedAttestationCache, - final ExecutionEngineChannel executionEngine) { + final OptimisticExecutionPayloadExecutor payloadExecutor) { return atBlock(signedBlock) .getForkChoiceUtil() - .onBlock(store, signedBlock, blockSlotState, indexedAttestationCache, executionEngine); + .onBlock(store, signedBlock, blockSlotState, indexedAttestationCache, payloadExecutor); } public boolean blockDescendsFromLatestFinalizedBlock( @@ -507,7 +507,7 @@ public BeaconState processBlock( final BeaconState preState, final SignedBeaconBlock block, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws StateTransitionException { try { final BeaconState blockSlotState = stateTransition.processSlots(preState, block.getSlot()); @@ -517,7 +517,7 @@ public BeaconState processBlock( blockSlotState, IndexedAttestationCache.NOOP, signatureVerifier, - executionEngine); + payloadExecutor); } catch (SlotProcessingException | EpochProcessingException e) { throw new StateTransitionException(e); } @@ -533,7 +533,7 @@ public BeaconState replayValidatedBlock(final BeaconState preState, final Signed block.getMessage(), IndexedAttestationCache.NOOP, BLSSignatureVerifier.NO_OP, - ExecutionEngineChannel.NOOP); + OptimisticExecutionPayloadExecutor.NOOP); } catch (SlotProcessingException | EpochProcessingException | BlockProcessingException e) { throw new StateTransitionException(e); } 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 777668d0f63..cd89c7b0d2a 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 @@ -19,6 +19,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.BeaconBlockBodyAltair; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.merge.BeaconBlockBodyMerge; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -44,6 +45,10 @@ public interface BeaconBlockBody extends SszContainer { SszList getVoluntaryExits(); + default Optional getOptionalExecutionPayload() { + return Optional.empty(); + } + @Override BeaconBlockBodySchema getSchema(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/merge/BeaconBlockBodyMerge.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/merge/BeaconBlockBodyMerge.java index 5dd4052a314..415af8dade1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/merge/BeaconBlockBodyMerge.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/merge/BeaconBlockBodyMerge.java @@ -33,6 +33,11 @@ static BeaconBlockBodyMerge required(final BeaconBlockBody body) { ExecutionPayload getExecutionPayload(); + @Override + default Optional getOptionalExecutionPayload() { + return Optional.of(getExecutionPayload()); + } + @Override BeaconBlockBodySchemaMerge getSchema(); 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 2bcf1054ed4..4ffbf3b9f09 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 @@ -60,7 +60,6 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; 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; @@ -75,6 +74,7 @@ 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.ValidatorsUtil; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.ssz.SszList; import tech.pegasys.teku.ssz.type.Bytes4; @@ -127,7 +127,7 @@ public BeaconState processAndValidateBlock( final SignedBeaconBlock signedBlock, final BeaconState blockSlotState, final IndexedAttestationCache indexedAttestationCache, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws StateTransitionException { final BatchSignatureVerifier signatureVerifier = new BatchSignatureVerifier(); final BeaconState result = @@ -136,7 +136,7 @@ public BeaconState processAndValidateBlock( blockSlotState, indexedAttestationCache, signatureVerifier, - executionEngine); + payloadExecutor); if (!signatureVerifier.batchVerify()) { throw new StateTransitionException( "Batch signature verification failed for block " @@ -151,7 +151,7 @@ public BeaconState processAndValidateBlock( final BeaconState blockSlotState, final IndexedAttestationCache indexedAttestationCache, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws StateTransitionException { try { // Process_block @@ -161,7 +161,7 @@ public BeaconState processAndValidateBlock( signedBlock.getMessage(), indexedAttestationCache, signatureVerifier, - executionEngine); + payloadExecutor); BlockValidationResult blockValidationResult = validateBlock( @@ -294,12 +294,12 @@ public BeaconState processUnsignedBlock( final BeaconBlock block, final IndexedAttestationCache indexedAttestationCache, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { return preState.updated( state -> processBlock( - state, block, indexedAttestationCache, signatureVerifier, executionEngine)); + state, block, indexedAttestationCache, signatureVerifier, payloadExecutor)); } protected void processBlock( @@ -307,7 +307,7 @@ protected void processBlock( final BeaconBlock block, final IndexedAttestationCache indexedAttestationCache, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { processBlockHeader(state, block); processRandaoNoValidation(state, block.getBody()); 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 023d26181fb..940d84b7a22 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 @@ -32,10 +32,10 @@ import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.operations.validation.OperationInvalidReason; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.ssz.SszList; public interface BlockProcessor { @@ -46,7 +46,7 @@ BeaconState processAndValidateBlock( SignedBeaconBlock signedBlock, BeaconState blockSlotState, IndexedAttestationCache indexedAttestationCache, - ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws StateTransitionException; /** @@ -57,7 +57,7 @@ BeaconState processAndValidateBlock( * already be advanced to the block's slot * @param indexedAttestationCache A cache of indexed attestations * @param signatureVerifier The signature verifier to use - * @param executionEngine The execution engine to verify payloads via + * @param payloadExecutor the optimistic payload executor to begin execution with * @return The post state after processing the block on top of {@code blockSlotState} * @throws StateTransitionException If the block is invalid or cannot be processed */ @@ -66,7 +66,7 @@ BeaconState processAndValidateBlock( BeaconState blockSlotState, IndexedAttestationCache indexedAttestationCache, BLSSignatureVerifier signatureVerifier, - ExecutionEngineChannel executionEngine) + OptimisticExecutionPayloadExecutor payloadExecutor) throws StateTransitionException; BeaconState processUnsignedBlock( @@ -74,7 +74,7 @@ BeaconState processUnsignedBlock( BeaconBlock block, IndexedAttestationCache indexedAttestationCache, BLSSignatureVerifier signatureVerifier, - ExecutionEngineChannel executionEngine) + OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException; void processBlockHeader(MutableBeaconState state, BeaconBlockSummary blockHeader) @@ -121,6 +121,6 @@ void processSyncAggregate( void processExecutionPayload( MutableBeaconState state, ExecutionPayload executionPayload, - ExecutionEngineChannel executionEngine) + OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/BlockProposalUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/BlockProposalUtil.java index 5c99f017be3..55e4856d4cd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/BlockProposalUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/BlockProposalUtil.java @@ -25,10 +25,10 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.block.BlockProcessor; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; public class BlockProposalUtil { @@ -80,7 +80,7 @@ public BeaconBlockAndState createNewUnsignedBlock( newBlock, IndexedAttestationCache.NOOP, BLSSignatureVerifier.NO_OP, - ExecutionEngineChannel.NOOP); + OptimisticExecutionPayloadExecutor.NOOP); Bytes32 stateRoot = newState.hashTreeRoot(); BeaconBlock newCompleteBlock = newBlock.withStateRoot(stateRoot); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java index 7143566aa6f..f0770267bc4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java @@ -36,12 +36,12 @@ import tech.pegasys.teku.spec.datastructures.state.Fork; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.block.BlockProcessor; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public class ForkChoiceUtil { @@ -317,7 +317,7 @@ public BlockImportResult onBlock( final SignedBeaconBlock signedBlock, final BeaconState blockSlotState, final IndexedAttestationCache indexedAttestationCache, - final ExecutionEngineChannel executionEngine) { + final OptimisticExecutionPayloadExecutor payloadExecutor) { checkArgument( blockSlotState.getSlot().equals(signedBlock.getSlot()), "State must have slots processed up to the block slot"); @@ -337,7 +337,7 @@ public BlockImportResult onBlock( try { state = blockProcessor.processAndValidateBlock( - signedBlock, blockSlotState, indexedAttestationCache, executionEngine); + signedBlock, blockSlotState, indexedAttestationCache, payloadExecutor); } catch (StateTransitionException e) { return BlockImportResult.failedStateTransition(e); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/block/BlockProcessorAltair.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/block/BlockProcessorAltair.java index 89bd9e24014..f090e405186 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/block/BlockProcessorAltair.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/block/BlockProcessorAltair.java @@ -38,7 +38,6 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.MutableBeaconStateAltair; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.block.AbstractBlockProcessor; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; @@ -50,6 +49,7 @@ import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; import tech.pegasys.teku.spec.logic.versions.altair.helpers.BeaconStateAccessorsAltair; import tech.pegasys.teku.spec.logic.versions.altair.helpers.MiscHelpersAltair; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.ssz.SszMutableList; import tech.pegasys.teku.ssz.SszVector; import tech.pegasys.teku.ssz.collections.SszUInt64List; @@ -95,12 +95,12 @@ public void processBlock( final BeaconBlock block, final IndexedAttestationCache indexedAttestationCache, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { final MutableBeaconStateAltair state = MutableBeaconStateAltair.required(genericState); final BeaconBlockBodyAltair blockBody = BeaconBlockBodyAltair.required(block.getBody()); - super.processBlock(state, block, indexedAttestationCache, signatureVerifier, executionEngine); + super.processBlock(state, block, indexedAttestationCache, signatureVerifier, payloadExecutor); processSyncAggregate(state, blockBody.getSyncAggregate(), signatureVerifier); } @@ -249,7 +249,7 @@ public void processSyncAggregate( public void processExecutionPayload( final MutableBeaconState state, final ExecutionPayload executionPayload, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { throw new UnsupportedOperationException("No ExecutionPayload in Altair"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/BlockProcessorMerge.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/BlockProcessorMerge.java index 6f7a87d75ef..3f0c89d7d96 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/BlockProcessorMerge.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/BlockProcessorMerge.java @@ -22,9 +22,6 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.merge.MutableBeaconStateMerge; -import tech.pegasys.teku.spec.executionengine.ExecutePayloadResult; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; -import tech.pegasys.teku.spec.executionengine.ExecutionPayloadStatus; 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; @@ -76,13 +73,13 @@ public void processBlock( final BeaconBlock block, final IndexedAttestationCache indexedAttestationCache, final BLSSignatureVerifier signatureVerifier, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { final MutableBeaconStateMerge state = MutableBeaconStateMerge.required(genericState); final BeaconBlockBodyMerge blockBody = BeaconBlockBodyMerge.required(block.getBody()); processBlockHeader(state, block); if (miscHelpersMerge.isExecutionEnabled(genericState, block)) { - processExecutionPayload(state, blockBody.getExecutionPayload(), executionEngine); + processExecutionPayload(state, blockBody.getExecutionPayload(), payloadExecutor); } processRandaoNoValidation(state, block.getBody()); processEth1Data(state, block.getBody()); @@ -94,7 +91,7 @@ public void processBlock( public void processExecutionPayload( final MutableBeaconState genericState, final ExecutionPayload payload, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { final MutableBeaconStateMerge state = MutableBeaconStateMerge.required(genericState); if (miscHelpersMerge.isMergeComplete(state)) { @@ -117,10 +114,9 @@ public void processExecutionPayload( "Execution payload timestamp does not match time for state slot"); } - final ExecutePayloadResult payloadResult = executionEngine.executePayload(payload).join(); - if (payloadResult.getStatus() != ExecutionPayloadStatus.VALID) { - throw new BlockProcessingException( - "Execution payload was not valid: " + payloadResult.getMessage()); + final boolean optimisticallyAccept = payloadExecutor.optimisticallyExecute(payload); + if (!optimisticallyAccept) { + throw new BlockProcessingException("Execution payload was not optimistically accepted"); } final ExecutionPayloadHeaderSchema executionPayloadHeaderSchema = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/OptimisticExecutionPayloadExecutor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/OptimisticExecutionPayloadExecutor.java new file mode 100644 index 00000000000..7cdefa8bfd0 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/merge/block/OptimisticExecutionPayloadExecutor.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 ConsenSys AG. + * + * 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.merge.block; + +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; + +public interface OptimisticExecutionPayloadExecutor { + + OptimisticExecutionPayloadExecutor NOOP = payload -> true; + + /** + * At least begins execution of the specified payload, which may complete asynchronously. Note + * that a {@code true} value does NOT indicate the payload is valid only that it is not + * immediately found to be invalid and can be optimistically accepted. + * + * @param executionPayload the payload to execute + * @return true if the payload should be optimistically accepted or false to * immediately + * invalidate the payload + */ + boolean optimisticallyExecute(final ExecutionPayload executionPayload); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/block/BlockProcessorPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/block/BlockProcessorPhase0.java index e010370734f..cf3ba3cbf61 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/block/BlockProcessorPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/block/BlockProcessorPhase0.java @@ -23,7 +23,6 @@ import tech.pegasys.teku.spec.datastructures.state.PendingAttestation; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.MutableBeaconStatePhase0; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.block.AbstractBlockProcessor; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; @@ -35,6 +34,7 @@ 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.ValidatorsUtil; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; public final class BlockProcessorPhase0 extends AbstractBlockProcessor { @@ -97,7 +97,7 @@ public void processSyncAggregate( public void processExecutionPayload( final MutableBeaconState state, final ExecutionPayload executionPayload, - final ExecutionEngineChannel executionEngine) + final OptimisticExecutionPayloadExecutor payloadExecutor) throws BlockProcessingException { throw new UnsupportedOperationException("No ExecutionPayload in phase0"); } 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 9930ee80545..088866f32e5 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 @@ -20,6 +20,8 @@ import com.google.common.base.Throwables; import java.util.List; import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; @@ -183,6 +185,10 @@ private SafeFuture onBlock( "State must have processed slots up to the block slot. Block slot %s, state slot %s", block.getSlot(), blockSlotState.get().getSlot()); + + final ForkChoicePayloadExecutor payloadExecutor = + new ForkChoicePayloadExecutor(recentChainData, forkChoiceExecutor, block, executionEngine); + return onForkChoiceThread( () -> { final ForkChoiceStrategy forkChoiceStrategy = getForkChoiceStrategy(); @@ -191,14 +197,13 @@ private SafeFuture onBlock( IndexedAttestationCache.capturing(); addParentStateRoots(blockSlotState.get(), transaction); - final BlockImportResult result = spec.onBlock( transaction, block, blockSlotState.get(), indexedAttestationCache, - executionEngine); + payloadExecutor); if (!result.isSuccessful()) { if (result.getFailureReason() != FailureReason.BLOCK_IS_FROM_FUTURE) { @@ -210,21 +215,29 @@ private SafeFuture onBlock( result.getFailureReason().name(), result.getFailureCause()); } - return result; + return SafeFuture.completedFuture(result); } - // Note: not using thenRun here because we want to ensure each step is on the event thread + // Note: not using thenRun here because we want to ensure each step is on the event + // thread transaction.commit().join(); - updateForkChoiceForImportedBlock(block, blockSlotState.get(), result, forkChoiceStrategy); + + proposerWeightings.onBlockReceived(block, blockSlotState.get(), forkChoiceStrategy); + final UInt64 currentEpoch = spec.computeEpochAtSlot(spec.getCurrentSlot(transaction)); // We only need to apply attestations from the current or previous epoch - // If the block is from before that, none of the attestations will be applicable so just + // If the block is from before that, none of the attestations will be applicable so + // just // skip the whole step. if (spec.computeEpochAtSlot(block.getSlot()) .isGreaterThanOrEqualTo(currentEpoch.minusMinZero(1))) { applyVotesFromBlock(forkChoiceStrategy, currentEpoch, indexedAttestationCache); } - return result; + + // Do the combine while still on the fork choice thread unless we really do have to + // wait for the payload execution to complete, so call this here rather than in + // .thenCompose below even though it means having to unwrap the SafeFuture + return payloadExecutor.combine(result); }); } @@ -250,48 +263,6 @@ private boolean validateBlockAttestation( .isSuccessful(); } - private void updateForkChoiceForImportedBlock( - final SignedBeaconBlock block, - final BeaconState blockSlotState, - final BlockImportResult result, - final ForkChoiceStrategy forkChoiceStrategy) { - if (result.isSuccessful()) { - proposerWeightings.onBlockReceived(block, blockSlotState, forkChoiceStrategy); - - final SlotAndBlockRoot bestHeadBlock = findNewChainHead(block, forkChoiceStrategy); - if (!bestHeadBlock.getBlockRoot().equals(recentChainData.getBestBlockRoot().orElseThrow())) { - recentChainData.updateHead(bestHeadBlock.getBlockRoot(), bestHeadBlock.getSlot()); - if (bestHeadBlock.getBlockRoot().equals(block.getRoot())) { - result.markAsCanonical(); - } - } - } - } - - private SlotAndBlockRoot findNewChainHead( - final SignedBeaconBlock block, final ForkChoiceStrategy forkChoiceStrategy) { - // If the new block builds on our current chain head it must be the new chain head. - // Since fork choice works by walking down the tree selecting the child block with - // the greatest weight, when a block has only one child it will automatically become - // a better choice than the block itself. So the first block we receive that is a - // child of our current chain head, must be the new chain head. If we'd had any other - // child of the current chain head we'd have already selected it as head. - if (recentChainData - .getChainHead() - .map(currentHead -> currentHead.getRoot().equals(block.getParentRoot())) - .orElse(false)) { - return new SlotAndBlockRoot(block.getSlot(), block.getRoot()); - } - - // Otherwise, use fork choice to find the new chain head as if this block is on time the - // proposer weighting may cause us to reorg. - // During sync, this may be noticeably slower than just comparing the chain head due to the way - // ProtoArray skips updating all ancestors when adding a new block but it's cheap when in sync. - final Checkpoint justifiedCheckpoint = recentChainData.getJustifiedCheckpoint().orElseThrow(); - final Checkpoint finalizedCheckpoint = recentChainData.getFinalizedCheckpoint().orElseThrow(); - return forkChoiceStrategy.findHead(justifiedCheckpoint, finalizedCheckpoint); - } - public SafeFuture onAttestation( final ValidateableAttestation attestation) { return recentChainData @@ -370,13 +341,22 @@ private IndexedAttestation getIndexedAttestation(final ValidateableAttestation a private SafeFuture onForkChoiceThread(final ExceptionThrowingRunnable task) { return onForkChoiceThread( - () -> { - task.run(); - return null; - }); + (ExceptionThrowingSupplier) + () -> { + task.run(); + return null; + }); } private SafeFuture onForkChoiceThread(final ExceptionThrowingSupplier task) { return forkChoiceExecutor.execute(task); } + + // Errorprone thinks we're ignoring return values because the execute() call winds up returning a + // nested SafeFuture> but we are unwrapping it so if either future fails we'll + // still handle the result + @SuppressWarnings("FutureReturnValueIgnored") + private SafeFuture onForkChoiceThread(final Supplier> task) { + return forkChoiceExecutor.execute(task::get).thenCompose(Function.identity()); + } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutor.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutor.java new file mode 100644 index 00000000000..ffd64443024 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutor.java @@ -0,0 +1,137 @@ +/* + * Copyright 2021 ConsenSys AG. + * + * 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.forkchoice; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.async.eventthread.EventThread; +import tech.pegasys.teku.protoarray.ForkChoiceStrategy; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.datastructures.state.Checkpoint; +import tech.pegasys.teku.spec.executionengine.ExecutePayloadResult; +import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; +import tech.pegasys.teku.spec.executionengine.ExecutionPayloadStatus; +import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; +import tech.pegasys.teku.storage.client.RecentChainData; + +class ForkChoicePayloadExecutor implements OptimisticExecutionPayloadExecutor { + + private final RecentChainData recentChainData; + private final EventThread forkChoiceExecutor; + private final SignedBeaconBlock block; + private final ExecutionEngineChannel executionEngine; + private Optional> result = Optional.empty(); + + ForkChoicePayloadExecutor( + final RecentChainData recentChainData, + final EventThread forkChoiceExecutor, + final SignedBeaconBlock block, + final ExecutionEngineChannel executionEngine) { + this.recentChainData = recentChainData; + this.forkChoiceExecutor = forkChoiceExecutor; + this.block = block; + this.executionEngine = executionEngine; + } + + public SafeFuture combine(final BlockImportResult blockImportResult) { + if (!blockImportResult.isSuccessful()) { + // If the block import failed there's no point waiting for the payload result. + return SafeFuture.completedFuture(blockImportResult); + } + if (result.isEmpty()) { + // No execution was started so can return result unchanged + updateForkChoiceForImportedBlock(block, blockImportResult, getForkChoiceStrategy()); + return SafeFuture.completedFuture(blockImportResult); + } + // Otherwise we'll have to wait for the payload result + return result + .get() + .thenApplyAsync( + payloadResult -> combineResults(blockImportResult, payloadResult), forkChoiceExecutor); + } + + private BlockImportResult combineResults( + final BlockImportResult blockImportResult, final ExecutePayloadResult payloadResult) { + final ForkChoiceStrategy forkChoiceStrategy = getForkChoiceStrategy(); + forkChoiceStrategy.onExecutionPayloadResult(block.getRoot(), payloadResult.getStatus()); + if (payloadResult.getStatus() == ExecutionPayloadStatus.INVALID) { + return BlockImportResult.failedStateTransition( + new IllegalStateException( + "Invalid ExecutionPayload: " + + payloadResult.getMessage().orElse("No reason provided"))); + } + + if (payloadResult.getStatus() == ExecutionPayloadStatus.VALID) { + updateForkChoiceForImportedBlock(block, blockImportResult, forkChoiceStrategy); + } + return blockImportResult; + } + + @Override + public boolean optimisticallyExecute(final ExecutionPayload executionPayload) { + result = Optional.of(executionEngine.executePayload(executionPayload)); + return true; + } + + private void updateForkChoiceForImportedBlock( + final SignedBeaconBlock block, + final BlockImportResult result, + final ForkChoiceStrategy forkChoiceStrategy) { + + final SlotAndBlockRoot bestHeadBlock = findNewChainHead(block, forkChoiceStrategy); + if (!bestHeadBlock.getBlockRoot().equals(recentChainData.getBestBlockRoot().orElseThrow())) { + recentChainData.updateHead(bestHeadBlock.getBlockRoot(), bestHeadBlock.getSlot()); + if (bestHeadBlock.getBlockRoot().equals(block.getRoot())) { + result.markAsCanonical(); + } + } + } + + private SlotAndBlockRoot findNewChainHead( + final SignedBeaconBlock block, final ForkChoiceStrategy forkChoiceStrategy) { + // If the new block builds on our current chain head it must be the new chain head. + // Since fork choice works by walking down the tree selecting the child block with + // the greatest weight, when a block has only one child it will automatically become + // a better choice than the block itself. So the first block we receive that is a + // child of our current chain head, must be the new chain head. If we'd had any other + // child of the current chain head we'd have already selected it as head. + if (recentChainData + .getChainHead() + .map(currentHead -> currentHead.getRoot().equals(block.getParentRoot())) + .orElse(false)) { + return new SlotAndBlockRoot(block.getSlot(), block.getRoot()); + } + + // Otherwise, use fork choice to find the new chain head as if this block is on time the + // proposer weighting may cause us to reorg. + // During sync, this may be noticeably slower than just comparing the chain head due to the way + // ProtoArray skips updating all ancestors when adding a new block but it's cheap when in sync. + final Checkpoint justifiedCheckpoint = recentChainData.getJustifiedCheckpoint().orElseThrow(); + final Checkpoint finalizedCheckpoint = recentChainData.getFinalizedCheckpoint().orElseThrow(); + return forkChoiceStrategy.findHead(justifiedCheckpoint, finalizedCheckpoint); + } + + private ForkChoiceStrategy getForkChoiceStrategy() { + forkChoiceExecutor.checkOnEventThread(); + return recentChainData + .getForkChoiceStrategy() + .orElseThrow( + () -> + new IllegalStateException( + "Attempting to perform fork choice operations before store has been initialized")); + } +} diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutorTest.java new file mode 100644 index 00000000000..89d0f92d8e2 --- /dev/null +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoicePayloadExecutorTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2021 ConsenSys AG. + * + * 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.forkchoice; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.async.eventthread.EventThread; +import tech.pegasys.teku.infrastructure.async.eventthread.InlineEventThread; +import tech.pegasys.teku.protoarray.ForkChoiceStrategy; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; +import tech.pegasys.teku.spec.executionengine.ExecutePayloadResult; +import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; +import tech.pegasys.teku.spec.executionengine.ExecutionPayloadStatus; +import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult; +import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.storage.client.RecentChainData; + +class ForkChoicePayloadExecutorTest { + + private final Spec spec = TestSpecFactory.createMinimalMerge(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private final ForkChoiceStrategy forkChoiceStrategy = mock(ForkChoiceStrategy.class); + private final RecentChainData recentChainData = mock(RecentChainData.class); + private final SafeFuture executionResult = new SafeFuture<>(); + private final ExecutionEngineChannel executionEngine = mock(ExecutionEngineChannel.class); + private EventThread forkChoiceExecutor = new InlineEventThread(); + + private final StateAndBlockSummary chainHead = + StateAndBlockSummary.create(dataStructureUtil.randomBeaconState()); + private final SignedBeaconBlock block = + dataStructureUtil.randomSignedBeaconBlock(0, chainHead.getRoot()); + + @BeforeEach + void setUp() { + when(recentChainData.getForkChoiceStrategy()) + .thenAnswer( + invocation -> { + forkChoiceExecutor.checkOnEventThread(); + return Optional.of(forkChoiceStrategy); + }); + when(recentChainData.getChainHead()).thenReturn(Optional.of(chainHead)); + when(recentChainData.getBestBlockRoot()).thenReturn(Optional.of(chainHead.getRoot())); + + when(executionEngine.executePayload(any())).thenReturn(executionResult); + } + + @Test + void optimisticallyExecute_shouldSendToExecutionEngineAndReturnTrue() { + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + final ExecutionPayload payload = dataStructureUtil.randomExecutionPayload(); + final boolean result = payloadExecutor.optimisticallyExecute(payload); + verify(executionEngine).executePayload(payload); + assertThat(result).isTrue(); + } + + @Test + void shouldReturnBlockImportResultImmediatelyWhenNotSuccessful() { + forkChoiceExecutor = mock(EventThread.class); + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + payloadExecutor.optimisticallyExecute(dataStructureUtil.randomExecutionPayload()); + + final BlockImportResult blockImportResult = + BlockImportResult.failedStateTransition(new RuntimeException("Bad block")); + final SafeFuture result = payloadExecutor.combine(blockImportResult); + assertThat(result).isCompletedWithValue(blockImportResult); + } + + @Test + void shouldReturnBlockImportResultImmediatelyWhenNoPayloadExecuted() { + forkChoiceExecutor = mock(EventThread.class); + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + + final BlockImportResult blockImportResult = BlockImportResult.successful(block); + final SafeFuture result = payloadExecutor.combine(blockImportResult); + assertThat(result).isCompletedWithValue(blockImportResult); + verifyChainHeadUpdated(blockImportResult); + } + + @Test + void shouldCombineWithValidPayloadExecution() { + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + final ExecutionPayload payload = dataStructureUtil.randomExecutionPayload(); + payloadExecutor.optimisticallyExecute(payload); + + final BlockImportResult blockImportResult = BlockImportResult.successful(block); + final SafeFuture result = payloadExecutor.combine(blockImportResult); + assertThat(result).isNotCompleted(); + verifyChainHeadNotUpdated(blockImportResult); + + executionResult.complete( + new ExecutePayloadResult(ExecutionPayloadStatus.VALID, Optional.empty(), Optional.empty())); + + assertThat(result).isCompletedWithValue(blockImportResult); + verify(forkChoiceStrategy) + .onExecutionPayloadResult(block.getRoot(), ExecutionPayloadStatus.VALID); + verifyChainHeadUpdated(blockImportResult); + } + + @Test + void shouldCombineWithInvalidPayloadExecution() { + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + final ExecutionPayload payload = dataStructureUtil.randomExecutionPayload(); + payloadExecutor.optimisticallyExecute(payload); + + final BlockImportResult blockImportResult = BlockImportResult.successful(block); + final SafeFuture result = payloadExecutor.combine(blockImportResult); + assertThat(result).isNotCompleted(); + verifyChainHeadNotUpdated(blockImportResult); + + executionResult.complete( + new ExecutePayloadResult( + ExecutionPayloadStatus.INVALID, Optional.empty(), Optional.empty())); + + assertThat(result).isCompleted(); + verify(forkChoiceStrategy) + .onExecutionPayloadResult(block.getRoot(), ExecutionPayloadStatus.INVALID); + final BlockImportResult finalImportResult = result.join(); + assertThat(finalImportResult.isSuccessful()).isFalse(); + verifyChainHeadNotUpdated(finalImportResult); + } + + @Test + void shouldCombineWithSyncingPayloadExecution() { + final ForkChoicePayloadExecutor payloadExecutor = createPayloadExecutor(); + final ExecutionPayload payload = dataStructureUtil.randomExecutionPayload(); + payloadExecutor.optimisticallyExecute(payload); + + final BlockImportResult blockImportResult = BlockImportResult.successful(block); + final SafeFuture result = payloadExecutor.combine(blockImportResult); + assertThat(result).isNotCompleted(); + verifyChainHeadNotUpdated(blockImportResult); + + executionResult.complete( + new ExecutePayloadResult( + ExecutionPayloadStatus.SYNCING, Optional.empty(), Optional.empty())); + + assertThat(result).isCompletedWithValue(blockImportResult); + verify(forkChoiceStrategy) + .onExecutionPayloadResult(block.getRoot(), ExecutionPayloadStatus.SYNCING); + verifyChainHeadNotUpdated(blockImportResult); + } + + private void verifyChainHeadNotUpdated(final BlockImportResult blockImportResult) { + verify(recentChainData, never()).updateHead(any(), any()); + assertThat(blockImportResult.isBlockOnCanonicalChain()).isFalse(); + } + + private void verifyChainHeadUpdated(final BlockImportResult blockImportResult) { + verify(recentChainData).updateHead(block.getRoot(), block.getSlot()); + assertThat(blockImportResult.isBlockOnCanonicalChain()).isTrue(); + } + + private ForkChoicePayloadExecutor createPayloadExecutor() { + return new ForkChoicePayloadExecutor( + recentChainData, forkChoiceExecutor, block, executionEngine); + } +} diff --git a/fuzz/src/main/java/tech/pegasys/teku/fuzz/FuzzUtil.java b/fuzz/src/main/java/tech/pegasys/teku/fuzz/FuzzUtil.java index 97aa0922d2d..c7df07029c2 100644 --- a/fuzz/src/main/java/tech/pegasys/teku/fuzz/FuzzUtil.java +++ b/fuzz/src/main/java/tech/pegasys/teku/fuzz/FuzzUtil.java @@ -39,9 +39,9 @@ import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.ssz.SszData; import tech.pegasys.teku.ssz.SszList; import tech.pegasys.teku.ssz.schema.SszSchema; @@ -59,7 +59,6 @@ public class FuzzUtil { private static final int OUTPUT_INDEX_BYTES = Long.BYTES; private final BLSSignatureVerifier signatureVerifier; - private final ExecutionEngineChannel executionEngineChannel = ExecutionEngineChannel.NOOP; // NOTE: this uses primitive values as parameters to more easily call via JNI public FuzzUtil(final boolean useMainnetConfig, final boolean disable_bls) { @@ -144,7 +143,7 @@ public Optional fuzzBlock(final byte[] input) { structuredInput.getState(), structuredInput.getSigned_block(), signatureVerifier, - executionEngineChannel); + OptimisticExecutionPayloadExecutor.NOOP); Bytes output = postState.sszSerialize(); return Optional.of(output.toArrayUnsafe()); } catch (StateTransitionException e) { diff --git a/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/SafeFuture.java b/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/SafeFuture.java index 1d2c62795da..f054c149f32 100644 --- a/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/SafeFuture.java +++ b/infrastructure/async/src/main/java/tech/pegasys/teku/infrastructure/async/SafeFuture.java @@ -408,6 +408,13 @@ public SafeFuture thenApply(final Function fn) { return (SafeFuture) super.thenApply(fn); } + @SuppressWarnings("unchecked") + @Override + public SafeFuture thenApplyAsync( + final Function fn, final Executor executor) { + return (SafeFuture) super.thenApplyAsync(fn, executor); + } + public SafeFuture thenApplyChecked(final ExceptionThrowingFunction function) { return thenCompose( value -> { diff --git a/protoarray/src/main/java/tech/pegasys/teku/protoarray/ForkChoiceStrategy.java b/protoarray/src/main/java/tech/pegasys/teku/protoarray/ForkChoiceStrategy.java index 877b2d2aaad..f8391a8d75b 100644 --- a/protoarray/src/main/java/tech/pegasys/teku/protoarray/ForkChoiceStrategy.java +++ b/protoarray/src/main/java/tech/pegasys/teku/protoarray/ForkChoiceStrategy.java @@ -45,6 +45,7 @@ import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.executionengine.ExecutionPayloadStatus; public class ForkChoiceStrategy implements BlockMetadataStore, ReadOnlyForkChoiceStrategy { private static final Logger LOG = LogManager.getLogger(); @@ -436,4 +437,26 @@ void processBlock( private Optional getProtoNode(Bytes32 blockRoot) { return protoArray.getProtoNode(blockRoot); } + + public void onExecutionPayloadResult( + final Bytes32 blockRoot, final ExecutionPayloadStatus status) { + if (status == ExecutionPayloadStatus.SYNCING) { + return; + } + protoArrayLock.writeLock().lock(); + try { + switch (status) { + case VALID: + protoArray.markNodeValid(blockRoot); + break; + case INVALID: + protoArray.markNodeInvalid(blockRoot); + break; + default: + throw new IllegalArgumentException("Unknown payload status: " + status); + } + } finally { + protoArrayLock.writeLock().unlock(); + } + } } diff --git a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/TransitionCommand.java b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/TransitionCommand.java index dd81bae3d55..e5b4ef3e576 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/TransitionCommand.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/TransitionCommand.java @@ -37,10 +37,10 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; +import tech.pegasys.teku.spec.logic.versions.merge.block.OptimisticExecutionPayloadExecutor; import tech.pegasys.teku.util.config.Constants; @Command( @@ -81,7 +81,10 @@ public int blocks( SignedBeaconBlock block = readBlock(spec, blockPath); state = spec.processBlock( - state, block, BLSSignatureVerifier.SIMPLE, ExecutionEngineChannel.NOOP); + state, + block, + BLSSignatureVerifier.SIMPLE, + OptimisticExecutionPayloadExecutor.NOOP); } } return state;