-
Notifications
You must be signed in to change notification settings - Fork 38.7k
doc: invalid block handling followups #32843
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/32843. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste 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. |
ad6cdc8 to
e32e720
Compare
maybe I'm missing something but even without the
|
Yes, that's true, good point! To phrase it in my words, the condition that checks whether I guess that means we could, instead of |
yes sounds good! (whenever you retouch next) Ah missed the fact that iteration of entire block index is done in |
stratospher
left a comment
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.
ACK e32e720.
src/validation.cpp
Outdated
|
|
||
| // Called both upon regular invalid block discovery *and* InvalidateBlock | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew) | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header = 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.
Default values are best defined in the header file
src/validation.cpp
Outdated
|
|
||
| // Called both upon regular invalid block discovery *and* InvalidateBlock | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew) | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header = 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.
Apologies for mostly coming with stop energy here, but I think I'm approach NACK on 30b190e, and I would prefer dropping the commit. While the duplicated header calculation and flag setting is wasteful, I don't think we should complicate our validation logic further for the very rare case that is invalidateblock.
I don't have a suggested alternative yet, but it seems to me that reorganizing this code into different, more natural functions might be an alternative worth exploring. I don't conceptually understand why we have different functions for InvalidChainFound and InvalidBlockFound, and in practice it seems they exist for reasons unrelated to their difference in name. Similarly, SetBlockFailureFlags is a confusing function that doesn't actually set that block's failure flags.
Even without an alternative, I would rather make no change than this change, I don't think anything actually needs fixing 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.
In commit "validation: Don't repeat work in InvalidateBlock" (30b190e)
re: #32843 (comment)
Apologies for mostly coming with stop energy here, but I think I'm approach NACK on 30b190e
I tend to disagree and think the code is clearer with this change than without it. Without it, intent is not possible to discern. The code is doing the same work twice without an indication of why it is doing that. With this change, behavior actually makes sense and intent is clear.
I do agree code could be reorganized better and made less fragile, but I don't see the new code here as being more fragile than current code, just more explicit.
I do share some concern about the default value, though, and I think it would be better if InvalidChainFound was not commenting on invalidateblock behavior, which could change at some point. So might suggest:
diff
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2064,13 +2064,12 @@ void Chainstate::CheckForkWarningConditions()
}
// Called both upon regular invalid block discovery *and* InvalidateBlock
-void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header = true)
+void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header)
{
AssertLockHeld(cs_main);
if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
m_chainman.m_best_invalid = pindexNew;
}
- // Unnecessary in the invalidateblock case, which has its own accounting for the failure flags and best header.
if (calc_flags_and_header) {
SetBlockFailureFlags(pindexNew);
if (m_chainman.m_best_header != nullptr && m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) == pindexNew) {
@@ -2098,7 +2097,7 @@ void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationSta
pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex);
- InvalidChainFound(pindex);
+ InvalidChainFound(pindex, /*calc_flags_and_header=*/true);
}
}
@@ -3376,7 +3375,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex*
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
- InvalidChainFound(vpindexToConnect.front());
+ InvalidChainFound(vpindexToConnect.front(), /*calc_flags_and_header=*/true);
}
state = BlockValidationState();
fInvalidFound = true;
@@ -3809,6 +3808,8 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
}
}
+ // Record invalid chain, avoiding recomputing m_best_header and applying
+ // BLOCK_FAILED_CHILD flags since these things were done above.
InvalidChainFound(to_mark_failed, /*calc_flags_and_header=*/false);
}
Or if this commit will be dropped, I think there should at least be a comment added before the InvalidChainFound call in InvalidateBlock saying InvalidChainFound will repeat some work that was already before cs_main was released, to make it clear the duplication was at least not an accident.
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.
After this discussion in which we discussed the separate topic that the current InvalidChainFound() call in ActivateBestChainStep() often does repeated work and logging, I decided to drop the commit, and will work on a PR to reorder things instead. Current rough plan is to move most things from InvalidChainFound to InvalidBlockFound and rename the former to UpdateBestInvalid().
For this PR, I have instead added a comment, so that it is doc-only now. I might also close this and add the commit to the other PR to avoid churn, I'll check that option next week.
ryanofsky
left a comment
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 e32e720. Thanks for these followups, I think they should make it easier to remember what this code is trying to do.
src/validation.cpp
Outdated
|
|
||
| // Called both upon regular invalid block discovery *and* InvalidateBlock | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew) | ||
| void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header = 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.
In commit "validation: Don't repeat work in InvalidateBlock" (30b190e)
re: #32843 (comment)
Apologies for mostly coming with stop energy here, but I think I'm approach NACK on 30b190e
I tend to disagree and think the code is clearer with this change than without it. Without it, intent is not possible to discern. The code is doing the same work twice without an indication of why it is doing that. With this change, behavior actually makes sense and intent is clear.
I do agree code could be reorganized better and made less fragile, but I don't see the new code here as being more fragile than current code, just more explicit.
I do share some concern about the default value, though, and I think it would be better if InvalidChainFound was not commenting on invalidateblock behavior, which could change at some point. So might suggest:
diff
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2064,13 +2064,12 @@ void Chainstate::CheckForkWarningConditions()
}
// Called both upon regular invalid block discovery *and* InvalidateBlock
-void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header = true)
+void Chainstate::InvalidChainFound(CBlockIndex* pindexNew, bool calc_flags_and_header)
{
AssertLockHeld(cs_main);
if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
m_chainman.m_best_invalid = pindexNew;
}
- // Unnecessary in the invalidateblock case, which has its own accounting for the failure flags and best header.
if (calc_flags_and_header) {
SetBlockFailureFlags(pindexNew);
if (m_chainman.m_best_header != nullptr && m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) == pindexNew) {
@@ -2098,7 +2097,7 @@ void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationSta
pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex);
- InvalidChainFound(pindex);
+ InvalidChainFound(pindex, /*calc_flags_and_header=*/true);
}
}
@@ -3376,7 +3375,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex*
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
- InvalidChainFound(vpindexToConnect.front());
+ InvalidChainFound(vpindexToConnect.front(), /*calc_flags_and_header=*/true);
}
state = BlockValidationState();
fInvalidFound = true;
@@ -3809,6 +3808,8 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
}
}
+ // Record invalid chain, avoiding recomputing m_best_header and applying
+ // BLOCK_FAILED_CHILD flags since these things were done above.
InvalidChainFound(to_mark_failed, /*calc_flags_and_header=*/false);
}
Or if this commit will be dropped, I think there should at least be a comment added before the InvalidChainFound call in InvalidateBlock saying InvalidChainFound will repeat some work that was already before cs_main was released, to make it clear the duplication was at least not an accident.
src/validation.cpp
Outdated
| // Do not remove candidate from the highpow_outofchain_headers cache, because it might be a descendant of the block being invalidated | ||
| // which needs to be marked failed later. | ||
| } | ||
| // Best header updating currently doesn't use CBlockIndexWorkComparator, so nChainWork is used for consistency. |
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.
In commit "doc: Improve doc for candidate blocks" (e32e720)
Would maybe s/nChainWork/only nChainWork/ since using nChainWork shouldn't be surprising here, but only using nChainWork might be.
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.
done
| // Best header updating currently doesn't use CBlockIndexWorkComparator, so nChainWork is used for consistency. | ||
| if (best_header_needs_update && | ||
| m_chainman.m_best_header->nChainWork < candidate->nChainWork) { | ||
| m_chainman.m_best_header = candidate; |
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.
In commit "doc: Improve doc for candidate blocks" (e32e720)
Note for reviewers: best description I've seen for why it's not currently safe to assume m_best_header is consistent with CBlockIndexWorkComparator ordering is here #31405 (comment)
src/validation.cpp
Outdated
| // Do not remove candidate from the highpow_outofchain_headers cache, because it might be a descendant of the block being invalidated | ||
| // which needs to be marked failed later. | ||
| } | ||
| // Best header updating currently doesn't use CBlockIndexWorkComparator, so nChainWork is used for consistency. |
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.
In commit "doc: Improve doc for candidate blocks" (e32e720)
To be complete, this could also mention that when there is a tie, this will set m_best_header to whichever block happens to come first in m_block_index std::unordered_map iteration order. This ordering is consistent with the ordering used in RecalculateBestHeader, but inconsistent with the ordering used in AddToBlockIndex, which is based on the order headers are received.
I'm not sure how much detail is necessary here, so also just making these notes for myself.
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 added a sentence with the basics (didn't include the describe the different orderings).
e32e720 to
f745092
Compare
It is not true in general that all blocks with the same work (as defined via nChainWork) as the tip are candidates expected to be in setBlockIndexCandidates - only ones with a better tiebreak than the tip. Make that clear in various spots. Also explain that we don't use CBlockIndexWorkComparator tiebreak rules for m_best_header.
f745092 to
c34e44d
Compare
|
🐙 This pull request conflicts with the target branch and needs rebase. |
Some follow-ups from #31405:
CBlockIndexWorkComparatorscore at least as good as the tip (and no others) are expected insetBlockIndexCandidates.People think of
nChainWorkwhen the termworkis used and not of the (slightly arbitrary)CBlockIndexWorkComparatortiebreaker rules, which makes the current documentation imprecise ( validation: stricter internal handling of invalid blocks #31405 (comment) )nChainWork, notCBlockIndexWorkComparatoris used form_best_header(validation: stricter internal handling of invalid blocks #31405 (comment))