RPC: augment getblockchaininfo bip9_softforks data

This commit is contained in:
mruddy 2016-05-06 22:08:39 +00:00
parent d736a6eb1f
commit fc146095d2
7 changed files with 121 additions and 44 deletions

View file

@ -81,6 +81,9 @@ class BIP9SoftForksTest(ComparisonTestFramework):
return info['bip9_softforks'][key] return info['bip9_softforks'][key]
def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignature, bitno): 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 # generate some coins for later
self.coinbase_blocks = self.nodes[0].generate(2) self.coinbase_blocks = self.nodes[0].generate(2)
self.height = 3 # height of the next block to build self.height = 3 # height of the next block to build
@ -89,6 +92,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
self.last_block_time = int(time.time()) self.last_block_time = int(time.time())
assert_equal(self.get_bip9_status(bipName)['status'], 'defined') assert_equal(self.get_bip9_status(bipName)['status'], 'defined')
assert_equal(self.get_bip9_status(bipName)['since'], 0)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
assert(bipName not in tmpl['vbavailable']) assert(bipName not in tmpl['vbavailable'])
@ -101,6 +105,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance(test_blocks, sync_every_block=False) yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'started') assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno) assert_equal(tmpl['vbavailable'][bipName], bitno)
@ -117,6 +122,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance(test_blocks, sync_every_block=False) yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'started') assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno) assert_equal(tmpl['vbavailable'][bipName], bitno)
@ -133,6 +139,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance(test_blocks, sync_every_block=False) 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)['status'], 'locked_in')
assert_equal(self.get_bip9_status(bipName)['since'], 432)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
@ -142,6 +149,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance(test_blocks, sync_every_block=False) 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)['status'], 'locked_in')
assert_equal(self.get_bip9_status(bipName)['since'], 432)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
@ -167,6 +175,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance([[block, True]]) yield TestInstance([[block, True]])
assert_equal(self.get_bip9_status(bipName)['status'], 'active') assert_equal(self.get_bip9_status(bipName)['status'], 'active')
assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName in tmpl['rules']) assert(bipName in tmpl['rules'])
assert(bipName not in tmpl['vbavailable']) assert(bipName not in tmpl['vbavailable'])

View file

@ -6918,6 +6918,12 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D
return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache); 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 class CMainCleanup
{ {
public: public:

View file

@ -297,7 +297,8 @@ std::string FormatStateMessage(const CValidationState &state);
/** Get the BIP9 state for a given deployment at the current tip. */ /** Get the BIP9 state for a given deployment at the current tip. */
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos); 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 * Count ECDSA signature operations the old-fashioned (pre-0.6) way

View file

@ -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("startTime", consensusParams.vDeployments[id].nStartTime));
rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout)); rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout));
rv.push_back(Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id)));
return rv; 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" " \"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" " \"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" " \"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" " }\n"
"}\n" "}\n"

View file

