BlockGen / Synchronizer improvements

BlockGenerator will now attempt to generate a new block if none of its
peers have a recent block either (in case of network stall). BlockGenerator
still needs a minimum number of peers before generating though.

Reduce BlockGenerator workload and use of blockchain lock if it
can't generate a block.

Reduce Synchronizer logging output.

Unify calculating timestamp threshold for 'recent' block into Controller.
This commit is contained in:
catbref 2019-06-17 11:06:21 +01:00
parent 5acc92ef26
commit 1d81c4db6b
4 changed files with 53 additions and 29 deletions

View File

@ -730,6 +730,20 @@ public class Block {
} }
} }
/**
* Returns timestamp based on previous block and this block's generator.
* <p>
* For qora-core, we'll using the minimum from BlockChain config.
*/
public static long calcMinimumTimestamp(BlockData parentBlockData, byte[] generatorPublicKey) {
long minBlockTime = BlockChain.getInstance().getMinBlockTime(); // seconds
return parentBlockData.getTimestamp() + (minBlockTime * 1000L);
}
public long calcMinimumTimestamp(BlockData parentBlockData) {
return calcMinimumTimestamp(parentBlockData, this.generator.getPublicKey());
}
/** /**
* Recalculate block's generator and transactions signatures, thus giving block full signature. * Recalculate block's generator and transactions signatures, thus giving block full signature.
* <p> * <p>

View File

@ -18,6 +18,7 @@ import org.qora.data.account.ProxyForgerData;
import org.qora.data.block.BlockData; import org.qora.data.block.BlockData;
import org.qora.data.transaction.TransactionData; import org.qora.data.transaction.TransactionData;
import org.qora.network.Network; import org.qora.network.Network;
import org.qora.network.Peer;
import org.qora.repository.BlockRepository; import org.qora.repository.BlockRepository;
import org.qora.repository.DataException; import org.qora.repository.DataException;
import org.qora.repository.Repository; import org.qora.repository.Repository;
@ -78,16 +79,30 @@ public class BlockGenerator extends Thread {
return; return;
} }
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
BlockData lastBlockData = blockRepository.getLastBlock();
// Disregard peers that have "misbehaved" recently
peers.removeIf(Controller.hasPeerMisbehaved);
// Don't generate if we don't have enough connected peers as where would the transactions/consensus come from? // Don't generate if we don't have enough connected peers as where would the transactions/consensus come from?
if (Network.getInstance().getUniqueHandshakedPeers().size() < Settings.getInstance().getMinBlockchainPeers()) if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
continue; continue;
// Don't generate if it looks like we're behind final long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
if (!Controller.getInstance().isUpToDate())
// Disregard peers that don't have a recent block
peers.removeIf(peer -> peer.getPeerData().getLastBlockTimestamp() == null || peer.getPeerData().getLastBlockTimestamp() < minLatestBlockTimestamp);
// If we have any peers with a recent block, but our latest block isn't recent
// then we need to synchronize instead of generating.
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
continue; continue;
// There are no peers with a recent block and/or our latest block is recent
// so go ahead and generate a block if possible.
// Check blockchain hasn't changed // Check blockchain hasn't changed
BlockData lastBlockData = blockRepository.getLastBlock();
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) { if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
previousBlock = new Block(repository, lastBlockData); previousBlock = new Block(repository, lastBlockData);
newBlocks.clear(); newBlocks.clear();
@ -95,6 +110,10 @@ public class BlockGenerator extends Thread {
// Do we need to build any potential new blocks? // Do we need to build any potential new blocks?
List<ForgingAccountData> forgingAccountsData = repository.getAccountRepository().getForgingAccounts(); List<ForgingAccountData> forgingAccountsData = repository.getAccountRepository().getForgingAccounts();
// No forging accounts?
if (forgingAccountsData.isEmpty())
continue;
List<PrivateKeyAccount> forgingAccounts = forgingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getSeed())).collect(Collectors.toList()); List<PrivateKeyAccount> forgingAccounts = forgingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getSeed())).collect(Collectors.toList());
// Discard accounts we have blocks for // Discard accounts we have blocks for
@ -112,6 +131,10 @@ public class BlockGenerator extends Thread {
} }
} }
// No potential block candidates?
if (newBlocks.isEmpty())
continue;
// Make sure we're the only thread modifying the blockchain // Make sure we're the only thread modifying the blockchain
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
if (!blockchainLock.tryLock()) if (!blockchainLock.tryLock())

View File

@ -1008,39 +1008,30 @@ public class Controller extends Thread {
/** Returns whether we think our node has up-to-date blockchain based on our info about other peers. */ /** Returns whether we think our node has up-to-date blockchain based on our info about other peers. */
public boolean isUpToDate() { public boolean isUpToDate() {
// Is our blockchain too old?
final long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp(); final long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp();
BlockData latestBlockData = getChainTip(); BlockData latestBlockData = getChainTip();
// Is our blockchain too old?
if (latestBlockData.getTimestamp() < minLatestBlockTimestamp) if (latestBlockData.getTimestamp() < minLatestBlockTimestamp)
return false; return false;
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers(); List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
// Disregard peers that have "misbehaved" recently
peers.removeIf(hasPeerMisbehaved);
// Check we have enough peers to potentially synchronize/generator // Check we have enough peers to potentially synchronize/generator
if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
return false; return false;
// Disregard peers that have "misbehaved" recently
peers.removeIf(hasPeerMisbehaved);
// Disregard peers with unknown height, lower height or same height and same block signature (unless we don't have their block signature)
// peers.removeIf(hasShorterBlockchain());
// Disregard peers that within 1 block of our height (actually ourHeight + 1)
// final int maxHeight = getChainHeight() + 1;
// peers.removeIf(peer -> peer.getPeerData().getLastHeight() <= maxHeight );
// Disregard peers that don't have a recent block // Disregard peers that don't have a recent block
peers.removeIf(peer -> peer.getPeerData().getLastBlockTimestamp() == null || peer.getPeerData().getLastBlockTimestamp() < minLatestBlockTimestamp); peers.removeIf(peer -> peer.getPeerData().getLastBlockTimestamp() == null || peer.getPeerData().getLastBlockTimestamp() < minLatestBlockTimestamp);
// If we have any peers left, then they would be candidates for synchronization therefore we're not up to date.
// return peers.isEmpty();
// If we don't have any peers left then can't synchronize, therefore consider ourself not up to date // If we don't have any peers left then can't synchronize, therefore consider ourself not up to date
return !peers.isEmpty(); return !peers.isEmpty();
} }
public long getMinimumLatestBlockTimestamp() { public static long getMinimumLatestBlockTimestamp() {
return NTP.getTime() - BlockChain.getInstance().getMaxBlockTime() * 1000L * MAX_BLOCKCHAIN_TIP_AGE; return NTP.getTime() - BlockChain.getInstance().getMaxBlockTime() * 1000L * MAX_BLOCKCHAIN_TIP_AGE;
} }

View File

@ -9,7 +9,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.qora.block.Block; import org.qora.block.Block;
import org.qora.block.Block.ValidationResult; import org.qora.block.Block.ValidationResult;
import org.qora.block.BlockChain;
import org.qora.data.block.BlockData; import org.qora.data.block.BlockData;
import org.qora.data.network.BlockSummaryData; import org.qora.data.network.BlockSummaryData;
import org.qora.data.transaction.TransactionData; import org.qora.data.transaction.TransactionData;
@ -28,7 +27,6 @@ import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager; import org.qora.repository.RepositoryManager;
import org.qora.transaction.Transaction; import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ApprovalStatus; import org.qora.transaction.Transaction.ApprovalStatus;
import org.qora.utils.NTP;
public class Synchronizer { public class Synchronizer {
@ -38,8 +36,6 @@ public class Synchronizer {
private static final int MAXIMUM_BLOCK_STEP = 500; private static final int MAXIMUM_BLOCK_STEP = 500;
private static final int MAXIMUM_HEIGHT_DELTA = 300; // XXX move to blockchain config? private static final int MAXIMUM_HEIGHT_DELTA = 300; // XXX move to blockchain config?
private static final int MAXIMUM_COMMON_DELTA = 60; // XXX move to blockchain config? private static final int MAXIMUM_COMMON_DELTA = 60; // XXX move to blockchain config?
/** Maximum age for our latest block before we consider ditching our fork. */
private static final long MAXIMUM_TIP_AGE = BlockChain.getInstance().getMaxBlockTime() * 1000L * 10; // XXX move to blockchain config?
private static final int SYNC_BATCH_SIZE = 200; private static final int SYNC_BATCH_SIZE = 200;
private static Synchronizer instance; private static Synchronizer instance;
@ -103,9 +99,9 @@ public class Synchronizer {
byte[] peersLastBlockSignature = peer.getPeerData().getLastBlockSignature(); byte[] peersLastBlockSignature = peer.getPeerData().getLastBlockSignature();
byte[] ourLastBlockSignature = ourLatestBlockData.getSignature(); byte[] ourLastBlockSignature = ourLatestBlockData.getSignature();
if (peerHeight == ourHeight && (peersLastBlockSignature == null || !Arrays.equals(peersLastBlockSignature, ourLastBlockSignature))) if (peerHeight == ourHeight && (peersLastBlockSignature == null || !Arrays.equals(peersLastBlockSignature, ourLastBlockSignature)))
LOGGER.info(String.format("Synchronizing with peer %s at height %d, our height %d, signatures differ", peer, peerHeight, ourHeight)); LOGGER.debug(String.format("Synchronizing with peer %s at height %d, our height %d, signatures differ", peer, peerHeight, ourHeight));
else else
LOGGER.info(String.format("Synchronizing with peer %s at height %d, our height %d", peer, peerHeight, ourHeight)); LOGGER.debug(String.format("Synchronizing with peer %s at height %d, our height %d", peer, peerHeight, ourHeight));
List<byte[]> signatures = findSignaturesFromCommonBlock(peer, ourHeight); List<byte[]> signatures = findSignaturesFromCommonBlock(peer, ourHeight);
if (signatures == null) { if (signatures == null) {
@ -134,9 +130,9 @@ public class Synchronizer {
// If common block is peer's latest block then we simply have the same, or longer, chain to peer, so exit now // If common block is peer's latest block then we simply have the same, or longer, chain to peer, so exit now
if (commonBlockHeight == peerHeight) { if (commonBlockHeight == peerHeight) {
if (peerHeight == ourHeight) if (peerHeight == ourHeight)
LOGGER.info(String.format("We have the same blockchain as peer %s", peer)); LOGGER.debug(String.format("We have the same blockchain as peer %s", peer));
else else
LOGGER.info(String.format("We have the same blockchain as peer %s, but longer", peer)); LOGGER.debug(String.format("We have the same blockchain as peer %s, but longer", peer));
return SynchronizationResult.NOTHING_TO_DO; return SynchronizationResult.NOTHING_TO_DO;
} }
@ -151,9 +147,9 @@ public class Synchronizer {
// If we have blocks after common block then decide whether we want to sync (lowest block signature wins) // If we have blocks after common block then decide whether we want to sync (lowest block signature wins)
int highestMutualHeight = Math.min(peerHeight, ourHeight); int highestMutualHeight = Math.min(peerHeight, ourHeight);
// XXX This might be obsolete now
// If our latest block is very old, we're very behind and should ditch our fork. // If our latest block is very old, we're very behind and should ditch our fork.
if (ourInitialHeight > commonBlockHeight && ourLatestBlockData.getTimestamp() < NTP.getTime() - MAXIMUM_TIP_AGE) { final long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
if (ourInitialHeight > commonBlockHeight && ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
LOGGER.info(String.format("Ditching our chain after height %d as our latest block is very old", commonBlockHeight)); LOGGER.info(String.format("Ditching our chain after height %d as our latest block is very old", commonBlockHeight));
highestMutualHeight = commonBlockHeight; highestMutualHeight = commonBlockHeight;
} }