Skip to content
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

feat: discard non-finalizable blocks #7746

Merged
merged 21 commits into from
Oct 7, 2022
Merged

Conversation

Longarithm
Copy link
Member

@Longarithm Longarithm commented Sep 30, 2022

Because flat storage head is correlated with final head, we won't be able to process blocks on top of blocks which are behind final head. Processing such blocks doesn't make sense anyway, because they will never be finalized. We can check it during header validation and avoid attempts to apply chunks.

More concretely - we check parents of the new block, and if we fall behind final head at some point, we declare that block can't be finalized and return an error. To avoid long delays, we limit number of checked parents to 20.

Testing

  • Existing tests.
  • Updating simple_chain tests to reflect new behaviour
  • process_blocks::test_discard_non_finalizable_block - integration test for checking chain behaviour

@Longarithm Longarithm self-assigned this Sep 30, 2022
@Longarithm Longarithm marked this pull request as ready for review September 30, 2022 21:50
@Longarithm Longarithm requested a review from a team as a code owner September 30, 2022 21:50
chain/chain-primitives/src/error.rs Outdated Show resolved Hide resolved
fn check_if_finalizable(&self, header: &BlockHeader) -> Result<(), Error> {
let mut header = header.clone();
let final_head = self.final_head()?;
for _ in 0..NUM_PARENTS_TO_CHECK_FINALITY {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about the need for this constant. This actually assumes that in the chain, we would never have a fork with 20 blocks. While this rarely happens, adding this assumption doesn't help much with the implementation in this function, so I don't think it should be added. the check that compares the block height with the final head height should be enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

This actually assumes that in the chain, we would never have a fork with 20 blocks.

I don't see where we need this assumption. If there is a fork with >20 blocks, it means that we just can't apply our optimization and discard it here. Having constant 20 helps us to discard more useless blocks (see below).

the check that compares the block height with the final head height should be enough.

I think the current check is helpful for scenarios as follows:

     /-> 2 -> 3 -> 5 -> 6 -> 7
   1
(final) 
     \-> 4 -> 8
  • processing blocks (1) - (6) don't change final head, so no blocks can be discarded
  • block (7) moves final head to (5)
  • now block (8) can be discarded during preprocessing, but to know it, we need to look at its parent (4).

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. I still don't understand why we need to only check the last 20 blocks. What do you think about just changing the for loop to loop?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm concerned about very long forks for which it can take a while to iterate over parents. Maybe there is some reason not to worry about it, but I don't see it.

Copy link
Member Author

Choose a reason for hiding this comment

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

If I put the check to preprocess_block, then these tests are failing:

tests::challenges::challenges_new_head_prev
tests::simple_chain::blocks_at_height

They create some tree of blocks on chain, and the tests fail because some of blocks are being discarded.

Moved it to Client::start_process_block, because it looks like optimization on Client side, and I wouldn't make Chain tests assuming this optimization as well. Even after this, two other tests had to be changed.

chain/chain/src/chain.rs Outdated Show resolved Hide resolved
}

/// Verifies that the block at height are updated correctly when blocks from different forks are
/// processed, especially when certain heights are skipped
/// processed, especially when certain heights are skipped.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a comment on what the chain looks like in this test?

@near-bulldozer near-bulldozer bot merged commit a0a0344 into master Oct 7, 2022
@near-bulldozer near-bulldozer bot deleted the discard-non-final branch October 7, 2022 00:13
nikurt pushed a commit that referenced this pull request Nov 9, 2022
Because flat storage head is correlated with final head, we won't be able to process blocks on top of blocks which are behind final head. Processing such blocks doesn't make sense anyway, because they will never be finalized. We can check it during header validation and avoid attempts to apply chunks.

More concretely - we check parents of the new block, and if we fall behind final head at some point, we declare that block can't be finalized and return an error. To avoid long delays, we limit number of checked parents to 20.

## Testing

* Existing tests.
* Updating `simple_chain` tests to reflect new behaviour
* `process_blocks::test_discard_non_finalizable_block` - integration test for checking chain behaviour
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants