From 855bf5a69f4ae553fa459b63eadc0889d2dbf329 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 30 Oct 2025 21:55:05 -0400 Subject: [PATCH 01/23] refactor: 21915 Introduce StateLifecycleManager Signed-off-by: Ivan Malygin --- .../node/app/HederaVirtualMapState.java | 14 -- .../app/state/merkle/SerializationTest.java | 47 ++-- .../node/app/fixtures/state/FakeState.java | 6 - .../BlockStreamRecoveryWorkflow.java | 10 +- .../otter/fixtures/app/OtterAppState.java | 6 - .../ConsistencyTestingToolState.java | 6 - .../swirlds/demo/iss/ISSTestingToolState.java | 6 - .../migration/MigrationTestingToolState.java | 6 - .../com/swirlds/platform/SwirldsPlatform.java | 21 +- .../platform/builder/PlatformBuilder.java | 8 +- .../builder/PlatformBuildingBlocks.java | 9 +- .../builder/PlatformComponentBuilder.java | 7 +- .../cli/GenesisPlatformStateCommand.java | 11 +- .../DefaultSavedStateController.java | 10 +- .../DefaultTransactionHandler.java | 27 +-- .../platform/gossip/SyncGossipModular.java | 14 +- .../protocol/ReconnectStateSyncProtocol.java | 10 +- .../reconnect/ReconnectController.java | 12 +- .../reconnect/ReconnectStatePeerProtocol.java | 10 +- .../recovery/EventRecoveryWorkflow.java | 13 +- .../platform/state/SwirldStateManager.java | 131 ----------- .../state/editor/StateEditorSave.java | 12 +- .../platform/state/signed/SignedState.java | 3 +- .../snapshot/DefaultStateSnapshotManager.java | 14 +- .../state/snapshot/SignedStateFileWriter.java | 67 +++--- .../SignedStateFileReadWriteTest.java | 26 ++- .../platform/StateFileManagerTests.java | 32 +-- .../DefaultTransactionHandlerTests.java | 6 +- .../TransactionHandlerTester.java | 21 +- .../reconnect/ReconnectControllerTest.java | 14 +- .../ReconnectStatePeerProtocolTests.java | 24 +-- ...s.java => StateLifecycleManagerTests.java} | 33 +-- .../state/StateSignatureCollectorTester.java | 2 +- .../state/signed/StartupStateUtilsTests.java | 16 +- .../com/swirlds/state/MerkleNodeState.java | 8 - .../swirlds/state/MerkleNodeStateAware.java | 16 ++ .../main/java/com/swirlds/state/State.java | 18 -- .../swirlds/state/StateLifecycleManager.java | 63 ++++++ .../merkle/StateLifecycleManagerImpl.java | 203 ++++++++++++++++++ .../swirlds/state/merkle/VirtualMapState.java | 60 ------ .../test/fixtures/merkle/MerkleStateRoot.java | 26 --- .../fixtures/merkle/TestVirtualMapState.java | 8 - 42 files changed, 571 insertions(+), 485 deletions(-) delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java rename platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/{SwirldsStateManagerTests.java => StateLifecycleManagerTests.java} (81%) create mode 100644 platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java create mode 100644 platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java create mode 100644 platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java index bea9ddc01cdb..f00f53f28b08 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java @@ -70,20 +70,6 @@ protected HederaVirtualMapState copyingConstructor() { return new HederaVirtualMapState(this); } - /** - * Creates a new instance of {@link HederaVirtualMapState} with the specified {@link VirtualMap}. - * - * @param virtualMap the virtual map whose metrics must already be registered - * @param metrics the platform metric instance to use when creating the new instance of state - * @param time the time instance to use when creating the new instance of state - * @return a new instance of {@link HederaVirtualMapState} - */ - @Override - protected HederaVirtualMapState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new HederaVirtualMapState(virtualMap, metrics, time); - } - /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index 4eced062e267..22739d3011c9 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -11,8 +11,10 @@ import com.hedera.node.app.spi.fixtures.TestSchema; import com.hedera.node.app.spi.migrate.StartupNetworks; import com.hedera.node.config.data.HederaConfig; +import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.common.merkle.utility.MerkleTreeSnapshotReader; +import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.sources.SimpleConfigSource; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; @@ -20,9 +22,11 @@ import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.merkle.disk.OnDiskReadableKVState; import com.swirlds.state.merkle.disk.OnDiskWritableKVState; import com.swirlds.state.spi.ReadableKVState; @@ -157,11 +161,12 @@ private void forceFlush(ReadableKVState state) { @ParameterizedTest @ValueSource(booleans = {true, false}) void simpleReadAndWrite(boolean forceFlush) throws IOException, ConstructableRegistryException { - final var schemaV1 = createV1Schema(); - final var originalTree = createMerkleHederaState(schemaV1); + final Schema schemaV1 = createV1Schema(); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); // When we serialize it to bytes and deserialize it back into a tree - MerkleNodeState copy = originalTree.copy(); // make a copy to make VM flushable + MerkleNodeState copy = stateLifecycleManager.copyMutableState(); // make a copy to make VM flushable final byte[] serializedBytes; if (forceFlush) { // Force flush the VMs to disk to test serialization and deserialization @@ -188,21 +193,23 @@ void simpleReadAndWrite(boolean forceFlush) throws IOException, ConstructableReg @Test void snapshot() throws IOException { - final var schemaV1 = createV1Schema(); - final var originalTree = createMerkleHederaState(schemaV1); - final var tempDir = LegacyTemporaryFileBuilder.buildTemporaryDirectory(config); + final Schema schemaV1 = createV1Schema(); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final Path tempDir = LegacyTemporaryFileBuilder.buildTemporaryDirectory(config); + final MerkleNodeState originalTree = stateLifecycleManager.getLatestImmutableState(); // prepare the tree and create a snapshot - originalTree.copy().release(); + stateLifecycleManager.getMutableState().release(); originalTree.computeHash(); - originalTree.createSnapshot(tempDir); + stateLifecycleManager.setSnapshotSource(() -> originalTree); + stateLifecycleManager.createSnapshot(tempDir); + originalTree.release(); final MerkleNodeState state = - originalTree.loadSnapshot(tempDir.resolve(MerkleTreeSnapshotReader.SIGNED_STATE_FILE_NAME)); + stateLifecycleManager.loadSnapshot(tempDir.resolve(MerkleTreeSnapshotReader.SIGNED_STATE_FILE_NAME)); initServices(schemaV1, state); assertTree(state); - originalTree.release(); state.release(); } @@ -213,13 +220,16 @@ void snapshot() throws IOException { */ @Test void dualReadAndWrite() throws IOException, ConstructableRegistryException { - final var schemaV1 = createV1Schema(); - final var originalTree = createMerkleHederaState(schemaV1); + final Schema schemaV1 = createV1Schema(); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); - MerkleNodeState copy = originalTree.copy(); // make a copy to make VM flushable + MerkleNodeState copy = stateLifecycleManager.copyMutableState(); // make a copy to make VM flushable forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(FRUIT_STATE_ID)); - copy.copy().release(); // make a fast copy because we can only write to disk an immutable copy + stateLifecycleManager + .copyMutableState() + .release(); // make a fast copy because we can only write to disk an immutable copy copy.getRoot().getHash(); final byte[] serializedBytes = writeTree(copy.getRoot(), dir); @@ -275,7 +285,7 @@ private void initServices(Schema schemaV1, MerkleNodeState load loadedTree.getRoot().migrate(MINIMUM_SUPPORTED_VERSION); } - private MerkleNodeState createMerkleHederaState(Schema schemaV1) { + private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { final SignedState randomState = new RandomSignedStateGenerator().setRound(1).build(); @@ -295,7 +305,12 @@ private MerkleNodeState createMerkleHederaState(Schema schemaV1) { migrationStateChanges, startupNetworks, TEST_PLATFORM_STATE_FACADE); - return originalTreeCopy; + + final StateLifecycleManager stateLifecycleManager = + new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); + + stateLifecycleManager.initState(originalTreeCopy, true); + return stateLifecycleManager; } private static void populateVmCache(State loadedTree) { diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java index 6a5dd4d7b5c4..a8c81e8bae5c 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java @@ -33,7 +33,6 @@ import com.swirlds.state.test.fixtures.MapWritableStates; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; import edu.umd.cs.findbugs.annotations.NonNull; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -303,9 +302,4 @@ public long queueElementPath(final int stateId, @NonNull final Bytes expectedVal public void initializeState(@NonNull final StateMetadata md) { // do nothing } - - @Override - public MerkleNodeState loadSnapshot(@NonNull final Path targetPath) { - throw new UnsupportedOperationException(); - } } diff --git a/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java b/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java index f62a848c74de..5449b1a0ca21 100644 --- a/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java +++ b/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java @@ -8,6 +8,7 @@ import com.hedera.hapi.block.stream.Block; import com.hedera.hapi.block.stream.BlockItem; import com.hedera.hapi.block.stream.output.StateChanges; +import com.hedera.node.app.HederaVirtualMapState; import com.hedera.node.app.hapi.utils.blocks.BlockStreamAccess; import com.hedera.node.app.hapi.utils.blocks.BlockStreamUtils; import com.hedera.statevalidation.util.PlatformContextHelper; @@ -18,6 +19,8 @@ import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileWriter; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.spi.CommittableWritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -149,9 +152,14 @@ public void applyBlocks( false, DEFAULT_PLATFORM_STATE_FACADE); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + platformContext.getMetrics(), + platformContext.getTime(), + vm -> new HederaVirtualMapState(vm, platformContext.getMetrics(), platformContext.getTime())); + stateLifecycleManager.setSnapshotSource(signedState); try { SignedStateFileWriter.writeSignedStateFilesToDirectory( - platformContext, selfId, outputPath, signedState, DEFAULT_PLATFORM_STATE_FACADE); + platformContext, selfId, outputPath, DEFAULT_PLATFORM_STATE_FACADE, stateLifecycleManager); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java index 80722bbaef99..ef753bd91844 100644 --- a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java +++ b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java @@ -82,12 +82,6 @@ protected OtterAppState copyingConstructor() { return new OtterAppState(this); } - @Override - protected OtterAppState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new OtterAppState(virtualMap, metrics, time); - } - /** * {@inheritDoc} */ diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java index 97778355e090..b41c9e329adc 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java @@ -119,12 +119,6 @@ protected ConsistencyTestingToolState copyingConstructor() { return new ConsistencyTestingToolState(this); } - @Override - protected ConsistencyTestingToolState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new ConsistencyTestingToolState(virtualMap, metrics, time); - } - /** * Initialize the state */ diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java index 9943d4786989..732781ed0a88 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java @@ -98,12 +98,6 @@ protected ISSTestingToolState copyingConstructor() { return new ISSTestingToolState(this); } - @Override - protected ISSTestingToolState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new ISSTestingToolState(virtualMap, metrics, time); - } - public void initState(InitTrigger trigger, Platform platform) { throwIfImmutable(); diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index 951983d56bea..b705d02e2b52 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -32,12 +32,6 @@ protected MigrationTestingToolState copyingConstructor() { return new MigrationTestingToolState(this); } - @Override - protected MigrationTestingToolState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new MigrationTestingToolState(virtualMap, metrics, time); - } - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index ae57f6da936d..b1b8a9e2203f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -38,7 +38,6 @@ import com.swirlds.platform.publisher.PlatformPublisher; import com.swirlds.platform.reconnect.DefaultSignedStateValidator; import com.swirlds.platform.reconnect.ReconnectController; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.nexus.DefaultLatestCompleteStateNexus; import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; import com.swirlds.platform.state.nexus.LockFreeStateNexus; @@ -59,6 +58,7 @@ import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.platform.wiring.PlatformCoordinator; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.util.List; @@ -138,6 +138,11 @@ public class SwirldsPlatform implements Platform { */ private final SavedStateController savedStateController; + /** + * Manages the lifecycle of the state. + */ + private final StateLifecycleManager stateLifecycleManager; + /** * Encapsulated wiring for the platform. */ @@ -185,7 +190,8 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { final LatestCompleteStateNexus latestCompleteStateNexus = new DefaultLatestCompleteStateNexus(platformContext); - savedStateController = new DefaultSavedStateController(platformContext); + savedStateController = new DefaultSavedStateController(platformContext, blocks.stateLifecycleManager()); + stateLifecycleManager = blocks.stateLifecycleManager(); final SignedStateMetrics signedStateMetrics = new SignedStateMetrics(platformContext.getMetrics()); final StateSignatureCollector stateSignatureCollector = @@ -211,15 +217,15 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { initializeState(this, platformContext, initialState, blocks.consensusStateEventHandler(), platformStateFacade); // This object makes a copy of the state. After this point, initialState becomes immutable. - final SwirldStateManager swirldStateManager = blocks.swirldStateManager(); - swirldStateManager.setState(initialState.getState(), true); - platformStateFacade.setCreationSoftwareVersionTo(swirldStateManager.getConsensusState(), blocks.appVersion()); + final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); + stateLifecycleManager.initState(initialState.getState(), true); + platformStateFacade.setCreationSoftwareVersionTo(stateLifecycleManager.getMutableState(), blocks.appVersion()); final EventWindowManager eventWindowManager = new DefaultEventWindowManager(); blocks.freezeCheckHolder() .setFreezeCheckRef(instant -> - platformStateFacade.isInFreezePeriod(instant, swirldStateManager.getConsensusState())); + platformStateFacade.isInFreezePeriod(instant, stateLifecycleManager.getMutableState())); final AppNotifier appNotifier = new DefaultAppNotifier(blocks.notificationEngine()); @@ -231,7 +237,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { this, platformContext, platformCoordinator, - swirldStateManager, + stateLifecycleManager, savedStateController, blocks.consensusStateEventHandler(), blocks.reservedSignedStateResultPromise(), @@ -382,6 +388,7 @@ public void performPcesRecovery() { } else { final SignedState signedState = reservedState.get(); signedState.markAsStateToSave(StateToDiskReason.PCES_RECOVERY_COMPLETE); + stateLifecycleManager.setSnapshotSource(signedState); final StateDumpRequest request = StateDumpRequest.create(signedState.reserve("dumping PCES recovery state")); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index e8eea69836a9..a8b680d980b6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -34,7 +34,6 @@ import com.swirlds.platform.reconnect.FallenBehindMonitor; import com.swirlds.platform.scratchpad.Scratchpad; import com.swirlds.platform.state.ConsensusStateEventHandler; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -42,6 +41,8 @@ import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.platform.wiring.PlatformWiring; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -433,7 +434,8 @@ public PlatformComponentBuilder buildComponentBuilder() { final ApplicationCallbacks callbacks = new ApplicationCallbacks(preconsensusEventConsumer, snapshotOverrideConsumer, staleEventConsumer); - final SwirldStateManager swirldStateManager = new SwirldStateManager(platformContext, currentRoster); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( + platformContext.getMetrics(), platformContext.getTime(), createStateFromVirtualMap); if (model == null) { final WiringConfig wiringConfig = platformContext.getConfiguration().getConfigData(WiringConfig.class); @@ -495,7 +497,7 @@ public PlatformComponentBuilder buildComponentBuilder() { issScratchpad, NotificationEngine.buildEngine(getStaticThreadManager()), new AtomicReference<>(), - swirldStateManager, + stateLifecycleManager, new AtomicReference<>(), firstPlatform, consensusStateEventHandler, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java index 40a36c60952b..5cf9520c7cee 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java @@ -15,13 +15,14 @@ import com.swirlds.platform.reconnect.FallenBehindMonitor; import com.swirlds.platform.scratchpad.Scratchpad; import com.swirlds.platform.state.ConsensusStateEventHandler; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -72,7 +73,7 @@ * @param notificationEngine for sending notifications to the application (legacy pattern) * @param statusActionSubmitterReference a reference to the status action submitter, this can be deleted once * platform status management is handled by the wiring framework - * @param swirldStateManager responsible for the mutable state, this is exposed here due to + * @param stateLifecycleManager responsible for the mutable state, this is exposed here due to * reconnect * @param getLatestCompleteStateReference a reference to a supplier that supplies the latest immutable state, * this is exposed here due to reconnect, can be removed once reconnect is @@ -110,7 +111,7 @@ public record PlatformBuildingBlocks( @NonNull Scratchpad issScratchpad, @NonNull NotificationEngine notificationEngine, @NonNull AtomicReference statusActionSubmitterReference, - @NonNull SwirldStateManager swirldStateManager, + @NonNull StateLifecycleManager stateLifecycleManager, @NonNull AtomicReference> getLatestCompleteStateReference, boolean firstPlatform, @NonNull ConsensusStateEventHandler consensusStateEventHandler, @@ -140,7 +141,7 @@ public record PlatformBuildingBlocks( requireNonNull(issScratchpad); requireNonNull(notificationEngine); requireNonNull(statusActionSubmitterReference); - requireNonNull(swirldStateManager); + requireNonNull(stateLifecycleManager); requireNonNull(getLatestCompleteStateReference); requireNonNull(consensusStateEventHandler); requireNonNull(platformStateFacade); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java index ebef4d2efd28..cd9239c2a2ad 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java @@ -804,7 +804,7 @@ public Gossip buildGossip() { blocks.rosterHistory().getCurrentRoster(), blocks.selfId(), blocks.appVersion(), - blocks.swirldStateManager(), + blocks.stateLifecycleManager(), () -> blocks.getLatestCompleteStateReference().get().get(), blocks.intakeEventCounter(), blocks.platformStateFacade(), @@ -882,7 +882,8 @@ public StateSnapshotManager buildStateSnapshotManager() { actualMainClassName, blocks.selfId(), blocks.swirldName(), - blocks.platformStateFacade()); + blocks.platformStateFacade(), + blocks.stateLifecycleManager()); } return stateSnapshotManager; } @@ -1042,7 +1043,7 @@ public TransactionHandler buildTransactionHandler() { if (transactionHandler == null) { transactionHandler = new DefaultTransactionHandler( blocks.platformContext(), - blocks.swirldStateManager(), + blocks.stateLifecycleManager(), blocks.statusActionSubmitterReference().get(), blocks.appVersion(), blocks.platformStateFacade(), diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 0885e4c845d0..192a2de7718e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -16,10 +16,13 @@ import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.util.BootstrapUtils; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.spi.CommittableWritableStates; import com.swirlds.state.spi.WritableStates; import java.io.IOException; @@ -60,6 +63,11 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti BootstrapUtils.setupConstructableRegistry(); final PlatformContext platformContext = PlatformContext.create(configuration); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + platformContext.getMetrics(), platformContext.getTime(), (virtualMap) -> { + // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19003 + throw new UnsupportedOperationException(); + }); System.out.printf("Reading from %s %n", statePath.toAbsolutePath()); final PlatformStateFacade stateFacade = DEFAULT_PLATFORM_STATE_FACADE; @@ -91,8 +99,9 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti .digestTreeAsync(reservedSignedState.get().getState().getRoot()) .get(); System.out.printf("Writing modified state to %s %n", outputDir.toAbsolutePath()); + stateLifecycleManager.setSnapshotSource(reservedSignedState.get()); writeSignedStateFilesToDirectory( - platformContext, NO_NODE_ID, outputDir, reservedSignedState.get(), stateFacade); + platformContext, NO_NODE_ID, outputDir, stateFacade, stateLifecycleManager); } return 0; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java index 3cb420a693f4..3dc076c4abdf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java @@ -13,6 +13,7 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.StateToDiskReason; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -31,14 +32,19 @@ public class DefaultSavedStateController implements SavedStateController { private Instant previousSavedStateTimestamp; private final StateConfig stateConfig; + private final StateLifecycleManager stateLifecycleManager; /** * Constructor * - * @param platformContext the platform context + * @param platformContext the platform context + * @param stateLifecycleManager */ - public DefaultSavedStateController(@NonNull final PlatformContext platformContext) { + public DefaultSavedStateController( + @NonNull final PlatformContext platformContext, + @NonNull final StateLifecycleManager stateLifecycleManager) { this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + this.stateLifecycleManager = stateLifecycleManager; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java index a1556b64b1a8..279360daa267 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java @@ -25,7 +25,6 @@ import com.swirlds.platform.metrics.TransactionMetrics; import com.swirlds.platform.state.ConsensusStateEventHandler; import com.swirlds.platform.state.PlatformStateModifier; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; @@ -34,6 +33,7 @@ import com.swirlds.platform.wiring.PlatformSchedulersConfig; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -62,7 +62,7 @@ public class DefaultTransactionHandler implements TransactionHandler { /** * The class responsible for all interactions with the swirld state */ - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final RoundHandlingMetrics handlerMetrics; @@ -130,14 +130,14 @@ public class DefaultTransactionHandler implements TransactionHandler { * Constructor * * @param platformContext contains various platform utilities - * @param swirldStateManager the swirld state manager to send events to + * @param stateLifecycleManager the swirld state manager to send events to * @param statusActionSubmitter enables submitting of platform status actions * @param softwareVersion the current version of the software * @param platformStateFacade enables access to the platform state */ public DefaultTransactionHandler( @NonNull final PlatformContext platformContext, - @NonNull final SwirldStateManager swirldStateManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final SemanticVersion softwareVersion, @NonNull final PlatformStateFacade platformStateFacade, @@ -145,7 +145,7 @@ public DefaultTransactionHandler( @NonNull final NodeId selfId) { this.platformContext = requireNonNull(platformContext); - this.swirldStateManager = requireNonNull(swirldStateManager); + this.stateLifecycleManager = requireNonNull(stateLifecycleManager); this.statusActionSubmitter = requireNonNull(statusActionSubmitter); this.softwareVersion = requireNonNull(softwareVersion); this.consensusStateEventHandler = requireNonNull(consensusStateEventHandler); @@ -205,7 +205,7 @@ public TransactionHandlerResult handleConsensusRound(@NonNull final ConsensusRou } if (platformStateFacade.isInFreezePeriod( - consensusRound.getConsensusTimestamp(), swirldStateManager.getConsensusState())) { + consensusRound.getConsensusTimestamp(), stateLifecycleManager.getMutableState())) { statusActionSubmitter.submitStatusAction(new FreezePeriodEnteredAction(consensusRound.getRoundNum())); freezeRoundReceived = true; logger.info( @@ -259,7 +259,7 @@ public TransactionHandlerResult handleConsensusRound(@NonNull final ConsensusRou */ private Queue> doHandleConsensusRound( final ConsensusRound round) { - final MerkleNodeState state = swirldStateManager.getConsensusState(); + final MerkleNodeState state = stateLifecycleManager.getMutableState(); final Queue> scopedSystemTransactions = new ConcurrentLinkedQueue<>(); try { @@ -296,7 +296,7 @@ private Queue> doHandleConsen * @param round the consensus round */ private void updatePlatformState(@NonNull final ConsensusRound round) { - platformStateFacade.bulkUpdateOf(swirldStateManager.getConsensusState(), v -> { + platformStateFacade.bulkUpdateOf(stateLifecycleManager.getMutableState(), v -> { v.setRound(round.getRoundNum()); v.setConsensusTimestamp(round.getConsensusTimestamp()); v.setCreationSoftwareVersion(softwareVersion); @@ -312,7 +312,7 @@ private void updatePlatformState(@NonNull final ConsensusRound round) { * @throws InterruptedException if this thread is interrupted */ private void updateRunningEventHash(@NonNull final ConsensusRound round) throws InterruptedException { - final State consensusState = swirldStateManager.getConsensusState(); + final State consensusState = stateLifecycleManager.getMutableState(); if (writeLegacyRunningEventHash) { final CesEvent last = round.getStreamedEvents().getLast(); @@ -350,9 +350,9 @@ private TransactionHandlerResult createSignedState( @NonNull final Queue> systemTransactions) throws InterruptedException { if (freezeRoundReceived) { - platformStateFacade.updateLastFrozenTime(swirldStateManager.getConsensusState()); + platformStateFacade.updateLastFrozenTime(stateLifecycleManager.getMutableState()); } - final MerkleNodeState state = swirldStateManager.getConsensusState(); + final MerkleNodeState state = stateLifecycleManager.getMutableState(); final boolean isBoundary = consensusStateEventHandler.onSealConsensusRound(consensusRound, state); final ReservedSignedState reservedSignedState; if (isBoundary || freezeRoundReceived) { @@ -366,13 +366,14 @@ but the app may not have done some work that it needs to (like finishing a block consensusRound.getRoundNum()); } handlerMetrics.setPhase(GETTING_STATE_TO_SIGN); - final MerkleNodeState immutableStateCons = swirldStateManager.getStateForSigning(); + stateLifecycleManager.copyMutableState(); + final MerkleNodeState immutableState = stateLifecycleManager.getLatestImmutableState(); handlerMetrics.setPhase(CREATING_SIGNED_STATE); final SignedState signedState = new SignedState( platformContext.getConfiguration(), CryptoStatic::verifySignature, - immutableStateCons, + immutableState, "TransactionHandler.createSignedState()", freezeRoundReceived, true, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java index 456cbc5f6473..6c2a408f1d7f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java @@ -34,12 +34,12 @@ import com.swirlds.platform.network.protocol.rpc.RpcProtocol; import com.swirlds.platform.reconnect.FallenBehindMonitor; import com.swirlds.platform.reconnect.ReconnectStateTeacherThrottle; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.wiring.NoInput; import com.swirlds.platform.wiring.components.Gossip; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.cert.X509Certificate; @@ -72,7 +72,7 @@ public class SyncGossipModular implements Gossip { private final AbstractSyncProtocol syncProtocol; private final FallenBehindMonitor fallenBehindMonitor; private final AbstractShadowgraphSynchronizer synchronizer; - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; // this is not a nice dependency, should be removed as well as the sharedState @@ -88,7 +88,7 @@ public class SyncGossipModular implements Gossip { * @param roster the current roster * @param selfId this node's ID * @param appVersion the version of the app - * @param swirldStateManager manages the mutable state + * @param stateLifecycleManager manages the mutable state * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param intakeEventCounter keeps track of the number of events in the intake pipeline from each peer * @param platformStateFacade the facade to access the platform state @@ -103,7 +103,7 @@ public SyncGossipModular( @NonNull final Roster roster, @NonNull final NodeId selfId, @NonNull final SemanticVersion appVersion, - @NonNull final SwirldStateManager swirldStateManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Supplier latestCompleteState, @NonNull final IntakeEventCounter intakeEventCounter, @NonNull final PlatformStateFacade platformStateFacade, @@ -131,7 +131,7 @@ public SyncGossipModular( this.network = new PeerCommunication(platformContext, peers, selfPeer, ownKeysAndCerts); this.fallenBehindMonitor = fallenBehindMonitor; - this.swirldStateManager = swirldStateManager; + this.stateLifecycleManager = stateLifecycleManager; this.createStateFromVirtualMap = createStateFromVirtualMap; final ProtocolConfig protocolConfig = platformContext.getConfiguration().getConfigData(ProtocolConfig.class); @@ -146,7 +146,7 @@ public SyncGossipModular( rosterSize, syncMetrics, event -> receivedEventHandler.accept(event), - this.fallenBehindMonitor, + fallenBehindMonitor, intakeEventCounter, selfId, lag -> syncLagHandler.accept(lag)); @@ -239,7 +239,7 @@ public ReconnectStateSyncProtocol createStateSyncProtocol( fallenBehindMonitor, platformStateFacade, reservedSignedStateResultPromise, - swirldStateManager, + stateLifecycleManager, createStateFromVirtualMap); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java index ec3e15c9e7ea..1e26ab142232 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java @@ -8,10 +8,10 @@ import com.swirlds.platform.reconnect.FallenBehindMonitor; import com.swirlds.platform.reconnect.ReconnectStatePeerProtocol; import com.swirlds.platform.reconnect.ReconnectStateTeacherThrottle; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; @@ -39,7 +39,7 @@ public class ReconnectStateSyncProtocol implements Protocol { private final PlatformContext platformContext; private final AtomicReference platformStatus = new AtomicReference<>(PlatformStatus.STARTING_UP); private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; public ReconnectStateSyncProtocol( @@ -52,7 +52,7 @@ public ReconnectStateSyncProtocol( @NonNull final FallenBehindMonitor fallenBehindManager, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final SwirldStateManager swirldStateManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); @@ -65,7 +65,7 @@ public ReconnectStateSyncProtocol( this.platformStateFacade = platformStateFacade; this.time = Objects.requireNonNull(platformContext.getTime()); this.reservedSignedStateResultPromise = Objects.requireNonNull(reservedSignedStateResultPromise); - this.swirldStateManager = Objects.requireNonNull(swirldStateManager); + this.stateLifecycleManager = Objects.requireNonNull(stateLifecycleManager); this.createStateFromVirtualMap = Objects.requireNonNull(createStateFromVirtualMap); } @@ -88,7 +88,7 @@ public ReconnectStatePeerProtocol createPeerInstance(@NonNull final NodeId peerI time, platformStateFacade, reservedSignedStateResultPromise, - swirldStateManager, + stateLifecycleManager, createStateFromVirtualMap); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java index 874223763e03..a72262f9e143 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java @@ -18,7 +18,6 @@ import com.swirlds.platform.network.protocol.ReservedSignedStateResultPromise; import com.swirlds.platform.network.protocol.ReservedSignedStateResultPromise.ReservedSignedStateResult; import com.swirlds.platform.state.ConsensusStateEventHandler; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateValidationData; @@ -32,6 +31,7 @@ import com.swirlds.platform.system.status.actions.ReconnectCompleteAction; import com.swirlds.platform.wiring.PlatformCoordinator; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; @@ -72,7 +72,7 @@ public class ReconnectController implements Runnable { private final Platform platform; private final PlatformContext platformContext; private final PlatformCoordinator platformCoordinator; - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final SavedStateController savedStateController; private final ConsensusStateEventHandler consensusStateEventHandler; private final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; @@ -90,7 +90,7 @@ public ReconnectController( @NonNull final Platform platform, @NonNull final PlatformContext platformContext, @NonNull final PlatformCoordinator platformCoordinator, - @NonNull final SwirldStateManager swirldStateManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final SavedStateController savedStateController, @NonNull final ConsensusStateEventHandler consensusStateEventHandler, @NonNull final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise, @@ -107,7 +107,7 @@ public ReconnectController( this.reconnectConfig = platformContext.getConfiguration().getConfigData(ReconnectConfig.class); this.platform = requireNonNull(platform); this.platformContext = requireNonNull(platformContext); - this.swirldStateManager = requireNonNull(swirldStateManager); + this.stateLifecycleManager = requireNonNull(stateLifecycleManager); this.savedStateController = requireNonNull(savedStateController); this.consensusStateEventHandler = requireNonNull(consensusStateEventHandler); this.time = platformContext.getTime(); @@ -161,7 +161,7 @@ public void run() { platformCoordinator.clear(); logger.info(RECONNECT.getMarker(), "Queues have been cleared"); - final MerkleNodeState currentState = swirldStateManager.getConsensusState(); + final MerkleNodeState currentState = stateLifecycleManager.getMutableState(); hashStateForReconnect(merkleCryptography, currentState); int failedReconnectsInARow = 0; do { @@ -261,7 +261,7 @@ private void loadState(@NonNull final SignedState signedState) { + Roster.JSON.toJSON(stateRoster) + ")"); } - swirldStateManager.setState(signedState.getState(), false); + stateLifecycleManager.initState(signedState.getState(), false); // kick off transition to RECONNECT_COMPLETE before beginning to save the reconnect state to disk // this guarantees that the platform status will be RECONNECT_COMPLETE before the state is saved platformCoordinator.submitStatusAction(new ReconnectCompleteAction(signedState.getRound())); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java index d5ad50544e81..2e4e282db531 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java @@ -21,10 +21,10 @@ import com.swirlds.platform.network.NetworkProtocolException; import com.swirlds.platform.network.protocol.PeerProtocol; import com.swirlds.platform.network.protocol.ReservedSignedStateResultPromise; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -82,7 +82,7 @@ public class ReconnectStatePeerProtocol implements PeerProtocol { private final Time time; private final PlatformContext platformContext; private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; /** @@ -112,7 +112,7 @@ public ReconnectStatePeerProtocol( @NonNull final Time time, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final SwirldStateManager swirldStateManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); @@ -126,7 +126,7 @@ public ReconnectStatePeerProtocol( this.platformStatusSupplier = Objects.requireNonNull(platformStatusSupplier); this.platformStateFacade = Objects.requireNonNull(platformStateFacade); this.reservedSignedStateResultPromise = Objects.requireNonNull(reservedSignedStateResultPromise); - this.swirldStateManager = Objects.requireNonNull(swirldStateManager); + this.stateLifecycleManager = Objects.requireNonNull(stateLifecycleManager); this.createStateFromVirtualMap = Objects.requireNonNull(createStateFromVirtualMap); Objects.requireNonNull(time); @@ -311,7 +311,7 @@ public void runProtocol(final Connection connection) private void learner(final Connection connection) { try { - final MerkleNodeState consensusState = swirldStateManager.getConsensusState(); + final MerkleNodeState consensusState = stateLifecycleManager.getMutableState(); final ReconnectStateLearner learner = new ReconnectStateLearner( platformContext, threadManager, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index 09c9f5e0b64b..05527497d16b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -48,6 +48,8 @@ import com.swirlds.platform.system.state.notifications.NewRecoveredStateNotification; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -155,7 +157,12 @@ public static void recoverState( .apply(v), platformStateFacade, platformContext); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + platformContext.getMetrics(), + platformContext.getTime(), + hederaApp.stateRootFromVirtualMap(platformContext.getMetrics(), platformContext.getTime())); try (final ReservedSignedState initialState = deserializedSignedState.reservedSignedState()) { + stateLifecycleManager.setSnapshotSource(initialState.get()); HederaUtils.updateStateHash(hederaApp, deserializedSignedState); logger.info( @@ -192,11 +199,7 @@ public static void recoverState( recoveredState.state().get().getState().copy(); SignedStateFileWriter.writeSignedStateFilesToDirectory( - platformContext, - selfId, - resultingStateDirectory, - recoveredState.state().get(), - platformStateFacade); + platformContext, selfId, resultingStateDirectory, platformStateFacade, stateLifecycleManager); final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); updateEmergencyRecoveryFile( stateConfig, resultingStateDirectory, initialState.get().getConsensusTimestamp()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java deleted file mode 100644 index 522814f5f625..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package com.swirlds.platform.state; - -import static com.swirlds.base.units.UnitConstants.NANOSECONDS_TO_MICROSECONDS; -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.state.roster.Roster; -import com.swirlds.common.context.PlatformContext; -import com.swirlds.state.MerkleNodeState; -import com.swirlds.state.State; -import com.swirlds.state.merkle.StateMetrics; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.atomic.AtomicReference; - -/** - * This class is responsible for maintaining references to the mutable state and the latest immutable state. - * It also updates these references upon state signing. - */ -public class SwirldStateManager { - - /** - * Metrics for the state object - */ - private final StateMetrics stateMetrics; - - /** - * reference to the state that reflects all known consensus transactions - */ - private final AtomicReference stateRef = new AtomicReference<>(); - - /** - * The most recent immutable state. No value until the first fast copy is created. - */ - private final AtomicReference latestImmutableState = new AtomicReference<>(); - - /** - * Constructor. - * - * @param platformContext the platform context - * @param roster the current roster - */ - public SwirldStateManager(@NonNull final PlatformContext platformContext, @NonNull final Roster roster) { - requireNonNull(platformContext); - requireNonNull(roster); - this.stateMetrics = new StateMetrics(platformContext.getMetrics()); - } - - /** - * Set the initial State for the platform. This method should only be called once. - * - * @param state the initial state - */ - public void setState(@NonNull final MerkleNodeState state, boolean onInit) { - requireNonNull(state); - - state.throwIfDestroyed("state must not be destroyed"); - state.throwIfImmutable("state must be mutable"); - - if (onInit && stateRef.get() != null) { - throw new IllegalStateException("Attempt to set initial state when there is already a state reference."); - } - - updateStateRefs(state); - } - - /** - * Returns the consensus state. The consensus state could become immutable at any time. Modifications must not be - * made to the returned state. - */ - public MerkleNodeState getConsensusState() { - return stateRef.get(); - } - - private void updateStateRefs(MerkleNodeState state) { - // Create a fast copy so there is always an immutable state to - // invoke handleTransaction on for pre-consensus transactions - final long copyStart = System.nanoTime(); - // Create a fast copy - final MerkleNodeState copy = state.copy(); - // Increment the reference count because this reference becomes the new value - copy.getRoot().reserve(); - final long copyEnd = System.nanoTime(); - stateMetrics.stateCopyMicros((copyEnd - copyStart) * NANOSECONDS_TO_MICROSECONDS); - // Set latest immutable first to prevent the newly immutable stateRoot from being deleted between setting the - // stateRef and the latestImmutableState - setLatestImmutableState(state); - updateStateRef(copy); - } - - /** - * Sets the consensus state to the state provided. Must be mutable and have a reference count of at least 1. - * - * @param state a new mutable state - */ - private void updateStateRef(final MerkleNodeState state) { - final var currVal = stateRef.get(); - if (currVal != null) { - currVal.release(); - } - // Do not increment the reference count because the state provided already has a reference count of at least - // one to represent this reference and to prevent it from being deleted before this reference is set. - stateRef.set(state); - } - - private void setLatestImmutableState(final MerkleNodeState immutableState) { - final State currVal = latestImmutableState.get(); - if (currVal != null) { - currVal.release(); - } - immutableState.getRoot().reserve(); - latestImmutableState.set(immutableState); - } - - /** - *

Updates the state to a fast copy of itself and returns a reference to the previous state to be used for - * signing. The reference count of the previous state returned by this is incremented to prevent it from being - * garbage collected until it is put in a signed state, so callers are responsible for decrementing the reference - * count when it is no longer needed.

- * - *

Consensus event handling will block until this method returns. Pre-consensus - * event handling may or may not be blocked depending on the implementation.

- * - * @return a copy of the state to use for the next signed state - * @see State#copy() - */ - public MerkleNodeState getStateForSigning() { - final MerkleNodeState state = stateRef.get(); - updateStateRefs(state); - return latestImmutableState.get(); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java index ac5045ec75ac..efc3df669f01 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java @@ -14,6 +14,9 @@ import com.swirlds.logging.legacy.LogMarker; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -46,6 +49,12 @@ public void run() { final PlatformContext platformContext = PlatformContext.create(configuration); logger.info(LogMarker.CLI.getMarker(), "Hashing state"); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + platformContext.getMetrics(), platformContext.getTime(), (virtualMap) -> { + // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19003 + throw new UnsupportedOperationException(); + }); + platformContext .getMerkleCryptography() .digestTreeAsync(reservedSignedState.get().getState().getRoot()) @@ -60,8 +69,9 @@ public void run() { } try (final ReservedSignedState signedState = getStateEditor().getSignedStateCopy()) { + stateLifecycleManager.setSnapshotSource(signedState.get()); writeSignedStateFilesToDirectory( - platformContext, NO_NODE_ID, directory, signedState.get(), DEFAULT_PLATFORM_STATE_FACADE); + platformContext, NO_NODE_ID, directory, DEFAULT_PLATFORM_STATE_FACADE, stateLifecycleManager); } } catch (final IOException e) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index 0a8e3bca82e8..40b7dd9fb871 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -23,6 +23,7 @@ import com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction; import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.MerkleNodeStateAware; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.security.cert.X509Certificate; @@ -62,7 +63,7 @@ * rejoining after a long absence. *

*/ -public class SignedState { +public class SignedState implements MerkleNodeStateAware { private static final Logger logger = LogManager.getLogger(SignedState.class); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java index d8f6f5f1ed5d..88e4d680ac98 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java @@ -16,6 +16,7 @@ import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; @@ -80,6 +81,11 @@ public class DefaultStateSnapshotManager implements StateSnapshotManager { */ private final SignedStateFilePath signedStateFilePath; + /** + * Provides access to the state + */ + private final StateLifecycleManager stateLifecycleManager; + /** * Creates a new instance. * @@ -88,13 +94,15 @@ public class DefaultStateSnapshotManager implements StateSnapshotManager { * @param selfId the ID of this node * @param swirldName the name of the swirld * @param platformStateFacade the facade to access the platform state + * @param stateLifecycleManager the state lifecycle manager */ public DefaultStateSnapshotManager( @NonNull final PlatformContext platformContext, @NonNull final String mainClassName, @NonNull final NodeId selfId, @NonNull final String swirldName, - @NonNull final PlatformStateFacade platformStateFacade) { + @NonNull final PlatformStateFacade platformStateFacade, + @NonNull StateLifecycleManager stateLifecycleManager) { this.platformContext = Objects.requireNonNull(platformContext); this.time = platformContext.getTime(); @@ -103,6 +111,7 @@ public DefaultStateSnapshotManager( this.swirldName = Objects.requireNonNull(swirldName); configuration = platformContext.getConfiguration(); this.platformStateFacade = platformStateFacade; + this.stateLifecycleManager = stateLifecycleManager; signedStateFilePath = new SignedStateFilePath(configuration.getConfigData(StateCommonConfig.class)); metrics = new StateSnapshotManagerMetrics(platformContext); } @@ -171,8 +180,9 @@ private static StateToDiskReason getReason(@NonNull final SignedState state) { private boolean saveStateTask(@NonNull final SignedState state, @NonNull final Path directory) { try { + stateLifecycleManager.setSnapshotSource(state); SignedStateFileWriter.writeSignedStateToDisk( - platformContext, selfId, directory, state, getReason(state), platformStateFacade); + platformContext, selfId, directory, getReason(state), platformStateFacade, stateLifecycleManager); return true; } catch (final Throwable e) { logger.error( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java index 0b32549a6c82..09c5e5b79b2e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java @@ -11,6 +11,7 @@ import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.HASH_INFO_FILE_NAME; import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.INIT_SIG_SET_FILE_VERSION; import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.SIGNATURE_SET_FILE_NAME; +import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.roster.Roster; import com.swirlds.common.context.PlatformContext; @@ -23,7 +24,7 @@ import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.state.MerkleNodeState; -import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.BufferedWriter; @@ -31,7 +32,6 @@ import java.io.IOException; import java.nio.file.Path; import java.time.Instant; -import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hiero.consensus.model.node.NodeId; @@ -87,14 +87,14 @@ public static void writeHashInfoFile( * @param directory the directory to write to * @param signedState the signed state being written */ - public static void writeMetadataFile( + private static void writeMetadataFile( @Nullable final NodeId selfId, @NonNull final Path directory, @NonNull final SignedState signedState, @NonNull final PlatformStateFacade platformStateFacade) throws IOException { - Objects.requireNonNull(directory, "directory must not be null"); - Objects.requireNonNull(signedState, "signedState must not be null"); + requireNonNull(directory, "directory must not be null"); + requireNonNull(signedState, "signedState must not be null"); final Path metadataFile = directory.resolve(SavedStateMetadata.FILE_NAME); @@ -131,27 +131,25 @@ public static void writeSignatureSetFile(final @NonNull Path directory, final @N * @param platformContext the platform context * @param selfId the id of the platform * @param directory the directory where all files should be placed - * @param signedState the signed state being written to disk */ public static void writeSignedStateFilesToDirectory( @Nullable final PlatformContext platformContext, @Nullable final NodeId selfId, @NonNull final Path directory, - @NonNull final SignedState signedState, - @NonNull final PlatformStateFacade platformStateFacade) + @NonNull final PlatformStateFacade platformStateFacade, + @NonNull final StateLifecycleManager stateLifecycleManager) throws IOException { - Objects.requireNonNull(platformContext); - Objects.requireNonNull(directory); - Objects.requireNonNull(signedState); - - final State state = signedState.getState(); - - state.createSnapshot(directory); - writeSignatureSetFile(directory, signedState); - writeHashInfoFile(platformContext, directory, signedState.getState(), platformStateFacade); - writeMetadataFile(selfId, directory, signedState, platformStateFacade); - writeEmergencyRecoveryFile(directory, signedState); - final Roster currentRoster = signedState.getRoster(); + requireNonNull(platformContext); + requireNonNull(directory); + requireNonNull(stateLifecycleManager.getSnapshotSource()); + + SignedState snapshotSource = stateLifecycleManager.getSnapshotSource(); + stateLifecycleManager.createSnapshot(directory); + writeSignatureSetFile(directory, snapshotSource); + writeHashInfoFile(platformContext, directory, snapshotSource.getState(), platformStateFacade); + writeMetadataFile(selfId, directory, snapshotSource, platformStateFacade); + writeEmergencyRecoveryFile(directory, snapshotSource); + final Roster currentRoster = snapshotSource.getRoster(); if (currentRoster != null) { writeRosterFile(directory, currentRoster); } @@ -162,8 +160,8 @@ public static void writeSignedStateFilesToDirectory( platformContext, selfId, directory, - platformStateFacade.ancientThresholdOf(signedState.getState()), - signedState.getRound()); + platformStateFacade.ancientThresholdOf(snapshotSource.getState()), + snapshotSource.getRound()); } } @@ -189,39 +187,42 @@ private static void writeRosterFile(@NonNull final Path directory, @NonNull fina * @param platformContext the platform context * @param selfId the id of the platform * @param savedStateDirectory the directory where the state will be stored - * @param signedState the object to be written * @param stateToDiskReason the reason the state is being written to disk + * @param platformStateFacade the facade to access the platform state + * @param stateLifecycleManager the state lifecycle manager */ public static void writeSignedStateToDisk( @NonNull final PlatformContext platformContext, @Nullable final NodeId selfId, @NonNull final Path savedStateDirectory, - @NonNull final SignedState signedState, @Nullable final StateToDiskReason stateToDiskReason, - @NonNull final PlatformStateFacade platformStateFacade) + @NonNull final PlatformStateFacade platformStateFacade, + @NonNull final StateLifecycleManager stateLifecycleManager) throws IOException { - Objects.requireNonNull(platformContext); - Objects.requireNonNull(savedStateDirectory); - Objects.requireNonNull(signedState); + requireNonNull(platformContext); + requireNonNull(savedStateDirectory); + requireNonNull(stateLifecycleManager); + final SignedState snapshotSource = stateLifecycleManager.getSnapshotSource(); + requireNonNull(snapshotSource); try { logger.info( STATE_TO_DISK.getMarker(), "Started writing round {} state to disk. Reason: {}, directory: {}", - signedState.getRound(), + snapshotSource.getRound(), stateToDiskReason == null ? "UNKNOWN" : stateToDiskReason, savedStateDirectory); executeAndRename( savedStateDirectory, directory -> writeSignedStateFilesToDirectory( - platformContext, selfId, directory, signedState, platformStateFacade), + platformContext, selfId, directory, platformStateFacade, stateLifecycleManager), platformContext.getConfiguration()); logger.info(STATE_TO_DISK.getMarker(), () -> new StateSavedToDiskPayload( - signedState.getRound(), - signedState.isFreezeState(), + snapshotSource.getRound(), + snapshotSource.isFreezeState(), stateToDiskReason == null ? "UNKNOWN" : stateToDiskReason.toString(), savedStateDirectory) .toString()); @@ -229,7 +230,7 @@ public static void writeSignedStateToDisk( logger.error( EXCEPTION.getMarker(), "Exception when writing the signed state for round {} to disk:", - signedState.getRound(), + snapshotSource.getRound(), e); throw e; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java index ae6d32452a09..8a63496d9e2d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java @@ -4,7 +4,6 @@ import static com.swirlds.common.io.utility.FileUtils.throwIfFileExists; import static com.swirlds.common.merkle.utility.MerkleTreeSnapshotReader.SIGNED_STATE_FILE_NAME; import static com.swirlds.platform.StateFileManagerTests.hashState; -import static com.swirlds.platform.StateFileManagerTests.makeImmutable; import static com.swirlds.platform.state.snapshot.SignedStateFileReader.readStateFile; import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.CURRENT_ROSTER_FILE_NAME; import static com.swirlds.platform.state.snapshot.SignedStateFileUtils.HASH_INFO_FILE_NAME; @@ -23,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.hedera.hapi.node.base.SemanticVersion; +import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.config.StateCommonConfig_; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.common.merkle.utility.MerkleTreeVisualizer; +import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; @@ -38,7 +39,8 @@ import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.state.MerkleNodeState; -import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; import java.io.BufferedReader; import java.io.FileReader; @@ -60,6 +62,7 @@ class SignedStateFileReadWriteTest { private static SemanticVersion platformVersion; private static PlatformStateFacade stateFacade; + private StateLifecycleManager stateLifecycleManager; @BeforeAll static void beforeAll() throws ConstructableRegistryException { @@ -78,6 +81,8 @@ static void beforeAll() throws ConstructableRegistryException { @BeforeEach void beforeEach() throws IOException { testDirectory = LegacyTemporaryFileBuilder.buildTemporaryFile("SignedStateFileReadWriteTest", CONFIGURATION); + stateLifecycleManager = + new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); LegacyTemporaryFileBuilder.overrideTemporaryFileLocation(testDirectory.resolve("tmp")); } @@ -126,10 +131,11 @@ void writeThenReadStateFileTest() throws IOException { assertFalse(exists(stateFile), "signed state file should not yet exist"); assertFalse(exists(signatureSetFile), "signature set file should not yet exist"); - State state = signedState.getState(); + MerkleNodeState state = signedState.getState(); state.copy().release(); hashState(signedState); - state.createSnapshot(testDirectory); + stateLifecycleManager.setSnapshotSource(signedState); + stateLifecycleManager.createSnapshot(testDirectory); writeSignatureSetFile(testDirectory, signedState); assertTrue(exists(stateFile), "signed state file should be present"); @@ -147,8 +153,8 @@ void writeThenReadStateFileTest() throws IOException { signedState.getState().getHash(), deserializedSignedState.reservedSignedState().get().getState().getHash(), "hash should match"); - assertNotSame(signedState, deserializedSignedState.reservedSignedState(), "state should be a different object"); - signedState.getState().release(); + assertNotSame( + signedState, deserializedSignedState.reservedSignedState().get(), "state should be a different object"); deserializedSignedState.reservedSignedState().get().getState().release(); } @@ -159,6 +165,7 @@ void writeSavedStateToDiskTest() throws IOException { .setSoftwareVersion(platformVersion) .build(); final Path directory = testDirectory.resolve("state"); + stateLifecycleManager.initState(signedState.getState(), true); final Path stateFile = directory.resolve(SIGNED_STATE_FILE_NAME); final Path hashInfoFile = directory.resolve(HASH_INFO_FILE_NAME); @@ -173,16 +180,17 @@ void writeSavedStateToDiskTest() throws IOException { .withConfiguration(configuration) .build(); - makeImmutable(signedState); + stateLifecycleManager.getMutableState().release(); hashState(signedState); + stateLifecycleManager.setSnapshotSource(signedState); writeSignedStateToDisk( platformContext, NodeId.of(0), directory, - signedState, StateToDiskReason.PERIODIC_SNAPSHOT, - stateFacade); + stateFacade, + stateLifecycleManager); assertTrue(exists(stateFile), "state file should exist"); assertTrue(exists(hashInfoFile), "hash info file should exist"); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index f814e34c2f2f..c8831755e6ee 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -41,6 +41,8 @@ import com.swirlds.platform.state.snapshot.StateDumpRequest; import com.swirlds.platform.state.snapshot.StateSnapshotManager; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; import java.io.IOException; import java.nio.file.Files; @@ -75,6 +77,7 @@ class StateFileManagerTests { private SignedStateFilePath signedStateFilePath; Path testDirectory; + private StateLifecycleManager stateLifecycleManager; @BeforeAll static void beforeAll() throws ConstructableRegistryException { @@ -97,6 +100,8 @@ void beforeEach() throws IOException { .build(); signedStateFilePath = new SignedStateFilePath(context.getConfiguration().getConfigData(StateCommonConfig.class)); + stateLifecycleManager = + new StateLifecycleManagerImpl<>(context.getMetrics(), context.getTime(), TestVirtualMapState::new); } @AfterEach @@ -154,7 +159,7 @@ private void validateSavingOfState(final SignedState originalState, final Path s @DisplayName("Standard Operation Test") void standardOperationTest(final boolean successExpected) throws IOException { final SignedState signedState = new RandomSignedStateGenerator().build(); - makeImmutable(signedState); + initLifecycleManagerAndMakeStateImmutable(signedState); hashState(signedState); if (!successExpected) { @@ -166,7 +171,7 @@ void standardOperationTest(final boolean successExpected) throws IOException { } final StateSnapshotManager manager = new DefaultStateSnapshotManager( - context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE); + context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE, stateLifecycleManager); final StateSavingResult stateSavingResult = manager.saveStateTask(signedState.reserve("test")); @@ -184,9 +189,9 @@ void saveISSignedState() throws IOException { final SignedState signedState = new RandomSignedStateGenerator().build(); final StateSnapshotManager manager = new DefaultStateSnapshotManager( - context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE); + context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE, stateLifecycleManager); signedState.markAsStateToSave(ISS); - makeImmutable(signedState); + initLifecycleManagerAndMakeStateImmutable(signedState); hashState(signedState); manager.dumpStateTask(StateDumpRequest.create(signedState.reserve("test"))); @@ -226,8 +231,8 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { final double standardDeviationTimeBetweenStates = 0.5; final StateSnapshotManager manager = new DefaultStateSnapshotManager( - context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE); - final SavedStateController controller = new DefaultSavedStateController(context); + context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE, stateLifecycleManager); + final SavedStateController controller = new DefaultSavedStateController(context, stateLifecycleManager); Instant timestamp; final long firstRound; @@ -266,8 +271,8 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { .build(); final ReservedSignedState reservedSignedState = signedState.reserve("initialTestReservation"); + initLifecycleManagerAndMakeStateImmutable(reservedSignedState.get()); controller.markSavedState(new StateWithHashComplexity(reservedSignedState, 1)); - makeImmutable(reservedSignedState.get()); hashState(signedState); if (signedState.isStateToSave()) { @@ -350,7 +355,7 @@ void stateDeletionTest() throws IOException { final int count = 10; final StateSnapshotManager manager = new DefaultStateSnapshotManager( - context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE); + context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE, stateLifecycleManager); final Path statesDirectory = signedStateFilePath.getSignedStatesDirectoryForSwirld(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); @@ -363,7 +368,7 @@ void stateDeletionTest() throws IOException { .resolve("node" + SELF_ID + "_round" + issRound); final SignedState issState = new RandomSignedStateGenerator(random).setRound(issRound).build(); - makeImmutable(issState); + initLifecycleManagerAndMakeStateImmutable(issState); issState.markAsStateToSave(ISS); hashState(issState); manager.dumpStateTask(StateDumpRequest.create(issState.reserve("test"))); @@ -377,7 +382,7 @@ void stateDeletionTest() throws IOException { .resolve("node" + SELF_ID + "_round" + fatalRound); final SignedState fatalState = new RandomSignedStateGenerator(random).setRound(fatalRound).build(); - makeImmutable(fatalState); + initLifecycleManagerAndMakeStateImmutable(fatalState); hashState(fatalState); fatalState.markAsStateToSave(FATAL_ERROR); manager.dumpStateTask(StateDumpRequest.create(fatalState.reserve("test"))); @@ -390,7 +395,7 @@ void stateDeletionTest() throws IOException { new RandomSignedStateGenerator(random).setRound(round).build(); issState.markAsStateToSave(PERIODIC_SNAPSHOT); states.add(signedState); - makeImmutable(signedState); + initLifecycleManagerAndMakeStateImmutable(signedState); hashState(signedState); manager.saveStateTask(signedState.reserve("test")); @@ -424,7 +429,8 @@ static void hashState(SignedState signedState) { signedState.getState().getRoot().getHash(); } - static void makeImmutable(SignedState signedState) { - signedState.getState().copy().release(); + void initLifecycleManagerAndMakeStateImmutable(SignedState state) { + stateLifecycleManager.initState(state.getState(), false); + stateLifecycleManager.getMutableState().release(); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java index 1141a4b3b387..90394fd382a1 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java @@ -89,7 +89,7 @@ private ConsensusRound newConsensusRound(final boolean pcesRound) { @ParameterizedTest @CsvSource({"false", "true"}) void normalOperation(final boolean pcesRound) throws InterruptedException { - final TransactionHandlerTester tester = new TransactionHandlerTester(roster); + final TransactionHandlerTester tester = new TransactionHandlerTester(); final ConsensusRound consensusRound = newConsensusRound(pcesRound); final TransactionHandlerResult handlerOutput = @@ -146,13 +146,13 @@ void normalOperation(final boolean pcesRound) throws InterruptedException { "the state should match the PCES boolean"); verify(tester.getStateEventHandler()) .onSealConsensusRound( - consensusRound, tester.getSwirldStateManager().getConsensusState()); + consensusRound, tester.getStateLifecycleManager().getMutableState()); } @Test @DisplayName("Round in freeze period") void freezeHandling() throws InterruptedException { - final TransactionHandlerTester tester = new TransactionHandlerTester(roster); + final TransactionHandlerTester tester = new TransactionHandlerTester(); final ConsensusRound consensusRound = newConsensusRound(false); when(tester.getPlatformStateFacade().isInFreezePeriod(consensusRound.getConsensusTimestamp(), (MerkleNodeState) tester.getConsensusState())) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java index 6f237c8a17f9..34465087a29a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java @@ -14,7 +14,6 @@ import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.state.ConsensusStateEventHandler; import com.swirlds.platform.state.PlatformStateModifier; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.service.PlatformStateValueAccumulator; import com.swirlds.platform.system.status.StatusActionSubmitter; @@ -22,6 +21,8 @@ import com.swirlds.platform.test.fixtures.state.TestPlatformStateFacade; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import java.util.ArrayList; import java.util.List; import org.hiero.consensus.model.hashgraph.Round; @@ -32,7 +33,7 @@ */ public class TransactionHandlerTester { private final PlatformStateModifier platformState; - private final SwirldStateManager swirldStateManager; + private final StateLifecycleManager stateLifecycleManager; private final DefaultTransactionHandler defaultTransactionHandler; private final List submittedActions = new ArrayList<>(); private final List handledRounds = new ArrayList<>(); @@ -43,9 +44,8 @@ public class TransactionHandlerTester { /** * Constructs a new {@link TransactionHandlerTester} with the given {@link Roster}. * - * @param roster the {@link Roster} to use */ - public TransactionHandlerTester(final Roster roster) { + public TransactionHandlerTester() { final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); platformState = new PlatformStateValueAccumulator(); @@ -66,11 +66,12 @@ public TransactionHandlerTester(final Roster roster) { .when(consensusStateEventHandler) .onHandleConsensusRound(any(), same(consensusState), any()); final StatusActionSubmitter statusActionSubmitter = submittedActions::add; - swirldStateManager = new SwirldStateManager(platformContext, roster); - swirldStateManager.setState(consensusState, true); + stateLifecycleManager = new StateLifecycleManagerImpl( + platformContext.getMetrics(), platformContext.getTime(), vm -> consensusState); + stateLifecycleManager.initState(consensusState, true); defaultTransactionHandler = new DefaultTransactionHandler( platformContext, - swirldStateManager, + stateLifecycleManager, statusActionSubmitter, mock(SemanticVersion.class), platformStateFacade, @@ -107,10 +108,10 @@ public List getHandledRounds() { } /** - * @return the {@link SwirldStateManager} used by this tester + * @return the {@link StateLifecycleManager} used by this tester */ - public SwirldStateManager getSwirldStateManager() { - return swirldStateManager; + public StateLifecycleManager getStateLifecycleManager() { + return stateLifecycleManager; } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java index 5dcca8d9a16c..ab7b976449b9 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java @@ -29,7 +29,6 @@ import com.swirlds.platform.components.SavedStateController; import com.swirlds.platform.network.protocol.ReservedSignedStateResultPromise; import com.swirlds.platform.state.ConsensusStateEventHandler; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SigSet; @@ -46,6 +45,7 @@ import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.platform.wiring.PlatformCoordinator; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; import java.time.Duration; import java.util.Random; @@ -84,7 +84,7 @@ class ReconnectControllerTest { private MerkleCryptography merkleCryptography; private Platform platform; private PlatformCoordinator platformCoordinator; - private SwirldStateManager swirldStateManager; + private StateLifecycleManager stateLifecycleManager; private SavedStateController savedStateController; private ConsensusStateEventHandler consensusStateEventHandler; private ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; @@ -166,8 +166,8 @@ void setUp() { platformCoordinator = mock(PlatformCoordinator.class); // Mock SwirldStateManager - swirldStateManager = mock(SwirldStateManager.class); - when(swirldStateManager.getConsensusState()).thenReturn(testWorkingState); + stateLifecycleManager = mock(StateLifecycleManager.class); + when(stateLifecycleManager.getMutableState()).thenReturn(testWorkingState); // Mock SavedStateController savedStateController = mock(SavedStateController.class); @@ -209,7 +209,7 @@ private ReconnectController createController() { platform, platformContext, platformCoordinator, - swirldStateManager, + stateLifecycleManager, savedStateController, consensusStateEventHandler, peerReservedSignedStateResultPromise, @@ -809,7 +809,7 @@ void testSystemExitOnReconnectWindowTimeout() throws Exception { platform, shortWindowContext, platformCoordinator, - swirldStateManager, + stateLifecycleManager, savedStateController, consensusStateEventHandler, peerReservedSignedStateResultPromise, @@ -886,7 +886,7 @@ void testSystemExitWhenReconnectDisabled() throws Exception { platform, disabledContext, platformCoordinator, - swirldStateManager, + stateLifecycleManager, savedStateController, consensusStateEventHandler, peerReservedSignedStateResultPromise, diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocolTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocolTests.java index ba9be70862b7..591f389738af 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocolTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocolTests.java @@ -33,11 +33,11 @@ import com.swirlds.platform.network.protocol.Protocol; import com.swirlds.platform.network.protocol.ReconnectStateSyncProtocol; import com.swirlds.platform.network.protocol.ReservedSignedStateResultPromise; -import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -162,7 +162,7 @@ void shouldInitiateTest(final InitiateParams params) { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); @@ -206,7 +206,7 @@ void testShouldAccept(final AcceptParams params) { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); assertEquals( @@ -245,7 +245,7 @@ void testTeacherThrottleReleased() { Time.getCurrent(), TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); final SignedState signedState = spy(new RandomSignedStateGenerator().build()); when(signedState.isComplete()).thenReturn(true); @@ -267,7 +267,7 @@ void testTeacherThrottleReleased() { Time.getCurrent(), TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); // pretend we have fallen behind @@ -302,7 +302,7 @@ void testPermitReleased() { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); assertFalse( @@ -362,7 +362,7 @@ void abortedLearner() { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); @@ -407,7 +407,7 @@ void abortedTeacher() { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); @@ -445,7 +445,7 @@ void teacherHasNoSignedState() { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); @@ -476,7 +476,7 @@ void teacherNotActive() { fallenBehindManager, TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(CHECKING); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); @@ -503,7 +503,7 @@ void teacherHoldsLearnerPermit() { mock(FallenBehindMonitor.class), TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); @@ -550,7 +550,7 @@ void teacherCantAcquireLearnerPermit() { mock(FallenBehindMonitor.class), TEST_PLATFORM_STATE_FACADE, reservedSignedStateResultPromise, - mock(SwirldStateManager.class), + mock(StateLifecycleManager.class), a -> null); reconnectProtocol.updatePlatformStatus(ACTIVE); final PeerProtocol peerProtocol = reconnectProtocol.createPeerInstance(NodeId.of(0)); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldsStateManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java similarity index 81% rename from platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldsStateManagerTests.java rename to platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java index d928a135ebdd..4b3d348ec65f 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldsStateManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java @@ -20,15 +20,17 @@ import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.platform.test.fixtures.state.TestingAppStateInitializer; import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class SwirldsStateManagerTests { +class StateLifecycleManagerTests { - private SwirldStateManager swirldStateManager; + private StateLifecycleManager stateLifecycleManager; private MerkleNodeState initialState; @BeforeEach @@ -41,8 +43,9 @@ void setup() { final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); - swirldStateManager = new SwirldStateManager(platformContext, roster); - swirldStateManager.setState(initialState, true); + stateLifecycleManager = new StateLifecycleManagerImpl<>( + platformContext.getMetrics(), platformContext.getTime(), TestVirtualMapState::new); + stateLifecycleManager.initState(initialState, true); } @AfterEach @@ -50,8 +53,8 @@ void tearDown() { if (!initialState.isDestroyed()) { initialState.release(); } - if (!swirldStateManager.getConsensusState().isDestroyed()) { - swirldStateManager.getConsensusState().release(); + if (!stateLifecycleManager.getMutableState().isDestroyed()) { + stateLifecycleManager.getMutableState().release(); } MerkleDbTestUtils.assertAllDatabasesClosed(); } @@ -64,39 +67,39 @@ void initialStateReferenceCount() { initialState.getRoot().getReservationCount(), "The initial state is copied and should be referenced once as the previous immutable state."); Reservable consensusStateAsReservable = - swirldStateManager.getConsensusState().getRoot(); + stateLifecycleManager.getMutableState().getRoot(); assertEquals( 1, consensusStateAsReservable.getReservationCount(), "The consensus state should have one reference."); } @Test @DisplayName("Load From Signed State - state reference counts") - void setStateRefCount() { + void initStateRefCount() { final SignedState ss1 = newSignedState(); final Reservable state1 = ss1.getState().getRoot(); - swirldStateManager.setState(ss1.getState(), false); + stateLifecycleManager.initState(ss1.getState(), false); assertEquals( 2, state1.getReservationCount(), "Loading from signed state should increment the reference count, because it is now referenced by the " - + "signed state and the previous immutable state in SwirldStateManager."); - final MerkleNodeState consensusState1 = swirldStateManager.getConsensusState(); + + "signed state and the previous immutable state in StateLifecycleManagerImpl."); + final MerkleNodeState consensusState1 = stateLifecycleManager.getMutableState(); assertEquals( 1, consensusState1.getRoot().getReservationCount(), "The current consensus state should have a single reference count."); final SignedState ss2 = newSignedState(); - swirldStateManager.setState(ss2.getState(), false); - final MerkleNodeState consensusState2 = swirldStateManager.getConsensusState(); + stateLifecycleManager.initState(ss2.getState(), false); + final MerkleNodeState consensusState2 = stateLifecycleManager.getMutableState(); Reservable state2 = ss2.getState().getRoot(); assertEquals( 2, state2.getReservationCount(), "Loading from signed state should increment the reference count, because it is now referenced by the " - + "signed state and the previous immutable state in SwirldStateManager."); + + "signed state and the previous immutable state in StateLifecycleManagerImpl."); assertEquals( 1, consensusState2.getRoot().getReservationCount(), @@ -114,7 +117,7 @@ void setStateRefCount() { private static MerkleNodeState newState(PlatformStateFacade platformStateFacade) { final String virtualMapLabel = - SwirldsStateManagerTests.class.getSimpleName() + "-" + java.util.UUID.randomUUID(); + StateLifecycleManagerTests.class.getSimpleName() + "-" + java.util.UUID.randomUUID(); final MerkleNodeState state = TestVirtualMapState.createInstanceWithVirtualMapLabel(virtualMapLabel); TestingAppStateInitializer.initPlatformState(state); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java index 2389a9b2c765..50691fa3078d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSignatureCollectorTester.java @@ -106,7 +106,7 @@ private List processStates(@Nullable final List stateLifecycleManager; @BeforeEach void beforeEach() throws IOException { @@ -126,22 +130,24 @@ private SignedState writeState( final SignedState signedState = new RandomSignedStateGenerator(random).setRound(round).build(); - // make the state immutable - signedState.getState().copy().release(); + stateLifecycleManager = + new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); + stateLifecycleManager.initState(signedState.getState(), true); + stateLifecycleManager.getMutableState().release(); // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19905 TestMerkleCryptoFactory.getInstance() .digestTreeSync(signedState.getState().getRoot()); final Path savedStateDirectory = signedStateFilePath.getSignedStateDirectory(mainClassName, selfId, swirldName, round); - + stateLifecycleManager.setSnapshotSource(signedState); writeSignedStateToDisk( platformContext, selfId, savedStateDirectory, - signedState, StateToDiskReason.PERIODIC_SNAPSHOT, - platformStateFacade); + platformStateFacade, + stateLifecycleManager); if (corrupted) { final Path stateFilePath = savedStateDirectory.resolve("SignedState.swh"); diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java index 78dd3d28a7aa..ac7097fe2b3a 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java @@ -6,8 +6,6 @@ import com.swirlds.common.merkle.MerkleNode; import com.swirlds.state.lifecycle.StateMetadata; import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; -import java.nio.file.Path; import org.hiero.base.crypto.Hash; /** @@ -74,12 +72,6 @@ default MerkleNode getRoot() { */ void removeServiceState(@NonNull String serviceName, int stateId); - /** - * Loads a snapshot of a state. - * @param targetPath The path to load the snapshot from. - */ - MerkleNodeState loadSnapshot(@NonNull Path targetPath) throws IOException; - /** * Get the merkle path of the singleton state by its ID. * @param stateId The state ID of the singleton state. diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java new file mode 100644 index 000000000000..69806a468c37 --- /dev/null +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.swirlds.state; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Implementations of this interface can provide access to their {@link MerkleNodeState}. + */ +public interface MerkleNodeStateAware { + /** + * An instance of {@link MerkleNodeState} associated with this object. + * @return an instance of {@link MerkleNodeState} associated with this object. + */ + @NonNull + MerkleNodeState getState(); +} diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/State.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/State.java index 2a1cb3e4ce09..5063f4ca0d34 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/State.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/State.java @@ -9,8 +9,6 @@ import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.io.IOException; -import java.nio.file.Path; import org.hiero.base.crypto.Hash; import org.hiero.base.crypto.Hashable; @@ -94,22 +92,6 @@ default void computeHash() { throw new UnsupportedOperationException(); } - /** - * Creates a snapshot for the state. The state has to be hashed and immutable before calling this method. - * @param targetPath The path to save the snapshot. - */ - default void createSnapshot(final @NonNull Path targetPath) { - throw new UnsupportedOperationException(); - } - - /** - * Loads a snapshot of a state. - * @param targetPath The path to load the snapshot from. - */ - default State loadSnapshot(final @NonNull Path targetPath) throws IOException { - throw new UnsupportedOperationException(); - } - /** * Used to track the status of the Platform. * @return {@code true} if Platform status is not {@code PlatformStatus.ACTIVE}. diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java new file mode 100644 index 000000000000..d7a29fcac6a3 --- /dev/null +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.swirlds.state; + +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.file.Path; + +/** + * An implementaion of this class is responsible + * @param a type of the snapshot source, should implement {@link MerkleNodeStateAware} + */ +public interface StateLifecycleManager { + + /** + * Set the initial State. This method should only be on a startup of after a reconnect. + * + * @param state the initial state + */ + void initState(@NonNull final MerkleNodeState state, boolean onStartup); + + /** + * Get the mutable state. + */ + MerkleNodeState getMutableState(); + + /** + * Get the latest immutable state. + * @return the latest immutable state. + */ + MerkleNodeState getLatestImmutableState(); + + /** + * Sets the state to create a snapshot from. + * @param source the state to create a snapshot from. + */ + void setSnapshotSource(@NonNull T source); + + T getSnapshotSource(); + + /** + * Creates a snapshot for the state that was previously set with {@link #setSnapshotSource(MerkleNodeStateAware)}. + * The state has to be hashed before calling this method. Once the snapshot is created, the manager releases the source + * state of the snapshot and clears the reference to it. + * + * @param targetPath The path to save the snapshot. + */ + void createSnapshot(@NonNull Path targetPath); + + /** + * Loads a snapshot of a state and uses it as a new mutable state. + * + * @param targetPath The path to load the snapshot from. + * @return mutable copy of the loaded state + */ + MerkleNodeState loadSnapshot(@NonNull Path targetPath); + + /** + * Creates a mutable copy of the state. The previous mutable state becomes immutable, + * replacing the latest immutable state. + * + * @return a mutable copy of the current mutable state which became the latest immutable state. + */ + MerkleNodeState copyMutableState(); +} diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java new file mode 100644 index 000000000000..1ee25591cd34 --- /dev/null +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.swirlds.state.merkle; + +import static com.swirlds.base.units.UnitConstants.NANOSECONDS_TO_MICROSECONDS; +import static java.util.Objects.requireNonNull; + +import com.swirlds.base.time.Time; +import com.swirlds.common.merkle.MerkleNode; +import com.swirlds.common.merkle.utility.MerkleTreeSnapshotReader; +import com.swirlds.common.merkle.utility.MerkleTreeSnapshotWriter; +import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.MerkleNodeState; +import com.swirlds.state.MerkleNodeStateAware; +import com.swirlds.state.State; +import com.swirlds.state.StateLifecycleManager; +import com.swirlds.virtualmap.VirtualMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +/** + * This class is responsible for maintaining references to the mutable state and the latest immutable state. + * It also updates these references upon state signing. + */ +public class StateLifecycleManagerImpl implements StateLifecycleManager { + + /** + * Metrics for the state object + */ + private final StateMetrics stateMetrics; + + /** + * Metrics for the snapshot creation process + */ + private final MerkleRootSnapshotMetrics snapshotMetrics; + + private final Time time; + + private final Metrics metrics; + private final Function stateSupplier; + + /** + * reference to the state that reflects all known consensus transactions + */ + private final AtomicReference stateRef = new AtomicReference<>(); + + /** + * The most recent immutable state. No value until the first fast copy is created. + */ + private final AtomicReference latestImmutableStateRef = new AtomicReference<>(); + + /** + * The most recent immutable state. No value until the first fast copy is created. + */ + private final AtomicReference snapshotSource = new AtomicReference<>(); + + /** + * Constructor. + * + * @param metrics the metrics object to gather state metrics + * @param time the time object + */ + public StateLifecycleManagerImpl( + @NonNull final Metrics metrics, + @NonNull Time time, + Function stateSupplier) { + requireNonNull(metrics); + this.stateSupplier = stateSupplier; + this.metrics = metrics; + this.stateMetrics = new StateMetrics(metrics); + this.snapshotMetrics = new MerkleRootSnapshotMetrics(metrics); + this.time = time; + } + + /** + * Set the initial State. This method should only be on a startup of after a reconnect. + * + * @param state the initial state + */ + public void initState(@NonNull final MerkleNodeState state, boolean onStartup) { + requireNonNull(state); + + state.throwIfDestroyed("state must not be destroyed"); + state.throwIfImmutable("state must be mutable"); + + if (onStartup && stateRef.get() != null) { + throw new IllegalStateException("Attempt to set initial state when there is already a state reference."); + } + + updateStateRefs(state); + } + + @Override + public void setSnapshotSource(@NonNull T source) { + requireNonNull(source); + MerkleNodeState state = source.getState(); + state.throwIfDestroyed("state must not be destroyed"); + state.throwIfMutable("state must be immutable"); + boolean result = snapshotSource.compareAndSet(null, source); + assert result : "Snapshot source was already set"; + } + + @Override + public T getSnapshotSource() { + return snapshotSource.get(); + } + + @Override + public MerkleNodeState getMutableState() { + return stateRef.get(); + } + + @Override + public MerkleNodeState getLatestImmutableState() { + return latestImmutableStateRef.get(); + } + + @Override + public MerkleNodeState copyMutableState() { + final MerkleNodeState state = stateRef.get(); + updateStateRefs(state); + return stateRef.get(); + } + + private void updateStateRefs(MerkleNodeState state) { + // Create a fast copy so there is always an immutable state to + // invoke handleTransaction on for pre-consensus transactions + final long copyStart = System.nanoTime(); + // Create a fast copy + final MerkleNodeState copy = state.copy(); + // Increment the reference count because this reference becomes the new value + copy.getRoot().reserve(); + final long copyEnd = System.nanoTime(); + stateMetrics.stateCopyMicros((copyEnd - copyStart) * NANOSECONDS_TO_MICROSECONDS); + // Set latest immutable first to prevent the newly immutable stateRoot from being deleted between setting the + // stateRef and the latestImmutableState + setLatestImmutableState(state); + updateStateRef(copy); + } + + /** + * Sets the consensus state to the state provided. Must be mutable and have a reference count of at least 1. + * + * @param state a new mutable state + */ + private void updateStateRef(final MerkleNodeState state) { + final var currVal = stateRef.get(); + if (currVal != null && !currVal.isDestroyed()) { + currVal.release(); + } + // Do not increment the reference count because the state provided already has a reference count of at least + // one to represent this reference and to prevent it from being deleted before this reference is set. + stateRef.set(state); + } + + private void setLatestImmutableState(final MerkleNodeState latestImmutableState) { + final State currVal = latestImmutableStateRef.get(); + if (currVal != null && !currVal.isDestroyed()) { + currVal.release(); + } + latestImmutableState.getRoot().reserve(); + latestImmutableStateRef.set(latestImmutableState); + } + + /** + * {@inheritDoc}} + */ + public void createSnapshot(final @NonNull Path targetPath) { + requireNonNull(time); + requireNonNull(snapshotMetrics); + VirtualMapState state = (VirtualMapState) snapshotSource.get().getState(); + state.throwIfMutable(); + state.throwIfDestroyed(); + final long startTime = time.currentTimeMillis(); + MerkleTreeSnapshotWriter.createSnapshot(state.virtualMap, targetPath, state.getRound()); + snapshotMetrics.updateWriteStateToDiskTimeMetric(time.currentTimeMillis() - startTime); + snapshotSource.set(null); + } + + @Override + public MerkleNodeState loadSnapshot(@NonNull Path targetPath) { + final MerkleNode root; + try { + root = MerkleTreeSnapshotReader.readStateFileData(targetPath).stateRoot(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + if (!(root instanceof VirtualMap readVirtualMap)) { + throw new IllegalStateException( + "Root should be a VirtualMap, but it is " + root.getClass().getSimpleName() + " instead"); + } + + final var mutableCopy = readVirtualMap.copy(); + mutableCopy.registerMetrics(metrics); + readVirtualMap.release(); + readVirtualMap = mutableCopy; + + return stateSupplier.apply(readVirtualMap); + } +} diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java index ce9d2fb32687..4e423cd76613 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java @@ -19,8 +19,6 @@ import com.swirlds.base.time.Time; import com.swirlds.common.Reservable; import com.swirlds.common.merkle.MerkleNode; -import com.swirlds.common.merkle.utility.MerkleTreeSnapshotReader; -import com.swirlds.common.merkle.utility.MerkleTreeSnapshotWriter; import com.swirlds.config.api.Configuration; import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.config.MerkleDbConfig; @@ -58,7 +56,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -80,11 +77,6 @@ public abstract class VirtualMapState> implements M private static final Logger logger = LogManager.getLogger(VirtualMapState.class); - /** - * Metrics for the snapshot creation process - */ - private final MerkleRootSnapshotMetrics snapshotMetrics; - /** * Maintains information about all services known by this instance. Map keys are * service names, values are service states by service ID. @@ -108,8 +100,6 @@ public abstract class VirtualMapState> implements M private final Metrics metrics; - private final Time time; - protected VirtualMap virtualMap; /** @@ -129,7 +119,6 @@ public VirtualMapState( @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Time time) { requireNonNull(configuration); this.metrics = requireNonNull(metrics); - this.time = requireNonNull(time); final MerkleDbDataSourceBuilder dsBuilder; final MerkleDbConfig merkleDbConfig = configuration.getConfigData(MerkleDbConfig.class); dsBuilder = new MerkleDbDataSourceBuilder( @@ -137,7 +126,6 @@ public VirtualMapState( this.virtualMap = new VirtualMap(VM_LABEL, dsBuilder, configuration); this.virtualMap.registerMetrics(metrics); - this.snapshotMetrics = new MerkleRootSnapshotMetrics(metrics); } /** @@ -151,8 +139,6 @@ public VirtualMapState( @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { this.virtualMap = requireNonNull(virtualMap); this.metrics = requireNonNull(metrics); - this.time = requireNonNull(time); - this.snapshotMetrics = new MerkleRootSnapshotMetrics(metrics); } /** @@ -163,9 +149,7 @@ public VirtualMapState( protected VirtualMapState(@NonNull final VirtualMapState from) { this.virtualMap = from.virtualMap.copy(); this.metrics = from.metrics; - this.time = from.time; this.startupMode = from.startupMode; - this.snapshotMetrics = new MerkleRootSnapshotMetrics(from.metrics); this.listeners.addAll(from.listeners); // Copy over the metadata @@ -181,16 +165,6 @@ protected VirtualMapState(@NonNull final VirtualMapState from) { */ protected abstract T copyingConstructor(); - /** - * Creates a new instance. - * - * @param virtualMap should have already registered metrics - * @param metrics the platform metric instance to use when creating the new instance of state - * @param time the time instance to use when creating the new instance of state - */ - protected abstract T newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time); - // State interface implementation /** @@ -251,40 +225,6 @@ public void computeHash() { virtualMap.getHash(); } - /** - * {@inheritDoc} - */ - @Override - public void createSnapshot(@NonNull final Path targetPath) { - requireNonNull(time); - requireNonNull(snapshotMetrics); - virtualMap.throwIfMutable(); - virtualMap.throwIfDestroyed(); - final long startTime = time.currentTimeMillis(); - MerkleTreeSnapshotWriter.createSnapshot(virtualMap, targetPath, getRound()); - snapshotMetrics.updateWriteStateToDiskTimeMetric(time.currentTimeMillis() - startTime); - } - - /** - * {@inheritDoc} - */ - @Override - public T loadSnapshot(@NonNull Path targetPath) throws IOException { - final MerkleNode root = - MerkleTreeSnapshotReader.readStateFileData(targetPath).stateRoot(); - if (!(root instanceof VirtualMap readVirtualMap)) { - throw new IllegalStateException( - "Root should be a VirtualMap, but it is " + root.getClass().getSimpleName() + " instead"); - } - - final var mutableCopy = readVirtualMap.copy(); - mutableCopy.registerMetrics(metrics); - readVirtualMap.release(); - readVirtualMap = mutableCopy; - - return newInstance(readVirtualMap, metrics, time); - } - /** * Initializes the defined service state. * diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java index 3333a0d80692..8b635f2a5684 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java @@ -12,8 +12,6 @@ import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.crypto.MerkleCryptography; import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; -import com.swirlds.common.merkle.utility.MerkleTreeSnapshotReader; -import com.swirlds.common.merkle.utility.MerkleTreeSnapshotWriter; import com.swirlds.common.utility.Labeled; import com.swirlds.common.utility.RuntimeObjectRecord; import com.swirlds.common.utility.RuntimeObjectRegistry; @@ -48,7 +46,6 @@ import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -848,27 +845,4 @@ public void computeHash() { Thread.currentThread().interrupt(); } } - - /** - * {@inheritDoc} - */ - @Override - public void createSnapshot(@NonNull final Path targetPath) { - requireNonNull(time); - requireNonNull(snapshotMetrics); - throwIfMutable(); - throwIfDestroyed(); - final long startTime = time.currentTimeMillis(); - MerkleTreeSnapshotWriter.createSnapshot(this, targetPath, getRound()); - snapshotMetrics.updateWriteStateToDiskTimeMetric(time.currentTimeMillis() - startTime); - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @Override - public T loadSnapshot(@NonNull Path targetPath) throws IOException { - return (T) MerkleTreeSnapshotReader.readStateFileData(targetPath).stateRoot(); - } } diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java index 152f7c7b57b8..cb658a311161 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java @@ -4,9 +4,7 @@ import static com.swirlds.state.test.fixtures.merkle.VirtualMapUtils.CONFIGURATION; import com.swirlds.base.test.fixtures.time.FakeTime; -import com.swirlds.base.time.Time; import com.swirlds.common.metrics.noop.NoOpMetrics; -import com.swirlds.metrics.api.Metrics; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; import com.swirlds.state.merkle.VirtualMapState; @@ -38,12 +36,6 @@ protected TestVirtualMapState copyingConstructor() { return new TestVirtualMapState(this); } - @Override - protected TestVirtualMapState newInstance( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - return new TestVirtualMapState(virtualMap); - } - public static TestVirtualMapState createInstanceWithVirtualMapLabel(@NonNull final String virtualMapLabel) { final var virtualMap = VirtualMapUtils.createVirtualMap(CONFIGURATION, virtualMapLabel); return new TestVirtualMapState(virtualMap); From c80769fe66625584453ccee190f24b2df8c651c5 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Mon, 3 Nov 2025 21:43:13 -0500 Subject: [PATCH 02/23] Addressed a review comment - added missing generics Signed-off-by: Ivan Malygin --- .../com/hedera/node/app/state/merkle/SerializationTest.java | 4 ++-- .../src/main/java/com/swirlds/platform/SwirldsPlatform.java | 2 +- .../java/com/swirlds/platform/builder/PlatformBuilder.java | 3 ++- .../platform/eventhandling/TransactionHandlerTester.java | 6 +++--- .../swirlds/platform/reconnect/ReconnectControllerTest.java | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index 22739d3011c9..c34686dea046 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -162,7 +162,7 @@ private void forceFlush(ReadableKVState state) { @ValueSource(booleans = {true, false}) void simpleReadAndWrite(boolean forceFlush) throws IOException, ConstructableRegistryException { final Schema schemaV1 = createV1Schema(); - final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); // When we serialize it to bytes and deserialize it back into a tree @@ -221,7 +221,7 @@ void snapshot() throws IOException { @Test void dualReadAndWrite() throws IOException, ConstructableRegistryException { final Schema schemaV1 = createV1Schema(); - final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); MerkleNodeState copy = stateLifecycleManager.copyMutableState(); // make a copy to make VM flushable diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index b1b8a9e2203f..2fcaa7ac595f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -217,7 +217,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { initializeState(this, platformContext, initialState, blocks.consensusStateEventHandler(), platformStateFacade); // This object makes a copy of the state. After this point, initialState becomes immutable. - final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); + final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); stateLifecycleManager.initState(initialState.getState(), true); platformStateFacade.setCreationSoftwareVersionTo(stateLifecycleManager.getMutableState(), blocks.appVersion()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index a8b680d980b6..6a5e1c569fa5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -37,6 +37,7 @@ import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.Platform; import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.platform.wiring.PlatformWiring; @@ -434,7 +435,7 @@ public PlatformComponentBuilder buildComponentBuilder() { final ApplicationCallbacks callbacks = new ApplicationCallbacks(preconsensusEventConsumer, snapshotOverrideConsumer, staleEventConsumer); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( platformContext.getMetrics(), platformContext.getTime(), createStateFromVirtualMap); if (model == null) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java index 34465087a29a..701fc3684259 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java @@ -33,7 +33,7 @@ */ public class TransactionHandlerTester { private final PlatformStateModifier platformState; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final DefaultTransactionHandler defaultTransactionHandler; private final List submittedActions = new ArrayList<>(); private final List handledRounds = new ArrayList<>(); @@ -66,7 +66,7 @@ public TransactionHandlerTester() { .when(consensusStateEventHandler) .onHandleConsensusRound(any(), same(consensusState), any()); final StatusActionSubmitter statusActionSubmitter = submittedActions::add; - stateLifecycleManager = new StateLifecycleManagerImpl( + stateLifecycleManager = new StateLifecycleManagerImpl<>( platformContext.getMetrics(), platformContext.getTime(), vm -> consensusState); stateLifecycleManager.initState(consensusState, true); defaultTransactionHandler = new DefaultTransactionHandler( @@ -110,7 +110,7 @@ public List getHandledRounds() { /** * @return the {@link StateLifecycleManager} used by this tester */ - public StateLifecycleManager getStateLifecycleManager() { + public StateLifecycleManager getStateLifecycleManager() { return stateLifecycleManager; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java index ab7b976449b9..bff45adc9777 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java @@ -84,7 +84,7 @@ class ReconnectControllerTest { private MerkleCryptography merkleCryptography; private Platform platform; private PlatformCoordinator platformCoordinator; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; private SavedStateController savedStateController; private ConsensusStateEventHandler consensusStateEventHandler; private ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; From d14a8af787abef83892f854e9e734bbc10ead348 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 09:49:43 -0500 Subject: [PATCH 03/23] Update platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java Co-authored-by: Nikita Lebedev Signed-off-by: Ivan Malygin --- .../com/swirlds/state/merkle/StateLifecycleManagerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 1ee25591cd34..e2f44fa33e81 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -65,8 +65,8 @@ public class StateLifecycleManagerImpl implement */ public StateLifecycleManagerImpl( @NonNull final Metrics metrics, - @NonNull Time time, - Function stateSupplier) { + @NonNull final Time time, + @NonNull final Function stateSupplier) { requireNonNull(metrics); this.stateSupplier = stateSupplier; this.metrics = metrics; From 835d3411824d1e41b282e5f67f4e0bb4ee1fe88c Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 09:49:52 -0500 Subject: [PATCH 04/23] Update platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java Co-authored-by: Nikita Lebedev Signed-off-by: Ivan Malygin --- .../com/swirlds/state/merkle/StateLifecycleManagerImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index e2f44fa33e81..f820e0916439 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -180,6 +180,9 @@ public void createSnapshot(final @NonNull Path targetPath) { snapshotSource.set(null); } + /** + * {@inheritDoc} + */ @Override public MerkleNodeState loadSnapshot(@NonNull Path targetPath) { final MerkleNode root; From a1c4f94b2b9628cd8a0b42efcff0143b8cc44c5d Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 09:50:01 -0500 Subject: [PATCH 05/23] Update platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java Co-authored-by: Nikita Lebedev Signed-off-by: Ivan Malygin --- .../com/swirlds/state/merkle/StateLifecycleManagerImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index f820e0916439..10e615209566 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -166,8 +166,9 @@ private void setLatestImmutableState(final MerkleNodeState latestImmutableState) } /** - * {@inheritDoc}} + * {@inheritDoc} */ + @Override public void createSnapshot(final @NonNull Path targetPath) { requireNonNull(time); requireNonNull(snapshotMetrics); From b0f959679a34300f56e8f5c5c8ec008443ac86c5 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 10:19:57 -0500 Subject: [PATCH 06/23] Update platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java Co-authored-by: Nikita Lebedev Signed-off-by: Ivan Malygin --- .../com/swirlds/state/merkle/StateLifecycleManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 10e615209566..286523fbbf98 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -125,7 +125,7 @@ public MerkleNodeState copyMutableState() { return stateRef.get(); } - private void updateStateRefs(MerkleNodeState state) { + private void copyAndUpdateStateRefs(MerkleNodeState state) { // Create a fast copy so there is always an immutable state to // invoke handleTransaction on for pre-consensus transactions final long copyStart = System.nanoTime(); From 5b1b28c69992bb91c690e37635359dbc9c7f7b0c Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 10:25:11 -0500 Subject: [PATCH 07/23] Removed unused fields, improved javadoc Signed-off-by: Ivan Malygin --- .../com/swirlds/state/StateLifecycleManager.java | 12 +++++++++--- .../state/merkle/StateLifecycleManagerImpl.java | 8 ++++++-- .../state/test/fixtures/merkle/MerkleStateRoot.java | 12 ------------ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index d7a29fcac6a3..8db30f69175a 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -5,8 +5,14 @@ import java.nio.file.Path; /** - * An implementaion of this class is responsible - * @param a type of the snapshot source, should implement {@link MerkleNodeStateAware} + * Implementations of this interface are responsible for managing the state lifecycle: + *
    + *
  • Maintaining references to a mutable state and the latest immutable state.
  • + *
  • Creating snapshots of the state.
  • + *
  • Loading snapshots of the state.
  • + *
  • Creating a mutable copy of the state, while making the current mutable state immutable.
  • + *
+ * @param A type of the snapshot source, which should implement {@link MerkleNodeStateAware}. */ public interface StateLifecycleManager { @@ -46,7 +52,7 @@ public interface StateLifecycleManager { void createSnapshot(@NonNull Path targetPath); /** - * Loads a snapshot of a state and uses it as a new mutable state. + * Loads a snapshot of a state. * * @param targetPath The path to load the snapshot from. * @return mutable copy of the loaded state diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 286523fbbf98..2db7f583c45c 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -90,7 +90,7 @@ public void initState(@NonNull final MerkleNodeState state, boolean onStartup) { throw new IllegalStateException("Attempt to set initial state when there is already a state reference."); } - updateStateRefs(state); + copyAndUpdateStateRefs(state); } @Override @@ -121,10 +121,14 @@ public MerkleNodeState getLatestImmutableState() { @Override public MerkleNodeState copyMutableState() { final MerkleNodeState state = stateRef.get(); - updateStateRefs(state); + copyAndUpdateStateRefs(state); return stateRef.get(); } + /** + * Copies the provided state and updates both the latest immutable state and the mutable state reference. + * @param state the state to copy and update references for + */ private void copyAndUpdateStateRefs(MerkleNodeState state) { // Create a fast copy so there is always an immutable state to // invoke handleTransaction on for pre-consensus transactions diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java index 8b635f2a5684..82b2654e1bdf 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/MerkleStateRoot.java @@ -19,7 +19,6 @@ import com.swirlds.state.State; import com.swirlds.state.StateChangeListener; import com.swirlds.state.lifecycle.StateMetadata; -import com.swirlds.state.merkle.MerkleRootSnapshotMetrics; import com.swirlds.state.spi.CommittableWritableStates; import com.swirlds.state.spi.EmptyReadableStates; import com.swirlds.state.spi.KVChangeListener; @@ -103,11 +102,6 @@ public abstract class MerkleStateRoot> extends Part return services; } - /** - * Metrics for the snapshot creation process - */ - private final MerkleRootSnapshotMetrics snapshotMetrics; - /** * Maintains information about all services known by this instance. Map keys are * service names, values are service states by service ID. @@ -135,8 +129,6 @@ public abstract class MerkleStateRoot> extends Part private final Metrics metrics; - private final Time time; - private final MerkleCryptography merkleCryptography; /** @@ -149,9 +141,7 @@ public MerkleStateRoot( @NonNull final MerkleCryptography merkleCryptography) { this.registryRecord = RuntimeObjectRegistry.createRecord(getClass()); this.metrics = requireNonNull(metrics); - this.time = requireNonNull(time); this.merkleCryptography = requireNonNull(merkleCryptography); - this.snapshotMetrics = new MerkleRootSnapshotMetrics(metrics); } /** @@ -164,9 +154,7 @@ protected MerkleStateRoot(@NonNull final MerkleStateRoot from) { super(from); this.registryRecord = RuntimeObjectRegistry.createRecord(getClass()); this.metrics = from.metrics; - this.time = from.time; this.merkleCryptography = from.merkleCryptography; - this.snapshotMetrics = new MerkleRootSnapshotMetrics(from.metrics); this.listeners.addAll(from.listeners); // Copy over the metadata From 4245de1896519fbdfbf9f828a286db584cbe8329 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 11:49:51 -0500 Subject: [PATCH 08/23] Added missing generics. Signed-off-by: Ivan Malygin --- .../com/hedera/node/app/state/merkle/SerializationTest.java | 2 +- .../platform/eventhandling/DefaultTransactionHandler.java | 4 ++-- .../java/com/swirlds/platform/gossip/SyncGossipModular.java | 4 ++-- .../platform/network/protocol/ReconnectStateSyncProtocol.java | 4 ++-- .../com/swirlds/platform/reconnect/ReconnectController.java | 4 ++-- .../platform/reconnect/ReconnectStatePeerProtocol.java | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index c34686dea046..4b4966c80b5e 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -285,7 +285,7 @@ private void initServices(Schema schemaV1, MerkleNodeState load loadedTree.getRoot().migrate(MINIMUM_SUPPORTED_VERSION); } - private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { + private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { final SignedState randomState = new RandomSignedStateGenerator().setRound(1).build(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java index 279360daa267..39a8821936f6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java @@ -62,7 +62,7 @@ public class DefaultTransactionHandler implements TransactionHandler { /** * The class responsible for all interactions with the swirld state */ - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final RoundHandlingMetrics handlerMetrics; @@ -137,7 +137,7 @@ public class DefaultTransactionHandler implements TransactionHandler { */ public DefaultTransactionHandler( @NonNull final PlatformContext platformContext, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final SemanticVersion softwareVersion, @NonNull final PlatformStateFacade platformStateFacade, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java index 6c2a408f1d7f..e097941e0fae 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java @@ -72,7 +72,7 @@ public class SyncGossipModular implements Gossip { private final AbstractSyncProtocol syncProtocol; private final FallenBehindMonitor fallenBehindMonitor; private final AbstractShadowgraphSynchronizer synchronizer; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; // this is not a nice dependency, should be removed as well as the sharedState @@ -103,7 +103,7 @@ public SyncGossipModular( @NonNull final Roster roster, @NonNull final NodeId selfId, @NonNull final SemanticVersion appVersion, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Supplier latestCompleteState, @NonNull final IntakeEventCounter intakeEventCounter, @NonNull final PlatformStateFacade platformStateFacade, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java index 1e26ab142232..2e3c5845ac90 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java @@ -39,7 +39,7 @@ public class ReconnectStateSyncProtocol implements Protocol { private final PlatformContext platformContext; private final AtomicReference platformStatus = new AtomicReference<>(PlatformStatus.STARTING_UP); private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; public ReconnectStateSyncProtocol( @@ -52,7 +52,7 @@ public ReconnectStateSyncProtocol( @NonNull final FallenBehindMonitor fallenBehindManager, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java index a72262f9e143..6684cd559fbd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java @@ -72,7 +72,7 @@ public class ReconnectController implements Runnable { private final Platform platform; private final PlatformContext platformContext; private final PlatformCoordinator platformCoordinator; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final SavedStateController savedStateController; private final ConsensusStateEventHandler consensusStateEventHandler; private final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; @@ -90,7 +90,7 @@ public ReconnectController( @NonNull final Platform platform, @NonNull final PlatformContext platformContext, @NonNull final PlatformCoordinator platformCoordinator, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final SavedStateController savedStateController, @NonNull final ConsensusStateEventHandler consensusStateEventHandler, @NonNull final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java index 2e4e282db531..07784d68e60f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java @@ -82,7 +82,7 @@ public class ReconnectStatePeerProtocol implements PeerProtocol { private final Time time; private final PlatformContext platformContext; private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; /** @@ -112,7 +112,7 @@ public ReconnectStatePeerProtocol( @NonNull final Time time, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); From 089d78b64dae03db4e182b105a1749208ed85a97 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 13:37:19 -0500 Subject: [PATCH 09/23] A flaky test fix. Signed-off-by: Ivan Malygin --- .../protocol/ReservedSignedStateResultPromiseTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/protocol/ReservedSignedStateResultPromiseTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/protocol/ReservedSignedStateResultPromiseTest.java index 95837dc5dde8..cb0f6735e078 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/protocol/ReservedSignedStateResultPromiseTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/protocol/ReservedSignedStateResultPromiseTest.java @@ -357,6 +357,7 @@ void testMultipleProvidersCompeting() throws InterruptedException { final AtomicInteger providedCount = new AtomicInteger(0); final AtomicInteger consumedCount = new AtomicInteger(0); final CountDownLatch allConsumed = new CountDownLatch(1); + final CountDownLatch allProvided = new CountDownLatch(numProviders); // Consumer final Thread consumer = new Thread(() -> { @@ -378,7 +379,6 @@ void testMultipleProvidersCompeting() throws InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(numProviders); for (int i = 0; i < numProviders; i++) { executor.submit(() -> { - int provided = 0; while (providedCount.get() < numResources) { if (promise.acquire()) { if (providedCount.get() < numResources) { @@ -386,7 +386,7 @@ void testMultipleProvidersCompeting() throws InterruptedException { final ReservedSignedState mockState = ReservedSignedState.createNullReservation(); promise.resolveWithValue(mockState); providedCount.incrementAndGet(); - provided++; + allProvided.countDown(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); return; @@ -400,6 +400,7 @@ void testMultipleProvidersCompeting() throws InterruptedException { }); } + assertTrue(allProvided.await(5, TimeUnit.SECONDS), "All resources should be provided"); assertTrue(allConsumed.await(5, TimeUnit.SECONDS), "All resources should be consumed"); assertEquals(numResources, consumedCount.get(), "Consumer should receive all resources"); assertEquals(numResources, providedCount.get(), "Exactly numResources should be provided"); From a6d272fb29b225c87e5f6e133a0762025361b2df Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 16:34:26 -0500 Subject: [PATCH 10/23] Addressed review comments: - removed an unused field - updated javadoc - gathered all copy and update refs logic in one method Signed-off-by: Ivan Malygin --- .../com/swirlds/platform/SwirldsPlatform.java | 2 +- .../DefaultSavedStateController.java | 10 +--- .../platform/StateFileManagerTests.java | 2 +- .../swirlds/state/StateLifecycleManager.java | 8 +-- .../merkle/StateLifecycleManagerImpl.java | 52 +++++++------------ 5 files changed, 27 insertions(+), 47 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 2fcaa7ac595f..5b2ccd78a11a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -190,7 +190,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { final LatestCompleteStateNexus latestCompleteStateNexus = new DefaultLatestCompleteStateNexus(platformContext); - savedStateController = new DefaultSavedStateController(platformContext, blocks.stateLifecycleManager()); + savedStateController = new DefaultSavedStateController(platformContext); stateLifecycleManager = blocks.stateLifecycleManager(); final SignedStateMetrics signedStateMetrics = new SignedStateMetrics(platformContext.getMetrics()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java index 3dc076c4abdf..65fe4dde7cea 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/DefaultSavedStateController.java @@ -13,7 +13,6 @@ import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.StateToDiskReason; -import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -32,19 +31,14 @@ public class DefaultSavedStateController implements SavedStateController { private Instant previousSavedStateTimestamp; private final StateConfig stateConfig; - private final StateLifecycleManager stateLifecycleManager; /** * Constructor * - * @param platformContext the platform context - * @param stateLifecycleManager + * @param platformContext the platform context* */ - public DefaultSavedStateController( - @NonNull final PlatformContext platformContext, - @NonNull final StateLifecycleManager stateLifecycleManager) { + public DefaultSavedStateController(@NonNull final PlatformContext platformContext) { this.stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); - this.stateLifecycleManager = stateLifecycleManager; } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index c8831755e6ee..693e0963b6db 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -232,7 +232,7 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { final StateSnapshotManager manager = new DefaultStateSnapshotManager( context, MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, TEST_PLATFORM_STATE_FACADE, stateLifecycleManager); - final SavedStateController controller = new DefaultSavedStateController(context, stateLifecycleManager); + final SavedStateController controller = new DefaultSavedStateController(context); Instant timestamp; final long firstRound; diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index 8db30f69175a..4ba9217ae01f 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -12,24 +12,26 @@ *
  • Loading snapshots of the state.
  • *
  • Creating a mutable copy of the state, while making the current mutable state immutable.
  • * + * + * An implementation of this class must be thread-safe. * @param A type of the snapshot source, which should implement {@link MerkleNodeStateAware}. */ public interface StateLifecycleManager { /** - * Set the initial State. This method should only be on a startup of after a reconnect. + * Set the initial State. This method should only be on a startup or after a reconnect. * * @param state the initial state */ void initState(@NonNull final MerkleNodeState state, boolean onStartup); /** - * Get the mutable state. + * Get the mutable state. This method is idempotent. */ MerkleNodeState getMutableState(); /** - * Get the latest immutable state. + * Get the latest immutable state. This method is idempotent. * @return the latest immutable state. */ MerkleNodeState getLatestImmutableState(); diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 2db7f583c45c..9d31b99b0d14 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -126,47 +126,31 @@ public MerkleNodeState copyMutableState() { } /** - * Copies the provided state and updates both the latest immutable state and the mutable state reference. - * @param state the state to copy and update references for + * Copies the provided to and updates both the latest immutable to and the mutable to reference. + * @param stateToCopy the state to copy and update references for */ - private void copyAndUpdateStateRefs(MerkleNodeState state) { - // Create a fast copy so there is always an immutable state to - // invoke handleTransaction on for pre-consensus transactions + private synchronized void copyAndUpdateStateRefs(MerkleNodeState stateToCopy) { final long copyStart = System.nanoTime(); - // Create a fast copy - final MerkleNodeState copy = state.copy(); + final MerkleNodeState newMutableState = stateToCopy.copy(); // Increment the reference count because this reference becomes the new value - copy.getRoot().reserve(); + newMutableState.getRoot().reserve(); final long copyEnd = System.nanoTime(); stateMetrics.stateCopyMicros((copyEnd - copyStart) * NANOSECONDS_TO_MICROSECONDS); - // Set latest immutable first to prevent the newly immutable stateRoot from being deleted between setting the - // stateRef and the latestImmutableState - setLatestImmutableState(state); - updateStateRef(copy); - } - - /** - * Sets the consensus state to the state provided. Must be mutable and have a reference count of at least 1. - * - * @param state a new mutable state - */ - private void updateStateRef(final MerkleNodeState state) { - final var currVal = stateRef.get(); - if (currVal != null && !currVal.isDestroyed()) { - currVal.release(); + // releasing previous immutable previousMutableState + final State previousImmutableState = latestImmutableStateRef.get(); + if (previousImmutableState != null && !previousImmutableState.isDestroyed()) { + previousImmutableState.release(); } - // Do not increment the reference count because the state provided already has a reference count of at least - // one to represent this reference and to prevent it from being deleted before this reference is set. - stateRef.set(state); - } - - private void setLatestImmutableState(final MerkleNodeState latestImmutableState) { - final State currVal = latestImmutableStateRef.get(); - if (currVal != null && !currVal.isDestroyed()) { - currVal.release(); + stateToCopy.getRoot().reserve(); + latestImmutableStateRef.set(stateToCopy); + final var previousMutableState = stateRef.get(); + if (previousMutableState != null && !previousMutableState.isDestroyed()) { + previousMutableState.release(); } - latestImmutableState.getRoot().reserve(); - latestImmutableStateRef.set(latestImmutableState); + // Do not increment the reference count because the stateToCopy provided already has a reference count of at + // least + // one to represent this reference and to prevent it from being deleted before this reference is set. + stateRef.set(newMutableState); } /** From afdfcb3b4018f1da15603e0f122a85b16b514a0e Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 17:18:14 -0500 Subject: [PATCH 11/23] Addressed review comments: - improved javadoc - added missing annotations - added missing final modifiers Signed-off-by: Ivan Malygin --- .../com/swirlds/platform/SwirldsPlatform.java | 3 +- .../reconnect/ReconnectController.java | 4 +- .../snapshot/DefaultStateSnapshotManager.java | 2 +- .../state/snapshot/SignedStateFileWriter.java | 6 +-- .../reconnect/ReconnectControllerTest.java | 2 +- .../swirlds/state/StateLifecycleManager.java | 14 ++++--- .../merkle/StateLifecycleManagerImpl.java | 37 +++++++++++++++---- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 5b2ccd78a11a..5cf05fdfd992 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -217,7 +217,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { initializeState(this, platformContext, initialState, blocks.consensusStateEventHandler(), platformStateFacade); // This object makes a copy of the state. After this point, initialState becomes immutable. - final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); + final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); stateLifecycleManager.initState(initialState.getState(), true); platformStateFacade.setCreationSoftwareVersionTo(stateLifecycleManager.getMutableState(), blocks.appVersion()); @@ -388,7 +388,6 @@ public void performPcesRecovery() { } else { final SignedState signedState = reservedState.get(); signedState.markAsStateToSave(StateToDiskReason.PCES_RECOVERY_COMPLETE); - stateLifecycleManager.setSnapshotSource(signedState); final StateDumpRequest request = StateDumpRequest.create(signedState.reserve("dumping PCES recovery state")); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java index 6684cd559fbd..f68115e7dc1c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java @@ -72,7 +72,7 @@ public class ReconnectController implements Runnable { private final Platform platform; private final PlatformContext platformContext; private final PlatformCoordinator platformCoordinator; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final SavedStateController savedStateController; private final ConsensusStateEventHandler consensusStateEventHandler; private final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; @@ -90,7 +90,7 @@ public ReconnectController( @NonNull final Platform platform, @NonNull final PlatformContext platformContext, @NonNull final PlatformCoordinator platformCoordinator, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final SavedStateController savedStateController, @NonNull final ConsensusStateEventHandler consensusStateEventHandler, @NonNull final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java index 88e4d680ac98..aef40b3a0c77 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java @@ -102,7 +102,7 @@ public DefaultStateSnapshotManager( @NonNull final NodeId selfId, @NonNull final String swirldName, @NonNull final PlatformStateFacade platformStateFacade, - @NonNull StateLifecycleManager stateLifecycleManager) { + @NonNull final StateLifecycleManager stateLifecycleManager) { this.platformContext = Objects.requireNonNull(platformContext); this.time = platformContext.getTime(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java index 09c5e5b79b2e..bcfe8e8876fd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java @@ -131,6 +131,8 @@ public static void writeSignatureSetFile(final @NonNull Path directory, final @N * @param platformContext the platform context * @param selfId the id of the platform * @param directory the directory where all files should be placed + * @param platformStateFacade the facade to access the platform state + * @param stateLifecycleManager the state lifecycle manager */ public static void writeSignedStateFilesToDirectory( @Nullable final PlatformContext platformContext, @@ -150,9 +152,7 @@ public static void writeSignedStateFilesToDirectory( writeMetadataFile(selfId, directory, snapshotSource, platformStateFacade); writeEmergencyRecoveryFile(directory, snapshotSource); final Roster currentRoster = snapshotSource.getRoster(); - if (currentRoster != null) { - writeRosterFile(directory, currentRoster); - } + writeRosterFile(directory, currentRoster); writeSettingsUsed(directory, platformContext.getConfiguration()); if (selfId != null) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java index bff45adc9777..7b7944e30684 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java @@ -84,7 +84,7 @@ class ReconnectControllerTest { private MerkleCryptography merkleCryptography; private Platform platform; private PlatformCoordinator platformCoordinator; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; private SavedStateController savedStateController; private ConsensusStateEventHandler consensusStateEventHandler; private ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index 4ba9217ae01f..c367cf802d3c 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -26,12 +26,14 @@ public interface StateLifecycleManager { void initState(@NonNull final MerkleNodeState state, boolean onStartup); /** - * Get the mutable state. This method is idempotent. + * Get the mutable state. Consecutive calls to this method may return different instances, + * if this method is not called on the one and the only thread that is calling {@link #copyMutableState} */ MerkleNodeState getMutableState(); /** - * Get the latest immutable state. This method is idempotent. + * Get the latest immutable state. Consecutive calls to this method may return different instances, + * if this method is not called on the one and the only thread that is calling {@link #copyMutableState} * @return the latest immutable state. */ MerkleNodeState getLatestImmutableState(); @@ -46,8 +48,8 @@ public interface StateLifecycleManager { /** * Creates a snapshot for the state that was previously set with {@link #setSnapshotSource(MerkleNodeStateAware)}. - * The state has to be hashed before calling this method. Once the snapshot is created, the manager releases the source - * state of the snapshot and clears the reference to it. + * The state has to be hashed before calling this method. Once the snapshot is created, the manager resets the snapshot source + * to null. Therefore, this method is not idempotent. * * @param targetPath The path to save the snapshot. */ @@ -62,10 +64,10 @@ public interface StateLifecycleManager { MerkleNodeState loadSnapshot(@NonNull Path targetPath); /** - * Creates a mutable copy of the state. The previous mutable state becomes immutable, + * Creates a mutable copy of the mutable state. The previous mutable state becomes immutable, * replacing the latest immutable state. * - * @return a mutable copy of the current mutable state which became the latest immutable state. + * @return a mutable copy of the previous mutable state */ MerkleNodeState copyMutableState(); } diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 9d31b99b0d14..62fbc0f3ebf5 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -15,6 +15,8 @@ import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -24,6 +26,8 @@ /** * This class is responsible for maintaining references to the mutable state and the latest immutable state. * It also updates these references upon state signing. + * + * @param A type of the snapshot source, which should implement {@link MerkleNodeStateAware}. */ public class StateLifecycleManagerImpl implements StateLifecycleManager { @@ -37,9 +41,19 @@ public class StateLifecycleManagerImpl implement */ private final MerkleRootSnapshotMetrics snapshotMetrics; + /** + * The object for time measurements + */ private final Time time; + /** + * The metrics registry + */ private final Metrics metrics; + + /** + * A factory object to create an instance of a class implementing {@link MerkleNodeState} from a {@link VirtualMap} + */ private final Function stateSupplier; /** @@ -80,7 +94,7 @@ public StateLifecycleManagerImpl( * * @param state the initial state */ - public void initState(@NonNull final MerkleNodeState state, boolean onStartup) { + public void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { requireNonNull(state); state.throwIfDestroyed("state must not be destroyed"); @@ -94,9 +108,9 @@ public void initState(@NonNull final MerkleNodeState state, boolean onStartup) { } @Override - public void setSnapshotSource(@NonNull T source) { + public void setSnapshotSource(@NonNull final T source) { requireNonNull(source); - MerkleNodeState state = source.getState(); + final MerkleNodeState state = source.getState(); state.throwIfDestroyed("state must not be destroyed"); state.throwIfMutable("state must be immutable"); boolean result = snapshotSource.compareAndSet(null, source); @@ -104,6 +118,7 @@ public void setSnapshotSource(@NonNull T source) { } @Override + @Nullable public T getSnapshotSource() { return snapshotSource.get(); } @@ -114,11 +129,13 @@ public MerkleNodeState getMutableState() { } @Override + @Nullable public MerkleNodeState getLatestImmutableState() { return latestImmutableStateRef.get(); } @Override + @NonNull public MerkleNodeState copyMutableState() { final MerkleNodeState state = stateRef.get(); copyAndUpdateStateRefs(state); @@ -129,7 +146,7 @@ public MerkleNodeState copyMutableState() { * Copies the provided to and updates both the latest immutable to and the mutable to reference. * @param stateToCopy the state to copy and update references for */ - private synchronized void copyAndUpdateStateRefs(MerkleNodeState stateToCopy) { + private synchronized void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) { final long copyStart = System.nanoTime(); final MerkleNodeState newMutableState = stateToCopy.copy(); // Increment the reference count because this reference becomes the new value @@ -143,7 +160,7 @@ private synchronized void copyAndUpdateStateRefs(MerkleNodeState stateToCopy) { } stateToCopy.getRoot().reserve(); latestImmutableStateRef.set(stateToCopy); - final var previousMutableState = stateRef.get(); + final MerkleNodeState previousMutableState = stateRef.get(); if (previousMutableState != null && !previousMutableState.isDestroyed()) { previousMutableState.release(); } @@ -158,9 +175,12 @@ private synchronized void copyAndUpdateStateRefs(MerkleNodeState stateToCopy) { */ @Override public void createSnapshot(final @NonNull Path targetPath) { + if( snapshotSource.get() == null) { + throw new IllegalStateException("Snapshot source is not set"); + } requireNonNull(time); requireNonNull(snapshotMetrics); - VirtualMapState state = (VirtualMapState) snapshotSource.get().getState(); + final VirtualMapState state = (VirtualMapState) snapshotSource.get().getState(); state.throwIfMutable(); state.throwIfDestroyed(); final long startTime = time.currentTimeMillis(); @@ -172,8 +192,9 @@ public void createSnapshot(final @NonNull Path targetPath) { /** * {@inheritDoc} */ + @NonNull @Override - public MerkleNodeState loadSnapshot(@NonNull Path targetPath) { + public MerkleNodeState loadSnapshot(@NonNull final Path targetPath) { final MerkleNode root; try { root = MerkleTreeSnapshotReader.readStateFileData(targetPath).stateRoot(); @@ -185,7 +206,7 @@ public MerkleNodeState loadSnapshot(@NonNull Path targetPath) { "Root should be a VirtualMap, but it is " + root.getClass().getSimpleName() + " instead"); } - final var mutableCopy = readVirtualMap.copy(); + final VirtualMap mutableCopy = readVirtualMap.copy(); mutableCopy.registerMetrics(metrics); readVirtualMap.release(); readVirtualMap = mutableCopy; From a632b74624e25424764cabcbbb9586a0604cbdcc Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 4 Nov 2025 21:07:32 -0500 Subject: [PATCH 12/23] Addressed review comments: - got rid of `MerkleNodeStateAware` - changed a signature of `createSnapshot` to take a state instance as an argument - added `getRound` method to `MerkleNodeState` Signed-off-by: Ivan Malygin --- .../app/state/merkle/SerializationTest.java | 15 +++--- .../node/app/fixtures/state/FakeState.java | 5 ++ .../BlockStreamRecoveryWorkflow.java | 10 ++-- .../otter/fixtures/app/OtterAppState.java | 2 +- .../swirlds/demo/stats/StatsDemoState.java | 2 +- .../ConsistencyTestingToolState.java | 2 +- .../swirlds/demo/iss/ISSTestingToolState.java | 2 +- .../migration/MigrationTestingToolState.java | 2 +- .../platform/PlatformTestingToolState.java | 2 +- .../com/swirlds/platform/SwirldsPlatform.java | 12 ++--- .../platform/builder/PlatformBuilder.java | 4 +- .../builder/PlatformBuildingBlocks.java | 3 +- .../cli/GenesisPlatformStateCommand.java | 13 +++-- .../DefaultTransactionHandler.java | 4 +- .../platform/gossip/SyncGossipModular.java | 4 +- .../protocol/ReconnectStateSyncProtocol.java | 4 +- .../reconnect/ReconnectController.java | 4 +- .../reconnect/ReconnectStatePeerProtocol.java | 4 +- .../recovery/EventRecoveryWorkflow.java | 17 ++++--- .../state/editor/StateEditorSave.java | 11 +++-- .../platform/state/signed/SignedState.java | 3 +- .../snapshot/DefaultStateSnapshotManager.java | 13 +++-- .../state/snapshot/SignedStateFileWriter.java | 47 +++++++++++-------- .../SignedStateFileReadWriteTest.java | 9 ++-- .../platform/StateFileManagerTests.java | 4 +- .../TransactionHandlerTester.java | 6 +-- .../reconnect/ReconnectControllerTest.java | 2 +- .../state/StateLifecycleManagerTests.java | 4 +- .../state/signed/StartupStateUtilsTests.java | 6 +-- .../com/swirlds/state/MerkleNodeState.java | 7 +++ .../swirlds/state/MerkleNodeStateAware.java | 16 ------- .../swirlds/state/StateLifecycleManager.java | 19 ++------ .../merkle/StateLifecycleManagerImpl.java | 41 +++------------- .../swirlds/state/merkle/VirtualMapState.java | 7 --- .../fixtures/merkle/TestVirtualMapState.java | 2 +- 35 files changed, 140 insertions(+), 168 deletions(-) delete mode 100644 platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index 4b4966c80b5e..5d40028214a4 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -162,7 +162,7 @@ private void forceFlush(ReadableKVState state) { @ValueSource(booleans = {true, false}) void simpleReadAndWrite(boolean forceFlush) throws IOException, ConstructableRegistryException { final Schema schemaV1 = createV1Schema(); - final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); // When we serialize it to bytes and deserialize it back into a tree @@ -201,8 +201,7 @@ void snapshot() throws IOException { // prepare the tree and create a snapshot stateLifecycleManager.getMutableState().release(); originalTree.computeHash(); - stateLifecycleManager.setSnapshotSource(() -> originalTree); - stateLifecycleManager.createSnapshot(tempDir); + stateLifecycleManager.createSnapshot(originalTree, tempDir); originalTree.release(); final MerkleNodeState state = @@ -221,7 +220,7 @@ void snapshot() throws IOException { @Test void dualReadAndWrite() throws IOException, ConstructableRegistryException { final Schema schemaV1 = createV1Schema(); - final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); + final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); MerkleNodeState copy = stateLifecycleManager.copyMutableState(); // make a copy to make VM flushable @@ -285,11 +284,11 @@ private void initServices(Schema schemaV1, MerkleNodeState load loadedTree.getRoot().migrate(MINIMUM_SUPPORTED_VERSION); } - private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { + private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { final SignedState randomState = new RandomSignedStateGenerator().setRound(1).build(); - final var originalTree = randomState.getState(); + final MerkleNodeState originalTree = randomState.getState(); // the state is not hashed yet final var originalTreeCopy = originalTree.copy(); originalTree.release(); @@ -306,8 +305,8 @@ private StateLifecycleManager createStateLifecycleManager(Schema schemaV1) { startupNetworks, TEST_PLATFORM_STATE_FACADE); - final StateLifecycleManager stateLifecycleManager = - new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); + final StateLifecycleManager stateLifecycleManager = + new StateLifecycleManagerImpl(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); stateLifecycleManager.initState(originalTreeCopy, true); return stateLifecycleManager; diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java index a8c81e8bae5c..48d91ee9ba41 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeState.java @@ -302,4 +302,9 @@ public long queueElementPath(final int stateId, @NonNull final Bytes expectedVal public void initializeState(@NonNull final StateMetadata md) { // do nothing } + + @Override + public long getRound() { + return 0; + } } diff --git a/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java b/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java index 5449b1a0ca21..b9e2cbd8b155 100644 --- a/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java +++ b/hedera-state-validator/src/main/java/com/hedera/statevalidation/blockstream/BlockStreamRecoveryWorkflow.java @@ -152,14 +152,18 @@ public void applyBlocks( false, DEFAULT_PLATFORM_STATE_FACADE); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( platformContext.getMetrics(), platformContext.getTime(), vm -> new HederaVirtualMapState(vm, platformContext.getMetrics(), platformContext.getTime())); - stateLifecycleManager.setSnapshotSource(signedState); try { SignedStateFileWriter.writeSignedStateFilesToDirectory( - platformContext, selfId, outputPath, DEFAULT_PLATFORM_STATE_FACADE, stateLifecycleManager); + platformContext, + selfId, + outputPath, + signedState, + DEFAULT_PLATFORM_STATE_FACADE, + stateLifecycleManager); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java index ef753bd91844..45481d0b1f40 100644 --- a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java +++ b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java @@ -86,7 +86,7 @@ protected OtterAppState copyingConstructor() { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } diff --git a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java index 7700db34ad50..db820fa0c581 100644 --- a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java +++ b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java @@ -66,7 +66,7 @@ private StatsDemoState(final StatsDemoState sourceState) { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java index b41c9e329adc..31d37678bef7 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java @@ -154,7 +154,7 @@ void initState(Path logFilePath) { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java index 732781ed0a88..0a41e74110c5 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java @@ -192,7 +192,7 @@ List getPlannedLogErrorList() { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } } diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index b705d02e2b52..0bd0353025de 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -36,7 +36,7 @@ protected MigrationTestingToolState copyingConstructor() { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } } diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java index 4728e1763cd0..9fe131b1116d 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java @@ -147,7 +147,7 @@ protected PlatformTestingToolState(final PlatformTestingToolState sourceState) { * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 5cf05fdfd992..f9f78d7f5636 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -57,6 +57,7 @@ import com.swirlds.platform.system.status.actions.StartedReplayingEventsAction; import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.platform.wiring.PlatformCoordinator; +import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; import com.swirlds.state.StateLifecycleManager; import edu.umd.cs.findbugs.annotations.NonNull; @@ -138,11 +139,6 @@ public class SwirldsPlatform implements Platform { */ private final SavedStateController savedStateController; - /** - * Manages the lifecycle of the state. - */ - private final StateLifecycleManager stateLifecycleManager; - /** * Encapsulated wiring for the platform. */ @@ -191,7 +187,6 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { final LatestCompleteStateNexus latestCompleteStateNexus = new DefaultLatestCompleteStateNexus(platformContext); savedStateController = new DefaultSavedStateController(platformContext); - stateLifecycleManager = blocks.stateLifecycleManager(); final SignedStateMetrics signedStateMetrics = new SignedStateMetrics(platformContext.getMetrics()); final StateSignatureCollector stateSignatureCollector = @@ -217,8 +212,9 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { initializeState(this, platformContext, initialState, blocks.consensusStateEventHandler(), platformStateFacade); // This object makes a copy of the state. After this point, initialState becomes immutable. - final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); - stateLifecycleManager.initState(initialState.getState(), true); + final StateLifecycleManager stateLifecycleManager = blocks.stateLifecycleManager(); + final MerkleNodeState state = initialState.getState(); + stateLifecycleManager.initState(state, true); platformStateFacade.setCreationSoftwareVersionTo(stateLifecycleManager.getMutableState(), blocks.appVersion()); final EventWindowManager eventWindowManager = new DefaultEventWindowManager(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index 6a5e1c569fa5..55db64dca99e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -37,7 +37,6 @@ import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.Platform; import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.platform.wiring.PlatformWiring; @@ -435,7 +434,8 @@ public PlatformComponentBuilder buildComponentBuilder() { final ApplicationCallbacks callbacks = new ApplicationCallbacks(preconsensusEventConsumer, snapshotOverrideConsumer, staleEventConsumer); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + @SuppressWarnings("unchecked") + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( platformContext.getMetrics(), platformContext.getTime(), createStateFromVirtualMap); if (model == null) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java index 5cf9520c7cee..27094e6f47cb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java @@ -18,7 +18,6 @@ import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.status.StatusActionSubmitter; import com.swirlds.platform.wiring.PlatformComponents; import com.swirlds.state.MerkleNodeState; @@ -111,7 +110,7 @@ public record PlatformBuildingBlocks( @NonNull Scratchpad issScratchpad, @NonNull NotificationEngine notificationEngine, @NonNull AtomicReference statusActionSubmitterReference, - @NonNull StateLifecycleManager stateLifecycleManager, + @NonNull StateLifecycleManager stateLifecycleManager, @NonNull AtomicReference> getLatestCompleteStateReference, boolean firstPlatform, @NonNull ConsensusStateEventHandler consensusStateEventHandler, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 192a2de7718e..6ba440b0d152 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -16,7 +16,6 @@ import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileReader; import com.swirlds.platform.util.BootstrapUtils; @@ -63,8 +62,8 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti BootstrapUtils.setupConstructableRegistry(); final PlatformContext platformContext = PlatformContext.create(configuration); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( - platformContext.getMetrics(), platformContext.getTime(), (virtualMap) -> { + final StateLifecycleManager stateLifecycleManager = + new StateLifecycleManagerImpl(platformContext.getMetrics(), platformContext.getTime(), (virtualMap) -> { // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19003 throw new UnsupportedOperationException(); }); @@ -99,9 +98,13 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti .digestTreeAsync(reservedSignedState.get().getState().getRoot()) .get(); System.out.printf("Writing modified state to %s %n", outputDir.toAbsolutePath()); - stateLifecycleManager.setSnapshotSource(reservedSignedState.get()); writeSignedStateFilesToDirectory( - platformContext, NO_NODE_ID, outputDir, stateFacade, stateLifecycleManager); + platformContext, + NO_NODE_ID, + outputDir, + reservedSignedState.get(), + stateFacade, + stateLifecycleManager); } return 0; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java index 39a8821936f6..279360daa267 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java @@ -62,7 +62,7 @@ public class DefaultTransactionHandler implements TransactionHandler { /** * The class responsible for all interactions with the swirld state */ - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final RoundHandlingMetrics handlerMetrics; @@ -137,7 +137,7 @@ public class DefaultTransactionHandler implements TransactionHandler { */ public DefaultTransactionHandler( @NonNull final PlatformContext platformContext, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final SemanticVersion softwareVersion, @NonNull final PlatformStateFacade platformStateFacade, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java index e097941e0fae..6c2a408f1d7f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/SyncGossipModular.java @@ -72,7 +72,7 @@ public class SyncGossipModular implements Gossip { private final AbstractSyncProtocol syncProtocol; private final FallenBehindMonitor fallenBehindMonitor; private final AbstractShadowgraphSynchronizer synchronizer; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; // this is not a nice dependency, should be removed as well as the sharedState @@ -103,7 +103,7 @@ public SyncGossipModular( @NonNull final Roster roster, @NonNull final NodeId selfId, @NonNull final SemanticVersion appVersion, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Supplier latestCompleteState, @NonNull final IntakeEventCounter intakeEventCounter, @NonNull final PlatformStateFacade platformStateFacade, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java index 2e3c5845ac90..1e26ab142232 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/protocol/ReconnectStateSyncProtocol.java @@ -39,7 +39,7 @@ public class ReconnectStateSyncProtocol implements Protocol { private final PlatformContext platformContext; private final AtomicReference platformStatus = new AtomicReference<>(PlatformStatus.STARTING_UP); private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; public ReconnectStateSyncProtocol( @@ -52,7 +52,7 @@ public ReconnectStateSyncProtocol( @NonNull final FallenBehindMonitor fallenBehindManager, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java index f68115e7dc1c..a72262f9e143 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectController.java @@ -72,7 +72,7 @@ public class ReconnectController implements Runnable { private final Platform platform; private final PlatformContext platformContext; private final PlatformCoordinator platformCoordinator; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final SavedStateController savedStateController; private final ConsensusStateEventHandler consensusStateEventHandler; private final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; @@ -90,7 +90,7 @@ public ReconnectController( @NonNull final Platform platform, @NonNull final PlatformContext platformContext, @NonNull final PlatformCoordinator platformCoordinator, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final SavedStateController savedStateController, @NonNull final ConsensusStateEventHandler consensusStateEventHandler, @NonNull final ReservedSignedStateResultPromise peerReservedSignedStateResultPromise, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java index 07784d68e60f..2e4e282db531 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectStatePeerProtocol.java @@ -82,7 +82,7 @@ public class ReconnectStatePeerProtocol implements PeerProtocol { private final Time time; private final PlatformContext platformContext; private final ReservedSignedStateResultPromise reservedSignedStateResultPromise; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final Function createStateFromVirtualMap; /** @@ -112,7 +112,7 @@ public ReconnectStatePeerProtocol( @NonNull final Time time, @NonNull final PlatformStateFacade platformStateFacade, @NonNull final ReservedSignedStateResultPromise reservedSignedStateResultPromise, - @NonNull final StateLifecycleManager stateLifecycleManager, + @NonNull final StateLifecycleManager stateLifecycleManager, @NonNull final Function createStateFromVirtualMap) { this.platformContext = Objects.requireNonNull(platformContext); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index 05527497d16b..877371f91acf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -50,6 +50,7 @@ import com.swirlds.state.State; import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.merkle.StateLifecycleManagerImpl; +import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -59,6 +60,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; +import java.util.function.Function; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hiero.base.CompareTo; @@ -157,12 +159,10 @@ public static void recoverState( .apply(v), platformStateFacade, platformContext); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( - platformContext.getMetrics(), - platformContext.getTime(), - hederaApp.stateRootFromVirtualMap(platformContext.getMetrics(), platformContext.getTime())); + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( + platformContext.getMetrics(), platformContext.getTime(), (Function) + hederaApp.stateRootFromVirtualMap(platformContext.getMetrics(), platformContext.getTime())); try (final ReservedSignedState initialState = deserializedSignedState.reservedSignedState()) { - stateLifecycleManager.setSnapshotSource(initialState.get()); HederaUtils.updateStateHash(hederaApp, deserializedSignedState); logger.info( @@ -199,7 +199,12 @@ public static void recoverState( recoveredState.state().get().getState().copy(); SignedStateFileWriter.writeSignedStateFilesToDirectory( - platformContext, selfId, resultingStateDirectory, platformStateFacade, stateLifecycleManager); + platformContext, + selfId, + resultingStateDirectory, + recoveredState.state().get(), + platformStateFacade, + stateLifecycleManager); final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); updateEmergencyRecoveryFile( stateConfig, resultingStateDirectory, initialState.get().getConsensusTimestamp()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java index efc3df669f01..8d1eeb7ca92b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java @@ -14,7 +14,6 @@ import com.swirlds.logging.legacy.LogMarker; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.merkle.StateLifecycleManagerImpl; import java.io.IOException; @@ -49,7 +48,7 @@ public void run() { final PlatformContext platformContext = PlatformContext.create(configuration); logger.info(LogMarker.CLI.getMarker(), "Hashing state"); - final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl<>( + final StateLifecycleManager stateLifecycleManager = new StateLifecycleManagerImpl( platformContext.getMetrics(), platformContext.getTime(), (virtualMap) -> { // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19003 throw new UnsupportedOperationException(); @@ -69,9 +68,13 @@ public void run() { } try (final ReservedSignedState signedState = getStateEditor().getSignedStateCopy()) { - stateLifecycleManager.setSnapshotSource(signedState.get()); writeSignedStateFilesToDirectory( - platformContext, NO_NODE_ID, directory, DEFAULT_PLATFORM_STATE_FACADE, stateLifecycleManager); + platformContext, + NO_NODE_ID, + directory, + signedState.get(), + DEFAULT_PLATFORM_STATE_FACADE, + stateLifecycleManager); } } catch (final IOException e) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index 40b7dd9fb871..0a8e3bca82e8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -23,7 +23,6 @@ import com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction; import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.state.MerkleNodeState; -import com.swirlds.state.MerkleNodeStateAware; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.security.cert.X509Certificate; @@ -63,7 +62,7 @@ * rejoining after a long absence. *

    */ -public class SignedState implements MerkleNodeStateAware { +public class SignedState { private static final Logger logger = LogManager.getLogger(SignedState.class); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java index aef40b3a0c77..89cf104bd71a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java @@ -84,7 +84,7 @@ public class DefaultStateSnapshotManager implements StateSnapshotManager { /** * Provides access to the state */ - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; /** * Creates a new instance. @@ -102,7 +102,7 @@ public DefaultStateSnapshotManager( @NonNull final NodeId selfId, @NonNull final String swirldName, @NonNull final PlatformStateFacade platformStateFacade, - @NonNull final StateLifecycleManager stateLifecycleManager) { + @NonNull final StateLifecycleManager stateLifecycleManager) { this.platformContext = Objects.requireNonNull(platformContext); this.time = platformContext.getTime(); @@ -180,9 +180,14 @@ private static StateToDiskReason getReason(@NonNull final SignedState state) { private boolean saveStateTask(@NonNull final SignedState state, @NonNull final Path directory) { try { - stateLifecycleManager.setSnapshotSource(state); SignedStateFileWriter.writeSignedStateToDisk( - platformContext, selfId, directory, getReason(state), platformStateFacade, stateLifecycleManager); + platformContext, + selfId, + directory, + getReason(state), + state, + platformStateFacade, + stateLifecycleManager); return true; } catch (final Throwable e) { logger.error( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java index bcfe8e8876fd..965be992161a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java @@ -138,20 +138,22 @@ public static void writeSignedStateFilesToDirectory( @Nullable final PlatformContext platformContext, @Nullable final NodeId selfId, @NonNull final Path directory, + @NonNull final SignedState signedState, @NonNull final PlatformStateFacade platformStateFacade, - @NonNull final StateLifecycleManager stateLifecycleManager) + @NonNull final StateLifecycleManager stateLifecycleManager) throws IOException { requireNonNull(platformContext); requireNonNull(directory); - requireNonNull(stateLifecycleManager.getSnapshotSource()); - - SignedState snapshotSource = stateLifecycleManager.getSnapshotSource(); - stateLifecycleManager.createSnapshot(directory); - writeSignatureSetFile(directory, snapshotSource); - writeHashInfoFile(platformContext, directory, snapshotSource.getState(), platformStateFacade); - writeMetadataFile(selfId, directory, snapshotSource, platformStateFacade); - writeEmergencyRecoveryFile(directory, snapshotSource); - final Roster currentRoster = snapshotSource.getRoster(); + requireNonNull(signedState); + requireNonNull(platformStateFacade); + requireNonNull(stateLifecycleManager); + + stateLifecycleManager.createSnapshot(signedState.getState(), directory); + writeSignatureSetFile(directory, signedState); + writeHashInfoFile(platformContext, directory, signedState.getState(), platformStateFacade); + writeMetadataFile(selfId, directory, signedState, platformStateFacade); + writeEmergencyRecoveryFile(directory, signedState); + final Roster currentRoster = signedState.getRoster(); writeRosterFile(directory, currentRoster); writeSettingsUsed(directory, platformContext.getConfiguration()); @@ -160,8 +162,8 @@ public static void writeSignedStateFilesToDirectory( platformContext, selfId, directory, - platformStateFacade.ancientThresholdOf(snapshotSource.getState()), - snapshotSource.getRound()); + platformStateFacade.ancientThresholdOf(signedState.getState()), + signedState.getRound()); } } @@ -196,33 +198,38 @@ public static void writeSignedStateToDisk( @Nullable final NodeId selfId, @NonNull final Path savedStateDirectory, @Nullable final StateToDiskReason stateToDiskReason, + @NonNull final SignedState signedState, @NonNull final PlatformStateFacade platformStateFacade, - @NonNull final StateLifecycleManager stateLifecycleManager) + @NonNull final StateLifecycleManager stateLifecycleManager) throws IOException { + requireNonNull(signedState); requireNonNull(platformContext); requireNonNull(savedStateDirectory); requireNonNull(stateLifecycleManager); - final SignedState snapshotSource = stateLifecycleManager.getSnapshotSource(); - requireNonNull(snapshotSource); try { logger.info( STATE_TO_DISK.getMarker(), "Started writing round {} state to disk. Reason: {}, directory: {}", - snapshotSource.getRound(), + signedState.getRound(), stateToDiskReason == null ? "UNKNOWN" : stateToDiskReason, savedStateDirectory); executeAndRename( savedStateDirectory, directory -> writeSignedStateFilesToDirectory( - platformContext, selfId, directory, platformStateFacade, stateLifecycleManager), + platformContext, + selfId, + directory, + signedState, + platformStateFacade, + stateLifecycleManager), platformContext.getConfiguration()); logger.info(STATE_TO_DISK.getMarker(), () -> new StateSavedToDiskPayload( - snapshotSource.getRound(), - snapshotSource.isFreezeState(), + signedState.getRound(), + signedState.isFreezeState(), stateToDiskReason == null ? "UNKNOWN" : stateToDiskReason.toString(), savedStateDirectory) .toString()); @@ -230,7 +237,7 @@ public static void writeSignedStateToDisk( logger.error( EXCEPTION.getMarker(), "Exception when writing the signed state for round {} to disk:", - snapshotSource.getRound(), + signedState.getRound(), e); throw e; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java index 8a63496d9e2d..45c9d600e410 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java @@ -62,7 +62,7 @@ class SignedStateFileReadWriteTest { private static SemanticVersion platformVersion; private static PlatformStateFacade stateFacade; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; @BeforeAll static void beforeAll() throws ConstructableRegistryException { @@ -82,7 +82,7 @@ static void beforeAll() throws ConstructableRegistryException { void beforeEach() throws IOException { testDirectory = LegacyTemporaryFileBuilder.buildTemporaryFile("SignedStateFileReadWriteTest", CONFIGURATION); stateLifecycleManager = - new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); + new StateLifecycleManagerImpl(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); LegacyTemporaryFileBuilder.overrideTemporaryFileLocation(testDirectory.resolve("tmp")); } @@ -134,8 +134,7 @@ void writeThenReadStateFileTest() throws IOException { MerkleNodeState state = signedState.getState(); state.copy().release(); hashState(signedState); - stateLifecycleManager.setSnapshotSource(signedState); - stateLifecycleManager.createSnapshot(testDirectory); + stateLifecycleManager.createSnapshot(signedState.getState(), testDirectory); writeSignatureSetFile(testDirectory, signedState); assertTrue(exists(stateFile), "signed state file should be present"); @@ -182,13 +181,13 @@ void writeSavedStateToDiskTest() throws IOException { stateLifecycleManager.getMutableState().release(); hashState(signedState); - stateLifecycleManager.setSnapshotSource(signedState); writeSignedStateToDisk( platformContext, NodeId.of(0), directory, StateToDiskReason.PERIODIC_SNAPSHOT, + signedState, stateFacade, stateLifecycleManager); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index 693e0963b6db..eb12933be39b 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -77,7 +77,7 @@ class StateFileManagerTests { private SignedStateFilePath signedStateFilePath; Path testDirectory; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; @BeforeAll static void beforeAll() throws ConstructableRegistryException { @@ -101,7 +101,7 @@ void beforeEach() throws IOException { signedStateFilePath = new SignedStateFilePath(context.getConfiguration().getConfigData(StateCommonConfig.class)); stateLifecycleManager = - new StateLifecycleManagerImpl<>(context.getMetrics(), context.getTime(), TestVirtualMapState::new); + new StateLifecycleManagerImpl(context.getMetrics(), context.getTime(), TestVirtualMapState::new); } @AfterEach diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java index 701fc3684259..34465087a29a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java @@ -33,7 +33,7 @@ */ public class TransactionHandlerTester { private final PlatformStateModifier platformState; - private final StateLifecycleManager stateLifecycleManager; + private final StateLifecycleManager stateLifecycleManager; private final DefaultTransactionHandler defaultTransactionHandler; private final List submittedActions = new ArrayList<>(); private final List handledRounds = new ArrayList<>(); @@ -66,7 +66,7 @@ public TransactionHandlerTester() { .when(consensusStateEventHandler) .onHandleConsensusRound(any(), same(consensusState), any()); final StatusActionSubmitter statusActionSubmitter = submittedActions::add; - stateLifecycleManager = new StateLifecycleManagerImpl<>( + stateLifecycleManager = new StateLifecycleManagerImpl( platformContext.getMetrics(), platformContext.getTime(), vm -> consensusState); stateLifecycleManager.initState(consensusState, true); defaultTransactionHandler = new DefaultTransactionHandler( @@ -110,7 +110,7 @@ public List getHandledRounds() { /** * @return the {@link StateLifecycleManager} used by this tester */ - public StateLifecycleManager getStateLifecycleManager() { + public StateLifecycleManager getStateLifecycleManager() { return stateLifecycleManager; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java index 7b7944e30684..ab7b976449b9 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectControllerTest.java @@ -84,7 +84,7 @@ class ReconnectControllerTest { private MerkleCryptography merkleCryptography; private Platform platform; private PlatformCoordinator platformCoordinator; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; private SavedStateController savedStateController; private ConsensusStateEventHandler consensusStateEventHandler; private ReservedSignedStateResultPromise peerReservedSignedStateResultPromise; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java index 4b3d348ec65f..17a3e44842b6 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java @@ -30,7 +30,7 @@ class StateLifecycleManagerTests { - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; private MerkleNodeState initialState; @BeforeEach @@ -43,7 +43,7 @@ void setup() { final PlatformContext platformContext = TestPlatformContextBuilder.create().build(); - stateLifecycleManager = new StateLifecycleManagerImpl<>( + stateLifecycleManager = new StateLifecycleManagerImpl( platformContext.getMetrics(), platformContext.getTime(), TestVirtualMapState::new); stateLifecycleManager.initState(initialState, true); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java index 7632767bb0c6..f6b7355512b5 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java @@ -74,7 +74,7 @@ public class StartupStateUtilsTests { private final String swirldName = "swirldName"; private SemanticVersion currentSoftwareVersion; private PlatformStateFacade platformStateFacade; - private StateLifecycleManager stateLifecycleManager; + private StateLifecycleManager stateLifecycleManager; @BeforeEach void beforeEach() throws IOException { @@ -131,7 +131,7 @@ private SignedState writeState( new RandomSignedStateGenerator(random).setRound(round).build(); stateLifecycleManager = - new StateLifecycleManagerImpl<>(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); + new StateLifecycleManagerImpl(new NoOpMetrics(), new FakeTime(), TestVirtualMapState::new); stateLifecycleManager.initState(signedState.getState(), true); stateLifecycleManager.getMutableState().release(); // FUTURE WORK: https://github.com/hiero-ledger/hiero-consensus-node/issues/19905 @@ -140,12 +140,12 @@ private SignedState writeState( final Path savedStateDirectory = signedStateFilePath.getSignedStateDirectory(mainClassName, selfId, swirldName, round); - stateLifecycleManager.setSnapshotSource(signedState); writeSignedStateToDisk( platformContext, selfId, savedStateDirectory, StateToDiskReason.PERIODIC_SNAPSHOT, + signedState, platformStateFacade, stateLifecycleManager); diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java index ac7097fe2b3a..68b3a9abb793 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java @@ -23,6 +23,13 @@ default MerkleNode getRoot() { return (MerkleNode) this; } + /** + * Retrieves the round number associated with this state. + * + * @return a round number of the state. + */ + long getRound(); + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java deleted file mode 100644 index 69806a468c37..000000000000 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeStateAware.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package com.swirlds.state; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Implementations of this interface can provide access to their {@link MerkleNodeState}. - */ -public interface MerkleNodeStateAware { - /** - * An instance of {@link MerkleNodeState} associated with this object. - * @return an instance of {@link MerkleNodeState} associated with this object. - */ - @NonNull - MerkleNodeState getState(); -} diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index c367cf802d3c..538a8a7d2ba6 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -14,9 +14,9 @@ * * * An implementation of this class must be thread-safe. - * @param A type of the snapshot source, which should implement {@link MerkleNodeStateAware}. + * */ -public interface StateLifecycleManager { +public interface StateLifecycleManager { /** * Set the initial State. This method should only be on a startup or after a reconnect. @@ -39,21 +39,12 @@ public interface StateLifecycleManager { MerkleNodeState getLatestImmutableState(); /** - * Sets the state to create a snapshot from. - * @param source the state to create a snapshot from. - */ - void setSnapshotSource(@NonNull T source); - - T getSnapshotSource(); - - /** - * Creates a snapshot for the state that was previously set with {@link #setSnapshotSource(MerkleNodeStateAware)}. - * The state has to be hashed before calling this method. Once the snapshot is created, the manager resets the snapshot source - * to null. Therefore, this method is not idempotent. + * Creates a snapshot for the state provided as a parameter. The state has to be hashed before calling this method. * + * @param merkleNodeState The state to save. * @param targetPath The path to save the snapshot. */ - void createSnapshot(@NonNull Path targetPath); + void createSnapshot(@NonNull MerkleNodeState merkleNodeState, @NonNull Path targetPath); /** * Loads a snapshot of a state. diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 62fbc0f3ebf5..48b1c2de4190 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -10,13 +10,11 @@ import com.swirlds.common.merkle.utility.MerkleTreeSnapshotWriter; import com.swirlds.metrics.api.Metrics; import com.swirlds.state.MerkleNodeState; -import com.swirlds.state.MerkleNodeStateAware; import com.swirlds.state.State; import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; - import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -27,9 +25,8 @@ * This class is responsible for maintaining references to the mutable state and the latest immutable state. * It also updates these references upon state signing. * - * @param A type of the snapshot source, which should implement {@link MerkleNodeStateAware}. */ -public class StateLifecycleManagerImpl implements StateLifecycleManager { +public class StateLifecycleManagerImpl implements StateLifecycleManager { /** * Metrics for the state object @@ -54,7 +51,7 @@ public class StateLifecycleManagerImpl implement /** * A factory object to create an instance of a class implementing {@link MerkleNodeState} from a {@link VirtualMap} */ - private final Function stateSupplier; + private final Function stateSupplier; /** * reference to the state that reflects all known consensus transactions @@ -67,10 +64,8 @@ public class StateLifecycleManagerImpl implement private final AtomicReference latestImmutableStateRef = new AtomicReference<>(); /** - * The most recent immutable state. No value until the first fast copy is created. + * The source of the snapshot. This is set when a snapshot is initiated and cleared when the snapshot is complete. */ - private final AtomicReference snapshotSource = new AtomicReference<>(); - /** * Constructor. * @@ -80,8 +75,9 @@ public class StateLifecycleManagerImpl implement public StateLifecycleManagerImpl( @NonNull final Metrics metrics, @NonNull final Time time, - @NonNull final Function stateSupplier) { + @NonNull final Function stateSupplier) { requireNonNull(metrics); + requireNonNull(time); this.stateSupplier = stateSupplier; this.metrics = metrics; this.stateMetrics = new StateMetrics(metrics); @@ -107,22 +103,6 @@ public void initState(@NonNull final MerkleNodeState state, final boolean onStar copyAndUpdateStateRefs(state); } - @Override - public void setSnapshotSource(@NonNull final T source) { - requireNonNull(source); - final MerkleNodeState state = source.getState(); - state.throwIfDestroyed("state must not be destroyed"); - state.throwIfMutable("state must be immutable"); - boolean result = snapshotSource.compareAndSet(null, source); - assert result : "Snapshot source was already set"; - } - - @Override - @Nullable - public T getSnapshotSource() { - return snapshotSource.get(); - } - @Override public MerkleNodeState getMutableState() { return stateRef.get(); @@ -174,19 +154,12 @@ private synchronized void copyAndUpdateStateRefs(final @NonNull MerkleNodeState * {@inheritDoc} */ @Override - public void createSnapshot(final @NonNull Path targetPath) { - if( snapshotSource.get() == null) { - throw new IllegalStateException("Snapshot source is not set"); - } - requireNonNull(time); - requireNonNull(snapshotMetrics); - final VirtualMapState state = (VirtualMapState) snapshotSource.get().getState(); + public void createSnapshot(final @NonNull MerkleNodeState state, final @NonNull Path targetPath) { state.throwIfMutable(); state.throwIfDestroyed(); final long startTime = time.currentTimeMillis(); - MerkleTreeSnapshotWriter.createSnapshot(state.virtualMap, targetPath, state.getRound()); + MerkleTreeSnapshotWriter.createSnapshot(state.getRoot(), targetPath, state.getRound()); snapshotMetrics.updateWriteStateToDiskTimeMetric(time.currentTimeMillis() - startTime); - snapshotSource.set(null); } /** diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java index 4e423cd76613..cb1902e24e20 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java @@ -304,13 +304,6 @@ public void removeServiceState(@NonNull final String serviceName, final int stat // Getters and setters - /** - * Retrieves the round number associated with this state. - * - * @return the round number as a long value - */ - protected abstract long getRound(); - public Map>> getServices() { return services; } diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java index cb658a311161..1f1366ac6763 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java @@ -45,7 +45,7 @@ public static TestVirtualMapState createInstanceWithVirtualMapLabel(@NonNull fin * {@inheritDoc} */ @Override - protected long getRound() { + public long getRound() { return 0; // genesis round } } From 7ef4a1e6895ed4a2ca855a682733d87232ab3ef9 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Wed, 5 Nov 2025 10:50:45 -0500 Subject: [PATCH 13/23] Addressed review comments: - updated javadoc - made `copyMutableState` and `initState` synchronized Signed-off-by: Ivan Malygin --- .../state/merkle/StateLifecycleManagerImpl.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 48b1c2de4190..3f72efd0c7b1 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -23,8 +23,7 @@ /** * This class is responsible for maintaining references to the mutable state and the latest immutable state. - * It also updates these references upon state signing. - * + * It also updates these references upon state signing. This implementation is thread-safe. */ public class StateLifecycleManagerImpl implements StateLifecycleManager { @@ -63,9 +62,6 @@ public class StateLifecycleManagerImpl implements StateLifecycleManager { */ private final AtomicReference latestImmutableStateRef = new AtomicReference<>(); - /** - * The source of the snapshot. This is set when a snapshot is initiated and cleared when the snapshot is complete. - */ /** * Constructor. * @@ -90,7 +86,7 @@ public StateLifecycleManagerImpl( * * @param state the initial state */ - public void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { + public synchronized void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { requireNonNull(state); state.throwIfDestroyed("state must not be destroyed"); @@ -116,7 +112,7 @@ public MerkleNodeState getLatestImmutableState() { @Override @NonNull - public MerkleNodeState copyMutableState() { + public synchronized MerkleNodeState copyMutableState() { final MerkleNodeState state = stateRef.get(); copyAndUpdateStateRefs(state); return stateRef.get(); @@ -126,7 +122,7 @@ public MerkleNodeState copyMutableState() { * Copies the provided to and updates both the latest immutable to and the mutable to reference. * @param stateToCopy the state to copy and update references for */ - private synchronized void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) { + private void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) { final long copyStart = System.nanoTime(); final MerkleNodeState newMutableState = stateToCopy.copy(); // Increment the reference count because this reference becomes the new value From f0df304b76f0257c639acb4ca21b1c3d47d75039 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Wed, 5 Nov 2025 12:32:47 -0500 Subject: [PATCH 14/23] Added unit tests Signed-off-by: Ivan Malygin --- .../state/StateLifecycleManagerTests.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java index 17a3e44842b6..1c8a4075d203 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java @@ -3,11 +3,15 @@ import static org.hiero.base.utility.test.fixtures.RandomUtils.nextInt; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.node.state.roster.Roster; +import com.swirlds.base.state.MutabilityException; import com.swirlds.common.Reservable; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.test.fixtures.Randotron; @@ -23,7 +27,13 @@ import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.hiero.base.constructable.ConstructableRegistry; +import org.hiero.base.constructable.ConstructableRegistryException; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -33,6 +43,16 @@ class StateLifecycleManagerTests { private StateLifecycleManager stateLifecycleManager; private MerkleNodeState initialState; + @BeforeAll + static void beforeAll() throws ConstructableRegistryException { + final var registry = ConstructableRegistry.getInstance(); + registry.registerConstructables("org.hiero"); + registry.registerConstructables("com.swirlds.platform"); + registry.registerConstructables("com.swirlds.state"); + registry.registerConstructables("com.swirlds.virtualmap"); + registry.registerConstructables("com.swirlds.merkledb"); + } + @BeforeEach void setup() { final SwirldsPlatform platform = mock(SwirldsPlatform.class); @@ -53,6 +73,10 @@ void tearDown() { if (!initialState.isDestroyed()) { initialState.release(); } + final MerkleNodeState latestImmutable = stateLifecycleManager.getLatestImmutableState(); + if (latestImmutable != null && latestImmutable != initialState && !latestImmutable.isDestroyed()) { + latestImmutable.release(); + } if (!stateLifecycleManager.getMutableState().isDestroyed()) { stateLifecycleManager.getMutableState().release(); } @@ -115,6 +139,40 @@ void initStateRefCount() { consensusState2.release(); } + @Test + @DisplayName("copyMutableState() updates references and reservation counts") + void copyMutableStateReferenceCounts() { + final MerkleNodeState beforeMutable = stateLifecycleManager.getMutableState(); + final MerkleNodeState beforeImmutable = stateLifecycleManager.getLatestImmutableState(); + + final MerkleNodeState afterMutable = stateLifecycleManager.copyMutableState(); + final MerkleNodeState newLatestImmutable = stateLifecycleManager.getLatestImmutableState(); + + assertSame(beforeMutable, newLatestImmutable, "Previous mutable should become latest immutable"); + assertNotSame(beforeMutable, afterMutable, "A new mutable state instance should be created"); + + assertEquals(1, afterMutable.getRoot().getReservationCount(), "Mutable state should have one reference"); + assertEquals(1, newLatestImmutable.getRoot().getReservationCount(), "Latest immutable should have one ref"); + assertEquals(-1, beforeImmutable.getRoot().getReservationCount(), "Old immutable should be released"); + } + + @Test + @DisplayName("initState() rejects second startup initialization") + void initStateRejectsSecondStartup() { + final PlatformStateFacade psf = new PlatformStateFacade(); + final MerkleNodeState another = newState(psf); + assertThrows(IllegalStateException.class, () -> stateLifecycleManager.initState(another, true)); + another.release(); + } + + @Test + @DisplayName("initState() rejects immutable input state") + void initStateRejectsImmutableInput() { + final MerkleNodeState immutable = stateLifecycleManager.getLatestImmutableState(); + assertThrows(MutabilityException.class, () -> stateLifecycleManager.initState(immutable, false)); + } + + private static MerkleNodeState newState(PlatformStateFacade platformStateFacade) { final String virtualMapLabel = StateLifecycleManagerTests.class.getSimpleName() + "-" + java.util.UUID.randomUUID(); From cc47000e02c6443d62d97f5bcfdc1795984e83de Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Wed, 5 Nov 2025 13:13:16 -0500 Subject: [PATCH 15/23] Spotless Signed-off-by: Ivan Malygin --- .../swirlds/platform/state/StateLifecycleManagerTests.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java index 1c8a4075d203..52e61ff0e168 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateLifecycleManagerTests.java @@ -27,9 +27,6 @@ import com.swirlds.state.StateLifecycleManager; import com.swirlds.state.merkle.StateLifecycleManagerImpl; import com.swirlds.state.test.fixtures.merkle.TestVirtualMapState; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import org.hiero.base.constructable.ConstructableRegistry; import org.hiero.base.constructable.ConstructableRegistryException; import org.junit.jupiter.api.AfterEach; @@ -172,7 +169,6 @@ void initStateRejectsImmutableInput() { assertThrows(MutabilityException.class, () -> stateLifecycleManager.initState(immutable, false)); } - private static MerkleNodeState newState(PlatformStateFacade platformStateFacade) { final String virtualMapLabel = StateLifecycleManagerTests.class.getSimpleName() + "-" + java.util.UUID.randomUUID(); From 627822d3082c174c6042b26071e42b5777a339e2 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 6 Nov 2025 11:04:41 -0500 Subject: [PATCH 16/23] Addressed review comments: - got rid of `getRound` method - improved javadoc - added missing nullity annotations Signed-off-by: Ivan Malygin --- .../node/app/HederaVirtualMapState.java | 17 ++++---- .../node/app/fixtures/state/FakeState.java | 5 --- .../otter/fixtures/app/OtterAppState.java | 13 +----- .../ConsistencyTestingToolState.java | 13 +----- .../swirlds/demo/iss/ISSTestingToolState.java | 13 +----- .../migration/MigrationTestingToolState.java | 14 +------ .../utility/MerkleTreeSnapshotWriter.java | 12 +++--- .../DefaultSavedStateController.java | 2 +- .../state/StateLifecycleManagerTests.java | 20 ++++++++++ .../com/swirlds/state/MerkleNodeState.java | 7 ---- .../swirlds/state/StateLifecycleManager.java | 7 +++- .../merkle/StateLifecycleManagerImpl.java | 40 ++++++++++++++----- .../swirlds/state/merkle/VirtualMapState.java | 14 +++---- .../fixtures/merkle/TestVirtualMapState.java | 13 +----- 14 files changed, 84 insertions(+), 106 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java index f00f53f28b08..4db1bbdcf921 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaVirtualMapState.java @@ -43,7 +43,7 @@ public class HederaVirtualMapState extends VirtualMapState md) { // do nothing } - - @Override - public long getRound() { - return 0; - } } diff --git a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java index 45481d0b1f40..08c6a4c19a67 100644 --- a/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java +++ b/platform-sdk/consensus-otter-tests/src/testFixtures/java/org/hiero/otter/fixtures/app/OtterAppState.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package org.hiero.otter.fixtures.app; -import static com.swirlds.platform.state.service.PlatformStateFacade.DEFAULT_PLATFORM_STATE_FACADE; import static org.hiero.otter.fixtures.app.state.OtterStateInitializer.initOtterAppState; import com.hedera.hapi.node.base.SemanticVersion; @@ -23,12 +22,12 @@ public class OtterAppState extends VirtualMapState implements Mer public OtterAppState( @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Time time) { - super(configuration, metrics, time); + super(configuration, metrics); } public OtterAppState( @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - super(virtualMap, metrics, time); + super(virtualMap, metrics); } /** @@ -82,14 +81,6 @@ protected OtterAppState copyingConstructor() { return new OtterAppState(this); } - /** - * {@inheritDoc} - */ - @Override - public long getRound() { - return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); - } - /** * Commit the state of all services. */ diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java index 31d37678bef7..6c33e67fc809 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java @@ -6,7 +6,6 @@ import static com.swirlds.demo.consistency.V0680ConsistencyTestingToolSchema.STATE_LONG_STATE_ID; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STARTUP; -import static com.swirlds.platform.state.service.PlatformStateFacade.DEFAULT_PLATFORM_STATE_FACADE; import static org.hiero.base.utility.ByteUtils.byteArrayToLong; import static org.hiero.base.utility.NonCryptographicHashing.hash64; @@ -84,7 +83,7 @@ public class ConsistencyTestingToolState extends VirtualMapState im public ISSTestingToolState( @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Time time) { - super(configuration, metrics, time); + super(configuration, metrics); } public ISSTestingToolState( @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { - super(virtualMap, metrics, time); + super(virtualMap, metrics); } /** @@ -187,12 +186,4 @@ List getPlannedIssList() { List getPlannedLogErrorList() { return plannedLogErrorList; } - - /** - * {@inheritDoc} - */ - @Override - public long getRound() { - return DEFAULT_PLATFORM_STATE_FACADE.roundOf(this); - } } diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index 0bd0353025de..823d0178585d 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -1,8 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.demo.migration; -import static com.swirlds.platform.state.service.PlatformStateFacade.DEFAULT_PLATFORM_STATE_FACADE; - import com.swirlds.base.time.Time; import com.swirlds.config.api.Configuration; import com.swirlds.metrics.api.Metrics; @@ -15,12 +13,12 @@ public class MigrationTestingToolState extends VirtualMapState stateLifecycleManager.initState(immutable, false)); } + @Test + @DisplayName("getMutableState() throws if not initialized") + void getMutableStateThrowsIfNotInitialized() { + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); + final StateLifecycleManager uninitialized = new StateLifecycleManagerImpl( + platformContext.getMetrics(), platformContext.getTime(), TestVirtualMapState::new); + assertThrows(IllegalStateException.class, uninitialized::getMutableState); + } + + @Test + @DisplayName("getLatestImmutableState() throws if not initialized") + void getLatestImmutableStateThrowsIfNotInitialized() { + final PlatformContext platformContext = + TestPlatformContextBuilder.create().build(); + final StateLifecycleManager uninitialized = new StateLifecycleManagerImpl( + platformContext.getMetrics(), platformContext.getTime(), TestVirtualMapState::new); + assertThrows(IllegalStateException.class, uninitialized::getLatestImmutableState); + } + private static MerkleNodeState newState(PlatformStateFacade platformStateFacade) { final String virtualMapLabel = StateLifecycleManagerTests.class.getSimpleName() + "-" + java.util.UUID.randomUUID(); diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java index 68b3a9abb793..ac7097fe2b3a 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/MerkleNodeState.java @@ -23,13 +23,6 @@ default MerkleNode getRoot() { return (MerkleNode) this; } - /** - * Retrieves the round number associated with this state. - * - * @return a round number of the state. - */ - long getRound(); - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index 538a8a7d2ba6..f3483bfe14b7 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -27,13 +27,18 @@ public interface StateLifecycleManager { /** * Get the mutable state. Consecutive calls to this method may return different instances, - * if this method is not called on the one and the only thread that is calling {@link #copyMutableState} + * if this method is not called on the one and the only thread that is calling {@link #copyMutableState}. + * If a parallel thread calls {@link #copyMutableState}, the returned object will become immutable and + * on the subsequent call of {@link #copyMutableState} it will be destroyed. + * + * @return the mutable state. */ MerkleNodeState getMutableState(); /** * Get the latest immutable state. Consecutive calls to this method may return different instances, * if this method is not called on the one and the only thread that is calling {@link #copyMutableState} + * If a parallel thread calls {@link #copyMutableState}, the returned object will become destroyed. * @return the latest immutable state. */ MerkleNodeState getLatestImmutableState(); diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 3f72efd0c7b1..e0380fde3ac1 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -14,7 +14,6 @@ import com.swirlds.state.StateLifecycleManager; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -23,7 +22,7 @@ /** * This class is responsible for maintaining references to the mutable state and the latest immutable state. - * It also updates these references upon state signing. This implementation is thread-safe. + * It also updates these references upon request. This implementation is thread-safe. */ public class StateLifecycleManagerImpl implements StateLifecycleManager { @@ -67,6 +66,7 @@ public class StateLifecycleManagerImpl implements StateLifecycleManager { * * @param metrics the metrics object to gather state metrics * @param time the time object + * @param stateSupplier a factory object to create an instance of a class implementing {@link MerkleNodeState} from a {@link VirtualMap} */ public StateLifecycleManagerImpl( @NonNull final Metrics metrics, @@ -82,9 +82,7 @@ public StateLifecycleManagerImpl( } /** - * Set the initial State. This method should only be on a startup of after a reconnect. - * - * @param state the initial state + * {@inheritDoc} */ public synchronized void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { requireNonNull(state); @@ -99,17 +97,35 @@ public synchronized void initState(@NonNull final MerkleNodeState state, final b copyAndUpdateStateRefs(state); } + /** + * {@inheritDoc} + */ + @NonNull @Override public MerkleNodeState getMutableState() { - return stateRef.get(); + final MerkleNodeState mutableState = stateRef.get(); + if (mutableState == null) { + throw new IllegalStateException("StateLifecycleManager has not been initialized."); + } + return mutableState; } + /** + * {@inheritDoc} + */ @Override - @Nullable + @NonNull public MerkleNodeState getLatestImmutableState() { - return latestImmutableStateRef.get(); + final MerkleNodeState latestImmutableState = latestImmutableStateRef.get(); + if (latestImmutableState == null) { + throw new IllegalStateException("StateLifecycleManager has not been initialized."); + } + return latestImmutableState; } + /** + * {@inheritDoc} + */ @Override @NonNull public synchronized MerkleNodeState copyMutableState() { @@ -131,13 +147,15 @@ private void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) stateMetrics.stateCopyMicros((copyEnd - copyStart) * NANOSECONDS_TO_MICROSECONDS); // releasing previous immutable previousMutableState final State previousImmutableState = latestImmutableStateRef.get(); - if (previousImmutableState != null && !previousImmutableState.isDestroyed()) { + if (previousImmutableState != null) { + previousImmutableState.throwIfDestroyed(); previousImmutableState.release(); } stateToCopy.getRoot().reserve(); latestImmutableStateRef.set(stateToCopy); final MerkleNodeState previousMutableState = stateRef.get(); - if (previousMutableState != null && !previousMutableState.isDestroyed()) { + if (previousMutableState != null) { + previousMutableState.throwIfDestroyed(); previousMutableState.release(); } // Do not increment the reference count because the stateToCopy provided already has a reference count of at @@ -154,7 +172,7 @@ public void createSnapshot(final @NonNull MerkleNodeState state, final @NonNull state.throwIfMutable(); state.throwIfDestroyed(); final long startTime = time.currentTimeMillis(); - MerkleTreeSnapshotWriter.createSnapshot(state.getRoot(), targetPath, state.getRound()); + MerkleTreeSnapshotWriter.createSnapshot(state.getRoot(), targetPath, state.toString()); snapshotMetrics.updateWriteStateToDiskTimeMetric(time.currentTimeMillis() - startTime); } diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java index cb1902e24e20..712b540a278a 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/VirtualMapState.java @@ -16,7 +16,6 @@ import com.hedera.pbj.runtime.Codec; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.base.time.Time; import com.swirlds.common.Reservable; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.config.api.Configuration; @@ -100,6 +99,9 @@ public abstract class VirtualMapState> implements M private final Metrics metrics; + /** + * The state storage + */ protected VirtualMap virtualMap; /** @@ -113,10 +115,8 @@ public abstract class VirtualMapState> implements M * * @param configuration the platform configuration instance to use when creating the new instance of state * @param metrics the platform metric instance to use when creating the new instance of state - * @param time the time instance to use when creating the new instance of state */ - public VirtualMapState( - @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Time time) { + public VirtualMapState(@NonNull final Configuration configuration, @NonNull final Metrics metrics) { requireNonNull(configuration); this.metrics = requireNonNull(metrics); final MerkleDbDataSourceBuilder dsBuilder; @@ -133,10 +133,8 @@ public VirtualMapState( * * @param virtualMap the virtual map with pre-registered metrics * @param metrics the platform metric instance to use when creating the new instance of state - * @param time the time instance to use when creating the new instance of state */ - public VirtualMapState( - @NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics, @NonNull final Time time) { + public VirtualMapState(@NonNull final VirtualMap virtualMap, @NonNull final Metrics metrics) { this.virtualMap = requireNonNull(virtualMap); this.metrics = requireNonNull(metrics); } @@ -165,8 +163,6 @@ protected VirtualMapState(@NonNull final VirtualMapState from) { */ protected abstract T copyingConstructor(); - // State interface implementation - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java index 1f1366ac6763..5c1bf6fcaecb 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/com/swirlds/state/test/fixtures/merkle/TestVirtualMapState.java @@ -3,7 +3,6 @@ import static com.swirlds.state.test.fixtures.merkle.VirtualMapUtils.CONFIGURATION; -import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.state.MerkleNodeState; import com.swirlds.state.State; @@ -17,11 +16,11 @@ public class TestVirtualMapState extends VirtualMapState implements MerkleNodeState { public TestVirtualMapState() { - super(CONFIGURATION, new NoOpMetrics(), new FakeTime()); + super(CONFIGURATION, new NoOpMetrics()); } public TestVirtualMapState(@NonNull final VirtualMap virtualMap) { - super(virtualMap, new NoOpMetrics(), new FakeTime()); + super(virtualMap, new NoOpMetrics()); } protected TestVirtualMapState(@NonNull final TestVirtualMapState from) { @@ -40,12 +39,4 @@ public static TestVirtualMapState createInstanceWithVirtualMapLabel(@NonNull fin final var virtualMap = VirtualMapUtils.createVirtualMap(CONFIGURATION, virtualMapLabel); return new TestVirtualMapState(virtualMap); } - - /** - * {@inheritDoc} - */ - @Override - public long getRound() { - return 0; // genesis round - } } From 8cb6973cadcd2dd2984b1f5a6772fbe1055d745f Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 6 Nov 2025 11:19:49 -0500 Subject: [PATCH 17/23] Fixed dependencies Signed-off-by: Ivan Malygin --- .../swirlds-state-impl/src/testFixtures/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform-sdk/swirlds-state-impl/src/testFixtures/java/module-info.java b/platform-sdk/swirlds-state-impl/src/testFixtures/java/module-info.java index 78cd8e903968..9b0baffa0eec 100644 --- a/platform-sdk/swirlds-state-impl/src/testFixtures/java/module-info.java +++ b/platform-sdk/swirlds-state-impl/src/testFixtures/java/module-info.java @@ -12,7 +12,6 @@ requires transitive com.swirlds.virtualmap; requires transitive org.hiero.base.utility; requires transitive org.junit.jupiter.params; - requires com.swirlds.base.test.fixtures; requires com.swirlds.common.test.fixtures; requires com.swirlds.config.extensions; requires com.swirlds.fcqueue; From c2d10cc06768b8fe5c03e2dc0cd2e1da09ad6875 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 6 Nov 2025 13:23:56 -0500 Subject: [PATCH 18/23] Unit test fix Signed-off-by: Ivan Malygin --- .../hedera/node/app/state/merkle/SerializationTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index 5d40028214a4..004eb0a92f58 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -141,7 +141,9 @@ private void forceFlush(ReadableKVState state) { if (vm.size() > 1) { vm.enableFlush(); - vm.release(); + if (vm.getReservationCount() > 0) { + vm.release(); + } vm.waitUntilFlushed(); } } catch (IllegalAccessException | NoSuchFieldException | InterruptedException e) { @@ -218,17 +220,17 @@ void snapshot() throws IOException { * After it gets saved to disk again, and then loaded back in, it results in ClassCastException due to incorrect classId. */ @Test - void dualReadAndWrite() throws IOException, ConstructableRegistryException { + void dualReadAndWrite() throws IOException { final Schema schemaV1 = createV1Schema(); final StateLifecycleManager stateLifecycleManager = createStateLifecycleManager(schemaV1); final MerkleNodeState originalTree = stateLifecycleManager.getMutableState(); MerkleNodeState copy = stateLifecycleManager.copyMutableState(); // make a copy to make VM flushable - forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(FRUIT_STATE_ID)); stateLifecycleManager .copyMutableState() .release(); // make a fast copy because we can only write to disk an immutable copy + forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(FRUIT_STATE_ID)); copy.getRoot().getHash(); final byte[] serializedBytes = writeTree(copy.getRoot(), dir); From 5ba04549c3310e0b0d8f78c335d23d1f3b6363d2 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 6 Nov 2025 14:42:05 -0500 Subject: [PATCH 19/23] Fixed unit tests. Addressed review comments: - improved javadocs - removed `synchronized` modifier from `initState` and `copyMutableState` Signed-off-by: Ivan Malygin --- .../swirlds/platform/StateFileManagerTests.java | 15 ++++++++++++--- .../swirlds/state/StateLifecycleManager.java | 2 -- .../state/merkle/StateLifecycleManagerImpl.java | 17 ++++++++++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index eb12933be39b..a83d73eac7a2 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -271,7 +271,7 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { .build(); final ReservedSignedState reservedSignedState = signedState.reserve("initialTestReservation"); - initLifecycleManagerAndMakeStateImmutable(reservedSignedState.get()); + initLifecycleManagerAndMakeStateImmutable(reservedSignedState.get(), round != firstRound); controller.markSavedState(new StateWithHashComplexity(reservedSignedState, 1)); hashState(signedState); @@ -382,7 +382,7 @@ void stateDeletionTest() throws IOException { .resolve("node" + SELF_ID + "_round" + fatalRound); final SignedState fatalState = new RandomSignedStateGenerator(random).setRound(fatalRound).build(); - initLifecycleManagerAndMakeStateImmutable(fatalState); + initLifecycleManagerAndMakeStateImmutable(fatalState, true); hashState(fatalState); fatalState.markAsStateToSave(FATAL_ERROR); manager.dumpStateTask(StateDumpRequest.create(fatalState.reserve("test"))); @@ -395,7 +395,7 @@ void stateDeletionTest() throws IOException { new RandomSignedStateGenerator(random).setRound(round).build(); issState.markAsStateToSave(PERIODIC_SNAPSHOT); states.add(signedState); - initLifecycleManagerAndMakeStateImmutable(signedState); + initLifecycleManagerAndMakeStateImmutable(signedState, true); hashState(signedState); manager.saveStateTask(signedState.reserve("test")); @@ -430,6 +430,15 @@ static void hashState(SignedState signedState) { } void initLifecycleManagerAndMakeStateImmutable(SignedState state) { + initLifecycleManagerAndMakeStateImmutable(state, false); + } + + void initLifecycleManagerAndMakeStateImmutable(SignedState state, boolean createNewStateLifecycleManager) { + if (createNewStateLifecycleManager) { + stateLifecycleManager = + new StateLifecycleManagerImpl(context.getMetrics(), context.getTime(), TestVirtualMapState::new); + } + stateLifecycleManager.initState(state.getState(), false); stateLifecycleManager.getMutableState().release(); } diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index f3483bfe14b7..1057beb779ce 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -13,8 +13,6 @@ *
  • Creating a mutable copy of the state, while making the current mutable state immutable.
  • * * - * An implementation of this class must be thread-safe. - * */ public interface StateLifecycleManager { diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index e0380fde3ac1..3555d6af7dac 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -22,7 +22,17 @@ /** * This class is responsible for maintaining references to the mutable state and the latest immutable state. - * It also updates these references upon request. This implementation is thread-safe. + * It also updates these references upon request. + * This implementation is NOT thread-safe. However, it provides the following guarantees: + *
      + *
    • After {@link #initState(MerkleNodeState, boolean)}, calls to {@link #getMutableState()} and {@link #getLatestImmutableState()} will always return + * non-null values.
    • + *
    • After {@link #copyMutableState()}, the updated mutable state will be visible and available to all threads via {@link #getMutableState()}, and + * the updated latest immutable state will be visible and available to all threads via {@link #getLatestImmutableState()}.
    • + *
    + * + * Important: {@link #initState(MerkleNodeState, boolean)} and {@link #copyMutableState()} are NOT supposed to be called from multiple threads. + * They only provide the happens-before guarantees that are described above. */ public class StateLifecycleManagerImpl implements StateLifecycleManager { @@ -84,7 +94,7 @@ public StateLifecycleManagerImpl( /** * {@inheritDoc} */ - public synchronized void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { + public void initState(@NonNull final MerkleNodeState state, final boolean onStartup) { requireNonNull(state); state.throwIfDestroyed("state must not be destroyed"); @@ -128,7 +138,7 @@ public MerkleNodeState getLatestImmutableState() { */ @Override @NonNull - public synchronized MerkleNodeState copyMutableState() { + public MerkleNodeState copyMutableState() { final MerkleNodeState state = stateRef.get(); copyAndUpdateStateRefs(state); return stateRef.get(); @@ -157,6 +167,7 @@ private void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) if (previousMutableState != null) { previousMutableState.throwIfDestroyed(); previousMutableState.release(); + previousMutableState.release(); } // Do not increment the reference count because the stateToCopy provided already has a reference count of at // least From 7e0628ffbe06ec53eb1a8fa7e9f656556064b2f0 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Thu, 6 Nov 2025 15:32:18 -0500 Subject: [PATCH 20/23] Removed accidentally added `previousMutableState.release()` Signed-off-by: Ivan Malygin --- .../java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index 3555d6af7dac..c0a16b83fc81 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -167,7 +167,6 @@ private void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) if (previousMutableState != null) { previousMutableState.throwIfDestroyed(); previousMutableState.release(); - previousMutableState.release(); } // Do not increment the reference count because the stateToCopy provided already has a reference count of at // least From 33abfaf6893c65c1e4b8f21c936b11a1ccbccfaf Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Fri, 7 Nov 2025 10:56:12 -0500 Subject: [PATCH 21/23] Addressed review comment: - improved javadoc Signed-off-by: Ivan Malygin --- .../com/swirlds/state/StateLifecycleManager.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index 1057beb779ce..9d8e5e7c7bc2 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -27,16 +27,22 @@ public interface StateLifecycleManager { * Get the mutable state. Consecutive calls to this method may return different instances, * if this method is not called on the one and the only thread that is calling {@link #copyMutableState}. * If a parallel thread calls {@link #copyMutableState}, the returned object will become immutable and - * on the subsequent call of {@link #copyMutableState} it will be destroyed. + * on the subsequent call of {@link #copyMutableState} it will be destroyed and, therefore, not usable in some contexts. * * @return the mutable state. */ MerkleNodeState getMutableState(); /** - * Get the latest immutable state. Consecutive calls to this method may return different instances, - * if this method is not called on the one and the only thread that is calling {@link #copyMutableState} - * If a parallel thread calls {@link #copyMutableState}, the returned object will become destroyed. + * Get the latest immutable state. Consecutive calls to this method may return different instances + * if this method is not called on the one and only thread that is calling {@link #copyMutableState}. + * If a parallel thread calls {@link #copyMutableState}, the returned object will become destroyed and, therefore, not usable in some contexts. + *
    + * If a durable long-term reference to the immutable state returned by this method is required, it is the + * responsibility of the caller to ensure a reference is maintained to prevent its garbage collection. Also, + * it is the responsibility of the caller to ensure that the object is not used in contexts in which it may become unusable + * (e.g., hashing of the destroyed state is not possible). + * * @return the latest immutable state. */ MerkleNodeState getLatestImmutableState(); From b156da99325dcf83cb176db6e1e522e5dec2fbc0 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Fri, 7 Nov 2025 14:13:02 -0500 Subject: [PATCH 22/23] Addressed review comment: - improved javadoc - improved handling of destroyed states Signed-off-by: Ivan Malygin --- .../swirlds/state/StateLifecycleManager.java | 6 ++++-- .../merkle/StateLifecycleManagerImpl.java | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java index 9d8e5e7c7bc2..6d4d6a8ad495 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/StateLifecycleManager.java @@ -27,7 +27,8 @@ public interface StateLifecycleManager { * Get the mutable state. Consecutive calls to this method may return different instances, * if this method is not called on the one and the only thread that is calling {@link #copyMutableState}. * If a parallel thread calls {@link #copyMutableState}, the returned object will become immutable and - * on the subsequent call of {@link #copyMutableState} it will be destroyed and, therefore, not usable in some contexts. + * on the subsequent call of {@link #copyMutableState} it will be destroyed (unless it was explicitly reserved outside of this class) + * and, therefore, not usable in some contexts. * * @return the mutable state. */ @@ -36,7 +37,8 @@ public interface StateLifecycleManager { /** * Get the latest immutable state. Consecutive calls to this method may return different instances * if this method is not called on the one and only thread that is calling {@link #copyMutableState}. - * If a parallel thread calls {@link #copyMutableState}, the returned object will become destroyed and, therefore, not usable in some contexts. + * If a parallel thread calls {@link #copyMutableState}, the returned object will become destroyed (unless it was explicitly reserved outside of this class) + * and, therefore, not usable in some contexts. *
    * If a durable long-term reference to the immutable state returned by this method is required, it is the * responsibility of the caller to ensure a reference is maintained to prevent its garbage collection. Also, diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java index c0a16b83fc81..bf650b4a8124 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/StateLifecycleManagerImpl.java @@ -2,6 +2,7 @@ package com.swirlds.state.merkle; import static com.swirlds.base.units.UnitConstants.NANOSECONDS_TO_MICROSECONDS; +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static java.util.Objects.requireNonNull; import com.swirlds.base.time.Time; @@ -19,6 +20,8 @@ import java.nio.file.Path; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * This class is responsible for maintaining references to the mutable state and the latest immutable state. @@ -36,6 +39,8 @@ */ public class StateLifecycleManagerImpl implements StateLifecycleManager { + private static final Logger log = LogManager.getLogger(StateLifecycleManagerImpl.class); + /** * Metrics for the state object */ @@ -158,15 +163,23 @@ private void copyAndUpdateStateRefs(final @NonNull MerkleNodeState stateToCopy) // releasing previous immutable previousMutableState final State previousImmutableState = latestImmutableStateRef.get(); if (previousImmutableState != null) { - previousImmutableState.throwIfDestroyed(); - previousImmutableState.release(); + assert !previousImmutableState.isDestroyed(); + if (previousImmutableState.isDestroyed()) { + log.error(EXCEPTION.getMarker(), "previousImmutableState is in destroyed state"); + } else { + previousImmutableState.release(); + } } stateToCopy.getRoot().reserve(); latestImmutableStateRef.set(stateToCopy); final MerkleNodeState previousMutableState = stateRef.get(); if (previousMutableState != null) { - previousMutableState.throwIfDestroyed(); - previousMutableState.release(); + assert !previousMutableState.isDestroyed(); + if (previousMutableState.isDestroyed()) { + log.error(EXCEPTION.getMarker(), "previousImmutableState is in destroyed state"); + } else { + previousMutableState.release(); + } } // Do not increment the reference count because the stateToCopy provided already has a reference count of at // least From cce3cef6b902fbc8da00a16f5479220e931463a9 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Fri, 7 Nov 2025 14:59:04 -0500 Subject: [PATCH 23/23] Resolved dependency issue Signed-off-by: Ivan Malygin --- platform-sdk/swirlds-state-impl/src/main/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-sdk/swirlds-state-impl/src/main/java/module-info.java b/platform-sdk/swirlds-state-impl/src/main/java/module-info.java index a0dbea12c468..49f571133015 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/module-info.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/module-info.java @@ -16,6 +16,7 @@ requires transitive com.swirlds.virtualmap; requires transitive org.hiero.base.crypto; requires transitive org.hiero.base.utility; + requires com.swirlds.logging; requires com.swirlds.merkledb; requires org.apache.logging.log4j; requires static transitive com.github.spotbugs.annotations;