Skip to content

Conversation

@jamesob
Copy link
Contributor

@jamesob jamesob commented Feb 10, 2018

In trying to fix the fork warning code (validation.cpp:CheckForkWarningConditions*), it became apparent that we are marking some (but not all) invalid blocks as invalid (via nStatus) when they are received and subsequently dropped. The fact that we never mark some invalid blocks as such prevents us from e.g. detecting and warning on invalid chains with significant work.

This change has ProcessNewBlock call InvalidateBlock on the invalid block to do the expected bookkeeping in mapBlockIndex before dropping the block.

This change also consolidates the setting of CBlockIndex's nStatus |= BLOCK_FAILED_VALID to a single function (InvalidBlockFound) since there's peripheral bookkeeping (e.g. g_failed_blocks.insert()) that we want to do consistently but is duplicated in some places or not done in other cases when it apparently should be.

One such replacement with InvalidBlockFound ensures addition of invalid blocks to g_failed_blocks and so (in theory) reduces CPU burden when being spammed with descendants of an invalid block, since we no longer have to walk mapBlockIndex to determine its invalidity. Based on reading usage of g_failed_blocks I can't tell if this savings is real, but in any case it seems worthwhile to keep that set consistent (i.e. 1-1 with blocks marked BLOCK_FAILED_VALID since last restart).

@jamesob
Copy link
Contributor Author

jamesob commented Feb 10, 2018

Hm: this changeset is failing tests that initially transmit a block with unexpected witness data (which gets rejected) and then retransmit without witness data for expected acceptance, but we get hung up on an existing mapBlockIndex entry that's been marked invalid.

Is it worth special-casing out "unexpected-witness" rejections from the InvalidateBlock call? Seems a little ugly.

Is the bookkeeping this PR wants to do even worth it? I guess we could just lump long invalid forks in with long headers-only forks, but that doesn't seem great either...

@sipa
Copy link
Member

sipa commented Feb 10, 2018

You can only mark a block as invalid when fPossibleCorruption is not set in the validation status.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from fe6c253 to 5ed8fe9 Compare February 10, 2018 21:48
@jamesob
Copy link
Contributor Author

jamesob commented Feb 10, 2018

Ah good point, thanks @sipa. That seems to have fixed it.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from 5ed8fe9 to 7bd2d85 Compare February 10, 2018 21:54
Copy link
Contributor

@dcousens dcousens left a comment

Choose a reason for hiding this comment

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

utACK 7bd2d85906f12661efb9e0747d8bbd30530d149d

@jamesob
Copy link
Contributor Author

jamesob commented Feb 14, 2018

I've rebased this changeset, removing the code that's now in #12431 as well as the semi-related log statement. I've also DRY'd up parts of validation which set BLOCK_FAILED_VALID on nStatus to use InvalidBlockFound instead.

This change should only be merged after #12431 since this will exercise the bug that PR fixes.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from 869f981 to 6af17e4 Compare February 14, 2018 22:37
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.

Slight utACK 6af17e40890651e5e5f5968a1a131c6a48ce998c. Slight because I don't know all the implications of adding the new InvalidBlockFound calls.

New test code looks great.

I'd find the main commit ("Do the proper InvalidateBlock bookkeeping for DoSy blocks") easier to understand if it were split up, with the actual behavior change in ProcessNewBlock in one commit, and all the documentation/assertlock/DRY cleanup stuff in a different commit or set of commits.

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 "[tests] Add utility for generating bad coinbase transactions"

I think:

(r.index, tip for r, tip in zip(rpc_connections, tips))

would be clearer than:

(rpc_connections[i].index, tip for i, tip in enumerate(tips))

but you should ignore this suggestion if you prefer enumerate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, will do.

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 "[tests] Add utility for generating bad coinbase transactions"

Could drop kwargs assignment with

