From a9267760ebe56a96a64bab94309735b4e2cc3b2e Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 8 Jul 2022 11:12:58 +0100 Subject: [PATCH] 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. # Conflicts: # src/test/java/org/qortal/test/minting/RewardTests.java --- src/main/java/org/qortal/block/Block.java | 2 +- .../java/org/qortal/block/BlockChain.java | 30 ++++++++---- src/main/resources/blockchain.json | 5 +- .../org/qortal/test/minting/RewardTests.java | 46 ++++++++++++++++++- .../test-chain-v2-block-timestamps.json | 5 +- .../test-chain-v2-disable-reference.json | 5 +- .../test-chain-v2-founder-rewards.json | 5 +- .../test-chain-v2-leftover-reward.json | 5 +- src/test/resources/test-chain-v2-minting.json | 5 +- .../test-chain-v2-qora-holder-extremes.json | 5 +- .../resources/test-chain-v2-qora-holder.json | 5 +- .../test-chain-v2-reward-levels.json | 5 +- .../test-chain-v2-reward-scaling.json | 5 +- .../test-chain-v2-reward-shares.json | 5 +- src/test/resources/test-chain-v2.json | 5 +- 15 files changed, 113 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 2ef97ef5..e1ad84ff 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1975,7 +1975,7 @@ public class Block { // Fetch list of legacy QORA holders who haven't reached their cap of QORT reward. List qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight()); 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 if (!haveFounders) { diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index cddb38cc..793c5210 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -126,9 +126,13 @@ public class BlockChain { /** Generated lookup of share-bin by account level */ private AccountLevelShareBin[] shareBinsByLevel; - /** Share of block reward/fees to legacy QORA coin holders */ - @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) - private Long qoraHoldersShare; + /** Share of block reward/fees to legacy QORA coin holders, by block height */ + public static class ShareByHeight { + public int height; + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + public long share; + } + private List qoraHoldersShareByHeight; /** How many legacy QORA per 1 QORT of block reward. */ @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) @@ -382,10 +386,6 @@ public class BlockChain { return this.cumulativeBlocksByLevel; } - public long getQoraHoldersShare() { - return this.qoraHoldersShare; - } - public long getQoraPerQortReward() { return this.qoraPerQortReward; } @@ -504,6 +504,15 @@ public class BlockChain { 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 */ private void validateConfig() { if (this.genesisInfo == null) @@ -515,8 +524,8 @@ public class BlockChain { if (this.sharesByLevel == null) Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config"); - if (this.qoraHoldersShare == null) - Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config"); + if (this.qoraHoldersShareByHeight == null) + Settings.throwValidationError("No \"qoraHoldersShareByHeight\" entry found in blockchain config"); if (this.qoraPerQortReward == null) Settings.throwValidationError("No \"qoraPerQortReward\" entry found in blockchain config"); @@ -554,7 +563,7 @@ public class BlockChain { Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name())); // Check block reward share bounds - long totalShare = this.qoraHoldersShare; + long totalShare = this.getQoraHoldersShareAtHeight(1); // Add share percents for account-level-based rewards for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel) totalShare += accountLevelShareBin.share; @@ -592,6 +601,7 @@ public class BlockChain { this.blocksNeededByLevel = Collections.unmodifiableList(this.blocksNeededByLevel); this.cumulativeBlocksByLevel = Collections.unmodifiableList(this.cumulativeBlocksByLevel); this.blockTimingsByHeight = Collections.unmodifiableList(this.blockTimingsByHeight); + this.qoraHoldersShareByHeight = Collections.unmodifiableList(this.qoraHoldersShareByHeight); } /** diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 381a280a..6d38ffef 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -46,7 +46,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 9999999, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/java/org/qortal/test/minting/RewardTests.java b/src/test/java/org/qortal/test/minting/RewardTests.java index 4aee2de1..5e2e2463 100644 --- a/src/test/java/org/qortal/test/minting/RewardTests.java +++ b/src/test/java/org/qortal/test/minting/RewardTests.java @@ -13,6 +13,7 @@ import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; +import org.qortal.block.Block; import org.qortal.block.BlockChain; import org.qortal.block.BlockChain.RewardByHeight; import org.qortal.controller.BlockMinter; @@ -108,7 +109,7 @@ public class RewardTests extends Common { public void testLegacyQoraReward() throws DataException { 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); long qoraPerQort = BlockChain.getInstance().getQoraPerQortReward(); @@ -189,6 +190,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 chloeQortBalanceDeltaAtEachHeight = new HashMap<>(); + + try (final Repository repository = RepositoryManager.getRepository()) { + Map> 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. */ @Test public void testLevel1() throws DataException { @@ -294,7 +336,7 @@ public class RewardTests extends Common { * 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; long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT); diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index d7beba3c..d041463f 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -26,7 +26,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index 3ad7da60..8a4a58cc 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -30,7 +30,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index c9d63800..e8f1e52b 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index ad5a1f9a..233f6aa0 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index 1d57e119..b1a25e86 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 97d7a320..a3bd1921 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 5, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 6d0c2223..76d10b0d 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index 03a05a5e..5434345d 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 1, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index a108c651..97736e7e 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 8f14e48f..74ce003c 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -30,7 +30,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 3b380d29..138dd5d9 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -31,7 +31,10 @@ { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } ], - "qoraHoldersShare": 0.20, + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], "qoraPerQortReward": 250, "minAccountsToActivateShareBin": 30, "shareBinActivationMinLevel": 7,