forked from Qortal/qortal
Allow an alternative block to be minted if the chain stalls due to an invalid block
If it has been more than 10 minutes since receiving the last valid block, but we have had at least one invalid block since then, this is indicative of a stuck chain due to no valid block candidates. In this case, we want to allow the block minter to mint an alternative candidate so that the chain can continue. This would create a fork at the point of the invalid block, in which two chains (valid an invalid) would diverge. The valid chain could never rejoin the invalid one, however it's likely that the invalid chain would be discarded in favour of the valid one shortly after, on the assumption that the majority of nodes would have picked the valid one.
This commit is contained in:
parent
06a2c380bd
commit
54e5a65cf0
@ -44,6 +44,9 @@ public class BlockMinter extends Thread {
|
|||||||
private static Long lastLogTimestamp;
|
private static Long lastLogTimestamp;
|
||||||
private static Long logTimeout;
|
private static Long logTimeout;
|
||||||
|
|
||||||
|
// Recovery
|
||||||
|
public static final long INVALID_BLOCK_RECOVERY_TIMEOUT = 10 * 60 * 1000L; // ms
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public BlockMinter() {
|
public BlockMinter() {
|
||||||
@ -144,9 +147,25 @@ public class BlockMinter extends Thread {
|
|||||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||||
continue;
|
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 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 (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||||
if (Controller.getInstance().getRecoveryMode() == false)
|
if (Controller.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// There are enough peers with a recent block and our latest block is recent
|
// There are enough peers with a recent block and our latest block is recent
|
||||||
|
@ -62,6 +62,11 @@ public class Synchronizer {
|
|||||||
// Keep track of the size of the last re-org, so it can be logged
|
// Keep track of the size of the last re-org, so it can be logged
|
||||||
private int lastReorgSize;
|
private int lastReorgSize;
|
||||||
|
|
||||||
|
// Keep track of invalid blocks so that we don't keep trying to sync them
|
||||||
|
private List<byte[]> invalidBlockSignatures = new ArrayList<>();
|
||||||
|
public Long timeValidBlockLastReceived = null;
|
||||||
|
public Long timeInvalidBlockLastReceived = null;
|
||||||
|
|
||||||
private static Synchronizer instance;
|
private static Synchronizer instance;
|
||||||
|
|
||||||
public enum SynchronizationResult {
|
public enum SynchronizationResult {
|
||||||
@ -526,6 +531,11 @@ public class Synchronizer {
|
|||||||
// Reset last re-org size as we are starting a new sync round
|
// Reset last re-org size as we are starting a new sync round
|
||||||
this.lastReorgSize = 0;
|
this.lastReorgSize = 0;
|
||||||
|
|
||||||
|
// Set the initial value of timeValidBlockLastReceived if it's null
|
||||||
|
if (this.timeValidBlockLastReceived == null) {
|
||||||
|
this.timeValidBlockLastReceived = NTP.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries, true);
|
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries, true);
|
||||||
if (findCommonBlockResult != SynchronizationResult.OK) {
|
if (findCommonBlockResult != SynchronizationResult.OK) {
|
||||||
@ -980,9 +990,13 @@ public class Synchronizer {
|
|||||||
if (blockResult != ValidationResult.OK) {
|
if (blockResult != ValidationResult.OK) {
|
||||||
LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer,
|
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()));
|
newBlock.getBlockData().getHeight(), Base58.encode(newBlock.getSignature()), blockResult.name()));
|
||||||
|
this.timeInvalidBlockLastReceived = NTP.getTime();
|
||||||
return SynchronizationResult.INVALID_DATA;
|
return SynchronizationResult.INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block is valid
|
||||||
|
this.timeValidBlockLastReceived = NTP.getTime();
|
||||||
|
|
||||||
// Save transactions attached to this block
|
// Save transactions attached to this block
|
||||||
for (Transaction transaction : newBlock.getTransactions()) {
|
for (Transaction transaction : newBlock.getTransactions()) {
|
||||||
TransactionData transactionData = transaction.getTransactionData();
|
TransactionData transactionData = transaction.getTransactionData();
|
||||||
@ -1068,9 +1082,13 @@ public class Synchronizer {
|
|||||||
if (blockResult != ValidationResult.OK) {
|
if (blockResult != ValidationResult.OK) {
|
||||||
LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer,
|
LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer,
|
||||||
ourHeight, Base58.encode(latestPeerSignature), blockResult.name()));
|
ourHeight, Base58.encode(latestPeerSignature), blockResult.name()));
|
||||||
|
this.timeInvalidBlockLastReceived = NTP.getTime();
|
||||||
return SynchronizationResult.INVALID_DATA;
|
return SynchronizationResult.INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block is valid
|
||||||
|
this.timeValidBlockLastReceived = NTP.getTime();
|
||||||
|
|
||||||
// Save transactions attached to this block
|
// Save transactions attached to this block
|
||||||
for (Transaction transaction : newBlock.getTransactions()) {
|
for (Transaction transaction : newBlock.getTransactions()) {
|
||||||
TransactionData transactionData = transaction.getTransactionData();
|
TransactionData transactionData = transaction.getTransactionData();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user