return create_coinbase(*args, **kwargs. txin=CTxIn(...))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yep, that's better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, Python 3.4 doesn't support this syntax (which is what we run on Travis). Will have to revert this.

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 "Do the proper InvalidateBlock bookkeeping for DoSy blocks"

Duplicating the CorruptionPossible check here seems unnecessarily confusing and fragile to me, though perhaps there's an advantage I'm not seeing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree in hindsight that this was overly paranoid. Will remove.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from 6af17e4 to 7d6e014 Compare March 6, 2018 23:38
Copy link
Contributor Author

@jamesob jamesob left a comment

Choose a reason for hiding this comment

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

@ryanofsky thanks very much for the review and good feedback. I've incorporated your recommendations and split out the last commit into two: one for the actual behavioral change and one for the cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yep, that's better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree in hindsight that this was overly paranoid. Will remove.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from 7d6e014 to bdfe29e Compare March 7, 2018 18:36
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This now happens in the InvalidBlockFound call above.

@maflcko
Copy link
Member

maflcko commented Mar 8, 2018

Needs rebase to fix travis (sorry)

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from bdfe29e to a12ce0d Compare March 8, 2018 15:37
@jamesob
Copy link
Contributor Author

jamesob commented Mar 8, 2018

Rebased.

@ryanofsky
Copy link
Contributor

Slight re-utACK a12ce0dde462911bb702070d31ed28ba32353a05. Again someone more knowledgeable than me should review safety of adding new InvalidBlockFound calls in ProcessNewBlock, AcceptBlock, and InvalidateBlock.

Changes since last review were rebase, python cleanups, adding CorruptionPossible log print, and some suggestions from the last review.

You may want to update PR description since it will become part of git history if this is merged.

laanwj added a commit that referenced this pull request Mar 15, 2018
f98b543 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial #12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f
Copy link
Member

Choose a reason for hiding this comment

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

Why log this unconditionally? I think it may be trivial to cause someone's logfile to be filled with these.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's a good point. I'll change this into a comment.

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch 2 times, most recently from c478e41 to 06a7c1e Compare March 23, 2018 17:17
@jamesob
Copy link
Contributor Author

jamesob commented Mar 23, 2018

Thanks @ryanofsky and @sipa for the re-review. I've incorporated @sipa's change (remove unconditional log in InvalidBlockFound when corruption is possible), rebased, and updated the PR description to better reflect the change's contents. I've added some text discussing the refactoring, reproduced here

This change also consolidates the setting of CBlockIndex's nStatus |= BLOCK_FAILED_VALID to a single function (InvalidBlockFound) since there's peripheral bookkeeping (e.g. g_failed_blocks.insert()) that we want to do consistently but is duplicated in some places or not done in other cases when it apparently should be.

One such replacement with InvalidBlockFound ensures addition of invalid blocks to g_failed_blocks and so (in theory) reduces CPU burden when being spammed with descendants of an invalid block, since we no longer have to walk mapBlockIndex to determine its invalidity. Based on reading usage of g_failed_blocks I can't tell if this savings is real, but in any case it seems worthwhile to keep that set consistent (i.e. 1-1 with blocks marked BLOCK_FAILED_VALID since last restart).

@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch 3 times, most recently from b3eec70 to 16d75a0 Compare March 23, 2018 20:10
@jamesob
Copy link
Contributor Author

jamesob commented May 2, 2018

Any hope of a concept ACK/NACK on this from @sipa @TheBlueMatt @sdaftuar?

Copy link
Member

Choose a reason for hiding this comment

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

The usual convention for method documentation in this project is to add the comment in the class definition, not above the implementation. This makes it easier to read the API documentation in one place when looking at the class.
That said, as both are in the .cpp file for this class I don't think that's much of an issue here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thanks.

@Empact
Copy link
Contributor

Empact commented May 18, 2018

Needs rebase

jamesob added 3 commits May 21, 2018 16:50
Also adds a small logging clarification for `sync_blocks`. Includes feedback
from @ryanofsky.
…blocks

