Skip to main content

clawft_kernel/
mesh_swarm_state.rs

1//! Per-node swarm state held by [`crate::mesh_artifact::ArtifactExchange`]
2//! (ADR-099 section 6, card mesh-placement-25): bandwidth caps, banned
3//! peers, measured link speeds, the revocation list, the seeding record and
4//! the cache hook.
5
6use std::sync::{Arc, Mutex, OnceLock, Weak};
7
8use dashmap::DashMap;
9
10use crate::mesh_artifact_types::ExchangeConfig;
11use crate::mesh_swarm_cache::ArtifactCache;
12use crate::mesh_swarm_picker::LinkStats;
13use crate::mesh_swarm_rate::Bandwidth;
14use crate::revocation::RevocationList;
15
16/// Where a package's licence stands, from its signed manifest.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum GrantOrigin {
19    /// Cognitum provenance (a `cognitum.*` attestation or a `cognitum`
20    /// release URL): licence-gated, whatever else the manifest says.
21    Cognitum {
22        /// Cog id from the manifest.
23        cog_id: String,
24        /// Cog version from the manifest.
25        version: String,
26    },
27    /// The signer wrote `redistributable = true`.
28    OptIn,
29    /// The signer did not opt in (the field is absent or false).
30    NotFlagged,
31}
32
33/// What allows one content hash to be seeded: the verified package that
34/// lists it and that package's signers.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct GrantInfo {
37    /// Package id of the verified manifest.
38    pub package_id: String,
39    /// Lower-case hex public keys of the manifest's accepted signers.
40    pub signers: Vec<String>,
41    /// Licence standing of the package. Anything but `OptIn` must not be
42    /// handed to other nodes by default (see [`ManifestPolicy`]).
43    pub origin: GrantOrigin,
44}
45
46impl GrantInfo {
47    /// True when the signer opted in to redistribution.
48    pub fn is_opt_in(&self) -> bool {
49        self.origin == GrantOrigin::OptIn
50    }
51}
52
53/// An authenticated (or not) peer asking to be served.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ServePeer {
56    /// The peer's node id.
57    pub node_id: String,
58    /// True only when the id was verified by admission (a signed hello bound
59    /// to the connection); false for a claimed id.
60    pub verified: bool,
61}
62
63impl ServePeer {
64    /// A peer whose id is only claimed (the default for [`crate::mesh_artifact::ArtifactExchange::serve`]).
65    pub fn unverified(node_id: impl Into<String>) -> Self {
66        Self {
67            node_id: node_id.into(),
68            verified: false,
69        }
70    }
71
72    /// A peer whose id admission verified.
73    pub fn verified(node_id: impl Into<String>) -> Self {
74        Self {
75            node_id: node_id.into(),
76            verified: true,
77        }
78    }
79}
80
81/// Who a redistribution decision is for.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum Audience<'a> {
84    /// Serving pieces (or a descriptor) to this requesting peer.
85    Serve(&'a ServePeer),
86    /// Listing the content in facts broadcast to every peer.
87    Advertise,
88    /// Starting to seed it (`artifact.seed`).
89    Seed,
90}
91
92/// Decides whether the grants for a content hash allow handing it to an
93/// audience. The one policy point: seeding, advertisement and serving all go
94/// through [`crate::mesh_artifact::ArtifactExchange`]'s grant check, which
95/// asks this. It is fixed when the exchange is built
96/// ([`crate::mesh_artifact_types::ExchangeConfig::redistribution`]); there is
97/// no way to swap it afterwards.
98///
99/// Whatever the policy says, content that is not `OptIn` is never listed in
100/// broadcast facts: it can be discovered only by asking a peer
101/// ([`crate::mesh_artifact::ArtifactExchange::who_has`]), where the serving
102/// side sees who is asking.
103///
104/// `grants` is every grant held for the hash, revoked or not (a revoked one
105/// still counts toward a veto until the sweep removes it).
106pub trait RedistributionPolicy: std::fmt::Debug + Send + Sync + 'static {
107    /// May `content_hash` be handed to `audience`, given its `grants`? Fail
108    /// closed: an empty list is "no".
109    fn allows(&self, content_hash: &[u8; 32], grants: &[GrantInfo], audience: &Audience<'_>)
110    -> bool;
111}
112
113/// The default: every package that lists the hash must have been signed
114/// `redistributable = true` and have no Cognitum provenance (`OptIn`); any
115/// other grant vetoes the hash for every package, for every audience.
116#[derive(Debug, Default, Clone, Copy)]
117pub struct ManifestPolicy;
118
119impl RedistributionPolicy for ManifestPolicy {
120    fn allows(&self, _: &[u8; 32], grants: &[GrantInfo], _: &Audience<'_>) -> bool {
121        !grants.is_empty() && grants.iter().all(GrantInfo::is_opt_in)
122    }
123}
124
125/// Mutable swarm state of one node.
126pub(crate) struct SwarmState {
127    pub(crate) bandwidth: Bandwidth,
128    pub(crate) links: LinkStats,
129    /// Banned peer -> reason.
130    pub(crate) bans: DashMap<String, String>,
131    /// Corrupt pieces seen per peer.
132    pub(crate) corrupt: DashMap<String, u32>,
133    pub(crate) revocations: OnceLock<Arc<RevocationList>>,
134    /// Redistribution policy, fixed at construction.
135    pub(crate) policy: Arc<dyn RedistributionPolicy>,
136    /// Blobs this exchange created in the store (the only ones it may evict).
137    pub(crate) owned: DashMap<[u8; 32], ()>,
138    /// Content hashes already chained as `artifact.seed`.
139    pub(crate) seeded: DashMap<[u8; 32], ()>,
140    cache: Mutex<Weak<ArtifactCache>>,
141}
142
143impl SwarmState {
144    pub(crate) fn new(cfg: &ExchangeConfig) -> Self {
145        Self {
146            bandwidth: Bandwidth::new(cfg.upload_bytes_per_sec, cfg.download_bytes_per_sec),
147            links: LinkStats::default(),
148            bans: DashMap::new(),
149            corrupt: DashMap::new(),
150            revocations: OnceLock::new(),
151            policy: cfg.redistribution.clone(),
152            seeded: DashMap::new(),
153            owned: DashMap::new(),
154            cache: Mutex::new(Weak::new()),
155        }
156    }
157
158    pub(crate) fn attach_cache(&self, cache: &Arc<ArtifactCache>) {
159        *self.cache.lock().unwrap_or_else(|p| p.into_inner()) = Arc::downgrade(cache);
160    }
161
162    pub(crate) fn cache(&self) -> Option<Arc<ArtifactCache>> {
163        self.cache.lock().unwrap_or_else(|p| p.into_inner()).upgrade()
164    }
165}