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::{
|
pub use observation::{
|
||||||
Dest, NodeId, Observation, ObservationId, Payload, SigAlgo, Signature, Tick, Topic,
|
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 transport::{Event, FrameBuf, SendError, Transport};
|
||||||
pub use wire::{frame_sender, Frame, WireError};
|
pub use wire::{frame_sender, Frame, WireError};
|
||||||
|
|
||||||
|
|||||||
+17
-8
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts, exchange::Exchange, BeliefPolicy, Dest, NodeEvent, NodeId, Observation, ObservationId,
|
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`].
|
/// Errors returned by [`Node::observe`].
|
||||||
@@ -219,13 +219,22 @@ impl<S: Store, T: Transport> Node<S, T> {
|
|||||||
&self.store
|
&self.store
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable access to the backing store.
|
/// Lift the quarantine on held observations once the host has trusted wall
|
||||||
///
|
/// time, by folding the wall clock into a [`WallClockStore`] backend.
|
||||||
/// Used by the host for the trusted-time state machine — peer-observation
|
pub fn set_wall_clock(&mut self, wall_now: u64, now: Tick) -> <S as WallClockStore>::FoldReport
|
||||||
/// time estimation (`LogStore::peer_time_estimate`) and, once trusted,
|
where
|
||||||
/// anchoring the wall clock (`LogStore::set_wall_clock`).
|
S: WallClockStore,
|
||||||
pub fn store_mut(&mut self) -> &mut S {
|
{
|
||||||
&mut self.store
|
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
|
/// 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 {
|
pub(crate) fn current_age(obs: &Observation, stored_at: Tick, now: Tick) -> u64 {
|
||||||
obs.age_ms.saturating_add(now.saturating_sub(stored_at))
|
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() {
|
if self.clock.is_trusted() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(est) = self.tines.store_mut().peer_time_estimate(now) else {
|
let Some(est) = self.tines.peer_time_estimate(now) else {
|
||||||
return;
|
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);
|
self.clock.anchor_wall(est, now);
|
||||||
match report.downtime_ms {
|
match report.downtime_ms {
|
||||||
Some(ms) => println!("tines: trusted wall clock from peer (folded {ms} ms downtime)"),
|
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 std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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 {
|
mod crc32c {
|
||||||
//! A minimal, std-only CRC-32C (Castagnoli).
|
//! 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`.
|
/// Write the log header: `magic ‖ format_version ‖ reserved`.
|
||||||
fn write_header(file: &mut File) -> Result<(), StoreError> {
|
fn write_header(file: &mut File) -> Result<(), StoreError> {
|
||||||
file.write_all(&MAGIC).map_err(|_| StoreError::Backend)?;
|
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.
|
// quarantined — the robot's own fail-safe is outside tines.
|
||||||
if !clock.is_trusted() {
|
if !clock.is_trusted() {
|
||||||
let now = clock.now();
|
let now = clock.now();
|
||||||
if let Some(est) = node.store_mut().peer_time_estimate(now) {
|
if let Some(est) = node.peer_time_estimate(now) {
|
||||||
node.store_mut().set_wall_clock(est, now);
|
node.set_wall_clock(est, now);
|
||||||
clock.anchor_wall(est, now);
|
clock.anchor_wall(est, now);
|
||||||
eprintln!("tines: trusted wall clock from peer (estimate {est} ms)");
|
eprintln!("tines: trusted wall clock from peer (estimate {est} ms)");
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -691,7 +691,6 @@ fn smallest_age_wins() {
|
|||||||
|
|
||||||
let now = t;
|
let now = t;
|
||||||
let est = node_b
|
let est = node_b
|
||||||
.store_mut()
|
|
||||||
.peer_time_estimate(now)
|
.peer_time_estimate(now)
|
||||||
.expect("peer_time_estimate should find both observations");
|
.expect("peer_time_estimate should find both observations");
|
||||||
// The young observation's observed_at (wall + 5000) is newer, so the
|
// 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.
|
// which is A's broadcast.
|
||||||
let now_b = t;
|
let now_b = t;
|
||||||
let est = node_b
|
let est = node_b
|
||||||
.store_mut()
|
|
||||||
.peer_time_estimate(now_b)
|
.peer_time_estimate(now_b)
|
||||||
.expect("peer_time_estimate should find A's broadcast");
|
.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
|
// (unknown) downtime B is in, the fold ages its held observation past
|
||||||
// lifetime and lifts its quarantine (the "becomes visible" event): the fold
|
// lifetime and lifts its quarantine (the "becomes visible" event): the fold
|
||||||
// report counts exactly that one addressed observation expiring.
|
// 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!(
|
assert_eq!(
|
||||||
report.downtime_ms, None,
|
report.downtime_ms, None,
|
||||||
"unknown downtime for a fresh store"
|
"unknown downtime for a fresh store"
|
||||||
|
|||||||
Reference in New Issue
Block a user