Skip to content

Commit 557c9a6

Browse files
committed
RPC: getblockchaininfo: BIP9 stats
add RPC tests for BIP9 counting stats
1 parent a0b1e57 commit 557c9a6

File tree

6 files changed

+121
-7
lines changed

6 files changed

+121
-7
lines changed

src/rpc/blockchain.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
191191
latestblock.hash = pindex->GetBlockHash();
192192
latestblock.height = pindex->nHeight;
193193
}
194-
cond_blockchange.notify_all();
194+
cond_blockchange.notify_all();
195195
}
196196

197197
UniValue waitfornewblock(const JSONRPCRequest& request)
@@ -1064,6 +1064,17 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse
10641064
rv.push_back(Pair("startTime", consensusParams.vDeployments[id].nStartTime));
10651065
rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout));
10661066
rv.push_back(Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id)));
1067+
if (THRESHOLD_STARTED == thresholdState)
1068+
{
1069+
UniValue statsUV(UniValue::VOBJ);
1070+
BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id);
1071+
statsUV.push_back(Pair("period", statsStruct.period));
1072+
statsUV.push_back(Pair("threshold", statsStruct.threshold));
1073+
statsUV.push_back(Pair("elapsed", statsStruct.elapsed));
1074+
statsUV.push_back(Pair("count", statsStruct.count));
1075+
statsUV.push_back(Pair("possible", statsStruct.possible));
1076+
rv.push_back(Pair("statistics", statsUV));
1077+
}
10671078
return rv;
10681079
}
10691080

@@ -1109,7 +1120,14 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
11091120
" \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
11101121
" \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
11111122
" \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
1112-
" \"since\": xx (numeric) height of the first block to which the status applies\n"
1123+
" \"since\": xx, (numeric) height of the first block to which the status applies\n"
1124+
" \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)\n"
1125+
" \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n"
1126+
" \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n"
1127+
" \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n"
1128+
" \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n"
1129+
" \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n"
1130+
" }\n"
11131131
" }\n"
11141132
" }\n"
11151133
"}\n"

src/validation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4153,6 +4153,12 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D
41534153
return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache);
41544154
}
41554155

4156+
BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos)
4157+
{
4158+
LOCK(cs_main);
4159+
return VersionBitsStatistics(chainActive.Tip(), params, pos);
4160+
}
4161+
41564162
int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos)
41574163
{
41584164
LOCK(cs_main);

src/validation.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ std::string FormatStateMessage(const CValidationState &state);
333333
/** Get the BIP9 state for a given deployment at the current tip. */
334334
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos);
335335

336+
/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */
337+
BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos);
338+
336339
/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */
337340
int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos);
338341

src/versionbits.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include "versionbits.h"
6-
76
#include "consensus/params.h"
87

98
const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = {
@@ -105,6 +104,36 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
105104
return state;
106105
}
107106

107+
// return the numerical statistics of blocks signalling the specified BIP9 condition in this current period
108+
BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const
109+
{
110+
BIP9Stats stats;
111+
112+
stats.period = Period(params);
113+
stats.threshold = Threshold(params);
114+
115+
if (pindex == NULL)
116+
return stats;
117+
118+
// Find beginning of period
119+
const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period));
120+
stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight;
121+
122+
// Count from current block to beginning of period
123+
int count = 0;
124+
const CBlockIndex* currentIndex = pindex;
125+
while (pindexEndOfPrevPeriod->nHeight != currentIndex->nHeight){
126+
if (Condition(currentIndex, params))
127+
count++;
128+
currentIndex = currentIndex->pprev;
129+
}
130+
131+
stats.count = count;
132+
stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count);
133+
134+
return stats;
135+
}
136+
108137
int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const
109138
{
110139
const ThresholdState initialState = GetStateFor(pindexPrev, params, cache);
@@ -167,6 +196,11 @@ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::
167196
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]);
168197
}
169198

199+
BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
200+
{
201+
return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params);
202+
}
203+
170204
int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache)
171205
{
172206
return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]);

src/versionbits.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ struct BIP9DeploymentInfo {
3737
bool gbt_force;
3838
};
3939

40+
struct BIP9Stats {
41+
int period;
42+
int threshold;
43+
int elapsed;
44+
int count;
45+
bool possible;
46+
};
47+
4048
extern const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[];
4149

