aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/sync.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:22 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:22 +0100
commit7323e8ed8598cda9784493aa3334d947120b53d8 (patch)
tree1e772cdf74974f918cf07d2e4b9d8e8593bed9dd /src/swarm/sync.rs
parentd322e7d4aa41d94232113cb7680cec1d65a11b0c (diff)
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).
Diffstat (limited to 'src/swarm/sync.rs')
-rw-r--r--src/swarm/sync.rs19
1 files changed, 15 insertions, 4 deletions
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<crate::document::ChangeHash>)>, bool),
Option<std::time::Duration>,
+ Option<(u32, Vec<crate::document::ChangeHash>)>,
);
tokio::task::spawn_blocking(move || -> Result<Option<Applied>> {
// 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,