1use std::collections::HashSet;
33use std::io::Read;
34use std::sync::Arc;
35
36use dashmap::DashMap;
37
38use crate::artifact_store::{ArtifactStore, ArtifactType};
39use crate::chain::ChainManager;
40pub use crate::mesh_artifact_types::{ExchangeConfig, ExchangeError};
41use crate::mesh_artifact_wire::{ArtifactDescriptor, ArtifactId, ArtifactKey, Bitfield};
42use crate::mesh_swarm_state::{Audience, GrantInfo, GrantOrigin, SwarmState};
43use crate::workload_pkg::codec::hex_encode;
44
45pub const MAX_PENDING: usize = 64;
47
48pub struct ArtifactExchange {
50 node_id: String,
51 pub(crate) store: Arc<ArtifactStore>,
52 config: ExchangeConfig,
53 pub(crate) descriptors: DashMap<ArtifactId, ArtifactDescriptor>,
55 pub(crate) pending: DashMap<ArtifactId, ArtifactDescriptor>,
57 pub(crate) by_content: DashMap<[u8; 32], ArtifactId>,
59 pub(crate) grants: DashMap<[u8; 32], Vec<GrantInfo>>,
61 served: DashMap<(ArtifactId, String), ()>,
63 pub(crate) peer_haves: DashMap<(ArtifactId, String), Bitfield>,
65 pub(crate) chain: Option<Arc<ChainManager>>,
66 pub(crate) swarm: SwarmState,
68}
69
70impl ArtifactExchange {
71 pub fn new(
73 node_id: impl Into<String>,
74 store: Arc<ArtifactStore>,
75 config: ExchangeConfig,
76 ) -> Result<Self, ExchangeError> {
77 config.validate()?;
78 Ok(Self {
79 node_id: node_id.into(),
80 store,
81 swarm: SwarmState::new(&config),
82 config,
83 descriptors: DashMap::new(),
84 pending: DashMap::new(),
85 by_content: DashMap::new(),
86 grants: DashMap::new(),
87 served: DashMap::new(),
88 peer_haves: DashMap::new(),
89 chain: None,
90 })
91 }
92
93 pub fn set_chain_manager(&mut self, cm: Arc<ChainManager>) {
95 self.chain = Some(cm);
96 }
97
98 pub fn node_id(&self) -> &str {
100 &self.node_id
101 }
102
103 pub fn store(&self) -> &Arc<ArtifactStore> {
105 &self.store
106 }
107
108 pub fn config(&self) -> &ExchangeConfig {
110 &self.config
111 }
112
113 pub fn seed_reader(
118 &self,
119 reader: &mut dyn Read,
120 expect: Option<([u8; 32], u64)>,
121 ) -> Result<ArtifactDescriptor, ExchangeError> {
122 let piece_size = self.config.piece_size;
123 let mut whole = blake3::Hasher::new();
124 let mut pieces = Vec::new();
125 let mut total = 0u64;
126 let mut buf = vec![0u8; piece_size as usize];
127 loop {
128 let n = read_full(reader, &mut buf)?;
129 if n == 0 {
130 break;
131 }
132 let piece = &buf[..n];
133 whole.update(piece);
134 let hash = *blake3::hash(piece).as_bytes();
135 self.store_piece(piece, &hash)?;
136 pieces.push(hash);
137 total += n as u64;
138 if n < buf.len() {
139 break;
140 }
141 }
142 let descriptor = ArtifactDescriptor {
143 piece_size,
144 total_size: total,
145 content_hash: *whole.finalize().as_bytes(),
146 pieces,
147 };
148 if let Some((hash, size)) = expect
149 && (hash != descriptor.content_hash || size != total)
150 {
151 return Err(ExchangeError::Mismatch(format!(
152 "expected {} ({size} bytes), read {} ({total} bytes)",
153 hex_encode(&hash),
154 descriptor.content_hex()
155 )));
156 }
157 self.register_verified(descriptor.clone())?;
159 Ok(descriptor)
160 }
161
162 pub fn seed_bytes(&self, content: &[u8]) -> Result<ArtifactDescriptor, ExchangeError> {
164 self.seed_reader(&mut &content[..], None)
165 }
166
167 fn register_verified(&self, d: ArtifactDescriptor) -> Result<ArtifactId, ExchangeError> {
170 d.validate()?;
171 let id = d.id();
172 self.pending.remove(&id);
173 let content_hash = d.content_hash;
174 self.by_content.insert(content_hash, id);
175 self.descriptors.insert(id, d);
176 self.on_verified(&id, &content_hash);
177 Ok(id)
178 }
179
180 pub(crate) fn note_pending(&self, d: &ArtifactDescriptor) -> Result<ArtifactId, ExchangeError> {
183 d.validate()?;
184 let id = d.id();
185 if !self.descriptors.contains_key(&id) {
186 if !self.pending.contains_key(&id) && self.pending.len() >= MAX_PENDING {
187 return Err(ExchangeError::Config(format!(
188 "{MAX_PENDING} partial downloads already held; forget one first"
189 )));
190 }
191 self.pending.insert(id, d.clone());
192 if let Some(cache) = self.swarm.cache() {
193 cache.on_pending();
194 }
195 }
196 Ok(id)
197 }
198
199 pub(crate) fn promote(
204 &self,
205 d: &ArtifactDescriptor,
206 written: &HashSet<[u8; 32]>,
207 ) -> Result<(), ExchangeError> {
208 match self.check_whole(d, &mut |_| Ok(())) {
209 Ok(_) => self.register_verified(d.clone()).map(|_| ()),
210 Err(e) => {
211 self.discard(d, written);
212 Err(e)
213 }
214 }
215 }
216
217 fn discard(&self, d: &ArtifactDescriptor, written: &HashSet<[u8; 32]>) {
227 let id = d.id();
228 if self.descriptors.get(&id).is_some_and(|v| *v == *d) {
229 return; }
231 self.pending.remove_if(&id, |_, p| p == d);
232 let mut keep = HashSet::new();
233 for v in self.descriptors.iter() {
234 keep.insert(v.content_hash);
235 keep.extend(v.pieces.iter().copied());
236 }
237 for p in self.pending.iter() {
238 keep.extend(p.pieces.iter().copied());
239 }
240 for piece in d.pieces.iter().filter(|p| written.contains(*p)) {
241 if !keep.contains(piece) {
242 let _ = self.store.remove(&hex_encode(piece));
243 }
244 }
245 }
246
247 pub fn descriptor(&self, id: &ArtifactId) -> Option<ArtifactDescriptor> {
249 self.descriptors.get(id).map(|d| d.clone())
250 }
251
252 pub fn is_verified(&self, id: &ArtifactId) -> bool {
254 self.descriptors.contains_key(id)
255 }
256
257 pub fn resolve(&self, key: &ArtifactKey) -> Option<ArtifactDescriptor> {
259 let id = match key {
260 ArtifactKey::Root(id) => *id,
261 ArtifactKey::Content(h) => *self.by_content.get(h)?,
262 };
263 self.descriptor(&id)
264 }
265
266 pub fn have(&self, id: &ArtifactId) -> Option<Bitfield> {
269 let d = self
270 .descriptor(id)
271 .or_else(|| self.pending.get(id).map(|d| d.clone()))?;
272 Some(self.have_of(&d))
273 }
274
275 pub(crate) fn have_of(&self, d: &ArtifactDescriptor) -> Bitfield {
277 let mut b = Bitfield::new(d.piece_count());
278 for (i, p) in d.pieces.iter().enumerate() {
279 b.set(i as u32, self.store.contains(&hex_encode(p)));
280 }
281 b
282 }
283
284 pub fn read_to(
287 &self,
288 id: &ArtifactId,
289 sink: &mut dyn FnMut(&[u8]) -> Result<(), ExchangeError>,
290 ) -> Result<u64, ExchangeError> {
291 let d = self
292 .descriptor(id)
293 .ok_or_else(|| ExchangeError::Unknown(id.to_string()))?;
294 self.check_whole(&d, sink)
295 }
296
297 fn check_whole(
298 &self,
299 d: &ArtifactDescriptor,
300 sink: &mut dyn FnMut(&[u8]) -> Result<(), ExchangeError>,
301 ) -> Result<u64, ExchangeError> {
302 let id = d.id();
303 let mut whole = blake3::Hasher::new();
304 let mut total = 0u64;
305 for i in 0..d.piece_count() {
306 let data = self.load_piece(d, i)?;
307 whole.update(&data);
308 total += data.len() as u64;
309 sink(&data)?;
310 }
311 if *whole.finalize().as_bytes() != d.content_hash || total != d.total_size {
312 return Err(ExchangeError::Mismatch(format!(
313 "pieces of {id} do not assemble to content {}",
314 d.content_hex()
315 )));
316 }
317 Ok(total)
318 }
319
320 pub fn read_all(&self, id: &ArtifactId) -> Result<Vec<u8>, ExchangeError> {
322 let d = self
323 .descriptor(id)
324 .ok_or_else(|| ExchangeError::Unknown(id.to_string()))?;
325 if d.total_size > self.config.materialize_limit {
326 return Err(ExchangeError::Config(format!(
327 "artifact {id} is larger than materialize_limit"
328 )));
329 }
330 let mut out = Vec::with_capacity(d.total_size as usize);
331 self.read_to(id, &mut |b| {
332 out.extend_from_slice(b);
333 Ok(())
334 })?;
335 Ok(out)
336 }
337
338 #[cfg(test)]
340 pub(crate) fn grant(&self, content_hash: [u8; 32], package_id: &str) {
341 self.grant_with(content_hash, package_id, Vec::new(), GrantOrigin::OptIn);
342 }
343
344 pub(crate) fn grant_with(
349 &self,
350 content_hash: [u8; 32],
351 package_id: &str,
352 signers: Vec<String>,
353 origin: GrantOrigin,
354 ) {
355 if self.is_revoked_subject(package_id, &signers, &content_hash) {
356 return; }
358 let info = GrantInfo {
359 package_id: package_id.to_string(),
360 signers,
361 origin,
362 };
363 {
364 let mut gs = self.grants.entry(content_hash).or_default();
365 match gs.iter_mut().find(|g| g.package_id == package_id) {
366 Some(g) => *g = info,
367 None => gs.push(info),
368 }
369 }
370 self.on_granted(&content_hash);
371 }
372
373 pub fn is_servable(&self, d: &ArtifactDescriptor) -> bool {
380 self.is_servable_to(d, &Audience::Advertise)
381 }
382
383 pub fn is_servable_to(&self, d: &ArtifactDescriptor, audience: &Audience<'_>) -> bool {
385 self.descriptors.get(&d.id()).is_some_and(|v| *v == *d)
386 && self.servable_grant(&d.content_hash, audience).is_some()
387 && !self.is_revoked(&d.content_hash)
388 }
389
390 pub(crate) fn note_peer_have(&self, id: ArtifactId, peer: &str, have: Bitfield) {
392 self.peer_haves.insert((id, peer.to_string()), have);
393 }
394
395 pub fn peer_have(&self, id: &ArtifactId, peer: &str) -> Option<Bitfield> {
397 self.peer_haves
398 .get(&(*id, peer.to_string()))
399 .map(|b| b.clone())
400 }
401
402 pub(crate) fn load_piece(
404 &self,
405 d: &ArtifactDescriptor,
406 i: u32,
407 ) -> Result<Vec<u8>, ExchangeError> {
408 let hash = d
409 .pieces
410 .get(i as usize)
411 .ok_or_else(|| ExchangeError::Unknown(format!("piece {i}")))?;
412 self.store
413 .load(&hex_encode(hash))
414 .map_err(|e| ExchangeError::Store(e.to_string()))
415 }
416
417 pub(crate) fn store_piece(&self, data: &[u8], hash: &[u8; 32]) -> Result<bool, ExchangeError> {
420 let key = hex_encode(hash);
421 if self.store.contains(&key) {
422 return Ok(false);
423 }
424 let stored = self
425 .store
426 .store(data, ArtifactType::Generic)
427 .map_err(|e| ExchangeError::Store(e.to_string()))?;
428 if stored != key {
429 return Err(ExchangeError::Mismatch(format!(
430 "piece stored as {stored}, expected {key}"
431 )));
432 }
433 self.swarm.owned.insert(*hash, ());
434 Ok(true)
435 }
436
437 pub(crate) fn materialize(&self, id: &ArtifactId) -> Result<(), ExchangeError> {
439 let Some(d) = self.descriptor(id) else {
440 return Ok(());
441 };
442 if d.total_size > self.config.materialize_limit || self.store.contains(&d.content_hex()) {
443 return Ok(());
444 }
445 let bytes = self.read_all(id)?;
446 self.store
447 .store(&bytes, ArtifactType::Generic)
448 .map_err(|e| ExchangeError::Store(e.to_string()))?;
449 self.swarm.owned.insert(d.content_hash, ());
450 Ok(())
451 }
452
453 pub(crate) fn chain_fetch(&self, payload: serde_json::Value) {
456 if let Some(cm) = &self.chain {
457 cm.append(
458 "mesh_artifact",
459 crate::chain::EVENT_KIND_ARTIFACT_FETCH,
460 Some(payload),
461 );
462 }
463 }
464
465 pub(crate) fn chain_piece_rejected(&self, id: &ArtifactId, index: u32, peer: &str, why: &str) {
466 if let Some(cm) = &self.chain {
467 cm.append(
468 "mesh_artifact",
469 crate::chain::EVENT_KIND_ARTIFACT_PIECE_REJECTED,
470 Some(serde_json::json!({
471 "artifact_id": id.to_string(),
472 "piece_index": index,
473 "peer": peer,
474 "reason": why,
475 "node": self.node_id,
476 })),
477 );
478 }
479 }
480
481 pub(crate) fn chain_serve_once(&self, d: &ArtifactDescriptor, peer: &str) {
483 let key = (d.id(), peer.to_string());
484 if self.served.insert(key, ()).is_some() {
485 return;
486 }
487 if let Some(cm) = &self.chain {
488 cm.append(
489 "mesh_artifact",
490 crate::chain::EVENT_KIND_ARTIFACT_SERVE,
491 Some(serde_json::json!({
492 "artifact_id": d.id().to_string(),
493 "content_hash": d.content_hex(),
494 "peer": peer,
495 "node": self.node_id,
496 })),
497 );
498 }
499 }
500}
501
502fn read_full(r: &mut dyn Read, buf: &mut [u8]) -> Result<usize, ExchangeError> {
503 let mut filled = 0;
504 while filled < buf.len() {
505 match r.read(&mut buf[filled..]) {
506 Ok(0) => break,
507 Ok(n) => filled += n,
508 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
509 Err(e) => return Err(ExchangeError::Io(e.to_string())),
510 }
511 }
512 Ok(filled)
513}
514
515#[cfg(test)]
516#[path = "mesh_artifact_unit_tests.rs"]
517mod tests;