diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index 8b6563f2..67a202df 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -44,6 +44,9 @@ public class BlockMinter extends Thread { private static Long lastLogTimestamp; private static Long logTimeout; + // Recovery + public static final long INVALID_BLOCK_RECOVERY_TIMEOUT = 10 * 60 * 1000L; // ms + // Constructors public BlockMinter() { @@ -144,9 +147,25 @@ public class BlockMinter extends Thread { if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) continue; + // If we are stuck on an invalid block, we should allow an alternative to be minted + boolean recoverInvalidBlock = false; + if (Synchronizer.getInstance().timeInvalidBlockLastReceived != null) { + // We've had at least one invalid block + long timeSinceLastValidBlock = NTP.getTime() - Synchronizer.getInstance().timeValidBlockLastReceived; + long timeSinceLastInvalidBlock = NTP.getTime() - Synchronizer.getInstance().timeInvalidBlockLastReceived; + if (timeSinceLastValidBlock > INVALID_BLOCK_RECOVERY_TIMEOUT) { + if (timeSinceLastInvalidBlock < INVALID_BLOCK_RECOVERY_TIMEOUT) { + // Last valid block was more than 10 mins ago, but we've had an invalid block since then + // Assume that the chain has stalled because there is no alternative valid candidate + // Enter recovery mode to allow alternative, valid candidates to be minted + recoverInvalidBlock = true; + } + } + } + // If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode. if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp) - if (Controller.getInstance().getRecoveryMode() == false) + if (Controller.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false) continue; // There are enough peers with a recent block and our latest block is recent diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 113af107..30f3c6ee 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -62,6 +62,11 @@ public class Synchronizer { // Keep track of the size of the last re-org, so it can be logged private int lastReorgSize; + // Keep track of invalid blocks so that we don't keep trying to sync them + private List invalidBlockSignatures = new ArrayList<>(); + public Long timeValidBlockLastReceived = null; + public Long timeInvalidBlockLastReceived = null; + private static Synchronizer instance; public enum SynchronizationResult { @@ -526,6 +531,11 @@ public class Synchronizer { // Reset last re-org size as we are starting a new sync round this.lastReorgSize = 0; + // Set the initial value of timeValidBlockLastReceived if it's null + if (this.timeValidBlockLastReceived == null) { + this.timeValidBlockLastReceived = NTP.getTime(); + } + List peerBlockSummaries = new ArrayList<>(); SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries, true); if (findCommonBlockResult != SynchronizationResult.OK) { @@ -980,9 +990,13 @@ public class Synchronizer { if (blockResult != ValidationResult.OK) { LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer, newBlock.getBlockData().getHeight(), Base58.encode(newBlock.getSignature()), blockResult.name())); + this.timeInvalidBlockLastReceived = NTP.getTime(); return SynchronizationResult.INVALID_DATA; } + // Block is valid + this.timeValidBlockLastReceived = NTP.getTime(); + // Save transactions attached to this block for (Transaction transaction : newBlock.getTransactions()) { TransactionData transactionData = transaction.getTransactionData(); @@ -1068,9 +1082,13 @@ public class Synchronizer { if (blockResult != ValidationResult.OK) { LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer, ourHeight, Base58.encode(latestPeerSignature), blockResult.name())); + this.timeInvalidBlockLastReceived = NTP.getTime(); return SynchronizationResult.INVALID_DATA; } + // Block is valid + this.timeValidBlockLastReceived = NTP.getTime(); + // Save transactions attached to this block for (Transaction transaction : newBlock.getTransactions()) { TransactionData transactionData = transaction.getTransactionData();