3
0
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:
catbref 2020-06-02 10:42:45 +01:00
parent b5512dfa91
commit 5ffddd0169
7 changed files with 354 additions and 87 deletions

View File

@ -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());

View File

@ -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);

View File

@ -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);
}
}
}

View 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 }
]
}
}

View 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 }
]
}
}

View File

@ -0,0 +1,7 @@
{
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v2-leftover-reward.json",
"wipeUnconfirmedOnStart": false,
"testNtpOffset": 0,
"minPeers": 0
}

View File

@ -0,0 +1,7 @@
{
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v2-reward-scaling.json",
"wipeUnconfirmedOnStart": false,
"testNtpOffset": 0,
"minPeers": 0
}