forked from Qortal/qortal
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:
parent
5acc92ef26
commit
1d81c4db6b
@ -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.
|
||||
* <p>
|
||||
|
@ -18,6 +18,7 @@ import org.qora.data.account.ProxyForgerData;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.network.Network;
|
||||
import org.qora.network.Peer;
|
||||
import org.qora.repository.BlockRepository;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
@ -78,16 +79,30 @@ public class BlockGenerator extends Thread {
|
||||
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?
|
||||
if (Network.getInstance().getUniqueHandshakedPeers().size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
continue;
|
||||
|
||||
// Don't generate if it looks like we're behind
|
||||
if (!Controller.getInstance().isUpToDate())
|
||||
final long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
BlockData lastBlockData = blockRepository.getLastBlock();
|
||||
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
||||
previousBlock = new Block(repository, lastBlockData);
|
||||
newBlocks.clear();
|
||||
@ -95,6 +110,10 @@ public class BlockGenerator extends Thread {
|
||||
|
||||
// Do we need to build any potential new blocks?
|
||||
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());
|
||||
|
||||
// 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
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
if (!blockchainLock.tryLock())
|
||||
|
@ -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. */
|
||||
public boolean isUpToDate() {
|
||||
// Is our blockchain too old?
|
||||
final long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp();
|
||||
BlockData latestBlockData = getChainTip();
|
||||
|
||||
// Is our blockchain too old?
|
||||
if (latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||
return false;
|
||||
|
||||
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
|
||||
|
||||
// Disregard peers that have "misbehaved" recently
|
||||
peers.removeIf(hasPeerMisbehaved);
|
||||
|
||||
// Check we have enough peers to potentially synchronize/generator
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
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
|
||||
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
|
||||
return !peers.isEmpty();
|
||||
}
|
||||
|
||||
public long getMinimumLatestBlockTimestamp() {
|
||||
public static long getMinimumLatestBlockTimestamp() {
|
||||
return NTP.getTime() - BlockChain.getInstance().getMaxBlockTime() * 1000L * MAX_BLOCKCHAIN_TIP_AGE;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qora.block.Block;
|
||||
import org.qora.block.Block.ValidationResult;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.network.BlockSummaryData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
@ -28,7 +27,6 @@ import org.qora.repository.Repository;
|
||||
import org.qora.repository.RepositoryManager;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transaction.Transaction.ApprovalStatus;
|
||||
import org.qora.utils.NTP;
|
||||
|
||||
public class Synchronizer {
|
||||
|
||||
@ -38,8 +36,6 @@ public class Synchronizer {
|
||||
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_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 Synchronizer instance;
|
||||
@ -103,9 +99,9 @@ public class Synchronizer {
|
||||
byte[] peersLastBlockSignature = peer.getPeerData().getLastBlockSignature();
|
||||
byte[] ourLastBlockSignature = ourLatestBlockData.getSignature();
|
||||
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
|
||||
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);
|
||||
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 (commonBlockHeight == peerHeight) {
|
||||
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
|
||||
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;
|
||||
}
|
||||
@ -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)
|
||||
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 (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));
|
||||
highestMutualHeight = commonBlockHeight;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user