Browse Source

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.
pull/67/head
catbref 5 years ago
parent
commit
1d81c4db6b
  1. 14
      src/main/java/org/qora/block/Block.java
  2. 31
      src/main/java/org/qora/block/BlockGenerator.java
  3. 21
      src/main/java/org/qora/controller/Controller.java
  4. 16
      src/main/java/org/qora/controller/Synchronizer.java

14
src/main/java/org/qora/block/Block.java

@ -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>

31
src/main/java/org/qora/block/BlockGenerator.java

@ -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())

21
src/main/java/org/qora/controller/Controller.java

@ -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();
// 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 );
// Check we have enough peers to potentially synchronize/generator
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
return false;
// 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;
}

16
src/main/java/org/qora/controller/Synchronizer.java

@ -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…
Cancel
Save