use crate::error::ConsensusError;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AdvancePayload {
pub at_least: u64,
}
impl AdvancePayload {
#[must_use]
pub fn merge(self, current: u64) -> u64 {
current.max(self.at_least)
}
}
pub fn reject_out_of_range_advance(at_least: u64) -> Result<(), ConsensusError> {
if at_least > tsoracle_core::PHYSICAL_MS_MAX {
return Err(ConsensusError::AdvanceOutOfRange(at_least));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
#[test]
fn advance_payload_postcard_pins_at_least_varint() {
let payload = AdvancePayload { at_least: 5 };
let bytes = postcard::to_stdvec(&payload).expect("serialize");
assert_eq!(bytes, vec![5]);
let back: AdvancePayload = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, payload);
}
#[cfg(feature = "serde")]
#[test]
fn advance_payload_postcard_round_trips_extremes() {
for at_least in [0u64, 1, u64::MAX] {
let payload = AdvancePayload { at_least };
let bytes = postcard::to_stdvec(&payload).expect("serialize");
let back: AdvancePayload = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(back, payload);
}
}
#[test]
fn merge_is_monotone_under_reordering_and_retries() {
assert_eq!(
AdvancePayload { at_least: 7 }.merge(3),
7,
"higher advance ratchets up"
);
assert_eq!(
AdvancePayload { at_least: 3 }.merge(7),
7,
"stale advance is absorbed"
);
assert_eq!(
AdvancePayload { at_least: 5 }.merge(5),
5,
"equal advance is idempotent"
);
assert_eq!(
AdvancePayload { at_least: 0 }.merge(0),
0,
"zero from a fresh state stays zero"
);
assert_eq!(
AdvancePayload { at_least: u64::MAX }.merge(0),
u64::MAX,
"the extreme advance ratchets to the maximum",
);
}
#[test]
fn reject_out_of_range_advance_accepts_up_to_the_cap() {
reject_out_of_range_advance(0).expect("zero is in range");
reject_out_of_range_advance(tsoracle_core::PHYSICAL_MS_MAX)
.expect("the maximum physical_ms is in range");
}
#[test]
fn reject_out_of_range_advance_rejects_above_the_cap_as_variant() {
for at_least in [tsoracle_core::PHYSICAL_MS_MAX + 1, u64::MAX] {
let err = reject_out_of_range_advance(at_least)
.expect_err("a value above the cap must be rejected");
match err {
ConsensusError::AdvanceOutOfRange(value) => assert_eq!(value, at_least),
other => panic!("expected AdvanceOutOfRange, got {other:?}"),
}
}
}
}