diff --git a/src/main/java/org/qora/block/Block.java b/src/main/java/org/qora/block/Block.java index e5788f0e..db2aaf60 100644 --- a/src/main/java/org/qora/block/Block.java +++ b/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. + *

+ * 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. *

diff --git a/src/main/java/org/qora/block/BlockGenerator.java b/src/main/java/org/qora/block/BlockGenerator.java index a1a998a2..3534aa6c 100644 --- a/src/main/java/org/qora/block/BlockGenerator.java +++ b/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 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 forgingAccountsData = repository.getAccountRepository().getForgingAccounts(); + // No forging accounts? + if (forgingAccountsData.isEmpty()) + continue; + List 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()) diff --git a/src/main/java/org/qora/controller/Controller.java b/src/main/java/org/qora/controller/Controller.java index 636ccad6..780072f2 100644 --- a/src/main/java/org/qora/controller/Controller.java +++ b/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 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; } diff --git a/src/main/java/org/qora/controller/Synchronizer.java b/src/main/java/org/qora/controller/Synchronizer.java index 0d602e46..e220a6eb 100644 --- a/src/main/java/org/qora/controller/Synchronizer.java +++ b/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 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; }