diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 01b12aa5a4bb..3ee3b80aca0e 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -342,6 +342,13 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 4032; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 3226; // 80% of 4032 + // Deployment of Block Reward Reallocation + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nStartTime = 1601510400; // Oct 1st, 2020 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nTimeout = 1633046400; // Oct 1st, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThreshold = 3226; // 80% of 4032 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000027b81f49774e9f7fc93f"); // 1215000 @@ -529,6 +536,13 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 100; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 50; // 50% of 100 + // Deployment of Block Reward Reallocation + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nStartTime = 1598918400; // Sep 1st, 2020 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nTimeout = 1630454400; // Sep 1st, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nWindowSize = 100; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThreshold = 50; // 50% of 100 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000000ac720e0b2ed13d"); // 260000 @@ -688,6 +702,13 @@ class CDevNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 100; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 50; // 50% of 100 + // Deployment of Block Reward Reallocation + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nStartTime = 1598918400; // Sep 1st, 2020 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nTimeout = 1900281600; // Mar 21st, 2030 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nWindowSize = 100; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThreshold = 50; // 50% of 100 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -826,6 +847,11 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].bit = 4; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nTimeout = 999999999999ULL; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nStartTime = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nTimeout = 999999999999ULL; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nWindowSize = 500; + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThreshold = 400; // 80% // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); diff --git a/src/consensus/params.h b/src/consensus/params.h index 4c6e030855fb..34145dcebb0b 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -20,6 +20,7 @@ enum DeploymentPos DEPLOYMENT_BIP147, // Deployment of BIP147 (NULLDUMMY) DEPLOYMENT_DIP0003, // Deployment of DIP0002 and DIP0003 (txv3 and deterministic MN lists) DEPLOYMENT_DIP0008, // Deployment of ChainLock enforcement + DEPLOYMENT_REALLOC, // Deployment of Block Reward Reallocation // NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp MAX_VERSION_BITS_DEPLOYMENTS }; diff --git a/src/masternode/masternode-payments.cpp b/src/masternode/masternode-payments.cpp index 99462e6183c5..e64935e39f11 100644 --- a/src/masternode/masternode-payments.cpp +++ b/src/masternode/masternode-payments.cpp @@ -313,13 +313,21 @@ bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, { voutMasternodePaymentsRet.clear(); - CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward); - const CBlockIndex* pindex; + int nReallocActivationHeight{std::numeric_limits::max()}; + { LOCK(cs_main); pindex = chainActive[nBlockHeight - 1]; + + const Consensus::Params& consensusParams = Params().GetConsensus(); + if (VersionBitsState(pindex, consensusParams, Consensus::DEPLOYMENT_REALLOC, versionbitscache) == ThresholdState::ACTIVE) { + nReallocActivationHeight = VersionBitsStateSinceHeight(pindex, consensusParams, Consensus::DEPLOYMENT_REALLOC, versionbitscache); + } } + + CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward, nReallocActivationHeight); + auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee(); if (!dmnPayee) { return false; diff --git a/src/validation.cpp b/src/validation.cpp index 01eb9bf0f595..27984a403352 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1164,7 +1164,7 @@ CAmount GetBlockSubsidy(int nPrevBits, int nPrevHeight, const Consensus::Params& return fSuperblockPartOnly ? nSuperblockPart : nSubsidy - nSuperblockPart; } -CAmount GetMasternodePayment(int nHeight, CAmount blockValue) +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, int nReallocActivationHeight) { CAmount ret = blockValue/5; // start at 20% @@ -1182,7 +1182,47 @@ CAmount GetMasternodePayment(int nHeight, CAmount blockValue) if(nHeight > nMNPIBlock+(nMNPIPeriod* 7)) ret += blockValue / 40; // 278960 - 47.5% - 2015-06-01 if(nHeight > nMNPIBlock+(nMNPIPeriod* 9)) ret += blockValue / 40; // 313520 - 50.0% - 2015-08-03 - return ret; + if (nHeight < nReallocActivationHeight) { + // Block Reward Realocation is not activated yet, nothing to do + return ret; + } + + int nSuperblockCycle = Params().GetConsensus().nSuperblockCycle; + // Actual realocation starts in the cycle next to one activation happens in + int nReallocStart = nReallocActivationHeight - nReallocActivationHeight % nSuperblockCycle + nSuperblockCycle; + + if (nHeight < nReallocStart) { + // Activated but we have to wait for the next cycle to start realocation, nothing to do + return ret; + } + + // Periods used to reallocate the masternode reward from 50% to 60% + static std::vector vecPeriods{ + 513, // Period 1: 51.3% + 526, // Period 2: 52.6% + 533, // Period 3: 53.3% + 540, // Period 4: 54% + 546, // Period 5: 54.6% + 552, // Period 6: 55.2% + 557, // Period 7: 55.7% + 562, // Period 8: 56.2% + 567, // Period 9: 56.7% + 572, // Period 10: 57.2% + 577, // Period 11: 57.7% + 582, // Period 12: 58.2% + 585, // Period 13: 58.5% + 588, // Period 14: 58.8% + 591, // Period 15: 59.1% + 594, // Period 16: 59.4% + 597, // Period 17: 59.7% + 599, // Period 18: 59.9% + 600 // Period 19: 60% + }; + + int nReallocCycle = nSuperblockCycle * 3; + int nCurrentPeriod = std::min((nHeight - nReallocStart) / nReallocCycle, vecPeriods.size() - 1); + + return static_cast(blockValue * vecPeriods[nCurrentPeriod] / 1000); } bool IsInitialBlockDownload() diff --git a/src/validation.h b/src/validation.h index 580be95353d7..c6ee9ca12388 100644 --- a/src/validation.h +++ b/src/validation.h @@ -290,7 +290,7 @@ bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, double ConvertBitsToDouble(unsigned int nBits); CAmount GetBlockSubsidy(int nBits, int nHeight, const Consensus::Params& consensusParams, bool fSuperblockPartOnly = false); -CAmount GetMasternodePayment(int nHeight, CAmount blockValue); +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, int nReallocActivationHeight = std::numeric_limits::max() /* not activated */); /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pindex); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index b6ba78426b2e..d41344b94a0a 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -35,7 +35,12 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/ "dip0008", /*.gbt_force =*/ true, /*.check_mn_protocol =*/ false, - } + }, + { + /*.name =*/ "realloc", + /*.gbt_force =*/ true, + /*.check_mn_protocol =*/ false, + }, }; ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const diff --git a/test/functional/feature_block_reward_reallocation.py b/test/functional/feature_block_reward_reallocation.py new file mode 100755 index 000000000000..6d84f75078ad --- /dev/null +++ b/test/functional/feature_block_reward_reallocation.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2020 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.blocktools import create_block, create_coinbase, get_masternode_payment +from test_framework.mininode import * +from test_framework.script import CScript +from test_framework.test_framework import DashTestFramework +from test_framework.util import assert_equal, get_bip9_status, hex_str_to_bytes + +''' +feature_block_reward_reallocation.py + +Checks block reward reallocation correctness + +''' + +class BlockRewardReallocationTest(DashTestFramework): + def set_test_params(self): + self.set_dash_test_params(4, 3, fast_dip3_enforcement=True) + self.set_dash_dip8_activation(450) + + # 536870912 == 0x20000000, i.e. not signalling for anything + def create_test_block(self, version=536870912): + self.bump_mocktime(150) + bt = self.nodes[0].getblocktemplate() + tip = int(bt['previousblockhash'], 16) + nextheight = bt['height'] + + coinbase = create_coinbase(nextheight) + coinbase.nVersion = 3 + coinbase.nType = 5 # CbTx + coinbase.vout[0].nValue = bt['coinbasevalue'] + for mn in bt['masternode']: + coinbase.vout.append(CTxOut(mn['amount'], CScript(hex_str_to_bytes(mn['script'])))) + coinbase.vout[0].nValue -= mn['amount'] + cbtx = FromHex(CCbTx(), bt['coinbase_payload']) + coinbase.vExtraPayload = cbtx.serialize() + coinbase.rehash() + coinbase.calc_sha256() + + block = create_block(tip, coinbase, self.mocktime) + block.nVersion = version + # Add quorum commitments from template + for tx in bt['transactions']: + tx2 = FromHex(CTransaction(), tx['data']) + if tx2.nType == 6: + block.vtx.append(tx2) + block.hashMerkleRoot = block.calc_merkle_root() + block.rehash() + block.solve() + return block + + def signal(self, num_blocks, expected_lockin): + self.log.info("Signal with %d/500 blocks" % (num_blocks)) + # create and send non-signalling blocks + for i in range(500 - num_blocks): + test_block = self.create_test_block() + self.nodes[0].p2p.send_blocks_and_test([test_block], self.nodes[0], timeout=5) + # generate at most 10 signaling blocks at a time + for i in range((num_blocks - 1) // 10): + self.bump_mocktime(10) + self.nodes[0].generate(10) + self.sync_blocks() + self.nodes[0].generate((num_blocks - 1) % 10) + self.sync_blocks() + assert_equal(get_bip9_status(self.nodes[0], 'realloc')['status'], 'started') + bestblockhash = self.nodes[0].generate(1)[0] + self.sync_blocks() + self.nodes[0].getblock(bestblockhash, 1) + if expected_lockin: + assert_equal(get_bip9_status(self.nodes[0], 'realloc')['status'], 'locked_in') + else: + assert_equal(get_bip9_status(self.nodes[0], 'realloc')['status'], 'started') + + def run_test(self): + self.log.info("Wait for DIP3 to activate") + while get_bip9_status(self.nodes[0], 'dip0003')['status'] != 'active': + self.bump_mocktime(10) + self.nodes[0].generate(10) + self.sync_blocks() + + self.nodes[0].add_p2p_connection(P2PDataStore()) + network_thread_start() + self.nodes[0].p2p.wait_for_verack() + + self.log.info("Mine all but one remaining block in the window") + bi = self.nodes[0].getblockchaininfo() + for i in range(498 - bi['blocks']): + self.bump_mocktime(1) + self.nodes[0].generate(1) + self.sync_blocks() + + self.log.info("Initial state is DEFINED") + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 498) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'defined') + + self.log.info("Advance from DEFINED to STARTED at height = 499") + self.nodes[0].generate(1) + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 499) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'started') + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], 400) + + self.signal(399, False) # 1 block short + self.signal(400, True) # just enough to lock in + + self.log.info("Still LOCKED_IN at height = 1498") + for i in range(49): + self.bump_mocktime(10) + self.nodes[0].generate(10) + self.sync_blocks() + self.nodes[0].generate(9) + self.sync_blocks() + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 1998) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'locked_in') + + self.log.info("Advance from LOCKED_IN to ACTIVE at height = 1999") + self.nodes[0].generate(1) # activation + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 1999) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'active') + assert_equal(bi['bip9_softforks']['realloc']['since'], 2000) + + self.log.info("Reward split should stay ~50/50 before the first superblock after activation") + # This applies even if reallocation was activated right at superblock height like it does here + bt = self.nodes[0].getblocktemplate() + assert_equal(bt['height'], 2000) + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2000)) + self.nodes[0].generate(9) + self.sync_blocks() + bt = self.nodes[0].getblocktemplate() + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2000)) + assert_equal(bt['coinbasevalue'], 17171634268) + assert_equal(bt['masternode'][0]['amount'], 8585817128) # 0.4999999997 + + self.log.info("Reallocation should kick-in with the superblock mined at height = 2010") + for period in range(19): # there will be 19 adjustments, 3 superblocks long each + for i in range(3): + self.bump_mocktime(10) + self.nodes[0].generate(10) + self.sync_blocks() + bt = self.nodes[0].getblocktemplate() + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2000)) + + self.log.info("Reward split should reach ~60/40 after reallocation is done") + assert_equal(bt['coinbasevalue'], 12766530779) + assert_equal(bt['masternode'][0]['amount'], 7659918467) # 0.6 + + self.log.info("Reward split should stay ~60/40 after reallocation is done") + for period in range(10): # check 10 next superblocks + self.bump_mocktime(10) + self.nodes[0].generate(10) + self.sync_blocks() + bt = self.nodes[0].getblocktemplate() + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2000)) + assert_equal(bt['coinbasevalue'], 12766530779) + assert_equal(bt['masternode'][0]['amount'], 7659918467) # 0.6 + +if __name__ == '__main__': + BlockRewardReallocationTest().main() diff --git a/test/functional/feature_dip3_deterministicmns.py b/test/functional/feature_dip3_deterministicmns.py index 9cd7e5ef2c3d..5dc363a0886a 100755 --- a/test/functional/feature_dip3_deterministicmns.py +++ b/test/functional/feature_dip3_deterministicmns.py @@ -390,7 +390,11 @@ def mine_block(self, node, vtx=[], miner_address=None, mn_payee=None, mn_amount= coinbasevalue += new_fees if mn_amount is None: - mn_amount = get_masternode_payment(height, coinbasevalue) + realloc_info = get_bip9_status(self.nodes[0], 'realloc') + realloc_height = 99999999 + if realloc_info['status'] == 'active': + realloc_height = realloc_info['since'] + mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height) miner_amount = coinbasevalue - mn_amount outputs = {miner_address: str(Decimal(miner_amount) / COIN)} diff --git a/test/functional/feature_llmq_is_cl_conflicts.py b/test/functional/feature_llmq_is_cl_conflicts.py index 8ce5ab388c18..463f6cf916e9 100755 --- a/test/functional/feature_llmq_is_cl_conflicts.py +++ b/test/functional/feature_llmq_is_cl_conflicts.py @@ -8,7 +8,7 @@ from test_framework.blocktools import get_masternode_payment, create_coinbase, create_block from test_framework.mininode import * from test_framework.test_framework import DashTestFramework -from test_framework.util import assert_raises_rpc_error +from test_framework.util import assert_raises_rpc_error, get_bip9_status ''' feature_llmq_is_cl_conflicts.py @@ -240,7 +240,11 @@ def create_block(self, node, vtx=[]): coinbasevalue -= bt_fees coinbasevalue += new_fees - mn_amount = get_masternode_payment(height, coinbasevalue) + realloc_info = get_bip9_status(self.nodes[0], 'realloc') + realloc_height = 99999999 + if realloc_info['status'] == 'active': + realloc_height = realloc_info['since'] + mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height) miner_amount = coinbasevalue - mn_amount outputs = {miner_address: str(Decimal(miner_amount) / COIN)} diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index f6730063f227..dd708c031d9f 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -87,7 +87,7 @@ def get_legacy_sigopcount_tx(tx, fAccurate=True): return count # Identical to GetMasternodePayment in C++ code -def get_masternode_payment(nHeight, blockValue): +def get_masternode_payment(nHeight, blockValue, nReallocActivationHeight): ret = int(blockValue / 5) nMNPIBlock = 350 @@ -112,4 +112,42 @@ def get_masternode_payment(nHeight, blockValue): if nHeight > nMNPIBlock+(nMNPIPeriod* 9): ret += int(blockValue / 40) - return ret + if nHeight < nReallocActivationHeight: + # Block Reward Realocation is not activated yet, nothing to do + return ret + + nSuperblockCycle = 10 + # Actual realocation starts in the cycle next to one activation happens in + nReallocStart = nReallocActivationHeight - nReallocActivationHeight % nSuperblockCycle + nSuperblockCycle + + if nHeight < nReallocStart: + # Activated but we have to wait for the next cycle to start realocation, nothing to do + return ret + + # Periods used to reallocate the masternode reward from 50% to 60% + vecPeriods = [ + 513, # Period 1: 51.3% + 526, # Period 2: 52.6% + 533, # Period 3: 53.3% + 540, # Period 4: 54% + 546, # Period 5: 54.6% + 552, # Period 6: 55.2% + 557, # Period 7: 55.7% + 562, # Period 8: 56.2% + 567, # Period 9: 56.7% + 572, # Period 10: 57.2% + 577, # Period 11: 57.7% + 582, # Period 12: 58.2% + 585, # Period 13: 58.5% + 588, # Period 14: 58.8% + 591, # Period 15: 59.1% + 594, # Period 16: 59.4% + 597, # Period 17: 59.7% + 599, # Period 18: 59.9% + 600 # Period 19: 60% + ] + + nReallocCycle = nSuperblockCycle * 3 + nCurrentPeriod = min(int((nHeight - nReallocStart) / nReallocCycle), len(vecPeriods) - 1) + + return int(blockValue * vecPeriods[nCurrentPeriod] / 1000) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index fe2d47343c85..71c5440aa03b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -82,6 +82,7 @@ 'feature_llmq_is_retroactive.py', # NOTE: needs dash_hash to pass 'feature_llmq_dkgerrors.py', # NOTE: needs dash_hash to pass 'feature_dip4_coinbasemerkleroots.py', # NOTE: needs dash_hash to pass + 'feature_block_reward_reallocation.py', # vv Tests less than 60s vv 'p2p_sendheaders.py', # NOTE: needs dash_hash to pass 'wallet_zapwallettxes.py',