-
Notifications
You must be signed in to change notification settings - Fork 256
Document how fork choice actually works #3700
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
Changes from all commits
cdad48b
15f2197
6e50da6
66c5ee0
7cdff2b
37206e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,7 +44,7 @@ use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentInde | |||||||||
| use subspace_core_primitives::solutions::SolutionRange; | ||||||||||
| use subspace_core_primitives::{BlockNumber, PublicKey}; | ||||||||||
| use subspace_proof_of_space::Table; | ||||||||||
| use subspace_verification::{PieceCheckParams, VerifySolutionParams, calculate_block_weight}; | ||||||||||
| use subspace_verification::{PieceCheckParams, VerifySolutionParams, calculate_block_fork_weight}; | ||||||||||
| use tracing::warn; | ||||||||||
|
|
||||||||||
| /// Notification with number of the block that is about to be imported and acknowledgement sender | ||||||||||
|
|
@@ -601,7 +601,7 @@ where | |||||||||
| let block_hash = block.post_hash(); | ||||||||||
| let block_number = *block.header.number(); | ||||||||||
|
|
||||||||||
| // Early exit if block already in chain | ||||||||||
| // Early exit if the block is already in the chain, and never treat it as the best block. | ||||||||||
| match self.client.status(block_hash)? { | ||||||||||
| sp_blockchain::BlockStatus::InChain => { | ||||||||||
| block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); | ||||||||||
|
|
@@ -637,16 +637,27 @@ where | |||||||||
| .await?; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Find the solution range weight of the chain with the parent block at its tip. | ||||||||||
| let parent_weight = if block_number.is_one() { | ||||||||||
| // The genesis block is given a zero fork weight. | ||||||||||
| 0 | ||||||||||
| } else { | ||||||||||
| // Parent block weight might be missing in special sync modes where block is imported in | ||||||||||
| // the middle of the blockchain history directly | ||||||||||
| // Parent block fork weight might be missing in special sync modes where the block is | ||||||||||
| // imported in the middle of the blockchain history directly. For forks off the same | ||||||||||
| // parent, this doesn't change the comparison outcome. | ||||||||||
| // | ||||||||||
| // For forks off different parents, this only changes the outcome if the fork is over an | ||||||||||
| // era transition. (Solution ranges are fixed within the same era.) In this case, the | ||||||||||
| // rest of the connected nodes will quickly converge, because they have weights starting | ||||||||||
| // further back. This convergence will overwhelm the inconsistent fork choices of any | ||||||||||
| // nodes that are currently snap syncing. | ||||||||||
| aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())? | ||||||||||
| .unwrap_or_default() | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| let added_weight = calculate_block_weight(subspace_digest_items.solution_range); | ||||||||||
| // We prioritise narrower (numerically smaller) solution ranges, using an inverse | ||||||||||
| // calculation. | ||||||||||
| let added_weight = calculate_block_fork_weight(subspace_digest_items.solution_range); | ||||||||||
| let total_weight = parent_weight.saturating_add(added_weight); | ||||||||||
|
|
||||||||||
| aux_schema::write_block_weight(block_hash, total_weight, |values| { | ||||||||||
|
|
@@ -672,18 +683,39 @@ where | |||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // The fork choice rule is that we pick the heaviest chain (i.e. smallest solution range), | ||||||||||
| // if there's a tie we go with the longest chain | ||||||||||
| // The fork choice rule is the largest fork solution range weight. | ||||||||||
| // | ||||||||||
| // This almost always prioritises: | ||||||||||
| // - the longest chain (the largest number of solutions), and if there is a tie | ||||||||||
| // - the strictest solutions (the numerically smallest solution ranges). | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solutions do not matter. As you can see above, the calculation of added weight is only based on the solution range in the era, and not the quality (strictness in your terminology) of individual solutions. It was (incorrectly) based on quality of individual solutions long time ago during one of the testnets and it was susceptible to block withholding attacks and maybe something else too. So it is just the heaviest chain rule like Jeremy commented originally.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, this could be clearer as
Suggested change
|
||||||||||
| // | ||||||||||
| // If these totals are equal: | ||||||||||
| // - each node keeps the block it already chose (the one that it processed first). | ||||||||||
| // | ||||||||||
| // If there is no previous best block, or the old best block is missing a weight, or has a | ||||||||||
| // zero weight: | ||||||||||
| // - the new block is chosen as the best block, as long as it has a non-zero weight. | ||||||||||
| // (The only blocks with zero weights are in the test runtime.) | ||||||||||
| // | ||||||||||
| // Solution ranges only change at the end of each era, where different block times can make | ||||||||||
| // the range in each fork different. This can lead to some edge cases: | ||||||||||
| // - one fork accepts a solution as within its range, but another with a narrower range | ||||||||||
| // does not, or | ||||||||||
|
Comment on lines
+702
to
+703
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this description makes a lot of sense. Era transition is based on the block number. So if you have different solutions, the parents must be different as well. A different fork will not accept the block not because its solution is not good enough, but rather because the parent is invalid/unknown to begin with.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, this would be clearer as:
Suggested change
It was meant to be a security argument about difficulty, not solution re-use. |
||||||||||
| // - a fork with a narrower range outweighs a fork with a wider range, leading to a reorg | ||||||||||
| // to a fork with fewer blocks. | ||||||||||
| // | ||||||||||
| // But these will be resolved with very high probability after a few blocks, assuming the | ||||||||||
| // network is well-connected. | ||||||||||
| let fork_choice = { | ||||||||||
| let info = self.client.info(); | ||||||||||
|
|
||||||||||
| let last_best_weight = if &info.best_hash == block.header.parent_hash() { | ||||||||||
| // the parent=genesis case is already covered for loading parent weight, so we don't | ||||||||||
| // need to cover again here | ||||||||||
| // The "parent is genesis" case is already covered when loading parent weight, so we don't | ||||||||||
| // need to cover it again here. | ||||||||||
| parent_weight | ||||||||||
| } else { | ||||||||||
| // Best block weight might be missing in special sync modes where block is imported | ||||||||||
| // in the middle of the blockchain history right after genesis | ||||||||||
| // The best block weight might be missing in special sync modes where the block is | ||||||||||
| // imported in the middle of the blockchain history, right after importing genesis. | ||||||||||
| aux_schema::load_block_weight(&*self.client, info.best_hash)?.unwrap_or_default() | ||||||||||
| }; | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,3 +33,5 @@ std = [ | |
| "subspace-kzg?/std", | ||
| "thiserror/std" | ||
| ] | ||
| # Activate test APIs and workarounds | ||
| testing = [] | ||
Uh oh!
There was an error while loading. Please reload this page.