Skip to content

Conversation

mzumsande
Copy link
Contributor

After loading a snapshot, pindexLastCommonBlock is usually already set to some block for existing peers. That means we'd continue syncing the background chain from those peers instead of prioritising the snapshot chain, which defeats the purpose of doing assumeutxo in the first place. Only existing peers are affected by this bug.

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 29, 2024

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

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK fjahr, Sjors, achow101
Stale ACK ryanofsky

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

@fanquake
Copy link
Member

fanquake commented Mar 5, 2024

Note that given that 27 will not ship with mainnet assumeutxo, this isn't a blocker, and a fix can still be backported to 27.x if deemed necessary.

@fjahr fjahr mentioned this pull request Mar 10, 2024
32 tasks
@Sjors
Copy link
Member

Sjors commented Mar 11, 2024

Concept ACK

ac547ea looks reasonable, but it would be nice to somehow add a test for it.

What happens once we've reached the tip and need more peers for background sync? And once background sync is done, is it updated to the tip again?

Would it make sense to introduce pindexLastCommonBackgroundSyncBlock so we have more control over what peers to dedicate to the tip vs background (which might take weeks on some devices)?

@mzumsande
Copy link
Contributor Author

mzumsande commented Mar 11, 2024

ac547ea looks reasonable, but it would be nice to somehow add a test for it.

I tested this syncing on signet, but I'll think about a functional test; I imagine that it would be hard to prevent race conditions in a functional test because we'd:

  • need to first connect to a peer that has the entire chain and start syncing the background chain
  • then load the snapshot (at the same time the peer would continue syncing the background chain)
  • then check that we switch to requesting from the snapshot chain (while the bg chain hopefully hasn't finished downloading yet)

It might be possible though if that peer was a P2PConnection() (that could stall / withhold certain blocks) instead of a real node.

What happens once we've reached the tip and need more peers for background sync? And once background sync is done, is it updated to the tip again?

Would it make sense to introduce pindexLastCommonBackgroundSyncBlock so we have more control over what peers to dedicate to the tip vs background (which might take weeks on some devices)?

We have this logic in SendMessages: It first tries FindNextBlocksToDownload meant for the active chainstate, and, if it doesn't fill up the vToDownload buffer (e.g. because we are at / near the tip), then calls TryDownloadingHistoricalBlocks specifically for the background chainstate. I think that this existing separation should work well, the problem that this PR fixes is that FindNextBlocksToDownload would pick historical blocks from the background chain when it shouldn't because higher-prio blocks are available.

@Sjors
Copy link
Member

Sjors commented Mar 12, 2024

I can see how it would be difficult to test, indeed.

I encountered this scenario while testing #29370 on signet. All my peers were only downloading the background state. I only got headers for the tip chain, but it didn't progress. The workaround there was to turn the network off and on, which is consistent with your observation that only existing peers are affected.

utACK ac547ea

@fjahr
Copy link
Contributor

fjahr commented Mar 25, 2024

The code and reasoning look good to me and I have been trying to write a test but I am currently unable to reproduce the behavior described before the change, i.e. to make the test fail on master.

Here is the test so far: fjahr@478fb28

This now does fail on master but only because it also checks for the debug log HERE I have added. This log confirms that the test does trigger the behavior change, i.e. on master the last common block is set once, after the change here it is set twice within FindNextBlockToDownload. But, of course, we would rather like to have the test fail at the asserts at the end but that is not the case so far.

@mzumsande do you have an idea why this doesn't reproduce the behavior you have seen on master?

@mzumsande
Copy link
Contributor Author

mzumsande commented Mar 25, 2024

@mzumsande do you have an idea why this doesn't reproduce the behavior you have seen on master?

I tested your branch, and I see the same - this is really interesting, and it works differently than I thought when I opened the PR!

I think the following happens in PeerManagerImpl::FindNextBlocks (after being called from FindNextBlocksToDownload()):
We process up to 1024 blocks ahead of the current pindexWalk:

int nWindowEnd = state->pindexLastCommonBlock->nHeight + BLOCK_DOWNLOAD_WINDOW;

But due to the lines

bitcoin/src/net_processing.cpp

Lines 1497 to 1502 in 2102c97

if (pindex->nStatus & BLOCK_HAVE_DATA || (activeChain && activeChain->Contains(pindex))) {
if (activeChain && pindex->HaveNumChainTxs()) {
state->pindexLastCommonBlock = pindex;
}
continue;
}

we never ask for the blocks below the snapshot block here: Even though we don't have their data, they are now in the active chain, so they are skipped instead.
In the test, we immediately ask for the blocks after the snapshot instead, which is exactly the desired behavior.

So why did I observe a different behavior on signet?
I think it only works like this in the test because there, the snapshot is just a 100 blocks away from pindexLastCommonBlock.

If this wasn't the case, we'd request nothing in FindNextBlocksToDownload(), only move our pindexLastCommonBlock ahead by a maximum of 1024. Then we have capacity for this peer left and would actually ask for the historical blocks via TryDownloadingHistoricalBlocks().

So I think what would actually happen on mainnet and signet is that each time we can process a block request with an existing peer, we'd move pindexLastCommonBlock 1024 blocks ahead, and then request historical blocks - until pindexLastCommonBlock has reached the snapshot. So we'd continuing downloading the background chain for a while (a few minutes?) with existing peers until we switch over to the snapshot chain (instead of never switching over as I originally thought when opening this PR).

Still, I think that this behavior is not great, and the fix is an improvement - not only because we start downloading the snapshot chain immediately, we also do much less iterating in FindNextBlocksToDownload , moving pindexLastCommonBlock ahead without actually downloading anything.

@fanquake fanquake removed this from the 27.0 milestone Apr 16, 2024
@fjahr
Copy link
Contributor

fjahr commented May 30, 2024

tACK ac547ea

I reproduced the issue on current master with signet and confirmed that it is fixed with the change here (three times actually with slightly different logs). Based on reading the code and some custom logging I agree with @mzumsande that his description above is what is indeed happening. Based on this I am fine to add this without a test.

@fjahr
Copy link
Contributor

fjahr commented May 30, 2024

@ryanofsky would be really great to get your review here when you get the chance :)

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Sorry for not seeing this earlier. I think I found a potential problem with this change, so I left a comment below and would be curious to find out more.

