Skip to content
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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft

Conversation

rrruko
Copy link

@rrruko rrruko commented Dec 18, 2024

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 the Interpeter type used in Ouroboros.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 (see Ouroboros.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:

  • an era transition is considered to have been announced if the block confirming the transition is 'stable', i.e. it is at least k blocks behind the tip.
  • at any given time, if an era transition has not been announced, it is guaranteed not to happen in the next 3k/f slots (this is the "safe zone").
  • because era transitions only happen at epoch boundaries, an era transition is guaranteed not to happen until the next epoch after the end of the safe zone.
  • this means that we can safely use the current era's parameters until the start of the epoch after the end of the safe zone.

the expectations for this deliverable have been kept pretty loose so far, so i'm open to any feedback.

Copy link
Contributor

@KtorZ KtorZ left a 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 Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
crates/amaru/src/time.rs Outdated Show resolved Hide resolved
}

// 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.
Copy link
Contributor

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?

Copy link
Contributor

@abailly abailly left a 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 :) )

crates/time/src/lib.rs Outdated Show resolved Hide resolved
pub struct Bound {
pub time: u64, // Milliseconds
pub slot: Slot,
pub epoch: u64,
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark than above

crates/time/src/lib.rs Outdated Show resolved Hide resolved
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));
Copy link
Contributor

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)

crates/time/src/lib.rs Outdated Show resolved Hide resolved
crates/time/src/lib.rs Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants