From bd487b50c091aa2b4e23595cfe4095107e637d3d Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 10:21:24 +0200 Subject: [PATCH 01/25] Make checkpoint sync work when finalized state is transitioned with empty slot --- .../datastructures/blocks/StateAndBlockSummary.java | 8 ++++++-- .../teku/spec/datastructures/state/AnchorPoint.java | 9 +++++++++ .../teku/infrastructure/logging/StatusLogger.java | 13 ++----------- .../services/beaconchain/BeaconChainController.java | 6 +++--- .../beaconchain/WeakSubjectivityInitializer.java | 5 ----- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java index 5dbeabc20a8..30bc8842476 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java @@ -33,11 +33,15 @@ public class StateAndBlockSummary implements BeaconBlockSummary { protected StateAndBlockSummary(final BeaconBlockSummary blockSummary, final BeaconState state) { checkNotNull(blockSummary); checkNotNull(state); + verifyStateAndBlockConsistency(); + this.blockSummary = blockSummary; + this.state = state; + } + + protected void verifyStateAndBlockConsistency() { checkArgument( blockSummary.getStateRoot().equals(state.hashTreeRoot()), "Block state root must match the supplied state"); - this.blockSummary = blockSummary; - this.state = state; } public static StateAndBlockSummary create(final BeaconState state) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 69046334b66..c4a7dcfd0bc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -56,6 +56,15 @@ private AnchorPoint( this.isGenesis = checkpoint.getEpoch().equals(SpecConfig.GENESIS_EPOCH); } + @Override + protected void verifyStateAndBlockConsistency() { + if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { + // skip verification when state is transitioned with empty slot(s) + return; + } + super.verifyStateAndBlockConsistency(); + } + public static AnchorPoint create( final Spec spec, Checkpoint checkpoint, diff --git a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/StatusLogger.java b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/StatusLogger.java index 26fd1c87a9c..aaad3325d08 100644 --- a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/StatusLogger.java +++ b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/StatusLogger.java @@ -322,21 +322,12 @@ public void loadedInitialStateResource( } } - public void errorIncompatibleInitialState(final UInt64 epoch) { - log.error( - "Cannot start with provided initial state for the epoch {}, " - + "checkpoint occurred on the empty slot, which is not yet supported.\n" - + "If you are using remote checkpoint source, " - + "please wait for the next epoch to finalize and retry.", - epoch); - } - public void warnInitialStateIgnored() { log.warn("Not loading specified initial state as chain data already exists."); } - public void warnFailedToLoadInitialState(final String message) { - log.warn(message); + public void warnFailedToLoadInitialState(final Throwable throwable) { + log.warn("Failed to load initial state", throwable); } public void warnOnInitialStateWithSkippedSlots( 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 18de49d5a2f..83247f22862 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 @@ -1384,14 +1384,14 @@ private Optional tryLoadingAnchorPointFromInitialState( initialAnchor = attemptToLoadAnchorPoint( networkConfiguration.getNetworkBoostrapConfig().getInitialState()); - } catch (final InvalidConfigurationException e) { + } catch (final InvalidConfigurationException ex) { final StateBoostrapConfig stateBoostrapConfig = networkConfiguration.getNetworkBoostrapConfig(); if (stateBoostrapConfig.isUsingCustomInitialState() && !stateBoostrapConfig.isUsingCheckpointSync()) { - throw e; + throw ex; } - STATUS_LOG.warnFailedToLoadInitialState(e.getMessage()); + STATUS_LOG.warnFailedToLoadInitialState(ex); } return initialAnchor; diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/WeakSubjectivityInitializer.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/WeakSubjectivityInitializer.java index e1d0c9d35a7..564e23d7eec 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/WeakSubjectivityInitializer.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/WeakSubjectivityInitializer.java @@ -13,7 +13,6 @@ package tech.pegasys.teku.services.beaconchain; -import static tech.pegasys.teku.infrastructure.exceptions.ExitConstants.ERROR_EXIT_CODE; import static tech.pegasys.teku.infrastructure.logging.StatusLogger.STATUS_LOG; import static tech.pegasys.teku.networks.Eth2NetworkConfiguration.FINALIZED_STATE_URL_PATH; @@ -86,10 +85,6 @@ private AnchorPoint getAnchorPoint(Spec spec, String stateResource, String sanit throws IOException { STATUS_LOG.loadingInitialStateResource(sanitizedResource); final BeaconState state = ChainDataLoader.loadState(spec, stateResource); - if (state.getSlot().isGreaterThan(state.getLatestBlockHeader().getSlot())) { - STATUS_LOG.errorIncompatibleInitialState(spec.computeEpochAtSlot(state.getSlot())); - System.exit(ERROR_EXIT_CODE); - } final AnchorPoint anchor = AnchorPoint.fromInitialState(spec, state); STATUS_LOG.loadedInitialStateResource( state.hashTreeRoot(), From 052620595b2d62871a084ee25b98ec26903d8e4e Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 10:34:40 +0200 Subject: [PATCH 02/25] fix --- .../teku/spec/datastructures/blocks/StateAndBlockSummary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java index 30bc8842476..6a40e3058f6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/StateAndBlockSummary.java @@ -33,9 +33,9 @@ public class StateAndBlockSummary implements BeaconBlockSummary { protected StateAndBlockSummary(final BeaconBlockSummary blockSummary, final BeaconState state) { checkNotNull(blockSummary); checkNotNull(state); - verifyStateAndBlockConsistency(); this.blockSummary = blockSummary; this.state = state; + verifyStateAndBlockConsistency(); } protected void verifyStateAndBlockConsistency() { From f157063d5fbcac7a62128c8aafe0e56aeea63a6e Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 10:39:57 +0200 Subject: [PATCH 03/25] Add actual verification --- .../pegasys/teku/spec/datastructures/state/AnchorPoint.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index c4a7dcfd0bc..fc39ef917b1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -59,7 +59,10 @@ private AnchorPoint( @Override protected void verifyStateAndBlockConsistency() { if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { - // skip verification when state is transitioned with empty slot(s) + // state is transitioned with empty slot(s) + checkArgument( + blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), + "Block state root must match the latest block header state root in the state"); return; } super.verifyStateAndBlockConsistency(); From e076e5decafc429bf0d5d3cbd5c7c9dd8c8a5871 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 10:43:08 +0200 Subject: [PATCH 04/25] change comment a bit --- .../pegasys/teku/spec/datastructures/state/AnchorPoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index fc39ef917b1..2026887f243 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -59,7 +59,7 @@ private AnchorPoint( @Override protected void verifyStateAndBlockConsistency() { if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { - // state is transitioned with empty slot(s) + // the finalized state is transitioned with empty slot(s) checkArgument( blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), "Block state root must match the latest block header state root in the state"); From 9e786006838d7f45f4dfc7724e699e60afccf051 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 11:41:52 +0200 Subject: [PATCH 05/25] changes --- .../datastructures/state/AnchorPoint.java | 24 +++++++++---------- .../pegasys/teku/storage/store/Store.java | 7 ++---- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 2026887f243..0a6890d804a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -56,18 +56,6 @@ private AnchorPoint( this.isGenesis = checkpoint.getEpoch().equals(SpecConfig.GENESIS_EPOCH); } - @Override - protected void verifyStateAndBlockConsistency() { - if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { - // the finalized state is transitioned with empty slot(s) - checkArgument( - blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), - "Block state root must match the latest block header state root in the state"); - return; - } - super.verifyStateAndBlockConsistency(); - } - public static AnchorPoint create( final Spec spec, Checkpoint checkpoint, @@ -138,6 +126,18 @@ public static AnchorPoint fromInitialBlockAndState( return new AnchorPoint(spec, checkpoint, state, block); } + @Override + protected void verifyStateAndBlockConsistency() { + if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { + // the finalized state is transitioned with empty slot(s) + checkArgument( + blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), + "Block state root must match the latest block header state root in the state"); + return; + } + super.verifyStateAndBlockConsistency(); + } + public boolean isGenesis() { return isGenesis; } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java index fd69c162f1b..d6478bd571d 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java @@ -858,13 +858,10 @@ private SafeFuture> getOrRegenerateBlockAndState( return SafeFuture.completedFuture(maybeEpochState); } - // if finalized is gone from cache we can still reconstruct that without regenerating + // if finalized is gone from cache we can use the finalized anchor without regenerating if (finalizedAnchor.getRoot().equals(blockRoot)) { LOG.trace("epochCache GET finalizedAnchor {}", finalizedAnchor::getSlot); - return SafeFuture.completedFuture( - Optional.of( - StateAndBlockSummary.create( - finalizedAnchor.getBlockSummary(), finalizedAnchor.getState()))); + return SafeFuture.completedFuture(Optional.of(finalizedAnchor)); } maybeEpochStates.ifPresent( From b201ce38696040c56a4a03912e233065865b75c9 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 11:51:30 +0200 Subject: [PATCH 06/25] Add to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e831c22511f..26dcd43098d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,3 +16,5 @@ the [releases page](https://github.com/Consensys/teku/releases). - Updated Javalin to v.6 (used by rest-api and keymanager-api). ### Bug Fixes + +- Fixed a checkpoint sync issue where Teku couldn't start when the finalized state has been transitioned with empty slot(s) From 872953bab3b2def872101249cca611af14db240c Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 12:59:43 +0200 Subject: [PATCH 07/25] Add test --- .../java/tech/pegasys/teku/spec/Spec.java | 4 +++ .../datastructures/state/AnchorPointTest.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) 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 896a1e42729..0a3ba95951c 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 @@ -426,6 +426,10 @@ public UInt64 computeStartSlotAtEpoch(final UInt64 epoch) { return atEpoch(epoch).miscHelpers().computeStartSlotAtEpoch(epoch); } + public UInt64 computeEndSlotAtEpoch(final UInt64 epoch) { + return atEpoch(epoch).miscHelpers().computeEndSlotAtEpoch(epoch); + } + public UInt64 computeEpochAtSlot(final UInt64 slot) { return atSlot(slot).miscHelpers().computeEpochAtSlot(slot); } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java index e7b5dd7c6f6..ecf7340265e 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.spec.datastructures.state; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import java.util.Optional; @@ -21,11 +22,19 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +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.util.DataStructureUtil; +import tech.pegasys.teku.storage.storageSystem.InMemoryStorageSystemBuilder; +import tech.pegasys.teku.storage.storageSystem.StorageSystem; public class AnchorPointTest { private final Spec spec = TestSpecFactory.createDefault(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private final StorageSystem storageSystem = + InMemoryStorageSystemBuilder.create().specProvider(spec).build(); @Test public void create_withCheckpointPriorToState() { @@ -41,4 +50,30 @@ public void create_withCheckpointPriorToState() { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Block must be at or prior to the start of the checkpoint epoch"); } + + @Test + public void createFromInitialState_WithEmptySlotOnCheckpointEpochTransition() + throws SlotProcessingException, EpochProcessingException { + storageSystem.chainUpdater().initializeGenesis(); + + final UInt64 latestBlockHeaderEpoch = UInt64.valueOf(4); + final UInt64 latestBlockHeaderSlot = spec.computeEndSlotAtEpoch(latestBlockHeaderEpoch); + final SignedBlockAndState blockAndState = + storageSystem.chainUpdater().advanceChainUntil(latestBlockHeaderSlot); + + // process empty slot on checkpoint epoch transition + final BeaconState postState = + spec.processSlots(blockAndState.getState(), latestBlockHeaderSlot.increment()); + + final AnchorPoint anchor = AnchorPoint.fromInitialState(spec, postState); + + // verify finalized anchor + assertThat(anchor.getBlockSlot()).isEqualTo(latestBlockHeaderSlot); + assertThat(anchor.getStateRoot()).isEqualTo(blockAndState.getBlock().getStateRoot()); + final Checkpoint expectedCheckpoint = + new Checkpoint( + latestBlockHeaderEpoch.plus(1), + blockAndState.getBlock().asHeader().getMessage().hashTreeRoot()); + assertThat(anchor.getCheckpoint()).isEqualTo(expectedCheckpoint); + } } From c05a1f2a494007ac5a08106d75376852029a383e Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 13:02:10 +0200 Subject: [PATCH 08/25] change test name --- .../teku/spec/datastructures/state/AnchorPointTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java index ecf7340265e..aed77ec2d19 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/AnchorPointTest.java @@ -52,7 +52,7 @@ public void create_withCheckpointPriorToState() { } @Test - public void createFromInitialState_WithEmptySlotOnCheckpointEpochTransition() + public void createFromInitialState_WhenFinalizedStateTransitionedWithAnEmptySlot() throws SlotProcessingException, EpochProcessingException { storageSystem.chainUpdater().initializeGenesis(); @@ -61,7 +61,7 @@ public void createFromInitialState_WithEmptySlotOnCheckpointEpochTransition() final SignedBlockAndState blockAndState = storageSystem.chainUpdater().advanceChainUntil(latestBlockHeaderSlot); - // process empty slot on checkpoint epoch transition + // empty slot transition final BeaconState postState = spec.processSlots(blockAndState.getState(), latestBlockHeaderSlot.increment()); From 6199a4a14d1cdfb882bd1cef8895426bbb735a0e Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 17:18:21 +0200 Subject: [PATCH 09/25] add additional checks --- .../datastructures/state/AnchorPoint.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 0a6890d804a..347e7436fbf 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -45,15 +45,10 @@ private AnchorPoint( final BeaconState state, final BeaconBlockSummary blockSummary) { super(blockSummary, state); - checkArgument( - checkpoint.getRoot().equals(blockSummary.getRoot()), "Checkpoint and block must match"); - checkArgument( - checkpoint.getEpochStartSlot(spec).isGreaterThanOrEqualTo(blockSummary.getSlot()), - "Block must be at or prior to the start of the checkpoint epoch"); - this.spec = spec; this.checkpoint = checkpoint; this.isGenesis = checkpoint.getEpoch().equals(SpecConfig.GENESIS_EPOCH); + verifyAnchor(); } public static AnchorPoint create( @@ -126,16 +121,37 @@ public static AnchorPoint fromInitialBlockAndState( return new AnchorPoint(spec, checkpoint, state, block); } + /** Skipping verification in the super class. All checks are made in {@link #verifyAnchor()} */ @Override - protected void verifyStateAndBlockConsistency() { - if (state.getSlot().isGreaterThan(blockSummary.getSlot())) { + protected void verifyStateAndBlockConsistency() {} + + private void verifyAnchor() { + final UInt64 blockSlot = blockSummary.getSlot(); + if (state.getSlot().isGreaterThan(blockSlot)) { // the finalized state is transitioned with empty slot(s) checkArgument( blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), "Block state root must match the latest block header state root in the state"); - return; + final int stateAndBlockRootsIndex = + blockSlot.mod(spec.getSlotsPerHistoricalRoot(blockSlot)).intValue(); + checkArgument( + blockSummary + .getStateRoot() + .equals(state.getStateRoots().get(stateAndBlockRootsIndex).get()), + "Block state root must match the state root for the block slot %s in the state roots", + blockSlot); + checkArgument( + blockSummary.getRoot().equals(state.getBlockRoots().get(stateAndBlockRootsIndex).get()), + "Block root must match the root for the block slot %s in the block roots in the state", + blockSlot); + } else { + super.verifyStateAndBlockConsistency(); } - super.verifyStateAndBlockConsistency(); + checkArgument( + checkpoint.getRoot().equals(blockSummary.getRoot()), "Checkpoint and block must match"); + checkArgument( + checkpoint.getEpochStartSlot(spec).isGreaterThanOrEqualTo(blockSlot), + "Block must be at or prior to the start of the checkpoint epoch"); } public boolean isGenesis() { From e05a5d73d5e38d2fc62b360439ed15ece161aad7 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 17:26:20 +0200 Subject: [PATCH 10/25] change comment --- .../pegasys/teku/spec/datastructures/state/AnchorPoint.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 347e7436fbf..214c84f51d0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -121,7 +121,10 @@ public static AnchorPoint fromInitialBlockAndState( return new AnchorPoint(spec, checkpoint, state, block); } - /** Skipping verification in the super class. All checks are made in {@link #verifyAnchor()} */ + /** + * Skipping verification in the super class. All checks are made in {@link #verifyAnchor()} + * instead + */ @Override protected void verifyStateAndBlockConsistency() {} From 1684b686e1df2ac8281807a17af36e765a8f116c Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 18 Apr 2024 18:58:51 +0400 Subject: [PATCH 11/25] Transitioned anchor startup with acceptance test --- .../TransitionedAnchorAcceptanceTest.java | 143 ++++++++++++++++++ .../test/acceptance/dsl/TekuBeaconNode.java | 19 +++ .../coordinator/DepositProviderTest.java | 27 ++++ .../tekuv1/beacon/GetStateByBlockRoot.java | 1 + .../datastructures/state/AnchorPoint.java | 6 + .../helpers/MiscHelpersBellatrixTest.java | 59 ++++++++ .../statetransition/block/BlockImporter.java | 5 +- .../validation/AttestationStateSelector.java | 1 + .../teku/storage/api/FinalizedChainData.java | 4 +- .../teku/storage/client/RecentChainData.java | 2 + .../pegasys/teku/storage/store/Store.java | 9 +- 11 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java create mode 100644 ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/bellatrix/helpers/MiscHelpersBellatrixTest.java diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java new file mode 100644 index 00000000000..e41c4035542 --- /dev/null +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java @@ -0,0 +1,143 @@ +/* + * 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.test.acceptance; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.io.Resources; +import java.net.URL; +import java.security.SecureRandom; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.test.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.teku.test.acceptance.dsl.GenesisGenerator.InitialStateData; +import tech.pegasys.teku.test.acceptance.dsl.TekuBeaconNode; +import tech.pegasys.teku.test.acceptance.dsl.TekuNodeConfigBuilder; +import tech.pegasys.teku.test.acceptance.dsl.tools.deposits.ValidatorKeys; +import tech.pegasys.teku.test.acceptance.dsl.tools.deposits.ValidatorKeystores; + +public class TransitionedAnchorAcceptanceTest extends AcceptanceTestBase { + private static final URL JWT_FILE = Resources.getResource("auth/ee-jwt-secret.hex"); + // SEED is chosen to have empty slots in the end of 3rd epoch. + // TODO: Good to find one with earlier end epoch empty slots + private static final byte[] SEED = new byte[] {0x11, 0x03, 0x04}; + + @Test + @SuppressWarnings("DoNotCreateSecureRandomDirectly") + void shouldMaintainValidatorsInMutableClient() throws Exception { + final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG"); + rnd.setSeed(SEED); + final String networkName = "swift"; + + final List node1Validators = + IntStream.range(0, 16).mapToObj(__ -> BLSKeyPair.random(rnd)).toList(); + final List node2Validators = + IntStream.range(0, 3).mapToObj(__ -> BLSKeyPair.random(rnd)).toList(); + + final ValidatorKeystores node1ValidatorKeystores = + new ValidatorKeystores( + node1Validators.stream() + .map(keyPair -> new ValidatorKeys(keyPair, Bytes32.random(rnd), false)) + .toList()); + // will be non-active in the beginning + final ValidatorKeystores node2ValidatorKeystores = + new ValidatorKeystores( + node2Validators.stream() + .map(keyPair -> new ValidatorKeys(keyPair, Bytes32.random(rnd), false)) + .toList()); + + final InitialStateData genesis = + createGenesisGenerator() + .network(networkName) + .withAltairEpoch(UInt64.ZERO) + .withBellatrixEpoch(UInt64.ZERO) + .validatorKeys(node1ValidatorKeystores, node2ValidatorKeystores) + .generate(); + + final String node1FeeRecipient = "0xFE3B557E8Fb62b89F4916B721be55cEb828dBd73"; + final TekuBeaconNode beaconNode1 = + createTekuBeaconNode( + TekuNodeConfigBuilder.createBeaconNode() + .withStubExecutionEngine() + .withJwtSecretFile(JWT_FILE) + .withNetwork(networkName) + .withRealNetwork() + .withInitialState(genesis) + .withAltairEpoch(UInt64.ZERO) + .withBellatrixEpoch(UInt64.ZERO) + .withValidatorProposerDefaultFeeRecipient(node1FeeRecipient) + .withReadOnlyKeystorePath(node1ValidatorKeystores) + .build()); + + beaconNode1.start(); + beaconNode1.waitForFinalizationAfter(UInt64.valueOf(3)); + + final BeaconState finalizedState = beaconNode1.fetchFinalizedState(); + final UInt64 finalizedSlot = finalizedState.getSlot(); + final UInt64 finalizedEpoch = beaconNode1.getSpec().computeEpochAtSlot(finalizedSlot); + final UInt64 finalizedEpochFirstSlot = + beaconNode1.getSpec().computeStartSlotAtEpoch(finalizedEpoch); + assertNotEquals(finalizedSlot, finalizedEpochFirstSlot); + final UInt64 maxFinalizedSlot = + beaconNode1.getSpec().computeStartSlotAtEpoch(finalizedEpoch.plus(1)); + System.out.println( + "State slot: " + + finalizedState.getSlot() + + ", maxSlot: " + + maxFinalizedSlot + + ", finalized Epoch: " + + finalizedEpoch); + assertTrue(finalizedState.getSlot().isLessThan(maxFinalizedSlot)); + final BeaconState transitionedFinalizedState = + beaconNode1.getSpec().processSlots(finalizedState, maxFinalizedSlot); + + final String node2FeeRecipient = "0xfe3b557E8Fb62B89F4916B721be55ceB828DBd55"; + final TekuBeaconNode beaconNode2 = + createTekuBeaconNode( + TekuNodeConfigBuilder.createBeaconNode() + .withStubExecutionEngine() + .withJwtSecretFile(JWT_FILE) + .withNetwork(networkName) + .withRealNetwork() + .withInitialState(new InitialStateData(transitionedFinalizedState)) + .withAltairEpoch(UInt64.ZERO) + .withBellatrixEpoch(UInt64.ZERO) + .withValidatorProposerDefaultFeeRecipient(node2FeeRecipient) + .withReadOnlyKeystorePath(node2ValidatorKeystores) + .withPeers(beaconNode1) + .build()); + + beaconNode2.start(); + beaconNode2.waitUntilInSyncWith(beaconNode1); + beaconNode2.waitForBlockSatisfying( + block -> { + assertThat(block).isInstanceOf(SignedBeaconBlockBellatrix.class); + final SignedBeaconBlockBellatrix bellatrixBlock = (SignedBeaconBlockBellatrix) block; + assertEquals( + Bytes20.fromHexString(node2FeeRecipient), + bellatrixBlock.getMessage().getBody().executionPayload.feeRecipient); + }); + beaconNode2.waitForNewFinalization(); + } +} diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java index cec4c8294f7..1574606dc47 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java @@ -456,6 +456,16 @@ public void waitForNewFinalization() { MINUTES); } + public void waitForFinalizationAfter(final UInt64 newFinalizedEpoch) { + LOG.debug("Wait for finalized block"); + waitFor( + () -> + assertThat(fetchStateFinalityCheckpoints().orElseThrow().finalized.epoch) + .isGreaterThanOrEqualTo(newFinalizedEpoch), + 9, + MINUTES); + } + public void waitForMilestone(final SpecMilestone expectedMilestone) { waitForLogMessageContaining("Activating network upgrade: " + expectedMilestone.name()); } @@ -779,4 +789,13 @@ public void expectElOffline() throws IOException { public void expectElOnline() throws IOException { assertThat(fetchSyncStatus().data.elOffline).isFalse(); } + + public BeaconState fetchFinalizedState() throws IOException { + final Bytes beaconStateBytes = + httpClient.getAsBytes( + getRestApiUrl(), + "eth/v2/debug/beacon/states/finalized", + Map.of("Accept", "application/octet-stream")); + return spec.deserializeBeaconState(Bytes.wrap(beaconStateBytes)); + } } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java index 47d27a2c759..2fed118cd78 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java @@ -482,6 +482,33 @@ void whenCallingForFinalizedSnapshotAndSnapshotAvailable_SnapshotReturned() { .isEqualTo(UInt64.valueOf(30)); } + @Test + void whenUsingTransitionedAnchorPoint_FinalizationIsNotFailed() throws Exception { + setup(16); + Bytes32 finalizedBlockRoot = Bytes32.fromHexString("0x01"); + updateStateEth1DepositIndex(10); + updateStateEth1DataDepositCount(10); + mockDepositsFromEth1Block(0, 20); + final AnchorPoint anchorPoint = mock(AnchorPoint.class); + final UpdatableStore store = mock(UpdatableStore.class); + when(recentChainData.getStore()).thenReturn(store); + when(store.getLatestFinalized()).thenReturn(anchorPoint); + final BeaconState transitionedState = spec.processSlots(state, state.getSlot().plus(2)); + when(anchorPoint.getState()).thenAnswer(__ -> transitionedState); + when(eth1DataCache.getEth1DataAndHeight(eq(transitionedState.getEth1Data()))) + .thenReturn(Optional.empty()); + + assertThat(depositProvider.getDepositMapSize()).isEqualTo(20); + + depositProvider.onNewFinalizedCheckpoint(new Checkpoint(UInt64.ONE, finalizedBlockRoot), false); + + assertThat(depositProvider.getDepositMapSize()).isEqualTo(10); + List availableDeposits = depositProvider.getAvailableDeposits(); + assertThat(availableDeposits.size()).isEqualTo(10); + + verify(eth1DataCache).getEth1DataAndHeight(eq(transitionedState.getEth1Data())); + } + private void checkThatDepositProofIsValid(SszList deposits) { final SpecVersion genesisSpec = spec.getGenesisSpec(); deposits.forEach( diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java index 1303ad2454d..6ad3c6392e5 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java @@ -69,6 +69,7 @@ public void handleRequest(RestApiRequest request) throws JsonProcessingException request.header(Header.CACHE_CONTROL, CACHE_NONE); final String blockId = request.getPathParameter(PARAMETER_BLOCK_ID); + // FIXME: not sure. Are we ok to return here transitioned state on keyword finalized? SafeFuture> future = chainDataProvider.getBeaconStateByBlockId(blockId); request.respondAsync( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 214c84f51d0..959f0e92b5f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -38,6 +38,7 @@ public class AnchorPoint extends StateAndBlockSummary { private final Spec spec; private final Checkpoint checkpoint; private final boolean isGenesis; + private final boolean isTransitioned; private AnchorPoint( final Spec spec, @@ -48,6 +49,7 @@ private AnchorPoint( this.spec = spec; this.checkpoint = checkpoint; this.isGenesis = checkpoint.getEpoch().equals(SpecConfig.GENESIS_EPOCH); + this.isTransitioned = state.getSlot().isGreaterThan(blockSummary.getSlot()); verifyAnchor(); } @@ -177,6 +179,10 @@ public UInt64 getEpochStartSlot() { return checkpoint.getEpochStartSlot(spec); } + public boolean isTransitioned() { + return isTransitioned; + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/bellatrix/helpers/MiscHelpersBellatrixTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/bellatrix/helpers/MiscHelpersBellatrixTest.java new file mode 100644 index 00000000000..d6bf06fb1a5 --- /dev/null +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/bellatrix/helpers/MiscHelpersBellatrixTest.java @@ -0,0 +1,59 @@ +/* + * 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.versions.bellatrix.helpers; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +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.bellatrix.BeaconStateBellatrix; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +class MiscHelpersBellatrixTest { + private final Spec spec = TestSpecFactory.createMinimalBellatrix(); + private final MiscHelpersBellatrix miscHelpersBellatrix = + new MiscHelpersBellatrix(spec.getGenesisSpecConfig().toVersionBellatrix().orElseThrow()); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + + @Test + public void shouldHaveCorrectTransitionCompleteWithTransitionedState() throws Exception { + final ExecutionPayloadHeader defaultPayloadHeader = + SchemaDefinitionsBellatrix.required(spec.getGenesisSchemaDefinitions()) + .getExecutionPayloadHeaderSchema() + .getDefault(); + final BeaconStateBellatrix preMerge = + dataStructureUtil + .stateBuilderBellatrix(32, 5) + .latestExecutionPayloadHeader(defaultPayloadHeader) + .build(); + final BeaconStateBellatrix postMerge = + dataStructureUtil + .stateBuilderBellatrix(32, 5) + .latestExecutionPayloadHeader(dataStructureUtil.randomExecutionPayloadHeader()) + .build(); + final BeaconState preMergeTransitioned = + spec.processSlots(preMerge, preMerge.getSlot().plus(2)); + final BeaconState postMergeTransitioned = + spec.processSlots(postMerge, postMerge.getSlot().plus(2)); + + assertThat(miscHelpersBellatrix.isMergeTransitionComplete(preMerge)).isFalse(); + assertThat(miscHelpersBellatrix.isMergeTransitionComplete(preMergeTransitioned)).isFalse(); + assertThat(miscHelpersBellatrix.isMergeTransitionComplete(postMerge)).isTrue(); + assertThat(miscHelpersBellatrix.isMergeTransitionComplete(postMergeTransitioned)).isTrue(); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockImporter.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockImporter.java index ae1b8b794fa..e78a10f2c14 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockImporter.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/block/BlockImporter.java @@ -172,9 +172,8 @@ SafeFuture getLatestCheckpointState() { if (finalizedCheckpoint != null && recentChainData .getStore() - .getLatestFinalized() - .getRoot() - .equals(finalizedCheckpoint.getRoot())) { + .getFinalizedCheckpoint() + .equals(finalizedCheckpoint.getCheckpoint())) { return SafeFuture.completedFuture(finalizedCheckpoint); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelector.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelector.java index 1304931c687..4988d368613 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelector.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelector.java @@ -88,6 +88,7 @@ public SafeFuture> getStateToValidate( // If the attestation is within the lookahead period for the finalized state, use that // If the target block doesn't descend from finalized the attestation is invalid final BeaconState finalizedState = recentChainData.getStore().getLatestFinalized().getState(); + // TODO: Test if (finalizedState.getSlot().isGreaterThanOrEqualTo(earliestSlot)) { appliedSelectorRule.labels("attestation_within_lookahead").inc(); return completedFuture(Optional.of(finalizedState)); diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/FinalizedChainData.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/FinalizedChainData.java index 83ba9723161..6e64531ecae 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/FinalizedChainData.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/FinalizedChainData.java @@ -98,7 +98,9 @@ public Builder latestFinalized(final AnchorPoint latestFinalized) { latestFinalized .getSignedBeaconBlock() .orElseThrow(() -> new IllegalStateException("Missing newly finalized block"))); - this.finalizedStates.put(latestFinalized.getRoot(), latestFinalized.getState()); + if (!latestFinalized.isTransitioned()) { + this.finalizedStates.put(latestFinalized.getRoot(), latestFinalized.getState()); + } finalizedChildAndParent(latestFinalized.getRoot(), latestFinalized.getParentRoot()); return this; } 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 5f8a6b9ddf3..a5e3dd19d11 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 @@ -301,6 +301,7 @@ public void updateHead(Bytes32 root, UInt64 currentSlot) { final Optional originalChainHead = chainHead; final ReadOnlyForkChoiceStrategy forkChoiceStrategy = store.getForkChoiceStrategy(); + // FIXME: with transitioned stateRoot already final Optional maybeBlockData = forkChoiceStrategy.getBlockData(root); if (maybeBlockData.isEmpty()) { LOG.error( @@ -375,6 +376,7 @@ private Optional computeReorgContext( return optionalReorgContext; } + // TODO: Test the consequences of having transitioned chainHead private ChainHead createNewChainHead( final Bytes32 root, final UInt64 currentSlot, final ProtoNodeData blockData) { final SafeFuture chainHeadStateFuture = diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java index d6478bd571d..b336e5b2ad2 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java @@ -173,7 +173,9 @@ private Store( // Track latest finalized block this.finalizedAnchor = finalizedAnchor; this.maybeEpochStates = maybeEpochStates; - states.cache(finalizedAnchor.getRoot(), finalizedAnchor); + if (!finalizedAnchor.isTransitioned()) { + states.cache(finalizedAnchor.getRoot(), finalizedAnchor); + } this.finalizedOptimisticTransitionPayload = finalizedOptimisticTransitionPayload; // Set up block provider to draw from in-memory blocks @@ -466,6 +468,7 @@ public Checkpoint getFinalizedCheckpoint() { } } + // TODO: Test me @Override public AnchorPoint getLatestFinalized() { readLock.lock(); @@ -674,6 +677,7 @@ public SafeFuture> retrieveStateAtSlot( new StateAtSlotTask(spec, slotAndBlockRoot, this::retrieveBlockState)); } + // TODO: Test me @Override public SafeFuture retrieveFinalizedCheckpointAndState() { final AnchorPoint finalized; @@ -842,6 +846,7 @@ private SafeFuture> getAndCacheBlockAndState( }); } + // TODO: Test me, will return transitioned state private SafeFuture> getOrRegenerateBlockAndState( final Bytes32 blockRoot) { // Avoid generating the hash tree to rebuild if the state is already available. @@ -990,7 +995,7 @@ private Optional getClosestAvailableBlockRootAndState( // If we haven't found a base state yet, we must have walked back to the latest finalized // block, check here for the base state final AnchorPoint finalized = getLatestFinalized(); - if (!treeBuilder.contains(finalized.getRoot())) { + if (!treeBuilder.contains(finalized.getRoot()) || finalized.isTransitioned()) { // We must have finalized a new block while processing and moved past our target root return Optional.empty(); } From 32a75b6785148fda9b406ec6017f66464af1822a Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Thu, 31 Oct 2024 13:59:23 +1000 Subject: [PATCH 12/25] clean up Signed-off-by: Gabriel Fukushima --- .../teku/spec/datastructures/state/AnchorPoint.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java index 959f0e92b5f..ec840d4a52b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/AnchorPoint.java @@ -17,6 +17,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -59,7 +60,9 @@ public static AnchorPoint create( BeaconState state, Optional block) { final BeaconBlockSummary blockSummary = - block.map(a -> a).orElseGet(() -> BeaconBlockHeader.fromState(state)); + block + .map(Function.identity()) + .orElseGet(() -> BeaconBlockHeader.fromState(state)); return new AnchorPoint(spec, checkpoint, state, blockSummary); } @@ -93,7 +96,7 @@ public static AnchorPoint fromInitialState(final Spec spec, final BeaconState st } else { final BeaconBlockHeader header = BeaconBlockHeader.fromState(state); - // Calculate closest epoch boundary to use for the checkpoint + // Calculate the closest epoch boundary to use for the checkpoint final UInt64 epoch = spec.computeNextEpochBoundary(state.getSlot()); final Checkpoint checkpoint = new Checkpoint(epoch, header.hashTreeRoot()); @@ -132,7 +135,7 @@ protected void verifyStateAndBlockConsistency() {} private void verifyAnchor() { final UInt64 blockSlot = blockSummary.getSlot(); - if (state.getSlot().isGreaterThan(blockSlot)) { + if (isTransitioned) { // the finalized state is transitioned with empty slot(s) checkArgument( blockSummary.getStateRoot().equals(state.getLatestBlockHeader().getStateRoot()), From 37aa12a46768a3edb41dc813208b5fb21d0138b4 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Thu, 31 Oct 2024 13:59:52 +1000 Subject: [PATCH 13/25] replace StateAndBlockSummary creation by AnchorPoint so we can handle the state transitioned scenario Signed-off-by: Gabriel Fukushima --- .../pegasys/teku/storage/server/kvstore/KvStoreDatabase.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index e7de1ecc70d..9d623c1ec5e 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -629,7 +629,10 @@ Optional createMemoryStore(final Supplier timeSupplier) { .equals(maybeAnchor.get().getEpochStartSlot(spec)); if (shouldIncludeAnchorBlock && !blockInformation.containsKey(maybeAnchor.get().getRoot())) { final Checkpoint anchor = maybeAnchor.orElseThrow(); - final StateAndBlockSummary latestFinalized = StateAndBlockSummary.create(finalizedState); + // Use AnchorPoint to skip the verification `verifyStateAndBlockConsistency` which doesn't + // allow for state that has been transitioned with an empty block + final StateAndBlockSummary latestFinalized = + AnchorPoint.create(spec, finalizedCheckpoint, finalizedState, Optional.empty()); if (!latestFinalized.getRoot().equals(anchor.getRoot())) { throw new IllegalStateException("Anchor state (" + anchor + ") is unavailable"); } From fc1f9b6f0b3576074ec46e56aab08c0b4abbd52e Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Thu, 31 Oct 2024 14:00:04 +1000 Subject: [PATCH 14/25] pass asyncRunner to builder Signed-off-by: Gabriel Fukushima --- .../tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java index 4dec9e32a9d..348af8fb075 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java @@ -216,6 +216,7 @@ public int getLatestFinalizedState( .map( storeData -> StoreBuilder.create() + .asyncRunner(asyncRunner) .onDiskStoreData(storeData) .metricsSystem(new NoOpMetricsSystem()) .specProvider(eth2NetworkOptions.getNetworkConfiguration().getSpec()) From 18c0fa95f5d2e814cd451375d00bf1465747d114 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Thu, 31 Oct 2024 14:45:35 +1000 Subject: [PATCH 15/25] spotless Signed-off-by: Gabriel Fukushima --- .../coordinator/DepositProviderTest.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java index bea697d682e..192dbcc9322 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java @@ -492,32 +492,32 @@ void whenCallingForFinalizedSnapshotAndSnapshotAvailable_SnapshotReturned() { .isEqualTo(UInt64.valueOf(30)); } - @Test - void whenUsingTransitionedAnchorPoint_FinalizationIsNotFailed() throws Exception { - setup(16); - Bytes32 finalizedBlockRoot = Bytes32.fromHexString("0x01"); - updateStateEth1DepositIndex(10); - updateStateEth1DataDepositCount(10); - mockDepositsFromEth1Block(0, 20); - final AnchorPoint anchorPoint = mock(AnchorPoint.class); - final UpdatableStore store = mock(UpdatableStore.class); - when(recentChainData.getStore()).thenReturn(store); - when(store.getLatestFinalized()).thenReturn(anchorPoint); - final BeaconState transitionedState = spec.processSlots(state, state.getSlot().plus(2)); - when(anchorPoint.getState()).thenAnswer(__ -> transitionedState); - when(eth1DataCache.getEth1DataAndHeight(eq(transitionedState.getEth1Data()))) - .thenReturn(Optional.empty()); - - assertThat(depositProvider.getDepositMapSize()).isEqualTo(20); - - depositProvider.onNewFinalizedCheckpoint(new Checkpoint(UInt64.ONE, finalizedBlockRoot), false); - - assertThat(depositProvider.getDepositMapSize()).isEqualTo(10); - List availableDeposits = depositProvider.getAvailableDeposits(); - assertThat(availableDeposits.size()).isEqualTo(10); - - verify(eth1DataCache).getEth1DataAndHeight(eq(transitionedState.getEth1Data())); - } + @Test + void whenUsingTransitionedAnchorPoint_FinalizationIsNotFailed() throws Exception { + setup(16); + Bytes32 finalizedBlockRoot = Bytes32.fromHexString("0x01"); + updateStateEth1DepositIndex(10); + updateStateEth1DataDepositCount(10); + mockDepositsFromEth1Block(0, 20); + final AnchorPoint anchorPoint = mock(AnchorPoint.class); + final UpdatableStore store = mock(UpdatableStore.class); + when(recentChainData.getStore()).thenReturn(store); + when(store.getLatestFinalized()).thenReturn(anchorPoint); + final BeaconState transitionedState = spec.processSlots(state, state.getSlot().plus(2)); + when(anchorPoint.getState()).thenAnswer(__ -> transitionedState); + when(eth1DataCache.getEth1DataAndHeight(eq(transitionedState.getEth1Data()))) + .thenReturn(Optional.empty()); + + assertThat(depositProvider.getDepositMapSize()).isEqualTo(20); + + depositProvider.onNewFinalizedCheckpoint(new Checkpoint(UInt64.ONE, finalizedBlockRoot), false); + + assertThat(depositProvider.getDepositMapSize()).isEqualTo(10); + List availableDeposits = depositProvider.getAvailableDeposits(); + assertThat(availableDeposits.size()).isEqualTo(10); + + verify(eth1DataCache).getEth1DataAndHeight(eq(transitionedState.getEth1Data())); + } private void checkThatDepositProofIsValid(final List depositsWithIndex) { final SpecVersion genesisSpec = spec.getGenesisSpec(); From 6efd31a9ee282e5f3792b6896058651619eef72a Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Tue, 5 Nov 2024 14:29:05 +1000 Subject: [PATCH 16/25] add test for chain head Signed-off-by: Gabriel Fukushima --- .../storage/client/RecentChainDataTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java index c040f5e291b..5de323262b8 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java @@ -52,6 +52,8 @@ import tech.pegasys.teku.spec.generator.ChainBuilder.BlockOptions; import tech.pegasys.teku.spec.generator.ChainProperties; import tech.pegasys.teku.spec.logic.common.block.AbstractBlockProcessor; +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.util.DataStructureUtil; import tech.pegasys.teku.storage.api.TrackingChainHeadChannel.HeadEvent; import tech.pegasys.teku.storage.api.TrackingChainHeadChannel.ReorgEvent; @@ -251,6 +253,26 @@ public void updateHead_blockAndStateAreMissing() { .contains(genesis.getRoot()); } + @Test + void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { + initPreGenesis(); + final ChainBuilder chainBuilder = ChainBuilder.create(spec); + final UInt64 genesisTime = UInt64.valueOf(5000); + chainBuilder.generateGenesis(genesisTime, true); + chainBuilder.generateBlocksUpToSlot(7); + + final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); + final BeaconState transitionedState = spec.processSlots(lastState.getState(), UInt64.valueOf(8)); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); + + final SignedBlockAndState transitionedStateAfterABlock = chainBuilder.generateBlockAtSlot(9); + + updateHead(recentChainData, transitionedStateAfterABlock); + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getParentRoot)) + .contains(anchorPoint.getRoot()); + } + @Test void retrieveStateInEffectAtSlot_returnEmptyWhenStoreNotSet() { initPreGenesis(); From 3e733225b67d9d581a9282fe7f561e21c46c3cad Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Tue, 5 Nov 2024 14:29:22 +1000 Subject: [PATCH 17/25] update comment Signed-off-by: Gabriel Fukushima --- .../java/tech/pegasys/teku/storage/client/RecentChainData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 394dc4f0db8..a47b8628dd7 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 @@ -302,7 +302,7 @@ public void updateHead(final Bytes32 root, final UInt64 currentSlot) { final Optional originalChainHead = chainHead; final ReadOnlyForkChoiceStrategy forkChoiceStrategy = store.getForkChoiceStrategy(); - // FIXME: with transitioned stateRoot already + // FIXME: with transitioned stateRoot already, clarify with Dmitrii what he meant by fix me here final Optional maybeBlockData = forkChoiceStrategy.getBlockData(root); if (maybeBlockData.isEmpty()) { LOG.error( From 23ab4973cddb602a9e147edc7d57dc4cdfe11fea Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Fri, 22 Nov 2024 16:44:37 +1000 Subject: [PATCH 18/25] remove fixme Signed-off-by: Gabriel Fukushima --- .../handlers/tekuv1/beacon/GetStateByBlockRoot.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java index 37c73bdf034..e1eed90f9e2 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java @@ -70,8 +70,7 @@ public void handleRequest(final RestApiRequest request) throws JsonProcessingExc request.header(Header.CACHE_CONTROL, CACHE_NONE); final String blockId = request.getPathParameter(PARAMETER_BLOCK_ID); - // FIXME: not sure. Are we ok to return here transitioned state on keyword finalized? - SafeFuture> future = chainDataProvider.getBeaconStateByBlockId(blockId); + SafeFuture> future = chainDataProvider.getBeaconStateByBlockId(blockId); request.respondAsync( future.thenApply( From 6b9b01776a0e169634febde78076c46ba82047a3 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Fri, 22 Nov 2024 16:44:56 +1000 Subject: [PATCH 19/25] remove fixme and add unit tests Signed-off-by: Gabriel Fukushima --- .../teku/storage/client/RecentChainData.java | 2 +- .../storage/client/RecentChainDataTest.java | 60 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) 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 a47b8628dd7..bea9ce0c720 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 @@ -302,7 +302,7 @@ public void updateHead(final Bytes32 root, final UInt64 currentSlot) { final Optional originalChainHead = chainHead; final ReadOnlyForkChoiceStrategy forkChoiceStrategy = store.getForkChoiceStrategy(); - // FIXME: with transitioned stateRoot already, clarify with Dmitrii what he meant by fix me here + final Optional maybeBlockData = forkChoiceStrategy.getBlockData(root); if (maybeBlockData.isEmpty()) { LOG.error( diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java index 5de323262b8..4b109f5c841 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java @@ -253,6 +253,64 @@ public void updateHead_blockAndStateAreMissing() { .contains(genesis.getRoot()); } + @Test + void createHead_upOnStatingWithNonTransitionedState() throws SlotProcessingException, EpochProcessingException { + initPreGenesis(); + final long lastNonEmptyBlockSlot = 7; + final ChainBuilder chainBuilder = ChainBuilder.create(spec); + final UInt64 genesisTime = UInt64.valueOf(1000); + chainBuilder.generateGenesis(genesisTime, true); + chainBuilder.generateBlocksUpToSlot(7); + + final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, lastState.getState()); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); + + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getRoot)) + .contains(anchorPoint.getRoot()); + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getSlot)) + .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); + } + + @Test + void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { + initPreGenesis(); + final long lastNonEmptyBlockSlot = 7; + final ChainBuilder chainBuilder = ChainBuilder.create(spec); + final UInt64 genesisTime = UInt64.valueOf(1000); + chainBuilder.generateGenesis(genesisTime, true); + chainBuilder.generateBlocksUpToSlot(7); + + final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); + final BeaconState transitionedState = spec.processSlots(lastState.getState(), UInt64.valueOf(8)); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); + + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getRoot)) + .contains(anchorPoint.getRoot()); + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getSlot)) + .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); + } + + @Test + void updateHead_afterStatingWithNonTransitionedState() throws SlotProcessingException, EpochProcessingException { + initPreGenesis(); + final ChainBuilder chainBuilder = ChainBuilder.create(spec); + final UInt64 genesisTime = UInt64.valueOf(5000); + chainBuilder.generateGenesis(genesisTime, true); + chainBuilder.generateBlocksUpToSlot(7); + + final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, lastState.getState()); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); + + final SignedBlockAndState transitionedStateAfterABlock = chainBuilder.generateBlockAtSlot(9); + + updateHead(recentChainData, transitionedStateAfterABlock); + assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getParentRoot)) + .contains(anchorPoint.getRoot()); + } + @Test void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { initPreGenesis(); @@ -270,7 +328,7 @@ void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotPro updateHead(recentChainData, transitionedStateAfterABlock); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getParentRoot)) - .contains(anchorPoint.getRoot()); + .contains(anchorPoint.getRoot()); } @Test From a5b201ea839d922480fcbdaaef26c18182aa25b4 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Fri, 22 Nov 2024 16:45:21 +1000 Subject: [PATCH 20/25] remove fixme Signed-off-by: Gabriel Fukushima --- .../java/tech/pegasys/teku/storage/client/RecentChainData.java | 1 - 1 file changed, 1 deletion(-) 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 bea9ce0c720..5a71c7b095b 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 @@ -377,7 +377,6 @@ private Optional computeReorgContext( return optionalReorgContext; } - // TODO: Test the consequences of having transitioned chainHead private ChainHead createNewChainHead( final Bytes32 root, final UInt64 currentSlot, final ProtoNodeData blockData) { final SafeFuture chainHeadStateFuture = From f7ff2837d5be51564a6ae1c3e74a7ed4e68d1883 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Tue, 26 Nov 2024 18:06:16 +1000 Subject: [PATCH 21/25] Add tests for attestationStateSelector using a simulated transitioned state Signed-off-by: Gabriel Fukushima --- .../AttestationStateSelectorTest.java | 73 +++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java index c46c081802f..da9dff69c04 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.bls.BLSSignatureVerifier; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -34,9 +33,12 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.generator.ChainBuilder; import tech.pegasys.teku.spec.logic.common.block.AbstractBlockProcessor; +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.util.DataStructureUtil; import tech.pegasys.teku.storage.client.ChainUpdater; import tech.pegasys.teku.storage.client.RecentChainData; @@ -67,14 +69,15 @@ public static void resetSession() { AbstractBlockProcessor.DEFAULT_DEPOSIT_SIGNATURE_VERIFIER; } - @BeforeEach - void setUp() { + + private void setUp() { chainUpdater.initializeGenesis(false); chainUpdater.updateBestBlock(chainUpdater.advanceChain(5)); } @Test void shouldSelectChainHeadWhenBlockRootIsChainHeadRoot() { + setUp(); final SafeFuture> result = selectStateFor( recentChainData.getHeadSlot(), recentChainData.getBestBlockRoot().orElseThrow()); @@ -85,6 +88,7 @@ void shouldSelectChainHeadWhenBlockRootIsChainHeadRoot() { @Test void shouldRollChainHeadForwardWhenBlockRootIsChainHeadRootButSlotIsTooFarInFuture() throws Exception { + setUp(); final UInt64 attestationEpoch = spec.computeEpochAtSlot(recentChainData.getHeadSlot()).plus(2); final UInt64 attestationSlot = spec.computeStartSlotAtEpoch(attestationEpoch).plus(3); final UInt64 requiredStateSlot = @@ -99,8 +103,9 @@ void shouldRollChainHeadForwardWhenBlockRootIsChainHeadRootButSlotIsTooFarInFutu @Test void shouldUseFinalizedStateIfWithinLookaheadPeriod() { + setUp(); // Finalized is currently genesis so can look ahead up to the end of epoch 1 - final UInt64 attestationSlot = spec.computeStartSlotAtEpoch(UInt64.valueOf(2)).minus(1); + final UInt64 attestationSlot = spec.computeEndSlotAtEpoch(UInt64.valueOf(1)); // Block root doesn't matter because to be valid the target block must descend from finalized final Bytes32 blockRoot = dataStructureUtil.randomBytes32(); final SafeFuture> result = selectStateFor(attestationSlot, blockRoot); @@ -112,6 +117,7 @@ void shouldUseFinalizedStateIfWithinLookaheadPeriod() { @Test void shouldUseHeadStateIfAttestationInSameEpochAndBlockRootAncestorOfHead() { + setUp(); // Advance chain so finalized checkpoint isn't suitable chainUpdater.updateBestBlock(chainUpdater.advanceChainUntil(25)); final SignedBlockAndState chainHead = chainBuilder.getLatestBlockAndState(); @@ -126,6 +132,7 @@ void shouldUseHeadStateIfAttestationInSameEpochAndBlockRootAncestorOfHead() { @Test void shouldUseHeadStateWhenAttestationIsForAncestorFromEarlierEpochWithinHistoricVector() { + setUp(); // Finalized is also suitable but we should prefer using the chain head since it will have the // most up to date caches chainUpdater.updateBestBlock( @@ -145,6 +152,7 @@ void shouldUseHeadStateWhenAttestationIsForAncestorFromEarlierEpochWithinHistori @Test void shouldNotRegenerateStateForAttestationsWithTargetWhichIsAlreadyJustified() throws ExecutionException, InterruptedException { + setUp(); // Advance the chain far enough that the finalized checkpoint isn't usable chainUpdater.updateBestBlock( chainUpdater.advanceChainUntil(spec.computeStartSlotAtEpoch(UInt64.valueOf(3)))); @@ -178,8 +186,63 @@ void shouldNotRegenerateStateForAttestationsWithTargetWhichIsAlreadyJustified() assertThatSafeFuture(resultWithNoCache).isCompletedWithEmptyOptional(); } + @Test + void shouldAttestToPreviousSlotUsingTransitionedState() throws SlotProcessingException, EpochProcessingException, ExecutionException, InterruptedException { + + final StorageSystem storageSystem2 = InMemoryStorageSystemBuilder.buildDefault(spec); + final ChainUpdater chainUpdater2 = storageSystem2.chainUpdater(); + final RecentChainData recentChainData2 = storageSystem2.recentChainData(); + + chainUpdater2.initializeGenesis(false); + chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(11)); + +// chainUpdater2.updateBestBlock(chainUpdater.advanceChain(11)); + BeaconState transitionedState = spec.processSlots(recentChainData2.getBestState().get().get(),UInt64.valueOf(12)); + chainUpdater2.finalizeEpoch(UInt64.valueOf(0)); + + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); + recentChainData.initializeFromAnchorPoint(anchorPoint,UInt64.valueOf(1000)); + + final UInt64 attestationSlot = recentChainData2.getHeadSlot(); + final Bytes32 blockRoot = recentChainData2.getBlockRootInEffectBySlot(attestationSlot).get(); + + final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); + final SafeFuture> result = selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); + final BeaconState selectedState = safeJoin(result).orElseThrow(); + assertThat(selectedState).isEqualTo(transitionedState); + + } + + @Test + void shouldAttestToNextSlotUsingTransitionedState() throws SlotProcessingException, EpochProcessingException, ExecutionException, InterruptedException { + + final StorageSystem storageSystem2 = InMemoryStorageSystemBuilder.buildDefault(spec); + final ChainUpdater chainUpdater2 = storageSystem2.chainUpdater(); + final RecentChainData recentChainData2 = storageSystem2.recentChainData(); + + chainUpdater2.initializeGenesis(false); + chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(11)); + + + BeaconState transitionedState = spec.processSlots(recentChainData2.getBestState().get().get(),UInt64.valueOf(12)); + chainUpdater2.finalizeEpoch(UInt64.valueOf(0)); + + chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(13)); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); + recentChainData.initializeFromAnchorPoint(anchorPoint,UInt64.valueOf(1000)); + + final UInt64 attestationSlot = recentChainData2.getHeadSlot(); + final Bytes32 blockRoot = recentChainData2.getBlockRootInEffectBySlot(attestationSlot).get(); + + final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); + final SafeFuture> result = selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); + final BeaconState selectedState = safeJoin(result).orElseThrow(); + assertThat(selectedState).isEqualTo(transitionedState); + + } + private SafeFuture> selectStateFor( - final UInt64 attestationSlot, final Bytes32 blockRoot) { + final UInt64 attestationSlot, final Bytes32 blockRoot) { final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); final SafeFuture> result = selector.getStateToValidate(attestationData); final Optional maybeState = safeJoin(result); From ed148a8f200a216fece3cbce45e059493c71bcc4 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Tue, 26 Nov 2024 18:07:14 +1000 Subject: [PATCH 22/25] spotless Signed-off-by: Gabriel Fukushima --- .../tekuv1/beacon/GetStateByBlockRoot.java | 2 +- .../AttestationStateSelectorTest.java | 36 +++++++++++-------- .../storage/client/RecentChainDataTest.java | 32 ++++++++++------- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java index e1eed90f9e2..34c5dc98b28 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/tekuv1/beacon/GetStateByBlockRoot.java @@ -70,7 +70,7 @@ public void handleRequest(final RestApiRequest request) throws JsonProcessingExc request.header(Header.CACHE_CONTROL, CACHE_NONE); final String blockId = request.getPathParameter(PARAMETER_BLOCK_ID); - SafeFuture> future = chainDataProvider.getBeaconStateByBlockId(blockId); + SafeFuture> future = chainDataProvider.getBeaconStateByBlockId(blockId); request.respondAsync( future.thenApply( diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java index da9dff69c04..30e7294765d 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/AttestationStateSelectorTest.java @@ -69,7 +69,6 @@ public static void resetSession() { AbstractBlockProcessor.DEFAULT_DEPOSIT_SIGNATURE_VERIFIER; } - private void setUp() { chainUpdater.initializeGenesis(false); chainUpdater.updateBestBlock(chainUpdater.advanceChain(5)); @@ -187,7 +186,11 @@ void shouldNotRegenerateStateForAttestationsWithTargetWhichIsAlreadyJustified() } @Test - void shouldAttestToPreviousSlotUsingTransitionedState() throws SlotProcessingException, EpochProcessingException, ExecutionException, InterruptedException { + void shouldAttestToPreviousSlotUsingTransitionedState() + throws SlotProcessingException, + EpochProcessingException, + ExecutionException, + InterruptedException { final StorageSystem storageSystem2 = InMemoryStorageSystemBuilder.buildDefault(spec); final ChainUpdater chainUpdater2 = storageSystem2.chainUpdater(); @@ -196,25 +199,30 @@ void shouldAttestToPreviousSlotUsingTransitionedState() throws SlotProcessingExc chainUpdater2.initializeGenesis(false); chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(11)); -// chainUpdater2.updateBestBlock(chainUpdater.advanceChain(11)); - BeaconState transitionedState = spec.processSlots(recentChainData2.getBestState().get().get(),UInt64.valueOf(12)); + // chainUpdater2.updateBestBlock(chainUpdater.advanceChain(11)); + BeaconState transitionedState = + spec.processSlots(recentChainData2.getBestState().get().get(), UInt64.valueOf(12)); chainUpdater2.finalizeEpoch(UInt64.valueOf(0)); final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); - recentChainData.initializeFromAnchorPoint(anchorPoint,UInt64.valueOf(1000)); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(1000)); final UInt64 attestationSlot = recentChainData2.getHeadSlot(); final Bytes32 blockRoot = recentChainData2.getBlockRootInEffectBySlot(attestationSlot).get(); final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); - final SafeFuture> result = selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); + final SafeFuture> result = + selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); final BeaconState selectedState = safeJoin(result).orElseThrow(); assertThat(selectedState).isEqualTo(transitionedState); - } @Test - void shouldAttestToNextSlotUsingTransitionedState() throws SlotProcessingException, EpochProcessingException, ExecutionException, InterruptedException { + void shouldAttestToNextSlotUsingTransitionedState() + throws SlotProcessingException, + EpochProcessingException, + ExecutionException, + InterruptedException { final StorageSystem storageSystem2 = InMemoryStorageSystemBuilder.buildDefault(spec); final ChainUpdater chainUpdater2 = storageSystem2.chainUpdater(); @@ -223,26 +231,26 @@ void shouldAttestToNextSlotUsingTransitionedState() throws SlotProcessingExcepti chainUpdater2.initializeGenesis(false); chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(11)); - - BeaconState transitionedState = spec.processSlots(recentChainData2.getBestState().get().get(),UInt64.valueOf(12)); + BeaconState transitionedState = + spec.processSlots(recentChainData2.getBestState().get().get(), UInt64.valueOf(12)); chainUpdater2.finalizeEpoch(UInt64.valueOf(0)); chainUpdater2.updateBestBlock(chainUpdater2.advanceChain(13)); final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); - recentChainData.initializeFromAnchorPoint(anchorPoint,UInt64.valueOf(1000)); + recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(1000)); final UInt64 attestationSlot = recentChainData2.getHeadSlot(); final Bytes32 blockRoot = recentChainData2.getBlockRootInEffectBySlot(attestationSlot).get(); final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); - final SafeFuture> result = selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); + final SafeFuture> result = + selectStateFor(attestationData.getSlot(), attestationData.getBeaconBlockRoot()); final BeaconState selectedState = safeJoin(result).orElseThrow(); assertThat(selectedState).isEqualTo(transitionedState); - } private SafeFuture> selectStateFor( - final UInt64 attestationSlot, final Bytes32 blockRoot) { + final UInt64 attestationSlot, final Bytes32 blockRoot) { final AttestationData attestationData = attestationFor(attestationSlot, blockRoot); final SafeFuture> result = selector.getStateToValidate(attestationData); final Optional maybeState = safeJoin(result); diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java index 4b109f5c841..4ce38f7d1ad 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java @@ -254,7 +254,8 @@ public void updateHead_blockAndStateAreMissing() { } @Test - void createHead_upOnStatingWithNonTransitionedState() throws SlotProcessingException, EpochProcessingException { + void createHead_upOnStatingWithNonTransitionedState() + throws SlotProcessingException, EpochProcessingException { initPreGenesis(); final long lastNonEmptyBlockSlot = 7; final ChainBuilder chainBuilder = ChainBuilder.create(spec); @@ -267,13 +268,14 @@ void createHead_upOnStatingWithNonTransitionedState() throws SlotProcessingExcep recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getRoot)) - .contains(anchorPoint.getRoot()); + .contains(anchorPoint.getRoot()); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getSlot)) - .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); + .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); } @Test - void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { + void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() + throws SlotProcessingException, EpochProcessingException { initPreGenesis(); final long lastNonEmptyBlockSlot = 7; final ChainBuilder chainBuilder = ChainBuilder.create(spec); @@ -282,18 +284,20 @@ void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() throws SlotProc chainBuilder.generateBlocksUpToSlot(7); final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); - final BeaconState transitionedState = spec.processSlots(lastState.getState(), UInt64.valueOf(8)); + final BeaconState transitionedState = + spec.processSlots(lastState.getState(), UInt64.valueOf(8)); final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getRoot)) - .contains(anchorPoint.getRoot()); + .contains(anchorPoint.getRoot()); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getSlot)) - .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); + .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); } @Test - void updateHead_afterStatingWithNonTransitionedState() throws SlotProcessingException, EpochProcessingException { + void updateHead_afterStatingWithNonTransitionedState() + throws SlotProcessingException, EpochProcessingException { initPreGenesis(); final ChainBuilder chainBuilder = ChainBuilder.create(spec); final UInt64 genesisTime = UInt64.valueOf(5000); @@ -301,18 +305,19 @@ void updateHead_afterStatingWithNonTransitionedState() throws SlotProcessingExce chainBuilder.generateBlocksUpToSlot(7); final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); - final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, lastState.getState()); + final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, lastState.getState()); recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); final SignedBlockAndState transitionedStateAfterABlock = chainBuilder.generateBlockAtSlot(9); updateHead(recentChainData, transitionedStateAfterABlock); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getParentRoot)) - .contains(anchorPoint.getRoot()); + .contains(anchorPoint.getRoot()); } @Test - void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { + void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() + throws SlotProcessingException, EpochProcessingException { initPreGenesis(); final ChainBuilder chainBuilder = ChainBuilder.create(spec); final UInt64 genesisTime = UInt64.valueOf(5000); @@ -320,7 +325,8 @@ void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotPro chainBuilder.generateBlocksUpToSlot(7); final SignedBlockAndState lastState = chainBuilder.getBlockAndStateAtSlot(7); - final BeaconState transitionedState = spec.processSlots(lastState.getState(), UInt64.valueOf(8)); + final BeaconState transitionedState = + spec.processSlots(lastState.getState(), UInt64.valueOf(8)); final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); @@ -328,7 +334,7 @@ void updateHead_afterStatingWithTransitionedStateWithEmptyBlock() throws SlotPro updateHead(recentChainData, transitionedStateAfterABlock); assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getParentRoot)) - .contains(anchorPoint.getRoot()); + .contains(anchorPoint.getRoot()); } @Test From 97b5279e7ded6876b172039feb527ca0e7d80406 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Wed, 27 Nov 2024 13:32:58 +1000 Subject: [PATCH 23/25] fix AT and dsl Signed-off-by: Gabriel Fukushima --- .../TransitionedAnchorAcceptanceTest.java | 5 ++--- .../test/acceptance/dsl/TekuBeaconNode.java | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java index e41c4035542..f63671c47c7 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java @@ -132,11 +132,10 @@ void shouldMaintainValidatorsInMutableClient() throws Exception { beaconNode2.waitUntilInSyncWith(beaconNode1); beaconNode2.waitForBlockSatisfying( block -> { - assertThat(block).isInstanceOf(SignedBeaconBlockBellatrix.class); - final SignedBeaconBlockBellatrix bellatrixBlock = (SignedBeaconBlockBellatrix) block; + final Bytes20 blockFeeRecipient = block.getMessage().getBody().getOptionalExecutionPayload().get().getFeeRecipient(); assertEquals( Bytes20.fromHexString(node2FeeRecipient), - bellatrixBlock.getMessage().getBody().executionPayload.feeRecipient); + blockFeeRecipient); }); beaconNode2.waitForNewFinalization(); } diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java index 098453619df..454372cf516 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java @@ -51,6 +51,8 @@ import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.api.response.v1.EventType; +import tech.pegasys.teku.api.response.v1.beacon.FinalityCheckpointsResponse; +import tech.pegasys.teku.api.response.v1.beacon.GetStateFinalityCheckpointsResponse; import tech.pegasys.teku.bls.BLS; import tech.pegasys.teku.bls.BLSKeyPair; import tech.pegasys.teku.bls.BLSSecretKey; @@ -458,9 +460,9 @@ public void waitForFinalizationAfter(final UInt64 newFinalizedEpoch) { LOG.debug("Wait for finalized block"); waitFor( () -> - assertThat(fetchStateFinalityCheckpoints().orElseThrow().finalized.epoch) + assertThat(fetchStateFinalityCheckpointEpoch().orElseThrow()) .isGreaterThanOrEqualTo(newFinalizedEpoch), - 9, + 2, MINUTES); } @@ -594,6 +596,18 @@ private Optional> fetchBeaconHeadRootData() throws IOExce return Optional.of(Pair.of(root, executionOptimistic)); } + private Optional fetchStateFinalityCheckpointEpoch() throws IOException { + final String result = + httpClient.get(getRestApiUrl(), "/eth/v1/beacon/states/head/finality_checkpoints"); + if (result.isEmpty()) { + return Optional.empty(); + } + final JsonNode jsonNode = + OBJECT_MAPPER.readTree(result); + final UInt64 finalizedEpoch = UInt64.valueOf(jsonNode.get("data").get("finalized").get("epoch").asText()); + return Optional.of(finalizedEpoch); + } + private Optional fetchBeaconHeadRoot() throws IOException { return fetchBeaconHeadRootData().map(Pair::getLeft); } From 5ddbf66d6b779f459d034b66ceb6a4323eafcdb2 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Wed, 27 Nov 2024 13:46:41 +1000 Subject: [PATCH 24/25] spotless Signed-off-by: Gabriel Fukushima --- .../acceptance/TransitionedAnchorAcceptanceTest.java | 9 +++------ .../teku/test/acceptance/dsl/TekuBeaconNode.java | 10 ++++------ .../teku/storage/client/RecentChainDataTest.java | 5 +++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java index f63671c47c7..8105c9b2ec0 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/TransitionedAnchorAcceptanceTest.java @@ -13,7 +13,6 @@ package tech.pegasys.teku.test.acceptance; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -25,7 +24,6 @@ import java.util.stream.IntStream; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; -import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; import tech.pegasys.teku.bls.BLSKeyPair; import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -132,10 +130,9 @@ void shouldMaintainValidatorsInMutableClient() throws Exception { beaconNode2.waitUntilInSyncWith(beaconNode1); beaconNode2.waitForBlockSatisfying( block -> { - final Bytes20 blockFeeRecipient = block.getMessage().getBody().getOptionalExecutionPayload().get().getFeeRecipient(); - assertEquals( - Bytes20.fromHexString(node2FeeRecipient), - blockFeeRecipient); + final Bytes20 blockFeeRecipient = + block.getMessage().getBody().getOptionalExecutionPayload().get().getFeeRecipient(); + assertEquals(Bytes20.fromHexString(node2FeeRecipient), blockFeeRecipient); }); beaconNode2.waitForNewFinalization(); } diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java index 454372cf516..e24804e9bf7 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java @@ -51,8 +51,6 @@ import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.api.response.v1.EventType; -import tech.pegasys.teku.api.response.v1.beacon.FinalityCheckpointsResponse; -import tech.pegasys.teku.api.response.v1.beacon.GetStateFinalityCheckpointsResponse; import tech.pegasys.teku.bls.BLS; import tech.pegasys.teku.bls.BLSKeyPair; import tech.pegasys.teku.bls.BLSSecretKey; @@ -598,13 +596,13 @@ private Optional> fetchBeaconHeadRootData() throws IOExce private Optional fetchStateFinalityCheckpointEpoch() throws IOException { final String result = - httpClient.get(getRestApiUrl(), "/eth/v1/beacon/states/head/finality_checkpoints"); + httpClient.get(getRestApiUrl(), "/eth/v1/beacon/states/head/finality_checkpoints"); if (result.isEmpty()) { return Optional.empty(); } - final JsonNode jsonNode = - OBJECT_MAPPER.readTree(result); - final UInt64 finalizedEpoch = UInt64.valueOf(jsonNode.get("data").get("finalized").get("epoch").asText()); + final JsonNode jsonNode = OBJECT_MAPPER.readTree(result); + final UInt64 finalizedEpoch = + UInt64.valueOf(jsonNode.get("data").get("finalized").get("epoch").asText()); return Optional.of(finalizedEpoch); } diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java index 4ce38f7d1ad..098b98d0cbf 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java @@ -288,11 +288,12 @@ void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() spec.processSlots(lastState.getState(), UInt64.valueOf(8)); final AnchorPoint anchorPoint = AnchorPoint.fromInitialState(spec, transitionedState); recentChainData.initializeFromAnchorPoint(anchorPoint, UInt64.valueOf(10000)); - + // blockRoot should be from last non-empty block assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getRoot)) .contains(anchorPoint.getRoot()); + // head should be assertThat(recentChainData.getChainHead().map(MinimalBeaconBlockSummary::getSlot)) - .contains(UInt64.valueOf(lastNonEmptyBlockSlot)); + .contains(UInt64.valueOf(8)); } @Test From 2d0a157c47a8e1a3e237e953a4644d79570c41e5 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Wed, 27 Nov 2024 13:57:53 +1000 Subject: [PATCH 25/25] remove this unused variable Signed-off-by: Gabriel Fukushima --- .../tech/pegasys/teku/storage/client/RecentChainDataTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java index 098b98d0cbf..13d74d277b6 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/RecentChainDataTest.java @@ -277,7 +277,6 @@ void createHead_upOnStatingWithNonTransitionedState() void createHead_upOnStatingWithTransitionedStateWithEmptyBlock() throws SlotProcessingException, EpochProcessingException { initPreGenesis(); - final long lastNonEmptyBlockSlot = 7; final ChainBuilder chainBuilder = ChainBuilder.create(spec); final UInt64 genesisTime = UInt64.valueOf(1000); chainBuilder.generateGenesis(genesisTime, true);