Browse Source

Merge #9571: RPC: getblockchaininfo returns BIP signaling statistics

557c9a6 RPC: getblockchaininfo: BIP9 stats (Matthew Zipkin)

Tree-SHA512: ecf0bf47f04f92becc77acc649fdfa270e768939acce42df39d30069398d40d9a30539862f7c307e08239f78d5c58c470ca5f6e717d2ab8e24db9be0dd7bec0c
0.15
Wladimir J. van der Laan 8 years ago
parent
commit
46771514fa
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
  1. 20
      src/rpc/blockchain.cpp
  2. 6
      src/validation.cpp
  3. 3
      src/validation.h
  4. 36
      src/versionbits.cpp
  5. 10
      src/versionbits.h
  6. 51
      test/functional/bip9-softforks.py

20
src/rpc/blockchain.cpp

@ -1074,6 +1074,17 @@ 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))); rv.push_back(Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id)));
if (THRESHOLD_STARTED == thresholdState)
{
UniValue statsUV(UniValue::VOBJ);
BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id);
statsUV.push_back(Pair("period", statsStruct.period));
statsUV.push_back(Pair("threshold", statsStruct.threshold));
statsUV.push_back(Pair("elapsed", statsStruct.elapsed));
statsUV.push_back(Pair("count", statsStruct.count));
statsUV.push_back(Pair("possible", statsStruct.possible));
rv.push_back(Pair("statistics", statsUV));
}
return rv; return rv;
} }
@ -1119,7 +1130,14 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
" \"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" " \"since\": xx, (numeric) height of the first block to which the status applies\n"
" \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)\n"
" \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n"
" \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n"
" \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n"
" \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n"
" \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n"
" }\n"
" }\n" " }\n"
" }\n" " }\n"
"}\n" "}\n"

6
src/validation.cpp

@ -3935,6 +3935,12 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D
return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache); return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache);
} }
BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos)
{
LOCK(cs_main);
return VersionBitsStatistics(chainActive.Tip(), params, pos);
}
int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos) int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos)
{ {
LOCK(cs_main); LOCK(cs_main);

3
src/validation.h

@ -339,6 +339,9 @@ 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 numerical statistics for the BIP9 state for a given deployment at the current tip. */
BIP9Stats VersionBitsTipStatistics(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. */ /** 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); int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos);

36
src/versionbits.cpp

@ -3,7 +3,6 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "versionbits.h" #include "versionbits.h"
#include "consensus/params.h" #include "consensus/params.h"
const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = {
@ -105,6 +104,36 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
return state; return state;
} }
// 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 stats;
stats.period = Period(params);
stats.threshold = Threshold(params);
if (pindex == NULL)
return stats;
// Find beginning of period
const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period));
stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight;
// Count from current block to beginning of period
int count = 0;
const CBlockIndex* currentIndex = pindex;
while (pindexEndOfPrevPeriod->nHeight != currentIndex->nHeight){
if (Condition(currentIndex, params))
count++;
currentIndex = currentIndex->pprev;
}
stats.count = count;
stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count);
return stats;
}
int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const
{ {
const ThresholdState initialState = GetStateFor(pindexPrev, params, cache); const ThresholdState initialState = GetStateFor(pindexPrev, params, cache);
@ -167,6 +196,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]);
} }
BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
{
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params);
}
int VersionBitsStateSinceHeight(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)
{ {
return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]); return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]);

10
src/versionbits.h

@ -37,6 +37,14 @@ struct BIP9DeploymentInfo {
bool gbt_force; bool gbt_force;
}; };
struct BIP9Stats {
int period;
int threshold;
int elapsed;
int count;
bool possible;
};
extern const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[]; extern const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[];
/** /**
@ -51,6 +59,7 @@ protected:
virtual int Threshold(const Consensus::Params& params) const =0; virtual int Threshold(const Consensus::Params& params) const =0;
public: public:
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const;
// Note that the functions below take 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; int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const;
@ -64,6 +73,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);
BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos);
int VersionBitsStateSinceHeight(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);

51
test/functional/bip9-softforks.py

@ -99,12 +99,43 @@ class BIP9SoftForksTest(ComparisonTestFramework):
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) assert_equal(self.get_bip9_status(bipName)['since'], 144)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
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)
assert_equal(tmpl['vbrequired'], 0) assert_equal(tmpl['vbrequired'], 0)
assert(tmpl['version'] & activated_version) assert(tmpl['version'] & activated_version)
# Test 1-A
# check stats after max number of "signalling not" blocks such that LOCKED_IN still possible this period
test_blocks = self.generate_blocks(36, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(10, activated_version) # 0x20000001 (signalling ready)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 46)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
# Test 1-B
# check stats after one additional "signalling not" block -- LOCKED_IN no longer possible this period
test_blocks = self.generate_blocks(1, 4, test_blocks) # 0x00000004 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 47)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], False)
# Test 1-C
# finish period with "ready" blocks, but soft fork will still fail to advance to LOCKED_IN
test_blocks = self.generate_blocks(97, activated_version) # 0x20000001 (signalling ready)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
# Test 2 # Test 2
# Fail to achieve LOCKED_IN 100 out of 144 signal bit 1 # Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
# using a variety of bits to simulate multiple parallel softforks # using a variety of bits to simulate multiple parallel softforks
@ -116,6 +147,8 @@ class BIP9SoftForksTest(ComparisonTestFramework):
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) assert_equal(self.get_bip9_status(bipName)['since'], 144)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
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)
@ -125,14 +158,24 @@ class BIP9SoftForksTest(ComparisonTestFramework):
# Test 3 # Test 3
# 108 out of 144 signal bit 1 to achieve LOCKED_IN # 108 out of 144 signal bit 1 to achieve LOCKED_IN
# using a variety of bits to simulate multiple parallel softforks # using a variety of bits to simulate multiple parallel softforks
test_blocks = self.generate_blocks(58, activated_version) # 0x20000001 (signalling ready) test_blocks = self.generate_blocks(57, activated_version) # 0x20000001 (signalling ready)
test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not) test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready) test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not) test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False) yield TestInstance(test_blocks, sync_every_block=False)
# check counting stats and "possible" flag before last block of this period achieves LOCKED_IN...
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 143)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 107)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
# ...continue with Test 3
test_blocks = self.generate_blocks(1, activated_version) # 0x20000001 (signalling ready)
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) assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
@ -142,7 +185,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) assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({}) tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules']) assert(bipName not in tmpl['rules'])
@ -168,7 +211,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) assert_equal(self.get_bip9_status(bipName)['since'], 720)
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'])

Loading…
Cancel
Save