-
Notifications
You must be signed in to change notification settings - Fork 36.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement BIP 8 based Speedy Trial activation #21392
Conversation
Maybe consider rebasing on #21380 ; it adds fuzz testing and removes the need for the |
b16a755
to
0dc3825
Compare
0dc3825
to
3d6fa01
Compare
Done. It did indeed make this simpler. The fuzzer also caught one minor issue. |
3d6fa01
to
c25fe12
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally looks ok to me couple minor comments; can do a more detailed review later
src/consensus/params.h
Outdated
/** Timeout/expiry MedianTime for the deployment attempt. */ | ||
int64_t nTimeout; | ||
/** Start block height for version bits miner confirmation. Must be a retarget block, can be in the past. */ | ||
int64_t startheight; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be set to NEVER_ACTIVE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The potential for uninitialized members here is concerning to me, so I am working on a followup to deal with those. That may be rolled into this PR.
453df2d
to
9cb9a09
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
e322337
to
9e50401
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK. Did some light review of 9e50401.
Would love to see a functional test that shows the speedy trial in action, by looking at softforks
in getblockchaininfo
(I might write one myself)
re-ACK 2e55bce |
ACK 2e55bce Ideally a choice on a Speedy Trial PR would have been made by now so I could focus review and testing on one PR and one approach. Spreading review over two competing PRs is doing none of us any favors. I would have liked to have spent more time testing this PR before ACKing it but I think the biggest danger at this point is spreading review too thinly across two PRs. |
int startheight; | ||
/** Timeout/expiry block height for the deployment attempt. Must be a retarget block. */ | ||
int timeoutheight; | ||
/** Threshold for lockin. Must be at least nRuleChangeActivationThreshold for that network. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this variable name changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix if I retouch.
return false; | ||
} | ||
if (timeoutheight < startheight + (2 * (int)consensus.nMinerConfirmationWindow)) { | ||
error = strprintf("Invalid timeoutheight (%d), must be at least two periods greater than the startheight (%d)", timeoutheight, startheight); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Just one period should be enough...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforces this sentence in BIP 8:
timeoutheight must be at least 4096 blocks (2 retarget intervals) after startheight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's needed for lot=true/MUST_SIGNAL to work consistently with lot=false. Could also be solved by adding a transition directly from DEFINED to MUST_SIGNAL (skipping STARTED) I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, probably not worth changing. One period is probably crazy anyway. And if we ever do need it, we can add the extra logic for it at that time.
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); | ||
bip9.pushKV("startheight", consensusParams.vDeployments[id].startheight); | ||
bip9.pushKV("timeoutheight", consensusParams.vDeployments[id].timeoutheight); | ||
bip9.pushKV("minimum_activation_height", consensusParams.vDeployments[id].m_min_activation_height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should either hide this when 0, or use starttime+2*period
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to always display.
As of commit 2e55bce, lightly tested the likely outcomes of ST and confirmed that Tested creating a regtest chain with this branch and I skimmed the code and didn't see any problems, but I don't feel confident enough in my reviewing abilities to say it definitely is fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at 2e55bce 🚴
Show signature and timestamp
Signature:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
I looked at 2e55bcedb8d73e49620a5731196bf7e23bb53ccc 🚴
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUg82Av/XqMgcTbyFlMastLZ9AJdjHOHQjwpOkce6XC8ktfvqwbWpRioWiSHf0+C
7bN6kFEJMxXPUAOZ4DeHLoBrsCMqTwPJZ21nNV9p8ZhByBBp5KPEA2PJ3s/RwBCp
YeRpKlLj856117mUv/5oFnABj4xuUMmZ3OkwuvU3RN7TrfKBUkR8ZkmzoSaOkv7s
O/bnZC4qudRl/dGFoa+CouYMWpi49mT+kB59Rs8UZha86KH5Td2siTAbbJBiMAi5
8bFUwGSEsvxJGAr+52PHAcUUymGMZp29neY4Q5f7HIJ28uX5ged/EzSiFeeu4GZn
tH9B7mSqOvjnFIdOCvy8HYy8TzShKGvkiD4PcNvcVWfkQi2WbXtns1AjaZ5Tjeu9
ry2OHAzyWSvqbT1DIPD36VTb2c46u/vd9d05rkCjuccHcnXPkV49f68JblA2p9Fj
LUpoCswXIJPZtPxs2ZSwb0LxP54YW1RnguVOy6jg7PcMnecDeu/LFD4MsJtVJE9W
bqp2Q82X
=zjni
-----END PGP SIGNATURE-----
Timestamp of file with hash 9fd4467b454c576b05f9c797bd20cd8620ad58c2b63ca8dd4e36f27ec7059a14 -
const Consensus::Params &mainnetParams = chainParams->GetConsensus(); | ||
const auto period = CreateChainParams(*m_node.args, CBaseChainParams::REGTEST)->GetConsensus().nMinerConfirmationWindow; | ||
gArgs.ForceSetArg("-vbparams", strprintf("testdummy:@%s:@%s", period, period * 3)); | ||
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::REGTEST); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to use gArgs in the previous line, then m_node.args here? Might be better to just use a local symbol to avoid potentially polluting other tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
} | ||
|
||
// Actual params must be on retarget block | ||
if (startheight < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not enforce startheight >= nMinerConfirmationWindow
? Otherwise it is possible to violate the "2 period rule" by setting @0:@288
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
{ | ||
// Special always or never active cases | ||
if ((startheight == Consensus::BIP9Deployment::NEVER_ACTIVE && timeoutheight == Consensus::BIP9Deployment::NEVER_ACTIVE) | ||
|| startheight == Consensus::BIP9Deployment::ALWAYS_ACTIVE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it could be ignored for NEVER as well or what is the reason to treat ALWAYS different from NEVER?
switch (state) { | ||
case ThresholdState::DEFINED: { | ||
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { | ||
stateNext = ThresholdState::FAILED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not assert this transition can't happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's being checked by the test cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fuzz test has all transitions of the state machine implemented (in a "come from" form rather than a "go to" form). I don't think it's possible to make incorrect transitions that it won't detect.
ACK 2e55bce In addition to the testing in #21392 (comment) I also checked that the fuzzer detects various off-by-one errors I manually introduced into the implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 2e55bce
reACK 2e55bce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK 2e55bce
{ | ||
// Special always or never active cases | ||
if ((startheight == Consensus::BIP9Deployment::NEVER_ACTIVE && timeoutheight == Consensus::BIP9Deployment::NEVER_ACTIVE) | ||
|| startheight == Consensus::BIP9Deployment::ALWAYS_ACTIVE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MarcoFalke I think that the rationale is that NO_TIMEOUT isn't a special value (unlike ALWAYS_ACTIVE
and NEVER_ACTIVE
), it's just a timestamp very far in the future.
@@ -1243,8 +1242,8 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam | |||
{ | |||
bip9.pushKV("bit", consensusParams.vDeployments[id].bit); | |||
} | |||
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); | |||
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); | |||
bip9.pushKV("startheight", consensusParams.vDeployments[id].startheight); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benthecarman To what?
I think I'm an approach NACK, or at least approach -1, on using heights for activation at this point: they're incompatible with activating on testnet due to hash rate inconsistency, and they're incompatible with activation on signet due to custom signets being able to have completely random heights, since anyone can start a new one at any time. They're not especially convenient for activating on mainnet either: we're choosing timeframes and then converting that to expected block heights, but that can have significant errors -- if we want to give people "3 months" to review an update before deploying it, and choose block heights to reflect that, we risk both increased hashpower reducing that time substantially, or reduced hashpower introducing unwanted delays. The main arguments I've seen for the height based approach is to avoid the signalling phase being skipped entirely, and to prevent a mandatory signalling phase (UASF/bip148/bip91/etc) from being skipped entirely, even in the event of a substantial loss of hashrate. But those goals can be achieved with the MTP approach as well -- see the top commit on https://github.com/ajtowns/bitcoin/commits/202103-bip9-uasf . There's the additional benefit that it's simpler to explain "signalling begins at height X and ends at height Y" than "signalling begins at the first retarget period after time X, and ends in the last retarget period before time Y", but that seems like a much lower priority than being able to test activations on testnet and signet prior to activation on mainnet... |
For the sake of respecting other reviewers' time, Taproot is already active on the default Signet for testing and experimentation with Taproot transactions. Activating Taproot on testnet seems like a very low priority, certainly a lower priority than ensuring the optimal code is merged for mainnet activation. If you want a summary of the arguments for a consistent use of block height versus using a mix of block height and MTP there is this SE question with two answers, one from harding and one from me summarizing the arguments expressed in the two Speedy Trial PRs. This appears to be the major differentiator between the two PRs. If you would prefer a consistent use of block height I would recommend reviewing this PR. If you would prefer a mix of block height and MTP I would recommend reviewing AJ's alternative Speedy Trial PR. As I've communicated on AJ's PR, spreading review over two competing PRs is doing none of us any favors at this point. |
Also this shows preferences for a consistent use of block height.
In addition @JeremyRubin stated here "I have a preference for fully height based design, correct." This is becoming a farce, pure and simple. |
I don't think test networks should be relevant to mainnet's activation mechanisms / consensus rules |
@@ -49,20 +57,17 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* | |||
pindexPrev = vToCompute.back(); | |||
vToCompute.pop_back(); | |||
|
|||
// We track state by previous-block, so the height we should be comparing is +1 | |||
const int64_t height = pindexPrev->nHeight + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you widening height from an int
(32 bit) to a int64_t
, and then comparing with int
s below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix if I need to retouch.
# Copyright (c) 2021 The Bitcoin Core developers | ||
# Distributed under the MIT software license, see the accompanying | ||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
# Test BIP 8 softforks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use docstrings for test description to separate from copyright notice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
from collections import namedtuple | ||
from test_framework.test_framework import BitcoinTestFramework |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate std library imports from local imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
self.setup_clean_chain = True | ||
self.extra_args = [ | ||
['-vbparams=testdummy:@-2:@-2'], # Node 0 has TestDummy inactive | ||
['-vbparams=testdummy:@144:@{}'.format(144 * 3)], # Node 1 has regular activation window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f-strings are generally preferred in new tests:
['-vbparams=testdummy:@144:@{}'.format(144 * 3)], # Node 1 has regular activation window | |
[f'-vbparams=testdummy:@144:@{144 * 3}'], # Node 1 has regular activation window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These use this format so that it is easier to backport. 0.21 is using a version python that doesn't support f-strings.
def run_test(self): | ||
self.log.info("Checking -vbparams") | ||
self.stop_node(3) | ||
self.nodes[3].assert_start_raises_init_error(extra_args=["-vbparams=testdummy:@-2:@1"], expected_msg="Error: When one of startheight or timeoutheight is -2, both must be -2") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of repetition here. You could factor this out into a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's about as repetitive if this were a function IMO.
if restart.status is None: | ||
assert "testdummy" not in info["softforks"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
else: | ||
assert_equal(info["softforks"]["testdummy"]["bip8"]["status"], status[restart.node]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I need to retouch
int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } | ||
int Threshold(const Consensus::Params& params) const override { return params.nRuleChangeActivationThreshold; } | ||
int Threshold(const Consensus::Params& params) const override { return params.m_vbits_min_threshold; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The warning message emitted when this threshold is reached is "Warning: unknown new rules activated", whereas now the threshold being reached really means "Warning: unknown new rules may be activated soon" (since 75% doesn't actually indicate that any vbits deployment will activate).
I think it might make sense to remove the m_vbits_min_threshold
parameter from chain params and set the threshold to 75% of nMinerConfirmationWindow here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps for a followup, or if I retouch.
I believe this is the intended state machine: To generate: $ dot -Tpng >states.png
digraph versionbits {
defined [shape=box,label="DEFINED"];
started [shape=box,label="STARTED"];
failed [shape=box,label="FAILED"];
locked [shape=box,label="LOCKED_IN"];
active [shape=box,label="ACTIVE"];
defined -> defined [label="height < begin"];
defined -> started [label="height >= begin"];
started -> started [label="sig < thresh\nheight < end"];
started -> failed [label="sig < thresh\nheight >= end"];
started -> locked [label="sig >= thresh\n"];
locked -> locked [label="height < min_active"];
locked -> active [label="height >= min_active"];
active -> active [label="always"];
failed -> failed [label="always"];
} |
Should |
Closing this for now as #21377 (with some changes) is agreeable to most people. |
Github-Pull: bitcoin#21392 Rebased-From: 2be730c
Co-authored-by: Anthony Towns <aj@erisian.com.au> Github-Pull: bitcoin#21392 Rebased-From: c95287e
Co-authored-by: Anthony Towns <aj@erisian.com.au> Github-Pull: bitcoin#21392 Rebased-From: 8f55573
Github-Pull: bitcoin#21392 Rebased-From: 1dac84b
Github-Pull: bitcoin#21392 Rebased-From: 73d2756
As thresholds are now parameterized, nRuleChangeActivationThreshold is no longer the threshold used for activating new rule changes. Instead it is now only used to warn if there is an unkonwn versionbits deployment. To make this clear, rename to m_vbits_min_threshold and update the comment describing it. Additionally, because this is just a minimum used for a warning, reduce the threshold to 75% so that future soft forks which may have thresholds lower than 95% will still have warnings. Github-Pull: bitcoin#21392 Rebased-From: 50eb7f0
Github-Pull: bitcoin#21392 Rebased-From: 91b2e4b
Github-Pull: bitcoin#21392 Rebased-From: 2e55bce
Implements the block height (BIP 8) based Speedy Trial activation proposal that was discussed on the bitcoin-dev mailing list.
In order to do so, this PR first changes the versionbits (BIP 9) implementation to use heights rather than MTP. Then the minimum activation height parameter is introduced. This also adds the minimum activation height as a third parameter of
-vbparams
. Additionally, the threshold is made a parameter of each deployment so that it can be customized for each deployment. Lastly several unit and functional tests are added.In order to make this easier to backport, the names of functions and classes are largely unchanged (except where necessary for the height based things). This means that some things are misnamed as they refer to BIP 9 even though this is really BIP 8.
This PR borrows 2 commits from #19573 to implement height based versionbits, one commit from #21377 for tests, and 1 commit formerly part of #21380 for the threshold parameterization.