From d322e7d4aa41d94232113cb7680cec1d65a11b0c Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:22 +0100 Subject: access: members refused as a whole With signatures required, a member whose own change broke a rule is refused as a whole: sent nothing, shown in /peers (refused), in stats (sync.refusedPeers) and on the dashboard, until a change of its is taken again or the epoch changes. Auth plan M6 (diverged standing). --- src/api/peers.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/api/peers.rs') diff --git a/src/api/peers.rs b/src/api/peers.rs index cd9381f..2bdde96 100644 --- a/src/api/peers.rs +++ b/src/api/peers.rs @@ -50,6 +50,9 @@ pub(crate) struct PeerStatus { /// How long its heads have differed from ours with no sync message from it in /// between; absent while they match. pub(crate) diverged_for_secs: Option, + /// Whether we refuse it as a whole, its own change having broken the swarm's rules + /// (`access::screen`): then we send it nothing. + pub(crate) refused: Option, pub(crate) has_pending_delivery: bool, /// Epoch the peer last advertised; absent until it has sent a message. pub(crate) epoch: Option, @@ -113,6 +116,16 @@ pub(crate) struct ThroughputInfo { pub(crate) secs_ago: u64, } +/// A peer refused as a whole (`PeerStatus::refused`). +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct RefusedPeer { + pub(crate) secs_ago: u64, + /// The change of its that broke the rules, and why. + pub(crate) change: String, + pub(crate) why: String, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PeerListResponse { @@ -176,6 +189,17 @@ pub(crate) async fn peer_list(state: &AppState) -> PeerListResponse { samples: estimate.samples, secs_ago: now.saturating_duration_since(estimate.at).as_secs(), }); + let refused = { + state + .screening + .lock_or_recover() + .refusing(&peer) + .map(|refusing| RefusedPeer { + secs_ago: now.saturating_duration_since(refusing.since).as_secs(), + change: refusing.change.to_string(), + why: refusing.why.clone(), + }) + }; let ps = peer_state_mutex.lock().await; let (status, dormant_for_secs) = match ps.liveness { @@ -223,6 +247,7 @@ pub(crate) async fn peer_list(state: &AppState) -> PeerListResponse { diverged_for_secs: ps .diverged_since .map(|t| now.saturating_duration_since(t).as_secs()), + refused, has_pending_delivery: ps.sync.sending.is_some() || ps.sync.retry_at.is_some(), epoch: ps.epoch.map(EpochRecord::from), same_epoch: ps.epoch.map(|epoch| epoch == local_epoch), -- cgit v1.3.1