@ -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 // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // 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); } 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); } 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 #define CHECKERS 6
@ -78,6 +79,16 @@ public:
return *this; 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() { VersionBitsTester& TestDefined() {
for (int i = 0; i < CHECKERS; i++) { for (int i = 0; i < CHECKERS; i++) {
if ((insecure_rand() & ((1 << i) - 1)) == 0) { if ((insecure_rand() & ((1 << i) - 1)) == 0) {
@ -137,53 +148,64 @@ BOOST_AUTO_TEST_CASE(versionbits_test)
{ {
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
// DEFINED -> FAILED // DEFINED -> FAILED
VersionBitsTester().TestDefined() VersionBitsTester().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0x100).TestDefined() .Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(11, TestTime(11), 0x100).TestDefined() .Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(989, TestTime(989), 0x100).TestDefined() .Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(999, TestTime(20000), 0x100).TestDefined() .Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(20000), 0x100).TestFailed() .Mine(1000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(1999, TestTime(30001), 0x100).TestFailed() .Mine(1999, TestTime(30001), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2000, TestTime(30002), 0x100).TestFailed() .Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2001, TestTime(30003), 0x100).TestFailed() .Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(2999, TestTime(30004), 0x100).TestFailed() .Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(1000)
.Mine(3000, TestTime(30005), 0x100).TestFailed() .Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(1000)
// DEFINED -> STARTED -> FAILED // DEFINED -> STARTED -> FAILED
.Reset().TestDefined() .Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined() .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x100).TestDefined() // One second more and it would be defined .Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
.Mine(2000, TestTime(10000), 0x100).TestStarted() // So that's what happens the next period .Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
.Mine(2051, TestTime(10010), 0).TestStarted() // 51 old blocks .Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) // 51 old blocks
.Mine(2950, TestTime(10020), 0x100).TestStarted() // 899 new blocks .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 899 new blocks
.Mine(3000, TestTime(20000), 0).TestFailed() // 50 old blocks (so 899 out of the past 1000) .Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
.Mine(4000, TestTime(20010), 0x100).TestFailed() .Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)
// DEFINED -> STARTED -> FAILED while threshold reached // DEFINED -> STARTED -> FAILED while threshold reached
.Reset().TestDefined() .Reset().TestDefined().TestStateSinceHeight(0)
.Mine(1, TestTime(1), 0).TestDefined() .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
.Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
.Mine(2999, TestTime(30000), 0x100).TestStarted() // 999 new blocks .Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks
.Mine(3000, TestTime(30000), 0x100).TestFailed() // 1 new block (so 1000 out of the past 1000 are new) .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() .Mine(3999, TestTime(30001), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestFailed() .Mine(4000, TestTime(30002), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(14333, TestTime(30003), 0).TestFailed() .Mine(14333, TestTime(30003), 0).TestFailed().TestStateSinceHeight(3000)
.Mine(24000, TestTime(40000), 0).TestFailed() .Mine(24000, TestTime(40000), 0).TestFailed().TestStateSinceHeight(3000)
// DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE // DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE
.Reset().TestDefined() .Reset().TestDefined()
.Mine(1, TestTime(1), 0).TestDefined() .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
.Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
.Mine(2050, TestTime(10010), 0x200).TestStarted() // 50 old blocks .Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) // 50 old blocks
.Mine(2950, TestTime(10020), 0x100).TestStarted() // 900 new blocks .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 900 new blocks
.Mine(2999, TestTime(19999), 0x200).TestStarted() // 49 old blocks .Mine(2999, TestTime(19999), 0x200).TestStarted().TestStateSinceHeight(2000) // 49 old blocks
.Mine(3000, TestTime(29999), 0x200).TestLockedIn() // 1 old block (so 900 out of the past 1000) .Mine(3000, TestTime(29999), 0x200).TestLockedIn().TestStateSinceHeight(3000) // 1 old block (so 900 out of the past 1000)
.Mine(3999, TestTime(30001), 0).TestLockedIn() .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
.Mine(4000, TestTime(30002), 0).TestActive() .Mine(4000, TestTime(30002), 0).TestActive().TestStateSinceHeight(4000)
.Mine(14333, TestTime(30003), 0).TestActive() .Mine(14333, TestTime(30003), 0).TestActive().TestStateSinceHeight(4000)
.Mine(24000, TestTime(40000), 0).TestActive(); .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 // Sanity checks of version bit deployments

View file

@ -105,6 +105,36 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
return state; 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 namespace
{ {
/** /**
@ -137,6 +167,11 @@ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]); 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) uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos)
{ {
return VersionBitsConditionChecker(pos).Mask(params); return VersionBitsConditionChecker(pos).Mask(params);

View file

@ -51,8 +51,9 @@ protected:
virtual int Threshold(const Consensus::Params& params) const =0; virtual int Threshold(const Consensus::Params& params) const =0;
public: 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; 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 struct VersionBitsCache
@ -63,6 +64,7 @@ struct VersionBitsCache
}; };
ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); 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); uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos);
#endif #endif