aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 18:21:55 +0000
committertv <tv@krebsco.de>2026-11-08 19:29:21 +0100
commitd14290ab6d0ea40b97cdb72f5e07354cc2721c45 (patch)
treee52d4b9a988967cd037c4d655a1001c06a4aea7c /src
parentff9e7c244c270853a8a8f58ee950859866358e1d (diff)
refusals: a big drop waits for a second member only where there is one
Review 2, N8: a node with one neighbour (a swarm of two, a leaf) never hears a second member name the change: its big drop was held for good, an error logged at every REFUSALS. It now waits only where it has another neighbour than the one telling it; with one, one word is all there is. access.md also says what the wait is: it guards against one peer, not two members saying the same (any node with the swarm key registers), and never makes a node drop what its own rules take.
Diffstat (limited to 'src')
-rw-r--r--src/state/host.rs15
1 files changed, 11 insertions, 4 deletions
diff --git a/src/state/host.rs b/src/state/host.rs
index ac36171..e7fc61a 100644
--- a/src/state/host.rs
+++ b/src/state/host.rs
@@ -99,14 +99,20 @@ impl AppState {
/// so). Every peer syncs anew. Nothing while a rejoin runs, or if the document doesn't
/// hold `root`; nor if `root` is an epoch's root, or nothing would be kept, which no
/// rule can mean (logged as an error). A drop taking much (`BIG_DROP`) waits for a
- /// second member to say so (`peer` the first, `why` its rules' reason): its rules and
- /// one peer's word might both be off, the swarm's not.
+ /// second member to say so (`peer` the first, `why` its rules' reason), where it has
+ /// another neighbour: its rules and one peer's word might both be off, the swarm's
+ /// not. Two members colluding pass it; it guards against one.
pub(crate) async fn drop_taken(&self, root: ChangeHash, peer: &str, why: &str) {
/// Changes a drop takes, at most, on one peer's word: more, or more than a tenth
/// of the document (past `SMALL_DROP`), wait for another's.
const BIG_DROP: usize = 100;
const SMALL_DROP: usize = 10;
let own = self.remake.own_actors();
+ // Another member to hear it from: a node with one neighbour, the one telling, has
+ // only its word (a swarm of two, a leaf), and waiting would hold the drop for good.
+ let another = crate::swarm::topology::our_neighbours(self)
+ .iter()
+ .any(|other| other != peer);
let state = self.clone();
let peer = peer.to_owned();
let why = why.to_owned();
@@ -136,8 +142,9 @@ impl AppState {
error!(change = %root, "Dropping a change would leave nothing; kept it");
return None;
}
- if tainted.len() > BIG_DROP
- || (tainted.len() > SMALL_DROP && tainted.len() * 10 > changes.len())
+ if another
+ && (tainted.len() > BIG_DROP
+ || (tainted.len() > SMALL_DROP && tainted.len() * 10 > changes.len()))
{
use crate::locks::MutexExt;
let mut screening = state.screening.lock_or_recover();