1use std::collections::BTreeMap;
9use std::path::{Path, PathBuf};
10
11use monetize_embed::fleet::{ask, call, kind, order_message, Ask, FleetCall, OrderAck, OrdersAfter, Report, SignedOrder, LONG_POLL_MAX_MS};
12use monetize_embed::VerifyingKey;
13use serde::{Deserialize, Serialize};
14
15use super::key::BoxKey;
16
17pub const STATE_REL: &str = "fleet/pulled.json";
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct Addressee {
23 pub product: String,
24 pub instance: String,
25 pub role: String,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Fault {
32 pub code: String,
33 pub message: String,
34}
35
36impl Fault {
37 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Fault {
38 Fault { code: code.into(), message: message.into() }
39 }
40}
41
42impl std::fmt::Display for Fault {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(f, "{}: {}", self.code, self.message)
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct Signed {
51 pub call: FleetCall,
52 pub signature: Vec<u8>,
53 pub box_key: Vec<u8>,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Eq)]
60pub struct Asked {
61 pub case_id: String,
62 pub words: String,
63 pub url: String,
64}
65
66pub trait Link {
68 fn report(&mut self, s: Signed) -> Result<u64, Fault>;
70 fn orders(&mut self, s: Signed) -> Result<Vec<SignedOrder>, Fault>;
71 fn ask(&mut self, s: Signed) -> Result<Asked, Fault>;
72 fn ask_bytes(&mut self, s: Signed) -> Result<(Asked, Vec<u8>), Fault> {
75 self.ask(s).map(|a| (a, Vec::new()))
76 }
77}
78
79pub trait Asker {
82 fn ask(&mut self, a: &Ask) -> Result<Asked, Fault>;
83 fn ask_bytes(&mut self, a: &Ask) -> Result<(Asked, Vec<u8>), Fault> {
85 self.ask(a).map(|x| (x, Vec::new()))
86 }
87}
88
89pub fn grow_set(asker: &mut dyn Asker, gib: u64) -> Result<String, String> {
92 let a = Ask { kind: ask::GROW_SET.into(), body: serde_json::json!({ "members": [], "gib": gib }) };
93 let Asked { case_id: case, words, .. } = asker.ask(&a).map_err(|f| f.to_string())?;
94 Ok(if words.is_empty() { case } else { words })
95}
96
97pub fn fetch(asker: &mut dyn Asker, f: &monetize_embed::fleet::Fetch) -> Result<Vec<u8>, String> {
102 let a = Ask { kind: ask::FETCH.into(), body: serde_json::json!({ "id": f.id }) };
103 let (_, bytes) = asker.ask_bytes(&a).map_err(|e| e.to_string())?;
104 if bytes.len() as u64 != f.bytes {
105 return Err(format!("the file pulled for {} is {} bytes, and the order names {}", f.param, bytes.len(), f.bytes));
106 }
107 Ok(bytes)
108}
109
110pub struct Ctx<'a> {
112 pub me: &'a Addressee,
113 pub key: &'a VerifyingKey,
115 pub now_unix_ms: u64,
116}
117
118pub trait PumpBox {
120 fn health(&self) -> BTreeMap<String, String> {
122 BTreeMap::new()
123 }
124 fn product_report(&self) -> Option<String> {
126 None
127 }
128 fn own_review(&self) -> Option<String> {
130 None
131 }
132 fn run(&self, ctx: &Ctx, order: &SignedOrder, ask: &mut dyn Asker) -> OrderAck;
137}
138
139pub use monetize_embed::fleet::FUTURE_SKEW_MS;
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum Checked {
146 Forged(String),
148 Refused(String),
150 Fresh,
152}
153
154pub fn check(o: &SignedOrder, me: &Addressee, key: &VerifyingKey, last_seq: u64, now_unix_ms: u64) -> Checked {
161 let signed = ed25519_dalek::Signature::from_slice(&o.signature).map(|s| key.verify_strict(&order_message(o), &s).is_ok()).unwrap_or(false);
163 if !signed {
164 return Checked::Forged(format!("order {} ({}) is not signed by this product's monetize key", o.seq, o.kind));
165 }
166 if o.product != me.product || o.instance != me.instance || o.role != me.role {
167 return Checked::Refused(format!("order {} is for {}/{}/{}, and this box is {}/{}/{}", o.seq, o.product, o.instance, o.role, me.product, me.instance, me.role));
168 }
169 if o.seq <= last_seq {
170 return Checked::Refused(format!("order {} is not newer than {last_seq}, the last one this box took: a replay", o.seq));
171 }
172 if o.not_after_unix_ms != 0 && now_unix_ms > o.not_after_unix_ms {
173 return Checked::Refused(format!("order {} expired {} s ago", o.seq, (now_unix_ms - o.not_after_unix_ms) / 1000));
174 }
175 if o.issued_unix_ms > now_unix_ms.saturating_add(FUTURE_SKEW_MS) {
176 return Checked::Refused(format!("order {} is dated {} s in the future; whose clock is wrong?", o.seq, (o.issued_unix_ms - now_unix_ms) / 1000));
177 }
178 Checked::Fresh
179}
180
181#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(default)]
184pub struct Pulled {
185 pub last_seq: u64,
187 pub pending: Vec<OrderAck>,
189 pub twin_review: Option<String>,
191}
192
193#[derive(Debug, Clone, Default, PartialEq, Eq)]
195pub struct Pull {
196 pub acks: Vec<OrderAck>,
197 pub forged: Vec<String>,
198}
199
200pub struct Puller {
201 pub me: Addressee,
202 pub build: String,
203 key: BoxKey,
204 state: Option<PathBuf>,
205 pub pulled: Pulled,
206 heard: bool,
207}
208
209impl Puller {
210 pub fn new(me: Addressee, key: BoxKey, build: impl Into<String>) -> Puller {
212 Puller { me, build: build.into(), key, state: None, pulled: Pulled::default(), heard: false }
213 }
214
215 pub fn at(root: &Path, me: Addressee, build: impl Into<String>) -> std::io::Result<Puller> {
217 let key = BoxKey::load_or_mint(root)?;
218 let state = root.join(STATE_REL);
219 let pulled = match std::fs::read(&state) {
220 Ok(bytes) => serde_json::from_slice(&bytes).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}: {e}", state.display())))?,
221 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Pulled::default(),
222 Err(e) => return Err(e),
223 };
224 Ok(Puller { me, build: build.into(), key, state: Some(state), pulled, heard: false })
225 }
226
227 pub fn box_public(&self) -> [u8; 32] {
228 self.key.public()
229 }
230
231 fn save(&self) -> Result<(), Fault> {
232 let Some(path) = &self.state else { return Ok(()) };
233 let bytes = serde_json::to_vec(&self.pulled).map_err(|e| Fault::new("internal", e.to_string()))?;
234 crate::write_atomic(path, &bytes, false).map_err(|e| Fault::new("io", format!("{}: {e}", path.display())))
235 }
236
237 fn signed(&self, which: &str, body: String, now_unix_ms: u64) -> Signed {
238 let c = FleetCall { product: self.me.product.clone(), instance: self.me.instance.clone(), role: self.me.role.clone(), call: which.to_owned(), body, at_unix_ms: now_unix_ms };
239 let signature = self.key.sign(&c);
240 Signed { call: c, signature, box_key: if self.heard { Vec::new() } else { self.key.public().to_vec() } }
241 }
242
243 pub fn report(&mut self, link: &mut dyn Link, health: BTreeMap<String, String>, product: Option<String>, own_review: Option<String>, now_unix_ms: u64) -> Result<u64, Fault> {
247 let sent = self.pulled.pending.len();
248 let report = Report {
249 build: self.build.clone(),
250 health,
251 acked: self.pulled.pending.clone(),
252 twin_review: own_review.or_else(|| self.pulled.twin_review.clone()),
253 product,
254 measured_unix_ms: now_unix_ms,
255 };
256 let body = serde_json::to_string(&report).map_err(|e| Fault::new("internal", e.to_string()))?;
257 let next = link.report(self.signed(call::REPORT, body, now_unix_ms))?;
258 self.heard = true;
259 self.pulled.pending.drain(..sent);
260 self.pulled.twin_review = None;
261 self.save()?;
262 Ok(next)
263 }
264
265 pub fn pull(&mut self, link: &mut dyn Link, b: &dyn PumpBox, monetize_pubkey: Option<&[u8; 32]>, wait_ms: u64, now_unix_ms: u64) -> Result<Pull, Fault> {
269 let Some(raw) = monetize_pubkey else {
270 return Err(Fault::new("no_key", "this box has no monetize key yet, so no order could be verified: none is pulled"));
271 };
272 let key = VerifyingKey::from_bytes(raw).map_err(|e| Fault::new("no_key", format!("the monetize key is not an Ed25519 point: {e}")))?;
273 let body = serde_json::to_string(&OrdersAfter { after: self.pulled.last_seq, wait_ms: wait_ms.min(LONG_POLL_MAX_MS) }).map_err(|e| Fault::new("internal", e.to_string()))?;
274 let mut orders = link.orders(self.signed(call::ORDERS, body, now_unix_ms))?;
275 self.heard = true;
276 orders.sort_by_key(|o| o.seq);
277 let mut out = Pull::default();
278 for o in orders {
279 let checked = check(&o, &self.me, &key, self.pulled.last_seq, now_unix_ms);
280 if let Checked::Forged(why) = checked {
281 out.forged.push(why);
282 continue;
283 }
284 if o.seq <= self.pulled.last_seq {
285 continue;
286 }
287 self.pulled.last_seq = o.seq;
288 self.save()?;
289 let ack = match checked {
290 Checked::Fresh => {
291 let me = self.me.clone();
292 let ctx = Ctx { me: &me, key: &key, now_unix_ms };
293 let ack = {
294 let mut via = AskVia { puller: &mut *self, link: &mut *link, now_unix_ms };
295 b.run(&ctx, &o, &mut via)
296 };
297 if o.kind == kind::REVIEW_TWIN && ack.ok {
298 self.pulled.twin_review = Some(ack.receipt_json.clone());
299 }
300 ack
301 }
302 Checked::Refused(detail) => OrderAck { seq: o.seq, ok: false, detail, receipt_json: String::new() },
303 Checked::Forged(_) => unreachable!("forged orders were skipped"),
304 };
305 self.pulled.pending.push(ack.clone());
306 self.save()?;
307 out.acks.push(ack);
308 }
309 Ok(out)
310 }
311
312 pub fn ask(&mut self, link: &mut dyn Link, a: &Ask, now_unix_ms: u64) -> Result<Asked, Fault> {
314 let body = serde_json::to_string(a).map_err(|e| Fault::new("internal", e.to_string()))?;
315 let answer = link.ask(self.signed(call::ASK, body, now_unix_ms))?;
316 self.heard = true;
317 Ok(answer)
318 }
319
320 pub fn ask_bytes(&mut self, link: &mut dyn Link, a: &Ask, now_unix_ms: u64) -> Result<(Asked, Vec<u8>), Fault> {
322 let body = serde_json::to_string(a).map_err(|e| Fault::new("internal", e.to_string()))?;
323 let answer = link.ask_bytes(self.signed(call::ASK, body, now_unix_ms))?;
324 self.heard = true;
325 Ok(answer)
326 }
327}
328
329struct AskVia<'a> {
331 puller: &'a mut Puller,
332 link: &'a mut dyn Link,
333 now_unix_ms: u64,
334}
335
336impl Asker for AskVia<'_> {
337 fn ask(&mut self, a: &Ask) -> Result<Asked, Fault> {
338 self.puller.ask(self.link, a, self.now_unix_ms)
339 }
340 fn ask_bytes(&mut self, a: &Ask) -> Result<(Asked, Vec<u8>), Fault> {
341 self.puller.ask_bytes(self.link, a, self.now_unix_ms)
342 }
343}
344
345#[cfg(test)]
346pub(crate) mod tests {
347 use super::*;
348 use ed25519_dalek::Signer as _;
349 use std::cell::RefCell;
350
351 pub const MONETIZE_SEED: [u8; 32] = [7u8; 32];
352
353 pub fn me(role: &str) -> Addressee {
354 Addressee { product: "a-product".into(), instance: "acme".into(), role: role.into() }
355 }
356
357 pub fn monetize_key() -> VerifyingKey {
358 ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).verifying_key()
359 }
360
361 fn mkey() -> [u8; 32] {
362 monetize_key().to_bytes()
363 }
364
365 pub fn signed(seq: u64, role: &str, kind: &str, body: &str, seed: [u8; 32]) -> SignedOrder {
367 let mut o = SignedOrder { seq, product: "a-product".into(), instance: "acme".into(), role: role.into(), kind: kind.into(), body: body.into(), issued_unix_ms: 9_000, not_after_unix_ms: 0, signature: Vec::new() };
368 o.signature = ed25519_dalek::SigningKey::from_bytes(&seed).sign(&order_message(&o)).to_bytes().to_vec();
369 o
370 }
371
372 #[derive(Default)]
374 pub struct Guardian {
375 pub queue: Vec<SignedOrder>,
376 pub heard: Vec<Signed>,
377 }
378
379 impl Link for Guardian {
380 fn report(&mut self, s: Signed) -> Result<u64, Fault> {
381 let r: Report = serde_json::from_str(&s.call.body).unwrap();
382 self.queue.retain(|o| !r.acked.iter().any(|a| a.seq == o.seq));
383 self.heard.push(s);
384 Ok(30_000)
385 }
386 fn orders(&mut self, s: Signed) -> Result<Vec<SignedOrder>, Fault> {
387 let after: OrdersAfter = serde_json::from_str(&s.call.body).unwrap();
388 self.heard.push(s);
389 Ok(self.queue.iter().filter(|o| o.seq > after.after).cloned().collect())
390 }
391 fn ask(&mut self, s: Signed) -> Result<Asked, Fault> {
392 let _: Ask = serde_json::from_str(&s.call.body).unwrap();
393 self.heard.push(s);
394 Ok(Asked { case_id: "c-1".into(), words: "resized".into(), url: String::new() })
395 }
396 }
397
398 #[derive(Default)]
401 pub struct Fake {
402 pub ran: RefCell<Vec<(u64, String)>>,
403 }
404
405 impl PumpBox for Fake {
406 fn run(&self, ctx: &Ctx, o: &SignedOrder, ask: &mut dyn Asker) -> OrderAck {
407 self.ran.borrow_mut().push((o.seq, o.kind.clone()));
408 let said = match o.kind.as_str() {
409 "note" => Ok(format!("noted for {}", ctx.me.role)),
410 "grow" => grow_set(ask, 64),
411 kind::REVIEW_TWIN => return OrderAck { seq: o.seq, ok: true, detail: "reviewed".into(), receipt_json: r#"{"complete":"yes"}"#.into() },
412 other => Err(format!("order {}: this product does not know the order kind {other:?}", o.seq)),
413 };
414 match said {
415 Ok(detail) => OrderAck { seq: o.seq, ok: true, detail, receipt_json: String::new() },
416 Err(detail) => OrderAck { seq: o.seq, ok: false, detail, receipt_json: String::new() },
417 }
418 }
419 }
420
421 #[test]
422 fn an_order_is_pulled_run_and_acked_in_the_next_report_and_never_twice() {
423 let b = Fake::default();
424 let mut g = Guardian::default();
425 g.queue.push(signed(4, "main", "note", "{}", MONETIZE_SEED));
426 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
427 let pulled = p.pull(&mut g, &b, Some(&mkey()), 0, 10_000).unwrap();
428 assert_eq!(pulled.acks.len(), 1);
429 assert!(pulled.acks[0].ok, "{pulled:?}");
430 assert_eq!(pulled.acks[0].detail, "noted for main");
431 p.report(&mut g, BTreeMap::from([("serving".into(), "yes".into())]), Some("{}".into()), None, 10_001).unwrap();
432 let r: Report = serde_json::from_str(&g.heard.last().unwrap().call.body).unwrap();
433 assert_eq!(r.acked.iter().map(|a| (a.seq, a.ok)).collect::<Vec<_>>(), vec![(4, true)]);
434 assert_eq!((r.product.as_deref(), r.build.as_str()), (Some("{}"), "test"));
435 assert!(g.queue.is_empty() && p.pulled.pending.is_empty());
436 assert!(p.pull(&mut g, &b, Some(&mkey()), 0, 10_002).unwrap().acks.is_empty(), "nothing runs twice");
437 assert_eq!(b.ran.borrow().len(), 1);
438 }
439
440 #[test]
445 fn a_weak_key_signs_no_order_and_a_real_one_still_does() {
446 let mut identity = [0u8; 32];
447 identity[0] = 1;
448 let weak = VerifyingKey::from_bytes(&identity).expect("the identity point decompresses");
449 let mut o = signed(3, "main", "recharge_from_twin", "{}", MONETIZE_SEED);
450 o.signature = { let mut s = vec![0u8; 64]; s[0] = 1; s };
451 assert!(matches!(check(&o, &me("main"), &weak, 0, 9_500), Checked::Forged(_)), "a weak key verifies nothing");
452 let real = signed(3, "main", "recharge_from_twin", "{}", MONETIZE_SEED);
453 assert_eq!(check(&real, &me("main"), &monetize_key(), 0, 9_500), Checked::Fresh);
454 }
455
456 #[test]
459 fn an_order_dated_ahead_of_the_box_clock_is_refused_by_name() {
460 let mut ahead = signed(3, "main", "note", "{}", MONETIZE_SEED);
461 ahead.issued_unix_ms = 9_000 + FUTURE_SKEW_MS + 1;
462 ahead.signature = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).sign(&order_message(&ahead)).to_bytes().to_vec();
463 match check(&ahead, &me("main"), &monetize_key(), 0, 9_000) {
464 Checked::Refused(why) => assert!(why.contains("in the future"), "{why}"),
465 other => panic!("{other:?}"),
466 }
467 let mut skew = ahead.clone();
468 skew.issued_unix_ms = 9_000 + FUTURE_SKEW_MS;
469 skew.signature = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).sign(&order_message(&skew)).to_bytes().to_vec();
470 assert_eq!(check(&skew, &me("main"), &monetize_key(), 0, 9_000), Checked::Fresh);
471 }
472
473 #[test]
474 fn a_forged_order_never_runs_never_acks_and_never_moves_the_sequence() {
475 let b = Fake::default();
476 let mut g = Guardian::default();
477 g.queue.push(signed(u64::MAX, "main", "note", "{}", [9u8; 32]));
478 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
479 let pulled = p.pull(&mut g, &b, Some(&mkey()), 0, 10_000).unwrap();
480 assert!(pulled.acks.is_empty() && pulled.forged.len() == 1);
481 assert_eq!(p.pulled.last_seq, 0);
482 assert!(b.ran.borrow().is_empty());
483 }
484
485 #[test]
489 fn another_boxs_an_expired_or_an_unknown_order_is_refused_by_name() {
490 let k = monetize_key();
491 let twin = signed(5, "twin", "note", "{}", MONETIZE_SEED);
492 assert!(matches!(check(&twin, &me("main"), &k, 0, 10_000), Checked::Refused(w) if w.contains("for a-product/acme/twin")));
493 let mut late = signed(5, "main", "note", "{}", MONETIZE_SEED);
494 late.not_after_unix_ms = 1_000;
495 let late = { let mut l = late.clone(); l.signature = signed_like(&l); l };
496 assert!(matches!(check(&late, &me("main"), &k, 0, 61_000), Checked::Refused(w) if w.contains("expired 60 s ago")));
497 assert!(matches!(check(&signed(5, "main", "note", "{}", MONETIZE_SEED), &me("main"), &k, 5, 1), Checked::Refused(w) if w.contains("a replay")));
498 assert_eq!(check(&signed(6, "main", "note", "{}", MONETIZE_SEED), &me("main"), &k, 5, 1), Checked::Fresh);
499 let b = Fake::default();
500 let mut g = Guardian::default();
501 g.queue.push(twin);
502 g.queue.push(signed(7, "main", "reboot_the_moon", "{}", MONETIZE_SEED));
503 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
504 let pulled = p.pull(&mut g, &b, Some(&mkey()), 0, 10_000).unwrap();
505 assert_eq!(pulled.acks.iter().map(|a| (a.seq, a.ok)).collect::<Vec<_>>(), vec![(5, false), (7, false)]);
506 assert!(pulled.acks[1].detail.contains("does not know the order kind \"reboot_the_moon\""), "{pulled:?}");
507 assert_eq!(*b.ran.borrow(), vec![(7, "reboot_the_moon".to_string())], "only the box's own order reaches the product");
508 }
509
510 fn signed_like(o: &SignedOrder) -> Vec<u8> {
511 ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).sign(&order_message(o)).to_bytes().to_vec()
512 }
513
514 #[test]
515 fn without_a_monetize_key_nothing_is_pulled_and_monetize_is_not_even_asked() {
516 let mut g = Guardian::default();
517 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
518 assert_eq!(p.pull(&mut g, &Fake::default(), None, 0, 10_000).unwrap_err().code, "no_key");
519 assert!(g.heard.is_empty());
520 }
521
522 #[test]
523 fn the_first_call_carries_the_box_key_and_later_ones_do_not() {
524 let mut g = Guardian::default();
525 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
526 p.report(&mut g, BTreeMap::new(), None, None, 1).unwrap();
527 p.report(&mut g, BTreeMap::new(), None, None, 2).unwrap();
528 assert_eq!(g.heard[0].box_key, p.box_public().to_vec());
529 assert!(g.heard[1].box_key.is_empty());
530 }
531
532 #[test]
533 fn a_twins_own_review_rides_every_report_and_an_ordered_one_the_next_only() {
534 let mut g = Guardian::default();
535 let mut p = Puller::new(me("twin"), BoxKey::from_seed([4u8; 32]), "test");
536 g.queue.push(signed(1, "twin", "review_twin", "{}", MONETIZE_SEED));
537 p.pull(&mut g, &Fake::default(), Some(&mkey()), 0, 10).unwrap();
538 p.report(&mut g, BTreeMap::new(), None, None, 11).unwrap();
539 p.report(&mut g, BTreeMap::new(), None, None, 12).unwrap();
540 p.report(&mut g, BTreeMap::new(), None, Some(r#"{"own":1}"#.into()), 13).unwrap();
541 let said: Vec<Option<String>> = g.heard.iter().filter(|s| s.call.call == "report").map(|s| serde_json::from_str::<Report>(&s.call.body).unwrap().twin_review).collect();
542 assert!(said[0].as_deref().is_some_and(|r| r.contains("complete")), "{said:?}");
543 assert_eq!(said[1], None);
544 assert_eq!(said[2].as_deref(), Some(r#"{"own":1}"#));
545 }
546
547 #[test]
549 fn an_order_asks_monetize_on_its_own_link_signed_by_the_box() {
550 let mut g = Guardian::default();
551 g.queue.push(signed(2, "main", "grow", "{}", MONETIZE_SEED));
552 let mut p = Puller::new(me("main"), BoxKey::from_seed([3u8; 32]), "test");
553 let pulled = p.pull(&mut g, &Fake::default(), Some(&mkey()), 0, 10_000).unwrap();
554 assert_eq!(pulled.acks[0].detail, "resized");
555 let asked = g.heard.iter().find(|s| s.call.call == "ask").expect("asked");
556 let a: Ask = serde_json::from_str(&asked.call.body).unwrap();
557 assert_eq!((a.kind.as_str(), a.body["gib"].as_u64()), ("grow_set", Some(64)));
558 let vk = VerifyingKey::from_bytes(&p.box_public()).unwrap();
559 monetize_embed::fleet::verify_fleet_call(&asked.call, &asked.signature, &vk, 10_000).expect("the ask is the box's");
560 }
561
562 #[test]
563 fn the_puller_remembers_what_it_took_across_a_restart() {
564 let dir = tempfile::tempdir().unwrap();
565 let mut g = Guardian::default();
566 g.queue.push(signed(6, "main", "note", "{}", MONETIZE_SEED));
567 let mut p = Puller::at(dir.path(), me("main"), "test").unwrap();
568 let key = p.box_public();
569 p.pull(&mut g, &Fake::default(), Some(&mkey()), 0, 10_000).unwrap();
570 drop(p);
571 let p = Puller::at(dir.path(), me("main"), "test").unwrap();
572 assert_eq!((p.box_public(), p.pulled.last_seq, p.pulled.pending.len()), (key, 6, 1));
573 }
574
575 #[test]
578 fn a_product_orders_file_is_pulled_by_a_fetch_ask_and_checked() {
579 struct Shop(Vec<u8>, Vec<Ask>);
580 impl Asker for Shop {
581 fn ask(&mut self, _: &Ask) -> Result<Asked, Fault> {
582 unreachable!("a fetch asks for its bytes")
583 }
584 fn ask_bytes(&mut self, a: &Ask) -> Result<(Asked, Vec<u8>), Fault> {
585 self.1.push(a.clone());
586 Ok((Asked::default(), self.0.clone()))
587 }
588 }
589 let f = monetize_embed::fleet::Fetch { id: "ab".repeat(32), bytes: 3, param: "bundle".into() };
590 let mut shop = Shop(b"abc".to_vec(), Vec::new());
591 assert_eq!(fetch(&mut shop, &f).unwrap(), b"abc");
592 assert_eq!((shop.1[0].kind.as_str(), shop.1[0].body["id"].as_str()), (ask::FETCH, Some(f.id.as_str())));
593 let mut short = Shop(b"ab".to_vec(), Vec::new());
594 assert!(fetch(&mut short, &f).unwrap_err().contains("names 3"));
595 }
596}