diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index 38c9f478524..ae9bd256937 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -97,6 +97,12 @@ protected void setupHandlers() { bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler")); bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported", lastVersion, Version.VERSION)); + bisqSetup.setDaoRequiresRestartHandler(() -> { + log.info("There was a problem with synchronizing the DAO state. " + + "A restart of the application is required to fix the issue."); + gracefulShutDownHandler.gracefulShutDown(() -> { + }); + }); corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files)); tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler")); diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 7b4a634f826..9b2d2300303 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -182,6 +182,9 @@ default void onRequestWalletPassword() { private Runnable qubesOSInfoHandler; @Setter @Nullable + private Runnable daoRequiresRestartHandler; + @Setter + @Nullable private Consumer downGradePreventionHandler; @Getter @@ -443,7 +446,8 @@ private void initDomainServices() { daoWarnMessageHandler, filterWarningHandler, voteResultExceptionHandler, - revolutAccountsUpdateHandler); + revolutAccountsUpdateHandler, + daoRequiresRestartHandler); if (walletsSetup.downloadPercentageProperty().get() == 1) { checkForLockedUpFunds(); diff --git a/core/src/main/java/bisq/core/app/DomainInitialisation.java b/core/src/main/java/bisq/core/app/DomainInitialisation.java index dcbd8491021..754ba3f187d 100644 --- a/core/src/main/java/bisq/core/app/DomainInitialisation.java +++ b/core/src/main/java/bisq/core/app/DomainInitialisation.java @@ -25,6 +25,7 @@ import bisq.core.dao.DaoSetup; import bisq.core.dao.governance.voteresult.VoteResultException; import bisq.core.dao.governance.voteresult.VoteResultService; +import bisq.core.dao.state.DaoStateSnapshotService; import bisq.core.filter.FilterManager; import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.alerts.DisputeMsgEvents; @@ -104,6 +105,7 @@ public class DomainInitialisation { private final PriceAlert priceAlert; private final MarketAlerts marketAlerts; private final User user; + private final DaoStateSnapshotService daoStateSnapshotService; @Inject public DomainInitialisation(ClockWatcher clockWatcher, @@ -138,7 +140,8 @@ public DomainInitialisation(ClockWatcher clockWatcher, DisputeMsgEvents disputeMsgEvents, PriceAlert priceAlert, MarketAlerts marketAlerts, - User user) { + User user, + DaoStateSnapshotService daoStateSnapshotService) { this.clockWatcher = clockWatcher; this.tradeLimits = tradeLimits; this.arbitrationManager = arbitrationManager; @@ -172,6 +175,7 @@ public DomainInitialisation(ClockWatcher clockWatcher, this.priceAlert = priceAlert; this.marketAlerts = marketAlerts; this.user = user; + this.daoStateSnapshotService = daoStateSnapshotService; } public void initDomainServices(Consumer rejectedTxErrorMessageHandler, @@ -180,7 +184,8 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, Consumer daoWarnMessageHandler, Consumer filterWarningHandler, Consumer voteResultExceptionHandler, - Consumer> revolutAccountsUpdateHandler) { + Consumer> revolutAccountsUpdateHandler, + Runnable daoRequiresRestartHandler) { clockWatcher.start(); tradeLimits.onAllServicesInitialized(); @@ -222,6 +227,8 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, if (daoWarnMessageHandler != null) daoWarnMessageHandler.accept(warningMessage); }); + + daoStateSnapshotService.setDaoRequiresRestartHandler(daoRequiresRestartHandler); } tradeStatisticsManager.onAllServicesInitialized(); diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java index 0c0e506016e..1752119b269 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java @@ -17,21 +17,29 @@ package bisq.core.dao.state; -import bisq.core.dao.governance.period.CycleService; import bisq.core.dao.monitoring.DaoStateMonitoringService; import bisq.core.dao.monitoring.model.DaoStateHash; import bisq.core.dao.state.model.DaoState; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.storage.DaoStateStorageService; +import bisq.common.config.Config; + import javax.inject.Inject; +import javax.inject.Named; import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.io.IOException; + import java.util.LinkedList; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + /** * Manages periodical snapshots of the DaoState. * At startup we apply a snapshot if available. @@ -45,13 +53,16 @@ public class DaoStateSnapshotService { private final DaoStateService daoStateService; private final GenesisTxInfo genesisTxInfo; - private final CycleService cycleService; private final DaoStateStorageService daoStateStorageService; private final DaoStateMonitoringService daoStateMonitoringService; + private final File storageDir; private DaoState daoStateSnapshotCandidate; private LinkedList daoStateHashChainSnapshotCandidate = new LinkedList<>(); private int chainHeightOfLastApplySnapshot; + @Setter + @Nullable + private Runnable daoRequiresRestartHandler; /////////////////////////////////////////////////////////////////////////////////////////// @@ -61,14 +72,14 @@ public class DaoStateSnapshotService { @Inject public DaoStateSnapshotService(DaoStateService daoStateService, GenesisTxInfo genesisTxInfo, - CycleService cycleService, DaoStateStorageService daoStateStorageService, - DaoStateMonitoringService daoStateMonitoringService) { + DaoStateMonitoringService daoStateMonitoringService, + @Named(Config.STORAGE_DIR) File storageDir) { this.daoStateService = daoStateService; this.genesisTxInfo = genesisTxInfo; - this.cycleService = cycleService; this.daoStateStorageService = daoStateStorageService; this.daoStateMonitoringService = daoStateMonitoringService; + this.storageDir = storageDir; } @@ -128,15 +139,19 @@ public void applySnapshot(boolean fromReorg) { } else { // The reorg might have been caused by the previous parsing which might contains a range of // blocks. - log.warn("We applied already a snapshot with chainHeight {}. We will reset the daoState and " + - "start over from the genesis transaction again.", chainHeightOfLastApplySnapshot); - applyEmptySnapshot(); + log.warn("We applied already a snapshot with chainHeight {}. " + + "We remove all dao store files and shutdown. After a restart resource files will " + + "be applied if available.", + chainHeightOfLastApplySnapshot); + resyncDaoStateFromResources(); } } } else if (fromReorg) { - log.info("We got a reorg and we want to apply the snapshot but it is empty. That is expected in the first blocks until the " + - "first snapshot has been created. We use our applySnapshot method and restart from the genesis tx"); - applyEmptySnapshot(); + log.info("We got a reorg and we want to apply the snapshot but it is empty. " + + "That is expected in the first blocks until the first snapshot has been created. " + + "We remove all dao store files and shutdown. " + + "After a restart resource files will be applied if available."); + resyncDaoStateFromResources(); } } else { log.info("Try to apply snapshot but no stored snapshot available. That is expected at first blocks."); @@ -152,16 +167,17 @@ private boolean isValidHeight(int heightOfLastBlock) { return heightOfLastBlock >= genesisTxInfo.getGenesisBlockHeight(); } - private void applyEmptySnapshot() { - DaoState emptyDaoState = new DaoState(); - int genesisBlockHeight = genesisTxInfo.getGenesisBlockHeight(); - emptyDaoState.setChainHeight(genesisBlockHeight); - chainHeightOfLastApplySnapshot = genesisBlockHeight; - daoStateService.applySnapshot(emptyDaoState); - // In case we apply an empty snapshot we need to trigger the cycleService.addFirstCycle method - cycleService.addFirstCycle(); + private void resyncDaoStateFromResources() { + log.info("resyncDaoStateFromResources called"); + try { + daoStateStorageService.resyncDaoStateFromResources(storageDir); - daoStateMonitoringService.applySnapshot(new LinkedList<>()); + if (daoRequiresRestartHandler != null) { + daoRequiresRestartHandler.run(); + } + } catch (IOException e) { + log.error("Error at resyncDaoStateFromResources: {}", e.toString()); + } } @VisibleForTesting diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index fdda850d637..cc475013188 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2850,6 +2850,7 @@ popup.info.shutDownWithOpenOffers=Bisq is being shut down, but there are open of popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\ Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes]. popup.warn.downGradePrevention=Downgrade from version {0} to version {1} is not supported. Please use the latest Bisq version. +popup.warn.daoRequiresRestart=There was a problem with synchronizing the DAO state. You have to restart the application to fix the issue. popup.privateNotification.headline=Important private notification! diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index bf81fff2dc5..e5c170f5419 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -417,6 +417,11 @@ private void setupHandlers() { .show(); }); + bisqSetup.setDaoRequiresRestartHandler(() -> new Popup().warning("popup.warn.daoRequiresRestart") + .useShutDownButton() + .hideCloseButton() + .show()); + corruptedStorageFileHandler.getFiles().ifPresent(files -> new Popup() .warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir)) .useShutDownButton() diff --git a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java index 5a0fca15af5..7644b40f397 100644 --- a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java @@ -20,6 +20,7 @@ import bisq.core.app.TorSetup; import bisq.core.app.misc.ExecutableForAppWithP2p; import bisq.core.app.misc.ModuleForAppWithP2p; +import bisq.core.dao.state.DaoStateSnapshotService; import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PServiceListener; @@ -30,6 +31,7 @@ import bisq.common.app.AppModule; import bisq.common.app.Capabilities; import bisq.common.app.Capability; +import bisq.common.app.DevEnv; import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; import bisq.common.handlers.ResultHandler; @@ -98,6 +100,11 @@ protected void applyInjector() { super.applyInjector(); seedNode.setInjector(injector); + + if (DevEnv.isDaoActivated()) { + injector.getInstance(DaoStateSnapshotService.class).setDaoRequiresRestartHandler(() -> gracefulShutDown(() -> { + })); + } } @Override