Docs

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,
    },
}