diff --git a/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java b/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java index 6e3e6550e96..ced89e3323c 100644 --- a/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java +++ b/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java @@ -51,17 +51,17 @@ import tech.pegasys.teku.api.schema.BeaconBlock; import tech.pegasys.teku.api.schema.ValidatorBlockResult; import tech.pegasys.teku.api.schema.altair.SignedBeaconBlockAltair; +import tech.pegasys.teku.api.schema.merge.SignedBeaconBlockMerge; import tech.pegasys.teku.api.schema.phase0.BeaconBlockPhase0; +import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; import tech.pegasys.teku.bls.BLSTestUtil; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.async.SafeFutureAssert; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.provider.JsonProvider; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecContext; import tech.pegasys.teku.spec.TestSpecInvocationContextProvider.SpecContext; -import tech.pegasys.teku.spec.TestSpecInvocationContextProvider.SpecContextExecutionHelper; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.util.DataStructureUtil; import tech.pegasys.teku.storage.client.ChainDataUnavailableException; @@ -198,9 +198,8 @@ void parseBlock_shouldParseBlocks() throws JsonProcessingException { } @TestTemplate - void parseBlock_shouldParseAltairBlocks(SpecContext specContext) throws JsonProcessingException { - SpecContextExecutionHelper.only(specContext, SpecMilestone.ALTAIR); - + void parseBlock_shouldParseMilestoneSpecificBlocks(SpecContext specContext) + throws JsonProcessingException { final SignedBeaconBlock internalSignedBlock = dataStructureUtil.randomSignedBeaconBlock(ONE); final tech.pegasys.teku.api.schema.SignedBeaconBlock signedBlock = schemaProvider.getSignedBeaconBlock(internalSignedBlock); @@ -210,7 +209,17 @@ void parseBlock_shouldParseAltairBlocks(SpecContext specContext) throws JsonProc provider.parseBlock(jsonProvider, signedBlockJson); assertThat(parsedBlock).isEqualTo(signedBlock); - assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockAltair.class); + switch (specContext.getSpecMilestone()) { + case PHASE0: + assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockPhase0.class); + break; + case ALTAIR: + assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockAltair.class); + break; + case MERGE: + assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockMerge.class); + break; + } } @TestTemplate diff --git a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java index 9fb62bfb3b1..6ac66b4b64c 100644 --- a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java +++ b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java @@ -32,6 +32,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; @@ -71,7 +72,8 @@ public SignedBlockAndState createNewBlock( final SszList deposits, final SszList exits, final Optional> transactions, - final Optional terminalBlock) + final Optional terminalBlock, + final Optional executionPayload) throws StateTransitionException, EpochProcessingException, SlotProcessingException { final UInt64 newEpoch = spec.computeEpochAtSlot(newSlot); @@ -99,8 +101,10 @@ public SignedBlockAndState createNewBlock( () -> dataStructureUtil.emptySyncAggregateIfRequiredByState(blockSlotState)) .executionPayload( () -> - createExecutionPayload( - newSlot, state, newEpoch, transactions, terminalBlock))); + executionPayload.orElseGet( + () -> + createExecutionPayload( + newSlot, state, newEpoch, transactions, terminalBlock)))); // Sign block and set block signature final BeaconBlock block = newBlockAndState.getBlock(); @@ -110,6 +114,77 @@ public SignedBlockAndState createNewBlock( return new SignedBlockAndState(signedBlock, newBlockAndState.getState()); } + public SignedBlockAndState createNewBlockSkippingStateTransition( + final Signer signer, + final UInt64 newSlot, + final BeaconState state, + final Bytes32 parentBlockSigningRoot, + final Eth1Data eth1Data, + final SszList attestations, + final SszList slashings, + final SszList deposits, + final SszList exits, + final Optional> transactions, + final Optional terminalBlock, + final Optional executionPayload) + throws EpochProcessingException, SlotProcessingException { + + final UInt64 newEpoch = spec.computeEpochAtSlot(newSlot); + final BLSSignature randaoReveal = + signer.createRandaoReveal(newEpoch, state.getForkInfo()).join(); + + final BeaconState blockSlotState = spec.processSlots(state, newSlot); + + // Sign block and set block signature + + final BeaconBlockBody blockBody = + spec.atSlot(newSlot) + .getSchemaDefinitions() + .getBeaconBlockBodySchema() + .createBlockBody( + builder -> + builder + .randaoReveal(randaoReveal) + .eth1Data(eth1Data) + .graffiti(Bytes32.ZERO) + .attestations(attestations) + .proposerSlashings(slashings) + .attesterSlashings(blockBodyLists.createAttesterSlashings()) + .deposits(deposits) + .voluntaryExits(exits) + .syncAggregate( + () -> + dataStructureUtil.emptySyncAggregateIfRequiredByState( + blockSlotState)) + .executionPayload( + () -> + executionPayload.orElseGet( + () -> + createExecutionPayload( + newSlot, + state, + newEpoch, + transactions, + terminalBlock)))); + + final BeaconBlock block = + spec.atSlot(newSlot) + .getSchemaDefinitions() + .getBeaconBlockSchema() + .create( + newSlot, + UInt64.valueOf(spec.getBeaconProposerIndex(blockSlotState, newSlot)), + parentBlockSigningRoot, + blockSlotState.hashTreeRoot(), + blockBody); + + // Sign block and set block signature + BLSSignature blockSignature = signer.signBlock(block, state.getForkInfo()).join(); + + final SignedBeaconBlock signedBlock = SignedBeaconBlock.create(spec, block, blockSignature); + return new SignedBlockAndState(signedBlock, blockSlotState); + } + private ExecutionPayload createExecutionPayload( final UInt64 newSlot, final BeaconState state, @@ -162,9 +237,26 @@ public SignedBlockAndState createBlock( final Optional> exits, final Optional eth1Data, final Optional> transactions, - final Optional terminalBlock) + final Optional terminalBlock, + final Optional executionPayload, + final boolean skipStateTransition) throws StateTransitionException, EpochProcessingException, SlotProcessingException { final UInt64 newEpoch = spec.computeEpochAtSlot(newSlot); + if (skipStateTransition) { + return createNewBlockSkippingStateTransition( + signer, + newSlot, + previousState, + parentBlockSigningRoot, + eth1Data.orElse(get_eth1_data_stub(previousState, newEpoch)), + attestations.orElse(blockBodyLists.createAttestations()), + blockBodyLists.createProposerSlashings(), + deposits.orElse(blockBodyLists.createDeposits()), + exits.orElse(blockBodyLists.createVoluntaryExits()), + transactions, + terminalBlock, + executionPayload); + } return createNewBlock( signer, newSlot, @@ -176,7 +268,8 @@ public SignedBlockAndState createBlock( deposits.orElse(blockBodyLists.createDeposits()), exits.orElse(blockBodyLists.createVoluntaryExits()), transactions, - terminalBlock); + terminalBlock, + executionPayload); } private Eth1Data get_eth1_data_stub(BeaconState state, UInt64 current_epoch) { diff --git a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/ChainBuilder.java b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/ChainBuilder.java index 0d265fbaeae..34ecda56a3c 100644 --- a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/ChainBuilder.java +++ b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/ChainBuilder.java @@ -45,6 +45,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.interop.MockStartBeaconStateGenerator; import tech.pegasys.teku.spec.datastructures.interop.MockStartDepositGenerator; @@ -410,7 +411,9 @@ private SignedBlockAndState appendNewBlockToChain(final UInt64 slot, final Block Optional.empty(), options.getEth1Data(), options.getTransactions(), - options.getTerminalBlockHash()); + options.getTerminalBlockHash(), + options.getExecutionPayload(), + options.getSkipStateTransition()); trackBlock(nextBlockAndState); return nextBlockAndState; } catch (StateTransitionException | EpochProcessingException | SlotProcessingException e) { @@ -521,6 +524,8 @@ public static final class BlockOptions { private Optional eth1Data = Optional.empty(); private Optional> transactions = Optional.empty(); private Optional terminalBlockHash = Optional.empty(); + private Optional executionPayload = Optional.empty(); + private boolean skipStateTransition = false; private BlockOptions() {} @@ -548,6 +553,16 @@ public BlockOptions setTerminalBlockHash(Bytes32 blockHash) { return this; } + public BlockOptions setExecutionPayload(ExecutionPayload executionPayload) { + this.executionPayload = Optional.of(executionPayload); + return this; + } + + public BlockOptions setSkipStateTransition(boolean skipStateTransition) { + this.skipStateTransition = skipStateTransition; + return this; + } + private List getAttestations() { return attestations; } @@ -563,5 +578,13 @@ public Optional> getTransactions() { public Optional getTerminalBlockHash() { return terminalBlockHash; } + + public Optional getExecutionPayload() { + return executionPayload; + } + + public boolean getSkipStateTransition() { + return skipStateTransition; + } } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecInvocationContextProvider.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecInvocationContextProvider.java index 3492721f2f6..def1ce636c6 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecInvocationContextProvider.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecInvocationContextProvider.java @@ -128,6 +128,34 @@ public SpecMilestone getSpecMilestone() { public Eth2Network getNetwork() { return network; } + + public void assumeIsOneOf(SpecMilestone... milestones) { + Assumptions.assumeTrue(List.of(milestones).contains(specMilestone), "Milestone skipped"); + } + + public void assumeIsOneOf(Eth2Network... networks) { + Assumptions.assumeTrue(List.of(networks).contains(network), "Network skipped"); + } + + public void assumeIsNotOneOf(SpecMilestone... milestones) { + Assumptions.assumeFalse(List.of(milestones).contains(specMilestone), "Milestone skipped"); + } + + public void assumeIsNotOneOf(Eth2Network... networks) { + Assumptions.assumeFalse(List.of(networks).contains(network), "Network skipped"); + } + + public void assumeMilestoneActive(SpecMilestone milestone) { + Assumptions.assumeTrue(specMilestone.isGreaterThanOrEqualTo(milestone), "Milestone skipped"); + } + + public void assumeMergeActive() { + assumeMilestoneActive(SpecMilestone.MERGE); + } + + public void assumeAltairActive() { + assumeMilestoneActive(SpecMilestone.ALTAIR); + } } public static class SpecContextParameterResolver implements ParameterResolver { @@ -151,41 +179,4 @@ public Object resolveParameter( return data; } } - - public static class SpecContextExecutionHelper { - public static void skip(SpecContext specContext, SpecMilestone... milestones) { - Assumptions.assumeFalse( - List.of(milestones).contains(specContext.getSpecMilestone()), "Milestone skipped"); - } - - public static void only(SpecContext specContext, SpecMilestone... milestones) { - Assumptions.assumeTrue( - List.of(milestones).contains(specContext.getSpecMilestone()), "Milestone skipped"); - } - - public static void skip(SpecContext specContext, Eth2Network... networks) { - Assumptions.assumeFalse( - List.of(networks).contains(specContext.getNetwork()), "Network skipped"); - } - - public static void only(SpecContext specContext, Eth2Network... networks) { - Assumptions.assumeTrue( - List.of(networks).contains(specContext.getNetwork()), "Network skipped"); - } - - public static void onlyPhase0(SpecContext specContext) { - Assumptions.assumeTrue( - specContext.getSpecMilestone().equals(SpecMilestone.PHASE0), "Milestone skipped"); - } - - public static void onlyAltair(SpecContext specContext) { - Assumptions.assumeTrue( - specContext.getSpecMilestone().equals(SpecMilestone.ALTAIR), "Milestone skipped"); - } - - public static void onlyMerge(SpecContext specContext) { - Assumptions.assumeTrue( - specContext.getSpecMilestone().equals(SpecMilestone.MERGE), "Milestone skipped"); - } - } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockValidator.java index a1cc4a0cb5f..67e2806e613 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockValidator.java @@ -19,7 +19,6 @@ import static tech.pegasys.teku.util.config.Constants.VALID_BLOCK_SET_SIZE; import com.google.common.base.Objects; -import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -81,25 +80,6 @@ public SafeFuture validate(SignedBeaconBlock block) { return completedFuture(reject("Block does not descend from finalized checkpoint")); } - Optional maybeExecutionPayload = - block.getMessage().getBody().getOptionalExecutionPayload(); - Optional maybeState = recentChainData.getBestState(); - if (maybeExecutionPayload.isPresent() && !maybeExecutionPayload.get().isDefault()) { - if (maybeState.isEmpty()) { - LOG.trace( - "No state is available to compute timestamp block slot. Block will be saved for future processing"); - return completedFuture(InternalValidationResult.SAVE_FOR_FUTURE); - } - if (maybeExecutionPayload - .get() - .getTimestamp() - .compareTo(spec.computeTimeAtSlot(maybeState.get(), block.getSlot())) - != 0) { - return completedFuture( - reject("Execution Payload timestamp is not consistence with and block slot time")); - } - } - return recentChainData .retrieveBlockByRoot(block.getMessage().getParentRoot()) .thenCompose( @@ -122,19 +102,36 @@ public SafeFuture validate(SignedBeaconBlock block) { parentBlock.get().getSlot().max(firstSlotInBlockEpoch), block.getParentRoot())) .thenApply( - postState -> { - if (postState.isEmpty()) { + maybePostState -> { + if (maybePostState.isEmpty()) { LOG.trace( "Block was available but state wasn't. Must have been pruned by finalized."); return InternalValidationResult.IGNORE; } - if (!blockIsProposedByTheExpectedProposer(block, postState.get())) { + final BeaconState postState = maybePostState.get(); + + if (!blockIsProposedByTheExpectedProposer(block, postState)) { return reject( "Block proposed by incorrect proposer (%s)", block.getProposerIndex()); } - if (!blockSignatureIsValidWithRespectToProposerIndex( - block, postState.get())) { + if (spec.atSlot(block.getSlot()).miscHelpers().isMergeComplete(postState)) { + ExecutionPayload executionPayload = + block + .getMessage() + .getBody() + .getOptionalExecutionPayload() + .orElseThrow(); + + if (executionPayload + .getTimestamp() + .compareTo(spec.computeTimeAtSlot(postState, block.getSlot())) + != 0) { + return reject( + "Execution Payload timestamp is not consistence with and block slot time"); + } + } + if (!blockSignatureIsValidWithRespectToProposerIndex(block, postState)) { return reject("Block signature is invalid"); } return InternalValidationResult.ACCEPT; diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockValidatorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockValidatorTest.java index 0917278a7e0..f8d510f35fd 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockValidatorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockValidatorTest.java @@ -15,13 +15,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; -import static tech.pegasys.teku.spec.TestSpecInvocationContextProvider.SpecContextExecutionHelper.onlyMerge; import java.util.List; -import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -32,6 +28,7 @@ import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.bls.BLSTestUtil; import tech.pegasys.teku.core.ChainBuilder; +import tech.pegasys.teku.core.ChainBuilder.BlockOptions; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; @@ -41,7 +38,6 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.logic.common.block.AbstractBlockProcessor; import tech.pegasys.teku.storage.client.ChainUpdater; import tech.pegasys.teku.storage.client.RecentChainData; @@ -246,7 +242,7 @@ void shouldReturnInvalidForBlockThatDoesNotDescendFromFinalizedCheckpoint() { @TestTemplate void shouldReturnAcceptOnCorrectExecutionPayloadTimestamp(SpecContext specContext) { - onlyMerge(specContext); + specContext.assumeMergeActive(); storageSystem = InMemoryStorageSystemBuilder.buildDefault(spec); storageSystem.chainUpdater().initializeGenesisWithPayload(false); @@ -264,7 +260,7 @@ void shouldReturnAcceptOnCorrectExecutionPayloadTimestamp(SpecContext specContex @TestTemplate void shouldReturnInvalidOnWrongExecutionPayloadTimestamp(SpecContext specContext) { - onlyMerge(specContext); + specContext.assumeMergeActive(); storageSystem = InMemoryStorageSystemBuilder.buildDefault(spec); storageSystem.chainUpdater().initializeGenesisWithPayload(false); @@ -275,19 +271,17 @@ void shouldReturnInvalidOnWrongExecutionPayloadTimestamp(SpecContext specContext storageSystem.chainUpdater().setCurrentSlot(nextSlot); final SignedBlockAndState signedBlockAndState = - storageSystem.chainBuilder().generateBlockAtSlot(nextSlot); - - SignedBeaconBlock block = spy(signedBlockAndState.getBlock()); - BeaconBlock beaconBlock = spy(block.getMessage()); - BeaconBlockBody beaconBlockBody = spy(beaconBlock.getBody()); - doReturn(beaconBlock).when(block).getMessage(); - doReturn(beaconBlockBody).when(beaconBlock).getBody(); - - doReturn(Optional.of(specContext.getDataStructureUtil().randomExecutionPayload())) - .when(beaconBlockBody) - .getOptionalExecutionPayload(); - - InternalValidationResult result = blockValidator.validate(block).join(); + storageSystem + .chainBuilder() + .generateBlockAtSlot( + nextSlot, + BlockOptions.create() + .setSkipStateTransition(true) + .setExecutionPayload( + specContext.getDataStructureUtil().randomExecutionPayload())); + + InternalValidationResult result = + blockValidator.validate(signedBlockAndState.getBlock()).join(); assertTrue(result.isReject()); } } diff --git a/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/BeaconChainUtil.java b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/BeaconChainUtil.java index 8c6b1a4fcb4..df74133d3df 100644 --- a/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/BeaconChainUtil.java +++ b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/BeaconChainUtil.java @@ -312,7 +312,9 @@ private SignedBlockAndState createBlockAndStateAtSlot( exits, eth1Data, Optional.empty(), - Optional.empty()); + Optional.empty(), + Optional.empty(), + false); } public void finalizeChainAtEpoch(final UInt64 epoch) throws Exception {