refactor(core): WallClockStore trait replaces the store_mut backdoor
Task 10b reached LogStore::set_wall_clock through a general Node::store_mut. Kyle: replace it with a narrower accessor. A WallClockStore trait (set_wall_clock + peer_time_estimate, implemented only by LogStore) now backs Node::set_wall_clock and Node::peer_time_estimate, so no &mut Store escapes the facade.
This commit is contained in:
@@ -25,7 +25,7 @@ pub use node::{Node, ObserveError};
|
||||
pub use observation::{
|
||||
Dest, NodeId, Observation, ObservationId, Payload, SigAlgo, Signature, Tick, Topic,
|
||||
};
|
||||
pub use store::{Store, StoreError, TrimPolicy};
|
||||
pub use store::{Store, StoreError, TrimPolicy, WallClockStore};
|
||||
pub use transport::{Event, FrameBuf, SendError, Transport};
|
||||
pub use wire::{frame_sender, Frame, WireError};
|
||||
|
||||
|
||||
+17
-8
@@ -12,7 +12,7 @@
|
||||
|
||||
use crate::{
|
||||
consts, exchange::Exchange, BeliefPolicy, Dest, NodeEvent, NodeId, Observation, ObservationId,
|
||||
Payload, Store, StoreError, Tick, Topic, Transport, TrimPolicy,
|
||||
Payload, Store, StoreError, Tick, Topic, Transport, TrimPolicy, WallClockStore,
|
||||
};
|
||||
|
||||
/// Errors returned by [`Node::observe`].
|
||||
@@ -219,13 +219,22 @@ impl<S: Store, T: Transport> Node<S, T> {
|
||||
&self.store
|
||||
}
|
||||
|
||||
/// Mutable access to the backing store.
|
||||
///
|
||||
/// Used by the host for the trusted-time state machine — peer-observation
|
||||
/// time estimation (`LogStore::peer_time_estimate`) and, once trusted,
|
||||
/// anchoring the wall clock (`LogStore::set_wall_clock`).
|
||||
pub fn store_mut(&mut self) -> &mut S {
|
||||
&mut self.store
|
||||
/// Lift the quarantine on held observations once the host has trusted wall
|
||||
/// time, by folding the wall clock into a [`WallClockStore`] backend.
|
||||
pub fn set_wall_clock(&mut self, wall_now: u64, now: Tick) -> <S as WallClockStore>::FoldReport
|
||||
where
|
||||
S: WallClockStore,
|
||||
{
|
||||
self.store.set_wall_clock(wall_now, now)
|
||||
}
|
||||
|
||||
/// Best estimate of trusted wall time from a peer's observation, if the
|
||||
/// backend supports it (see [`WallClockStore`]).
|
||||
pub fn peer_time_estimate(&self, now: Tick) -> Option<u64>
|
||||
where
|
||||
S: WallClockStore,
|
||||
{
|
||||
self.store.peer_time_estimate(now)
|
||||
}
|
||||
|
||||
/// Best-effort durability flush, advancing each stored observation's age
|
||||
|
||||
@@ -127,3 +127,25 @@ pub trait Store {
|
||||
pub(crate) fn current_age(obs: &Observation, stored_at: Tick, now: Tick) -> u64 {
|
||||
obs.age_ms.saturating_add(now.saturating_sub(stored_at))
|
||||
}
|
||||
|
||||
/// A [`Store`] that a host can feed trusted wall-clock time into.
|
||||
///
|
||||
/// tines-core itself never deals with wall time — this trait is a host-facing
|
||||
/// hook so a host can lift the quarantine on observations held across a
|
||||
/// power-off, and estimate trusted time from a peer's observation, without a
|
||||
/// general `&mut Store` backdoor (decision F in the spec). Only backends that
|
||||
/// support a wall-clock fold implement it; `MemStore` and other pure-tick
|
||||
/// stores do not.
|
||||
pub trait WallClockStore: Store {
|
||||
/// Summary of a wall-clock fold, for the host's logs.
|
||||
type FoldReport;
|
||||
|
||||
/// Fold trusted wall time into the store, lifting the quarantine on held
|
||||
/// observations. The host calls this exactly once, on the first trusted
|
||||
/// time.
|
||||
fn set_wall_clock(&mut self, wall_now: u64, now: Tick) -> Self::FoldReport;
|
||||
|
||||
/// Best estimate of trusted wall time from a peer's observation stored
|
||||
/// since the last open, if any. `None` means stay quarantined.
|
||||
fn peer_time_estimate(&self, now: Tick) -> Option<u64>;
|
||||
}
|
||||
|
||||
@@ -223,10 +223,10 @@ impl TinesNode {
|
||||
if self.clock.is_trusted() {
|
||||
return;
|
||||
}
|
||||
let Some(est) = self.tines.store_mut().peer_time_estimate(now) else {
|
||||
let Some(est) = self.tines.peer_time_estimate(now) else {
|
||||
return;
|
||||
};
|
||||
let report = self.tines.store_mut().set_wall_clock(est, now);
|
||||
let report = self.tines.set_wall_clock(est, now);
|
||||
self.clock.anchor_wall(est, now);
|
||||
match report.downtime_ms {
|
||||
Some(ms) => println!("tines: trusted wall clock from peer (folded {ms} ms downtime)"),
|
||||
|
||||
+13
-1
@@ -88,7 +88,7 @@ use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tines_core::{Observation, ObservationId, Store, StoreError, Tick, TrimPolicy};
|
||||
use tines_core::{Observation, ObservationId, Store, StoreError, Tick, TrimPolicy, WallClockStore};
|
||||
|
||||
mod crc32c {
|
||||
//! A minimal, std-only CRC-32C (Castagnoli).
|
||||
@@ -802,6 +802,18 @@ impl LogStore {
|
||||
}
|
||||
}
|
||||
|
||||
impl WallClockStore for LogStore {
|
||||
type FoldReport = FoldReport;
|
||||
|
||||
fn set_wall_clock(&mut self, wall_now: u64, now: Tick) -> FoldReport {
|
||||
LogStore::set_wall_clock(self, wall_now, now)
|
||||
}
|
||||
|
||||
fn peer_time_estimate(&self, now: Tick) -> Option<u64> {
|
||||
LogStore::peer_time_estimate(self, now)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the log header: `magic ‖ format_version ‖ reserved`.
|
||||
fn write_header(file: &mut File) -> Result<(), StoreError> {
|
||||
file.write_all(&MAGIC).map_err(|_| StoreError::Backend)?;
|
||||
|
||||
+2
-2
@@ -238,8 +238,8 @@ where
|
||||
// quarantined — the robot's own fail-safe is outside tines.
|
||||
if !clock.is_trusted() {
|
||||
let now = clock.now();
|
||||
if let Some(est) = node.store_mut().peer_time_estimate(now) {
|
||||
node.store_mut().set_wall_clock(est, now);
|
||||
if let Some(est) = node.peer_time_estimate(now) {
|
||||
node.set_wall_clock(est, now);
|
||||
clock.anchor_wall(est, now);
|
||||
eprintln!("tines: trusted wall clock from peer (estimate {est} ms)");
|
||||
}
|
||||
|
||||
+1
-3
@@ -691,7 +691,6 @@ fn smallest_age_wins() {
|
||||
|
||||
let now = t;
|
||||
let est = node_b
|
||||
.store_mut()
|
||||
.peer_time_estimate(now)
|
||||
.expect("peer_time_estimate should find both observations");
|
||||
// The young observation's observed_at (wall + 5000) is newer, so the
|
||||
@@ -744,7 +743,6 @@ fn time_from_peer() {
|
||||
// which is A's broadcast.
|
||||
let now_b = t;
|
||||
let est = node_b
|
||||
.store_mut()
|
||||
.peer_time_estimate(now_b)
|
||||
.expect("peer_time_estimate should find A's broadcast");
|
||||
|
||||
@@ -763,7 +761,7 @@ fn time_from_peer() {
|
||||
// (unknown) downtime B is in, the fold ages its held observation past
|
||||
// lifetime and lifts its quarantine (the "becomes visible" event): the fold
|
||||
// report counts exactly that one addressed observation expiring.
|
||||
let report = node_b.store_mut().set_wall_clock(est, now_b);
|
||||
let report = node_b.set_wall_clock(est, now_b);
|
||||
assert_eq!(
|
||||
report.downtime_ms, None,
|
||||
"unknown downtime for a fresh store"
|
||||
|
||||
Reference in New Issue
Block a user