4250
/**
@@ -51,6 +59,7 @@ class AbstractThresholdConditionChecker {
5159
virtual int Threshold(const Consensus::Params& params) const =0;
5260

5361
public:
62+
BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const;
5463
// Note that the functions below take a pindexPrev as input: they compute information for block B based on its parent.
5564
ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const;
5665
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const;
@@ -64,6 +73,7 @@ struct VersionBitsCache
6473
};
6574

6675
ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache);
76+
BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos);
6777
int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache);
6878
uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos);
6979

test/functional/bip9-softforks.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,43 @@ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignatu
104104

105105
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
106106
assert_equal(self.get_bip9_status(bipName)['since'], 144)
107+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
108+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
107109
tmpl = self.nodes[0].getblocktemplate({})
108110
assert(bipName not in tmpl['rules'])
109111
assert_equal(tmpl['vbavailable'][bipName], bitno)
110112
assert_equal(tmpl['vbrequired'], 0)
111113
assert(tmpl['version'] & activated_version)
112114

115+
# Test 1-A
116+
# check stats after max number of "signalling not" blocks such that LOCKED_IN still possible this period
117+
test_blocks = self.generate_blocks(36, 4, test_blocks) # 0x00000004 (signalling not)
118+
test_blocks = self.generate_blocks(10, activated_version) # 0x20000001 (signalling ready)
119+
yield TestInstance(test_blocks, sync_every_block=False)
120+
121+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 46)
122+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
123+
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
124+
125+
# Test 1-B
126+
# check stats after one additional "signalling not" block -- LOCKED_IN no longer possible this period
127+
test_blocks = self.generate_blocks(1, 4, test_blocks) # 0x00000004 (signalling not)
128+
yield TestInstance(test_blocks, sync_every_block=False)
129+
130+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 47)
131+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
132+
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], False)
133+
134+
# Test 1-C
135+
# finish period with "ready" blocks, but soft fork will still fail to advance to LOCKED_IN
136+
test_blocks = self.generate_blocks(97, activated_version) # 0x20000001 (signalling ready)
137+
yield TestInstance(test_blocks, sync_every_block=False)
138+
139+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
140+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
141+
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
142+
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
143+
113144
# Test 2
114145
# Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
115146
# using a variety of bits to simulate multiple parallel softforks
@@ -121,6 +152,8 @@ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignatu
121152

122153
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
123154
assert_equal(self.get_bip9_status(bipName)['since'], 144)
155+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
156+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
124157
tmpl = self.nodes[0].getblocktemplate({})
125158
assert(bipName not in tmpl['rules'])
126159
assert_equal(tmpl['vbavailable'][bipName], bitno)
@@ -130,14 +163,24 @@ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignatu
130163
# Test 3
131164
# 108 out of 144 signal bit 1 to achieve LOCKED_IN
132165
# using a variety of bits to simulate multiple parallel softforks
133-
test_blocks = self.generate_blocks(58, activated_version) # 0x20000001 (signalling ready)
166+
test_blocks = self.generate_blocks(57, activated_version) # 0x20000001 (signalling ready)
134167
test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
135168
test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
136169
test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not)
137170
yield TestInstance(test_blocks, sync_every_block=False)
138171

172+
# check counting stats and "possible" flag before last block of this period achieves LOCKED_IN...
173+
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 143)
174+
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 107)
175+
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
176+
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
177+
178+
# ...continue with Test 3
179+
test_blocks = self.generate_blocks(1, activated_version) # 0x20000001 (signalling ready)
180+
yield TestInstance(test_blocks, sync_every_block=False)
181+
139182
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
140-
assert_equal(self.get_bip9_status(bipName)['since'], 432)
183+
assert_equal(self.get_bip9_status(bipName)['since'], 576)
141184
tmpl = self.nodes[0].getblocktemplate({})
142185
assert(bipName not in tmpl['rules'])
143186

@@ -147,7 +190,7 @@ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignatu
147190
yield TestInstance(test_blocks, sync_every_block=False)
148191

149192
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
150-
assert_equal(self.get_bip9_status(bipName)['since'], 432)
193+
assert_equal(self.get_bip9_status(bipName)['since'], 576)
151194
tmpl = self.nodes[0].getblocktemplate({})
152195
assert(bipName not in tmpl['rules'])
153196

@@ -173,7 +216,7 @@ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignatu
173216
yield TestInstance([[block, True]])
174217

175218
assert_equal(self.get_bip9_status(bipName)['status'], 'active')
176-
assert_equal(self.get_bip9_status(bipName)['since'], 576)
219+
assert_equal(self.get_bip9_status(bipName)['since'], 720)
177220
tmpl = self.nodes[0].getblocktemplate({})
178221
assert(bipName in tmpl['rules'])
179222
assert(bipName not in tmpl['vbavailable'])

0 commit comments

Comments
 (0)