Skip to content

Conversation

TheBlueMatt
Copy link
Contributor

@TheBlueMatt TheBlueMatt commented Sep 11, 2019

Based on #16849, this was intended primarily to fix n invalid-direction comparison (see the first commit) but discovered there were further issues preventing the addition of a new rule in CheckBlockIndex. We can either go this way, or just delete the code that attempts (incorrectly) to mark blocks BLOCK_FAILED_CHILD in InvalidateBlock and not require this as an invariant (I don't have a strong opinion here, just PR'ing this as I wrote it during testing and figured I'd demonstrate).

sdaftuar and others added 4 commits September 10, 2019 14:54
Previously, we could release cs_main while leaving the block index in a state
that would fail CheckBlockIndex, because setBlockIndexCandidates was not being
fully populated before releasing cs_main.
This has no functional affect, as the any CBlockIndex*s which
to_mark_failed is set to will already have been marked failed.
This adds a new test in CheckBlockIndex which tests that any
descendants of a block marked BLOCK_FAILED_VALID will not be marked
BLOCK_FAILED_VALID as they should never have been connected in the
first place.

Note that this check requires further changes in InvalidateBlock as
if you iteratively call InvalidateBlock to walk the chain backwards
(instead of calling InvalidateBlock on an old block and allowing it
to do the walking), the later-in-the-chain blocks will violate this.
@@ -2813,33 +2848,62 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
setDirtyBlockIndex.insert(invalid_walk_tip);
setBlockIndexCandidates.erase(invalid_walk_tip);
setBlockIndexCandidates.insert(invalid_walk_tip->pprev);
if (invalid_walk_tip->pprev == to_mark_failed && (to_mark_failed->nStatus & BLOCK_FAILED_VALID)) {
if (invalid_walk_tip == to_mark_failed->pprev && (to_mark_failed->nStatus & BLOCK_FAILED_VALID)) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused by this fix. Let's say we called invalidateblock on block 10. In that case to_mark_failed is block 10, to_mark_failed->pprev is block 9. Let's say we're almost done and we just disconnected the tip at height 11. Then invalid_walk_tip is block 11. So this comparison always returns false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we're almost done, to_mark_failed got set to block 12 in the last iteration of the loop (yes, these variable names make no sense).

Copy link
Member

@Sjors Sjors Oct 3, 2019

Choose a reason for hiding this comment

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

Ah, it's actually pindex that stays at block 10 throughout this function. to_mark_failed is set to invalid_walk_tip. By the time we reach this line we chipped one block off of the tip.

Looking at the first iteration: let's say the original tip is block 20. to_mark_failed starts at block 10. We disconnect block 20 and mark it BLOCK_FAILED_VALID. Theif statement here returns false, so block 20 temporarily remains BLOCK_FAILED_VALID. The second iteration to_mark_failed points to block 20, so then we set it to BLOCK_FAILED_CHILD.

Do I understand correctly that the reason for this "we fix it the next iteration" is because we might get shut down? So we need to wait with BLOCK_FAILED_CHILD until we're sure the parent is BLOCK_FAILED_VALID .

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, I mean its not strictly because we want to be safe in case of shutdown (though that's nice), and more that this hunk previously never did anything at all (though its intent was "descendants get marked FAILED_CHILD").

@laanwj laanwj added the Bug label Sep 30, 2019
@Sjors
Copy link
Member

Sjors commented Sep 30, 2019

Looking at the pindexFirstInvalid check in isolation (0ae7281), that makes sense to me. If I drop everything except that check, then a whole bunch of functional tests throw an assert: ((pindex->nStatus & BLOCK_FAILED_VALID) == 0), function CheckBlockIndex, file validation.cpp, line 4669.. But I don't understand the fix itself, at least not the bit above my inline comment.

laanwj added a commit that referenced this pull request Oct 2, 2019
2a4e60b Fix block index inconsistency in InvalidateBlock() (Suhas Daftuar)

Pull request description:

  Previously, we could release `cs_main` while leaving the block index in a state
  that would fail `CheckBlockIndex()`, because `setBlockIndexCandidates` was not being
  fully populated before releasing `cs_main`.

ACKs for top commit:
  TheBlueMatt:
    utACK 2a4e60b. I also discovered another issue in InvalidateBlock while reviewing, see #16856.
  Sjors:
    ACK 2a4e60b. Tested on top of #16899. Also tested `invalidateblock` with `-checkblockindex=1`.
  fjahr:
    ACK 2a4e60b. Ran tests, reviewed code, inspected behavior while manually testing `invalidateblock`.

Tree-SHA512: ced12f9dfff0d413258c709921543fb154789898165590b30d1ee0cdc72863382f189744f7669a7c924d3689a1cc623efdf4e5ae3efc60054572c1e6826de612
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Oct 2, 2019
2a4e60b Fix block index inconsistency in InvalidateBlock() (Suhas Daftuar)

Pull request description:

  Previously, we could release `cs_main` while leaving the block index in a state
  that would fail `CheckBlockIndex()`, because `setBlockIndexCandidates` was not being
  fully populated before releasing `cs_main`.

ACKs for top commit:
  TheBlueMatt:
    utACK 2a4e60b. I also discovered another issue in InvalidateBlock while reviewing, see bitcoin#16856.
  Sjors:
    ACK 2a4e60b. Tested on top of bitcoin#16899. Also tested `invalidateblock` with `-checkblockindex=1`.
  fjahr:
    ACK 2a4e60b. Ran tests, reviewed code, inspected behavior while manually testing `invalidateblock`.

Tree-SHA512: ced12f9dfff0d413258c709921543fb154789898165590b30d1ee0cdc72863382f189744f7669a7c924d3689a1cc623efdf4e5ae3efc60054572c1e6826de612
@jnewbery
Copy link
Contributor

Can you give a bit of motivation for why we want to mark descendants of BLOCK_FAILED_VALID as BLOCK_FAILED_CHILD? I can't see any functional difference in the way that a CBlockIndex is treated when its nStatus is BLOCK_FAILED_CHILD. The only comparisons seem to be against BLOCK_FAILED_MASK.

If that's the case, then I think we should just mark all descendants of the block to invalidate as BLOCK_FAILED_VALID. If reconsiderblock is later called, the nStatus of the block and all its descendants are reset anyway. It seems to me that InvalidateBlock() would be much simpler if we did that.

@TheBlueMatt
Copy link
Contributor Author

Right, I don't think we really use it anywhere, I just figured as long as we make the distinction, make the distinction. Maybe @sipa recalls why it was there in the first place?

@sipa
Copy link
Member

sipa commented Dec 13, 2019

I don't recall exactly why they're separate. One point is that BLOCK_FAILED_CHILD doesn't actually need to be stored on disk, as it is recomputed at db load time. So an advantage of keeping them separate is that if only FAILED_CHILD gets set on a blockindex object, it doesn't need to be made dirty and written to disk. I'm not sure that optimization is ever taken advantage of (and it probably can be achieved in other ways too).

@jnewbery
Copy link
Contributor

I'm not sure that optimization is ever taken advantage of

Looks to me like it isn't used. I'd vote to just use BLOCK_FAILED_VALID and keep InvalidateBlock() simple.

@jnewbery
Copy link
Contributor

Alternative here https://github.com/jnewbery/bitcoin/tree/2019-12-block-failed-valid-descendants just removes tracking of BLOCK_FAILED_CHILD. Let me know what you think, @TheBlueMatt .

@bitcoin bitcoin deleted a comment from zachwylde00 Dec 21, 2019
@BrannonKing
Copy link

@jnewbery , that branch doesn't remove all references to BLOCK_FAILED_CHILD. Was the intent to remove them all?

@jnewbery
Copy link
Contributor

jnewbery commented Sep 6, 2020

@BrannonKing - The intent was to demonstrate how removing BLOCK_FAILED_CHILD from InvalidateBlock() makes it simpler (and therefore less likely to hide bugs). I agree that we should remove BLOCK_FAILED_CHILD everywhere.

@jnewbery
Copy link
Contributor

jnewbery commented Oct 1, 2020

@BrannonKing - I've rebased my branch on master and removed all the remaining instances of BLOCK_FAILED_CHILD.

@TheBlueMatt - This PR hasn't had any activity for about a year. Are you happy to close it?

UdjinM6 pushed a commit to UdjinM6/dash that referenced this pull request Oct 29, 2021
2a4e60b Fix block index inconsistency in InvalidateBlock() (Suhas Daftuar)

Pull request description:

  Previously, we could release `cs_main` while leaving the block index in a state
  that would fail `CheckBlockIndex()`, because `setBlockIndexCandidates` was not being
  fully populated before releasing `cs_main`.

ACKs for top commit:
  TheBlueMatt:
    utACK 2a4e60b. I also discovered another issue in InvalidateBlock while reviewing, see bitcoin#16856.
  Sjors:
    ACK 2a4e60b. Tested on top of bitcoin#16899. Also tested `invalidateblock` with `-checkblockindex=1`.
  fjahr:
    ACK 2a4e60b. Ran tests, reviewed code, inspected behavior while manually testing `invalidateblock`.

Tree-SHA512: ced12f9dfff0d413258c709921543fb154789898165590b30d1ee0cdc72863382f189744f7669a7c924d3689a1cc623efdf4e5ae3efc60054572c1e6826de612
pravblockc pushed a commit to pravblockc/dash that referenced this pull request Nov 18, 2021
2a4e60b Fix block index inconsistency in InvalidateBlock() (Suhas Daftuar)

Pull request description:

  Previously, we could release `cs_main` while leaving the block index in a state
  that would fail `CheckBlockIndex()`, because `setBlockIndexCandidates` was not being
  fully populated before releasing `cs_main`.

ACKs for top commit:
  TheBlueMatt:
    utACK 2a4e60b. I also discovered another issue in InvalidateBlock while reviewing, see bitcoin#16856.
  Sjors:
    ACK 2a4e60b. Tested on top of bitcoin#16899. Also tested `invalidateblock` with `-checkblockindex=1`.
  fjahr:
    ACK 2a4e60b. Ran tests, reviewed code, inspected behavior while manually testing `invalidateblock`.

Tree-SHA512: ced12f9dfff0d413258c709921543fb154789898165590b30d1ee0cdc72863382f189744f7669a7c924d3689a1cc623efdf4e5ae3efc60054572c1e6826de612
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
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.

8 participants