forked from Qortal/qortal
Merge branch 'block-rewards' into launch
This commit is contained in:
commit
ea4c51026b
@ -24,7 +24,7 @@ import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.at.AT;
|
||||
import org.qortal.block.BlockChain.BlockTimingByHeight;
|
||||
import org.qortal.block.BlockChain.ShareByLevel;
|
||||
import org.qortal.block.BlockChain.AccountLevelShareBin;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
@ -126,8 +126,13 @@ public class Block {
|
||||
/** Locally-generated AT fees */
|
||||
protected long ourAtFees; // Generated locally
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BlockRewardDistributor {
|
||||
long distribute(long amount, Map<Account, Long> balanceChanges) throws DataException;
|
||||
}
|
||||
|
||||
/** Lazy-instantiated expanded info on block's online accounts. */
|
||||
static class ExpandedAccount {
|
||||
private static class ExpandedAccount {
|
||||
private final RewardShareData rewardShareData;
|
||||
private final int sharePercent;
|
||||
private final boolean isRecipientAlsoMinter;
|
||||
@ -166,28 +171,29 @@ public class Block {
|
||||
* This is a method, not a final variable, because account's level can change between construction and call,
|
||||
* e.g. during Block.process() where account levels are bumped right before Block.distributeBlockReward().
|
||||
*
|
||||
* @return share "bin" (index into BlockShareByLevel blockchain config, so 0+), or -1 if no bin found
|
||||
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
||||
*/
|
||||
int getShareBin() {
|
||||
public AccountLevelShareBin getShareBin() {
|
||||
if (this.isMinterFounder)
|
||||
return -1;
|
||||
return null;
|
||||
|
||||
final List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
||||
final int accountLevel = this.mintingAccountData.getLevel();
|
||||
if (accountLevel <= 0)
|
||||
return null;
|
||||
|
||||
for (int s = 0; s < sharesByLevel.size(); ++s)
|
||||
if (sharesByLevel.get(s).levels.contains(accountLevel))
|
||||
return s;
|
||||
final AccountLevelShareBin[] shareBinsByLevel = BlockChain.getInstance().getShareBinsByAccountLevel();
|
||||
if (accountLevel > shareBinsByLevel.length)
|
||||
return null;
|
||||
|
||||
return -1;
|
||||
return shareBinsByLevel[accountLevel];
|
||||
}
|
||||
|
||||
void distribute(long accountAmount) throws DataException {
|
||||
public long distribute(long accountAmount, Map<Account, Long> balanceChanges) {
|
||||
if (this.isRecipientAlsoMinter) {
|
||||
// minter & recipient the same - simpler case
|
||||
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(accountAmount)));
|
||||
if (accountAmount != 0)
|
||||
this.mintingAccount.modifyAssetBalance(Asset.QORT, accountAmount);
|
||||
balanceChanges.merge(this.mintingAccount, accountAmount, Long::sum);
|
||||
} else {
|
||||
// minter & recipient different - extra work needed
|
||||
long recipientAmount = (accountAmount * this.sharePercent) / 100L / 100L; // because scaled by 2dp and 'percent' means "per 100"
|
||||
@ -195,12 +201,15 @@ public class Block {
|
||||
|
||||
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(minterAmount)));
|
||||
if (minterAmount != 0)
|
||||
this.mintingAccount.modifyAssetBalance(Asset.QORT, minterAmount);
|
||||
balanceChanges.merge(this.mintingAccount, minterAmount, Long::sum);
|
||||
|
||||
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), Amounts.prettyAmount(recipientAmount)));
|
||||
if (recipientAmount != 0)
|
||||
this.recipientAccount.modifyAssetBalance(Asset.QORT, recipientAmount);
|
||||
balanceChanges.merge(this.recipientAccount, recipientAmount, Long::sum);
|
||||
}
|
||||
|
||||
// We always distribute all of the amount
|
||||
return accountAmount;
|
||||
}
|
||||
}
|
||||
/** Always use getExpandedAccounts() to access this, as it's lazy-instantiated. */
|
||||
@ -1229,11 +1238,8 @@ public class Block {
|
||||
// Increase account levels
|
||||
increaseAccountLevels();
|
||||
|
||||
// Block rewards go before transactions processed
|
||||
// Distribute block rewards, including transaction fees, before transactions processed
|
||||
processBlockRewards();
|
||||
|
||||
// Give transaction fees to minter/reward-share account(s)
|
||||
rewardTransactionFees();
|
||||
}
|
||||
|
||||
// We're about to (test-)process a batch of transactions,
|
||||
@ -1307,10 +1313,13 @@ public class Block {
|
||||
}
|
||||
|
||||
protected void processBlockRewards() throws DataException {
|
||||
// General block reward
|
||||
long reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
|
||||
// Add transaction fees
|
||||
reward += this.blockData.getTotalFees();
|
||||
|
||||
// No reward for our height?
|
||||
if (reward == 0)
|
||||
// Nothing to reward?
|
||||
if (reward <= 0)
|
||||
return;
|
||||
|
||||
distributeBlockReward(reward);
|
||||
@ -1389,16 +1398,6 @@ public class Block {
|
||||
}
|
||||
}
|
||||
|
||||
protected void rewardTransactionFees() throws DataException {
|
||||
long blockFees = this.blockData.getTotalFees();
|
||||
|
||||
// No transaction fees?
|
||||
if (blockFees <= 0)
|
||||
return;
|
||||
|
||||
distributeBlockReward(blockFees);
|
||||
}
|
||||
|
||||
protected void processAtFeesAndStates() throws DataException {
|
||||
ATRepository atRepository = this.repository.getATRepository();
|
||||
|
||||
@ -1463,10 +1462,7 @@ public class Block {
|
||||
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
||||
this.cachedExpandedAccounts = null;
|
||||
|
||||
// Deduct any transaction fees from minter/reward-share account(s)
|
||||
deductTransactionFees();
|
||||
|
||||
// Block rewards removed after transactions undone
|
||||
// Block rewards, including transaction fees, removed after transactions undone
|
||||
orphanBlockRewards();
|
||||
|
||||
// Decrease account levels
|
||||
@ -1541,25 +1537,18 @@ public class Block {
|
||||
}
|
||||
|
||||
protected void orphanBlockRewards() throws DataException {
|
||||
// General block reward
|
||||
long reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
|
||||
// Add transaction fees
|
||||
reward += this.blockData.getTotalFees();
|
||||
|
||||
// No reward for our height?
|
||||
if (reward == 0)
|
||||
// Nothing to reward?
|
||||
if (reward <= 0)
|
||||
return;
|
||||
|
||||
distributeBlockReward(- reward);
|
||||
}
|
||||
|
||||
protected void deductTransactionFees() throws DataException {
|
||||
long blockFees = this.blockData.getTotalFees();
|
||||
|
||||
// No transaction fees?
|
||||
if (blockFees <= 0)
|
||||
return;
|
||||
|
||||
distributeBlockReward(- blockFees);
|
||||
}
|
||||
|
||||
protected void orphanAtFeesAndStates() throws DataException {
|
||||
ATRepository atRepository = this.repository.getATRepository();
|
||||
for (ATStateData atStateData : this.getATStates()) {
|
||||
@ -1615,62 +1604,218 @@ public class Block {
|
||||
}
|
||||
}
|
||||
|
||||
protected void distributeBlockReward(long totalAmount) throws DataException {
|
||||
LOGGER.trace(() -> String.format("Distributing: %s", Amounts.prettyAmount(totalAmount)));
|
||||
private static class BlockRewardCandidate {
|
||||
public final String description;
|
||||
public long share;
|
||||
public final BlockRewardDistributor distributionMethod;
|
||||
|
||||
// Distribute according to account level
|
||||
long sharedByLevelAmount = distributeBlockRewardByLevel(totalAmount);
|
||||
LOGGER.trace(() -> String.format("Shared %s of %s based on account levels", Amounts.prettyAmount(sharedByLevelAmount), Amounts.prettyAmount(totalAmount)));
|
||||
public BlockRewardCandidate(String description, long share, BlockRewardDistributor distributionMethod) {
|
||||
this.description = description;
|
||||
this.share = share;
|
||||
this.distributionMethod = distributionMethod;
|
||||
}
|
||||
|
||||
// Distribute amongst legacy QORA holders
|
||||
long sharedByQoraHoldersAmount = distributeBlockRewardToQoraHolders(totalAmount);
|
||||
LOGGER.trace(() -> String.format("Shared %s of %s to legacy QORA holders", Amounts.prettyAmount(sharedByQoraHoldersAmount), Amounts.prettyAmount(totalAmount)));
|
||||
|
||||
// Spread remainder across founder accounts
|
||||
long foundersAmount = totalAmount - sharedByLevelAmount - sharedByQoraHoldersAmount;
|
||||
distributeBlockRewardToFounders(foundersAmount);
|
||||
public long distribute(long distibutionAmount, Map<Account, Long> balanceChanges) throws DataException {
|
||||
return this.distributionMethod.distribute(distibutionAmount, balanceChanges);
|
||||
}
|
||||
}
|
||||
|
||||
private long distributeBlockRewardByLevel(long totalAmount) throws DataException {
|
||||
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
||||
protected void distributeBlockReward(long totalAmount) throws DataException {
|
||||
final long totalAmountForLogging = totalAmount;
|
||||
LOGGER.trace(() -> String.format("Distributing: %s", Amounts.prettyAmount(totalAmountForLogging)));
|
||||
|
||||
// Distribute amount across bins
|
||||
long sharedAmount = 0;
|
||||
for (int s = 0; s < sharesByLevel.size(); ++s) {
|
||||
final int binIndex = s;
|
||||
final boolean isProcessingNotOrphaning = totalAmount >= 0;
|
||||
|
||||
long binAmount = Amounts.roundDownScaledMultiply(totalAmount, sharesByLevel.get(binIndex).share);
|
||||
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, Amounts.prettyAmount(totalAmount), Amounts.prettyAmount(binAmount)));
|
||||
// How to distribute reward among groups, with ratio, IN ORDER
|
||||
List<BlockRewardCandidate> rewardCandidates = determineBlockRewardCandidates(isProcessingNotOrphaning);
|
||||
|
||||
// Spread across all accounts in bin. getShareBin() returns -1 for minter accounts that are also founders, so they are effectively filtered out.
|
||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin() == binIndex).collect(Collectors.toList());
|
||||
// Now distribute to candidates
|
||||
|
||||
// Collate all balance changes and then apply in one final step
|
||||
Map<Account, Long> balanceChanges = new HashMap<>();
|
||||
|
||||
long remainingAmount = totalAmount;
|
||||
for (int r = 0; r < rewardCandidates.size(); ++r) {
|
||||
BlockRewardCandidate rewardCandidate = rewardCandidates.get(r);
|
||||
|
||||
// Distribute to these reward candidate accounts
|
||||
final long distributionAmount = Amounts.roundDownScaledMultiply(totalAmount, rewardCandidate.share);
|
||||
|
||||
long sharedAmount = rewardCandidate.distribute(distributionAmount, balanceChanges);
|
||||
remainingAmount -= sharedAmount;
|
||||
|
||||
// Reallocate any amount we didn't distribute, e.g. from maxxed legacy QORA holders
|
||||
if (sharedAmount != distributionAmount)
|
||||
totalAmount += Amounts.scaledDivide(distributionAmount - sharedAmount, 1_00000000 - rewardCandidate.share);
|
||||
|
||||
final long remainingAmountForLogging = remainingAmount;
|
||||
LOGGER.trace(() -> String.format("%s share: %s. Actually shared: %s. Remaining: %s",
|
||||
rewardCandidate.description,
|
||||
Amounts.prettyAmount(distributionAmount),
|
||||
Amounts.prettyAmount(sharedAmount),
|
||||
Amounts.prettyAmount(remainingAmountForLogging)));
|
||||
}
|
||||
|
||||
// Apply balance changes
|
||||
for (Map.Entry<Account, Long> balanceChange : balanceChanges.entrySet())
|
||||
balanceChange.getKey().modifyAssetBalance(Asset.QORT, balanceChange.getValue());
|
||||
}
|
||||
|
||||
protected List<BlockRewardCandidate> determineBlockRewardCandidates(boolean isProcessingNotOrphaning) throws DataException {
|
||||
// How to distribute reward among groups, with ratio, IN ORDER
|
||||
List<BlockRewardCandidate> rewardCandidates = new ArrayList<>();
|
||||
|
||||
// All online accounts
|
||||
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||
|
||||
/*
|
||||
* Distribution rules:
|
||||
*
|
||||
* Distribution is based on the minting account of 'online' reward-shares.
|
||||
*
|
||||
* If ANY founders are online, then they receive the leftover non-distributed reward.
|
||||
* If NO founders are online, then account-level-based rewards are scaled up so 100% of reward is allocated.
|
||||
*
|
||||
* If ANY non-maxxed legacy QORA holders exist then they are always allocated their fixed share (e.g. 20%).
|
||||
*
|
||||
* There has to be either at least one 'online' account for blocks to be minted
|
||||
* so there is always either one account-level-based or founder reward candidate.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* With at least one founder online:
|
||||
* Level 1/2 accounts: 5%
|
||||
* Legacy QORA holders: 20%
|
||||
* Founders: ~75%
|
||||
*
|
||||
* No online founders:
|
||||
* Level 1/2 accounts: 5%
|
||||
* Level 5/6 accounts: 15%
|
||||
* Legacy QORA holders: 20%
|
||||
* Total: 40%
|
||||
*
|
||||
* After scaling account-level-based shares to fill 100%:
|
||||
* Level 1/2 accounts: 20%
|
||||
* Level 5/6 accounts: 60%
|
||||
* Legacy QORA holders: 20%
|
||||
* Total: 100%
|
||||
*/
|
||||
long totalShares = 0;
|
||||
|
||||
// Determine whether we have any online founders
|
||||
final List<ExpandedAccount> onlineFounderAccounts = expandedAccounts.stream().filter(expandedAccount -> expandedAccount.isMinterFounder).collect(Collectors.toList());
|
||||
final boolean haveFounders = !onlineFounderAccounts.isEmpty();
|
||||
|
||||
// Determine reward candidates based on account level
|
||||
List<AccountLevelShareBin> accountLevelShareBins = BlockChain.getInstance().getAccountLevelShareBins();
|
||||
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) {
|
||||
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
||||
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
|
||||
// Object reference compare is OK as all references are read-only from blockchain config.
|
||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin() == accountLevelShareBin).collect(Collectors.toList());
|
||||
|
||||
// No online accounts in this bin? Skip to next one
|
||||
if (binnedAccounts.isEmpty())
|
||||
continue;
|
||||
|
||||
long perAccountAmount = binAmount / binnedAccounts.size();
|
||||
String description = String.format("Bin %d", binIndex);
|
||||
BlockRewardDistributor accountLevelBinDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, binnedAccounts, balanceChanges);
|
||||
|
||||
for (int a = 0; a < binnedAccounts.size(); ++a) {
|
||||
ExpandedAccount expandedAccount = binnedAccounts.get(a);
|
||||
expandedAccount.distribute(perAccountAmount);
|
||||
sharedAmount += perAccountAmount;
|
||||
}
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate(description, accountLevelShareBin.share, accountLevelBinDistributor);
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
|
||||
totalShares += rewardCandidate.share;
|
||||
}
|
||||
|
||||
// Fetch list of legacy QORA holders who haven't reached their cap of QORT reward.
|
||||
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
|
||||
final boolean haveQoraHolders = !qoraHolders.isEmpty();
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
|
||||
// Perform account-level-based reward scaling if appropriate
|
||||
if (!haveFounders) {
|
||||
// Recalculate distribution ratios based on candidates
|
||||
|
||||
// Nothing shared? This shouldn't happen
|
||||
if (totalShares == 0)
|
||||
throw new DataException("Unexpected lack of block reward candidates?");
|
||||
|
||||
// Re-scale individual reward candidate's share as if total shared was 100% - legacy QORA holders' share
|
||||
long scalingFactor;
|
||||
if (haveQoraHolders)
|
||||
scalingFactor = Amounts.scaledDivide(totalShares, 1_00000000 - qoraHoldersShare);
|
||||
else
|
||||
scalingFactor = totalShares;
|
||||
|
||||
for (BlockRewardCandidate rewardCandidate : rewardCandidates)
|
||||
rewardCandidate.share = Amounts.scaledDivide(rewardCandidate.share, scalingFactor);
|
||||
}
|
||||
|
||||
// Add legacy QORA holders as reward candidate with fixed share (if appropriate)
|
||||
if (haveQoraHolders) {
|
||||
// Yes: add to reward candidates list
|
||||
BlockRewardDistributor legacyQoraHoldersDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardToQoraHolders(distributionAmount, qoraHolders, balanceChanges, this);
|
||||
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Legacy QORA holders", qoraHoldersShare, legacyQoraHoldersDistributor);
|
||||
|
||||
if (haveFounders)
|
||||
// We have founders, so distribute legacy QORA holders just before founders so founders get any non-distributed
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
else
|
||||
// No founder, so distribute legacy QORA holders first, so all account-level-based rewards get share of any non-distributed
|
||||
rewardCandidates.add(0, rewardCandidate);
|
||||
|
||||
totalShares += rewardCandidate.share;
|
||||
}
|
||||
|
||||
// Add founders as reward candidate if appropriate
|
||||
if (haveFounders) {
|
||||
// Yes: add to reward candidates list
|
||||
BlockRewardDistributor founderDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, onlineFounderAccounts, balanceChanges);
|
||||
|
||||
final long foundersShare = 1_00000000 - totalShares;
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Founders", foundersShare, founderDistributor);
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
}
|
||||
|
||||
return rewardCandidates;
|
||||
}
|
||||
|
||||
private static long distributeBlockRewardShare(long distributionAmount, List<ExpandedAccount> accounts, Map<Account, Long> balanceChanges) {
|
||||
// Collate all expanded accounts by minting account
|
||||
Map<String, List<ExpandedAccount>> accountsByMinter = new HashMap<>();
|
||||
|
||||
for (ExpandedAccount expandedAccount : accounts)
|
||||
accountsByMinter.compute(expandedAccount.mintingAccount.getAddress(), (minterAddress, otherAccounts) -> {
|
||||
if (otherAccounts == null) {
|
||||
return new ArrayList<>(Arrays.asList(expandedAccount));
|
||||
} else {
|
||||
otherAccounts.add(expandedAccount);
|
||||
return otherAccounts;
|
||||
}
|
||||
});
|
||||
|
||||
// Divide distribution amount by number of *minting* accounts
|
||||
long perMintingAccountAmount = distributionAmount / accountsByMinter.keySet().size();
|
||||
|
||||
// Distribute, reducing totalAmount by how much was actually distributed
|
||||
long sharedAmount = 0;
|
||||
for (List<ExpandedAccount> recipientAccounts : accountsByMinter.values()) {
|
||||
long perRecipientAccountAmount = perMintingAccountAmount / recipientAccounts.size();
|
||||
|
||||
for (ExpandedAccount expandedAccount : recipientAccounts)
|
||||
sharedAmount += expandedAccount.distribute(perRecipientAccountAmount, balanceChanges);
|
||||
}
|
||||
|
||||
return sharedAmount;
|
||||
}
|
||||
|
||||
private long distributeBlockRewardToQoraHolders(long totalAmount) throws DataException {
|
||||
long qoraHoldersAmount = Amounts.roundDownScaledMultiply(totalAmount, BlockChain.getInstance().getQoraHoldersShare());
|
||||
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", Amounts.prettyAmount(totalAmount), Amounts.prettyAmount(qoraHoldersAmount)));
|
||||
|
||||
final boolean isProcessingNotOrphaning = totalAmount >= 0;
|
||||
private static long distributeBlockRewardToQoraHolders(long qoraHoldersAmount, List<AccountBalanceData> qoraHolders, Map<Account, Long> balanceChanges, Block block) throws DataException {
|
||||
final boolean isProcessingNotOrphaning = qoraHoldersAmount >= 0;
|
||||
|
||||
long qoraPerQortReward = BlockChain.getInstance().getQoraPerQortReward();
|
||||
BigInteger qoraPerQortRewardBI = BigInteger.valueOf(qoraPerQortReward);
|
||||
|
||||
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
|
||||
|
||||
long totalQoraHeld = 0;
|
||||
for (int i = 0; i < qoraHolders.size(); ++i)
|
||||
totalQoraHeld += qoraHolders.get(i).getBalance();
|
||||
@ -1678,14 +1823,14 @@ public class Block {
|
||||
long finalTotalQoraHeld = totalQoraHeld;
|
||||
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", Amounts.prettyAmount(finalTotalQoraHeld)));
|
||||
|
||||
long sharedAmount = 0;
|
||||
if (totalQoraHeld <= 0)
|
||||
return sharedAmount;
|
||||
return 0;
|
||||
|
||||
// Could do with a faster 128bit integer library, but until then...
|
||||
BigInteger qoraHoldersAmountBI = BigInteger.valueOf(qoraHoldersAmount);
|
||||
BigInteger totalQoraHeldBI = BigInteger.valueOf(totalQoraHeld);
|
||||
|
||||
long sharedAmount = 0;
|
||||
for (int h = 0; h < qoraHolders.size(); ++h) {
|
||||
AccountBalanceData qoraHolder = qoraHolders.get(h);
|
||||
BigInteger qoraHolderBalanceBI = BigInteger.valueOf(qoraHolder.getBalance());
|
||||
@ -1694,15 +1839,15 @@ public class Block {
|
||||
// long holderReward = (qoraHoldersAmount * qoraHolder.getBalance()) / totalQoraHeld;
|
||||
long holderReward = qoraHoldersAmountBI.multiply(qoraHolderBalanceBI).divide(totalQoraHeldBI).longValue();
|
||||
|
||||
long finalHolderReward = holderReward;
|
||||
final long holderRewardForLogging = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(qoraHolder.getBalance()), finalTotalQoraHeld, Amounts.prettyAmount(finalHolderReward)));
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(qoraHolder.getBalance()), finalTotalQoraHeld, Amounts.prettyAmount(holderRewardForLogging)));
|
||||
|
||||
// Too small to register this time?
|
||||
if (holderReward == 0)
|
||||
continue;
|
||||
|
||||
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
|
||||
Account qoraHolderAccount = new Account(block.repository, qoraHolder.getAddress());
|
||||
|
||||
long newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA) + holderReward;
|
||||
|
||||
@ -1718,16 +1863,16 @@ public class Block {
|
||||
newQortFromQoraBalance -= adjustment;
|
||||
|
||||
// This is also the QORA holder's final QORT-from-QORA block
|
||||
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
|
||||
this.repository.getAccountRepository().save(qortFromQoraData);
|
||||
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, block.blockData.getHeight());
|
||||
block.repository.getAccountRepository().save(qortFromQoraData);
|
||||
|
||||
long finalAdjustedHolderReward = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s final share %s at height %d",
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), this.blockData.getHeight()));
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), block.blockData.getHeight()));
|
||||
}
|
||||
} else {
|
||||
// Orphaning
|
||||
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
||||
QortFromQoraData qortFromQoraData = block.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
|
||||
if (qortFromQoraData != null) {
|
||||
// Final QORT-from-QORA amount from repository was stored during processing, and hence positive.
|
||||
// So we use + here as qortFromQora is negative during orphaning.
|
||||
@ -1737,15 +1882,15 @@ public class Block {
|
||||
holderReward -= adjustment;
|
||||
newQortFromQoraBalance -= adjustment;
|
||||
|
||||
this.repository.getAccountRepository().deleteQortFromQoraInfo(qoraHolder.getAddress());
|
||||
block.repository.getAccountRepository().deleteQortFromQoraInfo(qoraHolder.getAddress());
|
||||
|
||||
long finalAdjustedHolderReward = holderReward;
|
||||
LOGGER.trace(() -> String.format("QORA holder %s final share %s was at height %d",
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), this.blockData.getHeight()));
|
||||
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), block.blockData.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
qoraHolderAccount.modifyAssetBalance(Asset.QORT, holderReward);
|
||||
balanceChanges.merge(qoraHolderAccount, holderReward, Long::sum);
|
||||
|
||||
if (newQortFromQoraBalance > 0)
|
||||
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
|
||||
@ -1759,41 +1904,6 @@ public class Block {
|
||||
return sharedAmount;
|
||||
}
|
||||
|
||||
private void distributeBlockRewardToFounders(long foundersAmount) throws DataException {
|
||||
// Remaining reward portion is spread across all founders, online or not
|
||||
List<AccountData> founderAccounts = this.repository.getAccountRepository().getFlaggedAccounts(Account.FOUNDER_FLAG);
|
||||
|
||||
long foundersCount = founderAccounts.size();
|
||||
long perFounderAmount = foundersAmount / foundersCount;
|
||||
|
||||
LOGGER.trace(() -> String.format("Sharing remaining %s to %d founder%s, %s each",
|
||||
Amounts.prettyAmount(foundersAmount),
|
||||
founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
|
||||
Amounts.prettyAmount(perFounderAmount)));
|
||||
|
||||
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||
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.
|
||||
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(
|
||||
accountInfo -> accountInfo.isMinterFounder &&
|
||||
accountInfo.mintingAccountData.getAddress().equals(founderAccount.getAddress())
|
||||
).collect(Collectors.toList());
|
||||
|
||||
if (founderExpandedAccounts.isEmpty()) {
|
||||
// Simple case: no founder-as-minter reward-shares online so founder gets whole amount.
|
||||
founderAccount.modifyAssetBalance(Asset.QORT, perFounderAmount);
|
||||
} else {
|
||||
// Distribute over reward-shares
|
||||
long perFounderRewardShareAmount = perFounderAmount / founderExpandedAccounts.size();
|
||||
|
||||
for (int fea = 0; fea < founderExpandedAccounts.size(); ++fea)
|
||||
founderExpandedAccounts.get(fea).distribute(perFounderRewardShareAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Opportunity to tidy repository, etc. after block process/orphan. */
|
||||
private void postBlockTidy() throws DataException {
|
||||
this.repository.getAccountRepository().tidy();
|
||||
|
@ -89,15 +89,17 @@ public class BlockChain {
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long reward;
|
||||
}
|
||||
List<RewardByHeight> rewardsByHeight;
|
||||
private List<RewardByHeight> rewardsByHeight;
|
||||
|
||||
/** Share of block reward/fees by account level */
|
||||
public static class ShareByLevel {
|
||||
public static class AccountLevelShareBin {
|
||||
public List<Integer> levels;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long share;
|
||||
}
|
||||
List<ShareByLevel> sharesByLevel;
|
||||
private List<AccountLevelShareBin> sharesByLevel;
|
||||
/** Generated lookup of share-bin by account level */
|
||||
private AccountLevelShareBin[] shareBinsByLevel;
|
||||
|
||||
/** Share of block reward/fees to legacy QORA coin holders */
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
@ -116,7 +118,7 @@ public class BlockChain {
|
||||
* Example: if <tt>blocksNeededByLevel[3]</tt> is 200,<br>
|
||||
* then level 3 accounts need to mint 200 blocks to reach level 4.
|
||||
*/
|
||||
List<Integer> blocksNeededByLevel;
|
||||
private List<Integer> blocksNeededByLevel;
|
||||
|
||||
/**
|
||||
* Cumulative number of minted blocks required to reach next level from scratch.
|
||||
@ -130,7 +132,7 @@ public class BlockChain {
|
||||
* <p>
|
||||
* Should NOT be present in blockchain config file!
|
||||
*/
|
||||
List<Integer> cumulativeBlocksByLevel;
|
||||
private List<Integer> cumulativeBlocksByLevel;
|
||||
|
||||
/** Block times by block height */
|
||||
public static class BlockTimingByHeight {
|
||||
@ -139,7 +141,7 @@ public class BlockChain {
|
||||
public long deviation; // ms
|
||||
public double power;
|
||||
}
|
||||
List<BlockTimingByHeight> blockTimingsByHeight;
|
||||
private List<BlockTimingByHeight> blockTimingsByHeight;
|
||||
|
||||
private int minAccountLevelToMint = 1;
|
||||
private int minAccountLevelToRewardShare;
|
||||
@ -316,10 +318,14 @@ public class BlockChain {
|
||||
return this.rewardsByHeight;
|
||||
}
|
||||
|
||||
public List<ShareByLevel> getBlockSharesByLevel() {
|
||||
public List<AccountLevelShareBin> getAccountLevelShareBins() {
|
||||
return this.sharesByLevel;
|
||||
}
|
||||
|
||||
public AccountLevelShareBin[] getShareBinsByAccountLevel() {
|
||||
return this.shareBinsByLevel;
|
||||
}
|
||||
|
||||
public List<Integer> getBlocksNeededByLevel() {
|
||||
return this.blocksNeededByLevel;
|
||||
}
|
||||
@ -433,6 +439,15 @@ public class BlockChain {
|
||||
for (FeatureTrigger featureTrigger : FeatureTrigger.values())
|
||||
if (!this.featureTriggers.containsKey(featureTrigger.name()))
|
||||
Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name()));
|
||||
|
||||
// Check block reward share bounds
|
||||
long totalShare = this.qoraHoldersShare;
|
||||
// Add share percents for account-level-based rewards
|
||||
for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel)
|
||||
totalShare += accountLevelShareBin.share;
|
||||
|
||||
if (totalShare < 0 || totalShare > 1_00000000L)
|
||||
Settings.throwValidationError("Total non-founder share out of bounds (0<x<1e8)");
|
||||
}
|
||||
|
||||
/** Minor normalization, cached value generation, etc. */
|
||||
@ -447,6 +462,17 @@ public class BlockChain {
|
||||
cumulativeBlocks += this.blocksNeededByLevel.get(level);
|
||||
}
|
||||
|
||||
// Generate lookup-array for account-level share bins
|
||||
AccountLevelShareBin lastAccountLevelShareBin = this.sharesByLevel.get(this.sharesByLevel.size() - 1);
|
||||
final int lastLevel = lastAccountLevelShareBin.levels.get(lastAccountLevelShareBin.levels.size() - 1);
|
||||
this.shareBinsByLevel = new AccountLevelShareBin[lastLevel];
|
||||
|
||||
for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel)
|
||||
for (int level : accountLevelShareBin.levels)
|
||||
// level 1 stored at index 0, level 2 stored at index 1, etc.
|
||||
// level 0 not allowed
|
||||
this.shareBinsByLevel[level - 1] = accountLevelShareBin;
|
||||
|
||||
// Convert collections to unmodifiable form
|
||||
this.rewardsByHeight = Collections.unmodifiableList(this.rewardsByHeight);
|
||||
this.sharesByLevel = Collections.unmodifiableList(this.sharesByLevel);
|
||||
|
@ -241,15 +241,15 @@ public class RewardTests extends Common {
|
||||
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// 3 founders (online or not) so blockReward divided by 3
|
||||
int founderCount = 3;
|
||||
// 2 founders online so blockReward divided by 2
|
||||
int founderCount = 2;
|
||||
long perFounderReward = blockReward / founderCount;
|
||||
|
||||
// Alice simple self-share so her reward is perFounderReward
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, perFounderReward);
|
||||
|
||||
// Bob not online so his reward is simply perFounderReward
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, perFounderReward);
|
||||
// Bob not online so his reward is zero
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, 0L);
|
||||
|
||||
// Chloe has two reward-shares, so her reward is divided by 2
|
||||
int chloeSharesCount = 2;
|
||||
@ -269,4 +269,71 @@ public class RewardTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check account-level-based reward scaling when no founders are online. */
|
||||
@Test
|
||||
public void testNoFounderRewardScaling() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-scaling.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Dilbert needs to create a self-share
|
||||
byte[] dilbertSelfSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); // Block minted by Alice
|
||||
PrivateKeyAccount dilbertSelfShareAccount = new PrivateKeyAccount(repository, dilbertSelfSharePrivateKey);
|
||||
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
|
||||
long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
|
||||
BlockMinter.mintTestingBlock(repository, dilbertSelfShareAccount);
|
||||
|
||||
/*
|
||||
* Dilbert is only account 'online'.
|
||||
* No founders online.
|
||||
* Some legacy QORA holders.
|
||||
*
|
||||
* So Dilbert should receive 100% - legacy QORA holder's share.
|
||||
*/
|
||||
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
final long remainingShare = 1_00000000 - qoraHoldersShare;
|
||||
|
||||
long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
dilbertExpectedBalance += Amounts.roundDownScaledMultiply(blockReward, remainingShare);
|
||||
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertExpectedBalance);
|
||||
|
||||
// After several blocks, the legacy QORA holder should be maxxed out
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Now Dilbert should be receiving 100% of block reward
|
||||
blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
|
||||
BlockMinter.mintTestingBlock(repository, dilbertSelfShareAccount);
|
||||
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertExpectedBalance + blockReward);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check leftover legacy QORA reward goes to online founders. */
|
||||
@Test
|
||||
public void testLeftoverReward() throws DataException {
|
||||
Common.useSettings("test-settings-v2-leftover-reward.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
|
||||
long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
|
||||
BlockUtils.mintBlock(repository); // Block minted by Alice self-share
|
||||
|
||||
// Chloe maxxes out her legacy QORA reward so some is leftover to reward to Alice.
|
||||
|
||||
TestAccount chloe = Common.getTestAccount(repository, "chloe");
|
||||
final long chloeQortFromQora = chloe.getConfirmedBalance(Asset.QORT_FROM_QORA);
|
||||
|
||||
long expectedBalance = initialBalances.get("alice").get(Asset.QORT) + blockReward - chloeQortFromQora;
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, expectedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
70
src/test/resources/test-chain-v2-leftover-reward.json
Normal file
70
src/test/resources/test-chain-v2-leftover-reward.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"isTestChain": true,
|
||||
"blockTimestampMargin": 500,
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
],
|
||||
"ciyamAtSettings": {
|
||||
"feePerStep": "0.0001",
|
||||
"maxStepsPerRound": 500,
|
||||
"stepsPerFunctionCall": 10,
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"messageHeight": 0,
|
||||
"atHeight": 0,
|
||||
"assetsTimestamp": 0,
|
||||
"votingTimestamp": 0,
|
||||
"arbitraryTimestamp": 0,
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": 0,
|
||||
"transactions": [
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "4000", "assetId": 1 },
|
||||
|
||||
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 },
|
||||
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 8 }
|
||||
]
|
||||
}
|
||||
}
|
70
src/test/resources/test-chain-v2-reward-scaling.json
Normal file
70
src/test/resources/test-chain-v2-reward-scaling.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"isTestChain": true,
|
||||
"blockTimestampMargin": 500,
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
],
|
||||
"ciyamAtSettings": {
|
||||
"feePerStep": "0.0001",
|
||||
"maxStepsPerRound": 500,
|
||||
"stepsPerFunctionCall": 10,
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"messageHeight": 0,
|
||||
"atHeight": 0,
|
||||
"assetsTimestamp": 0,
|
||||
"votingTimestamp": 0,
|
||||
"arbitraryTimestamp": 0,
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": 0,
|
||||
"transactions": [
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "40000", "assetId": 1 },
|
||||
|
||||
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 },
|
||||
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 8 }
|
||||
]
|
||||
}
|
||||
}
|
7
src/test/resources/test-settings-v2-leftover-reward.json
Normal file
7
src/test/resources/test-settings-v2-leftover-reward.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-leftover-reward.json",
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-qora-holder.json",
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-qora-holder-extremes.json",
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
|
7
src/test/resources/test-settings-v2-reward-scaling.json
Normal file
7
src/test/resources/test-settings-v2-reward-scaling.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-reward-scaling.json",
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
}
|
Loading…
Reference in New Issue
Block a user