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}