From 7d6aba47cfd85e2260f2141edb8963487eb88992 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 10 Sep 2020 19:23:11 +0300 Subject: [PATCH 1/3] Implement Block Reward Reallocation (#3691) * Implement Block Reward Reallocation * Add integr. test * drop unused variable * Sep -> Oct * Update test/functional/feature_block_reward_reallocation.py Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> * Revert to Sep for testnet and devnet * validation: Refactor reallocation calculations Makes it much more readable imo and avoids calculating the percentage each time. * test: Align reallocation calculation with c++ (GetMasternodePayment) * test: Make feature_block_reward_allocation.py executable * Make linter happy Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Co-authored-by: xdustinface --- src/chainparams.cpp | 26 +++ src/consensus/params.h | 1 + src/masternode/masternode-payments.cpp | 12 +- src/validation.cpp | 44 ++++- src/validation.h | 2 +- src/versionbits.cpp | 7 +- test/functional/dip3-deterministicmns.py | 6 +- .../feature_block_reward_reallocation.py | 163 ++++++++++++++++++ test/functional/llmq-is-cl-conflicts.py | 8 +- test/functional/test_framework/blocktools.py | 42 ++++- test/functional/test_runner.py | 1 + 11 files changed, 301 insertions(+), 11 deletions(-) create mode 100755 test/functional/feature_block_reward_reallocation.py diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d80a6ec8c4..9620c85d40 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -343,6 +343,13 @@ public: 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 @@ -530,6 +537,13 @@ public: 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 @@ -689,6 +703,13 @@ public: 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"); @@ -827,6 +848,11 @@ public: 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 f6cf8c60ff..62c7456a84 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 06debb1724..e99cc9cc6f 100644 --- a/src/masternode/masternode-payments.cpp +++ b/src/masternode/masternode-payments.cpp @@ -319,14 +319,22 @@ 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); + } } uint256 proTxHash; + + 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 9f6c4fa543..2bf0abf426 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1155,7 +1155,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% @@ -1173,7 +1173,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 5b1aa0adf1..e3ae7edbea 100644 --- a/src/validation.h +++ b/src/validation.h @@ -283,7 +283,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 8eb9d43b32..da6feb06c6 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/dip3-deterministicmns.py b/test/functional/dip3-deterministicmns.py index 03da7c3f66..2784976a04 100755 --- a/test/functional/dip3-deterministicmns.py +++ b/test/functional/dip3-deterministicmns.py @@ -392,7 +392,11 @@ class DIP3Test(BitcoinTestFramework): 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_block_reward_reallocation.py b/test/functional/feature_block_reward_reallocation.py new file mode 100755 index 0000000000..6d84f75078 --- /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/llmq-is-cl-conflicts.py b/test/functional/llmq-is-cl-conflicts.py index 6c64a26b56..a5e8829d37 100755 --- a/test/functional/llmq-is-cl-conflicts.py +++ b/test/functional/llmq-is-cl-conflicts.py @@ -9,7 +9,7 @@ from test_framework import mininode 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 sync_blocks, sync_mempools, p2p_port, assert_raises_rpc_error +from test_framework.util import sync_blocks, sync_mempools, p2p_port, assert_raises_rpc_error, get_bip9_status ''' llmq-is-cl-conflicts.py @@ -241,7 +241,11 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework): 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 67a6328241..b4ae17e8eb 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 f24a6aa32c..14553e1c0e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -77,6 +77,7 @@ BASE_SCRIPTS= [ 'llmq-is-retroactive.py', # NOTE: needs dash_hash to pass 'llmq-dkgerrors.py', # NOTE: needs dash_hash to pass 'dip4-coinbasemerkleroots.py', # NOTE: needs dash_hash to pass + 'feature_block_reward_reallocation.py', # vv Tests less than 60s vv 'sendheaders.py', # NOTE: needs dash_hash to pass 'zapwallettxes.py', From eb163c53a59e93ecc015de3c0f01b266fafb08e5 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sat, 12 Sep 2020 17:33:12 +0300 Subject: [PATCH 2/3] Implement dynamic activation thresholds (#3692) * Implement dynamic activation thresholds * fix * Revert unrelated changes * Clarify switching to/staying in LOCKED_IN state * Fix signal function to work correctly with num_blocks=0 * Add simplified threshold calculation and use it in tests * Check that thresholds are decreasing, reach the min level and stay there * Drop `;` --- src/chainparams.cpp | 56 +++++++---- src/chainparams.h | 4 +- src/consensus/params.h | 10 +- src/init.cpp | 23 +++-- src/test/versionbits_tests.cpp | 2 +- src/validation.cpp | 4 +- src/versionbits.cpp | 42 ++++++-- src/versionbits.h | 6 +- .../feature_block_reward_reallocation.py | 96 ++++++++++++++----- test/functional/test_runner.py | 2 +- 10 files changed, 172 insertions(+), 73 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 9620c85d40..ff13f4bcf0 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -82,15 +82,21 @@ static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits } -void CChainParams::UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold) +void CChainParams::UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff) { consensus.vDeployments[d].nStartTime = nStartTime; consensus.vDeployments[d].nTimeout = nTimeout; if (nWindowSize != -1) { consensus.vDeployments[d].nWindowSize = nWindowSize; } - if (nThreshold != -1) { - consensus.vDeployments[d].nThreshold = nThreshold; + if (nThresholdStart != -1) { + consensus.vDeployments[d].nThresholdStart = nThresholdStart; + } + if (nThresholdMin != -1) { + consensus.vDeployments[d].nThresholdMin = nThresholdMin; + } + if (nFalloffCoeff != -1) { + consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff; } } @@ -320,35 +326,37 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1508025600; // Oct 15th, 2017 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nTimeout = 1539561600; // Oct 15th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThreshold = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThresholdStart = 3226; // 80% of 4032 // Deployment of BIP147 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nStartTime = 1524477600; // Apr 23th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nTimeout = 1556013600; // Apr 23th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThreshold = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThresholdStart = 3226; // 80% of 4032 // Deployment of DIP0003 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].bit = 3; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nStartTime = 1546300800; // Jan 1st, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nTimeout = 1577836800; // Jan 1st, 2020 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThreshold = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThresholdStart = 3226; // 80% of 4032 // Deployment of DIP0008 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].bit = 4; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nStartTime = 1557878400; // May 15th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nTimeout = 1589500800; // May 15th, 2020 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThresholdStart = 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 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdStart = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdMin = 2420; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000027b81f49774e9f7fc93f"); // 1215000 @@ -514,35 +522,37 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1544655600; // Dec 13th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nTimeout = 1576191600; // Dec 13th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThresholdStart = 50; // 50% of 100 // Deployment of BIP147 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nStartTime = 1544655600; // Dec 13th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nTimeout = 1576191600; // Dec 13th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThresholdStart = 50; // 50% of 100 // Deployment of DIP0003 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].bit = 3; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nStartTime = 1544655600; // Dec 13th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nTimeout = 1576191600; // Dec 13th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThresholdStart = 50; // 50% of 100 // Deployment of DIP0008 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].bit = 4; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nStartTime = 1553126400; // Mar 21st, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nTimeout = 1584748800; // Mar 21st, 2020 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThresholdStart = 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 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdStart = 80; // 80% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdMin = 60; // 60% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000000ac720e0b2ed13d"); // 260000 @@ -680,35 +690,37 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1505692800; // Sep 18th, 2017 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nTimeout = 1537228800; // Sep 18th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nThresholdStart = 50; // 50% of 100 // Deployment of BIP147 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].bit = 2; consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nStartTime = 1517792400; // Feb 5th, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nTimeout = 1549328400; // Feb 5th, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThresholdStart = 50; // 50% of 100 // Deployment of DIP0003 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].bit = 3; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nStartTime = 1535752800; // Sep 1st, 2018 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nTimeout = 1567288800; // Sep 1st, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThresholdStart = 50; // 50% of 100 // Deployment of DIP0008 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].bit = 4; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nStartTime = 1553126400; // Mar 21st, 2019 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nTimeout = 1900281600; // Mar 21st, 2030 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThreshold = 50; // 50% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0008].nThresholdStart = 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 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdStart = 80; // 80% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdMin = 60; // 60% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -852,7 +864,9 @@ public: 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% + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdStart = 400; // 80% + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nThresholdMin = 300; // 60% + consensus.vDeployments[Consensus::DEPLOYMENT_REALLOC].nFalloffCoeff = 5; // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -955,9 +969,9 @@ void SelectParams(const std::string& network) globalChainParams = CreateChainParams(network); } -void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold) +void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff) { - globalChainParams->UpdateVersionBitsParameters(d, nStartTime, nTimeout, nWindowSize, nThreshold); + globalChainParams->UpdateVersionBitsParameters(d, nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff); } void UpdateDIP3Parameters(int nActivationHeight, int nEnforcementHeight) diff --git a/src/chainparams.h b/src/chainparams.h index 3a216cb0fa..c39da1c85a 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -81,7 +81,7 @@ public: const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } const ChainTxData& TxData() const { return chainTxData; } - void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold); + void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff); void UpdateDIP3Parameters(int nActivationHeight, int nEnforcementHeight); void UpdateBudgetParameters(int nMasternodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock); void UpdateSubsidyAndDiffParams(int nMinimumDifficultyBlocks, int nHighSubsidyBlocks, int nHighSubsidyFactor); @@ -151,7 +151,7 @@ void SelectParams(const std::string& chain); /** * Allows modifying the Version Bits regtest parameters. */ -void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold); +void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff); /** * Allows modifying the DIP3 activation and enforcement height diff --git a/src/consensus/params.h b/src/consensus/params.h index 62c7456a84..fb0d338a97 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -37,8 +37,12 @@ struct BIP9Deployment { int64_t nTimeout; /** The number of past blocks (including the block under consideration) to be taken into account for locking in a fork. */ int64_t nWindowSize{0}; - /** A number of blocks, in the range of 1..nWindowSize, which must signal for a fork in order to lock it in. */ - int64_t nThreshold{0}; + /** A starting number of blocks, in the range of 1..nWindowSize, which must signal for a fork in order to lock it in. */ + int64_t nThresholdStart{0}; + /** A minimum number of blocks, in the range of 1..nWindowSize, which must signal for a fork in order to lock it in. */ + int64_t nThresholdMin{0}; + /** A coefficient which adjusts the speed a required number of signaling blocks is decreasing from nThresholdStart to nThresholdMin at with each period. */ + int64_t nFalloffCoeff{0}; }; enum LLMQType : uint8_t @@ -158,7 +162,7 @@ struct Params { /** * Minimum blocks including miner confirmation of the total of nMinerConfirmationWindow blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. - * Default BIP9Deployment::nThreshold value for deployments where it's not specified and for unknown deployments. + * Default BIP9Deployment::nThresholdStart value for deployments where it's not specified and for unknown deployments. * Examples: 1916 for 95%, 1512 for testchains. */ uint32_t nRuleChangeActivationThreshold; diff --git a/src/init.cpp b/src/init.cpp index d3772e7c28..5d0cb21daa 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1351,10 +1351,10 @@ bool AppInitParameterInteraction() for (const std::string& strDeployment : gArgs.GetArgs("-vbparams")) { std::vector vDeploymentParams; boost::split(vDeploymentParams, strDeployment, boost::is_any_of(":")); - if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5) { - return InitError("Version bits parameters malformed, expecting deployment:start:end or deployment:start:end:window:threshold"); + if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5 && vDeploymentParams.size() != 7) { + return InitError("Version bits parameters malformed, expecting deployment:start:end or deployment:start:end:window:threshold or deployment:start:end:window:thresholdstart:thresholdmin:falloffcoeff"); } - int64_t nStartTime, nTimeout, nWindowSize = -1, nThreshold = -1; + int64_t nStartTime, nTimeout, nWindowSize = -1, nThresholdStart = -1, nThresholdMin = -1, nFalloffCoeff = -1; if (!ParseInt64(vDeploymentParams[1], &nStartTime)) { return InitError(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1])); } @@ -1365,17 +1365,26 @@ bool AppInitParameterInteraction() if (!ParseInt64(vDeploymentParams[3], &nWindowSize)) { return InitError(strprintf("Invalid nWindowSize (%s)", vDeploymentParams[3])); } - if (!ParseInt64(vDeploymentParams[4], &nThreshold)) { - return InitError(strprintf("Invalid nThreshold (%s)", vDeploymentParams[4])); + if (!ParseInt64(vDeploymentParams[4], &nThresholdStart)) { + return InitError(strprintf("Invalid nThresholdStart (%s)", vDeploymentParams[4])); + } + } + if (vDeploymentParams.size() == 7) { + if (!ParseInt64(vDeploymentParams[5], &nThresholdMin)) { + return InitError(strprintf("Invalid nThresholdMin (%s)", vDeploymentParams[5])); + } + if (!ParseInt64(vDeploymentParams[6], &nFalloffCoeff)) { + return InitError(strprintf("Invalid nFalloffCoeff (%s)", vDeploymentParams[6])); } } bool found = false; for (int j=0; j<(int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { if (vDeploymentParams[0].compare(VersionBitsDeploymentInfo[j].name) == 0) { - UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThreshold); + UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff); found = true; - LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, threshold=%ld\n", vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThreshold); + LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, thresholdstart=%ld, thresholdmin=%ld, falloffcoeff=%ld\n", + vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff); break; } } diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 0de4b81ebe..e287d67b35 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -25,7 +25,7 @@ public: int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); } int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); } int Period(const Consensus::Params& params) const override { return 1000; } - int Threshold(const Consensus::Params& params) const override { return 900; } + int Threshold(const Consensus::Params& params, int nAttempt) const override { return 900; } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); } ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); } diff --git a/src/validation.cpp b/src/validation.cpp index 2bf0abf426..442dea885f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1915,7 +1915,7 @@ public: int64_t BeginTime(const Consensus::Params& params) const override { return 0; } int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits::max(); } int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } - int Threshold(const Consensus::Params& params) const override { return params.nRuleChangeActivationThreshold; } + int Threshold(const Consensus::Params& params, int nAttempt) const override { return params.nRuleChangeActivationThreshold; } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { @@ -4895,7 +4895,7 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos) { LOCK(cs_main); - return VersionBitsStatistics(chainActive.Tip(), params, pos); + return VersionBitsStatistics(chainActive.Tip(), params, pos, versionbitscache); } int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos) diff --git a/src/versionbits.cpp b/src/versionbits.cpp index da6feb06c6..7b8b536f99 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -46,7 +46,6 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const { int nPeriod = Period(params); - int nThreshold = Threshold(params); int64_t nTimeStart = BeginTime(params); int64_t nTimeTimeout = EndTime(params); @@ -76,6 +75,13 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* assert(cache.count(pindexPrev)); ThresholdState state = cache[pindexPrev]; + int nStartHeight{std::numeric_limits::max()}; + for (const auto& pair : cache) { + if (pair.second == ThresholdState::STARTED && nStartHeight > pair.first->nHeight + 1) { + nStartHeight = pair.first->nHeight + 1; + } + } + // Now walk forward and compute the state of descendants of pindexPrev while (!vToCompute.empty()) { ThresholdState stateNext = state; @@ -88,6 +94,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* stateNext = THRESHOLD_FAILED; } else if (pindexPrev->GetMedianTimePast() >= nTimeStart) { stateNext = THRESHOLD_STARTED; + nStartHeight = pindexPrev->nHeight + 1; } break; } @@ -105,7 +112,9 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* } pindexCount = pindexCount->pprev; } - if (count >= nThreshold) { + assert(nStartHeight > 0 && nStartHeight < std::numeric_limits::max()); + int nAttempt = (pindexCount->nHeight + 1 - nStartHeight) / nPeriod; + if (count >= Threshold(params, nAttempt)) { stateNext = THRESHOLD_LOCKED_IN; } break; @@ -128,12 +137,12 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* } // return the numerical statistics of blocks signalling the specified BIP9 condition in this current period -BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const +BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, ThresholdConditionCache& cache) const { BIP9Stats stats = {}; stats.period = Period(params); - stats.threshold = Threshold(params); + stats.threshold = Threshold(params, 0); if (pindex == nullptr) return stats; @@ -142,6 +151,15 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period)); stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight; + // Re-calculate current threshold + int nAttempt{0}; + const ThresholdState state = GetStateFor(pindexEndOfPrevPeriod, params, cache); + if (state == ThresholdState::STARTED) { + int nStartHeight = GetStateSinceHeightFor(pindexEndOfPrevPeriod, params, cache); + nAttempt = (pindexEndOfPrevPeriod->nHeight + 1 - nStartHeight)/stats.period; + } + stats.threshold = Threshold(params, nAttempt); + // Count from current block to beginning of period int count = 0; const CBlockIndex* currentIndex = pindex; @@ -200,7 +218,17 @@ protected: int64_t BeginTime(const Consensus::Params& params) const override { return params.vDeployments[id].nStartTime; } int64_t EndTime(const Consensus::Params& params) const override { return params.vDeployments[id].nTimeout; } int Period(const Consensus::Params& params) const override { return params.vDeployments[id].nWindowSize ? params.vDeployments[id].nWindowSize : params.nMinerConfirmationWindow; } - int Threshold(const Consensus::Params& params) const override { return params.vDeployments[id].nThreshold ? params.vDeployments[id].nThreshold : params.nRuleChangeActivationThreshold; } + int Threshold(const Consensus::Params& params, int nAttempt) const override + { + if (params.vDeployments[id].nThresholdStart == 0) { + return params.nRuleChangeActivationThreshold; + } + if (params.vDeployments[id].nThresholdMin == 0 || params.vDeployments[id].nFalloffCoeff == 0) { + return params.vDeployments[id].nThresholdStart; + } + int64_t nThresholdCalc = params.vDeployments[id].nThresholdStart - nAttempt * nAttempt * Period(params) / 100 / params.vDeployments[id].nFalloffCoeff; + return std::max(params.vDeployments[id].nThresholdMin, nThresholdCalc); + } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { @@ -219,9 +247,9 @@ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus:: return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]); } -BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) +BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) { - return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params); + return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params, cache.caches[pos]); } int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) diff --git a/src/versionbits.h b/src/versionbits.h index 8e26f1fea1..467764ced4 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -58,10 +58,10 @@ protected: virtual int64_t BeginTime(const Consensus::Params& params) const =0; virtual int64_t EndTime(const Consensus::Params& params) const =0; virtual int Period(const Consensus::Params& params) const =0; - virtual int Threshold(const Consensus::Params& params) const =0; + virtual int Threshold(const Consensus::Params& params, int nAttempt) const =0; public: - BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const; + BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params, ThresholdConditionCache& cache) const; // Note that the functions below take a pindexPrev as input: they compute information for block B based on its parent. ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; @@ -75,7 +75,7 @@ struct VersionBitsCache }; ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); -BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); +BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos); diff --git a/test/functional/feature_block_reward_reallocation.py b/test/functional/feature_block_reward_reallocation.py index 6d84f75078..5c3ad6c8cb 100755 --- a/test/functional/feature_block_reward_reallocation.py +++ b/test/functional/feature_block_reward_reallocation.py @@ -58,21 +58,27 @@ class BlockRewardReallocationTest(DashTestFramework): 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) + if num_blocks > 0: + 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() - self.nodes[0].generate((num_blocks - 1) % 10) + assert_equal(get_bip9_status(self.nodes[0], 'realloc')['status'], 'started') + self.nodes[0].generate(1) 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 threshold(self, attempt): + threshold_calc = 400 - attempt * attempt + if threshold_calc < 300: + return 300 + return threshold_calc + def run_test(self): self.log.info("Wait for DIP3 to activate") while get_bip9_status(self.nodes[0], 'dip0003')['status'] != 'active': @@ -101,40 +107,56 @@ class BlockRewardReallocationTest(DashTestFramework): 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) + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], self.threshold(0)) 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") + self.log.info("Still STARTED but new threshold should be lower at height = 999") + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 999) + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], self.threshold(1)) + + self.signal(398, False) # 1 block short again + + self.log.info("Still STARTED but new threshold should be even lower at height = 1499") + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 1499) + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], self.threshold(2)) + pre_locked_in_blockhash = bi['bestblockhash'] + + self.signal(396, True) # just enough to lock in + self.log.info("Advanced to LOCKED_IN at height = 1999") + 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() + + self.log.info("Still LOCKED_IN at height = 2498") bi = self.nodes[0].getblockchaininfo() - assert_equal(bi['blocks'], 1998) + assert_equal(bi['blocks'], 2498) assert_equal(bi['bip9_softforks']['realloc']['status'], 'locked_in') - self.log.info("Advance from LOCKED_IN to ACTIVE at height = 1999") + self.log.info("Advance from LOCKED_IN to ACTIVE at height = 2499") self.nodes[0].generate(1) # activation bi = self.nodes[0].getblockchaininfo() - assert_equal(bi['blocks'], 1999) + assert_equal(bi['blocks'], 2499) assert_equal(bi['bip9_softforks']['realloc']['status'], 'active') - assert_equal(bi['bip9_softforks']['realloc']['since'], 2000) + assert_equal(bi['bip9_softforks']['realloc']['since'], 2500) 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)) + assert_equal(bt['height'], 2500) + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2500)) 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 + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2500)) + assert_equal(bt['coinbasevalue'], 13748571607) + assert_equal(bt['masternode'][0]['amount'], 6874285801) # 0.4999999998 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 @@ -143,11 +165,11 @@ class BlockRewardReallocationTest(DashTestFramework): 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['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2500)) 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 + assert_equal(bt['coinbasevalue'], 10221599170) + assert_equal(bt['masternode'][0]['amount'], 6132959502) # 0.6 self.log.info("Reward split should stay ~60/40 after reallocation is done") for period in range(10): # check 10 next superblocks @@ -155,9 +177,31 @@ class BlockRewardReallocationTest(DashTestFramework): 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 + assert_equal(bt['masternode'][0]['amount'], get_masternode_payment(bt['height'], bt['coinbasevalue'], 2500)) + assert_equal(bt['coinbasevalue'], 9491484944) + assert_equal(bt['masternode'][0]['amount'], 5694890966) # 0.6 + + self.log.info("Rollback the chain back to the STARTED state") + self.mocktime = self.nodes[0].getblock(pre_locked_in_blockhash, 1)['time'] + for node in self.nodes: + node.invalidateblock(pre_locked_in_blockhash) + self.sync_all() + # create and send non-signalling block + test_block = self.create_test_block() + self.nodes[0].p2p.send_blocks_and_test([test_block], self.nodes[0], timeout=5) + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 1499) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'started') + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], self.threshold(2)) + + self.log.info("Check thresholds reach min level and stay there") + for i in range(8): # 7 to reach min level and 1 more to check it doesn't go lower than that + self.signal(0, False) # no need to signal + bi = self.nodes[0].getblockchaininfo() + assert_equal(bi['blocks'], 1999 + i * 500) + assert_equal(bi['bip9_softforks']['realloc']['status'], 'started') + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], self.threshold(i + 3)) + assert_equal(bi['bip9_softforks']['realloc']['statistics']['threshold'], 300) if __name__ == '__main__': BlockRewardReallocationTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 14553e1c0e..6decac6153 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -55,6 +55,7 @@ BASE_SCRIPTS= [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel 'dip3-deterministicmns.py', # NOTE: needs dash_hash to pass + 'feature_block_reward_reallocation.py', 'wallet-hd.py', 'walletbackup.py', # vv Tests less than 5m vv @@ -77,7 +78,6 @@ BASE_SCRIPTS= [ 'llmq-is-retroactive.py', # NOTE: needs dash_hash to pass 'llmq-dkgerrors.py', # NOTE: needs dash_hash to pass 'dip4-coinbasemerkleroots.py', # NOTE: needs dash_hash to pass - 'feature_block_reward_reallocation.py', # vv Tests less than 60s vv 'sendheaders.py', # NOTE: needs dash_hash to pass 'zapwallettxes.py', From 82ad0025417e4d0269dfecf37dd18eeb0938c710 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 12 Sep 2020 13:26:51 -0400 Subject: [PATCH 3/3] fix reallocation backport Signed-off-by: pasta --- src/masternode/masternode-payments.cpp | 2 +- src/versionbits.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/masternode/masternode-payments.cpp b/src/masternode/masternode-payments.cpp index e99cc9cc6f..bb30f73585 100644 --- a/src/masternode/masternode-payments.cpp +++ b/src/masternode/masternode-payments.cpp @@ -327,7 +327,7 @@ bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, pindex = chainActive[nBlockHeight - 1]; const Consensus::Params& consensusParams = Params().GetConsensus(); - if (VersionBitsState(pindex, consensusParams, Consensus::DEPLOYMENT_REALLOC, versionbitscache) == ThresholdState::ACTIVE) { + if (VersionBitsState(pindex, consensusParams, Consensus::DEPLOYMENT_REALLOC, versionbitscache) == THRESHOLD_ACTIVE) { nReallocActivationHeight = VersionBitsStateSinceHeight(pindex, consensusParams, Consensus::DEPLOYMENT_REALLOC, versionbitscache); } } diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 7b8b536f99..c3c9324839 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -77,7 +77,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* int nStartHeight{std::numeric_limits::max()}; for (const auto& pair : cache) { - if (pair.second == ThresholdState::STARTED && nStartHeight > pair.first->nHeight + 1) { + if (pair.second == THRESHOLD_STARTED && nStartHeight > pair.first->nHeight + 1) { nStartHeight = pair.first->nHeight + 1; } } @@ -154,7 +154,7 @@ BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockI // Re-calculate current threshold int nAttempt{0}; const ThresholdState state = GetStateFor(pindexEndOfPrevPeriod, params, cache); - if (state == ThresholdState::STARTED) { + if (state == THRESHOLD_STARTED) { int nStartHeight = GetStateSinceHeightFor(pindexEndOfPrevPeriod, params, cache); nAttempt = (pindexEndOfPrevPeriod->nHeight + 1 - nStartHeight)/stats.period; }