-
Notifications
You must be signed in to change notification settings - Fork 4
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: implement slot arithmetic #39
base: main
Are you sure you want to change the base?
Conversation
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.
Nice start, thanks for the early PR!
crates/amaru/src/time.rs
Outdated
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.
Let's put that in another crate actually 🫡
crates/amaru/src/time.rs
Outdated
pub bound_time: u64, // Milliseconds | ||
pub bound_slot: u64, | ||
pub bound_epoch: u64, |
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.
pub bound_time: u64, // Milliseconds | |
pub bound_slot: u64, | |
pub bound_epoch: u64, | |
pub time: u64, // Milliseconds | |
pub slot: u64, | |
pub epoch: u64, |
crates/amaru/src/time.rs
Outdated
pub start_slot: u64, | ||
pub end_slot: u64, |
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.
pub start_slot: u64, | |
pub end_slot: u64, | |
pub start: Slot, | |
pub end: Slot, |
And define somewhere type Slot = u64
, that better convey the intent IMO, even though it's semantically the same.
crates/amaru/src/time.rs
Outdated
#[derive(PartialEq, Eq)] | ||
pub struct EraParams { | ||
pub epoch_size: u64, | ||
pub slot_length: u64, // Milliseconds | ||
} |
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.
Given that epoch_size
and slot_length
can't be null, it might be worth making those fields private and defining a smart constructor enforcing those invariants.
crates/amaru/src/time.rs
Outdated
#[derive(PartialEq, Eq)] | ||
pub struct Summary { | ||
pub start: Bound, | ||
pub end: Bound, |
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 i recall correctly, eras don't necessarily have an upper-bound (the last one in particular). At least, it used to be like that but I recall it was changed around Babbage. Not sure how anymore. To be confirmed 😅 ...
crates/amaru/src/time.rs
Outdated
// The last era in the provided EraHistory must end at the time horizon for accurate results. The | ||
// horizon is the end of the epoch containing the end of the current era's safe zone relative to | ||
// the current tip. Returns number of milliseconds elapsed since the system start time. | ||
pub fn slot_to_relative_time(eras: &EraHistory, slot: u64) -> Result<u64, TimeHorizonError> { |
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.
Seems like all functions in this module are defined on &EraHistory
, so perhaps consider making them methods of that type?
crates/amaru/src/time.rs
Outdated
#[derive(PartialEq, Eq)] | ||
pub struct EraHistory { | ||
pub eras: Vec<Summary>, | ||
} |
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.
Note: we'll eventually have to persist this on disk; so defining CBOR (minicbor) encoding and decoding for it would be useful. JSON (via serde) is also arguably useful, though priority is on CBOR.
crates/amaru/src/time.rs
Outdated
match t1 { | ||
Ok(t) => { | ||
assert_eq!(t, 172800000); | ||
} | ||
_ => { | ||
panic!("expected no error"); | ||
} | ||
} |
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.
match t1 { | |
Ok(t) => { | |
assert_eq!(t, 172800000); | |
} | |
_ => { | |
panic!("expected no error"); | |
} | |
} | |
assert_eq!(t1, Ok(172800000)); |
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.
Both more concise and will provide a better error message showing the actual unexpected value.
crates/amaru/src/time.rs
Outdated
let bounds1 = epoch_bounds(&eras, 10); | ||
match bounds1 { | ||
Err(TimeHorizonError::PastTimeHorizon) => { | ||
} | ||
_ => { | ||
panic!("expected error"); | ||
} | ||
} |
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.
let bounds1 = epoch_bounds(&eras, 10); | |
match bounds1 { | |
Err(TimeHorizonError::PastTimeHorizon) => { | |
} | |
_ => { | |
panic!("expected error"); | |
} | |
} | |
assert_eq!(epoch_bounds(&eras, 10), Err(TimeHorizonError::PastTimeHorizon)) |
crates/amaru/src/time.rs
Outdated
panic!("expected error"); | ||
} | ||
} | ||
} |
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.
Same for all those 😅, pardon my laziness to suggest changes.
} | ||
|
||
// The start is inclusive and the end is exclusive. In a valid EraHistory, the | ||
// end of each era will equal the start of the next one. |
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.
Is this another constraint it makes sense to validate / enforce in an EraHistory
constructor @KtorZ?
i.e. just pass in a Vec<(Bound, Params)>
, and then use the start
of the next one as the end
of the previous one, with the last one taking a None
?
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.
It's one of those parts of the system where I think we need test vectors to ensure computations are identical across implementations. That's something I could contribute if you think that's useful (but not next week :) )
|
||
#[derive(Clone, PartialEq, Eq)] | ||
pub struct Bound { | ||
pub time: u64, // Milliseconds |
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.
suggestion: either rename to time_ms
or have a type Millis = u64
to remove the comment
pub struct Bound { | ||
pub time: u64, // Milliseconds | ||
pub slot: Slot, | ||
pub epoch: u64, |
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 I would also have a type Epoch = u64
but maybe overkill?
#[derive(Clone, PartialEq, Eq)] | ||
pub struct EraParams { | ||
epoch_size: u64, | ||
slot_length: u64, // Milliseconds |
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.
Same remark than above
|
||
#[derive(Clone, PartialEq, Eq)] | ||
pub struct EraParams { | ||
epoch_size: u64, |
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 would make the unit explicit, either through the type or name
let t0 = eras.slot_to_relative_time(172801); | ||
assert_eq!(t0, Err(TimeHorizonError::PastTimeHorizon)); | ||
let t1 = eras.slot_to_relative_time(172800); | ||
assert_eq!(t1, Ok(172800000)); |
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 a big fan of testing several things in a single test: When the test fails, it's hard to know quickly what's failed, you would have to look at the source code of the test to know exactly what broke, which is annoying.
I would suggest to:
- not prefix test functions with
test_
, we know they are tests because there's an annotation just above - have more explicit test names (eg.
slot_to_time_fails_given_its_past_upper_bound
) - have each function test a single "thing" (possibly using parameterised tests to avoid duplication)
let params = EraParams::new(86400, 1000).unwrap(); | ||
let eras = EraHistory { | ||
eras: vec![ | ||
Summary { |
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.
You could remove duplication by lifting this definition to be a constant within the test module perhaps?
} | ||
|
||
#[test] | ||
fn test_encode_era_history() { |
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.
One would typically want to have unit tests for encoding/decoding and a roundtrip property which is quite powerful to ensure whatever is encoded can be decoded and find edge cases.
this is a draft PR for slot arithmetic to help align on implementation as per last week's meeting.
this implements slot-to-time, time-to-slot, slot-to-epoch, and epoch boundary functions. these functions depend on the current state of the node. in particular, the start and end times, the slot lengths, and the epoch lengths of all known eras are required. this information is captured by an
EraHistory
type roughly corresponding to theInterpeter
type used inOuroboros.Consensus.HardFork.History.Qry
.just like in
Qry.hs
, these functions require that the latest era in the era history argument ends at the epoch of the safe zone (seeOuroboros.Consensus.HardFork.Combinator.State.Infra
), so care must be taken by the caller to ensure that an up-to-date era history is supplied. the end of the era history expresses that we can't definitively predict times beyond that point.to elaborate on the details, as far as i understand:
the expectations for this deliverable have been kept pretty loose so far, so i'm open to any feedback.