qoraHoldersShare reworked to qoraHoldersShareByHeight.

This allows the QORA share percentage to be modified at different heights, based on community votes. Added unit test to simulate a reduction.
This commit is contained in:
CalDescent 2022-07-08 11:12:58 +01:00
parent 57bd3c3459
commit 90e8cfc737
15 changed files with 114 additions and 25 deletions

View File

@ -1914,7 +1914,7 @@ public class Block {
// Fetch list of legacy QORA holders who haven't reached their cap of QORT reward. // Fetch list of legacy QORA holders who haven't reached their cap of QORT reward.
List<EligibleQoraHolderData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight()); List<EligibleQoraHolderData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
final boolean haveQoraHolders = !qoraHolders.isEmpty(); final boolean haveQoraHolders = !qoraHolders.isEmpty();
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(this.blockData.getHeight());
// Perform account-level-based reward scaling if appropriate // Perform account-level-based reward scaling if appropriate
if (!haveFounders) { if (!haveFounders) {

View File

@ -113,9 +113,13 @@ public class BlockChain {
/** Generated lookup of share-bin by account level */ /** Generated lookup of share-bin by account level */
private AccountLevelShareBin[] shareBinsByLevel; private AccountLevelShareBin[] shareBinsByLevel;
/** Share of block reward/fees to legacy QORA coin holders */ /** Share of block reward/fees to legacy QORA coin holders, by block height */
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public static class ShareByHeight {
private Long qoraHoldersShare; public int height;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long share;
}
private List<ShareByHeight> qoraHoldersShareByHeight;
/** How many legacy QORA per 1 QORT of block reward. */ /** How many legacy QORA per 1 QORT of block reward. */
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
@ -354,10 +358,6 @@ public class BlockChain {
return this.cumulativeBlocksByLevel; return this.cumulativeBlocksByLevel;
} }
public long getQoraHoldersShare() {
return this.qoraHoldersShare;
}
public long getQoraPerQortReward() { public long getQoraPerQortReward() {
return this.qoraPerQortReward; return this.qoraPerQortReward;
} }
@ -468,6 +468,15 @@ public class BlockChain {
return 0; return 0;
} }
public long getQoraHoldersShareAtHeight(int ourHeight) {
// Scan through for QORA share at our height
for (int i = qoraHoldersShareByHeight.size() - 1; i >= 0; --i)
if (qoraHoldersShareByHeight.get(i).height <= ourHeight)
return qoraHoldersShareByHeight.get(i).share;
return 0;
}
/** Validate blockchain config read from JSON */ /** Validate blockchain config read from JSON */
private void validateConfig() { private void validateConfig() {
if (this.genesisInfo == null) if (this.genesisInfo == null)
@ -479,8 +488,8 @@ public class BlockChain {
if (this.sharesByLevel == null) if (this.sharesByLevel == null)
Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config"); Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config");
if (this.qoraHoldersShare == null) if (this.qoraHoldersShareByHeight == null)
Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config"); Settings.throwValidationError("No \"qoraHoldersShareByHeight\" entry found in blockchain config");
if (this.qoraPerQortReward == null) if (this.qoraPerQortReward == null)
Settings.throwValidationError("No \"qoraPerQortReward\" entry found in blockchain config"); Settings.throwValidationError("No \"qoraPerQortReward\" entry found in blockchain config");
@ -518,7 +527,7 @@ public class BlockChain {
Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name())); Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name()));
// Check block reward share bounds // Check block reward share bounds
long totalShare = this.qoraHoldersShare; long totalShare = this.getQoraHoldersShareAtHeight(1);
// Add share percents for account-level-based rewards // Add share percents for account-level-based rewards
for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel) for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel)
totalShare += accountLevelShareBin.share; totalShare += accountLevelShareBin.share;
@ -556,6 +565,7 @@ public class BlockChain {
this.blocksNeededByLevel = Collections.unmodifiableList(this.blocksNeededByLevel); this.blocksNeededByLevel = Collections.unmodifiableList(this.blocksNeededByLevel);
this.cumulativeBlocksByLevel = Collections.unmodifiableList(this.cumulativeBlocksByLevel); this.cumulativeBlocksByLevel = Collections.unmodifiableList(this.cumulativeBlocksByLevel);
this.blockTimingsByHeight = Collections.unmodifiableList(this.blockTimingsByHeight); this.blockTimingsByHeight = Collections.unmodifiableList(this.blockTimingsByHeight);
this.qoraHoldersShareByHeight = Collections.unmodifiableList(this.qoraHoldersShareByHeight);
} }
/** /**

View File

@ -45,7 +45,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 9999999, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ], "blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -4,6 +4,7 @@ import static org.junit.Assert.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -14,6 +15,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.Block;
import org.qortal.block.BlockChain; import org.qortal.block.BlockChain;
import org.qortal.block.BlockChain.RewardByHeight; import org.qortal.block.BlockChain.RewardByHeight;
import org.qortal.controller.BlockMinter; import org.qortal.controller.BlockMinter;
@ -109,7 +111,7 @@ public class RewardTests extends Common {
public void testLegacyQoraReward() throws DataException { public void testLegacyQoraReward() throws DataException {
Common.useSettings("test-settings-v2-qora-holder-extremes.json"); Common.useSettings("test-settings-v2-qora-holder-extremes.json");
long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1);
BigInteger qoraHoldersShareBI = BigInteger.valueOf(qoraHoldersShare); BigInteger qoraHoldersShareBI = BigInteger.valueOf(qoraHoldersShare);
long qoraPerQort = BlockChain.getInstance().getQoraPerQortReward(); long qoraPerQort = BlockChain.getInstance().getQoraPerQortReward();
@ -190,6 +192,47 @@ public class RewardTests extends Common {
} }
} }
@Test
public void testLegacyQoraRewardReduction() throws DataException {
Common.useSettings("test-settings-v2-qora-holder-extremes.json");
// Make sure that the QORA share reduces between blocks 4 and 5
assertTrue(BlockChain.getInstance().getQoraHoldersShareAtHeight(5) < BlockChain.getInstance().getQoraHoldersShareAtHeight(4));
// Keep track of balance deltas at each height
Map<Integer, Long> chloeQortBalanceDeltaAtEachHeight = new HashMap<>();
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 chloeLastQortBalance = initialBalances.get("chloe").get(Asset.QORT);
for (int i=2; i<=10; i++) {
Block block = BlockUtils.mintBlock(repository);
// Add to map of balance deltas at each height
long chloeNewQortBalance = AccountUtils.getBalance(repository, "chloe", Asset.QORT);
chloeQortBalanceDeltaAtEachHeight.put(block.getBlockData().getHeight(), chloeNewQortBalance - chloeLastQortBalance);
chloeLastQortBalance = chloeNewQortBalance;
}
// Ensure blocks 2-4 paid out the same rewards to Chloe
assertEquals(chloeQortBalanceDeltaAtEachHeight.get(2), chloeQortBalanceDeltaAtEachHeight.get(4));
// Ensure block 5 paid a lower reward
assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) < chloeQortBalanceDeltaAtEachHeight.get(4));
// Check that the reward was 20x lower
assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) == chloeQortBalanceDeltaAtEachHeight.get(4) / 20);
// Orphan to block 4 and ensure that Chloe's balance hasn't been incorrectly affected by the reward reduction
BlockUtils.orphanToBlock(repository, 4);
long expectedChloeQortBalance = initialBalances.get("chloe").get(Asset.QORT) + chloeQortBalanceDeltaAtEachHeight.get(2) +
chloeQortBalanceDeltaAtEachHeight.get(3) + chloeQortBalanceDeltaAtEachHeight.get(4);
assertEquals(expectedChloeQortBalance, AccountUtils.getBalance(repository, "chloe", Asset.QORT));
}
}
/** Use Alice-Chloe reward-share to bump Chloe from level 0 to level 1, then check orphaning works as expected. */ /** Use Alice-Chloe reward-share to bump Chloe from level 0 to level 1, then check orphaning works as expected. */
@Test @Test
public void testLevel1() throws DataException { public void testLevel1() throws DataException {
@ -295,7 +338,7 @@ public class RewardTests extends Common {
* So Dilbert should receive 100% - legacy QORA holder's share. * So Dilbert should receive 100% - legacy QORA holder's share.
*/ */
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1);
final long remainingShare = 1_00000000 - qoraHoldersShare; final long remainingShare = 1_00000000 - qoraHoldersShare;
long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT); long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT);

View File

@ -26,7 +26,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 5, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [

View File

@ -30,7 +30,10 @@
{ "levels": [ 7, 8 ], "share": 0.20 }, { "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 } { "levels": [ 9, 10 ], "share": 0.25 }
], ],
"qoraHoldersShare": 0.20, "qoraHoldersShareByHeight": [
{ "height": 1, "share": 0.20 },
{ "height": 1000000, "share": 0.01 }
],
"qoraPerQortReward": 250, "qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [ "blockTimingsByHeight": [