Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
RPC: getblockchaininfo returns BIP signaling statistics #9571
Conversation
fanquake
added
the
RPC/REST/ZMQ
label
Jan 18, 2017
|
NAK. The BIP9 quorum sensing is not a vote. |
|
Signaling statistics for soft forks pre-BIP9 used to be reported by I think it's helpful to see the adoption rate to better predict when a soft fork will engage. Currently there are several 3rd party websites that report these statistics, I would rather get them from my own node. |
|
I think these are useful statistics to have. Just don't call it a vote in the RPC help.
|
|
@sipa no V-word in the RPC help. I rephrased my commit description. |
|
yea, okay. Anyone interested in writing a test for one off on the possible boundary. |
|
@gmaxwell Added tests, look ok? |
| @@ -32,6 +32,8 @@ | ||
| #include <boost/unordered_map.hpp> | ||
| #include <boost/filesystem/path.hpp> | ||
| +#include <univalue.h> |
TheBlueMatt
Jan 18, 2017
Contributor
Preferably dont add a UniValue dependancy here...maybe just define a struct which carries the info you want?
|
Looks cool, I've previously written python crap to calculate similar stats from rpc data. |
| @@ -8,6 +8,7 @@ src/bitcoin-tx | ||
| src/test/test_bitcoin | ||
| src/test/test_bitcoin_fuzzy | ||
| src/qt/test/test_bitcoin-qt | ||
| +src/datadir |
| @@ -187,7 +187,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex) | ||
| latestblock.hash = pindex->GetBlockHash(); | ||
| latestblock.height = pindex->nHeight; | ||
| } | ||
| - cond_blockchange.notify_all(); | ||
| + cond_blockchange.notify_all(); |
TheBlueMatt
Jan 18, 2017
Contributor
That wasnt random, that was replacing an errant tab, I say leave it.
| + | ||
| + // Walk back from current block to beginning of period | ||
| + std::vector<const CBlockIndex*> vToCompute; | ||
| + while (pindexPrev->nHeight != currentIndex->nHeight){ |
robmcl4
Jan 18, 2017
Contributor
What if pindexPrev == currentIndex == NULL? Also, why not keep a rolling count rather than using an accessory vector?
|
@TheBlueMatt @robmcl4 Thanks, took out vector and just counted. If @instagibbs Removed my extra ignore line but left tab fix |
| @@ -32,6 +32,8 @@ | ||
| #include <boost/unordered_map.hpp> | ||
| #include <boost/filesystem/path.hpp> | ||
| +#include <univalue.h> |
| #include "consensus/params.h" | ||
| +#include <boost/foreach.hpp> |
| @@ -105,6 +106,47 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* | ||
| return state; | ||
| } | ||
| +// return the numerical statistics of blocks signalling the specified BIP9 condition in this current period | ||
| +UniValue AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const |
sipa
Jan 19, 2017
Owner
Does this code need to be in the condition checker implementation? It seems like it could be written generically, as it doesn't need access to the cache or internal state machine.
pinheadmz
Jan 19, 2017
Contributor
Should I take it out? VersionBitsConditionChecker already has Condition() function for the bitmask for the corresponding soft fork.
|
squashed |
jnewbery
reviewed
Jan 23, 2017
Concept ACK, and functional ACK. I've tested this and I'm fairly confident it's calculating the values correctly. Thanks for adding the additional tests to bip9-softforks.py
I have a few code style and variable naming nits.
| @@ -1102,7 +1114,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" | ||
| " \"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" | ||
| - " \"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\n" |
jnewbery
Jan 23, 2017
Member
nit: perhaps add (only for \"started\" status)\n (like for the bit field above)
| + " \"period length\": 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" |
| +// return the numerical statistics of blocks signalling the specified BIP9 condition in this current period | ||
| +BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindexPrev, const Consensus::Params& params) const | ||
| +{ | ||
| + BIP9Stats obj; |
jnewbery
Jan 23, 2017
Member
nit: I suspect the name 'obj' is from when you were passing the object back as a univalue. I'd prefer for this variable to be named a little more descriptively, perhaps 'stats' or 'bip9Stats'.
| + | ||
| + int elapsedBlocks; | ||
| + int nPeriod = Period(params); | ||
| + int nThreshold = Threshold(params); |
jnewbery
Jan 23, 2017
Member
You're declaring local variables, setting them once, and then setting the obj members to those values. Would it be cleaner to just set the obj members directly?
pinheadmz
Jan 23, 2017
Contributor
nPeriod and nThreshold are used again in line 137 (now line 135) to calculate the possible value. But you are right about elapsedBlocks and the actual bool possible, so I took those declarations out and simplified the assignments.
pinheadmz
Jan 23, 2017
Contributor
Actually just took out declarations for all those variables, assigned to return object directly and refer to those assignments downstream. (see 6eecfe0)
| + obj.threshold = nThreshold; | ||
| + | ||
| + // Find beginning of period | ||
| + if (pindexPrev != NULL) { |
jnewbery
Jan 23, 2017
Member
You're checking that pindexPrev isn't NULL, but then dereferencing further down (in line 130) even if this check fails.
Perhaps move this check to the top and return from the function if pindexPrev is NULL?
pinheadmz
Jan 23, 2017
Contributor
Moved the check up so the return struct is partially initialized. However, this function shouldn't ever be called unless THRESHOLD_STARTED == thresholdState anyway, which will never be the case at block 0.
jnewbery
Jan 23, 2017
Member
I agree that this should always be non-NULL, but it's better to be conservative, and it's certainly better to be consistently conservative than conservative here and not conservative later :)
| + | ||
| + // Find beginning of period | ||
| + if (pindexPrev != NULL) { | ||
| + pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); |
jnewbery
Jan 23, 2017
Member
I don't much like this reassignment of pindexPrev to the first block in the current difficulty window. I know that the same thing happens in AbstractThresholdConditionChecker() above, but I think it'd be clearer to have a local variable called something like pindexFirstInWindow. Reasoning about whether a function is doing what you want is easier if the variables are named what they're actually used for.
pinheadmz
Jan 23, 2017
Contributor
Yes I was mostly copying the format of AbstractThresholdConditionChecker::GetStateFor() but I agree with you, changed variable name.
| + if (pindexPrev != NULL) { | ||
| + pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); | ||
| + elapsedBlocks = currentIndex->nHeight - pindexPrev->nHeight; | ||
| + obj.elapsed = elapsedBlocks; |
jnewbery
Jan 23, 2017
Member
Is it tidier to calculate elapsed directly from (pindexPrev % nPeriod) + 1?
pinheadmz
Jan 23, 2017
Contributor
Not sure about this. After making this one line, it seems the same to me. Still need to get nHeight from each. Simpler to me to see "Current - Start = Elapsed"
| + | ||
| + obj.count = count; | ||
| + bool possible = (nPeriod - nThreshold) > (elapsedBlocks - count); | ||
| + obj.possible = possible; |
| @@ -53,6 +61,7 @@ class AbstractThresholdConditionChecker { | ||
| public: | ||
| // Note that the functions below take a pindexPrev as input: they compute information for block B based on its parent. |
jnewbery
Jan 23, 2017
•
Member
This comment is very misleading, and so is naming the variable pindexPrev.
When GetStateFor() was written, the meaning of the function was: "I have block B in hand, and I'm trying to connect it to block A in the tree. What are the BIP9 softfork consensus rules that I should enforce on block B based on block A?" In that context, pindexPrev makes sense - we're judging block B's consensus rules based on its pindexPrev pointer to block A.
GetStateStatisticsFor() and GetStateSinceHeightFor() (introduced in #7948) are being used for a different purpose. The meaning of these functions is "Block A is our view of the current tip. What is our view of current network consensus rules?" In that context, pindexPrev doesn't make sense at all. There's no block B with a pindexPointer back to block A.
I'd prefer for the variable pindexPrev to be renamed in GetStateStatisticsFor() and GetStateSinceHeightFor(). Perhaps change it to pindex and update the comment to describe what the functions are actually being used for.
pinheadmz
Jan 23, 2017
Contributor
Yeah ok this makes sense to me. I'll take my function out of the "functions below" list and rename variable to pindex. Should correcting GetStateSinceHeightFor() happen in a different commit though?
jnewbery
Jan 23, 2017
Member
yeah, I'd suggest changing GetStateSinceHeightFor() in a different commit. I can open a PR for that or leave it to you if you prefer.
| + { | ||
| + UniValue statsUV(UniValue::VOBJ); | ||
| + BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id); | ||
| + statsUV.push_back(Pair("period length", statsStruct.period)); |
|
utACK 83538dd |
laanwj
added this to the 0.15.0 milestone
Feb 10, 2017
| + statsUV.push_back(Pair("elapsed", statsStruct.elapsed)); | ||
| + statsUV.push_back(Pair("count", statsStruct.count)); | ||
| + statsUV.push_back(Pair("possible", statsStruct.possible)); | ||
| + |
TheBlueMatt
Feb 10, 2017
Contributor
Please be careful to not introduce lines with whitespace at the end. Looks like this patch has it in 5 places (here and 4 lines in AbstractThreasholdConditionCHecker::GetStateStatisticsFor).
| + return stats; | ||
| + | ||
| + // Find beginning of period | ||
| + const CBlockIndex* pindexFirstInPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period)); |
TheBlueMatt
Feb 10, 2017
Contributor
Can we get a more descriptive name? This is actually the last block in the previous period, not the first in this period (though it looks like all the later math is correct).
pinheadmz
Feb 10, 2017
Contributor
Ah I think you're right. I copied this line from AbstractThresholdConditionChecker::GetStateFor
We'll call it pindexEndOfPrevPeriod ?
| + } | ||
| + | ||
| + stats.count = count; | ||
| + stats.possible = (stats.period - stats.threshold ) > (stats.elapsed - count); |
| @@ -330,6 +330,9 @@ 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 numerical staistics for the BIP9 state for a given deployment at the current tip. */ |
| - test_blocks = self.generate_blocks(20, 4, test_blocks) # 0x00000004 (signalling not) | ||
| - test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready) | ||
| - test_blocks = self.generate_blocks(24, 4, test_blocks) # 0x20010000 (signalling not) | ||
| + test_blocks = self.generate_blocks(40, activated_version) # 0x20000001 (signalling ready) |
TheBlueMatt
Feb 20, 2017
Contributor
I'd kinda prefer if you didnt change Test 2 here...just add more tests as "Test 1" with a 144-block-total. Also, though I /think/ the new code is right, it'd be really nice if you could add two tests: one which fails to active by 1 block (and checks the "possible" flag is false after the first N), and one which activates with one more signaling block, to make sure the possible flat is set right.
pinheadmz
Feb 20, 2017
Contributor
I think I see what you mean, test the flag at the end of the period to make sure the flag matches the actual outcome in the end? That's cool for the negative test, but since it can only activate once, the positive test must borrow some blocks from Test 2?
pinheadmz
Feb 21, 2017
Contributor
@TheBlueMatt I rearranged the tests to hit the thresholds. I put Test 2 back to where it was, but still had to modify Test 3 to cover everything (just borrowed one block and tested the possible flag right before period completion). LMK what you think, thanks
|
utACK 9b9b685 thanks so much for sticking with this :). |
| @@ -330,6 +330,9 @@ 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 numerical statistics for the BIP9 state for a given deployment at the current tip. */ | ||
| +BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos); |
kallewoof
Mar 1, 2017
Member
Nit: I keep getting this and VersionBitsStatistics mixed up when I read through the changes. I think it would help readability to name this e.g. CurrentVersionBitsStatistics or ActiveVersionBitsStatistics or something.
|
rebased on master a0b1e57 |
|
Concept ACK |
pinheadmz commentedJan 18, 2017
•
Edited 1 time
-
pinheadmz
Jan 18, 2017
Adds a new array to
getblockchaininfothat returns the number of blocks in the current period signaling a BIP9 condition.Example:
and the explanation:
[related question]