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.
|
* Recalculate block's generator and transactions signatures, thus giving block full signature.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -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())
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user