-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Return BlockValidationState from ProcessNewBlock if CheckBlock/AcceptBlock fails #17479
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
Return BlockValidationState from ProcessNewBlock if CheckBlock/AcceptBlock fails #17479
Conversation
|
This is the first two commits from #16279. Thanks for your work, @TheBlueMatt! |
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
111d7b4 to
7186766
Compare
|
I've added a commit that deduplicates post-block-checking code, which makes the main commit in this PR smaller and (I hope) easier to understand. |
|
I've taken the final (behaviour change) commit from #16279 and PR'ed it as #17485. That gives some justification for this PR. If we get the anti-dos / mutation checks back from validation immediately, we can use that state to mark the block as no longer in-flight, rather than by indirectly checking that the block has been written to disk. Separating into two PRs makes this easier to review. This PR should result in no behaviour changes (modulo the |
|
I found the last commit in this PR (71867666388bf1137b431f944fd430f262480eba) a bit hard to parse, so I split it up in my branch here: master...dongcarl:2019-11-processnewblock-early-return-redo-last. The main change in the last commit on this branch resides in f46102b on mine, and f46102b also retains the same commit message. Feel free to use/use only to review/not use. |
src/validation.h
Outdated
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.
Perhaps I'm understanding wrong but this shouldn't apply anymore?
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.
BlockChecked is called for any block that we try to connect to the chain. See the call in ConnectTip():
- if the call to
ConnectBlock()failed, then we'll callBlockChecked()with a state that is NOTIsValid(). - if the call to
ConnectBlock()succeeded, then the block has been connected and is part of the main chain, and we'll callBlockChecked()with a state that ISIsValid().
Note that we may never even call ConnectTip() if we don't try to connect the block to the most work chain.
7186766 to
0bf87d2
Compare
|
Thanks for the branch @dongcarl ! I've reset to that and made a couple of minor comment changes. |
ariard
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.
Concept ACK, I think commit a49a153 can be made a bit better.
src/net_processing.cpp
Outdated
| } else if (!new_block) { | ||
| // Block was valid but we've seen it before. Clear it from mapBlockSource. | ||
| LOCK(cs_main); | ||
| ::mapBlockSource.erase(pblock->GetHash()); |
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.
Can't this branch combined with BlockChecked by passing new_block as param? Even wonder if we still need fNewBlock in PNB, as BlockChecked erase block from mapBlockSource independently of validity/novelty
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.
BlockChecked() will only be called from BlockProcessed() if the block failed CheckBlock()/AcceptBlock(). This else branch is catching the case where the block didn't fail, but we've seen it before.
If you think this can be cleaned up further, perhaps we can do it in a follow-up PR.
jkczyz
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.
src/net_processing.cpp
Outdated
| * A block has been processed. If the block was failed anti-dos / mutation checks, then | ||
| * call BlockChecked() to maybe punish peer. If this is the first time we've seen the block, | ||
| * update the node's nLastBlockTime. Otherwise erase it from mapBlockSource. |
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.
The documentation strikes me as repetitive with the code. Perhaps it could be phrased differently such that if the code changes the comment doesn't need to be updated.
nit: s/was failed/failed
0bf87d2 to
e48152f
Compare
|
Thanks for the review @jkczyz . I've updated the function comment to be less redundant. |
e48152f to
b2ee50d
Compare
|
rebased |
|
Concept ACK, will review after rebase. |
50048f4 to
cd4a987
Compare
cd4a987 to
6f1505d
Compare
|
rebased |
|
Took some time to prove to myself that there are no unintended behaviour changes. It wasn't obvious to me that 5e5d58c829001a774e2481a4a8c3598917b06e53 is trivially correct, so I'm most likely missing some context here. My understanding is that prior to 5e5d58c829001a774e2481a4a8c3598917b06e53, However, it seems that after 5e5d58c829001a774e2481a4a8c3598917b06e53, it is possible for both Here's the diff I used for testing, and it seems to fail at diff --git a/src/validation.cpp b/src/validation.cpp
index 524ae94b9a..3f5737fd35 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -3856,9 +3856,11 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s
ret = ::ChainstateActive().AcceptBlock(pblock, dos_state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock);
}
if (!ret) {
+ assert(!dos_state.IsValid()); // We should always trigger BlockChecked in BlockProcessed in this codepath
return error("%s: AcceptBlock FAILED (%s)", __func__, dos_state.ToString());
}
}
+ assert(dos_state.IsValid()); // We should NOT trigger BlockChecked in BlockProcessed in this codepath
NotifyHeaderTip();
|
6f1505d to
2adc864
Compare
|
The first commit has now been merged in #19533. I've rebased the remaining commits on master. |
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.
Almost ACK all commits except am still reviewing removal of the call to BlockChecked() in PNB in 372913c9 and other reasoning about this that @dongcarl states well in #17479 (comment). A few notes below. Code review, debug-built and ran tests at each commit; running a node on the branch ATM and observing behavior.
src/validation.h
Outdated
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.
1dd78f41
-
params order: can the
[out]paramstatebe inserted after the[in]params? -
should
@param[in] chainparamsbe added to the doxygen comment? -
maybe I'm missing something obvious; any reason to not name
state/dos_stateconsistently throughout, while adding it?
src/net_processing.cpp
Outdated
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 6730b5931 and 372913c9dfe4, can doxygen documentation be added for BlockProcessed params as they are added?
src/net_processing.cpp
Outdated
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.
372913c9df nits:
-
slightly clearer, more differentiating wording could be
s/we've/we have/and
s/we haven't/we have not/ -
why is past tense used above ("block failed", "was valid") and here present tense ("is valid")
-
s/set/Set/
src/validation.cpp
Outdated
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.
1dd78f4 Is there a reason to rename state to dos_state throughout this function? Doing so adds a fair amount of noise.
src/validation.cpp
Outdated
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.
5f552a0a34 This change could be done in the same commit 1dd78f4 that adds state/dos_state to the params. That said, is it strictly necessary? (a) The change adds noise and ISTM the comment and code position make it clear enough; (b) dummy is the kind of naming, like certain others (e.g. master/slave, whitelist/blacklist, etc.) that is perhaps a bit in decline nowadays and being deprecated, so perhaps no need to add it.
Co-authored-by: Carl Dong <[email protected]>
This is a pure refactor commit. This commit enables the caller of ProcessNewBlock to access the final BlockValidationState passed around between CheckBlock(), AcceptBlock(), and BlockChecked() inside ProcessNewBlock(). This is useful because in a future commit, we will move the BlockChecked() call out of ProcessNewBlock(), and BlockChecked() still needs to be able to access the BlockValidationState. Co-authored-by: John Newbery <[email protected]> Co-authored-by: Carl Dong <[email protected]>
This is a pure refactor commit. Since BlockChecked() doesn't actually depend on all of PeerLogicValidation but just PeerLogicValidation's CConnman, we can make a standalone, static function that simply has an extra CConnman parameter and have the non-static version call the static one. This also means that, in a future commit, when we move the BlockChecked() call out of ProcessNewBlock(), the caller of ProcessNewBlock() can call BlockChecked() directly even if they only have a CConnman. Co-authored-by: John Newbery <[email protected]> Co-authored-by: Carl Dong <[email protected]>
…ProcessNewBlock Net processing now passes a BlockValidationState object into ProcessNewBlock(). If CheckBlock() or AcceptBlock() fails, then PNB returns to net processing without calling the (asynchronous) BlockChecked Validation Interface method. net processing can use the invalid BlockValidationState returned to punish peers. CheckBlock() and AcceptBlock() represent the DoS checks on a block (ie PoW and malleability). Net processing wants to know about those failed checks immediately and shouldn't have to wait on a callback. Other validation interface clients don't care about net processing submitting bogus malleated blocks to validation, so they don't need to be notified of BlockChecked. Furthermore, if PNB returns a valid BlockValidationState, we never need to try to process (non-malleated) copies of the block from other peers. That makes it much easier to move the best chain activation logic to a background thread in future work. Co-authored-by: John Newbery <[email protected]> Co-authored-by: Carl Dong <[email protected]>
This is a pure refactor commit. Co-authored-by: John Newbery <[email protected]> Co-authored-by: Carl Dong <[email protected]>
Co-authored-by: John Newbery <[email protected]> Co-authored-by: Carl Dong <[email protected]>
|
rebased |
2adc864 to
17170a5
Compare
|
🐙 This pull request conflicts with the target branch and needs rebase. Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft". |
|
I'm focused on other things right now, so I'm going to close this with the intention of opening it again at some point in the future. |
Net processing now passes a
BlockValidationStateobject intoProcessNewBlock(). IfCheckBlock()orAcceptBlock()fails, then PNB returns to net processing without calling the (asynchronous)BlockCheckedValidation Interface method. net processing can use the invalidBlockValidationStatereturned to punish peers.CheckBlock()andAcceptBlock()represent the DoS checks on a block (ie PoW and malleability). Net processing wants to know about those failed checks immediately and shouldn't have to wait on a callback. Other validation interface clients don't care about net processing submitting bogus malleated blocks to validation, so they don't need to be notified ofBlockChecked.Furthermore, if PNB returns a valid
BlockValidationState, we never need to try to process (non-malleated) copies of the block from other peers. That makes it much easier to move the best chain activation logic to a background thread in future work.