gossip
Source: crates/lvcs-net/src/gossip/mod.rs.
/// What the gossip actor knows of a signer that holds a live `Message`
/// grant, a live grant to attest a channel, or a live grant to
/// attest an action channel (design §11, §15; decisions 111, 146,
/// 203, and 206).
///
/// It is `gossip`, the rate its gossip bucket refills at, and the windows
/// of the three kinds of grant that admit gossip: `message`, from its
/// `Message` grants, admits every kind but `action_failed` and
/// `action_succeeded`; `attest`, from its grants to attest a channel,
/// admits those two only (decision 203); and `channels`, from its grants
/// to attest a gated action's channel, one window per such channel's document
/// address, each admitting `presence` only, and only when the presence's
/// `doc` is that address (decision 206). `message` and `attest` are `None`
/// and `channels` empty when the signer holds no such grant. The daemon
/// shell derives it from the `.lvcs/caps` document's render
/// (`lvcs_doc::CapsRendered`): every signer that holds any of them there,
/// the owner included with `message` and `attest`, so `lvcs-net` never
/// names a grant (chosen at the M3 freeze). Every signer's `gossip` is the
/// limits in force's ([`Limits::gossip_rate`]), its per-second rate
/// replaced by the signer's grant terms where they name one (decision
/// 146), so its `burst` is always `gossip_burst`, the capacity the gossip
/// actor sizes its memory and tolerance by. The gossip actor drops a
/// message whose signer is not in the table, whose kind its windows don't
/// admit, or whose kind's window has ended by the shell's clock, before
/// forwarding it; whether a grant's paths cover the message's document is
/// checked only before delivery ([`GossipOutput::Deliver`]). Owner:
/// `M3.gossip`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignerCap {
/// The rate the signer's gossip bucket refills at.
pub gossip: Rate,
/// The window of its `Message` grants, which admits every kind but
/// the action notifications; `None` when it holds none.
pub message: Option<Window>,
/// The window of its grants to attest a channel, which admits
/// `action_failed` and `action_succeeded` only; `None` when it holds none.
pub attest: Option<Window>,
/// The window of its grants to attest each action channel, by
/// that channel document's address, each admitting `presence` about
/// that document only; empty when it holds none.
pub channels: BTreeMap<String, Window>,
}
/// How long a kind of grant admits a signer's gossip ([`SignerCap`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Window {
/// The end of the latest window among those grants, in Unix seconds;
/// `None` for a grant with no expiry.
pub until_s: Option<i64>,
}
/// The gossip actor's events, generated by its sml table: its mailbox and
/// journal type (decision 286). Time enters only as event data. Owner:
/// `M3.gossip`.
///
/// Its events, each the payload type its variant is named for: [`JoinTopic`],
/// [`LeaveTopic`], [`PublishEnvelope`], [`ReceiveMessage`],
/// [`AdmissionAnswer`], [`ReplaceSigners`], [`PeerUp`], [`PeerGone`],
/// [`SetLimits`].
pub use machine::{
AdmissionAnswer, GossipingEvents, JoinTopic, LeaveTopic, PeerGone, PeerUp, PublishEnvelope,
ReceiveMessage, ReplaceSigners, SetLimits,
};
/// The answer to the take of [`GossipOutput::Admit`]. Owner: `M3.gossip`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Admission {
/// The signer's bucket had a token (`GrantsOutput::Taken` with
/// `allowed`): accepted.
Admitted,
/// The signer's bucket was empty: ignored `rate_limited`.
RateLimited,
}
/// Outputs of the gossip actor, which the daemon shell carries out.
/// Owner: `M3.gossip`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GossipOutput {
/// Join a topic (`TransportCommand::Subscribe`).
Subscribe {
/// The topic to join.
topic: String,
},
/// Leave a topic (`TransportCommand::Unsubscribe`).
Unsubscribe {
/// The topic to leave.
topic: String,
},
/// Publish `data` on `topic` (`TransportCommand::Publish`).
Publish {
/// The topic to publish on.
topic: String,
/// The encoded envelope.
data: Vec<u8>,
},
/// Admit the message `id`, which passed every other check (design §9,
/// §15): the shell takes a token from `signer`'s gossip bucket at
/// `rate`, the signer table's (`MeteringEvents::TakeToken` with
/// `TakeKey::Gossip(id)` at `now_ms`), and answers with exactly one
/// [`GossipingEvents::AdmissionAnswer`].
Admit {
/// The message's id.
id: Vec<u8>,
/// Its signer.
signer: Signer,
/// The signer's gossip rate, from the signer table.
rate: Rate,
/// The shell's clock when the message was received, in Unix
/// milliseconds.
now_ms: i64,
},
/// Report the validation of the message `id`
/// (`TransportCommand::Validate`); `reason` is set for a rejected or
/// ignored message, for the span's `rejection` attribute.
Validate {
/// The message's id.
id: Vec<u8>,
/// The peer that forwarded it.
propagation: Peer,
/// The verdict.
acceptance: Acceptance,
/// Why it was rejected or ignored; `None` when accepted.
reason: Option<Rejection>,
},
/// An accepted message for this daemon (design §9). `address` is the
/// path of the document the message is about: a notification's or
/// typing's `doc`, presence's `doc`, or a thread post's `thread`, each
/// without its `#pointer`; `None` for a response (`note`, `hold`) and
/// for presence in a workspace. Before it delivers the message to local
/// clients, the shell checks its scope (decision 111): the subject
/// document is `address`, or for a response the document of the
/// notification it answers, and a response whose notification this
/// daemon doesn't hold is dropped; `lvcs_doc::authorize`, with the caps
/// render's grants, must accept `signer` doing `Capability::Message` on
/// the subject document at the shell's clock in whole Unix seconds
/// (rounded down) under one of its grants. An envelope names no grant,
/// so the shell tries the owner's `GrantId::OWNER` and every grant whose
/// grantee is `signer`; a thread document's address is covered by any
/// valid grant (decision 108). Out of scope, the message is dropped
/// with `rejection` = `out_of_scope`; presence in a workspace has no
/// subject document and needs no scope. In scope, the shell routes it by
/// `kind`: `notification`, `note`, `hold`, and `presence` go to the
/// coord actor; `say`, `ask`, and `answer` are pushed to clients (the
/// thread document catches up through sync); `typing` is pushed to
/// clients only. `payload` is the envelope's payload.
Deliver {
/// The topic it came on.
topic: String,
/// The envelope's kind.
kind: WireKind,
/// Its signer.
signer: Signer,
/// The path of the document it is about, if any.
address: Option<String>,
/// The envelope's payload.
payload: Vec<u8>,
},
/// Lower the forwarding device's score after a repeat offense (design
/// §15; `TransportCommand::Penalize`).
Penalize {
/// The forwarding peer.
peer: Peer,
/// The offense.
reason: Rejection,
},
}