From 7323e8ed8598cda9784493aa3334d947120b53d8 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:22 +0100 Subject: swarm: changes taken unjudged, dropped (REFUSALS) A node refusing changes a peer brought tells it which broke a rule; the peer judges them itself, and if its rules refuse them too, drops them with all built on them (its document forked without them, a fresh actor), quarantining its own writes among them. Wire version 42. Auth plan M6 (rejoin on taint). --- src/swarm/sync.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src/swarm/sync.rs') diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs index 9f928ad..7601d69 100644 --- a/src/swarm/sync.rs +++ b/src/swarm/sync.rs @@ -571,6 +571,7 @@ pub(crate) async fn handle_sync( bool, (Vec<(String, Vec)>, bool), Option, + Option<(u32, Vec)>, ); tokio::task::spawn_blocking(move || -> Result> { // Before the lock: inflating and decoding a large message takes a while. No @@ -618,6 +619,7 @@ pub(crate) async fn handle_sync( let epoch_seq = replica.epoch_seq; let mut message = message; let mut refused_again = false; + let mut roots = Vec::new(); replica .logged_signed(|doc, known, log| { let result = (|| { @@ -630,7 +632,7 @@ pub(crate) async fn handle_sync( if required { known.offer(doc, signatures.clone()); } - refused_again = crate::access::screen::apply_bytes( + let outcome = crate::access::screen::apply_bytes( doc, epoch, epoch_seq, @@ -638,8 +640,9 @@ pub(crate) async fn handle_sync( &screening, required.then_some(&mut *known), log, - )? - .refused_again; + )?; + refused_again = outcome.refused_again; + roots = outcome.roots; } sync.receive_logged(doc, message, log) })(); @@ -649,6 +652,10 @@ pub(crate) async fn handle_sync( peer_state.last_sync_rx = Some(Instant::now()); // It offers again only what we refused, and would as fast as we answer: we // answer later, later each time (`SyncSession::refused_again`). + // What it brought that broke the rules, told to it now and then + // (`swarm::refusals`). + let tell = (!roots.is_empty() && peer_state.sync.refusals_due(Instant::now())) + .then(|| (replica.epoch_seq, std::mem::take(&mut roots))); let back_off = if refused_again { Some(peer_state.sync.refused_again(Instant::now())) } else { @@ -744,16 +751,20 @@ pub(crate) async fn handle_sync( carried, authors, back_off, + tell, ))) }) .await .context("Sync task panicked")?? }; - let Some((caught_up, changed, carried, (authors, unauthored), back_off)) = applied else { + let Some((caught_up, changed, carried, (authors, unauthored), back_off, tell)) = applied else { debug!(peer = %peer, epoch = %epoch_label, "Dropped sync message from another epoch"); return Ok(()); }; + if let Some((epoch_seq, roots)) = tell { + crate::swarm::refusals::tell(state, peer, epoch, epoch_seq, roots).await; + } if let Some(delay) = back_off { debug!( peer = %peer, -- cgit v1.3.1