-
Notifications
You must be signed in to change notification settings - Fork 75
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
feat(sequencer): make ABCI response for account balances deterministic #1574
Conversation
balance_request()
deterministic
Note on the PR title:
|
/// If the denoms are the same type, returns their comparison. Otherwise, returns IBC prefixed | ||
/// denoms as less than trace prefixed denoms. | ||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||
match self { |
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.
Don't nest matches but instead match on tuples:
use Self::{TracePrefixed, IbcPrefixed};
match (self, other) {
(TracePrefixed(lhs), TracePrefixed(rhs)) => lhs.cmp(rhs),
(TracePrefixed(_), IbcPrefixed(_)) => Ordering::Less,
(IbcPrefixed(lhs), IbcPrefixed(rhs)) => lhs.cmp(rhs),
(IbcPrefixed(_), TracePrefixed(_)) => Prdering::Greater,
}
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.
Was unable to import Self
due to Rust's restrictions, but implemented the tuples in 535c1c4.
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.
For the Ord
impl here, can you provide exhaustive tests that demonstrate and document the order of the denoms?
Naively, I would expect a lexical ordering among trace-prefixed and ibc-prefixed denoms, so it would be great to have tests that also demonstrate that ordering.
I think I should also walk back my suggestion to order ibc-prefixed, trace-prefixed later: a naive implementation that keeps ibc denominations in string form (without parsing) would just use lexical ordering of the strings. I think the parsed variant here should also reflect that.
In addition to unit tests for IbcPrefixed
and TracePrefixed
, do a more complex example:
- create a "string list" of ibc denomination strings (mixing both trace-prefixed and ibc-prefixed).
- parse this list as a
Denom list
. - sort the string list (this will give lexical order).
- sort the denom list by its
Ord
implementation and then useto_string
to convert all elements to strings again - assert that results of steps 3 and 4 are the same.
/// Custom deterministic sorting function for `AssetBalance` that sorts by balance in descending | ||
/// order and then by denom in ascending order. This function should never return `Ordering::Equal`, | ||
/// as calling `sort_unstable_by` on equal values has a non-deterministic result. | ||
fn compare_asset_balances(lhs: &AssetBalance, rhs: &AssetBalance) -> std::cmp::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.
Rethinking this: these should just be sorted by lexical order of the denominations.
You cannot compare the magnitude of different quantities, so we shouldn't order by them.
@@ -742,4 +862,76 @@ mod tests { | |||
let denom = denom_str.parse::<Denom>().unwrap(); | |||
assert_eq!(denom_str.len(), denom.display_len()); | |||
} | |||
|
|||
#[test] | |||
fn ibc_prefixed_ord_matches_lexical_sort() { |
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'd like to have these be more atomic and not sort a vec (it's pretty difficult to read).
Instead do something like:
#[track_caller]
fn assert_denom_order<T: FromStr>(left: &str, right: &str]) {
let left_denom: T = left.parse().unwrap();
let right_denom: T = right.parse().unwrap();
assert_eq!(left_denom.cmp(&right_denom), left.cmp(right));
}
#[test]
fn denoms_are_lexically_ordered() {
assert_denom_order::<Denom>("ibc/etc", "trace/etc");
todo!("and all the rest");
}
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.
Did something similar in 0e5eeb5, let me know if it works for you!
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 looks great, thanks! The PartialOrd/Ord impls on Denom
will be useful for a small PR I am writing right now.
Please remove the comment above balances.sort_unstable_by
, but otherwise this looks good (for someone with knowledge of the Rust std the use of sort_unstable
is not unexpected, and its behavior does not need to be further explained: even if there were two keys of the same value and their position exchanged, this would not be an observable effect).
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.
Are any of the PartialEq
, Eq
, Ord
, and PartialOrd
impls even necessary at this point?
If they are not necessary, I'd rather remove them.
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've removed the impls for Ord
and PartialOrd
from AssetBalance
, but aside from that all of them are necessary. Denom::cmp
calls either IbcPrefixed::cmp
or TracePrefixed::cmp
, and trace calls TraceSegments::cmp
, etc.
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, those make sense. I just thought of AssetBalance
:)
Summary
Ensures that a response to an ABCI request for account balances is deterministic.
Background
Previously was not sorted, and the
AssetBalance
stream is non-deterministic, which isn't great for an external API.Changes
AssetBalance
s firstly in descending order by balance, then in ascending order by name if the balances are equal.Testing
Added unit test to ensure the sorting function works as expected.
Related Issues
closes #1451