aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 22:47:10 +0000
committertv <tv@krebsco.de>2026-11-09 00:14:20 +0100
commite7d02e2d71cd4c339b386a751f32050d33f86b89 (patch)
tree350a7a7eec479c76768a7fa4d5797b184571fb4d
parenteae437ce2734b8d65bf40aa207c134a3c641138f (diff)
swarm: no SIGNATURES
Signatures sent on their own (SIGNATURES, every second, to each peer not sent them yet) flooded every link: a signature made after its change left, or one that came with changes, went to every neighbour, whether or not it got the change along with its signature elsewhere. With every change of a node signed as it leaves (1217) and signatures going where their changes go (1218), none is left for them to carry: the message, its task and its traffic counter go (wire version 46). Signatures waiting for their changes are offered as before when sync messages bring them, and the changes waiting for signatures judged again with the next batch.
-rw-r--r--docs/architecture.md3
-rw-r--r--docs/measurements.md2
-rw-r--r--src/api/stats/prometheus.rs1
-rw-r--r--src/node.rs1
-rw-r--r--src/simtest/bench.rs9
-rw-r--r--src/simtest/signatures.rs12
-rw-r--r--src/swarm/mod.rs1
-rw-r--r--src/swarm/peer/inbound.rs10
-rw-r--r--src/swarm/signatures.rs109
-rw-r--r--src/transport/mod.rs16
-rw-r--r--src/transport/traffic.rs6
-rw-r--r--src/transport/wire.rs13
12 files changed, 13 insertions, 170 deletions
diff --git a/docs/architecture.md b/docs/architecture.md
index ef7928e..d867d4a 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -77,7 +77,7 @@ it switched to the right document without trusting anyone.
## Modules
**[`transport/`](../src/transport/)** — [`mod.rs`](../src/transport/mod.rs) defines the message types (HELLO, HELLO_REPLY, sync
-envelope, STATUS/STATUS_REPLY, PING/PONG, blob messages, PROBE/PROBE_REPLY, SIGNATURES, REFUSALS) and the `Transport` trait;
+envelope, STATUS/STATUS_REPLY, PING/PONG, blob messages, PROBE/PROBE_REPLY, REFUSALS) and the `Transport` trait;
[`wire.rs`](../src/transport/wire.rs) is the wire format (a version byte, bumped with every change to the format,
and a kind byte, then the message in MessagePack, derived from the types in [`mod.rs`](../src/transport/mod.rs)
with codecs for hashes and IDs); [`mycelium/`](../src/transport/mycelium/) (with the `mycelium` feature) is the REST
@@ -118,7 +118,6 @@ ignored 25-node soak test with a propagation bound (`cargo test soak -- --ignore
| `tree` | writers' trees: passing each writer's changes on to the members after us in its tree, the same on every node | [`peers.md`](peers.md) |
| `reach` | whom each neighbour reaches, as it says in its STATUS: the 2-hop neighbourhood trees are built on | [`peers.md`](peers.md) |
| `relay` | announcing heads where trees don't reach (at once) and to everyone else (slow round), and pulling what's missing | module docs |
-| `signatures` | sending signatures of changes that no sync message took, every second | [`api.md`](api.md), Signed changes |
| [`topology/`](../src/swarm/topology/) | which members are neighbours: ring plus low-score links, stable members preferred | module docs |
| `members` | the member registry: aliases, alive stamps, tiers, the electorate | module docs |
| `stability` | a node's own connectivity record and its stable/transient tier | module docs |
diff --git a/docs/measurements.md b/docs/measurements.md
index c377d9c..319586e 100644
--- a/docs/measurements.md
+++ b/docs/measurements.md
@@ -182,7 +182,7 @@ Messages and payload bytes by direction (`sent`, `received`, `refused`) and kind
(`hello`, `helloReply`, `sync`, `status`, `statusReply`, `blob`, `identity` for WHO and
ME, `paths` for PING and PONG, `probe` for PROBE
and PROBE_REPLY, `plumtree` for PRUNE and GRAFT, `hyparview` for NEIGHBOUR,
-NEIGHBOUR_REPLY and DISCONNECT, `signatures` for SIGNATURES, `refusals` for REFUSALS). The layers below the node count WHO and ME, and the
+NEIGHBOUR_REPLY and DISCONNECT, `refusals` for REFUSALS). The layers below the node count WHO and ME, and the
paths layer's own PINGs and PONGs, under the address they went to or came from; a peer
(`/peers`) has those of its addresses too, where it is known to be at them. Payload bytes are what a transport's message carries (the wire
encoding): not the transport's framing (for Mycelium, its topic and the base64 of its
diff --git a/src/api/stats/prometheus.rs b/src/api/stats/prometheus.rs
index dd49751..0d79d2c 100644
--- a/src/api/stats/prometheus.rs
+++ b/src/api/stats/prometheus.rs
@@ -193,7 +193,6 @@ fn traffic_series(traffic: &PeerTraffic) -> Vec<(&'static str, &'static str, Cou
(direction, "probe", by.probe),
(direction, "plumtree", by.plumtree),
(direction, "hyparview", by.hyparview),
- (direction, "signatures", by.signatures),
]
};
let mut series = Vec::new();
diff --git a/src/node.rs b/src/node.rs
index 742da09..2f82c96 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -274,7 +274,6 @@ impl Node {
tokio::spawn(peer::probe_when_reachable(state.clone(), reachable));
tokio::spawn(remake::run_worker(state.clone()));
- tokio::spawn(crate::swarm::signatures::run(state.clone()));
trace!("Remake worker task spawned");
tokio::spawn(swarm::topology::run_worker(state.clone()));
diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs
index 585ea1e..04b2eb6 100644
--- a/src/simtest/bench.rs
+++ b/src/simtest/bench.rs
@@ -491,7 +491,7 @@ impl Totals {
}
/// The kinds, in the order of the report.
-const KINDS: [&str; 12] = [
+const KINDS: [&str; 11] = [
"SYNC",
"STATUS",
"STATUS_REPLY",
@@ -503,10 +503,9 @@ const KINDS: [&str; 12] = [
"PROBE",
"PRUNE/GRAFT",
"NEIGHBOUR",
- "SIGNATURES",
];
-fn counters(by: &ByKind) -> [Counter; 12] {
+fn counters(by: &ByKind) -> [Counter; 11] {
[
by.sync,
by.status,
@@ -519,11 +518,10 @@ fn counters(by: &ByKind) -> [Counter; 12] {
by.probe,
by.plumtree,
by.hyparview,
- by.signatures,
]
}
-fn counters_mut(by: &mut ByKind) -> [&mut Counter; 12] {
+fn counters_mut(by: &mut ByKind) -> [&mut Counter; 11] {
[
&mut by.sync,
&mut by.status,
@@ -536,7 +534,6 @@ fn counters_mut(by: &mut ByKind) -> [&mut Counter; 12] {
&mut by.probe,
&mut by.plumtree,
&mut by.hyparview,
- &mut by.signatures,
]
}
diff --git a/src/simtest/signatures.rs b/src/simtest/signatures.rs
index c6232e7..b65b3a7 100644
--- a/src/simtest/signatures.rs
+++ b/src/simtest/signatures.rs
@@ -1,7 +1,7 @@
-//! Signed changes end to end (`document::signatures`, `swarm::signatures`): nodes under
-//! the identity layer, whose node keys sign, write through the API as a user would,
-//! and every node comes to see the writes verified, by signatures that came with sync
-//! messages or on their own.
+//! Signed changes end to end (`document::signatures`): nodes under the identity layer,
+//! whose node keys sign, write through the API as a user would, and every node comes
+//! to see the writes verified, by signatures that came with the changes in sync
+//! messages.
use std::time::Duration;
@@ -80,8 +80,8 @@ async fn a_signed_write_vouches_for_the_writes_before() {
let (actor, first) = write_as_user(&b, "metrics/cpu", json!(42), false).await;
sim.wait_converged(&[&a, &b, &c], SOON).await;
- // Signatures go within a second: none came for it. Unless b wrote protocol data
- // since (always signed), whose signature vouches for it too.
+ // None is made for it, nor sent. Unless b wrote protocol data since (always
+ // signed), whose signature vouches for it too.
tokio::time::sleep(Duration::from_secs(3)).await;
let latest = b
.doc
diff --git a/src/swarm/mod.rs b/src/swarm/mod.rs
index e08bc62..ad7f83e 100644
--- a/src/swarm/mod.rs
+++ b/src/swarm/mod.rs
@@ -19,7 +19,6 @@ pub(crate) mod refusals;
pub(crate) mod relay;
pub(crate) mod roots;
pub(crate) mod rtt;
-pub(crate) mod signatures;
pub(crate) mod skew;
pub(crate) mod source;
pub(crate) mod stability;
diff --git a/src/swarm/peer/inbound.rs b/src/swarm/peer/inbound.rs
index 3ddaf08..4a5b6ac 100644
--- a/src/swarm/peer/inbound.rs
+++ b/src/swarm/peer/inbound.rs
@@ -274,16 +274,6 @@ pub(crate) async fn handle_message(
heard_heads(state, &peer, &remote);
on_status(state, &peer, &remote, reactivated).await;
}
- transport::ParsedMessage::Signatures(message) => {
- // From members, of our epoch: checking costs (`Signatures::offer`).
- let member = matches!(
- peer_state.lock().await.standing,
- crate::state::Standing::Member { .. }
- );
- if member && same_epoch {
- crate::swarm::signatures::received(state, message).await;
- }
- }
transport::ParsedMessage::Refusals(message) => {
// From members, of our epoch: judging costs (`swarm::refusals`).
let member = matches!(
diff --git a/src/swarm/signatures.rs b/src/swarm/signatures.rs
deleted file mode 100644
index 111b3da..0000000
--- a/src/swarm/signatures.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-//! Signatures of changes sent on their own (SIGNATURES), where no sync message took
-//! them: a signature made after its change went (`--sign-every`), or one received
-//! without the change it vouches for, as from a peer that had it already. Every
-//! `SEND_EVERY`, each active peer on our epoch is sent the signatures it wasn't sent yet
-//! (`Signatures::newer_than`), if any; it checks them as it checks those sync messages
-//! bring (`Signatures::offer`), and sends them on in turn: each actor's latest goes to
-//! each node about once.
-
-use std::time::Duration;
-
-use tracing::debug;
-
-use crate::state::AppState;
-use crate::transport::{ParsedMessage, SignaturesMessage};
-
-/// How often the signatures the peers weren't sent yet go out.
-pub(crate) const SEND_EVERY: Duration = Duration::from_secs(1);
-
-/// Sends the signatures due every `SEND_EVERY`. Runs for as long as the node does.
-pub(crate) async fn run(state: AppState) {
- let mut ticks = tokio::time::interval(SEND_EVERY);
- ticks.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
- loop {
- ticks.tick().await;
- send_due(&state).await;
- }
-}
-
-/// Each active peer on our epoch: the signatures it wasn't sent yet, if any.
-async fn send_due(state: &AppState) {
- for peer in state.active_peers_on_local_epoch().await {
- let Some(peer_state) = state.get_peer_state(&peer).await else {
- continue;
- };
- let doc = std::sync::Arc::clone(&state.doc);
- // Lock order doc -> peer, as in sync generation.
- let message = tokio::task::spawn_blocking(move || {
- let replica = doc.blocking_read();
- let mut peer_state = peer_state.blocking_lock();
- let (signatures, sent) = replica.signatures.newer_than(peer_state.signatures_sent);
- if signatures.is_empty() {
- return None;
- }
- peer_state.signatures_sent = sent;
- Some(ParsedMessage::Signatures(SignaturesMessage {
- epoch: replica.epoch,
- epoch_seq: replica.epoch_seq,
- signatures: serde_bytes::ByteBuf::from(crate::document::signatures::pack(
- &signatures,
- )),
- }))
- })
- .await;
- if let Ok(Some(message)) = message
- && let Err(error) = state.transport.send_message(&peer, &message).await
- {
- // Lost, they come again with the next sync session (`PeerState::reset_sync`).
- debug!(peer = %peer, error = %format!("{error:#}"), "Couldn't send signatures");
- }
- }
-}
-
-/// SIGNATURES from `peer`, of our epoch: checked and kept as those of sync messages.
-pub(crate) async fn received(state: &AppState, message: SignaturesMessage) {
- let Some(signatures) = crate::document::signatures::unpack(&message.signatures)
- .filter(|signatures| signatures.len() <= crate::document::signatures::MAX_PER_MESSAGE)
- else {
- return;
- };
- let doc = std::sync::Arc::clone(&state.doc);
- let screening = std::sync::Arc::clone(&state.screening);
- // How many waiting changes went in, if any: published as received ones are.
- let applied = tokio::task::spawn_blocking(move || {
- let mut replica = doc.blocking_write();
- if replica.epoch != message.epoch {
- return 0;
- }
- let (epoch, epoch_seq) = (replica.epoch, replica.epoch_seq);
- let replica = &mut *replica;
- replica.signatures.offer(&replica.doc, signatures);
- // Changes waiting for them judged again (`access::screen`): those the policy they
- // were written under requires signed.
- let applied = replica.logged_signed(|doc, known, log| {
- let applied = crate::access::screen::apply_bytes(
- doc,
- epoch,
- epoch_seq,
- &[],
- &screening,
- Some(known),
- log,
- );
- (applied, true)
- });
- replica.refresh_heads();
- match applied {
- Ok(outcome) => outcome.applied,
- Err(error) => {
- tracing::warn!(error = %format!("{error:#}"), "Couldn't judge the changes waiting for signatures");
- 0
- }
- }
- })
- .await
- .unwrap_or(0);
- if applied > 0 {
- state.publish_change();
- }
-}
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index 640754a..d794271 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -252,8 +252,6 @@ pub(crate) enum ParsedMessage {
NeighbourReply(NeighbourAnswer),
/// Tells the recipient we are neighbours no more (`swarm::hyparview`).
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),
}
@@ -273,17 +271,6 @@ pub(crate) struct RefusalsMessage {
pub(crate) changes: Vec<crate::document::ChangeHash>,
}
-/// SIGNATURES: signatures of changes no sync message took (`swarm::signatures`).
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub(crate) struct SignaturesMessage {
- #[serde(with = "crate::transport::wire::codec::epoch")]
- pub(crate) epoch: Epoch,
- /// See `SyncEnvelope::epoch_seq`.
- pub(crate) epoch_seq: u32,
- /// As `SyncEnvelope::signatures`.
- pub(crate) signatures: serde_bytes::ByteBuf,
-}
-
/// A node's proof that it holds the key of its node ID (`identity`), sending from its
/// transport address to the recipient's: a signature over both addresses
/// (`transport::identity::signed`). WHO and ME end at the identity layer; the rest of
@@ -429,7 +416,6 @@ pub(crate) enum MessageKind {
Neighbour,
NeighbourReply,
Disconnect,
- Signatures,
Refusals,
}
@@ -453,7 +439,6 @@ impl ParsedMessage {
ParsedMessage::Neighbour(_) => MessageKind::Neighbour,
ParsedMessage::NeighbourReply(_) => MessageKind::NeighbourReply,
ParsedMessage::Disconnect(_) => MessageKind::Disconnect,
- ParsedMessage::Signatures(_) => MessageKind::Signatures,
ParsedMessage::Refusals(_) => MessageKind::Refusals,
}
}
@@ -480,7 +465,6 @@ impl ParsedMessage {
ParsedMessage::NeighbourReply(answer) | ParsedMessage::Disconnect(answer) => {
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(_)
diff --git a/src/transport/traffic.rs b/src/transport/traffic.rs
index e810595..b9506a9 100644
--- a/src/transport/traffic.rs
+++ b/src/transport/traffic.rs
@@ -57,8 +57,6 @@ pub(crate) struct ByKind {
pub(crate) plumtree: Counter,
/// NEIGHBOUR, NEIGHBOUR_REPLY and DISCONNECT (`swarm::hyparview`).
pub(crate) hyparview: Counter,
- /// SIGNATURES (`swarm::signatures`).
- pub(crate) signatures: Counter,
/// REFUSALS (`swarm::refusals`).
pub(crate) refusals: Counter,
}
@@ -77,7 +75,6 @@ impl ByKind {
MessageKind::Probe,
MessageKind::Prune,
MessageKind::Neighbour,
- MessageKind::Signatures,
MessageKind::Refusals,
] {
let theirs = *other.get(kind);
@@ -100,7 +97,6 @@ impl ByKind {
MessageKind::Neighbour | MessageKind::NeighbourReply | MessageKind::Disconnect => {
&self.hyparview
}
- MessageKind::Signatures => &self.signatures,
MessageKind::Refusals => &self.refusals,
}
}
@@ -120,7 +116,6 @@ impl ByKind {
MessageKind::Neighbour | MessageKind::NeighbourReply | MessageKind::Disconnect => {
&mut self.hyparview
}
- MessageKind::Signatures => &mut self.signatures,
MessageKind::Refusals => &mut self.refusals,
}
}
@@ -331,7 +326,6 @@ fn names(kind: MessageKind) -> (&'static str, &'static str) {
MessageKind::Neighbour => ("NEIGHBOUR", "hyparview"),
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 5763407..450ecb0 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 = 45;
+pub(crate) const VERSION: u8 = 46;
/// What the second byte of a payload says.
fn tag(kind: MessageKind) -> u8 {
@@ -36,7 +36,6 @@ fn tag(kind: MessageKind) -> u8 {
MessageKind::Neighbour => 16,
MessageKind::NeighbourReply => 17,
MessageKind::Disconnect => 18,
- MessageKind::Signatures => 19,
MessageKind::Refusals => 20,
}
}
@@ -99,7 +98,6 @@ pub(crate) fn encode(message: &ParsedMessage) -> Result<Vec<u8>, WireError> {
ParsedMessage::NeighbourReply(answer) | ParsedMessage::Disconnect(answer) => {
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,8 +178,6 @@ pub(crate) fn decode(payload: &[u8]) -> Result<ParsedMessage, WireError> {
ParsedMessage::NeighbourReply(rmp_serde::from_slice(body)?)
} else if is(MessageKind::Disconnect) {
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 {
@@ -545,11 +541,6 @@ pub(crate) fn samples() -> Vec<ParsedMessage> {
epoch_seq: 1,
agreed: false,
}),
- ParsedMessage::Signatures(crate::transport::SignaturesMessage {
- epoch,
- epoch_seq: 1,
- signatures: serde_bytes::ByteBuf::from(vec![9; 96]),
- }),
ParsedMessage::Refusals(crate::transport::RefusalsMessage {
epoch,
epoch_seq: 1,
@@ -586,7 +577,7 @@ mod tests {
#[test]
fn every_kind_round_trips() {
let samples = samples();
- assert_eq!(samples.len(), 19, "a kind is missing from `samples`");
+ assert_eq!(samples.len(), 18, "a kind is missing from `samples`");
for message in samples {
let bytes = encode(&message).unwrap();
let decoded = decode(&bytes).unwrap();