-
Notifications
You must be signed in to change notification settings - Fork 30
Refactor: SelectView
= BlockNo
× TiebreakerView
#1591
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
Conversation
instance NoThunks (TiebreakerView p) => NoThunks (SelectView p) | ||
|
||
-- | First compare block numbers, then compare the 'TiebreakerView'. | ||
deriving stock instance Ord (TiebreakerView p) => Ord (SelectView p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be an explicitly written-out Ord
instance? it would be bad if someone decided to reorder the fields in SelectView
and catastrophically broke chain selection lol
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, pushed 👍
Irrational grudge of mine: I always found it a bit depressing to write such instances by hand; motivated by #549, I wrote https://gist.github.com/amesgen/0ddcbe0e2bae458ba8b40d4799d1b6e4 (essentially a "surgery" in the sense of generic-data) that allowed you to permute the constructors of a data type in the Generic
representation for DerivingVia
. The same thing of course works for fields instead of constructors; with that, we could write
deriving
via Generically (PermuteFields ["svBlockNo", "svTiebreakerView"] (SelectView p))
instance Ord (TiebreakerView p) => Ord (SelectView p)
which is now resistant to syntactic reordering in the data declaration of data SelectView
. All that to avoid writing
instance Ord (TiebreakerView p) => Ord (SelectView p) where
compare =
mconcat
[ compare `on` svBlockNo
, compare `on` svTiebreakerView
]
by hand 💪 💪 💪
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I left a couple of comments. Let me know when the TODOs are addressed and a new review round is required 👍
...consensus-cardano/src/ouroboros-consensus-cardano/Ouroboros/Consensus/Cardano/CanHardFork.hs
Show resolved
Hide resolved
ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Protocol/Abstract.hs
Show resolved
Hide resolved
{ svBlockNo :: !BlockNo | ||
, svTiebreakerView :: !(TiebreakerView p) | ||
} | ||
deriving stock Generic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably unrelated to this PR, but I was wondering if we could elaborate on why the block number (which will become a weigth) and the notion of a tie-breaker cannot be unified into a single weight notion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to capture this in the Haddocks 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it's still unclear to me (the answer to the question above, the haddocks comment is clear :D). It seems that ultimately, we could write a function to sort two select views which take the information of the tie-breaker into consideration. Do we want to keep the block number and tiebreaker separately because of the separation that we currently have between sorting candidates and preferring a chain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not exactly sure what unification you have in mind, is it something different than what we have in current main
, where everybody defines SelectView as a "single" thing determining the chain order for themselves? Or sth else entirely?
we could write a function to sort two select views which take the information of the tie-breaker into consideration.
Are you expecting something different than in the Ord
/ChainOrder
instances for SelectView
in this PR?
Do we want to keep the block number and tiebreaker separately because of the separation that we currently have between sorting candidates and preferring a chain?
It is unrelated to that distinction; the motivation is
- (minor) to make it explicit that
BlockNo
must be the primary way of comparing chains; even before this PR,ConsensusProtocol
s didn't have the freedom to use arbitrarySelectView
s, and - nicely supports weighted chain comparisons, see "Primary motivation" in the PR description, as well as Introduce weighted chain comparisons #1594.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FTR, we discussed this offline with @amesgen. The key constraint we have is that we rely on a transitive Ord
instance when selecting candidates. Therefore, we cannot unify the sorting of Views
in a single function. I think that we have is indeed two comparison functions that operate on SelectView
s. It's just that we're explicit about the part of the field that is used for breaking ties (which I think makes sense)
05b7cb1
to
adb6891
Compare
adb6891
to
13c868f
Compare
...nsensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Protocol/ChainSel.hs
Outdated
Show resolved
Hide resolved
13c868f
to
9b07e31
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great.
ouroboros-consensus-cardano/changelog.d/20250716_190309_alexander.esgen_tiebreaker_view.md
Show resolved
Hide resolved
@@ -205,7 +211,7 @@ instance TranslateProto singleProto singleProto where | |||
translateChainDepState _ = id | |||
|
|||
-- | The chain order of some type; in the Consensus layer, this will always be | |||
-- the 'SelectView' of some 'ConsensusProtocol'. | |||
-- the 'TiebreakerView'/'SelectView' of some 'ConsensusProtocol'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we mention both in this comment? Would it make sense to clarify when we use one and when the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we have instances for both of them, where ChainOrder SelectView
uses ChainOrder TiebreakerView
. I explicitly mentioned this also here 👍
{ svBlockNo :: !BlockNo | ||
, svTiebreakerView :: !(TiebreakerView p) | ||
} | ||
deriving stock Generic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it's still unclear to me (the answer to the question above, the haddocks comment is clear :D). It seems that ultimately, we could write a function to sort two select views which take the information of the tie-breaker into consideration. Do we want to keep the block number and tiebreaker separately because of the separation that we currently have between sorting candidates and preferring a chain?
9b07e31
to
5cdc439
Compare
This PR does not change any behavior; most of the diff is purely mechanical.
Previously, the
SelectView
s of allConsensusProtocol
s contained theBlockNo
as the most important criterion for comparing chains, with only tiebreaking behavior (among chains of equal length) being different across different protocols.This PR makes this explicit:
SelectView
is now longer an associated type family ofConsensusProtocol
, but rather an ordinary data typewhere
TiebreakerView
is now an associated type family ofConsensusProtocol
.This PR also removes the
WithBlockNo
type that the HFC was already using to always attach aBlockNo
to itsHardForkSelectView
. The code can be simpler now thatSelectView
s always automatically contain an explicitBlockNo
.Also see #1542 (comment)
Primary motivation
The main motivation for this is to enable weighted chain selection in Peras (tweag/cardano-peras#62), which can now be easily modeled by adding the weight of a chain to the block number component, at least morally; we probably want to eventually have more type safety here to make it hard to "forget" to account for the weight, such as defining
If the only goal were to minimize the diff, then it would also be possible to add a function
(BlockNo -> BlockNo) -> SelectView p -> SelectView p
, but it seems more honest to "properly" extract out the block number.