-
Notifications
You must be signed in to change notification settings - Fork 608
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
Add modules for deterministic total ordering derivation from partial ord #1335
Conversation
The intention of this logic is to allow us to specify partial orderings when orderings are needed (e.g. beginblock, endblock, upgrade orders), and then have an automatic derivation of the relevant total ordering. partialord_test.go gives a sense of how the API would look if we went with this.
beginBlockOrd := partialord.NewPartialOrdering(moduleNames) | ||
beginBlockOrd.FirstElements("upgrades", "epochs", "capabilities") | ||
beginBlockOrd.After("ibctransfers", "ibc") | ||
beginBlockOrd.After("bech32ibc", "ibctransfers") | ||
beginBlockOrd.Before("mint", "distribution") | ||
// This is purely just to test last functionality, doesn't make sense in context | ||
beginBlockOrd.LastElements("auth", "authz", "wasm") |
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.
This should be what the app.go ordering logic ends up looking like.
I think in practice, we will just use Before
syntax, and not mix After
and Before
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 we then avoid exposing the unused API?
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 don't think so, what makes sense depends on the context of a given application
mixing before and after would be quite weird for the reader though
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.
If my understanding of the inverse relationship between Before
and After
is right (i.e. beginBlockOrd.After("ibctransfers", "ibc")
is the same as beginBlockOrd.Before("ibc", "ibctransfers")
)
I'm still not quite sure, why do we need to expose both? In what context do we need both if we can substitute one for the other by switching the order of arguments?
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.
Similar reason to why int libraries expose >
and <=
Codecov Report
@@ Coverage Diff @@
## main #1335 +/- ##
==========================================
+ Coverage 19.53% 19.82% +0.29%
==========================================
Files 200 202 +2
Lines 27532 27685 +153
==========================================
+ Hits 5377 5489 +112
- Misses 21153 21175 +22
- Partials 1002 1021 +19
Continue to review full report at Codecov.
|
I did a brief survey of other dag implementations before implementing this one, and I couldn't find any that followed either of:
Folks like hashicorp maintain an unexposed dag internally for dependency tracking things, but they don't have the same determinism requirements we have in the SDK. They put it in an internal package, so we couldn't use it even if we wanted. Seemed excessive to copy that rather than just writing a short file here imo, especially given how simple it is for this use case, and the ease of custom helpers like AddFirstElements and AddLastElements. |
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 think this is an awesome design, was really interesting to review this. Great work! I really like how much more readable and less prone to user input errors the new API will make it in app.go
Most of my comments are readability-related nits. However, I think that we should add more tests:
- Test for
AddLast
indag_test.to
- Tests in
partialord_test.go
related to the sequence of calls of the API- Aside from helping to spot bugs, if any, these tests should be helpful to understand the usage of the API
beginBlockOrd := partialord.NewPartialOrdering(moduleNames) | ||
beginBlockOrd.FirstElements("upgrades", "epochs", "capabilities") | ||
beginBlockOrd.After("ibctransfers", "ibc") | ||
beginBlockOrd.After("bech32ibc", "ibctransfers") | ||
beginBlockOrd.Before("mint", "distribution") | ||
// This is purely just to test last functionality, doesn't make sense in context | ||
beginBlockOrd.LastElements("auth", "authz", "wasm") |
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 we then avoid exposing the unused API?
Thanks for the detailed review! |
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.
LGTM! This is an awesome implementation, and I love how we could existing problems of manually listing orders of Enblockers using dag and total ordering!
General question I have would be what were the possible places in this implementation that could have caused non determinism and how do we ensure "deterministic ordering"?
directedEdgeList := make([]map[int]int8, len(dag.nodeNameToId)) | ||
for i := 0; i < len(dag.nodeNameToId); i++ { | ||
originalEdgeList := dag.directedEdgeList[i] | ||
directedEdgeList[i] = make(map[int]int8, len(originalEdgeList)) | ||
for k, v := range originalEdgeList { | ||
directedEdgeList[i][k] = v | ||
} | ||
} | ||
// we re-use nodeNameToId and idToNodeNames as these are fixed at dag creation. |
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.
What's the reason we iterate to copy Dag instead of directly using dag.directedEdgeList
? Is that to ensure we deep copy edge list?
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.
Yeah, the topological sort algorithm used basically removes edges from every node marked as 'done', So we have to remove these edges from a copy.
More space efficient things can technically be done, but should never be important at the relevant sizes here.
Co-authored-by: Matt, Park <[email protected]>
…s into dev/partial_ordering
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.
LGTM
Thanks for the really thorough reviews here 🙏 |
Component of #1329
What is the purpose of the change
The intention of this logic is to allow us to specify partial orderings when orderings are needed (e.g. beginblock, endblock, upgrade orders),and then have an automatic derivation of the relevant total ordering.
The problem faced right now is the important relations are obscured in app.go at the moment, and its a lot of tedious logic to verify, for an area that can have subtle errors slip through. The aim of this change is to remedy this.
Brief change log
Testing and Verifying
This change added tests and can be verified as follows:
I mainly wrote this for fun, its not really a priority if people don't have bandwidth to review. If you have a couple minutes, would appreciate feedback on if others agree
partialord_test.go
is a better API than what exists right now (osmosis/app/modules.go
Lines 225 to 254 in ed10116
This PR intentionally does not change app.go, and just shows the API via a test, so that we can do a moreful check of all orderings independently of logic / agreeing on the approach.
Documentation
Unreleased
section inCHANGELOG.md
? yes