Ensure that we mark any block which merits a DoS response as invalid, as well
as its associated chain tip. This will become important for detecting and
warning about long invalid forks.
When we receive a block which causes a DoS response, ensure that we mark its
status as "invalid" in `mapBlockIndex`. This is necessary for detecting and
warning about forks which are invalid but long.
@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch 3 times, most recently from 79f4e5f to ba1e6a0 Compare May 21, 2018 21:02
Consolidate BLOCK_FAILED_VALID nStatus updates to use InvalidBlockFound. Clean
up InvalidBlockFound for a defensive early exit if called when corruption is
possible.
@jamesob jamesob force-pushed the jamesob/2018-02-mark-headers-invalid branch from ba1e6a0 to 29bd480 Compare May 21, 2018 21:05
@jamesob
Copy link
Contributor Author

jamesob commented May 21, 2018

Rebased and addressed @laanwj's feedback.

@DrahtBot
Copy link
Contributor

Needs rebase

@jamesob
Copy link
Contributor Author

jamesob commented Jun 18, 2018

Closing this; isn't getting much traction and no point in rebasing if that's the case.

@jamesob jamesob closed this Jun 18, 2018
Mengerian pushed a commit to Mengerian/bitcoin-abc that referenced this pull request Aug 6, 2019
Summary:
f98b543 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial bitcoin/bitcoin#12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f

Backport of Core PR12431
https://github.com/bitcoin/bitcoin/pull/12431/files
Completes T680

Test Plan:
```
make check
test_runner.py
```

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D3748
PastaPastaPasta pushed a commit to PastaPastaPasta/dash that referenced this pull request Jun 13, 2020
f98b543 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial bitcoin#12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f
BrannonKing pushed a commit to lbryio/lbrycrd that referenced this pull request Sep 4, 2020
BrannonKing added a commit to lbryio/lbrycrd that referenced this pull request Sep 4, 2020
jonspock pushed a commit to jonspock/devault that referenced this pull request Sep 30, 2020
Summary:
f98b54352 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb25 [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial bitcoin/bitcoin#12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f

Backport of Core PR12431
https://github.com/bitcoin/bitcoin/pull/12431/files
Completes T680

Test Plan:
```
make check
test_runner.py
```

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D3748
jonspock pushed a commit to jonspock/devault that referenced this pull request Oct 5, 2020
Summary:
f98b54352 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb25 [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial bitcoin/bitcoin#12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f

Backport of Core PR12431
https://github.com/bitcoin/bitcoin/pull/12431/files
Completes T680

Test Plan:
```
make check
test_runner.py
```

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D3748
jonspock pushed a commit to devaultcrypto/devault that referenced this pull request Oct 10, 2020
Summary:
f98b54352 Only call NotifyBlockTip when the active chain changes (James O'Beirne)
152b7fb25 [tests] Add a (failing) test for waitforblockheight (James O'Beirne)

Pull request description:

  This is a subset of the more controversial bitcoin/bitcoin#12407, but this also adds a test demonstrating the bug.

  In InvalidateBlock, we're calling NotifyBlockTip with the now-invalid block's prev regardless of what chain the ancestor block is on. This could create numerous issues, but it at least screws up `waitforblockheight` (or anything else relying on `rpc/blockchain.cpp:latestblock`) when InvalidateBlock is called on a block not in chainActive, which can happen via RPC.

  Only call NotifyBlockTip when the block being marked invalid is on the active chain.

Tree-SHA512: 9a54fe5e8c7eb489daf5df4483c0986129e871e2ca931a456ba869ecb5d5a8d4f7bd27ccc9e711e9292c9ed79ddef896c85d0e81fc76883503e327995b0e914f

Backport of Core PR12431
https://github.com/bitcoin/bitcoin/pull/12431/files
Completes T680

Test Plan:
```
make check
test_runner.py
```

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D3748
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Dec 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants