From fc146095d20452686efe1944b143452bec394343 Mon Sep 17 00:00:00 2001 From: mruddy Date: Fri, 6 May 2016 22:08:39 +0000 Subject: [PATCH] RPC: augment getblockchaininfo bip9_softforks data --- qa/rpc-tests/bip9-softforks.py | 9 +++ src/main.cpp | 6 ++ src/main.h | 3 +- src/rpc/blockchain.cpp | 4 +- src/test/versionbits_tests.cpp | 104 ++++++++++++++++++++------------- src/versionbits.cpp | 35 +++++++++++ src/versionbits.h | 4 +- 7 files changed, 121 insertions(+), 44 deletions(-) diff --git a/qa/rpc-tests/bip9-softforks.py b/qa/rpc-tests/bip9-softforks.py index be6ddde11..c42ed44c2 100755 --- a/qa/rpc-tests/bip9-softforks.py +++ b/qa/rpc-tests/bip9-softforks.py @@ -81,6 +81,9 @@ class BIP9SoftForksTest(ComparisonTestFramework): return info['bip9_softforks'][key] def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignature, bitno): + assert_equal(self.get_bip9_status(bipName)['status'], 'defined') + assert_equal(self.get_bip9_status(bipName)['since'], 0) + # generate some coins for later self.coinbase_blocks = self.nodes[0].generate(2) self.height = 3 # height of the next block to build @@ -89,6 +92,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): self.last_block_time = int(time.time()) assert_equal(self.get_bip9_status(bipName)['status'], 'defined') + assert_equal(self.get_bip9_status(bipName)['since'], 0) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['vbavailable']) @@ -101,6 +105,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): yield TestInstance(test_blocks, sync_every_block=False) assert_equal(self.get_bip9_status(bipName)['status'], 'started') + assert_equal(self.get_bip9_status(bipName)['since'], 144) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName not in tmpl['rules']) assert_equal(tmpl['vbavailable'][bipName], bitno) @@ -117,6 +122,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): yield TestInstance(test_blocks, sync_every_block=False) assert_equal(self.get_bip9_status(bipName)['status'], 'started') + assert_equal(self.get_bip9_status(bipName)['since'], 144) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName not in tmpl['rules']) assert_equal(tmpl['vbavailable'][bipName], bitno) @@ -133,6 +139,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): yield TestInstance(test_blocks, sync_every_block=False) assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in') + assert_equal(self.get_bip9_status(bipName)['since'], 432) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName not in tmpl['rules']) @@ -142,6 +149,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): yield TestInstance(test_blocks, sync_every_block=False) assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in') + assert_equal(self.get_bip9_status(bipName)['since'], 432) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName not in tmpl['rules']) @@ -167,6 +175,7 @@ class BIP9SoftForksTest(ComparisonTestFramework): yield TestInstance([[block, True]]) assert_equal(self.get_bip9_status(bipName)['status'], 'active') + assert_equal(self.get_bip9_status(bipName)['since'], 576) tmpl = self.nodes[0].getblocktemplate({}) assert(bipName in tmpl['rules']) assert(bipName not in tmpl['vbavailable']) diff --git a/src/main.cpp b/src/main.cpp index 1777717cd..50158b468 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6918,6 +6918,12 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache); } +int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos) +{ + LOCK(cs_main); + return VersionBitsStateSinceHeight(chainActive.Tip(), params, pos, versionbitscache); +} + class CMainCleanup { public: diff --git a/src/main.h b/src/main.h index e91f6e46f..3eab9b89d 100644 --- a/src/main.h +++ b/src/main.h @@ -297,7 +297,8 @@ std::string FormatStateMessage(const CValidationState &state); /** Get the BIP9 state for a given deployment at the current tip. */ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos); - +/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ +int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos); /** * Count ECDSA signature operations the old-fashioned (pre-0.6) way diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 5414ac9ff..141ca87b6 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1009,6 +1009,7 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse } rv.push_back(Pair("startTime", consensusParams.vDeployments[id].nStartTime)); rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout)); + rv.push_back(Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id))); return rv; } @@ -1053,7 +1054,8 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp) " \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n" " \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n" " \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n" - " \"timeout\": xx (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n" + " \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n" + " \"since\": xx (numeric) height of the first block to which the status applies\n" " }\n" " }\n" "}\n" diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index ffc0ff6f8..784e79699 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2015 The Bitcoin Core developers +// Copyright (c) 2014-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -30,6 +30,7 @@ public: bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const { return (pindex->nVersion & 0x100); } ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); } + int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); } }; #define CHECKERS 6 @@ -78,6 +79,16 @@ public: return *this; } + VersionBitsTester& TestStateSinceHeight(int height) { + for (int i = 0; i < CHECKERS; i++) { + if ((insecure_rand() & ((1 << i) - 1)) == 0) { + BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(vpblock.empty() ? NULL : vpblock.back()) == height, strprintf("Test %i for StateSinceHeight", num)); + } + } + num++; + return *this; + } + VersionBitsTester& TestDefined() { for (int i = 0; i < CHECKERS; i++) { if ((insecure_rand() & ((1 << i) - 1)) == 0) { @@ -137,53 +148,64 @@ BOOST_AUTO_TEST_CASE(versionbits_test) { for (int i = 0; i < 64; i++) { // DEFINED -> FAILED - VersionBitsTester().TestDefined() - .Mine(1, TestTime(1), 0x100).TestDefined() - .Mine(11, TestTime(11), 0x100).TestDefined() - .Mine(989, TestTime(989), 0x100).TestDefined() - .Mine(999, TestTime(20000), 0x100).TestDefined() - .Mine(1000, TestTime(20000), 0x100).TestFailed() - .Mine(1999, TestTime(30001), 0x100).TestFailed() - .Mine(2000, TestTime(30002), 0x100).TestFailed() - .Mine(2001, TestTime(30003), 0x100).TestFailed() - .Mine(2999, TestTime(30004), 0x100).TestFailed() - .Mine(3000, TestTime(30005), 0x100).TestFailed() + VersionBitsTester().TestDefined().TestStateSinceHeight(0) + .Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0) + .Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0) + .Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0) + .Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0) + .Mine(1000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(1000) + .Mine(1999, TestTime(30001), 0x100).TestFailed().TestStateSinceHeight(1000) + .Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(1000) + .Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(1000) + .Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(1000) + .Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(1000) // DEFINED -> STARTED -> FAILED - .Reset().TestDefined() - .Mine(1, TestTime(1), 0).TestDefined() - .Mine(1000, TestTime(10000) - 1, 0x100).TestDefined() // One second more and it would be defined - .Mine(2000, TestTime(10000), 0x100).TestStarted() // So that's what happens the next period - .Mine(2051, TestTime(10010), 0).TestStarted() // 51 old blocks - .Mine(2950, TestTime(10020), 0x100).TestStarted() // 899 new blocks - .Mine(3000, TestTime(20000), 0).TestFailed() // 50 old blocks (so 899 out of the past 1000) - .Mine(4000, TestTime(20010), 0x100).TestFailed() + .Reset().TestDefined().TestStateSinceHeight(0) + .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0) + .Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined + .Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period + .Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) // 51 old blocks + .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 899 new blocks + .Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000) + .Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000) // DEFINED -> STARTED -> FAILED while threshold reached - .Reset().TestDefined() - .Mine(1, TestTime(1), 0).TestDefined() - .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined - .Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period - .Mine(2999, TestTime(30000), 0x100).TestStarted() // 999 new blocks - .Mine(3000, TestTime(30000), 0x100).TestFailed() // 1 new block (so 1000 out of the past 1000 are new) - .Mine(3999, TestTime(30001), 0).TestFailed() - .Mine(4000, TestTime(30002), 0).TestFailed() - .Mine(14333, TestTime(30003), 0).TestFailed() - .Mine(24000, TestTime(40000), 0).TestFailed() + .Reset().TestDefined().TestStateSinceHeight(0) + .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0) + .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined + .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period + .Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks + .Mine(3000, TestTime(30000), 0x100).TestFailed().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new) + .Mine(3999, TestTime(30001), 0).TestFailed().TestStateSinceHeight(3000) + .Mine(4000, TestTime(30002), 0).TestFailed().TestStateSinceHeight(3000) + .Mine(14333, TestTime(30003), 0).TestFailed().TestStateSinceHeight(3000) + .Mine(24000, TestTime(40000), 0).TestFailed().TestStateSinceHeight(3000) // DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE .Reset().TestDefined() - .Mine(1, TestTime(1), 0).TestDefined() - .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined - .Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period - .Mine(2050, TestTime(10010), 0x200).TestStarted() // 50 old blocks - .Mine(2950, TestTime(10020), 0x100).TestStarted() // 900 new blocks - .Mine(2999, TestTime(19999), 0x200).TestStarted() // 49 old blocks - .Mine(3000, TestTime(29999), 0x200).TestLockedIn() // 1 old block (so 900 out of the past 1000) - .Mine(3999, TestTime(30001), 0).TestLockedIn() - .Mine(4000, TestTime(30002), 0).TestActive() - .Mine(14333, TestTime(30003), 0).TestActive() - .Mine(24000, TestTime(40000), 0).TestActive(); + .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0) + .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined + .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period + .Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) // 50 old blocks + .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 900 new blocks + .Mine(2999, TestTime(19999), 0x200).TestStarted().TestStateSinceHeight(2000) // 49 old blocks + .Mine(3000, TestTime(29999), 0x200).TestLockedIn().TestStateSinceHeight(3000) // 1 old block (so 900 out of the past 1000) + .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000) + .Mine(4000, TestTime(30002), 0).TestActive().TestStateSinceHeight(4000) + .Mine(14333, TestTime(30003), 0).TestActive().TestStateSinceHeight(4000) + .Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000) + + // DEFINED multiple periods -> STARTED multiple periods -> FAILED + .Reset().TestDefined().TestStateSinceHeight(0) + .Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0) + .Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0) + .Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0) + .Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000) + .Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000) + .Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000) + .Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000) + .Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000); } // Sanity checks of version bit deployments diff --git a/src/versionbits.cpp b/src/versionbits.cpp index bf32ae662..d73f34051 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -105,6 +105,36 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* return state; } +int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const +{ + const ThresholdState initialState = GetStateFor(pindexPrev, params, cache); + + // BIP 9 about state DEFINED: "The genesis block is by definition in this state for each deployment." + if (initialState == THRESHOLD_DEFINED) { + return 0; + } + + const int nPeriod = Period(params); + + // A block's state is always the same as that of the first of its period, so it is computed based on a pindexPrev whose height equals a multiple of nPeriod - 1. + // To ease understanding of the following height calculation, it helps to remember that + // right now pindexPrev points to the block prior to the block that we are computing for, thus: + // if we are computing for the last block of a period, then pindexPrev points to the second to last block of the period, and + // if we are computing for the first block of a period, then pindexPrev points to the last block of the previous period. + // The parent of the genesis block is represented by NULL. + pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); + + const CBlockIndex* previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); + + while (previousPeriodParent != NULL && GetStateFor(previousPeriodParent, params, cache) == initialState) { + pindexPrev = previousPeriodParent; + previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); + } + + // Adjust the result because right now we point to the parent block. + return pindexPrev->nHeight + 1; +} + namespace { /** @@ -137,6 +167,11 @@ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus:: return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]); } +int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) +{ + return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]); +} + uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos) { return VersionBitsConditionChecker(pos).Mask(params); diff --git a/src/versionbits.h b/src/versionbits.h index ede2dcdda..7a929266a 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -51,8 +51,9 @@ protected: virtual int Threshold(const Consensus::Params& params) const =0; public: - // Note that the function below takes a pindexPrev as input: they compute information for block B based on its parent. + // 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; }; struct VersionBitsCache @@ -63,6 +64,7 @@ struct VersionBitsCache }; ThresholdState VersionBitsState(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); #endif