mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-14 11:15:49 +00:00
Changes to block reward distribution
Any reward leftover from ditributing to legacy QORA holders is reallocated to either: founders if any online or account-level-based reward candidates, if no founders online We should get pretty close to 100% block reward distribution, barring rounding artifacts. More documentation and tests. Removed BlockChain's founderShare as it is calculated in Block on a per-block basis instead.
This commit is contained in:
parent
b5512dfa91
commit
5ffddd0169
@ -1620,14 +1620,91 @@ public class Block {
|
||||
}
|
||||
}
|
||||
|
||||
protected void distributeBlockReward(final long totalAmount) throws DataException {
|
||||
LOGGER.trace(() -> String.format("Distributing: %s", Amounts.prettyAmount(totalAmount)));
|
||||
protected void distributeBlockReward(long totalAmount) throws DataException {
|
||||
final long totalAmountForLogging = totalAmount;
|
||||
LOGGER.trace(() -> String.format("Distributing: %s", Amounts.prettyAmount(totalAmountForLogging)));
|
||||
|
||||
final boolean isProcessingNotOrphaning = totalAmount >= 0;
|
||||
|
||||
// How to distribute reward among groups, with ratio, IN ORDER
|
||||
List<BlockRewardCandidate> rewardCandidates = determineBlockRewardCandidates(isProcessingNotOrphaning);
|
||||
|
||||
// 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();
|
||||
|
||||
// How to distribute reward among groups, with ratio, IN ORDER
|
||||
List<BlockRewardCandidate> rewardCandidates = new ArrayList<>();
|
||||
/*
|
||||
* 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();
|
||||
@ -1644,86 +1721,64 @@ public class Block {
|
||||
String description = String.format("Bin %d", binIndex);
|
||||
BlockRewardDistributor accountLevelBinDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, binnedAccounts, balanceChanges);
|
||||
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate(description, accountLevelShareBins.get(binIndex).share, accountLevelBinDistributor);
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate(description, accountLevelShareBin.share, accountLevelBinDistributor);
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
|
||||
totalShares += rewardCandidate.share;
|
||||
}
|
||||
|
||||
// Determine reward candidates based on legacy QORA held
|
||||
// Fetch list of legacy QORA holders who haven't reached their cap of QORT reward.
|
||||
final boolean isProcessingNotOrphaning = totalAmount >= 0;
|
||||
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
|
||||
final boolean haveQoraHolders = !qoraHolders.isEmpty();
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
|
||||
// Any eligible legacy QORA holders?
|
||||
if (!qoraHolders.isEmpty()) {
|
||||
// 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", BlockChain.getInstance().getQoraHoldersShare(), legacyQoraHoldersDistributor);
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
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;
|
||||
}
|
||||
|
||||
// Determine whether we reward founders
|
||||
final List<ExpandedAccount> onlineFounderAccounts = expandedAccounts.stream().filter(expandedAccount -> expandedAccount.isMinterFounder).collect(Collectors.toList());
|
||||
if (!onlineFounderAccounts.isEmpty()) {
|
||||
// Add founders as reward candidate if appropriate
|
||||
if (haveFounders) {
|
||||
// Yes: add to reward candidates list
|
||||
BlockRewardDistributor founderDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, onlineFounderAccounts, balanceChanges);
|
||||
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Founders", BlockChain.getInstance().getFoundersShare(), founderDistributor);
|
||||
final long foundersShare = 1_00000000 - totalShares;
|
||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Founders", foundersShare, founderDistributor);
|
||||
rewardCandidates.add(rewardCandidate);
|
||||
}
|
||||
|
||||
// Recalculate distribution ratios based on candidates
|
||||
long totalShared = 0;
|
||||
for (BlockRewardCandidate rewardCandidate : rewardCandidates)
|
||||
totalShared += rewardCandidate.share;
|
||||
|
||||
// No eligible candidates?
|
||||
// Example scenario: the only online accounts are legacy QORA holders and they've already reached their max capped QORT-from-QORA.
|
||||
if (totalShared == 0)
|
||||
return;
|
||||
|
||||
// Re-scale individual reward candidate's share as if total shared was 100%
|
||||
for (BlockRewardCandidate rewardCandidate : rewardCandidates)
|
||||
rewardCandidate.share = Amounts.scaledDivide(rewardCandidate.share, totalShared);
|
||||
|
||||
// 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);
|
||||
|
||||
long distributionAmount;
|
||||
if (r < rewardCandidates.size() - 1)
|
||||
// Distribute according to sharePercent
|
||||
distributionAmount = Amounts.roundDownScaledMultiply(totalAmount, rewardCandidate.share);
|
||||
else
|
||||
/*
|
||||
* Last reward candidate gets full remaining amount. Typically this will be online founders.
|
||||
*
|
||||
* The only time the amount would differ from above sharePercent calculation is
|
||||
* if the *previous* rewardCandidate was "legacy QORA holders" and not all of their
|
||||
* allotment was distributed.
|
||||
*/
|
||||
distributionAmount = remainingAmount;
|
||||
|
||||
// Distribute to these reward candidate accounts, reducing remainingAmount by how much was actually distributed
|
||||
long sharedAmount = rewardCandidate.distribute(distributionAmount, balanceChanges);
|
||||
remainingAmount -= sharedAmount;
|
||||
|
||||
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());
|
||||
return rewardCandidates;
|
||||
}
|
||||
|
||||
private static long distributeBlockRewardShare(long distributionAmount, List<ExpandedAccount> accounts, Map<Account, Long> balanceChanges) {
|
||||
@ -1768,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());
|
||||
|
@ -109,9 +109,6 @@ public class BlockChain {
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private Long qoraPerQortReward;
|
||||
|
||||
/** Share of block reward/fees to founders. CALCULATED */
|
||||
private long foundersShare;
|
||||
|
||||
/**
|
||||
* Number of minted blocks required to reach next level from previous.
|
||||
* <p>
|
||||
@ -345,10 +342,6 @@ public class BlockChain {
|
||||
return this.qoraPerQortReward;
|
||||
}
|
||||
|
||||
public long getFoundersShare() {
|
||||
return this.foundersShare;
|
||||
}
|
||||
|
||||
public int getMinAccountLevelToMint() {
|
||||
return this.minAccountLevelToMint;
|
||||
}
|
||||
@ -446,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. */
|
||||
@ -460,17 +462,6 @@ public class BlockChain {
|
||||
cumulativeBlocks += this.blocksNeededByLevel.get(level);
|
||||
}
|
||||
|
||||
// Calculate founders' share
|
||||
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)");
|
||||
|
||||
this.foundersShare = 1_00000000L - totalShare;
|
||||
|
||||
// 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);
|
||||
|
@ -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
|
||||
}
|
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…
x
Reference in New Issue
Block a user