Merge branch 'share-bin-activation'

This commit is contained in:
CalDescent 2022-07-17 19:20:19 +01:00
commit fcd0d71cb6
15 changed files with 530 additions and 69 deletions

View File

@ -199,6 +199,11 @@ public class Block {
} }
public boolean hasShareBin(AccountLevelShareBin shareBin, int blockHeight) {
AccountLevelShareBin ourShareBin = this.getShareBin(blockHeight);
return ourShareBin != null && shareBin.id == ourShareBin.id;
}
public long distribute(long accountAmount, Map<String, Long> balanceChanges) { public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
if (this.isRecipientAlsoMinter) { if (this.isRecipientAlsoMinter) {
// minter & recipient the same - simpler case // minter & recipient the same - simpler case
@ -1891,12 +1896,67 @@ public class Block {
final boolean haveFounders = !onlineFounderAccounts.isEmpty(); final boolean haveFounders = !onlineFounderAccounts.isEmpty();
// Determine reward candidates based on account level // Determine reward candidates based on account level
List<AccountLevelShareBin> accountLevelShareBins = BlockChain.getInstance().getAccountLevelShareBins(); // This needs a deep copy, so the shares can be modified when tiers aren't activated yet
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) { List<AccountLevelShareBin> accountLevelShareBins = new ArrayList<>();
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out. for (AccountLevelShareBin accountLevelShareBin : BlockChain.getInstance().getAccountLevelShareBins()) {
accountLevelShareBins.add((AccountLevelShareBin) accountLevelShareBin.clone());
}
Map<Integer, List<ExpandedAccount>> accountsForShareBin = new HashMap<>();
// We might need to combine some share bins if they haven't reached the minimum number of minters yet
for (int binIndex = accountLevelShareBins.size()-1; binIndex >= 0; --binIndex) {
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex); 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(this.blockData.getHeight()) == accountLevelShareBin).collect(Collectors.toList()); // Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.hasShareBin(accountLevelShareBin, this.blockData.getHeight())).collect(Collectors.toList());
// Add any accounts that have been moved down from a higher tier
List<ExpandedAccount> existingBinnedAccounts = accountsForShareBin.get(binIndex);
if (existingBinnedAccounts != null)
binnedAccounts.addAll(existingBinnedAccounts);
// Logic below may only apply to higher levels, and only for share bins with a specific range of online accounts
if (accountLevelShareBin.levels.get(0) < BlockChain.getInstance().getShareBinActivationMinLevel() ||
binnedAccounts.isEmpty() || binnedAccounts.size() >= BlockChain.getInstance().getMinAccountsToActivateShareBin()) {
// Add all accounts for this share bin to the accountsForShareBin list
accountsForShareBin.put(binIndex, binnedAccounts);
continue;
}
// Share bin contains more than one, but less than the minimum number of minters. We treat this share bin
// as not activated yet. In these cases, the rewards and minters are combined and paid out to the previous
// share bin, to prevent a single or handful of accounts receiving the entire rewards for a share bin.
//
// Example:
//
// - Share bin for levels 5 and 6 has 100 minters
// - Share bin for levels 7 and 8 has 10 minters
//
// This is below the minimum of 30, so share bins are reconstructed as follows:
//
// - Share bin for levels 5 and 6 now contains 110 minters
// - Share bin for levels 7 and 8 now contains 0 minters
// - Share bin for levels 5 and 6 now pays out rewards for levels 5, 6, 7, and 8
// - Share bin for levels 7 and 8 pays zero rewards
//
// This process is iterative, so will combine several tiers if needed.
// Designate this share bin as empty
accountsForShareBin.put(binIndex, new ArrayList<>());
// Move the accounts originally intended for this share bin to the previous one
accountsForShareBin.put(binIndex - 1, binnedAccounts);
// Move the block reward from this share bin to the previous one
AccountLevelShareBin previousShareBin = accountLevelShareBins.get(binIndex - 1);
previousShareBin.share += accountLevelShareBin.share;
accountLevelShareBin.share = 0L;
}
// Now loop through (potentially modified) share bins and determine the reward candidates
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) {
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
List<ExpandedAccount> binnedAccounts = accountsForShareBin.get(binIndex);
// No online accounts in this bin? Skip to next one // No online accounts in this bin? Skip to next one
if (binnedAccounts.isEmpty()) if (binnedAccounts.isEmpty())

View File

@ -104,10 +104,23 @@ public class BlockChain {
private List<RewardByHeight> rewardsByHeight; private List<RewardByHeight> rewardsByHeight;
/** Share of block reward/fees by account level */ /** Share of block reward/fees by account level */
public static class AccountLevelShareBin { public static class AccountLevelShareBin implements Cloneable {
public int id;
public List<Integer> levels; public List<Integer> levels;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long share; public long share;
public Object clone() {
AccountLevelShareBin shareBinCopy = new AccountLevelShareBin();
List<Integer> levelsCopy = new ArrayList<>();
for (Integer level : this.levels) {
levelsCopy.add(level);
}
shareBinCopy.id = this.id;
shareBinCopy.levels = levelsCopy;
shareBinCopy.share = this.share;
return shareBinCopy;
}
} }
private List<AccountLevelShareBin> sharesByLevel; private List<AccountLevelShareBin> sharesByLevel;
/** Generated lookup of share-bin by account level */ /** Generated lookup of share-bin by account level */
@ -121,6 +134,12 @@ public class BlockChain {
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private Long qoraPerQortReward; private Long qoraPerQortReward;
/** Minimum number of accounts before a share bin is considered activated */
private int minAccountsToActivateShareBin;
/** Min level at which share bin activation takes place; lower levels allow less than minAccountsPerShareBin */
private int shareBinActivationMinLevel;
/** /**
* Number of minted blocks required to reach next level from previous. * Number of minted blocks required to reach next level from previous.
* <p> * <p>
@ -362,6 +381,14 @@ public class BlockChain {
return this.qoraPerQortReward; return this.qoraPerQortReward;
} }
public int getMinAccountsToActivateShareBin() {
return this.minAccountsToActivateShareBin;
}
public int getShareBinActivationMinLevel() {
return this.shareBinActivationMinLevel;
}
public int getMinAccountLevelToMint() { public int getMinAccountLevelToMint() {
return this.minAccountLevelToMint; return this.minAccountLevelToMint;
} }

View File

@ -39,14 +39,16 @@
{ "height": 3110401, "reward": 2.00 } { "height": 3110401, "reward": 2.00 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ], "blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -3,10 +3,9 @@ package org.qortal.test.minting;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.After; import org.junit.After;
@ -702,6 +701,15 @@ public class RewardTests extends Common {
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward); AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward); AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward);
// Orphan and ensure balances return to their previous values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
} }
} }
@ -787,6 +795,348 @@ public class RewardTests extends Common {
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward); AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward); AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward);
// Orphan and ensure balances return to their previous values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
}
}
/** Test rewards for level 7 and 8 accounts, when the tier doesn't yet have enough minters in it */
@Test
public void testLevel7And8RewardsPreActivation() throws DataException, IllegalAccessException {
Common.useSettings("test-settings-v2-reward-levels.json");
// Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true);
try (final Repository repository = RepositoryManager.getRepository()) {
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
// Alice self share online
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
mintingAndOnlineAccounts.add(aliceSelfShare);
// Bob self-share NOT online
// Chloe self share online
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
// Dilbert self share online
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
// Mint enough blocks to bump testAccount levels to 7 and 8
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(8) - 20; // 20 blocks before level 8, so that the test accounts reach the correct levels
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure that the levels are as we expect
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel());
assertEquals(8, (int) Common.getTestAccount(repository, "dilbert").getLevel());
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
// Mint a block
final long blockReward = BlockUtils.getNextBlockReward(repository);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure we are using the correct block reward value
assertEquals(100000000L, blockReward);
/*
* Alice, Chloe, and Dilbert are 'online'.
* Chloe is level 7; Dilbert is level 8.
* One founder online (Alice, who is also level 7).
* No legacy QORA holders.
*
* Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin.
* There are no level 5 and 6 online.
* Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8.
* Alice should receive the remainder (65%).
*/
final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%)
final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L;
final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward);
// Orphan and ensure balances return to their previous values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
}
}
/** Test rewards for level 9 and 10 accounts, when the tier doesn't yet have enough minters in it.
* Tier 7-8 isn't activated either, so the rewards and minters are all moved to tier 5-6. */
@Test
public void testLevel9And10RewardsPreActivation() throws DataException, IllegalAccessException {
Common.useSettings("test-settings-v2-reward-levels.json");
// Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true);
try (final Repository repository = RepositoryManager.getRepository()) {
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
// Alice self share online
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
mintingAndOnlineAccounts.add(aliceSelfShare);
// Bob self-share not initially online
// Chloe self share online
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
// Dilbert self share online
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
// Mint enough blocks to bump testAccount levels to 9 and 10
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(10) - 20; // 20 blocks before level 10, so that the test accounts reach the correct levels
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Bob self-share now comes online
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
mintingAndOnlineAccounts.add(bobRewardShareAccount);
// Ensure that the levels are as we expect
assertEquals(9, (int) Common.getTestAccount(repository, "alice").getLevel());
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
assertEquals(9, (int) Common.getTestAccount(repository, "chloe").getLevel());
assertEquals(10, (int) Common.getTestAccount(repository, "dilbert").getLevel());
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
// Mint a block
final long blockReward = BlockUtils.getNextBlockReward(repository);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure we are using the correct block reward value
assertEquals(100000000L, blockReward);
/*
* Alice, Bob, Chloe, and Dilbert are 'online'.
* Bob is level 1; Chloe is level 9; Dilbert is level 10.
* One founder online (Alice, who is also level 9).
* No legacy QORA holders.
*
* Levels 7+8, and 9+10 are not yet activated, so their rewards are added to the level 5 and 6 share bin.
* There are no levels 5-8 online.
* Chloe and Dilbert should receive equal shares of the 60% block reward for levels 5 to 10.
* Alice should receive the remainder (40%).
*/
final int level1And2SharePercent = 5_00; // 5%
final int level5To10SharePercent = 60_00; // 60% (combined 15%, 20%, and 25%)
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
final long level5To10ShareAmount = (blockReward * level5To10SharePercent) / 100L / 100L;
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
final long expectedLevel5To10Reward = level5To10ShareAmount / 2; // The reward is split between Chloe and Dilbert
final long expectedFounderReward = blockReward - level1And2ShareAmount - level5To10ShareAmount; // Alice should receive the remainder
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To10Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To10Reward);
// Orphan and ensure balances return to their previous values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
}
}
/** Test rewards for level 7 and 8 accounts, when the tier reaches the minimum number of accounts */
@Test
public void testLevel7And8RewardsPreAndPostActivation() throws DataException, IllegalAccessException {
Common.useSettings("test-settings-v2-reward-levels.json");
// Set minAccountsToActivateShareBin to 2 so that share bins 7-8 and 9-10 are considered inactive at first
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 2, true);
try (final Repository repository = RepositoryManager.getRepository()) {
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
// Alice self share online
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
mintingAndOnlineAccounts.add(aliceSelfShare);
// Bob self-share NOT online
// Chloe self share online
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
// Dilbert self share online
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
// Mint enough blocks to bump two of the testAccount levels to 7
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(7) - 12; // 12 blocks before level 7, so that dilbert and alice have reached level 7, but chloe will reach it in the next 2 blocks
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure that the levels are as we expect
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel());
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
// Now that dilbert has reached level 7, we can capture initial balances
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
// Mint a block
long blockReward = BlockUtils.getNextBlockReward(repository);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure we are using the correct block reward value
assertEquals(100000000L, blockReward);
/*
* Alice, Chloe, and Dilbert are 'online'.
* Chloe is level 6; Dilbert is level 7.
* One founder online (Alice, who is also level 7).
* No legacy QORA holders.
*
* Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin.
* There are no level 5 and 6 online.
* Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8.
* Alice should receive the remainder (65%).
*/
final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%)
final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L;
final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward);
// Ensure that the levels are as we expect
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel());
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
// Capture pre-activation balances
Map<String, Map<Long, Long>> preActivationBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
final long alicePreActivationBalance = preActivationBalances.get("alice").get(Asset.QORT);
final long bobPreActivationBalance = preActivationBalances.get("bob").get(Asset.QORT);
final long chloePreActivationBalance = preActivationBalances.get("chloe").get(Asset.QORT);
final long dilbertPreActivationBalance = preActivationBalances.get("dilbert").get(Asset.QORT);
// Mint another block
blockReward = BlockUtils.getNextBlockReward(repository);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// Ensure that the levels are as we expect (chloe has now increased to level 7; level 7-8 is now activated)
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel());
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
/*
* Alice, Chloe, and Dilbert are 'online'.
* Chloe and Dilbert are level 7.
* One founder online (Alice, who is also level 7).
* No legacy QORA holders.
*
* Level 7 and 8 is now activated, so its rewards are paid out in the normal way.
* There are no level 5 and 6 online.
* Chloe and Dilbert should receive equal shares of the 20% block reward for levels 7 to 8.
* Alice should receive the remainder (80%).
*/
final int level7To8SharePercent = 20_00; // 20%
final long level7To8ShareAmount = (blockReward * level7To8SharePercent) / 100L / 100L;
final long expectedLevel7To8Reward = level7To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
final long newExpectedFounderReward = blockReward - level7To8ShareAmount; // Alice should receive the remainder
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance+newExpectedFounderReward);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance); // Bob not online so his balance remains the same
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance+expectedLevel7To8Reward);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance+expectedLevel7To8Reward);
// Orphan and ensure balances return to their pre-activation values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance);
// Orphan again and ensure balances return to their initial values
BlockUtils.orphanBlocks(repository, 1);
// Validate the balances
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
} }
} }

View File

@ -20,14 +20,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }, { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 },

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 1,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }

View File

@ -24,14 +24,16 @@
{ "height": 21, "reward": 1 } { "height": 21, "reward": 1 }
], ],
"sharesByLevel": [ "sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 }, { "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 }, { "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 }, { "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 }, { "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShare": 0.20,
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"minAccountsToActivateShareBin": 30,
"shareBinActivationMinLevel": 7,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }