test: property-test gossip convergence between two nodes
All checks were successful
CI / check (push) Successful in 3m45s

A fresh peer that backfills a node's log via the version vector must reach the
same global state (accounts, akills, forbids, groups); channels/jupes are Local
and correctly don't replicate. Guards the core federation guarantee — a
distinct invariant from same-node replay (exercises missing_for + version_vector
+ cross-node ingest).
This commit is contained in:
Jean Chevronnet 2026-07-16 11:17:43 +00:00
parent 56cac370eb
commit b23bf5d8a9
No known key found for this signature in database

View file

@ -624,6 +624,45 @@
assert_eq!(before, after, "compaction must preserve every persisted field"); assert_eq!(before, after, "compaction must preserve every persisted field");
} }
// Gossip convergence: a fresh peer that backfills a node's log via the version
// vector must arrive at the same GLOBAL state (accounts + network bans + groups).
// Channels/jupes are Local and deliberately don't replicate, so they're excluded.
#[test]
fn gossip_converges_global_state_to_a_peer() {
let pa = tmp("gossipA");
let mut a = Db::open(&pa, "N1");
a.scram_iterations = 4096;
a.register("alice", "pw", Some("a@x.io".into())).unwrap();
a.register("bob", "pw", None).unwrap();
a.set_greet("alice", "hi there").unwrap();
a.set_account_kill("alice", false).unwrap();
a.suspend_account("bob", "op", "bad", None).unwrap();
a.akill_add("G", "*@evil", "op", "spam", None).unwrap();
a.forbid_add("NICK", "bad*", "op", "squat").unwrap();
a.group_register("!g", "alice").unwrap();
a.group_set_flags("!g", "bob", "").unwrap();
let pb = tmp("gossipB");
let mut b = Db::open(&pb, "N2");
for entry in a.missing_for(&b.version_vector()) {
b.ingest(entry).unwrap();
}
let gsnap = |db: &Db| {
let mut ac: Vec<_> = db.accounts().cloned().collect();
ac.sort_by(|x, y| x.name.cmp(&y.name));
let mut ak = db.akills();
ak.sort_by(|x, y| x.mask.cmp(&y.mask));
let mut fb = db.forbids();
fb.sort_by(|x, y| x.mask.cmp(&y.mask));
let mut gn = db.groups();
gn.sort();
let gv: Vec<_> = gn.iter().filter_map(|n| db.group(n)).collect();
format!("{ac:?}\n{ak:?}\n{fb:?}\n{gv:?}")
};
assert_eq!(gsnap(&a), gsnap(&b), "peer B must converge to A's global state");
}
// Dropping an account erases every reference to it — channel access and // Dropping an account erases every reference to it — channel access and
// successorship, group membership and foundership — so a later re-registration // successorship, group membership and foundership — so a later re-registration
// of the same name can't inherit its standing. Holds live and after replay. // of the same name can't inherit its standing. Holds live and after replay.