From d4729751e8a38afcce3188690f8d4e8054907635 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sun, 2 Dec 2018 22:31:38 +0100 Subject: [PATCH] Fix handling of reorgs - apply snapshot in case of a reorg and if the snapshot is still empty. Set chain height to genesis height. - Handle case if cycles are empty - Add checks for getLast for linked lists --- .../dao/governance/myvote/MyVoteListService.java | 1 + .../core/dao/governance/period/CycleService.java | 4 ++-- .../core/dao/governance/period/PeriodService.java | 7 ++++++- core/src/main/java/bisq/core/dao/node/BsqNode.java | 13 ++----------- .../java/bisq/core/dao/node/parser/BlockParser.java | 2 +- .../java/bisq/core/dao/state/DaoStateService.java | 7 ++++++- .../core/dao/state/DaoStateSnapshotService.java | 8 +++++++- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java index 8e13a9633b..773e74121b 100644 --- a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java @@ -122,6 +122,7 @@ public class MyVoteListService implements PersistedDataHost { public List getMyVoteListForCycle() { return myVoteList.getList().stream() + .filter(e -> daoStateService.getCurrentCycle() != null) .filter(e -> daoStateService.getCurrentCycle().isInCycle(e.getHeight())) .collect(Collectors.toList()); } diff --git a/core/src/main/java/bisq/core/dao/governance/period/CycleService.java b/core/src/main/java/bisq/core/dao/governance/period/CycleService.java index 44587e9043..0300201dad 100644 --- a/core/src/main/java/bisq/core/dao/governance/period/CycleService.java +++ b/core/src/main/java/bisq/core/dao/governance/period/CycleService.java @@ -105,9 +105,9 @@ public class CycleService implements DaoStateListener, DaoSetupService { // applied the new cycle yet. But the first block of the old cycle will always be the same as the // first block of the new cycle. Cycle cycle = null; - if (blockHeight != genesisBlockHeight && isFirstBlockAfterPreviousCycle(blockHeight, cycles)) { + if (blockHeight != genesisBlockHeight && isFirstBlockAfterPreviousCycle(blockHeight, cycles) && !cycles.isEmpty()) { // We have the not update daoStateService.getCurrentCycle() so we grab here the previousCycle - final Cycle previousCycle = cycles.getLast(); + Cycle previousCycle = cycles.getLast(); // We create the new cycle as clone of the previous cycle and only if there have been change events we use // the new values from the change event. cycle = createNewCycle(blockHeight, previousCycle); diff --git a/core/src/main/java/bisq/core/dao/governance/period/PeriodService.java b/core/src/main/java/bisq/core/dao/governance/period/PeriodService.java index e8db5b24b2..f175183b27 100644 --- a/core/src/main/java/bisq/core/dao/governance/period/PeriodService.java +++ b/core/src/main/java/bisq/core/dao/governance/period/PeriodService.java @@ -29,6 +29,8 @@ import java.util.Optional; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + @Slf4j public final class PeriodService { private final DaoStateService daoStateService; @@ -52,6 +54,7 @@ public final class PeriodService { return daoStateService.getCycles(); } + @Nullable public Cycle getCurrentCycle() { return daoStateService.getCurrentCycle(); } @@ -65,7 +68,9 @@ public final class PeriodService { } public DaoPhase.Phase getCurrentPhase() { - return getCurrentCycle().getPhaseForHeight(this.getChainHeight()).get(); + return getCurrentCycle() != null ? + getCurrentCycle().getPhaseForHeight(this.getChainHeight()).orElse(DaoPhase.Phase.UNDEFINED) : + DaoPhase.Phase.UNDEFINED; } public boolean isFirstBlockInCycle(int height) { diff --git a/core/src/main/java/bisq/core/dao/node/BsqNode.java b/core/src/main/java/bisq/core/dao/node/BsqNode.java index 5ae8386bee..321e3e2eb9 100644 --- a/core/src/main/java/bisq/core/dao/node/BsqNode.java +++ b/core/src/main/java/bisq/core/dao/node/BsqNode.java @@ -137,7 +137,7 @@ public abstract class BsqNode implements DaoSetupService { @SuppressWarnings("WeakerAccess") protected void onInitialized() { - applySnapshot(); + daoStateSnapshotService.applySnapshot(false); if (p2PService.isBootstrapped()) { log.info("onAllServicesInitialized: isBootstrapped"); @@ -187,20 +187,11 @@ public abstract class BsqNode implements DaoSetupService { @SuppressWarnings("WeakerAccess") protected void startReOrgFromLastSnapshot() { - applySnapshot(); + daoStateSnapshotService.applySnapshot(true); startParseBlocks(); } protected boolean isBlockAlreadyAdded(RawBlock rawBlock) { return daoStateService.getBlockAtHeight(rawBlock.getHeight()).isPresent(); } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void applySnapshot() { - daoStateSnapshotService.applySnapshot(); - } } diff --git a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java index f7e1904ca0..fe10564841 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java @@ -121,7 +121,7 @@ public class BlockParser { private void validateIfBlockIsConnecting(RawBlock rawBlock) throws BlockNotConnectingException { LinkedList blocks = daoStateService.getBlocks(); - if (!isBlockConnecting(rawBlock, blocks)) { + if (!isBlockConnecting(rawBlock, blocks) && !blocks.isEmpty()) { Block last = blocks.getLast(); log.warn("addBlock called with a not connecting block. New block:\n" + "height()={}, hash()={}, lastBlock.height()={}, lastBlock.hash()={}", diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index 7dc7fc32ba..577e0cf5b4 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -55,6 +55,8 @@ import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkArgument; /** @@ -99,6 +101,8 @@ public class DaoStateService implements DaoSetupService { /////////////////////////////////////////////////////////////////////////////////////////// public void applySnapshot(DaoState snapshot) { + log.info("Apply snapshot with chain height {}", snapshot.getChainHeight()); + daoState.setChainHeight(snapshot.getChainHeight()); daoState.getBlocks().clear(); @@ -155,8 +159,9 @@ public class DaoStateService implements DaoSetupService { return daoState.getCycles(); } + @Nullable public Cycle getCurrentCycle() { - return getCycles().getLast(); + return !getCycles().isEmpty() ? getCycles().getLast() : null; } public Optional getCycle(int height) { 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 b110665c23..d701586fc6 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java @@ -77,6 +77,7 @@ public class DaoStateSnapshotService implements DaoStateListener { // different to our current height. boolean noSnapshotCandidateOrDifferentHeight = snapshotCandidate == null || snapshotCandidate.getChainHeight() != chainHeight; if (isSnapshotHeight(chainHeight) && + !daoStateService.getBlocks().isEmpty() && isValidHeight(daoStateService.getBlocks().getLast().getHeight()) && noSnapshotCandidateOrDifferentHeight) { // At trigger event we store the latest snapshotCandidate to disc @@ -108,7 +109,7 @@ public class DaoStateSnapshotService implements DaoStateListener { // API /////////////////////////////////////////////////////////////////////////////////////////// - public void applySnapshot() { + public void applySnapshot(boolean fromReorg) { DaoState persisted = daoStateStorageService.getPersistedBsqState(); if (persisted != null) { LinkedList blocks = persisted.getBlocks(); @@ -117,6 +118,11 @@ public class DaoStateSnapshotService implements DaoStateListener { log.info("applySnapshot from persisted daoState with height of last block {}", heightOfLastBlock); if (isValidHeight(heightOfLastBlock)) daoStateService.applySnapshot(persisted); + } 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"); + persisted.setChainHeight(genesisTxInfo.getGenesisBlockHeight()); + daoStateService.applySnapshot(persisted); } } else { log.info("Try to apply snapshot but no stored snapshot available. That is expected at first blocks.");