-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Walk pindexBestHeader back to ChainActive().Tip() if it is invalid #16974
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
Walk pindexBestHeader back to ChainActive().Tip() if it is invalid #16974
Conversation
@@ -1422,6 +1426,8 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c | |||
CheckForkWarningConditions(); | |||
} | |||
|
|||
// Same as InvalidChainFound, above, except not called directly from InvalidateBlock, | |||
// which does its own setBlockIndexCandidates manageent. |
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: Should be "management" :)
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
Instead of keeping pindexBestHeader set to the best header we've ever seen, reset it back to our validated tip if we find an ancestor of it turns out to be invalid. While the name is now a bit confusing, this matches much better with how it is used in practice, see below. Further, this opens up more use-cases for it in the future, namely aggressively searching for new peers in case we have discovered (possibly via some covert channel) headers which we do not know to be invalid, but which we cannot find block data for. Places pindexBestHeader is used: * Various GUI displays of the best header and getblockchaininfo["headers"], I don't think changing this is bad, and if anything this is less confusing in the presence of an invalid block. * IsCurrentForFeeEstimation(): If anything I think ensuring pindexBestHeader isn't some crazy invalid chain is better than the alternative, even in the case where you are rejecting the current chain due to hardware error (since hopefully in that case you won't get any new blocks anyway). * ConnectBlock assumevalid checks: We use pindexBestHeader to check that the block we're connecting leads to something with nMinimumChainWork (preventing a user-set assumevalid from having bogus work) and that the block we're connecting leads to pindexBestHeader (I'm not too worried about this one - it's nice to "disable" assumevalid if we have a long invalid headers chain, but I don't see it as a critical protection). * BlockRequestAllowed() uses pindexBestHeader as its target to ensure the requested block is within a month of the "current chain". I don't think this is a meaningful difference, if we're rejecting the current tip we're trivially fingerprintable anyway, and if the chain really does have a bunch of invalid crap near the tip, using the best not-invalid header is likely a better criteria. * ProcessGetBlockData uses pindexBestHeader as the "current chain" definition of whether a block request is "historical" for the purpose of bandwidth limiting. Similarly, I don't see why this is a meaningful change. * We use pindexBestHeader for requesting missing headers on receipt of a headers/compact block message or block inv as well as for initial getheaders. I think this is definitely wrong, using the best not-invalid header for such requests is much better. * We use pindexBestHeader to define the "current chain" for deciding when we're close to done with initial headers sync. I don't think this is a meaningful change. * We use pindexBestHeader to decide if initial headers sync has timed out. If we're rejecting the chain due to hardware error this may result in additional cases where we ban a peer, but this is already true, so I think its fine.
64376bc
to
0a50019
Compare
utACK, apart from verifying how the GUI will handle the situation of pindexBestHeader going backward. I can try to take a look at that later. (One observation that suggests that this ought to be a safe change is that in |
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 0a50019
Let's say you have headers topology :
B - C
/
A
\
D - E
If pindexBestHeader
points to E, if D or E are invalid blocks, block validation sequence is going to reset pointer to A until next header connection, after then it would point to C. Right now, pointer stays dirty on E until next header connection driven by AcceptBlockHeader
logic, after then it would point also to C. So contrary of what I thought at first, this change it's not an event anticipation on what header connection logic would have done but a real (slight) behavior modif.
Does this change have meaningful consequences ?
- GUI: getHeaderTip is used to display best known header, so yes that's a change but in anycase it's better to display a non-invalid one
- IsCurrentForFeeEstimation: right now we wouldn't consider for fee estimation txn even if we may not be effectively behind due to our best header being in fact invalid (and assuming that's only header branch we have). With this change, we are going to consider txn for estimation while we may be behind due to other non-invalid header branch being hidden by proven invalidity of our previous best header. That's a change in the set of wrong-considered transactions but this one is more liberal (and seems better to include near-to-tip txn than dropping them due to invalid chain header)
- assumevalid : if block failed before
hashAssumeValid
, pointer is going to be back to current valid tip, which is going to be part of the assumevalid chain but starting from then a new validation is going to start on another non-assumevalid branch on selection of whichpindexBestHeader
doesn't interfer even if still dirty. So no change here. - NotifyHeaderTip: called after
AcceptBlockHeader
inProcessNewBlockHeader
and beforeActivateBestChain
inProcessNewBlock
. Shouldn't see the difference - BlockRequestAllowed : if someone send you an invalid block, pointer is going to be reset to current tip and effectively offer a short grace delay compare to what it's now but this edge is flattened at next header announcement (and variance of their announcement is not meaningful compare to scale of STALE_RELAY_AGE_LIMIT)
- ProcessGetBlockData : same analysis than previously, a bit more meaningful because HISTORICAL_BLOCK_AGE is shorter but it's not a sound attack to produce expensive block to gain potentially bandwidth beyond rate-limiting cap for a super slight delay from other peers
- ProcessHeaderMessage: seems better to ask for headers from a valid starting point than a proven-invalid one
- inv.type == MSG_BLOCK : same analysis than previously
- strCommand == NetMsgType::CMPCTBLOCK : same analysis than previously
- on block sync : going to lock us a bit more in initial sync but right now we would exit based on proven-invalid block so better
nHeadersSyncTimeout
: invalid blocks is going to push backwardGetBlockTime
so may increase slightly timeout but is this going an edge compare to what an attacker could do withnHeadersSyncTimeout
? Don't think so
A more important scenario than the one you described is:
In the current model, if D is found to be invalid, even after we've connected to C, pindexBestHeader will still point to F, which is nonsense. |
I see no problem with changing the definition of It feels a bit strange to only implement this as a reset when an invalid block is found, as that does not actually maintain the "best not-known-to-be-invalid" invariant at all times. Given the relative unimportance of An alternative (open for consideration, I'm not convinced this is necessary) is to build something similar to |
Right, if we ever have a need for definitely-best-header, we can revive #12138 (which I think worked as-is, its just a nontrivial amount of code). |
Another alternative is to add the best header for each @TheBlueMatt how hard is to add a test that would fail without this change? |
@promag I don't think that helps - setBlockIndexCandidates is only for things for which we have the data, ie not-just-headers. |
ACK 0a50019 Thanks for the detailed comments and evaluation on the usages, I also grepped myself to make sure no important usages where missed. Here is a super simple test for the behavior change in |
ACK 0a50019 |
… is invalid 0a50019 Walk pindexBestHeader back to ChainActive().Tip() if it is invalid (Matt Corallo) Pull request description: Instead of keeping pindexBestHeader set to the best header we've ever seen, reset it back to our validated tip if we find an ancestor of it turns out to be invalid. While the name is now a bit confusing, this matches much better with how it is used in practice, see below. Further, this opens up more use-cases for it in the future, namely aggressively searching for new peers in case we have discovered (possibly via some covert channel) headers which we do not know to be invalid, but which we cannot find block data for. Places pindexBestHeader is used: * Various GUI displays of the best header and getblockchaininfo["headers"], I don't think changing this is bad, and if anything this is less confusing in the presence of an invalid block. * IsCurrentForFeeEstimation(): If anything I think ensuring pindexBestHeader isn't some crazy invalid chain is better than the alternative, even in the case where you are rejecting the current chain due to hardware error (since hopefully in that case you won't get any new blocks anyway). * ConnectBlock assumevalid checks: We use pindexBestHeader to check that the block we're connecting leads to something with nMinimumChainWork (preventing a user-set assumevalid from having bogus work) and that the block we're connecting leads to pindexBestHeader (I'm not too worried about this one - it's nice to "disable" assumevalid if we have a long invalid headers chain, but I don't see it as a critical protection). * BlockRequestAllowed() uses pindexBestHeader as its target to ensure the requested block is within a month of the "current chain". I don't think this is a meaningful difference, if we're rejecting the current tip we're trivially fingerprintable anyway, and if the chain really does have a bunch of invalid crap near the tip, using the best not-invalid header is likely a better criteria. * ProcessGetBlockData uses pindexBestHeader as the "current chain" definition of whether a block request is "historical" for the purpose of bandwidth limiting. Similarly, I don't see why this is a meaningful change. * We use pindexBestHeader for requesting missing headers on receipt of a headers/compact block message or block inv as well as for initial getheaders. I think this is definitely wrong, using the best not-invalid header for such requests is much better. * We use pindexBestHeader to define the "current chain" for deciding when we're close to done with initial headers sync. I don't think this is a meaningful change. * We use pindexBestHeader to decide if initial headers sync has timed out. If we're rejecting the chain due to hardware error this may result in additional cases where we ban a peer, but this is already true, so I think its fine. ACKs for top commit: fjahr: ACK 0a50019 kallewoof: ACK 0a50019 ariard: utACK 0a50019 Tree-SHA512: 2ecfa973a9878a00313ae7ede94a9bd7710e0caf55b544b10bbc46dc463a0478cbaf477e6cdd072356d5a0c5fb3848e9339284af785a2995c20bae8bd23f23e5
…) if it is invalid 0a50019 Walk pindexBestHeader back to ChainActive().Tip() if it is invalid (Matt Corallo) Pull request description: Instead of keeping pindexBestHeader set to the best header we've ever seen, reset it back to our validated tip if we find an ancestor of it turns out to be invalid. While the name is now a bit confusing, this matches much better with how it is used in practice, see below. Further, this opens up more use-cases for it in the future, namely aggressively searching for new peers in case we have discovered (possibly via some covert channel) headers which we do not know to be invalid, but which we cannot find block data for. Places pindexBestHeader is used: * Various GUI displays of the best header and getblockchaininfo["headers"], I don't think changing this is bad, and if anything this is less confusing in the presence of an invalid block. * IsCurrentForFeeEstimation(): If anything I think ensuring pindexBestHeader isn't some crazy invalid chain is better than the alternative, even in the case where you are rejecting the current chain due to hardware error (since hopefully in that case you won't get any new blocks anyway). * ConnectBlock assumevalid checks: We use pindexBestHeader to check that the block we're connecting leads to something with nMinimumChainWork (preventing a user-set assumevalid from having bogus work) and that the block we're connecting leads to pindexBestHeader (I'm not too worried about this one - it's nice to "disable" assumevalid if we have a long invalid headers chain, but I don't see it as a critical protection). * BlockRequestAllowed() uses pindexBestHeader as its target to ensure the requested block is within a month of the "current chain". I don't think this is a meaningful difference, if we're rejecting the current tip we're trivially fingerprintable anyway, and if the chain really does have a bunch of invalid crap near the tip, using the best not-invalid header is likely a better criteria. * ProcessGetBlockData uses pindexBestHeader as the "current chain" definition of whether a block request is "historical" for the purpose of bandwidth limiting. Similarly, I don't see why this is a meaningful change. * We use pindexBestHeader for requesting missing headers on receipt of a headers/compact block message or block inv as well as for initial getheaders. I think this is definitely wrong, using the best not-invalid header for such requests is much better. * We use pindexBestHeader to define the "current chain" for deciding when we're close to done with initial headers sync. I don't think this is a meaningful change. * We use pindexBestHeader to decide if initial headers sync has timed out. If we're rejecting the chain due to hardware error this may result in additional cases where we ban a peer, but this is already true, so I think its fine. ACKs for top commit: fjahr: ACK 0a50019 kallewoof: ACK 0a50019 ariard: utACK 0a50019 Tree-SHA512: 2ecfa973a9878a00313ae7ede94a9bd7710e0caf55b544b10bbc46dc463a0478cbaf477e6cdd072356d5a0c5fb3848e9339284af785a2995c20bae8bd23f23e5
…) if it is invalid 0a50019 Walk pindexBestHeader back to ChainActive().Tip() if it is invalid (Matt Corallo) Pull request description: Instead of keeping pindexBestHeader set to the best header we've ever seen, reset it back to our validated tip if we find an ancestor of it turns out to be invalid. While the name is now a bit confusing, this matches much better with how it is used in practice, see below. Further, this opens up more use-cases for it in the future, namely aggressively searching for new peers in case we have discovered (possibly via some covert channel) headers which we do not know to be invalid, but which we cannot find block data for. Places pindexBestHeader is used: * Various GUI displays of the best header and getblockchaininfo["headers"], I don't think changing this is bad, and if anything this is less confusing in the presence of an invalid block. * IsCurrentForFeeEstimation(): If anything I think ensuring pindexBestHeader isn't some crazy invalid chain is better than the alternative, even in the case where you are rejecting the current chain due to hardware error (since hopefully in that case you won't get any new blocks anyway). * ConnectBlock assumevalid checks: We use pindexBestHeader to check that the block we're connecting leads to something with nMinimumChainWork (preventing a user-set assumevalid from having bogus work) and that the block we're connecting leads to pindexBestHeader (I'm not too worried about this one - it's nice to "disable" assumevalid if we have a long invalid headers chain, but I don't see it as a critical protection). * BlockRequestAllowed() uses pindexBestHeader as its target 8000 to ensure the requested block is within a month of the "current chain". I don't think this is a meaningful difference, if we're rejecting the current tip we're trivially fingerprintable anyway, and if the chain really does have a bunch of invalid crap near the tip, using the best not-invalid header is likely a better criteria. * ProcessGetBlockData uses pindexBestHeader as the "current chain" definition of whether a block request is "historical" for the purpose of bandwidth limiting. Similarly, I don't see why this is a meaningful change. * We use pindexBestHeader for requesting missing headers on receipt of a headers/compact block message or block inv as well as for initial getheaders. I think this is definitely wrong, using the best not-invalid header for such requests is much better. * We use pindexBestHeader to define the "current chain" for deciding when we're close to done with initial headers sync. I don't think this is a meaningful change. * We use pindexBestHeader to decide if initial headers sync has timed out. If we're rejecting the chain due to hardware error this may result in additional cases where we ban a peer, but this is already true, so I think its fine. ACKs for top commit: fjahr: ACK 0a50019 kallewoof: ACK 0a50019 ariard: utACK 0a50019 Tree-SHA512: 2ecfa973a9878a00313ae7ede94a9bd7710e0caf55b544b10bbc46dc463a0478cbaf477e6cdd072356d5a0c5fb3848e9339284af785a2995c20bae8bd23f23e5
Summary: > Instead of keeping pindexBestHeader set to the best header we've > ever seen, reset it back to our validated tip if we find an ancestor > of it turns out to be invalid. While the name is now a bit confusing, > this matches much better with how it is used in practice, see below. > Further, this opens up more use-cases for it in the future, namely > aggressively searching for new peers in case we have discovered > (possibly via some covert channel) headers which we do not know to be > invalid, but which we cannot find block data for. > > Places pindexBestHeader is used: > > * Various GUI displays of the best header and getblockchaininfo["headers"], > I don't think changing this is bad, and if anything this is less confusing > in the presence of an invalid block. > * IsCurrentForFeeEstimation(): If anything I think ensuring pindexBestHeader > isn't some crazy invalid chain is better than the alternative, even in the > case where you are rejecting the current chain due to hardware error (since > hopefully in that case you won't get any new blocks anyway). > * ConnectBlock assumevalid checks: We use pindexBestHeader to check that the > block we're connecting leads to something with nMinimumChainWork (preventing > a user-set assumevalid from having bogus work) and that the block we're > connecting leads to pindexBestHeader (I'm not too worried about this one - > it's nice to "disable" assumevalid if we have a long invalid headers chain, > but I don't see it as a critical protection). > * BlockRequestAllowed() uses pindexBestHeader as its target to ensure the > requested block is within a month of the "current chain". I don't think this > is a meaningful difference, if we're rejecting the current tip we're > trivially fingerprintable anyway, and if the chain really does have a bunch > of invalid crap near the tip, using the best not-invalid header is likely a > better criteria. > * ProcessGetBlockData uses pindexBestHeader as the "current chain" definition > of whether a block request is "historical" for the purpose of bandwidth > limiting. Similarly, I don't see why this is a meaningful change. > * We use pindexBestHeader for requesting missing headers on receipt of a > headers/compact block message or block inv as well as for initial getheaders. > I think this is definitely wrong, using the best not-invalid header for such > requests is much better. > * We use pindexBestHeader to define the "current chain" for deciding when > we're close to done with initial headers sync. I don't think this is a > meaningful change. > * We use pindexBestHeader to decide if initial headers sync has timed out. If > we're rejecting the chain due to hardware error this may result in > additional cases where we ban a peer, but this is already true, so I think > its fine. This is a backport of Core [[bitcoin/bitcoin#16974 | PR16974]] Test Plan: `ninja all check-all` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D8736
Instead of keeping pindexBestHeader set to the best header we've
ever seen, reset it back to our validated tip if we find an ancestor
of it turns out to be invalid. While the name is now a bit confusing,
this matches much better with how it is used in practice, see below.
Further, this opens up more use-cases for it in the future, namely
aggressively searching for new peers in case we have discovered
(possibly via some covert channel) headers which we do not know to be
invalid, but which we cannot find block data for.
Places pindexBestHeader is used:
I don't think changing this is bad, and if anything this is less confusing
in the presence of an invalid block.
isn't some crazy invalid chain is better than the alternative, even in the
case where you are rejecting the current chain due to hardware error (since
hopefully in that case you won't get any new blocks anyway).
block we're connecting leads to something with nMinimumChainWork (preventing
a user-set assumevalid from having bogus work) and that the block we're
connecting leads to pindexBestHeader (I'm not too worried about this one -
it's nice to "disable" assumevalid if we have a long invalid headers chain,
but I don't see it as a critical protection).
requested block is within a month of the "current chain". I don't think this
is a meaningful difference, if we're rejecting the current tip we're
trivially fingerprintable anyway, and if the chain really does have a bunch
of invalid crap near the tip, using the best not-invalid header is likely a
better criteria.
of whether a block request is "historical" for the purpose of bandwidth
limiting. Similarly, I don't see why this is a meaningful change.
headers/compact block message or block inv as well as for initial getheaders.
I think this is definitely wrong, using the best not-invalid header for such
requests is much better.
we're close to done with initial headers sync. I don't think this is a
meaningful change.
we're rejecting the chain due to hardware error this may result in
additional cases where we ban a peer, but this is already true, so I think
its fine.