mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-22 20:26:50 +00:00
Improve comparing chains where some blocks signed with cancelled reward-share
Symptoms include this in logs: Unexpected zero effective minter level for reward-share %s - using 1 instead! This occurs when Synchronizer compares two sub-chains from a common block, and one of the blocks is signed by a reward-share key that has subsequently been cancelled. Although this is catered for, excessive log-spam is emited. So in addition to demoting the log level from WARN to DEBUG, more code has been added to try harder to find the actual data needed, thus preventing the logging in the first place. New repository transaction search method added to support above, along with corresponding tests.
This commit is contained in:
@@ -115,7 +115,7 @@ public class BlockMinter extends Thread {
|
||||
|
||||
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey());
|
||||
if (rewardShareData == null) {
|
||||
// Reward-share doesn't even exist - probably not a good sign
|
||||
// Reward-share doesn't exist - probably cancelled but not yet removed from node's list of minting accounts
|
||||
madi.remove();
|
||||
continue;
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import java.math.BigInteger;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -11,11 +12,13 @@ import java.util.stream.Collectors;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.Block.ValidationResult;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.transaction.RewardShareTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.BlockMessage;
|
||||
@@ -550,16 +553,34 @@ public class Synchronizer {
|
||||
}
|
||||
|
||||
private void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {
|
||||
final int firstBlockHeight = blockSummaries.get(0).getHeight();
|
||||
|
||||
for (int i = 0; i < blockSummaries.size(); ++i) {
|
||||
BlockSummaryData blockSummary = blockSummaries.get(i);
|
||||
|
||||
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockSummary.getMinterPublicKey());
|
||||
if (minterLevel == 0) {
|
||||
// We don't want to throw, or use zero, as this will kill Controller thread and make client unstable.
|
||||
// So we log this but use 1 instead
|
||||
LOGGER.warn(String.format("Unexpected zero effective minter level for reward-share %s - using 1 instead!", Base58.encode(blockSummary.getMinterPublicKey())));
|
||||
minterLevel = 1;
|
||||
// It looks like this block's minter's reward-share has been cancelled.
|
||||
// So search for REWARD_SHARE transactions since common block to find missing minter info
|
||||
List<byte[]> transactionSignatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(Transaction.TransactionType.REWARD_SHARE, null, firstBlockHeight, null);
|
||||
|
||||
for (byte[] transactionSignature : transactionSignatures) {
|
||||
RewardShareTransactionData transactionData = (RewardShareTransactionData) repository.getTransactionRepository().fromSignature(transactionSignature);
|
||||
|
||||
if (transactionData != null && Arrays.equals(transactionData.getRewardSharePublicKey(), blockSummary.getMinterPublicKey())) {
|
||||
Account rewardShareMinter = new PublicKeyAccount(repository, transactionData.getMinterPublicKey());
|
||||
minterLevel = rewardShareMinter.getEffectiveMintingLevel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (minterLevel == 0) {
|
||||
// We don't want to throw, or use zero, as this will kill Controller thread and make client unstable.
|
||||
// So we log this but use 1 instead
|
||||
LOGGER.debug(() -> String.format("Unexpected zero effective minter level for reward-share %s - using 1 instead!", Base58.encode(blockSummary.getMinterPublicKey())));
|
||||
minterLevel = 1;
|
||||
}
|
||||
}
|
||||
|
||||
blockSummary.setMinterLevel(minterLevel);
|
||||
|
@@ -91,6 +91,22 @@ public interface TransactionRepository {
|
||||
public List<byte[]> getSignaturesMatchingCriteria(TransactionType txType, byte[] publicKey,
|
||||
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signatures for transactions that match search criteria.
|
||||
* <p>
|
||||
* Simpler version that only checks accepts one (optional) transaction type,
|
||||
* and one (optional) public key, within an block height range.
|
||||
*
|
||||
* @param txType
|
||||
* @param publicKey
|
||||
* @param minBlockHeight
|
||||
* @param maxBlockHeight
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<byte[]> getSignaturesMatchingCriteria(TransactionType txType, byte[] publicKey,
|
||||
Integer minBlockHeight, Integer maxBlockHeight) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signature for latest auto-update transaction.
|
||||
* <p>
|
||||
|
@@ -586,6 +586,69 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getSignaturesMatchingCriteria(TransactionType txType, byte[] publicKey,
|
||||
Integer minBlockHeight, Integer maxBlockHeight) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
sql.append("SELECT signature FROM Transactions ");
|
||||
|
||||
List<String> whereClauses = new ArrayList<>();
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
if (txType != null) {
|
||||
whereClauses.add("type = ?");
|
||||
bindParams.add(txType.value);
|
||||
}
|
||||
|
||||
if (publicKey != null) {
|
||||
whereClauses.add("creator = ?");
|
||||
bindParams.add(publicKey);
|
||||
}
|
||||
|
||||
if (minBlockHeight != null) {
|
||||
whereClauses.add("Transactions.block_height >= ?");
|
||||
bindParams.add(minBlockHeight);
|
||||
}
|
||||
|
||||
if (maxBlockHeight != null) {
|
||||
whereClauses.add("Transactions.block_height <= ?");
|
||||
bindParams.add(maxBlockHeight);
|
||||
}
|
||||
|
||||
if (!whereClauses.isEmpty()) {
|
||||
sql.append(" WHERE ");
|
||||
|
||||
final int whereClausesSize = whereClauses.size();
|
||||
for (int wci = 0; wci < whereClausesSize; ++wci) {
|
||||
if (wci != 0)
|
||||
sql.append(" AND ");
|
||||
|
||||
sql.append(whereClauses.get(wci));
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(" ORDER BY Transactions.created_when");
|
||||
|
||||
LOGGER.trace(() -> String.format("Transaction search SQL: %s", sql));
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return signatures;
|
||||
|
||||
do {
|
||||
byte[] signature = resultSet.getBytes(1);
|
||||
|
||||
signatures.add(signature);
|
||||
} while (resultSet.next());
|
||||
|
||||
return signatures;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch matching transaction signatures from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getLatestAutoUpdateTransaction(TransactionType txType, int txGroupId, Integer service) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
|
Reference in New Issue
Block a user