diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/AppTestBase.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/AppTestBase.java index 08219af4e15f..33bf2cf427a3 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/AppTestBase.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/AppTestBase.java @@ -446,11 +446,6 @@ public AutoCloseableWrapper getLatestImmutableState(@ return null; } - @Override - public AutoCloseableWrapper getLatestSignedState(@NonNull String s) { - return null; - } - @Override public boolean createTransaction(@NonNull byte[] bytes) { return false; 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 dcb9e390a43a..fd0a704e652f 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 @@ -70,6 +70,7 @@ import com.swirlds.platform.components.appcomm.AppCommunicationComponent; import com.swirlds.platform.components.state.DefaultStateManagementComponent; import com.swirlds.platform.components.state.StateManagementComponent; +import com.swirlds.platform.components.state.output.NewLatestCompleteStateConsumer; import com.swirlds.platform.components.transaction.system.ConsensusSystemTransactionManager; import com.swirlds.platform.components.transaction.system.PreconsensusSystemTransactionManager; import com.swirlds.platform.config.ThreadConfig; @@ -151,6 +152,7 @@ import com.swirlds.platform.state.iss.IssHandler; import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.nexus.EmergencyStateNexus; +import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SavedStateInfo; import com.swirlds.platform.state.signed.SignedState; @@ -511,8 +513,17 @@ public class SwirldsPlatform implements Platform { platformWiring.bind(signedStateFileManager); + final LatestCompleteStateNexus latestCompleteState = + new LatestCompleteStateNexus(stateConfig, platformContext.getMetrics()); final SavedStateController savedStateController = new SavedStateController(stateConfig, platformWiring.getSaveStateToDiskInput()::offer); + final NewLatestCompleteStateConsumer newLatestCompleteStateConsumer = ss -> { + // the app comm component will reserve the state, this should be done by the wiring in the future + appCommunicationComponent.newLatestCompleteStateEvent(ss); + // the nexus expects a state to be reserved for it + // in the future, all of these reservations will be done by the wiring + latestCompleteState.setState(ss.reserve("setting latest complete state")); + }; stateManagementComponent = new DefaultStateManagementComponent( platformContext, @@ -520,7 +531,7 @@ public class SwirldsPlatform implements Platform { dispatchBuilder, new PlatformSigner(keysAndCerts), txn -> this.createSystemTransaction(txn, true), - appCommunicationComponent, + newLatestCompleteStateConsumer, this::handleFatalError, platformStatusManager, savedStateController, @@ -599,11 +610,16 @@ public class SwirldsPlatform implements Platform { initialState.getState(), appVersion); + final InterruptableConsumer newSignedStateFromTransactionsConsumer = rs -> { + latestCompleteState.newIncompleteState(rs.get().getRound()); + stateManagementComponent.newSignedStateFromTransactions(rs); + }; + stateHashSignQueue = components.add(new QueueThreadConfiguration(threadManager) .setNodeId(selfId) .setComponent(PLATFORM_THREAD_POOL_NAME) .setThreadName("state-hash-sign") - .setHandler(stateManagementComponent::newSignedStateFromTransactions) + .setHandler(newSignedStateFromTransactionsConsumer) .setCapacity(1) .setMetricsConfiguration(new QueueThreadMetricsConfiguration(metrics).enableBusyTimeMetric()) .build()); @@ -806,7 +822,7 @@ public class SwirldsPlatform implements Platform { consensusRef, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, eventValidator, eventObserverDispatcher, syncMetrics, @@ -891,6 +907,7 @@ public class SwirldsPlatform implements Platform { GuiPlatformAccessor.getInstance().setShadowGraph(selfId, shadowGraph); GuiPlatformAccessor.getInstance().setStateManagementComponent(selfId, stateManagementComponent); GuiPlatformAccessor.getInstance().setConsensusReference(selfId, consensusRef); + GuiPlatformAccessor.getInstance().setLatestCompleteStateComponent(selfId, latestCompleteState); } /** @@ -1364,17 +1381,6 @@ public AutoCloseableWrapper getLatestImmutableState(@ wrapper.isNull() ? null : (T) wrapper.get().getState().getSwirldState(), wrapper::close); } - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @Override - public AutoCloseableWrapper getLatestSignedState(@NonNull final String reason) { - final ReservedSignedState wrapper = stateManagementComponent.getLatestSignedState(reason); - return new AutoCloseableWrapper<>( - wrapper.isNull() ? null : (T) wrapper.get().getState().getSwirldState(), wrapper::close); - } - /** * check whether the given event is the last event in its round, and the platform enters freeze period * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/DefaultStateManagementComponent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/DefaultStateManagementComponent.java index 48a976be3d17..01511b9427df 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/DefaultStateManagementComponent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/DefaultStateManagementComponent.java @@ -16,7 +16,6 @@ package com.swirlds.platform.components.state; -import static com.swirlds.common.metrics.Metrics.PLATFORM_CATEGORY; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STATE_TO_DISK; @@ -24,7 +23,6 @@ import com.swirlds.common.config.StateConfig; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Signature; -import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.common.stream.HashSigner; import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.platform.components.SavedStateController; @@ -103,11 +101,6 @@ public class DefaultStateManagementComponent implements StateManagementComponent private final SavedStateController savedStateController; private final Consumer stateDumpConsumer; - private static final RunningAverageMetric.Config AVG_ROUND_SUPERMAJORITY_CONFIG = new RunningAverageMetric.Config( - PLATFORM_CATEGORY, "roundSup") - .withDescription("latest round with state signed by a supermajority") - .withUnit("round"); - /** * @param platformContext the platform context * @param threadManager manages platform thread resources @@ -162,10 +155,6 @@ public DefaultStateManagementComponent( newLatestCompleteStateConsumer, this::stateHasEnoughSignatures, this::stateLacksSignatures); - - final RunningAverageMetric avgRoundSupermajority = - platformContext.getMetrics().getOrCreate(AVG_ROUND_SUPERMAJORITY_CONFIG); - platformContext.getMetrics().addUpdater(() -> avgRoundSupermajority.update(getLastCompleteRound())); } /** @@ -245,15 +234,6 @@ public void newSignedStateFromTransactions(@NonNull final ReservedSignedState si } } - /** - * {@inheritDoc} - */ - @Override - @NonNull - public ReservedSignedState getLatestSignedState(@NonNull final String reason) { - return signedStateManager.getLatestSignedState(reason); - } - /** * {@inheritDoc} */ @@ -262,14 +242,6 @@ public ReservedSignedState getLatestImmutableState(@NonNull final String reason) return signedStateManager.getLatestImmutableState(reason); } - /** - * {@inheritDoc} - */ - @Override - public long getLastCompleteRound() { - return signedStateManager.getLastCompleteRound(); - } - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/StateManagementComponent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/StateManagementComponent.java index 943850764d59..e499936b6c9f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/StateManagementComponent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/StateManagementComponent.java @@ -19,8 +19,11 @@ import com.swirlds.platform.components.PlatformComponent; import com.swirlds.platform.components.common.output.NewSignedStateFromTransactionsConsumer; import com.swirlds.platform.components.common.output.SignedStateToLoadConsumer; -import com.swirlds.platform.components.state.query.LatestSignedStateProvider; -import com.swirlds.platform.state.signed.*; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedStateFinder; +import com.swirlds.platform.state.signed.SignedStateInfo; +import com.swirlds.platform.state.signed.SignedStateManager; +import com.swirlds.platform.state.signed.StateToDiskReason; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -41,8 +44,7 @@ public interface StateManagementComponent extends PlatformComponent, SignedStateFinder, SignedStateToLoadConsumer, - NewSignedStateFromTransactionsConsumer, - LatestSignedStateProvider { + NewSignedStateFromTransactionsConsumer { /** * Get a reserved instance of the latest immutable signed state. May be unhashed, may or may not have all required @@ -54,13 +56,6 @@ public interface StateManagementComponent */ ReservedSignedState getLatestImmutableState(@NonNull final String reason); - /** - * Returns the latest round for which there is a complete signed state. - * - * @return the latest round number - */ - long getLastCompleteRound(); - /** * Get the latest signed states stored by this component. This method creates a copy, so no changes to the array * will be made. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/query/LatestSignedStateProvider.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/query/LatestSignedStateProvider.java deleted file mode 100644 index dc93e0a6ab86..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/state/query/LatestSignedStateProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.components.state.query; - -import com.swirlds.common.utility.AutoCloseableWrapper; -import com.swirlds.platform.state.signed.ReservedSignedState; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Provides the latest complete signed state, or null if none is available. - */ -public interface LatestSignedStateProvider { - - /** - * Returns the latest complete (fully signed) state with a reservation that is released when the - * {@link AutoCloseableWrapper} is closed. - * - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @return an auto-closeable with the latest complete state, or an auto-closeable wrapper with {@code null} if none - * is available. - */ - @NonNull - ReservedSignedState getLatestSignedState(@NonNull final String reason); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/PreconsensusSystemTransactionManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/PreconsensusSystemTransactionManager.java index 56fc62b26c14..2ad651ef2f95 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/PreconsensusSystemTransactionManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/PreconsensusSystemTransactionManager.java @@ -87,7 +87,7 @@ private void handleTransaction(@NonNull final NodeId creatorId, @NonNull final S } catch (final RuntimeException e) { logger.error( EXCEPTION.getMarker(), - "Error while handling system transaction preconsensus: handler: {}, id: {}, transaction: {}, error: {}", + "Error while handling system transaction preconsensus: handler: {}, id: {}, transaction: {}", handler, creatorId, transaction, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java index a823b0c1ca45..a278c131e9ca 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java @@ -33,7 +33,6 @@ import com.swirlds.common.threading.framework.QueueThread; import com.swirlds.common.threading.framework.config.StoppableThreadConfiguration; import com.swirlds.common.threading.manager.ThreadManager; -import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.config.ThreadConfig; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; @@ -57,6 +56,7 @@ import com.swirlds.platform.reconnect.ReconnectLearnerThrottle; import com.swirlds.platform.reconnect.ReconnectThrottle; import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.PlatformConstructionException; import com.swirlds.platform.system.SoftwareVersion; @@ -124,7 +124,7 @@ public abstract class AbstractGossip implements ConnectionTracker, Gossip { * @param appVersion the version of the app * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state - * @param stateManagementComponent manages the lifecycle of the state queue + * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param syncMetrics metrics for sync * @param statusActionSubmitter enables submitting platform status actions * @param loadReconnectState a method that should be called when a state from reconnect is obtained @@ -140,7 +140,7 @@ protected AbstractGossip( @NonNull final SoftwareVersion appVersion, @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final StateManagementComponent stateManagementComponent, + @NonNull final SignedStateNexus latestCompleteState, @NonNull final SyncMetrics syncMetrics, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final Consumer loadReconnectState, @@ -214,7 +214,7 @@ protected AbstractGossip( this::pause, clearAllPipelinesForReconnect::run, swirldStateManager::getConsensusState, - stateManagementComponent::getLastCompleteRound, + latestCompleteState::getRound, new ReconnectLearnerThrottle(time, selfId, reconnectConfig), loadReconnectState, new ReconnectLearnerFactory( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java index 019f32771761..4328da0c3feb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java @@ -26,7 +26,6 @@ import com.swirlds.common.threading.framework.QueueThread; import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.platform.Consensus; -import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; @@ -40,6 +39,7 @@ import com.swirlds.platform.observers.EventObserverDispatcher; import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; @@ -80,7 +80,7 @@ private GossipFactory() {} * @param consensusRef a pointer to consensus * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state - * @param stateManagementComponent manages the lifecycle of the state + * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param eventValidator validates events and passes valid events further along the intake pipeline * @param eventObserverDispatcher the object used to wire event intake * @param syncMetrics metrics for sync @@ -107,7 +107,7 @@ public static Gossip buildGossip( @NonNull final AtomicReference consensusRef, @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final StateManagementComponent stateManagementComponent, + @NonNull final SignedStateNexus latestCompleteState, @NonNull final EventValidator eventValidator, @NonNull final EventObserverDispatcher eventObserverDispatcher, @NonNull final SyncMetrics syncMetrics, @@ -131,7 +131,7 @@ public static Gossip buildGossip( Objects.requireNonNull(consensusRef); Objects.requireNonNull(intakeQueue); Objects.requireNonNull(swirldStateManager); - Objects.requireNonNull(stateManagementComponent); + Objects.requireNonNull(latestCompleteState); Objects.requireNonNull(eventValidator); Objects.requireNonNull(eventObserverDispatcher); Objects.requireNonNull(syncMetrics); @@ -160,7 +160,7 @@ public static Gossip buildGossip( consensusRef, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, eventValidator, eventObserverDispatcher, syncMetrics, @@ -183,7 +183,7 @@ public static Gossip buildGossip( shadowGraph, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, syncMetrics, platformStatusManager, loadReconnectState, @@ -205,7 +205,7 @@ public static Gossip buildGossip( consensusRef, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, syncMetrics, eventLinker, platformStatusManager, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java index 7e3a9cb24bf1..9f659c9795a3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java @@ -39,7 +39,6 @@ import com.swirlds.common.utility.Clearable; import com.swirlds.common.utility.LoggingClearables; import com.swirlds.platform.Consensus; -import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; @@ -66,6 +65,7 @@ import com.swirlds.platform.reconnect.emergency.EmergencyReconnectProtocol; import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; @@ -112,7 +112,7 @@ public class ChatterGossip extends AbstractGossip { * @param consensusRef a pointer to consensus * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state - * @param stateManagementComponent manages the lifecycle of the state + * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param eventValidator validates events and passes valid events along the intake pipeline * @param eventObserverDispatcher the object used to wire event intake * @param syncMetrics metrics for sync @@ -137,7 +137,7 @@ public ChatterGossip( @NonNull final AtomicReference consensusRef, @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final StateManagementComponent stateManagementComponent, + @NonNull final SignedStateNexus latestCompleteState, @NonNull final EventValidator eventValidator, @NonNull final EventObserverDispatcher eventObserverDispatcher, @NonNull final SyncMetrics syncMetrics, @@ -156,7 +156,7 @@ public ChatterGossip( appVersion, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, syncMetrics, platformStatusManager, loadReconnectState, @@ -250,8 +250,7 @@ public ChatterGossip( threadManager, otherId, reconnectThrottle, - () -> stateManagementComponent.getLatestSignedState( - "SwirldsPlatform: ReconnectProtocol"), + () -> latestCompleteState.getState("SwirldsPlatform: ReconnectProtocol"), reconnectConfig.asyncStreamTimeout(), reconnectMetrics, reconnectController, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java index 453a0a043852..1101c430a1a2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java @@ -28,7 +28,6 @@ import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.common.utility.Clearable; import com.swirlds.common.utility.LoggingClearables; -import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.AbstractGossip; @@ -36,6 +35,7 @@ import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; @@ -71,7 +71,7 @@ public class SingleNodeSyncGossip extends AbstractGossip { * @param shadowGraph contains non-ancient events * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state - * @param stateManagementComponent manages the lifecycle of the state + * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param syncMetrics metrics for sync * @param statusActionSubmitter enables submitting platform status actions * @param loadReconnectState a method that should be called when a state from reconnect is obtained @@ -88,7 +88,7 @@ public SingleNodeSyncGossip( @NonNull final ShadowGraph shadowGraph, @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final StateManagementComponent stateManagementComponent, + @NonNull final SignedStateNexus latestCompleteState, @NonNull final SyncMetrics syncMetrics, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final Consumer loadReconnectState, @@ -104,7 +104,7 @@ public SingleNodeSyncGossip( appVersion, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, syncMetrics, statusActionSubmitter, loadReconnectState, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java index 5c56f69cac0d..82213775e3dc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java @@ -38,7 +38,6 @@ import com.swirlds.common.utility.Clearable; import com.swirlds.common.utility.LoggingClearables; import com.swirlds.platform.Consensus; -import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; @@ -64,6 +63,7 @@ import com.swirlds.platform.reconnect.emergency.EmergencyReconnectProtocol; import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.SwirldStateManager; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; @@ -124,7 +124,7 @@ public class SyncGossip extends AbstractGossip { * @param consensusRef a pointer to consensus * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state - * @param stateManagementComponent manages the lifecycle of the state + * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable * @param syncMetrics metrics for sync * @param eventLinker links events to their parents, buffers orphans if configured to do so * @param platformStatusManager the platform status manager @@ -148,7 +148,7 @@ public SyncGossip( @NonNull final AtomicReference consensusRef, @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, - @NonNull final StateManagementComponent stateManagementComponent, + @NonNull final SignedStateNexus latestCompleteState, @NonNull final SyncMetrics syncMetrics, @NonNull final EventLinker eventLinker, @NonNull final PlatformStatusManager platformStatusManager, @@ -166,7 +166,7 @@ public SyncGossip( appVersion, intakeQueue, swirldStateManager, - stateManagementComponent, + latestCompleteState, syncMetrics, platformStatusManager, loadReconnectState, @@ -276,8 +276,7 @@ public SyncGossip( threadManager, otherId, reconnectThrottle, - () -> stateManagementComponent.getLatestSignedState( - "SwirldsPlatform: ReconnectProtocol"), + () -> latestCompleteState.getState("SwirldsPlatform: ReconnectProtocol"), reconnectConfig.asyncStreamTimeout(), reconnectMetrics, reconnectController, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiPlatformAccessor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiPlatformAccessor.java index ee3c2be279bd..b1606a0db7f2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiPlatformAccessor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiPlatformAccessor.java @@ -22,6 +22,7 @@ import com.swirlds.platform.components.state.StateManagementComponent; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.system.events.PlatformEvent; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -45,6 +46,7 @@ public final class GuiPlatformAccessor { private final Map shadowGraphs = new ConcurrentHashMap<>(); private final Map stateManagementComponents = new ConcurrentHashMap<>(); private final Map> consensusReferences = new ConcurrentHashMap<>(); + private final Map latestCompleteStateComponents = new ConcurrentHashMap<>(); private static final GuiPlatformAccessor INSTANCE = new GuiPlatformAccessor(); @@ -199,4 +201,29 @@ public Consensus getConsensus(@NonNull final NodeId nodeId) { } return consensusReference.get(); } + + /** + * Set the latest complete state component for a node. + * + * @param nodeId the ID of the node + * @param latestCompleteState the latest complete state component + */ + public void setLatestCompleteStateComponent( + @NonNull final NodeId nodeId, @NonNull final SignedStateNexus latestCompleteState) { + Objects.requireNonNull(nodeId, "nodeId must not be null"); + Objects.requireNonNull(latestCompleteState, "latestCompleteState must not be null"); + latestCompleteStateComponents.put(nodeId, latestCompleteState); + } + + /** + * Get the latest complete state component for a node, or null if none is set. + * + * @param nodeId the ID of the node + * @return the latest complete state component + */ + @Nullable + public SignedStateNexus getLatestCompleteStateComponent(@NonNull final NodeId nodeId) { + Objects.requireNonNull(nodeId, "nodeId must not be null"); + return latestCompleteStateComponents.getOrDefault(nodeId, null); + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTab2Consensus.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTab2Consensus.java index 7ee33d8f29ca..9b74bf72529e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTab2Consensus.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/internal/WinTab2Consensus.java @@ -24,6 +24,7 @@ import com.swirlds.platform.gui.GuiUtils; import com.swirlds.platform.gui.components.PrePaintableJPanel; import com.swirlds.platform.gui.model.GuiModel; +import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.SignedStateInfo; import com.swirlds.platform.system.Platform; import java.awt.Font; @@ -65,7 +66,9 @@ public void prePaint() { long r3 = consensus.getMaxRound(); final StateManagementComponent stateManagementComponent = GuiPlatformAccessor.getInstance().getStateManagementComponent(platform.getSelfId()); - long r0 = stateManagementComponent.getLastCompleteRound(); + final SignedStateNexus latestCompleteStateComponent = + GuiPlatformAccessor.getInstance().getLatestCompleteStateComponent(platform.getSelfId()); + long r0 = latestCompleteStateComponent.getRound(); if (r1 == -1) { s += "\n = latest deleted round-created"; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java index 39f87a2eb134..c40b1101068c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java @@ -160,20 +160,6 @@ public synchronized AutoCloseableWrapper getLatestImm reservedSignedState::close); } - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @Override - public AutoCloseableWrapper getLatestSignedState(@NonNull final String reason) { - final ReservedSignedState reservedSignedState = immutableState.getAndReserve(reason); - return new AutoCloseableWrapper<>( - reservedSignedState.isNull() - ? null - : (T) reservedSignedState.get().getSwirldState(), - reservedSignedState::close); - } - /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/LatestCompleteStateNexus.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/LatestCompleteStateNexus.java new file mode 100644 index 000000000000..74e4d5a0e774 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/nexus/LatestCompleteStateNexus.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.nexus; + +import static com.swirlds.common.metrics.Metrics.PLATFORM_CATEGORY; + +import com.swirlds.common.config.StateConfig; +import com.swirlds.common.metrics.Metrics; +import com.swirlds.common.metrics.RunningAverageMetric; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; + +/** + * A nexus that holds the latest complete signed state. + */ +public class LatestCompleteStateNexus extends SignedStateNexus { + private static final RunningAverageMetric.Config AVG_ROUND_SUPERMAJORITY_CONFIG = new RunningAverageMetric.Config( + PLATFORM_CATEGORY, "roundSup") + .withDescription("latest round with state signed by a supermajority") + .withUnit("round"); + + private final StateConfig stateConfig; + + /** + * Create a new nexus that holds the latest complete signed state. + * + * @param stateConfig the state configuration + * @param metrics the metrics object to update + */ + public LatestCompleteStateNexus(@NonNull final StateConfig stateConfig, @NonNull final Metrics metrics) { + this.stateConfig = Objects.requireNonNull(stateConfig); + Objects.requireNonNull(metrics); + + final RunningAverageMetric avgRoundSupermajority = metrics.getOrCreate(AVG_ROUND_SUPERMAJORITY_CONFIG); + metrics.addUpdater(() -> avgRoundSupermajority.update(getRound())); + } + + /** + * Notify the nexus that a new signed state has been created. This is useful for the nexus to know when it should + * clear the latest complete state. This is used so that we don't hold the latest complete state forever in case we + * have trouble gathering signatures. + * + * @param newStateRound a new signed state round that is not yet complete + */ + public void newIncompleteState(final long newStateRound) { + // NOTE: This logic is duplicated in SignedStateManager, but will be removed from the signed state manager + // once its refactor is done + + // Any state older than this is unconditionally removed, even if it is the latest + final long earliestPermittedRound = newStateRound - stateConfig.roundsToKeepForSigning() + 1; + + // Is the latest complete round older than the earliest permitted round? + if (getRound() < earliestPermittedRound) { + // Yes, so remove it + clear(); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/ReservedSignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/ReservedSignedState.java index 1259c0fdd2a4..4194560d284f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/ReservedSignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/ReservedSignedState.java @@ -47,7 +47,7 @@ public class ReservedSignedState implements AutoCloseableNonThrowing { private final SignedState signedState; private final String reason; - private final long reservationId = nextReservationId.getAndIncrement(); + private final long reservationId; private boolean closed = false; /** @@ -63,19 +63,23 @@ public class ReservedSignedState implements AutoCloseableNonThrowing { private ReservedSignedState() { this.signedState = null; this.reason = ""; + this.reservationId = nextReservationId.getAndIncrement(); } /** * Create a new reserved signed state. * - * @param signedState the signed state to reserve - * @param reason a short description of why this SignedState is being reserved. Each location where a - * SignedState is reserved should attempt to use a unique reason, as this makes debugging - * reservation bugs easier. - */ - ReservedSignedState(@NonNull final SignedState signedState, @NonNull final String reason) { + * @param signedState the signed state to reserve + * @param reason a short description of why this SignedState is being reserved. Each location where a + * SignedState is reserved should attempt to use a unique reason, as this makes debugging + * reservation bugs easier. + * @param reservationId a unique id to track reserving and releasing of signed states + */ + private ReservedSignedState( + @NonNull final SignedState signedState, @NonNull final String reason, final long reservationId) { this.signedState = Objects.requireNonNull(signedState); this.reason = Objects.requireNonNull(reason); + this.reservationId = reservationId; } /** @@ -88,24 +92,12 @@ private ReservedSignedState() { */ static @NonNull ReservedSignedState createAndReserve( @NonNull final SignedState signedState, @NonNull final String reason) { - final ReservedSignedState reservedSignedState = new ReservedSignedState(signedState, reason); + final ReservedSignedState reservedSignedState = + new ReservedSignedState(signedState, reason, nextReservationId.getAndIncrement()); signedState.incrementReservationCount(reason, reservedSignedState.getReservationId()); return reservedSignedState; } - /** - * Create a new reserved signed state. This method assumes that the reservation count will be incremented by the - * caller. - * - * @param signedState the signed state to reserve - * @param reason a short description of why this SignedState is being reserved. Each location where a - * SignedState is reserved should attempt to use a unique reason, as this makes debugging - * reservation bugs easier. - */ - static @NonNull ReservedSignedState create(@NonNull final SignedState signedState, @NonNull final String reason) { - return new ReservedSignedState(signedState, reason); - } - /** * Check if the signed state is null. * @@ -154,10 +146,11 @@ public boolean isNotNull() { if (signedState == null) { return new ReservedSignedState(); } + final long reservationId = nextReservationId.getAndIncrement(); if (!signedState.tryIncrementReservationCount(reason, reservationId)) { return null; } - return create(signedState, reason); + return new ReservedSignedState(signedState, reason, reservationId); } /** 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 929016a3b44c..8504299d71c8 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 @@ -727,7 +727,7 @@ public void pruneInvalidSignatures(@NonNull final AddressBook trustedAddressBook * @return the reservation history */ @Nullable - SignedStateHistory getHistory() { + public SignedStateHistory getHistory() { return history; } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java index 16b2c0c3342e..9f3127d4976e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileManager.java @@ -19,9 +19,6 @@ import static com.swirlds.common.io.utility.FileUtils.deleteDirectoryAndLog; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STATE_TO_DISK; -import static com.swirlds.platform.state.signed.SignedStateFileReader.getSavedStateFiles; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStateDirectory; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStatesBaseDirectory; import static com.swirlds.platform.state.signed.StateToDiskReason.UNKNOWN; import com.swirlds.base.time.Time; @@ -77,6 +74,8 @@ public class SignedStateFileManager { * Provides system time */ private final Time time; + /** Used to determine the path of a signed state */ + private final SignedStateFilePath signedStateFilePath; /** * Creates a new instance. @@ -103,6 +102,7 @@ public SignedStateFileManager( this.swirldName = Objects.requireNonNull(swirldName); this.platformContext = Objects.requireNonNull(context); this.configuration = Objects.requireNonNull(context.getConfiguration()); + this.signedStateFilePath = new SignedStateFilePath(configuration.getConfigData(StateConfig.class)); } /** @@ -161,7 +161,8 @@ public void dumpStateTask(@NonNull final StateDumpRequest request) { // states requested to be written out-of-band are always written to disk saveStateTask( reservedSignedState.get(), - getSignedStatesBaseDirectory() + signedStateFilePath + .getSignedStatesBaseDirectory() .resolve(getReason(signedState).getDescription()) .resolve(String.format("node%d_round%d", selfId.id(), signedState.getRound()))); } @@ -217,7 +218,7 @@ private void stateLacksSignatures(@NonNull final SignedState reservedState) { * @return the File that represents the directory of the signed state for the particular round */ private Path getSignedStateDir(final long round) { - return getSignedStateDirectory(mainClassName, selfId, swirldName, round); + return signedStateFilePath.getSignedStateDirectory(mainClassName, selfId, swirldName, round); } /** @@ -225,7 +226,8 @@ private Path getSignedStateDir(final long round) { * @return the minimum generation non-ancient of the oldest state that was not deleted */ private long deleteOldStates() { - final List savedStates = getSavedStateFiles(mainClassName, selfId, swirldName); + final List savedStates = + signedStateFilePath.getSavedStateFiles(mainClassName, selfId, swirldName); // States are returned newest to oldest. So delete from the end of the list to delete the oldest states. int index = savedStates.size() - 1; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java new file mode 100644 index 000000000000..185d27d13d2b --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFilePath.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state.signed; + +import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.platform.state.signed.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; +import static java.nio.file.Files.exists; +import static java.nio.file.Files.isDirectory; + +import com.swirlds.common.config.StateConfig; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Utility methods for determining the path of signed states on disk. + */ +public class SignedStateFilePath { + private static final Logger logger = LogManager.getLogger(SignedStateFilePath.class); + final StateConfig stateConfig; + + /** + * Create a new instance of this class. + * + * @param stateConfig the config that contains the location of the saved state directory + */ + public SignedStateFilePath(@NonNull final StateConfig stateConfig) { + this.stateConfig = stateConfig; + } + + /** + *

+ * Get the base directory where all states will be stored. + *

+ * + *
+     * e.g. data/saved/
+     *      |--------|
+     *          |
+     *       location where
+     *       states are saved
+     * 
+ * + * @return the base directory for all signed state files + */ + public @NonNull Path getSignedStatesBaseDirectory() { + return getAbsolutePath(stateConfig.savedStateDirectory()); + } + + /** + *

+ * Get the directory that contains saved states for a particular app. + *

+ * + *
+     * e.g. data/saved/com.swirlds.foobar
+     *      |--------| |----------------|
+     *          |             |
+     *          |         mainClassName
+     *          |
+     *       location where
+     *       states are saved
+     * 
+ * + * @param mainClassName the name of the app + * @return the path of a directory, may not exist + */ + public @NonNull Path getSignedStatesDirectoryForApp(final String mainClassName) { + return getSignedStatesBaseDirectory().resolve(mainClassName); + } + + /** + *

+ * Get the directory that contains contains saved states for a particular node. + *

+ * + *
+     * e.g. data/saved/com.swirlds.foobar/1234
+     *      |--------| |----------------| |--|
+     *          |             |            |
+     *          |         mainClassName    |
+     *          |                          |
+     *       location where              selfId
+     *       states are saved
+     * 
+ * + * @param mainClassName the name of the app + * @param selfId the ID of this node + * @return the path of a directory, may not exist + */ + public @NonNull Path getSignedStatesDirectoryForNode(final String mainClassName, final NodeId selfId) { + return getSignedStatesDirectoryForApp(mainClassName).resolve(selfId.toString()); + } + + /** + *

+ * Get the directory that contains saved states for a particular swirld (i.e. an instance of an app). + *

+ * + *
+     * e.g. data/saved/com.swirlds.foobar/1234/mySwirld
+     *      |--------| |----------------| |--| |------|
+     *          |             |            |       |
+     *          |         mainClassName    |    swirldName
+     *          |                          |
+     *       location where              selfId
+     *       states are saved
+     * 
+ * + * @param mainClassName the name of the app + * @param selfId the ID of this node + * @param swirldName the name of the swirld + * @return the path of a directory, may not exist + */ + public @NonNull Path getSignedStatesDirectoryForSwirld( + final String mainClassName, final NodeId selfId, final String swirldName) { + + return getSignedStatesDirectoryForNode(mainClassName, selfId).resolve(swirldName); + } + + /** + *

+ * Get the fully qualified path to the directory for a particular signed state. This directory might not exist. + *

+ * + *
+     * e.g. data/saved/com.swirlds.foobar/1234/mySwirld/1000
+     *      |--------| |----------------| |--| |------| |--|
+     *          |             |            |      |      |
+     *          |         mainClassName    |      |    round
+     *          |                          |  swirldName
+     *       location where              selfId
+     *       states are saved
+     *
+     * 
+ * + * @param mainClassName the name of the app + * @param selfId the ID of this node + * @param swirldName the name of the swirld + * @param round the round number of the state + * @return the path of the signed state for the particular round + */ + public @NonNull Path getSignedStateDirectory( + final String mainClassName, final NodeId selfId, final String swirldName, final long round) { + // FUTURE WORK: mainClass, selfId and swirldName never change during a run, so they should be constructor + // parameters + return getSignedStatesDirectoryForSwirld(mainClassName, selfId, swirldName) + .resolve(Long.toString(round)); + } + + /** + * Looks for saved state files locally and returns a list of them sorted from newest to oldest + * + * @param mainClassName + * the name of the main app class + * @param platformId + * the ID of the platform + * @param swirldName + * the swirld name + * @return Information about saved states on disk, or null if none are found + */ + @SuppressWarnings("resource") + @NonNull + public List getSavedStateFiles( + final String mainClassName, final NodeId platformId, final String swirldName) { + + try { + final Path dir = getSignedStatesDirectoryForSwirld(mainClassName, platformId, swirldName); + + if (!exists(dir) || !isDirectory(dir)) { + return List.of(); + } + + final List dirs = Files.list(dir).filter(Files::isDirectory).toList(); + + final TreeMap savedStates = new TreeMap<>(); + for (final Path subDir : dirs) { + try { + final long round = Long.parseLong(subDir.getFileName().toString()); + final Path stateFile = subDir.resolve(SIGNED_STATE_FILE_NAME); + if (!exists(stateFile)) { + logger.warn( + EXCEPTION.getMarker(), + "Saved state file ({}) not found, but directory exists '{}'", + stateFile.getFileName(), + subDir.toAbsolutePath()); + continue; + } + + final Path metdataPath = subDir.resolve(SavedStateMetadata.FILE_NAME); + final SavedStateMetadata metadata; + try { + metadata = SavedStateMetadata.parse(metdataPath); + } catch (final IOException e) { + logger.error( + EXCEPTION.getMarker(), "Unable to read saved state metadata file '{}'", metdataPath); + continue; + } + + savedStates.put(round, new SavedStateInfo(stateFile, metadata)); + + } catch (final NumberFormatException e) { + logger.warn( + EXCEPTION.getMarker(), + "Unexpected directory '{}' in '{}'", + subDir.getFileName(), + dir.toAbsolutePath()); + } + } + return new ArrayList<>(savedStates.descendingMap().values()); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java index 29105c1fb95e..9a13a2482dfc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileReader.java @@ -17,14 +17,12 @@ package com.swirlds.platform.state.signed; import static com.swirlds.common.io.streams.StreamDebugUtils.deserializeAndDebugOnFailure; -import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.platform.state.signed.SignedStateFileUtils.MAX_MERKLE_NODES_IN_STATE; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.SIGNED_STATE_FILE_NAME; import static com.swirlds.platform.state.signed.SignedStateFileUtils.VERSIONED_FILE_BYTE; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStatesDirectoryForSwirld; import static java.nio.file.Files.exists; -import static java.nio.file.Files.isDirectory; +import com.swirlds.common.config.StateConfig; +import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.io.streams.MerkleDataInputStream; @@ -34,88 +32,31 @@ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.TreeMap; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Utility methods for reading a signed state from disk. */ public final class SignedStateFileReader { - - private static final Logger logger = LogManager.getLogger(SignedStateFileReader.class); - private SignedStateFileReader() {} /** - * Looks for saved state files locally and returns a list of them sorted from newest to oldest + * Same as {@link SignedStateFilePath#getSavedStateFiles(String, NodeId, String)} but uses the config from + * {@link ConfigurationHolder} * - * @param mainClassName - * the name of the main app class - * @param platformId - * the ID of the platform - * @param swirldName - * the swirld name - * @return Information about saved states on disk, or null if none are found + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ - @SuppressWarnings("resource") + @Deprecated(forRemoval = true) @NonNull public static List getSavedStateFiles( final String mainClassName, final NodeId platformId, final String swirldName) { - - try { - final Path dir = getSignedStatesDirectoryForSwirld(mainClassName, platformId, swirldName); - - if (!exists(dir) || !isDirectory(dir)) { - return List.of(); - } - - final List dirs = Files.list(dir).filter(Files::isDirectory).toList(); - - final TreeMap savedStates = new TreeMap<>(); - for (final Path subDir : dirs) { - try { - final long round = Long.parseLong(subDir.getFileName().toString()); - final Path stateFile = subDir.resolve(SIGNED_STATE_FILE_NAME); - if (!exists(stateFile)) { - logger.warn( - EXCEPTION.getMarker(), - "Saved state file ({}) not found, but directory exists '{}'", - stateFile.getFileName(), - subDir.toAbsolutePath()); - continue; - } - - final Path metdataPath = subDir.resolve(SavedStateMetadata.FILE_NAME); - final SavedStateMetadata metadata; - try { - metadata = SavedStateMetadata.parse(metdataPath); - } catch (final IOException e) { - logger.error( - EXCEPTION.getMarker(), "Unable to read saved state metadata file '{}'", metdataPath); - continue; - } - - savedStates.put(round, new SavedStateInfo(stateFile, metadata)); - - } catch (final NumberFormatException e) { - logger.warn( - EXCEPTION.getMarker(), - "Unexpected directory '{}' in '{}'", - subDir.getFileName(), - dir.toAbsolutePath()); - } - } - return new ArrayList<>(savedStates.descendingMap().values()); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSavedStateFiles(mainClassName, platformId, swirldName); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java index 504ae8fa74aa..d3d4d03f5663 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileUtils.java @@ -16,25 +16,18 @@ package com.swirlds.platform.state.signed; -import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; - import com.swirlds.common.config.StateConfig; import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.platform.NodeId; import java.nio.file.Path; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Utility methods for dealing with signed states on disk. */ public final class SignedStateFileUtils { - - private static final Logger logger = LogManager.getLogger(SignedStateFileUtils.class); - /** - * Fun trivia: the file extension ".swh" stands for "SWirlds Hashgraph", although - * this is a bit misleading... as this file doesn't actually contain a hashgraph. + * Fun trivia: the file extension ".swh" stands for "SWirlds Hashgraph", although this is a bit misleading... as + * this file doesn't actually contain a hashgraph. */ public static final String SIGNED_STATE_FILE_NAME = "SignedState.swh"; @@ -60,133 +53,74 @@ public final class SignedStateFileUtils { private SignedStateFileUtils() {} /** - *

- * Get the base directory where all states will be stored. - *

+ * Same as {@link SignedStateFilePath#getSignedStatesBaseDirectory()} but uses the config from + * {@link ConfigurationHolder} * - *
-     * e.g. data/saved/
-     *      |--------|
-     *          |
-     *       location where
-     *       states are saved
-     * 
- * - * @return the base directory for all signed state files + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ + @Deprecated(forRemoval = true) public static Path getSignedStatesBaseDirectory() { - final StateConfig stateConfig = ConfigurationHolder.getConfigData(StateConfig.class); - return getAbsolutePath(stateConfig.savedStateDirectory()); + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSignedStatesBaseDirectory(); } /** - *

- * Get the directory that contains saved states for a particular app. - *

- * - *
-     * e.g. data/saved/com.swirlds.foobar
-     *      |--------| |----------------|
-     *          |             |
-     *          |         mainClassName
-     *          |
-     *       location where
-     *       states are saved
-     * 
+ * Same as {@link SignedStateFilePath#getSignedStatesDirectoryForApp(String)} but uses the config from + * {@link ConfigurationHolder} * - * @param mainClassName - * the name of the app - * @return the path of a directory, may not exist + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ + @Deprecated(forRemoval = true) public static Path getSignedStatesDirectoryForApp(final String mainClassName) { - return getSignedStatesBaseDirectory().resolve(mainClassName); + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSignedStatesDirectoryForApp(mainClassName); } /** - *

- * Get the directory that contains contains saved states for a particular node. - *

- * - *
-     * e.g. data/saved/com.swirlds.foobar/1234
-     *      |--------| |----------------| |--|
-     *          |             |            |
-     *          |         mainClassName    |
-     *          |                          |
-     *       location where              selfId
-     *       states are saved
-     * 
+ * Same as {@link SignedStateFilePath#getSignedStatesDirectoryForNode(String, NodeId)} but uses the config from + * {@link ConfigurationHolder} * - * @param mainClassName - * the name of the app - * @param selfId - * the ID of this node - * @return the path of a directory, may not exist + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ + @Deprecated(forRemoval = true) public static Path getSignedStatesDirectoryForNode(final String mainClassName, final NodeId selfId) { - - return getSignedStatesDirectoryForApp(mainClassName).resolve(selfId.toString()); + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSignedStatesDirectoryForNode(mainClassName, selfId); } /** - *

- * Get the directory that contains saved states for a particular swirld (i.e. an instance of an app). - *

- * - *
-     * e.g. data/saved/com.swirlds.foobar/1234/mySwirld
-     *      |--------| |----------------| |--| |------|
-     *          |             |            |       |
-     *          |         mainClassName    |    swirldName
-     *          |                          |
-     *       location where              selfId
-     *       states are saved
-     * 
+ * Same as {@link SignedStateFilePath#getSignedStatesDirectoryForSwirld(String, NodeId, String)} but uses the config + * from {@link ConfigurationHolder} * - * @param mainClassName - * the name of the app - * @param selfId - * the ID of this node - * @param swirldName - * the name of the swirld - * @return the path of a directory, may not exist + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ + @Deprecated(forRemoval = true) public static Path getSignedStatesDirectoryForSwirld( final String mainClassName, final NodeId selfId, final String swirldName) { - - return getSignedStatesDirectoryForNode(mainClassName, selfId).resolve(swirldName); + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSignedStatesDirectoryForSwirld(mainClassName, selfId, swirldName); } /** - *

- * Get the fully qualified path to the directory for a particular signed state. This directory might not exist. - *

+ * Same as {@link SignedStateFilePath#getSignedStateDirectory(String, NodeId, String, long)} but uses the config + * from {@link ConfigurationHolder} * - *
-     * e.g. data/saved/com.swirlds.foobar/1234/mySwirld/1000
-     *      |--------| |----------------| |--| |------| |--|
-     *          |             |            |      |      |
-     *          |         mainClassName    |      |    round
-     *          |                          |  swirldName
-     *       location where              selfId
-     *       states are saved
-     *
-     * 
- * - * @param mainClassName - * the name of the app - * @param selfId - * the ID of this node - * @param swirldName - * the name of the swirld - * @param round - * the round number of the state - * @return the path of the signed state for the particular round + * @deprecated this uses a static config, which means that a unit test cannot configure it for its scope. this + * causes unit tests to fail randomly if another test sets an inadequate value in the config holder. */ + @Deprecated(forRemoval = true) public static Path getSignedStateDirectory( final String mainClassName, final NodeId selfId, final String swirldName, final long round) { - - return getSignedStatesDirectoryForSwirld(mainClassName, selfId, swirldName) - .resolve(Long.toString(round)); + // new instance on every call in case the config changes in the holder + return new SignedStateFilePath(ConfigurationHolder.getConfigData(StateConfig.class)) + .getSignedStateDirectory(mainClassName, selfId, swirldName, round); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java index 03733de8b338..d3f4bb192aab 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java @@ -144,27 +144,6 @@ public SignedStateManager( new StandardSequenceSet<>(0, stateConfig.maxAgeOfFutureStateSignatures(), SavedSignature::round); } - /** - * Get the round number of the last complete round. Will return -1 if there is not any recent round that has - * gathered sufficient signatures. - * - * @return latest round for which we have a majority of signatures - */ - public long getLastCompleteRound() { - return completeStates.getLatestRound(); - } - - /** - * Get the last complete signed state - * - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @return the latest complete signed state, or a null reservation if no recent states that are complete - */ - public @NonNull ReservedSignedState getLatestSignedState(@NonNull final String reason) { - return completeStates.getLatestAndReserve(reason); - } - /** * Get the latest immutable signed state. May be unhashed, may or may not have all required signatures. State is * returned with a reservation. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java index 3eca9282568a..f9bd7fd6020c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java @@ -21,7 +21,6 @@ import static com.swirlds.logging.legacy.LogMarker.STARTUP; import static com.swirlds.platform.state.GenesisStateBuilder.buildGenesisState; import static com.swirlds.platform.state.signed.ReservedSignedState.createNullReservation; -import static com.swirlds.platform.state.signed.SignedStateFileReader.getSavedStateFiles; import static com.swirlds.platform.state.signed.SignedStateFileReader.readStateFile; import com.swirlds.common.config.StateConfig; @@ -96,7 +95,9 @@ public static void doRecoveryCleanup( + "Any states with a round number higher than {} will be recycled.", initialStateRound); - final List savedStateFiles = getSavedStateFiles(actualMainClassName, selfId, swirldName); + final List savedStateFiles = new SignedStateFilePath( + platformContext.getConfiguration().getConfigData(StateConfig.class)) + .getSavedStateFiles(actualMainClassName, selfId, swirldName); for (final SavedStateInfo stateInfo : savedStateFiles) { if (stateInfo.metadata().round() > initialStateRound) { recycleState(recycleBin, stateInfo); @@ -201,7 +202,9 @@ static ReservedSignedState loadStateFile( final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); final String actualMainClassName = stateConfig.getMainClassName(mainClassName); - final List savedStateFiles = getSavedStateFiles(actualMainClassName, selfId, swirldName); + final List savedStateFiles = new SignedStateFilePath( + platformContext.getConfiguration().getConfigData(StateConfig.class)) + .getSavedStateFiles(actualMainClassName, selfId, swirldName); logStatesFound(savedStateFiles); if (savedStateFiles.isEmpty()) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java index 93e937491208..e083f2c52cbd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/StateAccessor.java @@ -34,17 +34,4 @@ public interface StateAccessor { * @return a wrapper around the most recent immutable state */ AutoCloseableWrapper getLatestImmutableState(@NonNull final String reason); - - /** - * Get the most recent fully signed state. May return a wrapper around null if the platform does not have any fully - * signed states still in memory (e.g. right after boot or if there is trouble with the collection of state - * signatures). - * - * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is - * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. - * @param the type of the state - * @return a wrapper around the most recent fully signed state, or a wrapper around null if there are no available - * fully signed states - */ - AutoCloseableWrapper getLatestSignedState(@NonNull final String reason); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java index 447813578fae..375c923c3e92 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java @@ -22,7 +22,6 @@ exports com.swirlds.platform.components.common.query; exports com.swirlds.platform.components.state; exports com.swirlds.platform.components.state.output; - exports com.swirlds.platform.components.state.query; exports com.swirlds.platform.config; exports com.swirlds.platform.config.legacy; exports com.swirlds.platform.event.report; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java index 0dc3eec6b7e3..8030619607ff 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileManagerTests.java @@ -20,8 +20,6 @@ import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.platform.state.signed.SignedStateFileReader.readStateFile; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStateDirectory; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStatesBaseDirectory; import static com.swirlds.platform.state.signed.StateToDiskReason.FATAL_ERROR; import static com.swirlds.platform.state.signed.StateToDiskReason.ISS; import static com.swirlds.platform.state.signed.StateToDiskReason.PERIODIC_SNAPSHOT; @@ -57,6 +55,7 @@ import com.swirlds.platform.state.signed.SavedStateMetadata; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateFileManager; +import com.swirlds.platform.state.signed.SignedStateFilePath; import com.swirlds.platform.state.signed.SignedStateFileReader; import com.swirlds.platform.state.signed.SignedStateFileUtils; import com.swirlds.platform.state.signed.SignedStateMetrics; @@ -91,6 +90,10 @@ class SignedStateFileManagerTests { private static final NodeId SELF_ID = new NodeId(1234); private static final String MAIN_CLASS_NAME = "com.swirlds.foobar"; private static final String SWIRLD_NAME = "mySwirld"; + + private PlatformContext context; + private SignedStateFilePath signedStateFilePath; + /** * Temporary directory provided by JUnit */ @@ -105,6 +108,14 @@ static void beforeAll() throws ConstructableRegistryException { @BeforeEach void beforeEach() throws IOException { TemporaryFileBuilder.overrideTemporaryFileLocation(testDirectory); + final TestConfigBuilder configBuilder = new TestConfigBuilder() + .withValue( + StateConfig_.SAVED_STATE_DIRECTORY, + testDirectory.toFile().toString()); + context = TestPlatformContextBuilder.create() + .withConfiguration(configBuilder.getOrCreateConfig()) + .build(); + signedStateFilePath = new SignedStateFilePath(context.getConfiguration().getConfigData(StateConfig.class)); } private SignedStateMetrics buildMockMetrics() { @@ -120,8 +131,8 @@ private SignedStateMetrics buildMockMetrics() { */ private void validateSavingOfState(final SignedState originalState) throws IOException { - final Path stateDirectory = - getSignedStateDirectory(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, originalState.getRound()); + final Path stateDirectory = signedStateFilePath.getSignedStateDirectory( + MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, originalState.getRound()); validateSavingOfState(originalState, stateDirectory); } @@ -167,20 +178,12 @@ private void validateSavingOfState(final SignedState originalState, final Path s @ValueSource(booleans = {true, false}) @DisplayName("Standard Operation Test") void standardOperationTest(final boolean successExpected) throws IOException { - final TestConfigBuilder configBuilder = new TestConfigBuilder() - .withValue( - StateConfig_.SAVED_STATE_DIRECTORY, - testDirectory.toFile().toString()); - final PlatformContext context = TestPlatformContextBuilder.create() - .withConfiguration(configBuilder.getOrCreateConfig()) - .build(); - final SignedState signedState = new RandomSignedStateGenerator().build(); if (!successExpected) { // To make the save fail, create a file with the name of the directory the state will try to be saved to - final Path savedDir = - getSignedStateDirectory(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, signedState.getRound()); + final Path savedDir = signedStateFilePath.getSignedStateDirectory( + MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME, signedState.getRound()); Files.createDirectories(savedDir.getParent()); Files.createFile(savedDir); } @@ -201,14 +204,6 @@ void standardOperationTest(final boolean successExpected) throws IOException { @Test @DisplayName("Save Fatal Signed State") void saveFatalSignedState() throws InterruptedException, IOException { - final TestConfigBuilder configBuilder = new TestConfigBuilder() - .withValue( - StateConfig_.SAVED_STATE_DIRECTORY, - testDirectory.toFile().toString()); - final PlatformContext context = TestPlatformContextBuilder.create() - .withConfiguration(configBuilder.getOrCreateConfig()) - .build(); - final SignedState signedState = new RandomSignedStateGenerator().build(); ((DummySwirldState) signedState.getSwirldState()).enableBlockingSerialization(); @@ -236,14 +231,6 @@ void saveFatalSignedState() throws InterruptedException, IOException { @Test @DisplayName("Save ISS Signed State") void saveISSignedState() throws IOException { - final TestConfigBuilder configBuilder = new TestConfigBuilder() - .withValue( - StateConfig_.SAVED_STATE_DIRECTORY, - testDirectory.toFile().toString()); - final PlatformContext context = TestPlatformContextBuilder.create() - .withConfiguration(configBuilder.getOrCreateConfig()) - .build(); - final SignedState signedState = new RandomSignedStateGenerator().build(); final SignedStateFileManager manager = new SignedStateFileManager( @@ -408,12 +395,14 @@ void stateDeletionTest() throws IOException { context, buildMockMetrics(), new FakeTime(), MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); final Path statesDirectory = - SignedStateFileUtils.getSignedStatesDirectoryForSwirld(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); + signedStateFilePath.getSignedStatesDirectoryForSwirld(MAIN_CLASS_NAME, SELF_ID, SWIRLD_NAME); // Simulate the saving of an ISS state final int issRound = 666; - final Path issDirectory = - getSignedStatesBaseDirectory().resolve("iss").resolve("node" + SELF_ID + "_round" + issRound); + final Path issDirectory = signedStateFilePath + .getSignedStatesBaseDirectory() + .resolve("iss") + .resolve("node" + SELF_ID + "_round" + issRound); final SignedState issState = new RandomSignedStateGenerator(random).setRound(issRound).build(); issState.markAsStateToSave(ISS); @@ -422,8 +411,10 @@ void stateDeletionTest() throws IOException { // Simulate the saving of a fatal state final int fatalRound = 667; - final Path fatalDirectory = - getSignedStatesBaseDirectory().resolve("fatal").resolve("node" + SELF_ID + "_round" + fatalRound); + final Path fatalDirectory = signedStateFilePath + .getSignedStatesBaseDirectory() + .resolve("fatal") + .resolve("node" + SELF_ID + "_round" + fatalRound); final SignedState fatalState = new RandomSignedStateGenerator(random).setRound(fatalRound).build(); fatalState.markAsStateToSave(FATAL_ERROR); @@ -449,8 +440,10 @@ void stateDeletionTest() throws IOException { } // Verify that old states are properly deleted - assertEquals(Math.min(statesOnDisk, round), (int) - Files.list(statesDirectory).count()); + assertEquals( + Math.min(statesOnDisk, round), + (int) Files.list(statesDirectory).count(), + "unexpected number of states on disk after saving round " + round); // ISS/fatal state should still be in place validateSavingOfState(issState, issDirectory); 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 85fc7fed295e..279a6d07c785 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 @@ -67,10 +67,6 @@ @DisplayName("SignedState Read/Write Test") class SignedStateFileReadWriteTest { - - private static final NodeId SELF_ID = new NodeId(1234); - private static final String MAIN_CLASS_NAME = "com.swirlds.foobar"; - private static final String SWIRLD_NAME = "mySwirld"; /** * Temporary directory provided by JUnit */ diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/state/StateManagementComponentTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/state/StateManagementComponentTests.java index a47786b497bf..5c546b8c81f3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/state/StateManagementComponentTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/state/StateManagementComponentTests.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import com.swirlds.common.config.StateConfig_; @@ -40,7 +39,6 @@ import com.swirlds.platform.dispatch.DispatchBuilder; import com.swirlds.platform.dispatch.DispatchConfiguration; import com.swirlds.platform.state.RandomSignedStateGenerator; -import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SourceOfSignedState; import com.swirlds.platform.system.address.AddressBook; @@ -125,76 +123,6 @@ void newStateFromTransactionsSubmitsSystemTransaction() { component.stop(); } - /** - * Verify that when the component is provided a complete signed state to load, it is returned when asked for the - * latest complete signed state. - */ - @Test - @DisplayName("Signed state to load becomes the latest complete signed state") - void signedStateToLoadIsLatestComplete() { - final Random random = RandomUtils.getRandomPrintSeed(); - final DefaultStateManagementComponent component = newStateManagementComponent(); - - component.start(); - - final int firstRound = 1; - final int lastRound = 100; - - // Send a bunch of signed states for the component to load, in order - for (int roundNum = firstRound; roundNum <= lastRound; roundNum++) { - final SignedState signedState = - new RandomSignedStateGenerator(random).setRound(roundNum).build(); - - final SignedState signedStateSpy = spy(signedState); - when(signedStateSpy.isComplete()).thenReturn(true); - - component.stateToLoad(signedStateSpy, SourceOfSignedState.DISK); - - // Some basic assertions on the signed state provided to the new latest complete state consumer - verifyNewLatestCompleteStateConsumer(roundNum, signedStateSpy); - - verifyLatestCompleteState(signedStateSpy, component); - } - - // Send a bunch of signed states that are older than the latest complete signed state - for (int roundNum = firstRound; roundNum < lastRound; roundNum++) { - final SignedState signedState = - new RandomSignedStateGenerator(random).setRound(roundNum).build(); - - final SignedState signedStateSpy = spy(signedState); - when(signedStateSpy.isComplete()).thenReturn(true); - - component.stateToLoad(signedStateSpy, SourceOfSignedState.DISK); - - // The signed state provided is old, so the consumer should not be invoked again - assertEquals( - lastRound, - newLatestCompleteStateConsumer.getNumInvocations(), - "The new latest complete state consumer should not be invoked for states that are older than the " - + "current latest complete state"); - - // The latest complete signed state should still be the same as before and not the one just provided - verifyLatestCompleteState(newLatestCompleteStateConsumer.getLastSignedState(), component); - } - - component.stop(); - } - - private void verifyLatestCompleteState( - final SignedState expectedSignedState, final StateManagementComponent component) { - // Check that the correct signed state is provided when the latest complete state is requested - try (final ReservedSignedState wrapper = component.getLatestSignedState("test")) { - assertEquals(expectedSignedState, wrapper.get(), "Incorrect latest signed state provided"); - - // 1 for being the latest complete signed state - // 1 for being the latest signed state - // 1 for the AutoCloseableWrapper - assertEquals(3, wrapper.get().getReservationCount(), "Incorrect number of reservations"); - } - assertEquals( - expectedSignedState.getRound(), component.getLastCompleteRound(), "Incorrect latest complete round"); - } - private void verifyNewLatestCompleteStateConsumer(final int roundNum, final SignedState signedState) { final SignedState lastCompleteSignedState = newLatestCompleteStateConsumer.getLastSignedState(); assertEquals( @@ -240,10 +168,7 @@ void stateSignaturesAppliedAndTracked() { // This state should be sent out as the latest complete state final int finalRoundNum = roundNum; AssertionUtils.assertEventuallyDoesNotThrow( - () -> { - verifyNewLatestCompleteStateConsumer(finalRoundNum / 2, signedState); - verifyLatestCompleteState(signedState, component); - }, + () -> verifyNewLatestCompleteStateConsumer(finalRoundNum / 2, signedState), Duration.ofSeconds(2), "The unit test failed."); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java index 99728339eb18..9897985b24ce 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; +import com.swirlds.common.config.StateConfig; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; @@ -35,7 +36,7 @@ import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.fixtures.state.DummySwirldState; -import com.swirlds.test.framework.context.TestPlatformContextBuilder; +import com.swirlds.test.framework.config.TestConfigBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.ArrayList; @@ -181,7 +182,11 @@ public SignedState build() { consensusTimestampInstance)); final SignedState signedState = new SignedState( - TestPlatformContextBuilder.create().build(), + new TestConfigBuilder() + .withValue("state.stateHistoryEnabled", true) + .withConfigDataType(StateConfig.class) + .getOrCreateConfig() + .getConfigData(StateConfig.class), stateInstance, "RandomSignedStateGenerator.build()", freezeStateInstance); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateManagerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateManagerTester.java new file mode 100644 index 000000000000..0cb360bfbc5f --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateManagerTester.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state; + +import com.swirlds.common.config.StateConfig; +import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.platform.components.state.output.NewLatestCompleteStateConsumer; +import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; +import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; +import com.swirlds.platform.state.nexus.LatestCompleteStateNexus; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.platform.state.signed.SignedStateManager; +import com.swirlds.platform.state.signed.SignedStateMetrics; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * A SignedStateManager that is used for unit testing. Since the SignedStateManager is in the process of being broken up + * into smaller components, this class is a temporary solution to allow unit tests function. In the future, these unit + * tests should become small integration tests that test multiple components. + */ +public class SignedStateManagerTester extends SignedStateManager { + private final LatestCompleteStateNexus latestSignedState; + + private SignedStateManagerTester( + @NonNull final StateConfig stateConfig, + @NonNull final SignedStateMetrics signedStateMetrics, + @NonNull final NewLatestCompleteStateConsumer newLatestCompleteStateConsumer, + @NonNull final StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer, + @NonNull final StateLacksSignaturesConsumer stateLacksSignaturesConsumer, + @NonNull final LatestCompleteStateNexus latestSignedState) { + super( + stateConfig, + signedStateMetrics, + newLatestCompleteStateConsumer, + stateHasEnoughSignaturesConsumer, + stateLacksSignaturesConsumer); + this.latestSignedState = latestSignedState; + } + + public static SignedStateManagerTester create( + @NonNull final StateConfig stateConfig, + @NonNull final SignedStateMetrics signedStateMetrics, + @NonNull final NewLatestCompleteStateConsumer newLatestCompleteStateConsumer, + @NonNull final StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer, + @NonNull final StateLacksSignaturesConsumer stateLacksSignaturesConsumer) { + final LatestCompleteStateNexus latestSignedState = new LatestCompleteStateNexus(stateConfig, new NoOpMetrics()); + return new SignedStateManagerTester( + stateConfig, + signedStateMetrics, + s -> { + newLatestCompleteStateConsumer.newLatestCompleteStateEvent(s); + latestSignedState.setState(s.reserve("LatestCompleteStateNexus.setState")); + }, + stateHasEnoughSignaturesConsumer, + stateLacksSignaturesConsumer, + latestSignedState); + } + + @Override + public synchronized void addState(@NonNull final SignedState signedState) { + super.addState(signedState); + latestSignedState.newIncompleteState(signedState.getRound()); + } + + /** + * Get the last complete signed state + * + * @param reason a short description of why this SignedState is being reserved. Each location where a SignedState is + * reserved should attempt to use a unique reason, as this makes debugging reservation bugs easier. + * @return the latest complete signed state, or a null if there are no recent states that are complete + */ + @Nullable + public ReservedSignedState getLatestSignedState(@NonNull final String reason) { + return latestSignedState.getState(reason); + } + + public long getLastCompleteRound() { + return latestSignedState.getRound(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractSignedStateManagerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractSignedStateManagerTest.java index eacc98d00927..96af3dd2c971 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractSignedStateManagerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractSignedStateManagerTest.java @@ -56,6 +56,7 @@ public class AbstractSignedStateManagerTest { protected final Map signedStates = new ConcurrentHashMap<>(); protected final AtomicLong highestRound = new AtomicLong(-1); + protected final AtomicLong highestCompleteRound = new AtomicLong(-1); protected final int roundsToKeepForSigning = 5; protected final int futureStateSignatureRounds = 16; protected int roundsToKeepAfterSigning = 0; @@ -135,12 +136,28 @@ protected void validateReservationCounts(final Predicate shouldRoundBePres if (shouldRoundBePresent.test(round)) { assertEquals(-1, signedState.getReservationCount(), "state should have no reservations"); } else { + int expectedReservationCount = 1; if (round == highestRound.get()) { - // the most recent state has an extra reservation - assertEquals(2, signedState.getReservationCount(), "unexpected reservation count"); - } else { - assertEquals(1, signedState.getReservationCount(), "unexpected reservation count"); + // the most recent state has an extra reservation inside the SSM + expectedReservationCount++; } + if (round == highestCompleteRound.get()) { + // the most recent complete state has an extra reservation held by the nexus + expectedReservationCount++; + } + assertEquals( + expectedReservationCount, + signedState.getReservationCount(), + ("unexpected reservation count!%n" + + "round: %d%n" + + "highestRound: %d%n" + + "highestCompleteRound: %d%n" + + "history:%n%s") + .formatted( + round, + highestRound.get(), + highestCompleteRound.get(), + signedState.getHistory())); } } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java index e235a9303c0a..38d50f9b5596 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java @@ -30,9 +30,9 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import java.util.HashMap; @@ -75,6 +75,7 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { return ss -> { assertEquals(highestRound.get() - roundAgeToSign, ss.getRound(), "unexpected round completed"); + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); stateHasEnoughSignaturesCount.getAndIncrement(); }; } @@ -83,7 +84,7 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Add Incomplete State Test") void addIncompleteStateTest() { - SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()) + SignedStateManagerTester manager = new SignedStateManagerBuilder(buildStateConfig()) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); @@ -117,7 +118,7 @@ void addIncompleteStateTest() { assertEquals( stateFromDisk.getState().getPlatformState().getPlatformData().getRound(), manager.getFirstStateRound()); - assertNull(manager.getLatestSignedState("test").getNullable()); + assertNull(manager.getLatestSignedState("test")); assertEquals(-1, manager.getLastCompleteRound()); try (final ReservedSignedState wrapper = diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java index ccc898aa8ec8..3bbc054d05d3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java @@ -27,9 +27,9 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.transaction.StateSignatureTransaction; import java.time.Instant; @@ -70,7 +70,10 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { * This consumer is provided by the wiring layer, so it should release the resource when finished. */ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { - return ss -> stateHasEnoughSignaturesCount.getAndIncrement(); + return ss -> { + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); + stateHasEnoughSignaturesCount.getAndIncrement(); + }; } @Test @@ -79,7 +82,7 @@ void earlySignaturesTest() throws InterruptedException { final int count = 100; final StateConfig stateConfig = buildStateConfig(); final int futureSignatures = stateConfig.maxAgeOfFutureStateSignatures(); - final SignedStateManager manager = new SignedStateManagerBuilder(stateConfig) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(stateConfig) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); @@ -173,10 +176,11 @@ void earlySignaturesTest() throws InterruptedException { lastExpectedCompletedRound = Math.max(lastExpectedCompletedRound, roundToSign); } - try (final ReservedSignedState lastState = manager.getLatestImmutableState("test")) { + try (final ReservedSignedState lastState = manager.getLatestImmutableState("test get lastState")) { assertSame(signedState, lastState.get(), "last signed state has unexpected value"); } - try (final ReservedSignedState lastCompletedState = manager.getLatestSignedState("test")) { + try (final ReservedSignedState lastCompletedState = + manager.getLatestSignedState("test get lastCompletedState")) { assertSame( signedStates.get(lastExpectedCompletedRound), lastCompletedState.get(), diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EmergencyStateFinderTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EmergencyStateFinderTests.java deleted file mode 100644 index 9f0aff37b3ca..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EmergencyStateFinderTests.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state.manager; - -import static com.swirlds.platform.reconnect.emergency.EmergencyReconnectTeacher.emergencyStateCriteria; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; -import com.swirlds.common.test.fixtures.RandomUtils; -import com.swirlds.platform.state.RandomSignedStateGenerator; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; -import com.swirlds.platform.system.address.AddressBook; -import java.util.HashMap; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -@DisplayName("SignedStateManager: Emergency State Finder") -public class EmergencyStateFinderTests extends AbstractSignedStateManagerTest { - - private final AddressBook addressBook = new RandomAddressBookGenerator(random) - .setSize(4) - .setWeightDistributionStrategy(RandomAddressBookGenerator.WeightDistributionStrategy.BALANCED) - .build(); - - @DisplayName("Emergency State Finder Test") - @Test - void testFind() { - final SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()).build(); - - final int roundAgeToSign = 3; - - // Create a series of signed states, none of which are ancient - for (int round = 0; round < roundsToKeepForSigning - 1; round++) { - final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) - .setRound(round) - .setSignatures(new HashMap<>()) - .build(); - - signedStates.put((long) round, signedState); - highestRound.set(round); - - manager.addState(signedState); - - // Add some signatures to one of the previous states - final long roundToSign = round - roundAgeToSign; - if (roundToSign >= 0) { - addSignature(manager, roundToSign, addressBook.getNodeId(1)); - addSignature(manager, roundToSign, addressBook.getNodeId(2)); - addSignature(manager, roundToSign, addressBook.getNodeId(3)); - } - } - - validateReservationCounts(round -> round < signedStates.size() - roundAgeToSign - 1); - - try (final ReservedSignedState lastCompleteWrapper = manager.getLatestSignedState("test")) { - final SignedState lastComplete = lastCompleteWrapper.get(); - - // Search for a round and hash that match the last complete state exactly - try (final ReservedSignedState actualWrapper = manager.find( - emergencyStateCriteria( - lastComplete.getRound(), lastComplete.getState().getHash()), - "test")) { - final SignedState actual = actualWrapper.get(); - // the last complete state should always have 3 reservations - // 1 for the reservation - // 1 for the lastCompleteWrapper AutoCloseableWrapper - // 1 for the actualWrapper AutoCloseableWrapper - verifyFoundSignedState( - lastComplete, - actual, - 3, - "Requesting the last complete round should return the last complete round"); - } - - // Search for a round earlier than the last complete state - try (final ReservedSignedState actualWrapper = manager.find( - emergencyStateCriteria(lastComplete.getRound() - 1, RandomUtils.randomHash(random)), "test")) { - - final SignedState actual = actualWrapper.get(); - verifyFoundSignedState( - lastComplete, - actual, - 3, - "Requesting a round earlier than the last complete round should return the last complete " - + "round"); - } - } - - try (final ReservedSignedState lastWrapper = manager.getLatestImmutableState("test")) { - final SignedState last = lastWrapper.get(); - - // Search for a round and hash that match the last state exactly - try (final ReservedSignedState actualWrapper = manager.find( - emergencyStateCriteria(last.getRound(), last.getState().getHash()), "test")) { - final SignedState actual = actualWrapper.get(); - // the last state should have 4 reservations: - // 2 for being the last state held by the manager - // 1 for the lastWrapper AutoCloseableWrapper - // 1 for the actualWrapper AutoCloseableWrapper - verifyFoundSignedState(last, actual, 4, "Requesting the last round should return the last round"); - } - - for (long i = manager.getLastCompleteRound() + 1; i <= last.getRound(); i++) { - // Search for a round later than the last complete round with a hash that doesn't match any state - try (final ReservedSignedState actualWrapper = - manager.find(emergencyStateCriteria(i, RandomUtils.randomHash(random)), "test")) { - final SignedState actual = actualWrapper.getNullable(); - assertNull( - actual, - "Requesting a round later than the last complete " - + "round with an unknown hash should return null"); - } - } - } - } - - private void verifyFoundSignedState( - final SignedState lastComplete, - final SignedState actual, - final int numReservations, - final String wrongStateMsg) { - - assertEquals(lastComplete, actual, wrongStateMsg); - - assertEquals(numReservations, actual.getReservationCount(), "Incorrect number of reservations"); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java index 1fa5bed3c081..30b0ac8d76f6 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/OldCompleteStateEventuallyReleasedTest.java @@ -26,11 +26,12 @@ import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; +import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import java.util.HashMap; @@ -59,6 +60,15 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { return ss -> stateLacksSignaturesCount.getAndIncrement(); } + /** + * Called on each state as it gathers enough signatures to be complete. + *

+ * This consumer is provided by the wiring layer, so it should release the resource when finished. + */ + private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { + return ss -> highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); + } + /** * Keep adding new states to the manager but never sign any of them (other than self signatures). */ @@ -66,8 +76,9 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { @DisplayName("Old Complete State Eventually Released") void oldCompleteStateEventuallyReleased() throws InterruptedException { - final SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(buildStateConfig()) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) + .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); final Hash stateHash = randomHash(random); @@ -108,7 +119,7 @@ void oldCompleteStateEventuallyReleased() throws InterruptedException { try (final ReservedSignedState lastCompletedState = manager.getLatestSignedState("test")) { if (round >= roundsToKeepForSigning) { - assertNull(lastCompletedState.getNullable(), "initial state should have been released"); + assertNull(lastCompletedState, "initial state should have been released"); } else { assertSame(lastCompletedState.get(), stateFromDisk); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java index f62658c0bbe2..68ebf149d2cf 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/PostconsensusSignaturesTest.java @@ -25,6 +25,7 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateManager; @@ -60,7 +61,10 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { * Called on each state as it gathers enough signatures to be complete. */ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { - return ss -> stateHasEnoughSignaturesCount.getAndIncrement(); + return ss -> { + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); + stateHasEnoughSignaturesCount.getAndIncrement(); + }; } @Test @@ -69,7 +73,7 @@ void postconsensusSignatureTests() throws InterruptedException { final int count = 100; final StateConfig stateConfig = buildStateConfig(); - final SignedStateManager manager = new SignedStateManagerBuilder(stateConfig) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(stateConfig) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java index e0b246942a94..aa01f2f82df9 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java @@ -25,9 +25,9 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.AddressBook; import java.time.Instant; import java.util.HashMap; @@ -66,7 +66,10 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { * This consumer is provided by the wiring layer, so it should release the resource when finished. */ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { - return ss -> stateHasEnoughSignaturesCount.getAndIncrement(); + return ss -> { + stateHasEnoughSignaturesCount.getAndIncrement(); + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); + }; } /** @@ -75,7 +78,7 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @Test @DisplayName("Register States Without Signatures") void registerStatesWithoutSignatures() throws InterruptedException { - final SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(buildStateConfig()) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); @@ -113,7 +116,7 @@ void registerStatesWithoutSignatures() throws InterruptedException { assertSame(signedState, lastState.get(), "last signed state has unexpected value"); } try (final ReservedSignedState lastCompletedState = manager.getLatestSignedState("test")) { - assertNull(lastCompletedState.getNullable(), "no states should be completed in this test"); + assertNull(lastCompletedState, "no states should be completed in this test"); } final int expectedUnsignedStates = Math.max(0, round - roundsToKeepForSigning + 1); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java index 5f15fbbdcb9a..cb8b9bbeb83d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesRestartTest.java @@ -29,9 +29,9 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import java.util.HashMap; @@ -74,6 +74,7 @@ private StateLacksSignaturesConsumer stateLacksSignaturesConsumer() { private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { return ss -> { assertEquals(highestRound.get() - roundAgeToSign, ss.getRound(), "unexpected round completed"); + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); stateHasEnoughSignaturesCount.getAndIncrement(); }; } @@ -82,7 +83,7 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Sequential Signatures After Restart Test") void sequentialSignaturesAfterRestartTest() throws InterruptedException { - final SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(buildStateConfig()) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java index b57998bd8259..d3c8b681982d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java @@ -28,9 +28,9 @@ import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; import com.swirlds.platform.state.RandomSignedStateGenerator; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.system.address.AddressBook; import java.time.Instant; import java.util.HashMap; @@ -71,6 +71,7 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { return ss -> { assertEquals(highestRound.get() - roundAgeToSign, ss.getRound(), "unexpected round completed"); stateHasEnoughSignaturesCount.getAndIncrement(); + highestCompleteRound.accumulateAndGet(ss.getRound(), Math::max); }; } @@ -78,7 +79,7 @@ private StateHasEnoughSignaturesConsumer stateHasEnoughSignaturesConsumer() { @DisplayName("Sequential Signatures Test") void sequentialSignaturesTest() throws InterruptedException { this.roundsToKeepAfterSigning = 4; - final SignedStateManager manager = new SignedStateManagerBuilder(buildStateConfig()) + final SignedStateManagerTester manager = new SignedStateManagerBuilder(buildStateConfig()) .stateLacksSignaturesConsumer(stateLacksSignaturesConsumer()) .stateHasEnoughSignaturesConsumer(stateHasEnoughSignaturesConsumer()) .build(); @@ -130,7 +131,7 @@ void sequentialSignaturesTest() throws InterruptedException { assertSame( signedStates.get(roundToSign), lastCompletedState.get(), "unexpected last completed state"); } else { - assertNull(lastCompletedState.getNullable(), "no states should be completed yet"); + assertNull(lastCompletedState, "no states should be completed yet"); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignedStateManagerBuilder.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignedStateManagerBuilder.java index 43442ff42f36..9ea2fb917322 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignedStateManagerBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SignedStateManagerBuilder.java @@ -21,6 +21,7 @@ import com.swirlds.platform.components.state.output.NewLatestCompleteStateConsumer; import com.swirlds.platform.components.state.output.StateHasEnoughSignaturesConsumer; import com.swirlds.platform.components.state.output.StateLacksSignaturesConsumer; +import com.swirlds.platform.state.SignedStateManagerTester; import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.state.signed.SignedStateMetrics; @@ -51,8 +52,8 @@ public SignedStateManagerBuilder stateLacksSignaturesConsumer(final StateLacksSi return this; } - public SignedStateManager build() { - return new SignedStateManager( + public SignedStateManagerTester build() { + return SignedStateManagerTester.create( stateConfig, metrics, newLatestCompleteStateConsumer, 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 0c14e979c7b1..06e174a6a36c 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 @@ -18,7 +18,6 @@ import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.common.test.fixtures.RandomUtils.randomHash; -import static com.swirlds.platform.state.signed.SignedStateFileUtils.getSignedStateDirectory; import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateToDisk; import static com.swirlds.platform.state.signed.StartupStateUtils.doRecoveryCleanup; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -82,6 +81,8 @@ class StartupStateUtilsTests { @TempDir Path testDirectory; + private SignedStateFilePath signedStateFilePath; + private final NodeId selfId = new NodeId(0); private final String mainClassName = "mainClassName"; private final String swirldName = "swirldName"; @@ -89,6 +90,10 @@ class StartupStateUtilsTests { @BeforeEach void beforeEach() throws IOException { FileUtils.deleteDirectory(testDirectory); + signedStateFilePath = new SignedStateFilePath(new TestConfigBuilder() + .withValue("state.savedStateDirectory", testDirectory.toString()) + .getOrCreateConfig() + .getConfigData(StateConfig.class)); } @AfterEach @@ -134,7 +139,8 @@ private SignedState writeState( .setEpoch(epoch) .build(); - final Path savedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, round); + final Path savedStateDirectory = + signedStateFilePath.getSignedStateDirectory(mainClassName, selfId, swirldName, round); writeSignedStateToDisk( platformContext, selfId, savedStateDirectory, signedState, StateToDiskReason.PERIODIC_SNAPSHOT); @@ -280,7 +286,8 @@ void corruptedStateRecyclingPermittedTest(final int invalidStateCount) assertNull(loadedState); } - final Path savedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) + final Path savedStateDirectory = signedStateFilePath + .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); assertEquals(5 - invalidStateCount, Files.list(savedStateDirectory).count()); @@ -704,7 +711,8 @@ void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) assertNull(loadedState); } - final Path savedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) + final Path savedStateDirectory = signedStateFilePath + .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); assertEquals(5 - invalidStateCount, Files.list(savedStateDirectory).count()); @@ -754,7 +762,8 @@ void doRecoveryCleanupInitialEpochTest() throws IOException { doRecoveryCleanup(platformContext, recycleBin, selfId, swirldName, mainClassName, null, latestRound); - final Path signedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) + final Path signedStateDirectory = signedStateFilePath + .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); assertEquals(0, recycleCount.get()); @@ -811,7 +820,8 @@ void doRecoveryCleanupAlreadyCleanedUpTest() throws IOException { doRecoveryCleanup(platformContext, recycleBin, selfId, swirldName, mainClassName, epoch, latestRound); - final Path signedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) + final Path signedStateDirectory = signedStateFilePath + .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); assertEquals(0, recycleCount.get()); @@ -881,7 +891,8 @@ void doRecoveryCleanupWorkRequiredTest(final int statesToDelete) throws IOExcept assertEquals(epoch, scratchpad.get(RecoveryScratchpad.EPOCH_HASH)); - final Path signedStateDirectory = getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) + final Path signedStateDirectory = signedStateFilePath + .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); assertEquals(statesToDelete, recycleCount.get());