From bd487b50c091aa2b4e23595cfe4095107e637d3d Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 19 Feb 2024 10:21:24 +0200 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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(); }