aboutsummaryrefslogtreecommitdiffstats
path: root/src/transport
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/transport
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/transport')
-rw-r--r--src/transport/mod.rs20
-rw-r--r--src/transport/traffic.rs6
-rw-r--r--src/transport/wire.rs13
3 files changed, 37 insertions, 2 deletions
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index a1fb9d5..e0359b0 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -254,6 +254,23 @@ pub(crate) enum ParsedMessage {
Disconnect(NeighbourAnswer),
/// Signatures of changes, sent on their own (`swarm::signatures`).
Signatures(SignaturesMessage),
+ /// Changes the sender refused, of those the recipient brought (`swarm::refusals`).
+ Refusals(RefusalsMessage),
+}
+
+/// REFUSALS: changes the sender refused by the swarm's rules (`access::screen`), of
+/// those the recipient brought it: the recipient judges them itself, and rejoins if its
+/// own rules refuse them too, as it took them under another policy (`swarm::refusals`).
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub(crate) struct RefusalsMessage {
+ #[serde(with = "crate::transport::wire::codec::epoch")]
+ pub(crate) epoch: Epoch,
+ /// See `SyncEnvelope::epoch_seq`.
+ pub(crate) epoch_seq: u32,
+ /// The changes that broke the rules (not those refused for building on them), at
+ /// most `swarm::refusals::MAX_LISTED`.
+ #[serde(with = "crate::transport::wire::codec::change_hashes")]
+ pub(crate) changes: Vec<crate::document::ChangeHash>,
}
/// SIGNATURES: signatures of changes no sync message took (`swarm::signatures`).
@@ -413,6 +430,7 @@ pub(crate) enum MessageKind {
NeighbourReply,
Disconnect,
Signatures,
+ Refusals,
}
impl ParsedMessage {
@@ -436,6 +454,7 @@ impl ParsedMessage {
ParsedMessage::NeighbourReply(_) => MessageKind::NeighbourReply,
ParsedMessage::Disconnect(_) => MessageKind::Disconnect,
ParsedMessage::Signatures(_) => MessageKind::Signatures,
+ ParsedMessage::Refusals(_) => MessageKind::Refusals,
}
}
@@ -462,6 +481,7 @@ impl ParsedMessage {
Some((answer.epoch, answer.epoch_seq))
}
ParsedMessage::Signatures(message) => Some((message.epoch, message.epoch_seq)),
+ ParsedMessage::Refusals(message) => Some((message.epoch, message.epoch_seq)),
ParsedMessage::Who(_)
| ParsedMessage::Me(_)
| ParsedMessage::Ping(_)
diff --git a/src/transport/traffic.rs b/src/transport/traffic.rs
index 180d3b5..e810595 100644
--- a/src/transport/traffic.rs
+++ b/src/transport/traffic.rs
@@ -59,6 +59,8 @@ pub(crate) struct ByKind {
pub(crate) hyparview: Counter,
/// SIGNATURES (`swarm::signatures`).
pub(crate) signatures: Counter,
+ /// REFUSALS (`swarm::refusals`).
+ pub(crate) refusals: Counter,
}
impl ByKind {
@@ -76,6 +78,7 @@ impl ByKind {
MessageKind::Prune,
MessageKind::Neighbour,
MessageKind::Signatures,
+ MessageKind::Refusals,
] {
let theirs = *other.get(kind);
self.get_mut(kind).absorb(theirs);
@@ -98,6 +101,7 @@ impl ByKind {
&self.hyparview
}
MessageKind::Signatures => &self.signatures,
+ MessageKind::Refusals => &self.refusals,
}
}
@@ -117,6 +121,7 @@ impl ByKind {
&mut self.hyparview
}
MessageKind::Signatures => &mut self.signatures,
+ MessageKind::Refusals => &mut self.refusals,
}
}
}
@@ -327,6 +332,7 @@ fn names(kind: MessageKind) -> (&'static str, &'static str) {
MessageKind::NeighbourReply => ("NEIGHBOUR_REPLY", "hyparview"),
MessageKind::Disconnect => ("DISCONNECT", "hyparview"),
MessageKind::Signatures => ("SIGNATURES", "signatures"),
+ MessageKind::Refusals => ("REFUSALS", "refusals"),
}
}
diff --git a/src/transport/wire.rs b/src/transport/wire.rs
index 88b2916..7ff7835 100644
--- a/src/transport/wire.rs
+++ b/src/transport/wire.rs
@@ -14,7 +14,7 @@ use super::{EncodedMessage, MessageKind, ParsedMessage};
/// The wire format's version, the first byte of every payload. Bump it with every change
/// to a message type or a codec (`golden_bytes` fails then).
-pub(crate) const VERSION: u8 = 41;
+pub(crate) const VERSION: u8 = 42;
/// What the second byte of a payload says.
fn tag(kind: MessageKind) -> u8 {
@@ -37,6 +37,7 @@ fn tag(kind: MessageKind) -> u8 {
MessageKind::NeighbourReply => 17,
MessageKind::Disconnect => 18,
MessageKind::Signatures => 19,
+ MessageKind::Refusals => 20,
}
}
@@ -99,6 +100,7 @@ pub(crate) fn encode(message: &ParsedMessage) -> Result<Vec<u8>, WireError> {
rmp_serde::encode::write(&mut payload, answer)?
}
ParsedMessage::Signatures(message) => rmp_serde::encode::write(&mut payload, message)?,
+ ParsedMessage::Refusals(message) => rmp_serde::encode::write(&mut payload, message)?,
}
Ok(payload)
}
@@ -180,6 +182,8 @@ pub(crate) fn decode(payload: &[u8]) -> Result<ParsedMessage, WireError> {
ParsedMessage::Disconnect(rmp_serde::from_slice(body)?)
} else if is(MessageKind::Signatures) {
ParsedMessage::Signatures(rmp_serde::from_slice(body)?)
+ } else if is(MessageKind::Refusals) {
+ ParsedMessage::Refusals(rmp_serde::from_slice(body)?)
} else {
return Err(WireError::UnknownKind(kind));
})
@@ -544,6 +548,11 @@ pub(crate) fn samples() -> Vec<ParsedMessage> {
epoch_seq: 1,
signatures: serde_bytes::ByteBuf::from(vec![9; 96]),
}),
+ ParsedMessage::Refusals(crate::transport::RefusalsMessage {
+ epoch,
+ epoch_seq: 1,
+ changes: vec![crate::document::ChangeHash([7; 32])],
+ }),
]
}
@@ -575,7 +584,7 @@ mod tests {
#[test]
fn every_kind_round_trips() {
let samples = samples();
- assert_eq!(samples.len(), 18, "a kind is missing from `samples`");
+ assert_eq!(samples.len(), 19, "a kind is missing from `samples`");
for message in samples {
let bytes = encode(&message).unwrap();
let decoded = decode(&bytes).unwrap();