forked from Qortal/qortal
Reworking/speed-ups for block rewards & general account DB manipulation
**NOTE** currently under wider test - maybe not be final version!
This commit is contained in:
parent
f7e2ee383e
commit
d30d61edab
@ -9,7 +9,6 @@ import java.math.RoundingMode;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -126,31 +125,43 @@ public class Block {
|
|||||||
protected BigDecimal ourAtFees; // Generated locally
|
protected BigDecimal ourAtFees; // Generated locally
|
||||||
|
|
||||||
/** Lazy-instantiated expanded info on block's online accounts. */
|
/** Lazy-instantiated expanded info on block's online accounts. */
|
||||||
class ExpandedAccount {
|
static class ExpandedAccount {
|
||||||
final RewardShareData rewardShareData;
|
private static final BigDecimal oneHundred = BigDecimal.valueOf(100L);
|
||||||
final boolean isRecipientAlsoMinter;
|
|
||||||
|
|
||||||
final Account mintingAccount;
|
private final Repository repository;
|
||||||
final AccountData mintingAccountData;
|
|
||||||
final boolean isMinterFounder;
|
|
||||||
|
|
||||||
final Account recipientAccount;
|
private final RewardShareData rewardShareData;
|
||||||
final AccountData recipientAccountData;
|
private final boolean isRecipientAlsoMinter;
|
||||||
final boolean isRecipientFounder;
|
|
||||||
|
private final Account mintingAccount;
|
||||||
|
private final AccountData mintingAccountData;
|
||||||
|
private final boolean isMinterFounder;
|
||||||
|
|
||||||
|
private final Account recipientAccount;
|
||||||
|
private final AccountData recipientAccountData;
|
||||||
|
private final boolean isRecipientFounder;
|
||||||
|
|
||||||
ExpandedAccount(Repository repository, int accountIndex) throws DataException {
|
ExpandedAccount(Repository repository, int accountIndex) throws DataException {
|
||||||
|
this.repository = repository;
|
||||||
this.rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
|
this.rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
|
||||||
|
|
||||||
this.mintingAccount = new PublicKeyAccount(repository, this.rewardShareData.getMinterPublicKey());
|
this.mintingAccount = new PublicKeyAccount(repository, this.rewardShareData.getMinterPublicKey());
|
||||||
this.recipientAccount = new Account(repository, this.rewardShareData.getRecipient());
|
|
||||||
|
|
||||||
this.mintingAccountData = repository.getAccountRepository().getAccount(this.mintingAccount.getAddress());
|
this.mintingAccountData = repository.getAccountRepository().getAccount(this.mintingAccount.getAddress());
|
||||||
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
||||||
|
|
||||||
this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress());
|
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
|
||||||
this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags());
|
|
||||||
|
|
||||||
this.isRecipientAlsoMinter = this.mintingAccountData.getAddress().equals(this.recipientAccountData.getAddress());
|
if (this.isRecipientAlsoMinter) {
|
||||||
|
// Self-share: minter is also recipient
|
||||||
|
this.recipientAccount = this.mintingAccount;
|
||||||
|
this.recipientAccountData = this.mintingAccountData;
|
||||||
|
this.isRecipientFounder = this.isMinterFounder;
|
||||||
|
} else {
|
||||||
|
// Recipient differs from minter
|
||||||
|
this.recipientAccount = new Account(repository, this.rewardShareData.getRecipient());
|
||||||
|
this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress());
|
||||||
|
this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,22 +187,26 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void distribute(BigDecimal accountAmount) throws DataException {
|
void distribute(BigDecimal accountAmount) throws DataException {
|
||||||
final BigDecimal oneHundred = BigDecimal.valueOf(100L);
|
if (this.isRecipientAlsoMinter) {
|
||||||
|
|
||||||
if (this.mintingAccount.getAddress().equals(this.recipientAccount.getAddress())) {
|
|
||||||
// minter & recipient the same - simpler case
|
// minter & recipient the same - simpler case
|
||||||
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), accountAmount.toPlainString()));
|
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), accountAmount.toPlainString()));
|
||||||
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
|
if (accountAmount.signum() != 0)
|
||||||
|
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
|
||||||
|
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount);
|
||||||
} else {
|
} else {
|
||||||
// minter & recipient different - extra work needed
|
// minter & recipient different - extra work needed
|
||||||
BigDecimal recipientAmount = accountAmount.multiply(this.rewardShareData.getSharePercent()).divide(oneHundred, RoundingMode.DOWN);
|
BigDecimal recipientAmount = accountAmount.multiply(this.rewardShareData.getSharePercent()).divide(oneHundred, RoundingMode.DOWN);
|
||||||
BigDecimal minterAmount = accountAmount.subtract(recipientAmount);
|
BigDecimal minterAmount = accountAmount.subtract(recipientAmount);
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), minterAmount.toPlainString()));
|
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), minterAmount.toPlainString()));
|
||||||
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
|
if (minterAmount.signum() != 0)
|
||||||
|
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
|
||||||
|
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, minterAmount);
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString()));
|
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString()));
|
||||||
this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
|
if (recipientAmount.signum() != 0)
|
||||||
|
// this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
|
||||||
|
this.repository.getAccountRepository().modifyAssetBalance(this.recipientAccount.getAddress(), Asset.QORT, recipientAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1256,8 +1271,9 @@ public class Block {
|
|||||||
AccountData accountData = getAccountData.apply(expandedAccount);
|
AccountData accountData = getAccountData.apply(expandedAccount);
|
||||||
|
|
||||||
accountData.setBlocksMinted(accountData.getBlocksMinted() + 1);
|
accountData.setBlocksMinted(accountData.getBlocksMinted() + 1);
|
||||||
repository.getAccountRepository().setMintedBlockCount(accountData);
|
// repository.getAccountRepository().setMintedBlockCount(accountData); int rowCount = 1; // Until HSQLDB rev 6100 is fixed
|
||||||
LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
|
int rowCount = repository.getAccountRepository().modifyMintedBlockCount(accountData.getAddress(), +1);
|
||||||
|
LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s (rowCount: %d)", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""), rowCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are only interested in accounts that are NOT already highest level
|
// We are only interested in accounts that are NOT already highest level
|
||||||
@ -1425,35 +1441,40 @@ public class Block {
|
|||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
|
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
|
||||||
|
|
||||||
// Return AT fees and delete AT states from repository
|
this.repository.setDebug(false);
|
||||||
orphanAtFeesAndStates();
|
try {
|
||||||
|
// Return AT fees and delete AT states from repository
|
||||||
|
orphanAtFeesAndStates();
|
||||||
|
|
||||||
// Orphan, and unlink, transactions from this block
|
// Orphan, and unlink, transactions from this block
|
||||||
orphanTransactionsFromBlock();
|
orphanTransactionsFromBlock();
|
||||||
|
|
||||||
// Undo any group-approval decisions that happen at this block
|
// Undo any group-approval decisions that happen at this block
|
||||||
orphanGroupApprovalTransactions();
|
orphanGroupApprovalTransactions();
|
||||||
|
|
||||||
if (this.blockData.getHeight() > 1) {
|
if (this.blockData.getHeight() > 1) {
|
||||||
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
||||||
this.cachedExpandedAccounts = null;
|
this.cachedExpandedAccounts = null;
|
||||||
|
|
||||||
// Deduct any transaction fees from minter/reward-share account(s)
|
// Deduct any transaction fees from minter/reward-share account(s)
|
||||||
deductTransactionFees();
|
deductTransactionFees();
|
||||||
|
|
||||||
// Block rewards removed after transactions undone
|
// Block rewards removed after transactions undone
|
||||||
orphanBlockRewards();
|
orphanBlockRewards();
|
||||||
|
|
||||||
// Decrease account levels
|
// Decrease account levels
|
||||||
decreaseAccountLevels();
|
decreaseAccountLevels();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete orphaned balances
|
||||||
|
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
|
||||||
|
|
||||||
|
// Delete block from blockchain
|
||||||
|
this.repository.getBlockRepository().delete(this.blockData);
|
||||||
|
this.blockData.setHeight(null);
|
||||||
|
} finally {
|
||||||
|
this.repository.setDebug(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete orphaned balances
|
|
||||||
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
|
|
||||||
|
|
||||||
// Delete block from blockchain
|
|
||||||
this.repository.getBlockRepository().delete(this.blockData);
|
|
||||||
this.blockData.setHeight(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void orphanTransactionsFromBlock() throws DataException {
|
protected void orphanTransactionsFromBlock() throws DataException {
|
||||||
@ -1571,8 +1592,9 @@ public class Block {
|
|||||||
AccountData accountData = getAccountData.apply(expandedAccount);
|
AccountData accountData = getAccountData.apply(expandedAccount);
|
||||||
|
|
||||||
accountData.setBlocksMinted(accountData.getBlocksMinted() - 1);
|
accountData.setBlocksMinted(accountData.getBlocksMinted() - 1);
|
||||||
repository.getAccountRepository().setMintedBlockCount(accountData);
|
// repository.getAccountRepository().setMintedBlockCount(accountData); int rowCount = 1; // Until HSQLDB rev 6100 is fixed
|
||||||
LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
|
int rowCount = repository.getAccountRepository().modifyMintedBlockCount(accountData.getAddress(), -1);
|
||||||
|
LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s (rowCount: %d)", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""), rowCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are only interested in accounts that are NOT already lowest level
|
// We are only interested in accounts that are NOT already lowest level
|
||||||
@ -1602,8 +1624,22 @@ public class Block {
|
|||||||
protected void distributeBlockReward(BigDecimal totalAmount) throws DataException {
|
protected void distributeBlockReward(BigDecimal totalAmount) throws DataException {
|
||||||
LOGGER.trace(() -> String.format("Distributing: %s", totalAmount.toPlainString()));
|
LOGGER.trace(() -> String.format("Distributing: %s", totalAmount.toPlainString()));
|
||||||
|
|
||||||
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
// Distribute according to account level
|
||||||
|
BigDecimal sharedByLevelAmount = distributeBlockRewardByLevel(totalAmount);
|
||||||
|
LOGGER.trace(() -> String.format("Shared %s of %s based on account levels", sharedByLevelAmount.toPlainString(), totalAmount.toPlainString()));
|
||||||
|
|
||||||
|
// Distribute amongst legacy QORA holders
|
||||||
|
BigDecimal sharedByQoraHoldersAmount = distributeBlockRewardToQoraHolders(totalAmount);
|
||||||
|
LOGGER.trace(() -> String.format("Shared %s of %s to legacy QORA holders", sharedByQoraHoldersAmount.toPlainString(), totalAmount.toPlainString()));
|
||||||
|
|
||||||
|
// Spread remainder across founder accounts
|
||||||
|
BigDecimal foundersAmount = totalAmount.subtract(sharedByLevelAmount).subtract(sharedByQoraHoldersAmount);
|
||||||
|
distributeBlockRewardToFounders(foundersAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal distributeBlockRewardByLevel(BigDecimal totalAmount) throws DataException {
|
||||||
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
|
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
||||||
|
|
||||||
// Distribute amount across bins
|
// Distribute amount across bins
|
||||||
BigDecimal sharedAmount = BigDecimal.ZERO;
|
BigDecimal sharedAmount = BigDecimal.ZERO;
|
||||||
@ -1628,36 +1664,17 @@ public class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distribute share across legacy QORA holders
|
return sharedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal distributeBlockRewardToQoraHolders(BigDecimal totalAmount) throws DataException {
|
||||||
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
|
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
|
||||||
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
|
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
|
||||||
|
|
||||||
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getAssetBalances(Asset.LEGACY_QORA, true);
|
final boolean isProcessingNotOrphaning = totalAmount.signum() >= 0;
|
||||||
|
|
||||||
// Filter out qoraHolders who have received max QORT due to holding legacy QORA, (ratio from blockchain config)
|
|
||||||
BigDecimal qoraPerQortReward = BlockChain.getInstance().getQoraPerQortReward();
|
BigDecimal qoraPerQortReward = BlockChain.getInstance().getQoraPerQortReward();
|
||||||
Iterator<AccountBalanceData> qoraHoldersIterator = qoraHolders.iterator();
|
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
|
||||||
while (qoraHoldersIterator.hasNext()) {
|
|
||||||
AccountBalanceData qoraHolder = qoraHoldersIterator.next();
|
|
||||||
|
|
||||||
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
|
|
||||||
BigDecimal qortFromQora = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA);
|
|
||||||
|
|
||||||
// If we're processing a block, then totalAmount will be positive
|
|
||||||
if (totalAmount.signum() >= 0) {
|
|
||||||
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
|
|
||||||
|
|
||||||
// Disregard qora holders who have already received maximum qort from holding legacy qora
|
|
||||||
if (qortFromQora.compareTo(maxQortFromQora) >= 0)
|
|
||||||
qoraHoldersIterator.remove();
|
|
||||||
} else {
|
|
||||||
// We're orphaning a block
|
|
||||||
// so disregard qora holders who have already had their final qort-from-qora reward (i.e. reward reward block is earlier than this one)
|
|
||||||
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
|
||||||
if (qortFromQoraData != null && qortFromQoraData.getFinalBlockHeight() < this.blockData.getHeight())
|
|
||||||
qoraHoldersIterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal totalQoraHeld = BigDecimal.ZERO;
|
BigDecimal totalQoraHeld = BigDecimal.ZERO;
|
||||||
for (int i = 0; i < qoraHolders.size(); ++i)
|
for (int i = 0; i < qoraHolders.size(); ++i)
|
||||||
@ -1666,6 +1683,7 @@ public class Block {
|
|||||||
BigDecimal finalTotalQoraHeld = totalQoraHeld;
|
BigDecimal finalTotalQoraHeld = totalQoraHeld;
|
||||||
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
|
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
|
||||||
|
|
||||||
|
BigDecimal sharedAmount = BigDecimal.ZERO;
|
||||||
for (int h = 0; h < qoraHolders.size(); ++h) {
|
for (int h = 0; h < qoraHolders.size(); ++h) {
|
||||||
AccountBalanceData qoraHolder = qoraHolders.get(h);
|
AccountBalanceData qoraHolder = qoraHolders.get(h);
|
||||||
|
|
||||||
@ -1674,12 +1692,16 @@ public class Block {
|
|||||||
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
|
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
|
||||||
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, finalHolderReward.toPlainString()));
|
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, finalHolderReward.toPlainString()));
|
||||||
|
|
||||||
|
// Too small to register this time?
|
||||||
|
if (holderReward.signum() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
|
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
|
||||||
|
|
||||||
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(holderReward);
|
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(holderReward);
|
||||||
|
|
||||||
// If processing, make sure we don't overpay
|
// If processing, make sure we don't overpay
|
||||||
if (totalAmount.signum() >= 0) {
|
if (isProcessingNotOrphaning) {
|
||||||
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
|
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
|
||||||
|
|
||||||
if (newQortFromQoraBalance.compareTo(maxQortFromQora) >= 0) {
|
if (newQortFromQoraBalance.compareTo(maxQortFromQora) >= 0) {
|
||||||
@ -1689,7 +1711,7 @@ public class Block {
|
|||||||
holderReward = holderReward.subtract(adjustment);
|
holderReward = holderReward.subtract(adjustment);
|
||||||
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
||||||
|
|
||||||
// This is also qora holders final qort-from-qora block
|
// This is also the QORA holder's final QORT-from-QORA block
|
||||||
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
|
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
|
||||||
this.repository.getAccountRepository().save(qortFromQoraData);
|
this.repository.getAccountRepository().save(qortFromQoraData);
|
||||||
|
|
||||||
@ -1701,9 +1723,10 @@ public class Block {
|
|||||||
// Orphaning
|
// Orphaning
|
||||||
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
||||||
if (qortFromQoraData != null) {
|
if (qortFromQoraData != null) {
|
||||||
// Note use of negate() here as qortFromQora will be negative during orphaning,
|
// Final QORT-from-QORA amount from repository was stored during processing, and hence positive.
|
||||||
// but final qort-from-qora is stored in repository during processing (and hence positive).
|
// So we use add() here as qortFromQora is negative during orphaning.
|
||||||
BigDecimal adjustment = holderReward.subtract(qortFromQoraData.getFinalQortFromQora().negate());
|
// More efficient than holderReward.subtract(final-qort-from-qora.negate())
|
||||||
|
BigDecimal adjustment = holderReward.add(qortFromQoraData.getFinalQortFromQora());
|
||||||
|
|
||||||
holderReward = holderReward.subtract(adjustment);
|
holderReward = holderReward.subtract(adjustment);
|
||||||
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
||||||
@ -1716,7 +1739,8 @@ public class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
|
// qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
|
||||||
|
this.repository.getAccountRepository().modifyAssetBalance(qoraHolder.getAddress(), Asset.QORT, holderReward);
|
||||||
|
|
||||||
if (newQortFromQoraBalance.signum() > 0)
|
if (newQortFromQoraBalance.signum() > 0)
|
||||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
|
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
|
||||||
@ -1727,27 +1751,39 @@ public class Block {
|
|||||||
sharedAmount = sharedAmount.add(holderReward);
|
sharedAmount = sharedAmount.add(holderReward);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spread remainder across founder accounts
|
return sharedAmount;
|
||||||
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
|
}
|
||||||
BigDecimal finalSharedAmount = sharedAmount;
|
|
||||||
|
|
||||||
|
private void distributeBlockRewardToFounders(BigDecimal foundersAmount) throws DataException {
|
||||||
|
// Remaining reward portion is spread across all founders, online or not
|
||||||
List<AccountData> founderAccounts = this.repository.getAccountRepository().getFlaggedAccounts(Account.FOUNDER_FLAG);
|
List<AccountData> founderAccounts = this.repository.getAccountRepository().getFlaggedAccounts(Account.FOUNDER_FLAG);
|
||||||
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
|
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
|
||||||
BigDecimal perFounderAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
|
BigDecimal perFounderAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Shared %s of %s, remaining %s to %d founder%s, %s each",
|
LOGGER.trace(() -> String.format("Sharing remaining %s to %d founder%s, %s each",
|
||||||
finalSharedAmount.toPlainString(), totalAmount.toPlainString(),
|
|
||||||
foundersAmount.toPlainString(), founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
|
foundersAmount.toPlainString(), founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
|
||||||
perFounderAmount.toPlainString()));
|
perFounderAmount.toPlainString()));
|
||||||
|
|
||||||
|
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
for (int a = 0; a < founderAccounts.size(); ++a) {
|
for (int a = 0; a < founderAccounts.size(); ++a) {
|
||||||
|
Account founderAccount = new Account(this.repository, founderAccounts.get(a).getAddress());
|
||||||
|
|
||||||
// If founder is minter in any online reward-shares then founder's amount is spread across these, otherwise founder gets whole amount.
|
// If founder is minter in any online reward-shares then founder's amount is spread across these, otherwise founder gets whole amount.
|
||||||
|
|
||||||
|
/* Fixed version:
|
||||||
|
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(
|
||||||
|
accountInfo -> accountInfo.isMinterFounder &&
|
||||||
|
accountInfo.mintingAccountData.getAddress().equals(founderAccount.getAddress())
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Broken version:
|
||||||
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isMinterFounder).collect(Collectors.toList());
|
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isMinterFounder).collect(Collectors.toList());
|
||||||
|
|
||||||
if (founderExpandedAccounts.isEmpty()) {
|
if (founderExpandedAccounts.isEmpty()) {
|
||||||
// Simple case: no founder-as-minter reward-shares online so founder gets whole amount.
|
// Simple case: no founder-as-minter reward-shares online so founder gets whole amount.
|
||||||
Account founderAccount = new Account(this.repository, founderAccounts.get(a).getAddress());
|
// founderAccount.setConfirmedBalance(Asset.QORT, founderAccount.getConfirmedBalance(Asset.QORT).add(perFounderAmount));
|
||||||
founderAccount.setConfirmedBalance(Asset.QORT, founderAccount.getConfirmedBalance(Asset.QORT).add(perFounderAmount));
|
this.repository.getAccountRepository().modifyAssetBalance(founderAccount.getAddress(), Asset.QORT, perFounderAmount);
|
||||||
} else {
|
} else {
|
||||||
// Distribute over reward-shares
|
// Distribute over reward-shares
|
||||||
BigDecimal perFounderRewardShareAmount = perFounderAmount.divide(BigDecimal.valueOf(founderExpandedAccounts.size()), RoundingMode.DOWN);
|
BigDecimal perFounderRewardShareAmount = perFounderAmount.divide(BigDecimal.valueOf(founderExpandedAccounts.size()), RoundingMode.DOWN);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.qortal.repository;
|
package org.qortal.repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
@ -82,6 +83,12 @@ public interface AccountRepository {
|
|||||||
*/
|
*/
|
||||||
public void setMintedBlockCount(AccountData accountData) throws DataException;
|
public void setMintedBlockCount(AccountData accountData) throws DataException;
|
||||||
|
|
||||||
|
/** Modifies account's minted block count only.
|
||||||
|
* <p>
|
||||||
|
* @return 2 if minted block count updated, 1 if block count set to delta, 0 if address not found.
|
||||||
|
*/
|
||||||
|
public int modifyMintedBlockCount(String address, int delta) throws DataException;
|
||||||
|
|
||||||
/** Delete account from repository. */
|
/** Delete account from repository. */
|
||||||
public void delete(String address) throws DataException;
|
public void delete(String address) throws DataException;
|
||||||
|
|
||||||
@ -105,6 +112,8 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
public List<AccountBalanceData> getAssetBalances(List<String> addresses, List<Long> assetIds, BalanceOrdering balanceOrdering, Boolean excludeZero, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<AccountBalanceData> getAssetBalances(List<String> addresses, List<Long> assetIds, BalanceOrdering balanceOrdering, Boolean excludeZero, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException;
|
||||||
|
|
||||||
public void save(AccountBalanceData accountBalanceData) throws DataException;
|
public void save(AccountBalanceData accountBalanceData) throws DataException;
|
||||||
|
|
||||||
public void delete(String address, long assetId) throws DataException;
|
public void delete(String address, long assetId) throws DataException;
|
||||||
@ -155,6 +164,21 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
// Managing QORT from legacy QORA
|
// Managing QORT from legacy QORA
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns balance data for accounts with legacy QORA asset that are eligible
|
||||||
|
* for more block reward (block processing) or for block reward removal (block orphaning).
|
||||||
|
* <p>
|
||||||
|
* For block processing, accounts that have already received their final QORT reward for owning
|
||||||
|
* legacy QORA are omitted from the results. <tt>blockHeight</tt> should be <tt>null</tt>.
|
||||||
|
* <p>
|
||||||
|
* For block orphaning, accounts that did not receive a QORT reward at <tt>blockHeight</tt>
|
||||||
|
* are omitted from the results.
|
||||||
|
*
|
||||||
|
* @param blockHeight QORT reward must have be present at this height (for orphaning only)
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public List<AccountBalanceData> getEligibleLegacyQoraHolders(Integer blockHeight) throws DataException;
|
||||||
|
|
||||||
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException;
|
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException;
|
||||||
|
|
||||||
public void save(QortFromQoraData qortFromQoraData) throws DataException;
|
public void save(QortFromQoraData qortFromQoraData) throws DataException;
|
||||||
|
@ -7,6 +7,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.account.MintingAccountData;
|
import org.qortal.data.account.MintingAccountData;
|
||||||
@ -144,6 +145,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void ensureAccount(AccountData accountData) throws DataException {
|
public void ensureAccount(AccountData accountData) throws DataException {
|
||||||
|
/*
|
||||||
|
* Why do we need to check/set the public_key?
|
||||||
|
* Is there something that sets an account's balance which also needs to set the public key?
|
||||||
|
|
||||||
byte[] publicKey = accountData.getPublicKey();
|
byte[] publicKey = accountData.getPublicKey();
|
||||||
String sql = "SELECT public_key FROM Accounts WHERE account = ?";
|
String sql = "SELECT public_key FROM Accounts WHERE account = ?";
|
||||||
|
|
||||||
@ -168,6 +173,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to ensure minimal account in repository", e);
|
throw new DataException("Unable to ensure minimal account in repository", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
String sql = "INSERT IGNORE INTO Accounts (account) VALUES (?)"; // MySQL syntax
|
||||||
|
try {
|
||||||
|
this.repository.checkedExecuteUpdateCount(sql, accountData.getAddress());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to ensure minimal account in repository", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -273,6 +287,18 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int modifyMintedBlockCount(String address, int delta) throws DataException {
|
||||||
|
String sql = "INSERT INTO Accounts (account, blocks_minted) VALUES (?, ?) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE blocks_minted = blocks_minted + ?";
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.repository.checkedExecuteUpdateCount(sql, address, delta, delta);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to modify account's minted block count in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String address) throws DataException {
|
public void delete(String address) throws DataException {
|
||||||
// NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY
|
// NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY
|
||||||
@ -470,6 +496,54 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException {
|
||||||
|
// If deltaBalance is zero then do nothing
|
||||||
|
if (deltaBalance.signum() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If deltaBalance is negative then we assume AccountBalances & parent Accounts rows exist
|
||||||
|
if (deltaBalance.signum() < 0) {
|
||||||
|
// Perform actual balance change
|
||||||
|
String sql = "UPDATE AccountBalances set balance = balance + ? WHERE account = ? AND asset_id = ?";
|
||||||
|
try {
|
||||||
|
this.repository.checkedExecuteUpdateCount(sql, deltaBalance, address, assetId);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to reduce account balance in repository", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If balance is now zero, and there are no prior historic balances, then simply delete row for this address-assetId (typically during orphaning)
|
||||||
|
String deleteWhereSql = "account = ? AND asset_id = ? AND balance = 0 " + // covers "if balance now zero"
|
||||||
|
"AND (" +
|
||||||
|
"SELECT TRUE FROM HistoricAccountBalances " +
|
||||||
|
"WHERE account = ? AND asset_id = ? AND height < (SELECT height - 1 FROM NextBlockHeight) " +
|
||||||
|
"LIMIT 1" +
|
||||||
|
")";
|
||||||
|
try {
|
||||||
|
this.repository.delete("AccountBalances", deleteWhereSql, address, assetId, address, assetId);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to prune account balance in repository", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We have to ensure parent row exists to satisfy foreign key constraint
|
||||||
|
try {
|
||||||
|
String sql = "INSERT IGNORE INTO Accounts (account) VALUES (?)"; // MySQL syntax
|
||||||
|
this.repository.checkedExecuteUpdateCount(sql, address);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to ensure minimal account in repository", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform actual balance change
|
||||||
|
String sql = "INSERT INTO AccountBalances (account, asset_id, balance) VALUES (?, ?, ?) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE balance = balance + ?";
|
||||||
|
try {
|
||||||
|
this.repository.checkedExecuteUpdateCount(sql, address, assetId, deltaBalance, deltaBalance);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to increase account balance in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(AccountBalanceData accountBalanceData) throws DataException {
|
public void save(AccountBalanceData accountBalanceData) throws DataException {
|
||||||
// If balance is zero and there are no prior historic balance, then simply delete balances for this assetId (typically during orphaning)
|
// If balance is zero and there are no prior historic balance, then simply delete balances for this assetId (typically during orphaning)
|
||||||
@ -490,13 +564,17 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
throw new DataException("Unable to delete account balance from repository", e);
|
throw new DataException("Unable to delete account balance from repository", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// I don't think we need to do this as Block.orphan() would do this for us?
|
/*
|
||||||
|
* I don't think we need to do this as Block.orphan() would do this for us?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.repository.delete("HistoricAccountBalances", "account = ? AND asset_id = ?", accountBalanceData.getAddress(), accountBalanceData.getAssetId());
|
this.repository.delete("HistoricAccountBalances", "account = ? AND asset_id = ?", accountBalanceData.getAddress(), accountBalanceData.getAssetId());
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to delete historic account balances from repository", e);
|
throw new DataException("Unable to delete historic account balances from repository", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -768,6 +846,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
|
|
||||||
// Minting accounts used by BlockMinter
|
// Minting accounts used by BlockMinter
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<MintingAccountData> getMintingAccounts() throws DataException {
|
public List<MintingAccountData> getMintingAccounts() throws DataException {
|
||||||
List<MintingAccountData> mintingAccounts = new ArrayList<>();
|
List<MintingAccountData> mintingAccounts = new ArrayList<>();
|
||||||
|
|
||||||
@ -787,6 +866,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void save(MintingAccountData mintingAccountData) throws DataException {
|
public void save(MintingAccountData mintingAccountData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("MintingAccounts");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("MintingAccounts");
|
||||||
|
|
||||||
@ -799,6 +879,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int delete(byte[] minterPrivateKey) throws DataException {
|
public int delete(byte[] minterPrivateKey) throws DataException {
|
||||||
try {
|
try {
|
||||||
return this.repository.delete("MintingAccounts", "minter_private_key = ?", minterPrivateKey);
|
return this.repository.delete("MintingAccounts", "minter_private_key = ?", minterPrivateKey);
|
||||||
@ -809,6 +890,42 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
|
|
||||||
// Managing QORT from legacy QORA
|
// Managing QORT from legacy QORA
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AccountBalanceData> getEligibleLegacyQoraHolders(Integer blockHeight) throws DataException {
|
||||||
|
StringBuilder sql = new StringBuilder(1024);
|
||||||
|
sql.append("SELECT account, balance from AccountBalances ");
|
||||||
|
sql.append("LEFT OUTER JOIN AccountQortFromQoraInfo USING (account) ");
|
||||||
|
sql.append("WHERE asset_id = ");
|
||||||
|
sql.append(Asset.LEGACY_QORA); // int is safe to use literally
|
||||||
|
sql.append(" AND (final_block_height IS NULL");
|
||||||
|
|
||||||
|
if (blockHeight != null) {
|
||||||
|
sql.append(" OR final_block_height >= ");
|
||||||
|
sql.append(blockHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(")");
|
||||||
|
|
||||||
|
List<AccountBalanceData> accountBalances = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return accountBalances;
|
||||||
|
|
||||||
|
do {
|
||||||
|
String address = resultSet.getString(1);
|
||||||
|
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
|
||||||
|
|
||||||
|
accountBalances.add(new AccountBalanceData(address, Asset.LEGACY_QORA, balance));
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return accountBalances;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch eligible legacy QORA holders from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException {
|
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException {
|
||||||
String sql = "SELECT final_qort_from_qora, final_block_height FROM AccountQortFromQoraInfo WHERE account = ?";
|
String sql = "SELECT final_qort_from_qora, final_block_height FROM AccountQortFromQoraInfo WHERE account = ?";
|
||||||
|
|
||||||
@ -827,6 +944,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void save(QortFromQoraData qortFromQoraData) throws DataException {
|
public void save(QortFromQoraData qortFromQoraData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountQortFromQoraInfo");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountQortFromQoraInfo");
|
||||||
|
|
||||||
@ -841,6 +959,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int deleteQortFromQoraInfo(String address) throws DataException {
|
public int deleteQortFromQoraInfo(String address) throws DataException {
|
||||||
try {
|
try {
|
||||||
return this.repository.delete("AccountQortFromQoraInfo", "account = ?", address);
|
return this.repository.delete("AccountQortFromQoraInfo", "account = ?", address);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user