-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Fix tiebreak when loading blocks from disk (and add tests for comparing chain ties) #29640
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
base: master
Are you sure you want to change the base?
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29640. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. 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. |
43873be
to
18820ac
Compare
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. |
a705e00
to
77e1c45
Compare
8225bd6
to
f95e896
Compare
f95e896
to
e4cf8d0
Compare
Rebased to drop the custom log fix in favor of a more generic solution (#29640 (comment)) |
e4cf8d0
to
2cdc369
Compare
🚧 At least one of the CI tasks failed. HintsMake sure to run all tests locally, according to the documentation. The failure may happen due to a number of reasons, for example:
Leave a comment here, if you need help tracking down a confusing failure. |
2cdc369
to
c6ca2a1
Compare
Before this, if we had two (or more) same work tip candidates and restarted our node, it could be the case that the block set as tip after bootstrap didn't match the one before stopping. That's because the work and `nSequenceId` of both block will be the same (the latter is only kept in memory), so the active chain after restart would have depended on what tip candidate was loaded first. This makes sure that we are consistent over reboots. Github-Pull: bitcoin#29640 Rebased-From: c8f5e62
Make it easier to follow what the values come without having to go over the comments, plus easier to maintain Github-Pull: bitcoin#29640 Rebased-From: c7f9061
Adds tests to make sure we are consistent on activating the same chain over a node restart if two or more candidates have the same work when the node is shutdown Github-Pull: bitcoin#29640 Rebased-From: 177d07f
src/chain.h
Outdated
@@ -35,6 +35,9 @@ static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; | |||
* MAX_FUTURE_BLOCK_TIME. | |||
*/ | |||
static constexpr int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; | |||
//! Init values for CBlockIndex nSequenceId when loaded from disk |
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.
Nit: The extra whitespace at the beginning should be removed.
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.
Covered in 3869469
assert_equal(node.getbestblockhash(), blocks[2].hash) | ||
|
||
self.log.info('Send parents B3-B4 of B8-B10 in reverse order') | ||
peer.send_blocks_and_test([blocks[4]], node, success=False, force_send=True) |
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 would be an easy doc fix, something like:
- - if success is True: assert that the node's tip advances to the most recent block
- - if success is False: assert that the node's tip doesn't advance
+ - if success is True: assert that the node's tip is the last block in blocks at the end of the operation.
+ - if success is False: assert that the node's tip isn't the last block in blocks at the end of the operation
Will submit separately if it does not get picked up here.
177d07f
to
3cb689a
Compare
Thanks for reviewing @TheCharlatan and @furszy. I rebased the code and addressed the outstanding comments. |
blocks[-1].solve() | ||
|
||
# Send blocks and test the last one is not connected | ||
self.log.info('Send A1 and A2. Make sure than only the former connects') |
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.
Possible typos and grammar issues:
In feature_chain_tiebreaks.py: “Make sure than only the former connects” -> “Make sure that only the former connects” [‘than’ should be ‘that’]
In feature_chain_tiebreaks.py: “# Restart and check enough times to this to eventually fail if the logic is broken” -> “# Restart and check this enough times to eventually fail if the logic is broken” [‘to this’ is misplaced and hinders comprehension]
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.
Fixed in 8d73371
3cb689a
to
a4b9381
Compare
I think all feedback has been addressed @mzumsande @sipa @furszy @TheCharlatan @maflcko Please let me know if there's anything else pending or if anything doesn't make sense. |
a4b9381
to
fb31512
Compare
Rebased to switch from the old (removed) |
Before this, if we had two (or more) same work tip candidates and restarted our node, it could be the case that the block set as tip after bootstrap didn't match the one before stopping. That's because the work and `nSequenceId` of both block will be the same (the latter is only kept in memory), so the active chain after restart would have depended on what tip candidate was loaded first. This makes sure that we are consistent over reboots.
Make it easier to follow what the values come without having to go over the comments, plus easier to maintain
Adds tests to make sure we are consistent on activating the same chain over a node restart if two or more candidates have the same work when the node is shutdown
It's not true that if success=False the tip doesn't advance. It doesn'test advance to the provided tip, but it can advance to a competing one
fb31512
to
0465574
Compare
Github-Pull: bitcoin#29640 Rebased-From: ab145cb
Github-Pull: bitcoin#29640 Rebased-From: 5370bed
Before this, if we had two (or more) same work tip candidates and restarted our node, it could be the case that the block set as tip after bootstrap didn't match the one before stopping. That's because the work and `nSequenceId` of both block will be the same (the latter is only kept in memory), so the active chain after restart would have depended on what tip candidate was loaded first. This makes sure that we are consistent over reboots. Github-Pull: bitcoin#29640 Rebased-From: 8b91883
Make it easier to follow what the values come without having to go over the comments, plus easier to maintain Github-Pull: bitcoin#29640 Rebased-From: 18524b0
Adds tests to make sure we are consistent on activating the same chain over a node restart if two or more candidates have the same work when the node is shutdown Github-Pull: bitcoin#29640 Rebased-From: 09c95f2
This PR grabs some interesting bits from #29284 and fixes some edge cases in how block tiebreaks are dealt with.
Regarding #29284
The main functionality from the PR was dropped given it was not an issue anymore, however, reviewers pointed out some comments were outdated #29284 (comment) (which to my understanding may have led to thinking that there was still an issue) it also added test coverage for the aforementioned case which was already passing on master and is useful to keep.
New functionality
While reviewing the superseded PR, it was noticed that blocks that are loaded from disk may face a similar issue (check #29284 (comment) for more context).
The issue comes from how tiebreaks for equal work blocks are handled: if two blocks have the same amount of work, the one that is activatable first wins, that is, the one for which we have all its data (and all of its ancestors'). The variable that keeps track of this, within
CBlockIndex
isnSequenceId
, which is not persisted over restarts. This means that when a node is restarted, all blocks loaded from disk are defaulted the samenSequenceId
: 0.Now, when trying to decide what chain is best on loading blocks from disk, the previous tiebreaker rule is not decisive anymore, so the
CBlockIndexWorkComparator
has to default to its last rule: whatever block is loaded first (has a smaller memory address).This means that if multiple same work tip candidates were available before restarting the node, it could be the case that the selected chain tip after restarting does not match the one before.
Therefore, the way
nSequenceId
is initialized is changed to: