Skip to content

Conversation

sr-gi
Copy link
Member

@sr-gi sr-gi commented Mar 12, 2024

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 is nSequenceId, which is not persisted over restarts. This means that when a node is restarted, all blocks loaded from disk are defaulted the same nSequenceId: 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:

  • 0 for blocks that belong to the previously known best chain
  • 1 to all other blocks loaded from disk

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 12, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29640.

Reviews

See the guideline for information on the review process.

Type Reviewers
Stale ACK sipa, mzumsande, furszy

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32885 (Cache m_cached_finished_ibd where SetTip is called. by pstratem)

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.

@sr-gi sr-gi changed the title Adds missing test to chain ties (CBlockIndexWorkComparator) Fixes tiebreak when loading blocks from disk and adds missing test to chain ties (CBlockIndexWorkComparator) Mar 14, 2024
@sr-gi sr-gi changed the title Fixes tiebreak when loading blocks from disk and adds missing test to chain ties (CBlockIndexWorkComparator) Fix tiebreak when loading blocks from disk (and add tests for comparing chain ties) Mar 14, 2024
@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from 43873be to 18820ac Compare March 14, 2024 20:52
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/22675498262

@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch 3 times, most recently from a705e00 to 77e1c45 Compare March 15, 2024 21:09
@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch 2 times, most recently from 8225bd6 to f95e896 Compare March 22, 2024 15:23
@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from f95e896 to e4cf8d0 Compare March 25, 2024 07:44
@sr-gi
Copy link
Member Author

sr-gi commented Mar 25, 2024

Rebased to drop the custom log fix in favor of a more generic solution (#29640 (comment))

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/26038919395

Hints

Make sure to run all tests locally, according to the documentation.

The failure may happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from 2cdc369 to c6ca2a1 Compare July 24, 2024 15:08
@sr-gi
Copy link
Member Author

sr-gi commented Jul 24, 2024

Rebased to deal with CI failing.

c76f4c6 has been amended to include __file__ in the ChainTiebreaksTest constructor, as required by any subclass of BitcoinTestFramework since #30463

luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Jun 6, 2025
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
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Jun 6, 2025
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
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Jun 6, 2025
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
Copy link
Contributor

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.

Copy link
Member Author

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)
Copy link
Contributor

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.

@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from 177d07f to 3cb689a Compare June 20, 2025 14:59
@sr-gi
Copy link
Member Author

sr-gi commented Jun 20, 2025

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')
Copy link
Member

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]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8d73371

@sr-gi
Copy link
Member Author

sr-gi commented Jul 8, 2025

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.

@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from a4b9381 to fb31512 Compare July 21, 2025 14:34
@sr-gi
Copy link
Member Author

sr-gi commented Jul 21, 2025

Rebased to switch from the old (removed) CBlock::hash property to the recently introduced hash_int and hash_hex

sr-gi and others added 6 commits July 28, 2025 10:11
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
@sr-gi sr-gi force-pushed the 202403-block-tiebreak branch from fb31512 to 0465574 Compare July 28, 2025 14:15
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jul 31, 2025
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jul 31, 2025
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jul 31, 2025
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
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jul 31, 2025
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
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jul 31, 2025
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants