Docs

transport

Source: crates/lvcs-net/src/transport/mod.rs.

/// A peer device as the net actors name it (design §2).
///
/// It is its Ed25519 public key, from which its libp2p `PeerId` derives.
/// Every LVCS device key is Ed25519, so the transport refuses a connection
/// whose identity is another key type (chosen at the M3 freeze: a
/// fixed-size, journaled form, where a `PeerId` would need libp2p's serde
/// feature). Journaled as a byte string. Owner: `M3.transport`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Peer(#[serde(with = "crate::wire::cbor::fixed")] pub [u8; 32]);

/// How the transport found a peer (design §2).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Discovery {
    /// mDNS on the local network.
    Mdns,
    /// The Kademlia DHT.
    Kademlia,
    /// A `--bootstrap` address.
    Bootstrap,
}

/// What `AutoNAT` concluded about this device's reachability (design §2):
/// a `Private` device is reached through a circuit relay v2 reservation,
/// upgraded by `DCUtR` when hole punching succeeds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NatStatus {
    /// Not concluded yet.
    Unknown,
    /// Publicly reachable.
    Public,
    /// Not publicly reachable.
    Private,
}

/// How the transport runs, from `lvcs daemon`'s flags (design §2).
///
/// It holds the multiaddrs to listen on (`--listen`) and to dial at start
/// (`--bootstrap`), the wire major it speaks, and seed mode (`--seed`): the same binary with no
/// special authority, which pins every document it learns of, serves as a
/// circuit relay v2 server, and is a bootstrap address for others. Owner:
/// `M3.transport`; the daemon (`M3.daemon`) builds it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransportConfig {
    /// The multiaddrs to listen on (`--listen`).
    pub listen: Vec<String>,
    /// The multiaddrs to dial at start (`--bootstrap`).
    pub bootstrap: Vec<String>,
    /// Seed mode (`--seed`).
    pub seed: bool,
    /// Whether the swarm runs mDNS (off with `--no-mdns`, decision 307),
    /// which `build_swarm` composes.
    pub mdns: bool,
    /// The project id (`ID_ENCODING`), which names the sync protocol, so a
    /// peer of another project is never synced with (decision 320).
    pub project: String,
    /// The wire major version this daemon speaks (production-readiness
    /// U4): `WIRE_MAJOR`, except where a debug build's
    /// `WIRE_MAJOR_TEST_ENV` sets another. It names the sync protocol id
    /// and is advertised in identify; a peer that advertises another, or
    /// lists no sync protocol of this one, is incompatible.
    pub wire: u16,
}

impl Default for TransportConfig {
    /// No listen or bootstrap address, no seed mode, mDNS on, no project,
    /// and `WIRE_MAJOR`.
    fn default() -> Self {
        Self {
            listen: Vec::new(),
            bootstrap: Vec::new(),
            seed: false,
            mdns: true,
            project: String::new(),
            wire: lvcs_vocab::ids::WIRE_MAJOR,
        }
    }
}

/// What the swarm reports (design §2), in the order it saw it.
///
/// The daemon shell turns each into the events of the actors that need it:
/// peers and the sync stream go to the sync actor (`SyncingEvents`), gossip
/// messages to the gossip actor (`GossipingEvents`). Addresses are multiaddrs
/// in text form. Owner: `M3.transport`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportEvent {
    /// The swarm listens on `addr`.
    Listening {
        /// The listen address.
        addr: String,
    },
    /// A connection to `peer` is up (the first one, if there are several).
    Connected {
        /// The connected peer.
        peer: Peer,
    },
    /// The last connection to `peer` closed.
    Gone {
        /// The peer whose last connection closed.
        peer: Peer,
    },
    /// `peer` was discovered at `addrs` through `via`.
    Discovered {
        /// The discovered peer.
        peer: Peer,
        /// Its addresses.
        addrs: Vec<String>,
        /// How it was found.
        via: Discovery,
    },
    /// `AutoNAT`'s conclusion changed.
    Nat(NatStatus),
    /// The one sync stream with `peer` is open (decision 68): whichever
    /// side's device key bytes are smaller opens it, and the transport
    /// closes a second one, so each peer pair has exactly one.
    SyncOpened {
        /// The peer the stream is with.
        peer: Peer,
    },
    /// One length-delimited frame read from the sync stream: an encoded
    /// envelope of kind `sync`, at most `WIRE_MAX_BYTES` (a longer frame
    /// closes the stream and is reported as [`TransportEvent::SyncClosed`]).
    SyncFrame {
        /// The peer the stream is with.
        peer: Peer,
        /// The frame's bytes.
        frame: Vec<u8>,
    },
    /// The sync stream with `peer` closed.
    SyncClosed {
        /// The peer the stream was with.
        peer: Peer,
    },
    /// A gossipsub message waiting for validation (design §9).
    ///
    /// `id` is gossipsub's message id, `propagation` the peer that
    /// forwarded it (whose score a repeat offense lowers, §15), and `data`
    /// the encoded envelope, which its participant signed. Gossipsub
    /// forwards it only after [`TransportCommand::Validate`] accepts it.
    /// The publishing device is not reported: nothing downstream needs it,
    /// since the envelope names its signer and the yield order reads only
    /// the payload (chosen at the M3 freeze).
    Gossip {
        /// Gossipsub's message id.
        id: Vec<u8>,
        /// The peer that forwarded it.
        propagation: Peer,
        /// The topic it came on.
        topic: String,
        /// The encoded envelope.
        data: Vec<u8>,
    },
    /// `peer` runs a release this daemon can't sync with
    /// (production-readiness U4): its identify advertises another wire
    /// major, or no agent version this daemon parses, or lists no sync
    /// protocol of this daemon's major. Reported once per peer and daemon
    /// run, until [`TransportEvent::Cleared`]; no sync stream opens with
    /// it meanwhile.
    Incompatible {
        /// The incompatible peer.
        peer: Peer,
        /// Its identify agent version, printable ASCII, at most
        /// `AGENT_MAX_BYTES`.
        agent: String,
        /// The wire major it advertises, `None` when its agent version
        /// names none.
        wire: Option<u16>,
        /// The wire major this daemon speaks.
        here: u16,
    },
    /// This daemon refused `peer`'s sync and disconnected it, though it
    /// speaks the same wire major: its build and this one disagree
    /// (decision 320). Reported on its first refusal; the transport then
    /// lets it connect only after a doubling backoff, until
    /// [`TransportEvent::Cleared`].
    BuildRefused {
        /// The refused peer.
        peer: Peer,
        /// Its identify agent version, empty if none arrived.
        agent: String,
    },
    /// A peer reported [`TransportEvent::Incompatible`] or
    /// [`TransportEvent::BuildRefused`] is no longer: it identified again
    /// as compatible (it upgraded, or downgraded), or the transport forgot
    /// it to remember a newer one past its bound.
    Cleared {
        /// The peer.
        peer: Peer,
    },
    /// `peer` told this side it joined `topic`: on their first connection,
    /// gossipsub's greeting lists every topic the peer is subscribed to,
    /// and a later join is told on its own. A publish on `topic` reaches
    /// the peer from now on.
    Subscribed {
        /// The subscribed peer.
        peer: Peer,
        /// The topic it joined.
        topic: String,
    },
}

/// Gossipsub's validation verdict for a received message (design §9):
/// only an accepted message is forwarded; a rejected one also counts
/// against the propagating peer's score; an ignored one does neither.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Acceptance {
    /// Forward the message.
    Accept,
    /// Drop it and count it against the propagating peer's score.
    Reject,
    /// Drop it.
    Ignore,
}

/// What the daemon shell asks the swarm to do, built from the net actors'
/// outputs. Owner: `M3.transport`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportCommand {
    /// Dial the multiaddr `addr`.
    Dial {
        /// The multiaddr to dial.
        addr: String,
    },
    /// Write one frame (an encoded envelope) to the sync stream with
    /// `peer`, length-delimited.
    SyncFrame {
        /// The peer the stream is with.
        peer: Peer,
        /// The frame's bytes.
        frame: Vec<u8>,
    },
    /// Close every connection to `peer`.
    Disconnect {
        /// The peer to disconnect.
        peer: Peer,
    },
    /// Join a gossipsub topic.
    Subscribe {
        /// The topic to join.
        topic: String,
    },
    /// Leave a gossipsub topic.
    Unsubscribe {
        /// The topic to leave.
        topic: String,
    },
    /// Publish `data` (an encoded envelope) on `topic`.
    Publish {
        /// The topic to publish on.
        topic: String,
        /// The encoded envelope.
        data: Vec<u8>,
    },
    /// Report the validation of the gossipsub message `id` received from
    /// `propagation`.
    Validate {
        /// Gossipsub's message id.
        id: Vec<u8>,
        /// The peer that forwarded it.
        propagation: Peer,
        /// The verdict.
        acceptance: Acceptance,
    },
    /// Lower `peer`'s score for a protocol violation (design §11, §15).
    Penalize {
        /// The peer to penalize.
        peer: Peer,
        /// The violation.
        reason: Rejection,
    },
    /// Stop the transport, as its last command: every connection is closed
    /// before the swarm is dropped, so each peer sees it end at once rather
    /// than when its pings fail, and a restarted daemon's next connection is
    /// its peers' first again.
    Stop,
}