forked from Qortal/qortal
BlockMinter now only acquires repository instance as needed to prevent long HSQLDB rollbacks
This commit is contained in:
parent
84d850ee0b
commit
0cf32f6c5e
@ -65,9 +65,8 @@ public class BlockMinter extends Thread {
|
|||||||
// Lite nodes do not mint
|
// Lite nodes do not mint
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
|
||||||
// Wipe existing unconfirmed transactions
|
// Wipe existing unconfirmed transactions
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
|
|
||||||
@ -77,30 +76,31 @@ public class BlockMinter extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.warn("Repository issue trying to wipe unconfirmed transactions on start-up: {}", e.getMessage());
|
||||||
|
// Fall-through to normal behaviour in case we can recover
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Going to need this a lot...
|
BlockData previousBlockData = null;
|
||||||
BlockRepository blockRepository = repository.getBlockRepository();
|
|
||||||
BlockData previousBlockData = null;
|
|
||||||
|
|
||||||
// Vars to keep track of blocks that were skipped due to chain weight
|
// Vars to keep track of blocks that were skipped due to chain weight
|
||||||
byte[] parentSignatureForLastLowWeightBlock = null;
|
byte[] parentSignatureForLastLowWeightBlock = null;
|
||||||
Long timeOfLastLowWeightBlock = null;
|
Long timeOfLastLowWeightBlock = null;
|
||||||
|
|
||||||
List<Block> newBlocks = new ArrayList<>();
|
List<Block> newBlocks = new ArrayList<>();
|
||||||
|
|
||||||
// Flags for tracking change in whether minting is possible,
|
// Flags for tracking change in whether minting is possible,
|
||||||
// so we can notify Controller, and further update SysTray, etc.
|
// so we can notify Controller, and further update SysTray, etc.
|
||||||
boolean isMintingPossible = false;
|
boolean isMintingPossible = false;
|
||||||
boolean wasMintingPossible = isMintingPossible;
|
boolean wasMintingPossible = isMintingPossible;
|
||||||
while (running) {
|
while (running) {
|
||||||
repository.discardChanges(); // Free repository locks, if any
|
if (isMintingPossible != wasMintingPossible)
|
||||||
|
Controller.getInstance().onMintingPossibleChange(isMintingPossible);
|
||||||
|
|
||||||
if (isMintingPossible != wasMintingPossible)
|
wasMintingPossible = isMintingPossible;
|
||||||
Controller.getInstance().onMintingPossibleChange(isMintingPossible);
|
|
||||||
|
|
||||||
wasMintingPossible = isMintingPossible;
|
|
||||||
|
|
||||||
|
try {
|
||||||
// Sleep for a while
|
// Sleep for a while
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
@ -118,315 +118,321 @@ public class BlockMinter extends Thread {
|
|||||||
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
// No minting accounts?
|
// Going to need this a lot...
|
||||||
if (mintingAccountsData.isEmpty())
|
BlockRepository blockRepository = repository.getBlockRepository();
|
||||||
continue;
|
|
||||||
|
|
||||||
// Disregard minting accounts that are no longer valid, e.g. by transfer/loss of founder flag or account level
|
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
|
||||||
// Note that minting accounts are actually reward-shares in Qortal
|
// No minting accounts?
|
||||||
Iterator<MintingAccountData> madi = mintingAccountsData.iterator();
|
if (mintingAccountsData.isEmpty())
|
||||||
while (madi.hasNext()) {
|
|
||||||
MintingAccountData mintingAccountData = madi.next();
|
|
||||||
|
|
||||||
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey());
|
|
||||||
if (rewardShareData == null) {
|
|
||||||
// Reward-share doesn't exist - probably cancelled but not yet removed from node's list of minting accounts
|
|
||||||
madi.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
|
|
||||||
if (!mintingAccount.canMint()) {
|
|
||||||
// Minting-account component of reward-share can no longer mint - disregard
|
|
||||||
madi.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional (non-validated) prevention of block submissions below a defined level.
|
|
||||||
// This is an unvalidated version of Blockchain.minAccountLevelToMint
|
|
||||||
// and exists only to reduce block candidates by default.
|
|
||||||
int level = mintingAccount.getEffectiveMintingLevel();
|
|
||||||
if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) {
|
|
||||||
madi.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needs a mutable copy of the unmodifiableList
|
|
||||||
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
|
||||||
BlockData lastBlockData = blockRepository.getLastBlock();
|
|
||||||
|
|
||||||
// Disregard peers that have "misbehaved" recently
|
|
||||||
peers.removeIf(Controller.hasMisbehaved);
|
|
||||||
|
|
||||||
// Disregard peers that don't have a recent block, but only if we're not in recovery mode.
|
|
||||||
// In that mode, we want to allow minting on top of older blocks, to recover stalled networks.
|
|
||||||
if (Synchronizer.getInstance().getRecoveryMode() == false)
|
|
||||||
peers.removeIf(Controller.hasNoRecentBlock);
|
|
||||||
|
|
||||||
// Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from?
|
|
||||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If we are stuck on an invalid block, we should allow an alternative to be minted
|
|
||||||
boolean recoverInvalidBlock = false;
|
|
||||||
if (Synchronizer.getInstance().timeInvalidBlockLastReceived != null) {
|
|
||||||
// We've had at least one invalid block
|
|
||||||
long timeSinceLastValidBlock = NTP.getTime() - Synchronizer.getInstance().timeValidBlockLastReceived;
|
|
||||||
long timeSinceLastInvalidBlock = NTP.getTime() - Synchronizer.getInstance().timeInvalidBlockLastReceived;
|
|
||||||
if (timeSinceLastValidBlock > INVALID_BLOCK_RECOVERY_TIMEOUT) {
|
|
||||||
if (timeSinceLastInvalidBlock < INVALID_BLOCK_RECOVERY_TIMEOUT) {
|
|
||||||
// Last valid block was more than 10 mins ago, but we've had an invalid block since then
|
|
||||||
// Assume that the chain has stalled because there is no alternative valid candidate
|
|
||||||
// Enter recovery mode to allow alternative, valid candidates to be minted
|
|
||||||
recoverInvalidBlock = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode.
|
|
||||||
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
|
|
||||||
if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// There are enough peers with a recent block and our latest block is recent
|
// Disregard minting accounts that are no longer valid, e.g. by transfer/loss of founder flag or account level
|
||||||
// so go ahead and mint a block if possible.
|
// Note that minting accounts are actually reward-shares in Qortal
|
||||||
isMintingPossible = true;
|
Iterator<MintingAccountData> madi = mintingAccountsData.iterator();
|
||||||
|
while (madi.hasNext()) {
|
||||||
|
MintingAccountData mintingAccountData = madi.next();
|
||||||
|
|
||||||
// Check blockchain hasn't changed
|
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey());
|
||||||
if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) {
|
if (rewardShareData == null) {
|
||||||
previousBlockData = lastBlockData;
|
// Reward-share doesn't exist - probably cancelled but not yet removed from node's list of minting accounts
|
||||||
newBlocks.clear();
|
madi.remove();
|
||||||
|
|
||||||
// Reduce log timeout
|
|
||||||
logTimeout = 10 * 1000L;
|
|
||||||
|
|
||||||
// Last low weight block is no longer valid
|
|
||||||
parentSignatureForLastLowWeightBlock = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard accounts we have already built blocks with
|
|
||||||
mintingAccountsData.removeIf(mintingAccountData -> newBlocks.stream().anyMatch(newBlock -> Arrays.equals(newBlock.getBlockData().getMinterPublicKey(), mintingAccountData.getPublicKey())));
|
|
||||||
|
|
||||||
// Do we need to build any potential new blocks?
|
|
||||||
List<PrivateKeyAccount> newBlocksMintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList());
|
|
||||||
|
|
||||||
// We might need to sit the next block out, if one of our minting accounts signed the previous one
|
|
||||||
final byte[] previousBlockMinter = previousBlockData.getMinterPublicKey();
|
|
||||||
final boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter));
|
|
||||||
if (mintedLastBlock) {
|
|
||||||
LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentSignatureForLastLowWeightBlock != null) {
|
|
||||||
// The last iteration found a higher weight block in the network, so sleep for a while
|
|
||||||
// to allow is to sync the higher weight chain. We are sleeping here rather than when
|
|
||||||
// detected as we don't want to hold the blockchain lock open.
|
|
||||||
LOGGER.debug("Sleeping for 10 seconds...");
|
|
||||||
Thread.sleep(10 * 1000L);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PrivateKeyAccount mintingAccount : newBlocksMintingAccounts) {
|
|
||||||
// First block does the AT heavy-lifting
|
|
||||||
if (newBlocks.isEmpty()) {
|
|
||||||
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
|
|
||||||
if (newBlock == null) {
|
|
||||||
// For some reason we can't mint right now
|
|
||||||
moderatedLog(() -> LOGGER.error("Couldn't build a to-be-minted block"));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks.add(newBlock);
|
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
|
||||||
} else {
|
if (!mintingAccount.canMint()) {
|
||||||
// The blocks for other minters require less effort...
|
// Minting-account component of reward-share can no longer mint - disregard
|
||||||
Block newBlock = newBlocks.get(0).remint(mintingAccount);
|
madi.remove();
|
||||||
if (newBlock == null) {
|
|
||||||
// For some reason we can't mint right now
|
|
||||||
moderatedLog(() -> LOGGER.error("Couldn't rebuild a to-be-minted block"));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlocks.add(newBlock);
|
// Optional (non-validated) prevention of block submissions below a defined level.
|
||||||
|
// This is an unvalidated version of Blockchain.minAccountLevelToMint
|
||||||
|
// and exists only to reduce block candidates by default.
|
||||||
|
int level = mintingAccount.getEffectiveMintingLevel();
|
||||||
|
if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) {
|
||||||
|
madi.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No potential block candidates?
|
// Needs a mutable copy of the unmodifiableList
|
||||||
if (newBlocks.isEmpty())
|
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||||
continue;
|
BlockData lastBlockData = blockRepository.getLastBlock();
|
||||||
|
|
||||||
// Make sure we're the only thread modifying the blockchain
|
// Disregard peers that have "misbehaved" recently
|
||||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
peers.removeIf(Controller.hasMisbehaved);
|
||||||
if (!blockchainLock.tryLock(30, TimeUnit.SECONDS)) {
|
|
||||||
LOGGER.debug("Couldn't acquire blockchain lock even after waiting 30 seconds");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean newBlockMinted = false;
|
// Disregard peers that don't have a recent block, but only if we're not in recovery mode.
|
||||||
Block newBlock = null;
|
// In that mode, we want to allow minting on top of older blocks, to recover stalled networks.
|
||||||
|
if (Synchronizer.getInstance().getRecoveryMode() == false)
|
||||||
|
peers.removeIf(Controller.hasNoRecentBlock);
|
||||||
|
|
||||||
try {
|
// Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from?
|
||||||
// Clear repository session state so we have latest view of data
|
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||||
repository.discardChanges();
|
|
||||||
|
|
||||||
// Now that we have blockchain lock, do final check that chain hasn't changed
|
|
||||||
BlockData latestBlockData = blockRepository.getLastBlock();
|
|
||||||
if (!Arrays.equals(lastBlockData.getSignature(), latestBlockData.getSignature()))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
List<Block> goodBlocks = new ArrayList<>();
|
// If we are stuck on an invalid block, we should allow an alternative to be minted
|
||||||
for (Block testBlock : newBlocks) {
|
boolean recoverInvalidBlock = false;
|
||||||
// Is new block's timestamp valid yet?
|
if (Synchronizer.getInstance().timeInvalidBlockLastReceived != null) {
|
||||||
// We do a separate check as some timestamp checks are skipped for testchains
|
// We've had at least one invalid block
|
||||||
if (testBlock.isTimestampValid() != ValidationResult.OK)
|
long timeSinceLastValidBlock = NTP.getTime() - Synchronizer.getInstance().timeValidBlockLastReceived;
|
||||||
continue;
|
long timeSinceLastInvalidBlock = NTP.getTime() - Synchronizer.getInstance().timeInvalidBlockLastReceived;
|
||||||
|
if (timeSinceLastValidBlock > INVALID_BLOCK_RECOVERY_TIMEOUT) {
|
||||||
testBlock.preProcess();
|
if (timeSinceLastInvalidBlock < INVALID_BLOCK_RECOVERY_TIMEOUT) {
|
||||||
|
// Last valid block was more than 10 mins ago, but we've had an invalid block since then
|
||||||
// Is new block valid yet? (Before adding unconfirmed transactions)
|
// Assume that the chain has stalled because there is no alternative valid candidate
|
||||||
ValidationResult result = testBlock.isValid();
|
// Enter recovery mode to allow alternative, valid candidates to be minted
|
||||||
if (result != ValidationResult.OK) {
|
recoverInvalidBlock = true;
|
||||||
moderatedLog(() -> LOGGER.error(String.format("To-be-minted block invalid '%s' before adding transactions?", result.name())));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
goodBlocks.add(testBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (goodBlocks.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Pick best block
|
|
||||||
final int parentHeight = previousBlockData.getHeight();
|
|
||||||
final byte[] parentBlockSignature = previousBlockData.getSignature();
|
|
||||||
|
|
||||||
BigInteger bestWeight = null;
|
|
||||||
|
|
||||||
for (int bi = 0; bi < goodBlocks.size(); ++bi) {
|
|
||||||
BlockData blockData = goodBlocks.get(bi).getBlockData();
|
|
||||||
|
|
||||||
BlockSummaryData blockSummaryData = new BlockSummaryData(blockData);
|
|
||||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockData.getMinterPublicKey());
|
|
||||||
blockSummaryData.setMinterLevel(minterLevel);
|
|
||||||
|
|
||||||
BigInteger blockWeight = Block.calcBlockWeight(parentHeight, parentBlockSignature, blockSummaryData);
|
|
||||||
|
|
||||||
if (bestWeight == null || blockWeight.compareTo(bestWeight) < 0) {
|
|
||||||
newBlock = goodBlocks.get(bi);
|
|
||||||
bestWeight = blockWeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.higherWeightChainExists(repository, bestWeight)) {
|
|
||||||
|
|
||||||
// Check if the base block has updated since the last time we were here
|
|
||||||
if (parentSignatureForLastLowWeightBlock == null || timeOfLastLowWeightBlock == null ||
|
|
||||||
!Arrays.equals(parentSignatureForLastLowWeightBlock, previousBlockData.getSignature())) {
|
|
||||||
// We've switched to a different chain, so reset the timer
|
|
||||||
timeOfLastLowWeightBlock = NTP.getTime();
|
|
||||||
}
|
}
|
||||||
parentSignatureForLastLowWeightBlock = previousBlockData.getSignature();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If less than 30 seconds has passed since first detection the higher weight chain,
|
// If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode.
|
||||||
// we should skip our block submission to give us the opportunity to sync to the better chain
|
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||||
if (NTP.getTime() - timeOfLastLowWeightBlock < 30*1000L) {
|
if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false)
|
||||||
LOGGER.debug("Higher weight chain found in peers, so not signing a block this round");
|
continue;
|
||||||
LOGGER.debug("Time since detected: {}ms", NTP.getTime() - timeOfLastLowWeightBlock);
|
|
||||||
|
// There are enough peers with a recent block and our latest block is recent
|
||||||
|
// so go ahead and mint a block if possible.
|
||||||
|
isMintingPossible = true;
|
||||||
|
|
||||||
|
// Reattach newBlocks to new repository handle
|
||||||
|
for (Block newBlock : newBlocks)
|
||||||
|
newBlock.setRepository(repository);
|
||||||
|
|
||||||
|
// Check blockchain hasn't changed
|
||||||
|
if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) {
|
||||||
|
previousBlockData = lastBlockData;
|
||||||
|
newBlocks.clear();
|
||||||
|
|
||||||
|
// Reduce log timeout
|
||||||
|
logTimeout = 10 * 1000L;
|
||||||
|
|
||||||
|
// Last low weight block is no longer valid
|
||||||
|
parentSignatureForLastLowWeightBlock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard accounts we have already built blocks with
|
||||||
|
mintingAccountsData.removeIf(mintingAccountData -> newBlocks.stream().anyMatch(newBlock -> Arrays.equals(newBlock.getBlockData().getMinterPublicKey(), mintingAccountData.getPublicKey())));
|
||||||
|
|
||||||
|
// Do we need to build any potential new blocks?
|
||||||
|
List<PrivateKeyAccount> newBlocksMintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// We might need to sit the next block out, if one of our minting accounts signed the previous one
|
||||||
|
final byte[] previousBlockMinter = previousBlockData.getMinterPublicKey();
|
||||||
|
final boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter));
|
||||||
|
if (mintedLastBlock) {
|
||||||
|
LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentSignatureForLastLowWeightBlock != null) {
|
||||||
|
// The last iteration found a higher weight block in the network, so sleep for a while
|
||||||
|
// to allow is to sync the higher weight chain. We are sleeping here rather than when
|
||||||
|
// detected as we don't want to hold the blockchain lock open.
|
||||||
|
LOGGER.info("Sleeping for 10 seconds...");
|
||||||
|
Thread.sleep(10 * 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PrivateKeyAccount mintingAccount : newBlocksMintingAccounts) {
|
||||||
|
// First block does the AT heavy-lifting
|
||||||
|
if (newBlocks.isEmpty()) {
|
||||||
|
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
|
||||||
|
if (newBlock == null) {
|
||||||
|
// For some reason we can't mint right now
|
||||||
|
moderatedLog(() -> LOGGER.error("Couldn't build a to-be-minted block"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// More than 30 seconds have passed, so we should submit our block candidate anyway.
|
newBlocks.add(newBlock);
|
||||||
LOGGER.debug("More than 30 seconds passed, so proceeding to submit block candidate...");
|
} else {
|
||||||
|
// The blocks for other minters require less effort...
|
||||||
|
Block newBlock = newBlocks.get(0).remint(mintingAccount);
|
||||||
|
if (newBlock == null) {
|
||||||
|
// For some reason we can't mint right now
|
||||||
|
moderatedLog(() -> LOGGER.error("Couldn't rebuild a to-be-minted block"));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newBlocks.add(newBlock);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
LOGGER.debug("No higher weight chain found in peers");
|
|
||||||
}
|
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.debug("Unable to check for a higher weight chain. Proceeding anyway...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard any uncommitted changes as a result of the higher weight chain detection
|
// No potential block candidates?
|
||||||
repository.discardChanges();
|
if (newBlocks.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
// Clear variables that track low weight blocks
|
// Make sure we're the only thread modifying the blockchain
|
||||||
parentSignatureForLastLowWeightBlock = null;
|
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
timeOfLastLowWeightBlock = null;
|
if (!blockchainLock.tryLock(30, TimeUnit.SECONDS)) {
|
||||||
|
LOGGER.debug("Couldn't acquire blockchain lock even after waiting 30 seconds");
|
||||||
|
|
||||||
// Add unconfirmed transactions
|
|
||||||
addUnconfirmedTransactions(repository, newBlock);
|
|
||||||
|
|
||||||
// Sign to create block's signature
|
|
||||||
newBlock.sign();
|
|
||||||
|
|
||||||
// Is newBlock still valid?
|
|
||||||
ValidationResult validationResult = newBlock.isValid();
|
|
||||||
if (validationResult != ValidationResult.OK) {
|
|
||||||
// No longer valid? Report and discard
|
|
||||||
LOGGER.error(String.format("To-be-minted block now invalid '%s' after adding unconfirmed transactions?", validationResult.name()));
|
|
||||||
|
|
||||||
// Rebuild block candidates, just to be sure
|
|
||||||
newBlocks.clear();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to blockchain - something else will notice and broadcast new block to network
|
boolean newBlockMinted = false;
|
||||||
|
Block newBlock = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
newBlock.process();
|
// Clear repository session state so we have latest view of data
|
||||||
|
repository.discardChanges();
|
||||||
|
|
||||||
repository.saveChanges();
|
// Now that we have blockchain lock, do final check that chain hasn't changed
|
||||||
|
BlockData latestBlockData = blockRepository.getLastBlock();
|
||||||
|
if (!Arrays.equals(lastBlockData.getSignature(), latestBlockData.getSignature()))
|
||||||
|
continue;
|
||||||
|
|
||||||
LOGGER.info(String.format("Minted new block: %d", newBlock.getBlockData().getHeight()));
|
List<Block> goodBlocks = new ArrayList<>();
|
||||||
|
for (Block testBlock : newBlocks) {
|
||||||
|
// Is new block's timestamp valid yet?
|
||||||
|
// We do a separate check as some timestamp checks are skipped for testchains
|
||||||
|
if (testBlock.isTimestampValid() != ValidationResult.OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(newBlock.getBlockData().getMinterPublicKey());
|
testBlock.preProcess();
|
||||||
|
|
||||||
if (rewardShareData != null) {
|
// Is new block valid yet? (Before adding unconfirmed transactions)
|
||||||
LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s on behalf of %s",
|
ValidationResult result = testBlock.isValid();
|
||||||
newBlock.getBlockData().getHeight(),
|
if (result != ValidationResult.OK) {
|
||||||
Base58.encode(newBlock.getBlockData().getSignature()),
|
moderatedLog(() -> LOGGER.error(String.format("To-be-minted block invalid '%s' before adding transactions?", result.name())));
|
||||||
Base58.encode(newBlock.getParent().getSignature()),
|
|
||||||
rewardShareData.getMinter(),
|
continue;
|
||||||
rewardShareData.getRecipient()));
|
}
|
||||||
} else {
|
|
||||||
LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s",
|
goodBlocks.add(testBlock);
|
||||||
newBlock.getBlockData().getHeight(),
|
|
||||||
Base58.encode(newBlock.getBlockData().getSignature()),
|
|
||||||
Base58.encode(newBlock.getParent().getSignature()),
|
|
||||||
newBlock.getMinter().getAddress()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify network after we're released blockchain lock
|
if (goodBlocks.isEmpty())
|
||||||
newBlockMinted = true;
|
continue;
|
||||||
|
|
||||||
// Notify Controller
|
// Pick best block
|
||||||
repository.discardChanges(); // clear transaction status to prevent deadlocks
|
final int parentHeight = previousBlockData.getHeight();
|
||||||
Controller.getInstance().onNewBlock(newBlock.getBlockData());
|
final byte[] parentBlockSignature = previousBlockData.getSignature();
|
||||||
} catch (DataException e) {
|
|
||||||
// Unable to process block - report and discard
|
BigInteger bestWeight = null;
|
||||||
LOGGER.error("Unable to process newly minted block?", e);
|
|
||||||
newBlocks.clear();
|
for (int bi = 0; bi < goodBlocks.size(); ++bi) {
|
||||||
|
BlockData blockData = goodBlocks.get(bi).getBlockData();
|
||||||
|
|
||||||
|
BlockSummaryData blockSummaryData = new BlockSummaryData(blockData);
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockData.getMinterPublicKey());
|
||||||
|
blockSummaryData.setMinterLevel(minterLevel);
|
||||||
|
|
||||||
|
BigInteger blockWeight = Block.calcBlockWeight(parentHeight, parentBlockSignature, blockSummaryData);
|
||||||
|
|
||||||
|
if (bestWeight == null || blockWeight.compareTo(bestWeight) < 0) {
|
||||||
|
newBlock = goodBlocks.get(bi);
|
||||||
|
bestWeight = blockWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.higherWeightChainExists(repository, bestWeight)) {
|
||||||
|
|
||||||
|
// Check if the base block has updated since the last time we were here
|
||||||
|
if (parentSignatureForLastLowWeightBlock == null || timeOfLastLowWeightBlock == null ||
|
||||||
|
!Arrays.equals(parentSignatureForLastLowWeightBlock, previousBlockData.getSignature())) {
|
||||||
|
// We've switched to a different chain, so reset the timer
|
||||||
|
timeOfLastLowWeightBlock = NTP.getTime();
|
||||||
|
}
|
||||||
|
parentSignatureForLastLowWeightBlock = previousBlockData.getSignature();
|
||||||
|
|
||||||
|
// If less than 30 seconds has passed since first detection the higher weight chain,
|
||||||
|
// we should skip our block submission to give us the opportunity to sync to the better chain
|
||||||
|
if (NTP.getTime() - timeOfLastLowWeightBlock < 30 * 1000L) {
|
||||||
|
LOGGER.info("Higher weight chain found in peers, so not signing a block this round");
|
||||||
|
LOGGER.info("Time since detected: {}", NTP.getTime() - timeOfLastLowWeightBlock);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// More than 30 seconds have passed, so we should submit our block candidate anyway.
|
||||||
|
LOGGER.info("More than 30 seconds passed, so proceeding to submit block candidate...");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("No higher weight chain found in peers");
|
||||||
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.debug("Unable to check for a higher weight chain. Proceeding anyway...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard any uncommitted changes as a result of the higher weight chain detection
|
||||||
|
repository.discardChanges();
|
||||||
|
|
||||||
|
// Clear variables that track low weight blocks
|
||||||
|
parentSignatureForLastLowWeightBlock = null;
|
||||||
|
timeOfLastLowWeightBlock = null;
|
||||||
|
|
||||||
|
// Add unconfirmed transactions
|
||||||
|
addUnconfirmedTransactions(repository, newBlock);
|
||||||
|
|
||||||
|
// Sign to create block's signature
|
||||||
|
newBlock.sign();
|
||||||
|
|
||||||
|
// Is newBlock still valid?
|
||||||
|
ValidationResult validationResult = newBlock.isValid();
|
||||||
|
if (validationResult != ValidationResult.OK) {
|
||||||
|
// No longer valid? Report and discard
|
||||||
|
LOGGER.error(String.format("To-be-minted block now invalid '%s' after adding unconfirmed transactions?", validationResult.name()));
|
||||||
|
|
||||||
|
// Rebuild block candidates, just to be sure
|
||||||
|
newBlocks.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to blockchain - something else will notice and broadcast new block to network
|
||||||
|
try {
|
||||||
|
newBlock.process();
|
||||||
|
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Minted new block: %d", newBlock.getBlockData().getHeight()));
|
||||||
|
|
||||||
|
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(newBlock.getBlockData().getMinterPublicKey());
|
||||||
|
|
||||||
|
if (rewardShareData != null) {
|
||||||
|
LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s on behalf of %s",
|
||||||
|
newBlock.getBlockData().getHeight(),
|
||||||
|
Base58.encode(newBlock.getBlockData().getSignature()),
|
||||||
|
Base58.encode(newBlock.getParent().getSignature()),
|
||||||
|
rewardShareData.getMinter(),
|
||||||
|
rewardShareData.getRecipient()));
|
||||||
|
} else {
|
||||||
|
LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s",
|
||||||
|
newBlock.getBlockData().getHeight(),
|
||||||
|
Base58.encode(newBlock.getBlockData().getSignature()),
|
||||||
|
Base58.encode(newBlock.getParent().getSignature()),
|
||||||
|
newBlock.getMinter().getAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify network after we're released blockchain lock
|
||||||
|
newBlockMinted = true;
|
||||||
|
|
||||||
|
// Notify Controller
|
||||||
|
repository.discardChanges(); // clear transaction status to prevent deadlocks
|
||||||
|
Controller.getInstance().onNewBlock(newBlock.getBlockData());
|
||||||
|
} catch (DataException e) {
|
||||||
|
// Unable to process block - report and discard
|
||||||
|
LOGGER.error("Unable to process newly minted block?", e);
|
||||||
|
newBlocks.clear();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
blockchainLock.unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
blockchainLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newBlockMinted) {
|
if (newBlockMinted) {
|
||||||
// Broadcast our new chain to network
|
// Broadcast our new chain to network
|
||||||
BlockData newBlockData = newBlock.getBlockData();
|
BlockData newBlockData = newBlock.getBlockData();
|
||||||
|
|
||||||
Network network = Network.getInstance();
|
Network network = Network.getInstance();
|
||||||
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
||||||
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.warn("Repository issue while running block minter", e);
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// We've been interrupted - time to exit
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.warn("Repository issue while running block minter", e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// We've been interrupted - time to exit
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user