client: Reorg to new best when finalizing divergent branch#2869
client: Reorg to new best when finalizing divergent branch#2869
Conversation
be88c6f to
068f966
Compare
Demi-Marie
left a comment
There was a problem hiding this comment.
This looks good to me, but I won’t approve it myself, as I am not familiar with this part of the codebase.
| /// to finalize next. | ||
| fn finality_target( | ||
| /// Get the best block in the fork containing `target_hash`, if any. | ||
| fn best_containing<'a>( |
There was a problem hiding this comment.
That's not the goal of finality_target -- the previous goal was to return the highest ancestor block which was suitable to finalize. This is typically the identity function applied to best_containing, but for e.g. Polkadot we need to have additional voting rules based on what erasure-coded pieces we've seen.
SelectChain is meant to make a distinction between what we optimistically think is best and what we are willing to commit to finalizing.
core/client/src/client.rs
Outdated
| // find the new best block that is a descendent of the finalized | ||
| // block. we already have the import lock acquired so there's no | ||
| // need to re-acquire it in `select_chain`. | ||
| Some(select_chain) => select_chain.best_containing(block, None, None)? |
There was a problem hiding this comment.
If this were finality_target, this wouldn't work. Neither would it if best_containing were implemented for Polkadot in a way that limited to only chains where availability pieces have been seen.
core/client/src/client.rs
Outdated
| Some(select_chain) => select_chain.best_containing(block, None, None)? | ||
| .unwrap_or(block), | ||
| // if no `select_chain` is provided set the finalized block as best | ||
| None => block, |
There was a problem hiding this comment.
In some situations, this is clearly OK, but I would worry that None is passed even in situations where we need it. Rather than use an Option, I recommend creating a dummy SelectChain for the cases where we currently return None, which always returns the known best block for best_containing.
core/client/src/client.rs
Outdated
| &self, | ||
| id: BlockId<Block>, | ||
| justification: Option<Justification>, | ||
| select_chain: Option<&dyn SelectChain<Block>>, |
There was a problem hiding this comment.
Same here. I don't know why the select_chain would ever be optional.
rphmeier
left a comment
There was a problem hiding this comment.
The finality_target interface doesn't support the needs we wanted it to anymore. And the select_chain being optional can lead to weird corner cases that I'd prefer to avoid.
Otherwise looks good.
|
My thoughts regarding
I wanted to somehow unfiy I also wanted to further simplify For Polkadot what I would imagine is that when we receive a new availability piece we check whether there's any leaf that has all the pieces, in which case it should be the new "best" and we set it explicitly through the client (this doesn't really work for the case where I take suggestions wrt how to deal with the light client integration. Should we just start tracking leaves there as well? |
|
looks stale... @rphmeier @andresilva what's happening here? |
|
@svyatonik could you have a look at this and do you have any suggestions regarding light client integration? |
|
Oh, I thought @rphmeier had complains - that's why I have ignored that PR until now :/ Will take a look |
Not necessarily. It should be equal to or a descendent of the block we would select to build upon. Which is going to be at most the K'th descendent of a chain we've seen every availability piece for. At heart, we need a couple different things:
The fork choice abstraction is tricky. We need to apply it upon block import but also upon "external events" like importing availability messages. It would be nice to do this lazily, but there doesn't seem to be an elegant way to integrate something like that with every call to |
|
From what I've understood, in Polkadot we'll need some complicated best-block/block-to-finalize selection strategies, which require some additional data that won't be available on light client (availability pieces?). So, no matter what, we can't guarantee that even if both light and full client have synced the same blocks, they still have same best-block (just because full client will have some additional data for decision making). So given that, imo:
So my vote is not to track leaves on light && instead always make last finalized block the best block on light nodes. Obviously, I can't be a final judge here - that's just my opinion. |
@svyatonik This is the approach that's implemented in this PR, but we only update the best block when finalizing if it's from a divergent branch compared to the current best block, otherwise we keep the current best block as is.
@rphmeier Doing it lazily makes sense since it might be potentially expensive, and given that constraint I better understand the original idea behind |
c9a3fbb to
3ff701c
Compare
|
@rphmeier Updated this PR with your suggestions. The light client code is ready to deal with an arbitrary |
b69bd29 to
da12388
Compare
rphmeier
left a comment
There was a problem hiding this comment.
We may want to rethink the BlockImport stuff a bunch. Originally they were intended to be composable. Maybe the SelectChain thing should be passed in with the ImportBlock parameter as opposed to being owned by the BlockImport.
e51d404 to
26424a1
Compare
|
I talked to @rphmeier about this and we decided the extra complexity added with I updated the PR so that when finalizing a block that diverges from the current best branch we set the finalized block as the best block (much simpler). I also needed to make some changes to the |
When we finalize a block on a divergent branch from the currently tracked best we need to figure out what the new best block is on the finalized branch and we need to re-org to it. This is done by having
finalize_blockoptionally take aSelectChainwhich is the pluggable logic for best fork selection (currently only longest chain is implemented). If noSelectChainis provided and the finalized block is on a divergent branch then we set the finalized block as best (this is the case for the light client since it doesn't track leaves). When importing a block that is finalized the same thing can happen but in this case we don't needSelectChainsince the given block that's being imported must be the new best.I also renamed the
finality_targetinSelectChainsince there's nothing relating to finality there (e.g. GRANDPA has further voting rules that restrict what we vote on), insteadSelectChainis strictly concerned with defining the notion of a best block, regardless of what we do with it.Fix #1442.