if (state->pindexLastCommonBlock == nullptr) {
std::optional<int> snapshot_height{m_chainman.GetSnapshotBaseHeight()};
if (state->pindexLastCommonBlock == nullptr ||
(snapshot_height.has_value() && state->pindexLastCommonBlock->nHeight < snapshot_height.value())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

In commit "p2p: For assumeutxo, download snapshot chain before background chain" (ac547ea)

It seems like the pindexLastCommonBlock->nHeight < snapshot_height check is not strict enough because the snapshot block might not necessarily be an ancestor the peer's best known block.

In this case, the pindexLastCommonBlock assignments on line 1406 and 1411 will reset it back to the fork point between the two nodes each time FindNextBlocksToDownload is called. And if the peer's best block is more than BLOCK_DOWNLOAD_WINDOW blocks away from the current chain, the node will never be able to download enough blocks to reach it and validate it. This means if the snapshot is not on the best chain, the node may be unable to sync to a better chain.

Suggestion to fix would be:

--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1487,12 +1487,14 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co
         return;
     }
 
-    std::optional<int> snapshot_height{m_chainman.GetSnapshotBaseHeight()};
+    // If pindexLastCommonBlock has not been set yet, set it now. Also reset it
+    // if an assumetxo snapshot has been loaded and it is an ancestor of the
+    // snapshot, so only blocks after the snapshot will be downloaded.
+    const CBlockIndex* snap_base{m_chainman.GetSnapshotBaseBlock()};
     if (state->pindexLastCommonBlock == nullptr ||
-        (snapshot_height.has_value() && state->pindexLastCommonBlock->nHeight < snapshot_height.value())) {
+        (snap_base && snap_base->GetAncestor(state->pindexLastCommonBlock->nHeight) == state->pindexLastCommonBlock)) {
         // Bootstrap quickly by guessing a parent of our best tip is the forking point.
         // Guessing wrong in either direction is not a problem.
-        // Also applies after loading a snapshot, when we want to prioritise loading the snapshot chain and therefore need to skip ahead.
         state->pindexLastCommonBlock = m_chainman.ActiveChain()[std::min(state->pindexBestKnownBlock->nHeight, m_chainman.ActiveChain().Height())];
     }
 

Copy link
Contributor Author

@mzumsande mzumsande Jun 11, 2024

Choose a reason for hiding this comment

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

This means if the snapshot is not on the best chain, the node may be unable to sync to a better chain.

I've thought about this for a while, and I'm not sure if that problem is really being fixed by your suggestion for the following (slightly philosophical) reasons:

  • Accepting a snapshot means that we've created a new chainstate with its Active Tip being set to that block.
  • That is a commitment, we can't just reorg away from it to another chain that doesn't contain the snapshot block, even if such a chain existed - at least not until the background sync has finished (we'd need the full blocks below the snapshot), and once that has happened, the snapshot chainstate gets deleted.
  • If all of our peers are on a better chain not containing the Snapshot, it seems to me that we are in a hopeless situation anyway: With the current patch we might download no blocks from those peers, with your suggestion we'd download useless blocks that can't be connected - I'm not sure why one is better than the other.

So I think that the premise that we could get into this situation should be rejected:
If a valid snapshot was provided and put into a release, it should only be possible if there was a massive reorg of multiple thousands of blocks (and we'd also need to have accepted header chains ending in both chaintips from different peers). I'd say that in this hypothetical situation, AssumeUtxo is not at the top of the list of our concerns - bitcoin would have far bigger problems.

Without a massive reorg, there is the alternative that the snapshot provided was bad somehow. But then (apart from the fact that it shouldn't have been hardcoded into chainparams), it also shouldn't have been loaded in the first place because its header should not have been accepted to our block index with our header DoS protections.

So all in all I think that once we've accepted a snapshot, we've made a commitment and have to treat it as part of the best chain, at least until background sync has finished.

Copy link
Contributor

@ryanofsky ryanofsky Jun 12, 2024

Choose a reason for hiding this comment

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

re: #29519 (comment)

Good points. My suggested change was premised on the false assumption that the node would be able to seamlessly reorg to the most-work chain. But that's not true because of missing undo data.

Your philosophical point is interesting. I believe that:

  • Loading snapshots should not affect how nodes come to consensus, it should only affect what order they download blocks, and help them compute the latest UTXO sets sooner.

And you seem to be suggesting:

  • Loading a snapshot should force a node to only consider chains that include the snapshot block and ignore other chains, even if they have more work.

On this PR more specifically:

When I made my suggested change, I was forgetting that we don't have any undo data for the snapshot block and blocks before it, so unfortunately it is not possible for a node to follow the most-work chain when a snapshot is loaded until all the preceding blocks are downloaded.

Given this fact, I think the best thing FindNextBlocksToDownload can do when an unvalidated snapshot is loaded is to ignore peers on other chains not containing the snapshot block, until it actually has the blocks and undo data that would be needed to validate other chains. I think the current PR is not exactly doing this though. It looks like it will still download up to BLOCK_DOWNLOAD_WINDOW useless blocks on the peer's chain, starting at the fork point between the two chains, which is not great. It will also not download later blocks after snapshot is validated, when undo data needed to enable a re-org becomes available.


On the broader approach to downloading blocks when a snapshot is loaded:

I'd agree now that the only sensible block downloading behavior for an unvalidated snapshot chainstate is to only download blocks after the snapshot (due to lack of undo data before then) and ignore any peers whose best blocks don't descend from the snapshot block.

But it is less clear which blocks should be downloaded and attached to the older chainstate, and whether it should target the most-work block or the snapshot block. Since d43a1f1 from #27746 it targets the snapshot block, but was not doing that originally. Comparing the two alternatives:

  • Targeting the most-work block seems like a purer approach that would probably simplify the code and remove special cases, and would also have the benefit of the node syncing to the most-work chain faster, with less wasted work if it the chain turns out to be valid.

  • Targeting the snapshot block is also very reasonable, and is what the code is doing currently, and guarantees the snapshot will be validated even if it is not used, and could be a practical defense against eclipse attacks, or fork chains that appear to have more work but turn out to be invalid.


On whether we should care about any of this:

I don't think we need to care about these design choices too much, since everything above is referring to a case we don't expect to encounter, where there is a possibly-valid chain of blocks, not including the snapshot block, that has more work than any known chain including the snapshot block. The only time when we should see this is when there is a major fork with a lot of work behind it.

However, I don't think "the premise that we could get into this situation should be rejected." That seems like it is going too far. We should put some thought into how code will behavior in presence for forks for practical reasons, and also to inform design of the code and to write it in a clean way that isn't full of assumptions and special cases.

I do think, as long as we are hardcoding snapshot hashes in the source code, we can reject premise that snapshot chainstate turns out to be invalid, and just refuse to run in that case. But the case where the snapshot is valid, but the snapshot block is not on the most-work chain seems different and worth thinking about.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And you seem to be suggesting:
Loading a snapshot should force a node to only consider chains that include the snapshot block and ignore other chains, even if they have more work.

This was meant more as a description of the status quo than a description of the ideal world. I think that with today's logic we don't really have another choice but to do so - temporarily until the background sync has finished.

but the snapshot block is not on the most-work chain seems different and worth thinking about

I agree with that.Some possible ways to deal with that:

1.) Tighten the criteria for snapshot acceptance: We could reject loading the snapshot if the snapshot block is not a predecessor of m_best_header. Currently, we only check that it has more work than the ActiveTip. This is easy to implement, though it doesn't cover the case where we only learn about the better chain after the snapshot has been loaded.

2.) After a snapshot is accepted and we detect that the snapshot is no longer on the most-work chain we know of, we could abort syncing the snapshot, delete the chainstate and go back to normal sync mode. This sounds more complicated to implement, and I'm not convinced it's worth it.

It looks like it will still download up to BLOCK_DOWNLOAD_WINDOW useless blocks on the peer's chain, starting at the fork point between the two chains, which is not great.

That's true. I will try to adjust the PR to deal with that. Since I find the logic in FindNextBlocksToDownload / TryDownloadingHistoricalBlocks / FindNextBlocks rather hard to understand, I would like to reproduce the situation in a test (at least locally, sounds like timing issues could make this hard to include in a PR) to be more confident that this is really how it works, which could take a few days.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have now added a first commit that avoids downloading blocks not on the snapshot chain completely.
Local testing revealed that not only are we unable to reorg to a more-work chain, we also won't fail gracefully: On master, we would still download the blocks and attempt to reorg, but then the node would just crash.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Code review ACK ac547ea. This is an improvement over the status quo in the normal case, though it seems like it introduces some odd behavior if snapshot block is not on the most work chain, as described in other comments. I think it would be nice to clean the code up more and clarify what is trying to do, instead of merging this change as it is. But it would also be ok to merge in its current form, if you prefer that.

@mzumsande
Copy link
Contributor Author

I think it would be nice to clean the code up more and clarify what is trying to do, instead of merging this change as it is.

I will look into improving this (see thread above) in the next days, so hold off merging please.

@mzumsande mzumsande force-pushed the 202202_fix_assumeutxo_block_download branch from ac547ea to e977c69 Compare June 14, 2024 15:37
@mzumsande
Copy link
Contributor Author

ac547ea to e977c69:
Added a commit that avoids downloading blocks not on the snapshot chain (see discussion above).
Also made small changes to the (now) second commit using snap_base instead of GetSnapshotBaseHeight(), and reworked the documentation.

Copy link
Contributor

@fjahr fjahr left a comment

Choose a reason for hiding this comment

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

tACK e977c69

I reviewed the new changes and re-tested the behavior on Signet.

I mostly agree with @mzumsande above and will post some more thoughts on #30288 rather than here since the conversation seems to have moved over there.

So, since I agree this is not something we should encounter this is not a blocker, but just to confirm my understanding: with the current state of the change, if there was an actual other best chain deeper than the snapshot discovered, our node would get stuck because all other nodes would reorg to the best chain and then we would not request blocks from any of our peers, right?

@DrahtBot DrahtBot requested review from ryanofsky and Sjors June 18, 2024 14:25
@mzumsande
Copy link
Contributor Author

if there was an actual other best chain deeper than the snapshot discovered, our node would get stuck because all other nodes would reorg to the best chain and then we would not request blocks from any of our peers, right?

yes, but as you mentioned only if all peers were on that other chain. Whereas the behavior on master would be to download some blocks and then crash with an assert trying to reorg to the new chain - note that this would also happen if the other chain was invalid (for reasons we'd only find out during connecting the blocks) and even if there was just a single peer feeding us that chain.

@fjahr
Copy link
Contributor

fjahr commented Jun 19, 2024

yes, but as you mentioned only if all peers were on that other chain.

It may be worth logging something every time we ignore a peer that is on another chain. In case we encounter this (very unlikely) scenario it would help identify the issue faster and it could be an interesting data point alone to know if anyone ever sees this at all.

@Sjors
Copy link
Member

Sjors commented Jul 23, 2024

In the unlikely event of such a deep reorg and a lack of peers on the old side, I think it's acceptable if the user needs to restart their node in order to follow the new longest headers.

If the best chain of the peer doesn't include the snapshot
block, it is futile to download blocks from this chain,
because we couldn't reorg to it. We'd also crash
trying to reorg because this scenario is not handled.
After loading a snapshot, pindexLastCommonBlock is usually already set
to some block for existing peers. That means we'd continue syncing the
background chain from those peers instead of prioritising the snapshot
chain, which defeats the purpose of doing assumeutxo in the first place.
Only existing peers are affected by this bug.
@mzumsande mzumsande force-pushed the 202202_fix_assumeutxo_block_download branch from e977c69 to 49d569c Compare August 6, 2024 20:05
@mzumsande
Copy link
Contributor Author

It may be worth logging something every time we ignore a peer that is on another chain.

e977c69 to 49d569c:
Added a log message and slightly reworded a comment.

@fjahr
Copy link
Contributor

fjahr commented Aug 6, 2024

re-ACK 49d569c

Only trivial changes addressing comments since my last review, per diff: https://github.com/bitcoin/bitcoin/compare/e977c698f97ac9bba30e4e3837f41721841c28c4..49d569cb1fdd62a9da8dff51dccaf4680fe3d0eb

Copy link
Member

@Sjors Sjors left a comment

Choose a reason for hiding this comment

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

tACK 49d569c

Stalling fix

I tested (rebased on master) that without the second commit, when loading a testnet3 snapshot, and only having outbound peers, many peers keep downloading background sync blocks. A few newly connected peers do contribute to the tip.

With the second commit synced_blocks jumps to the snapshot height. Once the tip is reached (takes a while due to the loppocalypse and because the snapshot is a year old), and background sync needs to take over, synced_blocks does not jump back to the background sync height. However the inflight blocks are in the expected range for background sync.

Reorg fix

I tested the reorg scenario as well. First I invalidated block 2,870,000 near the tip and CPU mined a couple of blocks (by messing with the timestamp). I then made a snapshot for it which I hardcoded in the chain params.

I then spun up a fresh node, and first connected it to the previous node to get the right header. I then connected it to random nodes to find the real chain. Trying to load the fake snapshot runs into #30320, so I reverted that to simulate the scenario of a reorg that happens during snapshot handling.

With this PR I get the Not downloading blocks from peer log messages. The sync completely stalls, and their synced_blocks stays far below the snapshot height. After a few minutes it finds new outbound peers, but for those synced_blocks and synced_headers stays -1.

When I connect to my stale node the tip updates. The background sync then continues. Once that completes (I had to restart due to an unrelated crash) it winds back for a reorg, but fails:

2024-08-07T14:24:12Z [error] ReadBlockFromDisk: OpenBlockFile failed for FlatFilePos(nFile=-1, nPos=0)
2024-08-07T14:24:12Z [error] DisconnectTip(): Failed to read block
2024-08-07T14:24:12Z [error] A fatal internal error occurred, see debug.log for details: Failed to disconnect block.
2024-08-07T14:24:12Z [error] ProcessNewBlock: ActivateBestChain failed (Failed to disconnect block.)

It's a pruned node, but that's not the reason - it simply doesn't have the undo data yet for blocks below the snapshot. Restarting just causes it to stop again. Certainly better than an assert. A followup PR could nuke the chainstate_snapshot dir in this scenario. Doing so manually makes it continue.

Reorg behaviour before this PR

Without the commits here, once the snapshot is loaded it stays at the snapshot height for a while. Both the slate and real chaintips are headers-only. This is expected stalling behavior.

I then restart the node. The chaintips remain headers-only for a while. Once I manually connect it to my stale node, it immediately syncs the fake tip. It doesn't attempt to reorg.

Worse, when I restart the node it won't do any background sync until I connect it to the node with the fake chaintip. It would only download from that peer.

When background sync finishes I get the same behaviour: it tries to reorg and fails to disconnect block 2,870,000.

Is there another crash you were referring to? The first commit message says:

We'd also crash trying to reorg because this scenario is not handled.

That could be clarified a bit. The scenario still isn't handled, but we fail a bit later - and not with an assert.

Update: I do notice a difference (improvement). Before this PR after I delete the snapshot chainstate, it just syncs from the snapshot height to the tip - completely forgetting that it still needs to do a background sync. I'm a bit puzzled by this difference.

// so downloading blocks from it would be futile.
const CBlockIndex* snap_base{m_chainman.GetSnapshotBaseBlock()};
if (snap_base && state->pindexBestKnownBlock->GetAncestor(snap_base->nHeight) != snap_base) {
LogDebug(BCLog::NET, "Not downloading blocks from peer=%d, which doesn't have the snapshot block in its best chain.\n", peer.m_id);
Copy link
Member

Choose a reason for hiding this comment

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

7a88551: although this scenario is worthy of LogWarning, we have to be careful to avoid a log spam attack. So for now LogDebug is fine.

@mzumsande
Copy link
Contributor Author

mzumsande commented Aug 7, 2024

Is there another crash you were referring to? The first commit message says:
We'd also crash trying to reorg because this scenario is not handled.

I didn't mean an assert with that, just a shutdown with a fatal error. I tested this in a functional test, if I recall correctly the following happened:

After loading the snapshot, I would connect a peer with a higher-work tip branching off below the snapshot height. On master we'd download blocks from this peer, and as soon as we'd have a an alternative block with more work in setBlockIndexCandidates, we would try to reorg to that (below the snapshot height) - but shut down with a fatal error because the undo data wasn't available.

@Sjors
Copy link
Member

Sjors commented Aug 8, 2024

What you recall sounds similar to what still happens. But it seems this PR makes recovery from that failure easier (just delete the snapshot dir). I just don't fully understand why and it's very time consuming to reproduce on testnet3.

@achow101 achow101 added this to the 28.0 milestone Aug 8, 2024
@achow101
Copy link
Member

achow101 commented Aug 9, 2024

ACK 49d569c

@achow101 achow101 merged commit c2d15d9 into bitcoin:master Aug 9, 2024
16 checks passed
@mzumsande mzumsande deleted the 202202_fix_assumeutxo_block_download branch August 12, 2024 01:54
Johnnygatvol

This comment was marked as spam.

vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Apr 1, 2025
…ore background chain

49d569c p2p: For assumeutxo, download snapshot chain before background chain (Martin Zumsande)
7a88551 p2p: Restrict downloading of blocks for snapshot chain (Martin Zumsande)

Pull request description:

  After loading a snapshot, `pindexLastCommonBlock` is usually already set to some block for existing peers. That means we'd continue syncing the background chain from those peers instead of prioritising the snapshot chain, which defeats the purpose of doing assumeutxo in the first place. Only existing peers are affected by this bug.

ACKs for top commit:
  fjahr:
    re-ACK 49d569c
  achow101:
    ACK 49d569c
  Sjors:
    tACK 49d569c

Tree-SHA512: 0eaebe1c29a8510d5ced57e14c09b128ccb34b491692815291df68bf12e2a15b52b1e7bf8d9f34808904e7f7bc20f70b0ad0f7e14df93bbdf456bd12cc02a5d2
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Aug 11, 2025
Summary:
If the best chain of the peer doesn't include the snapshot
block, it is futile to download blocks from this chain,
because we couldn't reorg to it. We'd also crash
trying to reorg because this scenario is not handled.

This is a partial backport of [[bitcoin/bitcoin#29519 | core#29519]]
bitcoin/bitcoin@7a88551

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D18455
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Aug 11, 2025
Summary:
After loading a snapshot, pindexLastCommonBlock is usually already set
to some block for existing peers. That means we'd continue syncing the
background chain from those peers instead of prioritising the snapshot
chain, which defeats the purpose of doing assumeutxo in the first place.

This is a backport of [[bitcoin/bitcoin#29519 | core#29519]]
bitcoin/bitcoin@49d569c
Depends on D18455

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D18456
@bitcoin bitcoin locked and limited conversation to collaborators Aug 13, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

8 participants