use crate::event::{Attestation, EventId, PublicKey};
use sashite_sanki_engine::domain::time::Timestamp;
#[must_use]
pub fn canonical_attestation(
attestations: &[Attestation],
attested: EventId,
timestamper: PublicKey,
) -> Option<&Attestation> {
attestations
.iter()
.filter(|attestation| attestation.attests == attested && attestation.signer == timestamper)
.min_by_key(|attestation| (attestation.created_at, attestation.id))
}
#[must_use]
pub fn canonical_timing(
attestations: &[Attestation],
event_id: EventId,
event_created_at: Timestamp,
timestamper: Option<PublicKey>,
) -> Option<Timestamp> {
match timestamper {
Some(ts) => canonical_attestation(attestations, event_id, ts).map(|a| a.created_at),
None => Some(event_created_at),
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
use super::{canonical_attestation, canonical_timing};
use crate::event::{Attestation, EventId, PublicKey};
use sashite_sanki_engine::domain::time::Timestamp;
const TIMESTAMPER: u8 = 99;
fn pk(byte: u8) -> PublicKey {
PublicKey::from_bytes([byte; 32])
}
fn eid(byte: u8) -> EventId {
EventId::from_bytes([byte; 32])
}
fn ts(secs: i64) -> Timestamp {
Timestamp::from_unix(secs)
}
fn att(id: u8, signer: u8, attests: u8, at: i64) -> Attestation {
Attestation::new(eid(id), pk(signer), eid(attests), ts(at))
}
fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
if items.len() <= 1 {
return vec![items.to_vec()];
}
let mut orders = Vec::new();
for index in 0..items.len() {
let mut rest = items.to_vec();
let head = rest.remove(index);
for mut order in permutations(&rest) {
order.insert(0, head.clone());
orders.push(order);
}
}
orders
}
#[test]
fn meta_resolution_smallest_created_at() {
let atts = vec![att(1, TIMESTAMPER, 50, 1000), att(2, TIMESTAMPER, 50, 900)];
let canonical = canonical_attestation(&atts, eid(50), pk(TIMESTAMPER)).expect("attested");
assert_eq!(canonical.created_at, ts(900));
}
#[test]
fn meta_resolution_tiebreak_by_attestation_id() {
let atts = vec![att(6, TIMESTAMPER, 50, 1000), att(5, TIMESTAMPER, 50, 1000)];
let canonical = canonical_attestation(&atts, eid(50), pk(TIMESTAMPER)).expect("attested");
assert_eq!(*canonical.id.as_bytes(), [5; 32]);
}
#[test]
fn meta_resolution_ignores_non_timestamper_signer() {
let atts = vec![att(1, 7, 50, 100)]; assert!(canonical_attestation(&atts, eid(50), pk(TIMESTAMPER)).is_none());
}
#[test]
fn meta_resolution_ignores_other_attested_event() {
let atts = vec![att(1, TIMESTAMPER, 51, 100)]; assert!(canonical_attestation(&atts, eid(50), pk(TIMESTAMPER)).is_none());
let atts = vec![att(1, TIMESTAMPER, 51, 100), att(2, TIMESTAMPER, 50, 900)];
assert_eq!(
canonical_timing(&atts, eid(50), ts(0), Some(pk(TIMESTAMPER))),
Some(ts(900))
);
}
#[test]
fn a_strangers_attestation_can_never_move_a_timing() {
let earlier = vec![att(1, TIMESTAMPER, 50, 1000), att(2, 7, 50, 100)];
assert_eq!(
canonical_timing(&earlier, eid(50), ts(0), Some(pk(TIMESTAMPER))),
Some(ts(1000))
);
let later = vec![att(1, TIMESTAMPER, 50, 1000), att(2, 7, 50, 5000)];
assert_eq!(
canonical_timing(&later, eid(50), ts(0), Some(pk(TIMESTAMPER))),
Some(ts(1000))
);
let tied = vec![att(9, TIMESTAMPER, 50, 1000), att(1, 7, 50, 1000)];
let canonical =
canonical_attestation(&tied, eid(50), pk(TIMESTAMPER)).expect("the designated one");
assert_eq!(*canonical.id.as_bytes(), [9; 32]);
let strangers = vec![att(1, 7, 50, 100), att(2, 8, 50, 200)];
assert_eq!(
canonical_timing(&strangers, eid(50), ts(4242), Some(pk(TIMESTAMPER))),
None
);
}
#[test]
fn attested_timing_ignores_the_events_own_created_at() {
let atts = vec![att(1, TIMESTAMPER, 50, 900)];
for claimed in [0, 123_456, -7] {
assert_eq!(
canonical_timing(&atts, eid(50), ts(claimed), Some(pk(TIMESTAMPER))),
Some(ts(900)),
"self-claim {claimed} leaked into the attested timing"
);
}
}
#[test]
fn self_timed_never_consults_attestations() {
let atts = vec![att(1, TIMESTAMPER, 50, 1), att(2, 7, 50, 2)];
assert_eq!(
canonical_timing(&atts, eid(50), ts(1234), None),
Some(ts(1234))
);
}
#[test]
fn meta_resolution_is_independent_of_the_input_order() {
let atts = [
att(1, TIMESTAMPER, 50, 100),
att(2, TIMESTAMPER, 50, 100),
att(3, 7, 50, 1),
att(4, TIMESTAMPER, 51, 1),
att(5, TIMESTAMPER, 50, 200),
];
for order in permutations(&atts) {
let canonical =
canonical_attestation(&order, eid(50), pk(TIMESTAMPER)).expect("a conforming one");
assert_eq!(*canonical.id.as_bytes(), [1; 32]);
assert_eq!(canonical.created_at, ts(100));
}
}
#[test]
fn extreme_timings_are_compared_not_saturated() {
let floor = vec![
att(1, TIMESTAMPER, 50, i64::MIN),
att(2, TIMESTAMPER, 50, 0),
];
assert_eq!(
canonical_timing(&floor, eid(50), ts(0), Some(pk(TIMESTAMPER))),
Some(ts(i64::MIN))
);
let ceiling = vec![att(1, TIMESTAMPER, 50, i64::MAX)];
assert_eq!(
canonical_timing(&ceiling, eid(50), ts(0), Some(pk(TIMESTAMPER))),
Some(ts(i64::MAX))
);
assert_eq!(
canonical_timing(&[], eid(50), ts(i64::MIN), None),
Some(ts(i64::MIN))
);
}
#[test]
fn self_timed_timing_uses_event_created_at() {
let atts: Vec<Attestation> = Vec::new();
assert_eq!(
canonical_timing(&atts, eid(50), ts(1234), None),
Some(ts(1234))
);
}
}