Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Chain Selection Subsystem Logic #3277

Merged
85 commits merged into from
Jun 21, 2021
Merged

Chain Selection Subsystem Logic #3277

85 commits merged into from
Jun 21, 2021

Conversation

rphmeier
Copy link
Contributor

@rphmeier rphmeier commented Jun 17, 2021

Closes #3235
Closes #3103

This implements the logic of the chain selection subsystem as described in #3262 . I took a slightly different approach to DB handling than what is done in other subsystems that wrap a DB, by using a Backend trait and defining BackendWriteOps, along with a helper struct BackendOverlay for maintaining a consistent state when querying changes that haven't been committed. I think this simplifies the code and will make it more difficult to write logic errors in the class of #3311

The primary code here is located within the tree submodule which is used to maintain a coherent view of all unfinalized blocks and implements the core operations of import, finalization, reversion, approval, and stagnation.

Unlike #3235 which describes separate fields for stagnation and reversion, I realized that I could combine these fields into a single field for tracking the earliest unviable ancestor of each block. This allows for further code reduction as we don't need to handle these in separate paths. The helper function for propagate_viability_update does most of the heavy lifting and the algorithm functions the same both when propagating a viable->unviable change and unviable->viable changes.

TODO:

  • Block import
  • Block reversion logs
  • Block finalization
  • Approve Message
  • Stagnant updates
  • Tests

Deferred to later:

  • Plug into overseer
  • Implement DB
  • Send Approve messages from Approval Voting (even when insta-approving)
  • Stagnation loop

@rphmeier rphmeier added A3-in_progress Pull request is in progress. No review needed at this stage. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. labels Jun 17, 2021
@github-actions github-actions bot added A0-please_review Pull request needs code review. and removed A3-in_progress Pull request is in progress. No review needed at this stage. labels Jun 17, 2021
Copy link
Contributor

@Lldenaurois Lldenaurois left a comment

Choose a reason for hiding this comment

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

First pass

node/core/chain-selection/src/lib.rs Outdated Show resolved Hide resolved
node/core/chain-selection/src/lib.rs Show resolved Hide resolved
@rphmeier
Copy link
Contributor Author

I've tested this to a satisfactory degree. I think the tests are very comprehensive, at least for the features we support now. The follow-up PR will add stagnation and with that we'll be able to test that approvals make chains viable again. I found a few bugs but did not find any bugs in tree::propagate_viability_update.

Copy link
Member

@ordian ordian left a comment

Choose a reason for hiding this comment

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

I didn't understand all of it, but further improvements can go to #3293.


// Propagate viability update to descendants of the given block. This writes
// the `base` entry as well as all descendants. If the parent of the block
// entry is not viable, this wlil not affect any descendants.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// entry is not viable, this wlil not affect any descendants.
// entry is not viable, this will not affect any descendants.

node/core/chain-selection/src/backend.rs Show resolved Hide resolved
node/core/chain-selection/src/backend.rs Show resolved Hide resolved
@rphmeier rphmeier removed the request for review from coriolinus June 21, 2021 00:52
@rphmeier rphmeier mentioned this pull request Jun 21, 2021
5 tasks
Copy link
Contributor

@drahnr drahnr left a comment

Choose a reason for hiding this comment

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

A few small nits and notes, as far as I can see follows protocol-chain-selection.md exactly 👍

node/core/chain-selection/src/lib.rs Outdated Show resolved Hide resolved
node/core/chain-selection/src/lib.rs Show resolved Hide resolved
}

#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

node/core/chain-selection/src/lib.rs Show resolved Hide resolved
node/core/chain-selection/src/lib.rs Show resolved Hide resolved
// a leaf. this is fine according to the expected usage of the
// function. `None` responses should just `unwrap_or(required)`,
// so if the required block is the finalized block, then voilá.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this worth a tracing::debug! message if the best leaf is equiv the finalized required - it seems that should happen rather rarely and indicates potential issues in block production or overzealous finalization.
EDIT: this actually similar to what you mentioned in the guide.

node/core/chain-selection/src/lib.rs Show resolved Hide resolved
node/core/chain-selection/src/lib.rs Show resolved Hide resolved
/// return true if `ancestor` is in `head`'s chain.
///
/// If the ancestor is an older finalized block, this will return `false`.
fn contains_ancestor(
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be part of the Backend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I can see that, but I believe it's simpler right now to expose a minimal Backend API and build on top of that. We're going to be disk-bound by this anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

This function is \Omega(block_height) in the worst case, so if someone were to pass in the genesis hash we would basically read the entire chain, right?

I wonder whether it makes sense to put a limit on the depth of this loop...

Copy link
Contributor Author

@rphmeier rphmeier Jun 22, 2021

Choose a reason for hiding this comment

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

@Lldenaurois It's actually O(block_height - finalized_height) because we only store unfinalized subtrees here. Pretty much all the algorithms here have the same complexity but as long as we don't have thousands of unfinalized blocks they'll work fine.

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 now it's more inefficient than it needs to be but we could easily keep a HashSet<Hash> of block-entries we've visited already and bail early when we encounter a parent hash we've already seen. Most chains will have some common ancestor before the last finalized block.

@rphmeier
Copy link
Contributor Author

bot merge

@ghost
Copy link

ghost commented Jun 21, 2021

Waiting for commit status.

This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders.
Projects
None yet
4 participants