forked from Qortal/qortal
Qortal-style block rewards
This commit is contained in:
parent
a0be1273c7
commit
a1e83109a8
@ -21,6 +21,7 @@ public class Account {
|
|||||||
private static final Logger LOGGER = LogManager.getLogger(Account.class);
|
private static final Logger LOGGER = LogManager.getLogger(Account.class);
|
||||||
|
|
||||||
public static final int ADDRESS_LENGTH = 25;
|
public static final int ADDRESS_LENGTH = 25;
|
||||||
|
public static final int FOUNDER_FLAG = 0x1;
|
||||||
|
|
||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
protected String address;
|
protected String address;
|
||||||
@ -202,8 +203,18 @@ public class Account {
|
|||||||
this.repository.getAccountRepository().setFlags(accountData);
|
this.repository.getAccountRepository().setFlags(accountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFounder() throws DataException {
|
||||||
|
Integer flags = this.getFlags();
|
||||||
|
return flags != null && (flags & FOUNDER_FLAG) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Forging Enabler
|
// Forging Enabler
|
||||||
|
|
||||||
|
public boolean canForge() throws DataException {
|
||||||
|
Integer level = this.getLevel();
|
||||||
|
return level != null && level > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void setForgingEnabler(String address) throws DataException {
|
public void setForgingEnabler(String address) throws DataException {
|
||||||
AccountData accountData = this.buildAccountData();
|
AccountData accountData = this.buildAccountData();
|
||||||
accountData.setForgingEnabler(address);
|
accountData.setForgingEnabler(address);
|
||||||
|
@ -23,6 +23,7 @@ import org.qora.asset.Asset;
|
|||||||
import org.qora.at.AT;
|
import org.qora.at.AT;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.block.BlockChain.BlockTimingByHeight;
|
import org.qora.block.BlockChain.BlockTimingByHeight;
|
||||||
|
import org.qora.block.BlockChain.ShareByLevel;
|
||||||
import org.qora.controller.Controller;
|
import org.qora.controller.Controller;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.ProxyForgerData;
|
import org.qora.data.account.ProxyForgerData;
|
||||||
@ -1057,6 +1058,9 @@ public class Block {
|
|||||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
this.blockData.setHeight(blockchainHeight + 1);
|
this.blockData.setHeight(blockchainHeight + 1);
|
||||||
|
|
||||||
|
// Increase account levels
|
||||||
|
increaseAccountLevels();
|
||||||
|
|
||||||
// Block rewards go before transactions processed
|
// Block rewards go before transactions processed
|
||||||
processBlockRewards();
|
processBlockRewards();
|
||||||
|
|
||||||
@ -1085,6 +1089,10 @@ public class Block {
|
|||||||
linkTransactionsToBlock();
|
linkTransactionsToBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void increaseAccountLevels() throws DataException {
|
||||||
|
// TODO!
|
||||||
|
}
|
||||||
|
|
||||||
protected void processBlockRewards() throws DataException {
|
protected void processBlockRewards() throws DataException {
|
||||||
BigDecimal reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
|
BigDecimal reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
|
||||||
|
|
||||||
@ -1092,22 +1100,7 @@ public class Block {
|
|||||||
if (reward == null)
|
if (reward == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Is generator public key actually a proxy forge key?
|
distributeByAccountLevel(reward);
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
|
||||||
if (proxyForgerData != null) {
|
|
||||||
// Split reward between forger and recipient
|
|
||||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
|
||||||
BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
|
||||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(recipientShare));
|
|
||||||
|
|
||||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
|
||||||
BigDecimal forgerShare = reward.subtract(recipientShare);
|
|
||||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(forgerShare));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give block reward to generator
|
|
||||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(reward));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processTransactions() throws DataException {
|
protected void processTransactions() throws DataException {
|
||||||
@ -1190,22 +1183,7 @@ public class Block {
|
|||||||
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
|
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Is generator public key actually a proxy forge key?
|
distributeByAccountLevel(blockFees);
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
|
||||||
if (proxyForgerData != null) {
|
|
||||||
// Split fees between forger and recipient
|
|
||||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
|
||||||
BigDecimal recipientShare = blockFees.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
|
||||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(recipientShare));
|
|
||||||
|
|
||||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
|
||||||
BigDecimal forgerShare = blockFees.subtract(recipientShare);
|
|
||||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(forgerShare));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give transaction fees to generator
|
|
||||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFees));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processAtFeesAndStates() throws DataException {
|
protected void processAtFeesAndStates() throws DataException {
|
||||||
@ -1254,6 +1232,9 @@ public class Block {
|
|||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
|
// Deduct any transaction fees from generator/proxy
|
||||||
|
deductTransactionFees();
|
||||||
|
|
||||||
// Orphan, and unlink, transactions from this block
|
// Orphan, and unlink, transactions from this block
|
||||||
orphanTransactionsFromBlock();
|
orphanTransactionsFromBlock();
|
||||||
|
|
||||||
@ -1263,8 +1244,8 @@ public class Block {
|
|||||||
// Block rewards removed after transactions undone
|
// Block rewards removed after transactions undone
|
||||||
orphanBlockRewards();
|
orphanBlockRewards();
|
||||||
|
|
||||||
// Deduct any transaction fees from generator/proxy
|
// Decrease account levels
|
||||||
deductTransactionFees();
|
decreaseAccountLevels();
|
||||||
|
|
||||||
// Return AT fees and delete AT states from repository
|
// Return AT fees and delete AT states from repository
|
||||||
orphanAtFeesAndStates();
|
orphanAtFeesAndStates();
|
||||||
@ -1341,22 +1322,7 @@ public class Block {
|
|||||||
if (reward == null)
|
if (reward == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Is generator public key actually a proxy forge key?
|
distributeByAccountLevel(reward.negate());
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
|
||||||
if (proxyForgerData != null) {
|
|
||||||
// Split reward between forger and recipient
|
|
||||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
|
||||||
BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
|
||||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(recipientShare));
|
|
||||||
|
|
||||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
|
||||||
BigDecimal forgerShare = reward.subtract(recipientShare);
|
|
||||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(forgerShare));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take block reward from generator
|
|
||||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(reward));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void deductTransactionFees() throws DataException {
|
protected void deductTransactionFees() throws DataException {
|
||||||
@ -1366,22 +1332,7 @@ public class Block {
|
|||||||
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
|
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Is generator public key actually a proxy forge key?
|
distributeByAccountLevel(blockFees.negate());
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
|
||||||
if (proxyForgerData != null) {
|
|
||||||
// Split fees between forger and recipient
|
|
||||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
|
||||||
BigDecimal recipientShare = blockFees.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
|
||||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(recipientShare));
|
|
||||||
|
|
||||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
|
||||||
BigDecimal forgerShare = blockFees.subtract(recipientShare);
|
|
||||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(forgerShare));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduct transaction fees to generator
|
|
||||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFees));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void orphanAtFeesAndStates() throws DataException {
|
protected void orphanAtFeesAndStates() throws DataException {
|
||||||
@ -1397,4 +1348,116 @@ public class Block {
|
|||||||
atRepository.deleteATStates(this.blockData.getHeight());
|
atRepository.deleteATStates(this.blockData.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void decreaseAccountLevels() throws DataException {
|
||||||
|
// TODO !
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void distributeByAccountLevel(BigDecimal totalAmount) throws DataException {
|
||||||
|
class AccountInfo {
|
||||||
|
final ProxyForgerData proxyForgerData;
|
||||||
|
final Account forgerAccount;
|
||||||
|
final boolean isFounder;
|
||||||
|
final int level;
|
||||||
|
final int shareBin;
|
||||||
|
final Account recipientAccount;
|
||||||
|
|
||||||
|
AccountInfo(Repository repository, int accountIndex, List<ShareByLevel> sharesByLevel) throws DataException {
|
||||||
|
this.proxyForgerData = repository.getAccountRepository().getProxyAccountByIndex(accountIndex);
|
||||||
|
|
||||||
|
this.forgerAccount = new PublicKeyAccount(repository, this.proxyForgerData.getForgerPublicKey());
|
||||||
|
this.recipientAccount = new Account(repository, this.proxyForgerData.getRecipient());
|
||||||
|
|
||||||
|
if (this.forgerAccount.isFounder()) {
|
||||||
|
this.isFounder = true;
|
||||||
|
this.level = 0;
|
||||||
|
this.shareBin = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFounder = false;
|
||||||
|
this.level = this.forgerAccount.getLevel();
|
||||||
|
|
||||||
|
for (int s = 0; s < sharesByLevel.size(); ++s)
|
||||||
|
if (sharesByLevel.get(s).levels.contains(this.level)) {
|
||||||
|
this.shareBin = s;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shareBin = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void distribute(BigDecimal accountAmount) throws DataException {
|
||||||
|
final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100L);
|
||||||
|
|
||||||
|
Account forgerAccount = this.forgerAccount;
|
||||||
|
Account recipientAccount = this.recipientAccount;
|
||||||
|
|
||||||
|
if (forgerAccount.getAddress().equals(recipientAccount.getAddress())) {
|
||||||
|
// forger & recipient the same - simpler case
|
||||||
|
LOGGER.trace(() -> String.format("Forger/recipient account %s share: %s", forgerAccount.getAddress(), accountAmount.toPlainString()));
|
||||||
|
forgerAccount.setConfirmedBalance(Asset.QORA, forgerAccount.getConfirmedBalance(Asset.QORA).add(accountAmount));
|
||||||
|
} else {
|
||||||
|
// forger & recipient different - extra work needed
|
||||||
|
BigDecimal recipientAmount = accountAmount.multiply(this.proxyForgerData.getShare()).divide(ONE_HUNDRED, RoundingMode.DOWN);
|
||||||
|
BigDecimal forgerAmount = accountAmount.subtract(recipientAmount);
|
||||||
|
|
||||||
|
LOGGER.trace(() -> String.format("Forger account %s share: %s", forgerAccount.getAddress(), forgerAmount.toPlainString()));
|
||||||
|
forgerAccount.setConfirmedBalance(Asset.QORA, forgerAccount.getConfirmedBalance(Asset.QORA).add(forgerAmount));
|
||||||
|
|
||||||
|
LOGGER.trace(() -> String.format("Recipient account %s share: %s", recipientAccount.getAddress(), recipientAmount.toPlainString()));
|
||||||
|
recipientAccount.setConfirmedBalance(Asset.QORA, recipientAccount.getConfirmedBalance(Asset.QORA).add(recipientAmount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
||||||
|
|
||||||
|
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||||
|
List<AccountInfo> expandedAccounts = new ArrayList<AccountInfo>();
|
||||||
|
|
||||||
|
IntIterator iterator = accountIndexes.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
int accountIndex = iterator.next();
|
||||||
|
AccountInfo accountInfo = new AccountInfo(repository, accountIndex, sharesByLevel);
|
||||||
|
expandedAccounts.add(accountInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute amount across bins
|
||||||
|
BigDecimal sharedAmount = BigDecimal.ZERO;
|
||||||
|
for (int s = 0; s < sharesByLevel.size(); ++s) {
|
||||||
|
final int binIndex = s;
|
||||||
|
|
||||||
|
BigDecimal binAmount = sharesByLevel.get(binIndex).share.multiply(totalAmount).setScale(8, RoundingMode.DOWN);
|
||||||
|
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, totalAmount.toPlainString(), binAmount.toPlainString()));
|
||||||
|
|
||||||
|
// Spread across all accounts in bin
|
||||||
|
List<AccountInfo> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList());
|
||||||
|
if (binnedAccounts.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BigDecimal binSize = BigDecimal.valueOf(binnedAccounts.size());
|
||||||
|
BigDecimal accountAmount = binAmount.divide(binSize, RoundingMode.DOWN);
|
||||||
|
|
||||||
|
for (int a = 0; a < binnedAccounts.size(); ++a) {
|
||||||
|
AccountInfo accountInfo = binnedAccounts.get(a);
|
||||||
|
accountInfo.distribute(accountAmount);
|
||||||
|
sharedAmount = sharedAmount.add(accountAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spread remainder across founder accounts
|
||||||
|
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
|
||||||
|
LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString()));
|
||||||
|
|
||||||
|
List<AccountInfo> founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isFounder).collect(Collectors.toList());
|
||||||
|
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
|
||||||
|
BigDecimal accountAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
|
||||||
|
|
||||||
|
for (int a = 0; a < founderAccounts.size(); ++a) {
|
||||||
|
AccountInfo accountInfo = founderAccounts.get(a);
|
||||||
|
accountInfo.distribute(accountAmount);
|
||||||
|
sharedAmount = sharedAmount.add(accountAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,13 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
List<RewardByHeight> rewardsByHeight;
|
List<RewardByHeight> rewardsByHeight;
|
||||||
|
|
||||||
|
/** Share of block reward/fees by account level */
|
||||||
|
public static class ShareByLevel {
|
||||||
|
public List<Integer> levels;
|
||||||
|
public BigDecimal share;
|
||||||
|
}
|
||||||
|
List<ShareByLevel> sharesByLevel;
|
||||||
|
|
||||||
/** Block times by block height */
|
/** Block times by block height */
|
||||||
public static class BlockTimingByHeight {
|
public static class BlockTimingByHeight {
|
||||||
public int height;
|
public int height;
|
||||||
@ -273,6 +280,10 @@ public class BlockChain {
|
|||||||
return this.rewardsByHeight;
|
return this.rewardsByHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ShareByLevel> getBlockSharesByLevel() {
|
||||||
|
return this.sharesByLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public List<ForgingTier> getForgingTiers() {
|
public List<ForgingTier> getForgingTiers() {
|
||||||
return this.forgingTiers;
|
return this.forgingTiers;
|
||||||
}
|
}
|
||||||
@ -351,8 +362,14 @@ public class BlockChain {
|
|||||||
if (this.genesisInfo == null)
|
if (this.genesisInfo == null)
|
||||||
Settings.throwValidationError("No \"genesisInfo\" entry found in blockchain config");
|
Settings.throwValidationError("No \"genesisInfo\" entry found in blockchain config");
|
||||||
|
|
||||||
if (this.featureTriggers == null)
|
if (this.rewardsByHeight == null)
|
||||||
Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config");
|
Settings.throwValidationError("No \"rewardsByHeight\" entry found in blockchain config");
|
||||||
|
|
||||||
|
if (this.sharesByLevel == null)
|
||||||
|
Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config");
|
||||||
|
|
||||||
|
if (this.blockTimingsByHeight == null)
|
||||||
|
Settings.throwValidationError("No \"blockTimingsByHeight\" entry found in blockchain config");
|
||||||
|
|
||||||
if (this.blockTimestampMargin <= 0)
|
if (this.blockTimestampMargin <= 0)
|
||||||
Settings.throwValidationError("Invalid \"blockTimestampMargin\" in blockchain config");
|
Settings.throwValidationError("Invalid \"blockTimestampMargin\" in blockchain config");
|
||||||
@ -363,6 +380,9 @@ public class BlockChain {
|
|||||||
if (this.maxBlockSize <= 0)
|
if (this.maxBlockSize <= 0)
|
||||||
Settings.throwValidationError("Invalid \"maxBlockSize\" in blockchain config");
|
Settings.throwValidationError("Invalid \"maxBlockSize\" in blockchain config");
|
||||||
|
|
||||||
|
if (this.featureTriggers == null)
|
||||||
|
Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config");
|
||||||
|
|
||||||
// Check all featureTriggers are present
|
// Check all featureTriggers are present
|
||||||
for (FeatureTrigger featureTrigger : FeatureTrigger.values())
|
for (FeatureTrigger featureTrigger : FeatureTrigger.values())
|
||||||
if (!this.featureTriggers.containsKey(featureTrigger.name()))
|
if (!this.featureTriggers.containsKey(featureTrigger.name()))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user