pub struct Cluster { /* private fields */ }Expand description
A set of in-process eidetica peers wired for multi-peer sync. Each peer is a
full Instance with its own backend. The cluster owns the wiring; the test
owns the databases, the auth, and the order of operations.
Implementations§
Source§impl Cluster
impl Cluster
Sourcepub fn builder() -> ClusterBuilder
pub fn builder() -> ClusterBuilder
Start building a cluster. Defaults: 2 peers, per-peer FixedClock,
HttpLoopback transport.
Sourcepub fn peer_mut(&mut self, i: usize) -> &mut Peer
pub fn peer_mut(&mut self, i: usize) -> &mut Peer
Mutable access to a peer, for create_database / open_database on its
User.
Sourcepub async fn bootstrap(
&mut self,
from: usize,
to: usize,
tree: &ID,
permission: Permission,
) -> Result<()>
pub async fn bootstrap( &mut self, from: usize, to: usize, tree: &ID, permission: Permission, ) -> Result<()>
Have peer to bootstrap tree from peer from: request the tree with
to’s own key (asking for permission), flush, and track it locally
(sync disabled — the test turns on serve/auto_sync if it wants
more). The joining-peer dance as one call.
permission is the access level to requests; the harness does not pick
it — auth posture stays the test’s. from must already be serving tree
(see Peer::serve) with a policy that admits this request.
Sourcepub async fn exchange(&self, from: usize, to: usize, tree: &ID) -> Result<()>
pub async fn exchange(&self, from: usize, to: usize, tree: &ID) -> Result<()>
Drive a sync exchange for tree, initiated by peer from against peer
to. eidetica’s sync_with_peer exchanges in both directions, so after
this both peers hold each other’s entries for the tree. Both sync queues
are flushed; flush errors propagate (this is a bug-finding tool — it does
not swallow them).
Sourcepub async fn auto_sync(&mut self, a: usize, b: usize, tree: &ID) -> Result<()>
pub async fn auto_sync(&mut self, a: usize, b: usize, tree: &ID) -> Result<()>
Turn on background, automatic sync of tree between peers a and b,
in both directions. After this a commit on either peer is queued for the
other automatically (sync-on-commit) — no per-write exchange call. Use
flush to push the queue immediately, or let the background interval
carry it.
Both peers must already hold tree (e.g. one Peer::served it and the
other bootstrapped it). This wires the peer relationship both ways
(register peer + dial-back address + per-tree sync target) and re-tracks
the tree as on_commit on each side.
Sourcepub async fn auto_sync_all(&mut self, tree: &ID) -> Result<()>
pub async fn auto_sync_all(&mut self, tree: &ID) -> Result<()>
Sourcepub async fn flush(&self, peer: usize) -> Result<()>
pub async fn flush(&self, peer: usize) -> Result<()>
Push peer peer’s pending auto-sync queue to its targets now, instead of
waiting for the background interval. The deterministic barrier for
auto-sync tests: commit, flush, assert.
Sourcepub async fn flush_all(&self) -> Result<()>
pub async fn flush_all(&self) -> Result<()>
Drain the whole cluster: repeatedly flush every peer until all pending
auto-sync work has propagated everywhere, then settle.
A single pass over the peers is not enough in general. flush visits
each peer once, in index order, so a pass advances an in-flight entry at
most one hop along its sync path (and only in the index direction — an
entry that must travel “backwards”, from a higher-indexed peer to a lower
one, waits for the next pass). One pass suffices only when every peer
pushes directly to every other (a full mesh); a sparser topology — a relay
chain — needs up to one pass per hop. len() + 1 passes covers the worst
case, since no propagation path through len() peers is longer than
len() - 1 hops. This is the whole-cluster barrier: after it, every
deliverable entry has reached every peer.
Sourcepub async fn snapshot(&self, peer: usize, tree: &ID) -> Result<Snapshot>
pub async fn snapshot(&self, peer: usize, tree: &ID) -> Result<Snapshot>
The Snapshot peer peer currently holds for tree — the canonical
(sorted, deduplicated) tip set identifying its state. Snapshot::EMPTY
if the peer has never seen the tree.
Sourcepub async fn converged(&self, peers: &[usize], tree: &ID) -> Result<bool>
pub async fn converged(&self, peers: &[usize], tree: &ID) -> Result<bool>
True if the named peers all agree on tree’s Snapshot — the
convergence invariant. The caller names which peers should have converged;
a peer that never received the tree has an empty snapshot and will not
match. Comparison is Snapshot set-equality, so tip order never matters.
Sourcepub async fn converged_all(&self, tree: &ID) -> Result<bool>
pub async fn converged_all(&self, tree: &ID) -> Result<bool>
Whether every peer agrees on tree’s tip set — the common convergence
check. Shorthand for converged over all peers; the explicit
&[peers] form stays for partition tests that expect only a subset to
agree.
Sourcepub async fn converge(&self, tree: &ID) -> Result<bool>
pub async fn converge(&self, tree: &ID) -> Result<bool>
Drive bidirectional exchange across every peer pair, round after
round, until the whole cluster holds an identical tip set for tree —
then return true. Bounded to peers rounds (a complete graph converges
in one, the budget is slack for safety); returns the final convergence
status if the budget is spent without settling.
Quiescent only: there must be no concurrent writes while this runs (it
has no way to observe them). Every peer must already hold and serve
tree so it can answer an exchange — bootstrap then Peer::serve on
each joiner. The fixpoint barrier the N-peer / partition-heal tests
assert against.
Sourcepub async fn entries(&self, peer: usize, tree: &ID) -> Result<Vec<Entry>>
pub async fn entries(&self, peer: usize, tree: &ID) -> Result<Vec<Entry>>
Every entry peer peer holds for tree, in id order. The full DAG of the
tree — settings, auth, and every store — not just the tips.
Sourcepub async fn entry_ids(&self, peer: usize, tree: &ID) -> Result<Vec<ID>>
pub async fn entry_ids(&self, peer: usize, tree: &ID) -> Result<Vec<ID>>
The id of every entry peer peer holds for tree, sorted.
Sourcepub async fn assert_no_lost_entries(
&self,
peers: &[usize],
tree: &ID,
) -> Result<()>
pub async fn assert_no_lost_entries( &self, peers: &[usize], tree: &ID, ) -> Result<()>
Assert no peer in peers is missing an entry another holds for tree —
the merge converged onto the union of histories, never silently dropping
one peer’s signed entry. Stronger than converged, which only compares
tips.
Limitation: if every peer dropped the same entry the union is also short
it, so this can’t see that loss — use assert_all_present with an
externally-known id set for the absolute form.
Sourcepub async fn assert_all_present(
&self,
peers: &[usize],
tree: &ID,
expected: &[ID],
) -> Result<()>
pub async fn assert_all_present( &self, peers: &[usize], tree: &ID, expected: &[ID], ) -> Result<()>
Assert every id in expected is present on every peer in peers. The
absolute form of assert_no_lost_entries: the test names entries it knows
were committed (e.g. ids captured from its own writes) and demands they
survive the merge everywhere.
Sourcepub async fn assert_all_signed(&self, peer: usize, tree: &ID) -> Result<()>
pub async fn assert_all_signed(&self, peer: usize, tree: &ID) -> Result<()>
Assert every entry peer peer holds for tree carries a well-formed
signature. A synced CRDT under global auth must never store an unsigned or
malformed-signature entry; this catches one that slipped through.
Sourcepub async fn assert_all_verified(&self, peer: usize, tree: &ID) -> Result<()>
pub async fn assert_all_verified(&self, peer: usize, tree: &ID) -> Result<()>
Assert no entry peer peer holds for tree is in the Failed verification
state — every entry, including those received over sync, verified against
the tree’s auth. Stronger than tip equality: a peer can converge on the
right tips while having stored a received entry that does not verify.
This is only a meaningful convergence invariant once sync runs a per-entry
verification pass that promotes received entries after their signing
context arrives. On a build where sync ingestion records a placeholder
status instead of a real signature check (see the TODO on
VerificationStatus and docs/src/design/verification.md), the stored
status does not reflect verification and this assertion should not be used
— a bootstrapped peer legitimately holds entries marked Failed that no
pass has yet promoted. Provided for the harness’s forward path: exercise it
once verification-on-ingest is in place.
Auto Trait Implementations§
impl Freeze for Cluster
impl !RefUnwindSafe for Cluster
impl Send for Cluster
impl Sync for Cluster
impl Unpin for Cluster
impl UnsafeUnpin for Cluster
impl !UnwindSafe for Cluster
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more