mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-22 20:26:50 +00:00
Account assets balances now height-dependant. QORT-from-QORA block reward fixes.
This commit is contained in:
@@ -163,7 +163,7 @@ public class Account {
|
||||
* @throws DataException
|
||||
*/
|
||||
public void setLastReference(byte[] reference) throws DataException {
|
||||
LOGGER.trace(() -> String.format("Setting last reference for %s to %s", this.address, Base58.encode(reference)));
|
||||
LOGGER.trace(() -> String.format("Setting last reference for %s to %s", this.address, (reference == null ? "null" : Base58.encode(reference))));
|
||||
|
||||
AccountData accountData = this.buildAccountData();
|
||||
accountData.setReference(reference);
|
||||
|
@@ -1250,7 +1250,7 @@ public class Block {
|
||||
|
||||
accountData.setBlocksMinted(accountData.getBlocksMinted() + 1);
|
||||
repository.getAccountRepository().setMintedBlockCount(accountData);
|
||||
LOGGER.trace(() -> String.format("Block minted %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
|
||||
LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
|
||||
}
|
||||
|
||||
// We are only interested in accounts that are NOT founders and NOT already highest level
|
||||
@@ -1437,6 +1437,9 @@ public class Block {
|
||||
// Return AT fees and delete AT states from repository
|
||||
orphanAtFeesAndStates();
|
||||
|
||||
// Delete orphaned balances
|
||||
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
|
||||
|
||||
// Delete block from blockchain
|
||||
this.repository.getBlockRepository().delete(this.blockData);
|
||||
this.blockData.setHeight(null);
|
||||
@@ -1640,12 +1643,9 @@ public class Block {
|
||||
qoraHoldersIterator.remove();
|
||||
} else {
|
||||
// We're orphaning a block
|
||||
// so disregard qora holders whose final block is earlier than this one
|
||||
// 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)
|
||||
throw new IllegalStateException(String.format("Missing QORT-from-QORA data for %s", qoraHolder.getAddress()));
|
||||
|
||||
if (qortFromQoraData.getFinalBlockHeight() != null && qortFromQoraData.getFinalBlockHeight() < this.blockData.getHeight())
|
||||
if (qortFromQoraData != null && qortFromQoraData.getFinalBlockHeight() < this.blockData.getHeight())
|
||||
qoraHoldersIterator.remove();
|
||||
}
|
||||
}
|
||||
@@ -1660,18 +1660,14 @@ public class Block {
|
||||
for (int h = 0; h < qoraHolders.size(); ++h) {
|
||||
AccountBalanceData qoraHolder = qoraHolders.get(h);
|
||||
|
||||
final BigDecimal holderReward = qoraHoldersAmount.multiply(totalQoraHeld).divide(qoraHolder.getBalance(), RoundingMode.DOWN).setScale(8, RoundingMode.DOWN);
|
||||
BigDecimal holderReward = qoraHoldersAmount.multiply(qoraHolder.getBalance()).divide(totalQoraHeld, RoundingMode.DOWN).setScale(8, RoundingMode.DOWN);
|
||||
BigDecimal finalHolderReward = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
|
||||
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, holderReward.toPlainString()));
|
||||
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, finalHolderReward.toPlainString()));
|
||||
|
||||
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
|
||||
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
||||
if (qortFromQoraData == null)
|
||||
qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), BigDecimal.ZERO.setScale(8), null);
|
||||
|
||||
BigDecimal qortFromQora = holderReward.divide(qoraPerQortReward, RoundingMode.DOWN);
|
||||
|
||||
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(qortFromQora);
|
||||
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(holderReward);
|
||||
|
||||
// If processing, make sure we don't overpay
|
||||
if (totalAmount.signum() >= 0) {
|
||||
@@ -1681,41 +1677,39 @@ public class Block {
|
||||
// Reduce final QORT-from-QORA payment to match max
|
||||
BigDecimal adjustment = newQortFromQoraBalance.subtract(maxQortFromQora);
|
||||
|
||||
qortFromQora = qortFromQora.subtract(adjustment);
|
||||
holderReward = holderReward.subtract(adjustment);
|
||||
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
||||
|
||||
// This is also qora holders final qort-from-qora block
|
||||
qortFromQoraData.setFinalQortFromQora(qortFromQora);
|
||||
qortFromQoraData.setFinalBlockHeight(this.blockData.getHeight());
|
||||
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
|
||||
this.repository.getAccountRepository().save(qortFromQoraData);
|
||||
|
||||
BigDecimal finalQortFromQora = qortFromQora;
|
||||
BigDecimal finalAdjustedHolderReward = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s final share %s at height %d",
|
||||
qoraHolder.getAddress(), finalQortFromQora.toPlainString(), this.blockData.getHeight()));
|
||||
qoraHolder.getAddress(), finalAdjustedHolderReward.toPlainString(), this.blockData.getHeight()));
|
||||
}
|
||||
} else {
|
||||
// Orphaning
|
||||
if (qortFromQoraData.getFinalBlockHeight() != null) {
|
||||
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
||||
if (qortFromQoraData != null) {
|
||||
// Note use of negate() here as qortFromQora will be negative during orphaning,
|
||||
// but final qort-from-qora is stored in repository during processing (and hence positive).
|
||||
BigDecimal adjustment = qortFromQora.subtract(qortFromQoraData.getFinalQortFromQora().negate());
|
||||
BigDecimal adjustment = holderReward.subtract(qortFromQoraData.getFinalQortFromQora().negate());
|
||||
|
||||
qortFromQora = qortFromQora.subtract(adjustment);
|
||||
holderReward = holderReward.subtract(adjustment);
|
||||
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
|
||||
|
||||
qortFromQoraData.setFinalQortFromQora(null);
|
||||
qortFromQoraData.setFinalBlockHeight(null);
|
||||
this.repository.getAccountRepository().deleteQortFromQoraInfo(qoraHolder.getAddress());
|
||||
|
||||
BigDecimal finalQortFromQora = qortFromQora;
|
||||
BigDecimal finalAdjustedHolderReward = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s final share %s was at height %d",
|
||||
qoraHolder.getAddress(), finalQortFromQora.toPlainString(), this.blockData.getHeight()));
|
||||
qoraHolder.getAddress(), finalAdjustedHolderReward.toPlainString(), this.blockData.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(qortFromQora));
|
||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
|
||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
|
||||
|
||||
this.repository.getAccountRepository().save(qortFromQoraData);
|
||||
|
||||
sharedAmount = sharedAmount.add(holderReward);
|
||||
}
|
||||
|
||||
|
@@ -86,18 +86,26 @@ public interface AccountRepository {
|
||||
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||
|
||||
/** Returns account balance data for address & assetId at (or before) passed block height. */
|
||||
public AccountBalanceData getBalance(String address, long assetId, int height) throws DataException;
|
||||
|
||||
public enum BalanceOrdering {
|
||||
ASSET_BALANCE_ACCOUNT,
|
||||
ACCOUNT_ASSET,
|
||||
ASSET_ACCOUNT
|
||||
}
|
||||
|
||||
public List<AccountBalanceData> getAssetBalances(long assetId, Boolean excludeZero) 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 save(AccountBalanceData accountBalanceData) throws DataException;
|
||||
|
||||
public void delete(String address, long assetId) throws DataException;
|
||||
|
||||
/** Deletes orphaned balances at block height >= <tt>height</tt>. */
|
||||
public int deleteBalancesFromHeight(int height) throws DataException;
|
||||
|
||||
// Reward-shares
|
||||
|
||||
public RewardShareData getRewardShare(byte[] mintingAccountPublicKey, String recipientAccount) throws DataException;
|
||||
@@ -145,4 +153,6 @@ public interface AccountRepository {
|
||||
|
||||
public void save(QortFromQoraData qortFromQoraData) throws DataException;
|
||||
|
||||
public int deleteQortFromQoraInfo(String address) throws DataException;
|
||||
|
||||
}
|
||||
|
@@ -259,7 +259,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
|
||||
@Override
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException {
|
||||
String sql = "SELECT balance FROM AccountBalances WHERE account = ? AND asset_id = ?";
|
||||
String sql = "SELECT balance FROM AccountBalances WHERE account = ? AND asset_id = ? ORDER BY height DESC LIMIT 1";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address, assetId)) {
|
||||
if (resultSet == null)
|
||||
@@ -273,6 +273,48 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountBalanceData getBalance(String address, long assetId, int height) throws DataException {
|
||||
String sql = "SELECT balance FROM AccountBalances WHERE account = ? AND asset_id = ? AND height <= ? ORDER BY height DESC LIMIT 1";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address, assetId, height)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
BigDecimal balance = resultSet.getBigDecimal(1).setScale(8);
|
||||
|
||||
return new AccountBalanceData(address, assetId, balance);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch account balance from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountBalanceData> getAssetBalances(long assetId, Boolean excludeZero) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
sql.append("SELECT account, IFNULL(balance, 0) FROM NewestAccountBalances WHERE asset_id = ?");
|
||||
|
||||
if (excludeZero != null && excludeZero)
|
||||
sql.append(" AND balance != 0");
|
||||
|
||||
List<AccountBalanceData> accountBalances = new ArrayList<>();
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), assetId)) {
|
||||
if (resultSet == null)
|
||||
return accountBalances;
|
||||
|
||||
do {
|
||||
String address = resultSet.getString(1);
|
||||
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
|
||||
|
||||
accountBalances.add(new AccountBalanceData(address, assetId, balance));
|
||||
} while (resultSet.next());
|
||||
|
||||
return accountBalances;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch asset balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountBalanceData> getAssetBalances(List<String> addresses, List<Long> assetIds, BalanceOrdering balanceOrdering, Boolean excludeZero,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
@@ -291,10 +333,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
|
||||
sql.append(") AS Accounts (account) ");
|
||||
sql.append("CROSS JOIN Assets LEFT OUTER JOIN AccountBalances USING (asset_id, account) ");
|
||||
sql.append("CROSS JOIN Assets LEFT OUTER JOIN NewestAccountBalances USING (asset_id, account) ");
|
||||
} else {
|
||||
// Simplier, no-address query
|
||||
sql.append("AccountBalances NATURAL JOIN Assets ");
|
||||
sql.append("NewestAccountBalances NATURAL JOIN Assets ");
|
||||
}
|
||||
|
||||
if (!assetIds.isEmpty()) {
|
||||
@@ -378,6 +420,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
accountBalanceData.getBalance());
|
||||
|
||||
try {
|
||||
// Fill in 'height'
|
||||
int height = this.repository.checkedExecute("SELECT COUNT(*) + 1 FROM Blocks").getInt(1);
|
||||
saveHelper.bind("height", height);
|
||||
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save account balance into repository", e);
|
||||
@@ -387,12 +433,21 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
@Override
|
||||
public void delete(String address, long assetId) throws DataException {
|
||||
try {
|
||||
this.repository.delete("AccountBalances", "account = ? and asset_id = ?", address, assetId);
|
||||
this.repository.delete("AccountBalances", "account = ? AND asset_id = ?", address, assetId);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete account balance from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteBalancesFromHeight(int height) throws DataException {
|
||||
try {
|
||||
return this.repository.delete("AccountBalances", "height >= ?", height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete old account balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Reward-Share
|
||||
|
||||
@Override
|
||||
@@ -695,4 +750,12 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public int deleteQortFromQoraInfo(String address) throws DataException {
|
||||
try {
|
||||
return this.repository.delete("AccountQortFromQoraInfo", "account = ?", address);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete qort-from-qora info from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -847,6 +847,23 @@ public class HSQLDBDatabaseUpdates {
|
||||
+ "PRIMARY KEY (account), FOREIGN KEY (account) REFERENCES Accounts (account) ON DELETE CASCADE)");
|
||||
break;
|
||||
|
||||
case 60: // Adding height to account balances
|
||||
// We need to drop primary key first
|
||||
stmt.execute("ALTER TABLE AccountBalances DROP PRIMARY KEY");
|
||||
// Add height to account balances
|
||||
stmt.execute("ALTER TABLE AccountBalances ADD COLUMN height INT NOT NULL DEFAULT 0 BEFORE BALANCE");
|
||||
// Add new primary key
|
||||
stmt.execute("ALTER TABLE AccountBalances ADD PRIMARY KEY (asset_id, account, height)");
|
||||
/// Create a view for account balances at greatest height
|
||||
stmt.execute("CREATE VIEW NewestAccountBalances (account, asset_id, balance) AS "
|
||||
+ "SELECT AccountBalances.account, AccountBalances.asset_id, AccountBalances.balance FROM AccountBalances "
|
||||
+ "LEFT OUTER JOIN AccountBalances AS NewerAccountBalances "
|
||||
+ "ON NewerAccountBalances.account = AccountBalances.account "
|
||||
+ "AND NewerAccountBalances.asset_id = AccountBalances.asset_id "
|
||||
+ "AND NewerAccountBalances.height > AccountBalances.height "
|
||||
+ "WHERE NewerAccountBalances.height IS NULL");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
Reference in New Issue
Block a user