diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 94b64e805f..67f35967c1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -498,9 +498,11 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].min_activation_height = 0; // No activation delay consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; - consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = gArgs.GetArg("-con_taproot_signal_start", Consensus::BIP9Deployment::ALWAYS_ACTIVE); consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nPeriod = 128; // test ability to change from default + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nThreshold = 128; consensus.nMinimumChainWork = uint256{}; consensus.defaultAssumeValid = uint256{}; @@ -1016,6 +1018,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1554500; // November 1, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nPeriod = 10080; // one week... + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nThreshold = 10080; // ...of 100% signalling + // Activated from block 1,000,000. consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].bit = 25; // Allow blocksigners to delay activation. @@ -1285,6 +1293,10 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].min_activation_height = 0; // No activation delay } + if (args.IsArgSet("-con_taproot_signal_start")) { + consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = gArgs.GetArg("-con_taproot_signal_start", 0); + } + // END ELEMENTS fields } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index afb0b19cae..861e4ec801 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -59,6 +59,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-con_dyna_deploy_signal", "Whether to signal for the Dynamic Federations deployment (default: false).", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-dynamic_epoch_length", "Per-chain parameter that sets how many blocks dynamic federation voting and enforcement are in effect for.", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-total_valid_epochs", "Per-chain parameter that sets how long a particular fedpegscript is in effect for.", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); + argsman.AddArg("-con_taproot_signal_start", "Whether, and at what blockheight, to start signalling for Taproot activation (default: false) (regtest, Liquid testnet, or custom only).", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); // END ELEMENTS // } diff --git a/src/consensus/params.h b/src/consensus/params.h index a771bce1b8..b5b7cfc1d0 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -7,6 +7,7 @@ #define BITCOIN_CONSENSUS_PARAMS_H #include +#include #include #include @@ -56,6 +57,11 @@ struct BIP9Deployment { */ int min_activation_height{0}; + // ELEMENTS: allow overriding the signalling period length rather than using `nMinerConfirmationWindow` + std::optional nPeriod{std::nullopt}; + // ELEMENTS: allow overriding the activation threshold rather than using `nRuleChangeActivationThreshold` + std::optional nThreshold{std::nullopt}; + /** Constant for nTimeout very far in the future. */ static constexpr int64_t NO_TIMEOUT = std::numeric_limits::max(); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index cb89f83e65..c7a8ef9500 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -186,8 +186,20 @@ 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 MinActivationHeight(const Consensus::Params& params) const override { return params.vDeployments[id].min_activation_height; } - int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } - int Threshold(const Consensus::Params& params) const override { return params.nRuleChangeActivationThreshold; } + int Period(const Consensus::Params& params) const override { + if (params.vDeployments[id].nPeriod) { + return *params.vDeployments[id].nPeriod; + } else { + return params.nMinerConfirmationWindow; + } + } + int Threshold(const Consensus::Params& params) const override { + if (params.vDeployments[id].nThreshold) { + return *params.vDeployments[id].nThreshold; + } else { + return params.nRuleChangeActivationThreshold; + } + } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { diff --git a/test/functional/feature_elements_taproot_activation.py b/test/functional/feature_elements_taproot_activation.py new file mode 100755 index 0000000000..5a476381e2 --- /dev/null +++ b/test/functional/feature_elements_taproot_activation.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test Taproot soft fork activation for Elements + +Unlike Bitcoin, this (a) does not use Speedy Trial, and (b) does use +a pair of new versionbits features which allows Taproot to have its +own signalling period length (128 blocks on regtest) and activation +threshold (100%). + +The primary purpose of this test is to confirm that this configuration +works; the actual activation (e.g. how are Taproot-enabled transactions +treated) is covered by other tests and by manual testing. + +There is an undocumented option `con_taproot_signal_start` which sets +the block at which signalling starts; otherwise it is set to "always +on" which means that signalling will not occur. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +class TaprootActivationTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def test_activation(self, rpc, activation_height): + self.log.info("Testing activation at height %d" % activation_height) + activation_height = 128 * ((activation_height + 127) // 128) + + assert_equal(rpc.getblockcount(), 0) + + blocks = rpc.generatetoaddress(activation_height - 2, rpc.getnewaddress()) + assert_equal(rpc.getblockcount(), activation_height - 2) + + for n, block in enumerate(blocks): + decode = rpc.getblockheader(block) + if n < 143: + assert_equal (decode["versionHex"], "20000000") + elif n < 431: + # TESTDUMMY deployment: 144 blocks active, 144 blocks locked in + assert_equal (decode["versionHex"], "30000000") + else: + assert_equal (decode["versionHex"], "20000000") + + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "defined") + # The 1023rd block does not signal, but changes the signalling state + # to "started" from "defined" + blocks = rpc.generatetoaddress(1, rpc.getnewaddress()) + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "started") + assert_equal(rpc.getblockheader(blocks[0])["versionHex"], "20000000") + + blocks = rpc.generatetoaddress(127, rpc.getnewaddress()) + for n, block in enumerate(blocks): + decode = rpc.getblockheader(block) + assert_equal (decode["versionHex"], "20000004") + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "started") + + # Fail to signal on the 128th block. Since the threshold for Taproot is + # 100% this will prevent activation. Note that our period is 128, not + # 144 (the default), as we have overridden the period for Taproot. On + # the main Liquid chain it is overridden to be one week of signalling. + block = rpc.getnewblockhex() + block = block[:1] + "0" + block[2:] # turn off Taproot signal + rpc.submitblock(block) + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "started") + + # Run through another 128 blocks, without failing to signal + blocks = rpc.generatetoaddress(127, rpc.getnewaddress()) + for n, block in enumerate(blocks): + decode = rpc.getblockheader(block) + assert_equal (decode["versionHex"], "20000004") + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "started") + # The 128th block then switches from "started" to "locked_in" + blocks = rpc.generatetoaddress(1, rpc.getnewaddress()) + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "locked_in") + assert_equal(rpc.getblockheader(blocks[0])["versionHex"], "20000004") + + # Run through another 128 blocks, which will go from "locked in" to "active" regardless of signalling + blocks = rpc.generatetoaddress(127, rpc.getnewaddress()) + for n, block in enumerate(blocks): + decode = rpc.getblockheader(block) + assert_equal (decode["versionHex"], "20000004") + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "locked_in") + block = rpc.getnewblockhex() + block = block[:1] + "0" + block[2:] # turn off Taproot signal + rpc.submitblock(block) + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "active") + + # After the state is "active", signallng stops by default. + blocks = rpc.generatetoaddress(1, self.nodes[0].getnewaddress()) + assert_equal(rpc.getblockchaininfo()["softforks"]["taproot"]["bip9"]["status"], "active") + assert_equal(rpc.getblockheader(blocks[0])["versionHex"], "20000000") + + def run_test(self): + # Test that regtest nodes without -con_taproot_signal_start never signal + self.log.info("Testing node not configured to activate taproot") + blocks = self.nodes[0].generatetoaddress(2500, self.nodes[0].getnewaddress()) + assert_equal(self.nodes[0].getblockcount(), 2500) + for n, block in enumerate(blocks): + decode = self.nodes[0].getblockheader(block) + if n < 143: + assert_equal (decode["versionHex"], "20000000") + elif n < 431: + # TESTDUMMY deployment: 144 blocks active, 144 blocks locked in + assert_equal (decode["versionHex"], "30000000") + else: + assert_equal (decode["versionHex"], "20000000") + + # Test activation starting from height 1000 + self.restart_node(0, ["-con_taproot_signal_start=500"]) + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(1)) + self.test_activation(self.nodes[0], 500) + +if __name__ == '__main__': + TaprootActivationTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b9c96a97f4..9e4eaa58c3 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -223,6 +223,7 @@ BASE_SCRIPTS = [ 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', 'p2p_invalid_block.py', + 'feature_elements_taproot_activation.py', # ELEMENTS: needs to be fixed #'p2p_invalid_messages.py', 'p2p_invalid_tx.py',