1use std::collections::BTreeMap;
10
11use monetize_embed::fleet::{GrowGoAhead, OrderAck};
12use monetize_product::pull::{AddPrincipal, GrowRoom, PurgeAnswer, PurgeTenant};
13use monetize_product::EntitlementFact;
14
15use crate::order::Order;
16
17pub use crate::order::Recharge as RechargeAsk;
18
19pub const SECTORS_PER_GIB: u64 = 2_097_152;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Applied {
25 New(String),
27 Held(String),
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum GrowState {
34 Running(String),
35 WaitingMembers(String),
37 Done(String),
38 Failed(String),
39}
40
41pub trait GrowSet {
43 fn grow_set(&mut self, gib: u64) -> Result<String, String>;
44}
45
46pub struct NoAsk;
48impl GrowSet for NoAsk {
49 fn grow_set(&mut self, _: u64) -> Result<String, String> {
50 Err("this box has no pull channel to ask monetize for its disks".into())
51 }
52}
53
54pub trait HolgerBox {
57 fn serving(&self) -> bool;
59 fn health(&self) -> BTreeMap<String, String> {
61 BTreeMap::new()
62 }
63 fn product_report(&self) -> Option<String> {
65 None
66 }
67 fn own_review(&self) -> Option<String> {
69 None
70 }
71 fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
73 Err(format!(
74 "this box holds no entitlements, so the fact for {} is not applied",
75 fact.tenant.0
76 ))
77 }
78 fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
80 Err(format!(
81 "this box purges no repositories, so {} is not purged",
82 p.tenant
83 ))
84 }
85 fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
88 let _ = (&s.to, &s.host, s.ttl_secs);
89 Err(format!(
90 "this box mints no sign-in links, so it cannot run sign_in_again for {}",
91 s.account
92 ))
93 }
94 fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
97 Err(format!(
98 "this box keeps no estate keys fed again, so {} is not kept",
99 r.keys.keys().cloned().collect::<Vec<_>>().join(", ")
100 ))
101 }
102 fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
105 Err(format!(
106 "this box makes no repositories for a signup, so {} is not made",
107 a.username
108 ))
109 }
110 fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
112 Err(format!(
113 "this box grows no data set, so {} bytes wanted cannot be kept",
114 g.need_bytes
115 ))
116 }
117 fn grow_carried(&self, data_gib: u64) -> Result<bool, String> {
119 Err(format!(
120 "this box grows no data set, so it cannot grow to {data_gib} GiB"
121 ))
122 }
123 fn twin_holds(&self, data_gib: u64) -> Result<String, String> {
125 Err(format!(
126 "this box cannot see whether its twin holds {data_gib} GiB a disk"
127 ))
128 }
129 fn grow_start(&self, data_gib: u64, nonce: &str) -> Result<GrowState, String> {
131 let _ = (data_gib, nonce);
132 Err("this box grows no data set".into())
133 }
134 fn grow_settle(&self, past_waiting: bool) -> GrowState {
136 let _ = past_waiting;
137 GrowState::Failed("this box grows no data set".into())
138 }
139 fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
141 let _ = give_up;
142 Err("this box grows no data set".into())
143 }
144 fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
146 Err(format!(
147 "this box runs no recharge lane, so {} is not filled from {}",
148 r.main, r.twin
149 ))
150 }
151 fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
154 let _ = epoch;
155 Err(format!("this box cannot fence the primary at {primary}: the guardian fences it at the cloud (the recharge's `fenced` proof)"))
156 }
157 fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
159 let _ = principal;
160 Err(format!(
161 "this box cannot certify the twin at {twin}: the front's certification does"
162 ))
163 }
164 fn become_twin(&self, primary: &str) -> Result<String, String> {
166 Err(format!("this box cannot become the twin of {primary}"))
167 }
168 fn review(&self) -> Result<serde_json::Value, String> {
170 Err("this box cannot review a twin".into())
171 }
172}
173
174pub use monetize_estate_side::pump::Ctx;
176
177fn said(seq: u64, r: Result<String, String>) -> OrderAck {
178 match r {
179 Ok(detail) => OrderAck {
180 seq,
181 ok: true,
182 detail,
183 receipt_json: String::new(),
184 },
185 Err(detail) => OrderAck {
186 seq,
187 ok: false,
188 detail,
189 receipt_json: String::new(),
190 },
191 }
192}
193
194pub fn execute<B: HolgerBox + ?Sized>(
197 b: &B,
198 ctx: &Ctx,
199 seq: u64,
200 order: &Order,
201 ask: &mut dyn GrowSet,
202) -> OrderAck {
203 let not_serving = || {
204 Err::<String, String>("this box does not serve as the primary (a twin, or awaiting its recharge): nothing applied".into())
205 };
206 match order {
207 Order::Entitlement(fact) => {
208 if !b.serving() {
209 return said(seq, not_serving());
210 }
211 if let Err(e) = monetize_embed::signing::verify_fact(fact, ctx.key) {
212 return said(seq, Err(format!("the fact for {} does not verify under this box's monetize key ({e}): nothing moved", fact.tenant.0)));
213 }
214 said(
215 seq,
216 b.entitle(fact).map(|a| match a {
217 Applied::New(w) | Applied::Held(w) => w,
218 }),
219 )
220 }
221 Order::RestateEntitlements(snap) => {
222 if !b.serving() {
223 return said(seq, not_serving());
224 }
225 if let Err(e) = monetize_embed::signing::verify_snapshot(snap, ctx.key) {
226 return said(seq, Err(format!("the snapshot does not verify under this box's monetize key ({e}): nothing restated")));
227 }
228 let (mut new, mut held) = (0usize, 0usize);
229 for fact in &snap.facts {
230 match b.entitle(fact) {
231 Ok(Applied::New(_)) => new += 1,
232 Ok(Applied::Held(_)) => held += 1,
233 Err(e) => {
234 return said(
235 seq,
236 Err(format!(
237 "restated {new} before {} was refused: {e}",
238 fact.tenant.0
239 )),
240 )
241 }
242 }
243 }
244 said(seq, Ok(format!("restated {new} entitlement fact(s) ({held} already held as new or newer); nothing deleted")))
245 }
246 Order::PurgeTenant(p) => {
247 if !b.serving() {
248 return said(seq, not_serving());
249 }
250 match b.purge(p) {
251 Ok(answer) => OrderAck {
252 seq,
253 ok: true,
254 detail: serde_json::to_string(&answer).unwrap_or_default(),
255 receipt_json: String::new(),
256 },
257 Err(e) => said(seq, Err(e)),
258 }
259 }
260 Order::GrowRoom(g) => said(seq, b.want_room(g)),
261 Order::AddPrincipal(a) => {
262 if !b.serving() {
263 return said(seq, not_serving());
264 }
265 said(seq, b.add_principal(a))
266 }
267 Order::SignInAgain(s) => {
268 if !b.serving() {
269 return said(seq, not_serving());
270 }
271 said(seq, b.sign_in_again(s))
272 }
273 Order::Refeed(r) => said(seq, b.keep_refeed(r)),
276 Order::GrowGoAhead(g) => grow(b, ctx, seq, g, ask),
277 Order::RechargeFromTwin(r) => {
278 let fenced = match r.fence.as_deref().map(str::trim) {
284 Some(old) if old == r.main.trim() => {
285 return said(seq, Err(format!("order {seq} names the main {} as the primary to fence: a recharge never fences the box it fills", r.main)));
286 }
287 Some(old) => match b.fence(old, r.epoch) {
288 Ok(_) => Some(old.to_string()),
289 Err(e) => return said(seq, Err(format!("order {seq} fences {old} first and that fence did not hold ({e}): nothing was recharged"))),
290 },
291 None => None,
292 };
293 let prefix = match (&fenced, r.fenced.as_deref()) {
294 (Some(f), _) => format!("fenced {f}; "),
295 (None, Some(p)) => format!("cloud-fenced ({p}); "),
296 (None, None) => String::new(),
297 };
298 said(
299 seq,
300 b.recharge(r)
301 .map(|w| format!("{prefix}{w}"))
302 .map_err(|e| format!("{prefix}{e}")),
303 )
304 }
305 Order::FencePrimary { primary, epoch } => said(seq, b.fence(primary, *epoch)),
306 Order::AdoptTwin { twin, principal } => said(seq, b.adopt(twin, principal)),
307 Order::BecomeTwin { primary } => said(seq, b.become_twin(primary)),
308 Order::ReviewTwin => match b.review() {
309 Ok(v) => OrderAck {
310 seq,
311 ok: true,
312 detail: v
313 .get("verdict")
314 .and_then(|w| w.as_str())
315 .unwrap_or("reviewed")
316 .to_string(),
317 receipt_json: v.to_string(),
318 },
319 Err(e) => said(seq, Err(e)),
320 },
321 }
322}
323
324fn grow<B: HolgerBox + ?Sized>(
328 b: &B,
329 ctx: &Ctx,
330 seq: u64,
331 g: &GrowGoAhead,
332 ask: &mut dyn GrowSet,
333) -> OrderAck {
334 let fail = |w: String| said(seq, Err(w));
335 let step = match monetize_embed::signing::verify_estate_growth(g.go_ahead.as_bytes(), ctx.key) {
336 Ok(s) => s,
337 Err(e) => {
338 return fail(format!(
339 "the paid step does not verify under this box's monetize key ({e}): nothing moved"
340 ))
341 }
342 };
343 if step.estate != ctx.me.instance {
344 return fail(format!(
345 "the paid step is estate {}'s; this box pulls as {}: nothing moved",
346 step.estate, ctx.me.instance
347 ));
348 }
349 if step.until_unix_ms <= ctx.now_unix_ms as i64 {
350 return fail(format!(
351 "the paid step was good until {} (unix ms): its period is over, nothing moved",
352 step.until_unix_ms
353 ));
354 }
355 let twin = ctx.me.role == "twin";
356 let main_gib = step.main_data_gib_per_member;
357 if !twin && g.data_gib != main_gib {
358 return fail(format!("the order sizes main's disks to {} GiB, the paid step to {main_gib} GiB: nothing moved", g.data_gib));
359 }
360 if twin && g.data_gib < main_gib {
361 return fail(format!("the order sizes the twin's disk to {} GiB, under main's {main_gib} GiB a disk: nothing moved", g.data_gib));
362 }
363 match b.grow_carried(g.data_gib) {
364 Ok(true) => {
365 return said(
366 seq,
367 Ok(format!(
368 "already {} GiB a data disk: nothing moved",
369 g.data_gib
370 )),
371 )
372 }
373 Ok(false) => {}
374 Err(e) => return fail(e),
375 }
376 if !twin {
377 if let Err(e) = b.twin_holds(g.data_gib) {
378 return fail(e);
379 }
380 }
381 match b.grow_start(g.data_gib, &format!("pull-{seq}-{}", step.reference)) {
382 Ok(GrowState::Done(d)) => return said(seq, Ok(d)),
383 Ok(GrowState::Failed(f)) => return fail(f),
384 Ok(_) => {}
385 Err(e) => return fail(e),
386 }
387 match b.grow_settle(false) {
388 GrowState::WaitingMembers(_) => {}
389 GrowState::Done(d) => return said(seq, Ok(d)),
390 GrowState::Failed(f) | GrowState::Running(f) => return fail(f),
391 }
392 let asked = ask.grow_set(g.data_gib);
393 if let Err(e) = b.grow_look_again(asked.is_err()) {
394 return fail(format!("the grow machine would not look again: {e}"));
395 }
396 let end = b.grow_settle(true);
397 match (asked, end) {
398 (Ok(words), GrowState::Done(d)) => said(seq, Ok(format!("{d} (monetize: {words})"))),
399 (
400 Ok(words),
401 GrowState::Failed(f) | GrowState::Running(f) | GrowState::WaitingMembers(f),
402 ) => fail(format!("{f} (monetize: {words})")),
403 (
404 Err(e),
405 GrowState::Done(s)
406 | GrowState::Failed(s)
407 | GrowState::Running(s)
408 | GrowState::WaitingMembers(s),
409 ) => fail(format!(
410 "monetize did not size this box's disks ({e}); /data: {s}"
411 )),
412 }
413}
414
415#[cfg(test)]
416pub(crate) mod tests {
417 use super::*;
418 use crate::order::tests::{me, monetize_key, MONETIZE_SEED};
419 use ed25519_dalek::Signer as _;
420 use monetize_product::{State, TenantId};
421 use std::cell::RefCell;
422
423 #[derive(Default)]
425 pub struct Fake {
426 pub twin: bool,
427 pub facts: RefCell<BTreeMap<String, EntitlementFact>>,
428 pub did: RefCell<Vec<String>>,
429 pub carried: bool,
430 pub settle: RefCell<Vec<GrowState>>,
431 }
432
433 impl HolgerBox for Fake {
434 fn serving(&self) -> bool {
435 !self.twin
436 }
437 fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
438 let mut f = self.facts.borrow_mut();
439 if f.get(&fact.tenant.0)
440 .is_some_and(|h| h.issued_unix_ms >= fact.issued_unix_ms)
441 {
442 return Ok(Applied::Held(format!("{}: held", fact.tenant.0)));
443 }
444 f.insert(fact.tenant.0.clone(), fact.clone());
445 Ok(Applied::New(format!(
446 "{} is {:?}",
447 fact.tenant.0, fact.state
448 )))
449 }
450 fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
451 self.did.borrow_mut().push(format!("purge {}", p.tenant));
452 Ok(PurgeAnswer {
453 job: format!("purge:{}", p.tenant),
454 detail: "3 artifacts".into(),
455 ..Default::default()
456 })
457 }
458 fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
459 self.did.borrow_mut().push(format!("room {}", g.need_bytes));
460 Ok("kept".into())
461 }
462 fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
463 self.did.borrow_mut().push(format!(
464 "sign-in {} {} {} {}",
465 s.account, s.to, s.host, s.ttl_secs
466 ));
467 Ok(format!(
468 "a new sign-in link for {} was mailed to {}",
469 s.account, s.to
470 ))
471 }
472 fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
473 self.did.borrow_mut().push(format!(
474 "refeed {}",
475 r.keys.keys().cloned().collect::<Vec<_>>().join(",")
476 ));
477 Ok(format!(
478 "kept {}",
479 r.keys.keys().cloned().collect::<Vec<_>>().join(", ")
480 ))
481 }
482 fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
483 self.did
484 .borrow_mut()
485 .push(format!("signup {} {}", a.username, a.signup));
486 if a.username == "theirs" {
487 Err("taken: theirs is another signup's".into())
488 } else {
489 Ok(a.username.clone())
490 }
491 }
492 fn grow_carried(&self, _: u64) -> Result<bool, String> {
493 Ok(self.carried)
494 }
495 fn twin_holds(&self, gib: u64) -> Result<String, String> {
496 self.did.borrow_mut().push(format!("twin_holds {gib}"));
497 Ok("the twin holds it".into())
498 }
499 fn grow_start(&self, gib: u64, nonce: &str) -> Result<GrowState, String> {
500 self.did.borrow_mut().push(format!("start {gib} {nonce}"));
501 Ok(GrowState::Running("draining".into()))
502 }
503 fn grow_settle(&self, past: bool) -> GrowState {
504 self.did.borrow_mut().push(format!("settle {past}"));
505 let mut s = self.settle.borrow_mut();
506 if s.is_empty() {
507 if past {
508 GrowState::Done("grown".into())
509 } else {
510 GrowState::WaitingMembers("waiting-members".into())
511 }
512 } else {
513 s.remove(0)
514 }
515 }
516 fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
517 self.did.borrow_mut().push(format!("look_again {give_up}"));
518 Ok(())
519 }
520 fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
521 self.did.borrow_mut().push(format!(
522 "recharge {} from {} lost={}",
523 r.main, r.twin, r.accept_lost
524 ));
525 Ok("recharged".into())
526 }
527 fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
528 self.did
529 .borrow_mut()
530 .push(format!("fence {primary} {epoch}"));
531 if primary.starts_with("stuck") {
532 Err(format!("{primary} answers nothing"))
533 } else {
534 Ok(format!("{primary} is fenced at epoch {epoch}"))
535 }
536 }
537 fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
538 self.did
539 .borrow_mut()
540 .push(format!("adopt {twin} {principal}"));
541 Ok("follows".into())
542 }
543 fn review(&self) -> Result<serde_json::Value, String> {
544 Ok(serde_json::json!({"verdict": "the twin is whole", "complete": true}))
545 }
546 fn become_twin(&self, primary: &str) -> Result<String, String> {
547 self.did.borrow_mut().push(format!("become_twin {primary}"));
548 Ok("elected".into())
549 }
550 }
551
552 pub fn fact(repo: &str, issued: u64, signer: [u8; 32]) -> EntitlementFact {
553 let mut f = EntitlementFact {
554 tenant: TenantId(repo.into()),
555 plan: "o-1".into(),
556 state: State::Paid,
557 paid_until_unix_ms: Some(99_000_000),
558 caps: Default::default(),
559 source: "payment:stripe:o-1".into(),
560 signature: Vec::new(),
561 issued_unix_ms: (issued != 0).then_some(issued),
562 issued_signature: Vec::new(),
563 };
564 let key = ed25519_dalek::SigningKey::from_bytes(&signer);
565 f.signature = key
566 .sign(&monetize_embed::signing::fact_message(&f))
567 .to_bytes()
568 .to_vec();
569 if issued != 0 {
570 f.issued_signature = key
571 .sign(&monetize_embed::signing::fact_message_issued(&f))
572 .to_bytes()
573 .to_vec();
574 }
575 f
576 }
577
578 fn run(b: &dyn HolgerBox, role: &str, o: Order) -> OrderAck {
579 let (m, k) = (me(role), monetize_key());
580 execute(
581 b,
582 &Ctx {
583 me: &m,
584 key: &k,
585 now_unix_ms: 10_000,
586 },
587 5,
588 &o,
589 &mut NoAsk,
590 )
591 }
592
593 struct Asked(Vec<u64>, bool);
594 impl GrowSet for Asked {
595 fn grow_set(&mut self, gib: u64) -> Result<String, String> {
596 self.0.push(gib);
597 if self.1 {
598 Ok("resized".into())
599 } else {
600 Err("no answer".into())
601 }
602 }
603 }
604
605 fn growth(estate: &str, gib: u64, until: i64) -> String {
606 let mut g = monetize_embed::signing::EstateGrowth {
607 v: 1,
608 signer: "shop".into(),
609 estate: estate.into(),
610 main_data_gib_per_member: gib,
611 until_unix_ms: until,
612 reference: "o-7".into(),
613 issued_unix_ms: 1,
614 signature: String::new(),
615 };
616 let sig = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED)
617 .sign(&monetize_embed::signing::estate_growth_message(&g));
618 g.signature = monetize_embed::signing::base64_encode(&sig.to_bytes());
619 serde_json::to_string(&g).unwrap()
620 }
621
622 #[test]
623 fn entitlement_is_applied_newer_wins_and_a_forgery_moves_nothing() {
624 let b = Fake::default();
625 let a = run(
626 &b,
627 "main",
628 Order::Entitlement(fact("libs", 5, MONETIZE_SEED)),
629 );
630 assert!(a.ok, "{a:?}");
631 assert!(b.facts.borrow().contains_key("libs"));
632 let again = run(
633 &b,
634 "main",
635 Order::Entitlement(fact("libs", 4, MONETIZE_SEED)),
636 );
637 assert!(
638 again.ok && again.detail.contains("held"),
639 "an older fact is held, not refused: {again:?}"
640 );
641 let forged = run(&b, "main", Order::Entitlement(fact("other", 6, [9u8; 32])));
642 assert!(
643 !forged.ok && forged.detail.contains("does not verify"),
644 "{forged:?}"
645 );
646 assert!(!b.facts.borrow().contains_key("other"));
647 let twin = Fake {
648 twin: true,
649 ..Default::default()
650 };
651 let refused = run(
652 &twin,
653 "twin",
654 Order::Entitlement(fact("libs", 5, MONETIZE_SEED)),
655 );
656 assert!(
657 !refused.ok && refused.detail.contains("does not serve"),
658 "{refused:?}"
659 );
660 }
661
662 #[test]
665 fn a_sign_in_again_reaches_the_serving_box_and_a_twin_mints_nothing() {
666 let s = crate::order::SignInAgain {
667 account: "acme".into(),
668 to: "buyer@mock.invalid".into(),
669 host: "acme.holger.mock.invalid".into(),
670 ttl_secs: 86_400,
671 };
672 let b = Fake::default();
673 let a = run(&b, "main", Order::SignInAgain(s.clone()));
674 assert!(
675 a.ok && a.detail.contains("mailed to buyer@mock.invalid"),
676 "{a:?}"
677 );
678 assert_eq!(
679 b.did.borrow().as_slice(),
680 ["sign-in acme buyer@mock.invalid acme.holger.mock.invalid 86400"]
681 );
682 let twin = Fake {
683 twin: true,
684 ..Default::default()
685 };
686 let refused = run(&twin, "twin", Order::SignInAgain(s.clone()));
687 assert!(
688 !refused.ok
689 && refused.detail.contains("does not serve")
690 && twin.did.borrow().is_empty(),
691 "{refused:?}"
692 );
693 struct Bare;
694 impl HolgerBox for Bare {
695 fn serving(&self) -> bool {
696 true
697 }
698 }
699 let none = run(&Bare, "main", Order::SignInAgain(s));
700 assert!(
701 !none.ok && none.detail.contains("mints no sign-in link"),
702 "{none:?}"
703 );
704 }
705
706 #[test]
708 fn a_refeed_reaches_the_boxes_keep_and_a_box_that_keeps_none_refuses() {
709 let r = crate::order::Refeed {
710 keys: [
711 ("secret.smtp_url", "smtps://u:tok@relay.mock.invalid:465"),
712 ("secret.smtp_from", "holger@mock.invalid"),
713 ("public_repositories", "crates"),
714 ]
715 .iter()
716 .map(|(k, v)| (k.to_string(), v.to_string()))
717 .collect(),
718 };
719 let b = Fake::default();
720 let a = run(&b, "main", Order::Refeed(r.clone()));
721 assert!(
722 a.ok && a.detail.contains("public_repositories") && !a.detail.contains("tok@"),
723 "{a:?}"
724 );
725 assert_eq!(
726 b.did.borrow().as_slice(),
727 ["refeed public_repositories,secret.smtp_from,secret.smtp_url"]
728 );
729 struct Bare;
730 impl HolgerBox for Bare {
731 fn serving(&self) -> bool {
732 true
733 }
734 }
735 let none = run(&Bare, "main", Order::Refeed(r));
736 assert!(
737 !none.ok && none.detail.contains("keeps no estate keys"),
738 "{none:?}"
739 );
740 }
741
742 #[test]
743 fn restate_entitlements_verifies_the_snapshot_and_counts_new_and_held() {
744 let b = Fake::default();
745 run(&b, "main", Order::Entitlement(fact("a", 5, MONETIZE_SEED)));
746 let facts = vec![fact("a", 5, MONETIZE_SEED), fact("b", 5, MONETIZE_SEED)];
747 let mut snap = monetize_embed::Snapshot {
748 issued_unix_ms: 7,
749 facts,
750 signature: Vec::new(),
751 };
752 snap.signature = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED)
753 .sign(&monetize_embed::signing::snapshot_message(
754 snap.issued_unix_ms,
755 &snap.facts,
756 ))
757 .to_bytes()
758 .to_vec();
759 let a = run(&b, "main", Order::RestateEntitlements(snap.clone()));
760 assert!(
761 a.ok && a.detail.contains("restated 1") && a.detail.contains("1 already held"),
762 "{a:?}"
763 );
764 snap.signature = vec![0; 64];
765 let bad = run(&b, "main", Order::RestateEntitlements(snap));
766 assert!(!bad.ok && bad.detail.contains("does not verify"), "{bad:?}");
767 }
768
769 #[test]
770 fn purge_tenant_answers_monetizes_purge_answer() {
771 let b = Fake::default();
772 let a = run(
773 &b,
774 "main",
775 Order::PurgeTenant(PurgeTenant {
776 tenant: "libs".into(),
777 reason: "nonpay".into(),
778 job: String::new(),
779 }),
780 );
781 assert!(a.ok, "{a:?}");
782 let answer: PurgeAnswer = serde_json::from_str(&a.detail).unwrap();
783 assert_eq!(answer.job, "purge:libs");
784 assert_eq!(b.did.borrow().as_slice(), ["purge libs"]);
785 }
786
787 #[test]
788 fn add_principal_makes_a_repository_on_a_plan_and_says_taken_by_name() {
789 let b = Fake::default();
790 let a = run(
791 &b,
792 "main",
793 Order::AddPrincipal(AddPrincipal {
794 username: "crates".into(),
795 kind: "org".into(),
796 signup: "s-1".into(),
797 }),
798 );
799 assert!(
800 a.ok && a.detail == "crates",
801 "the namespace made is the repository: {a:?}"
802 );
803 let t = run(
804 &b,
805 "main",
806 Order::AddPrincipal(AddPrincipal {
807 username: "theirs".into(),
808 kind: "org".into(),
809 signup: "s-2".into(),
810 }),
811 );
812 assert!(!t.ok && t.detail.starts_with("taken"), "{t:?}");
813 let twin = Fake {
814 twin: true,
815 ..Default::default()
816 };
817 assert!(
818 !run(
819 &twin,
820 "twin",
821 Order::AddPrincipal(AddPrincipal {
822 username: "crates".into(),
823 kind: "org".into(),
824 signup: "s-1".into()
825 })
826 )
827 .ok,
828 "only the main that serves"
829 );
830 }
831
832 #[test]
833 fn grow_room_is_kept_for_the_operator() {
834 let b = Fake::default();
835 let a = run(
836 &b,
837 "main",
838 Order::GrowRoom(GrowRoom {
839 need_bytes: 1 << 30,
840 reason: "libs".into(),
841 }),
842 );
843 assert!(a.ok, "{a:?}");
844 assert_eq!(b.did.borrow().as_slice(), ["room 1073741824"]);
845 }
846
847 #[test]
848 fn grow_go_ahead_runs_twin_first_asks_monetize_and_remounts() {
849 let b = Fake::default();
850 let (m, k) = (me("main"), monetize_key());
851 let mut ask = Asked(Vec::new(), true);
852 let o = Order::GrowGoAhead(GrowGoAhead {
853 go_ahead: growth("acme", 120, 99_000),
854 data_gib: 120,
855 });
856 let a = execute(
857 &b,
858 &Ctx {
859 me: &m,
860 key: &k,
861 now_unix_ms: 10_000,
862 },
863 6,
864 &o,
865 &mut ask,
866 );
867 assert!(a.ok, "{a:?}");
868 assert_eq!(
869 b.did.borrow().as_slice(),
870 [
871 "twin_holds 120",
872 "start 120 pull-6-o-7",
873 "settle false",
874 "look_again false",
875 "settle true"
876 ]
877 );
878 assert_eq!(ask.0, [120]);
879
880 let b = Fake::default();
882 let mut deaf = Asked(Vec::new(), false);
883 b.settle.borrow_mut().extend([
884 GrowState::WaitingMembers("w".into()),
885 GrowState::Failed("mounted back".into()),
886 ]);
887 let a = execute(
888 &b,
889 &Ctx {
890 me: &m,
891 key: &k,
892 now_unix_ms: 10_000,
893 },
894 7,
895 &o,
896 &mut deaf,
897 );
898 assert!(
899 !a.ok && a.detail.contains("no answer") && a.detail.contains("mounted back"),
900 "{a:?}"
901 );
902 assert!(b.did.borrow().contains(&"look_again true".to_string()));
903 }
904
905 #[test]
906 fn grow_go_ahead_refuses_another_estate_a_lapsed_step_and_a_forgery_and_skips_a_done_one() {
907 let b = Fake::default();
908 for (go, why) in [
909 (growth("other", 120, 99_000), "estate other"),
910 (growth("acme", 120, 5_000), "period is over"),
911 (
912 growth("acme", 120, 99_000).replace("\"o-7\"", "\"o-8\""),
913 "does not verify",
914 ),
915 ] {
916 let a = run(
917 &b,
918 "main",
919 Order::GrowGoAhead(GrowGoAhead {
920 go_ahead: go,
921 data_gib: 120,
922 }),
923 );
924 assert!(!a.ok && a.detail.contains(why), "{why}: {a:?}");
925 }
926 assert!(b.did.borrow().is_empty(), "nothing moved");
927 let done = Fake {
928 carried: true,
929 ..Default::default()
930 };
931 let a = run(
932 &done,
933 "main",
934 Order::GrowGoAhead(GrowGoAhead {
935 go_ahead: growth("acme", 120, 99_000),
936 data_gib: 120,
937 }),
938 );
939 assert!(a.ok && a.detail.contains("nothing moved"), "{a:?}");
940 let twin = Fake {
941 twin: true,
942 ..Default::default()
943 };
944 let a = run(
945 &twin,
946 "twin",
947 Order::GrowGoAhead(GrowGoAhead {
948 go_ahead: growth("acme", 120, 99_000),
949 data_gib: 240,
950 }),
951 );
952 assert!(a.detail.contains("no pull channel"), "{a:?}");
953 assert!(
954 !twin
955 .did
956 .borrow()
957 .iter()
958 .any(|d| d.starts_with("twin_holds")),
959 "the twin grows first, asking no twin: {a:?}"
960 );
961 }
962
963 #[test]
964 fn recharge_from_twin_runs_the_lane_on_a_cloud_fence_proof() {
965 let b = Fake::default();
966 let r = RechargeAsk {
967 main: "main:1".into(),
968 twin: "twin:1".into(),
969 accept_lost: true,
970 fenced: Some("upcloud: gone".into()),
971 ..Default::default()
972 };
973 let a = run(&b, "twin", Order::RechargeFromTwin(r));
974 assert!(a.ok && a.detail.starts_with("cloud-fenced"), "{a:?}");
975 assert_eq!(
976 b.did.borrow().as_slice(),
977 ["recharge main:1 from twin:1 lost=true"]
978 );
979 }
980
981 #[test]
986 fn recharge_from_twin_fences_the_old_primary_first() {
987 let b = Fake::default();
988 let r = RechargeAsk {
989 main: "main:2".into(),
990 twin: "twin:1".into(),
991 accept_lost: false,
992 epoch: 4,
993 fence: Some("old:1".into()),
994 ..Default::default()
995 };
996 let a = run(&b, "twin", Order::RechargeFromTwin(r.clone()));
997 assert!(a.ok && a.detail.starts_with("fenced old:1; "), "{a:?}");
998 assert_eq!(
999 b.did.borrow().as_slice(),
1000 ["fence old:1 4", "recharge main:2 from twin:1 lost=false"]
1001 );
1002
1003 let stuck = Fake::default();
1004 let a = run(
1005 &stuck,
1006 "twin",
1007 Order::RechargeFromTwin(RechargeAsk {
1008 fence: Some("stuck:1".into()),
1009 ..r.clone()
1010 }),
1011 );
1012 assert!(
1013 !a.ok
1014 && a.detail.contains("stuck:1 answers nothing")
1015 && a.detail.contains("nothing was recharged"),
1016 "{a:?}"
1017 );
1018 assert_eq!(
1019 stuck.did.borrow().as_slice(),
1020 ["fence stuck:1 4"],
1021 "no recharge beside an unfenced primary"
1022 );
1023
1024 let same = Fake::default();
1025 let a = run(
1026 &same,
1027 "twin",
1028 Order::RechargeFromTwin(RechargeAsk {
1029 fence: Some("main:2".into()),
1030 ..r
1031 }),
1032 );
1033 assert!(
1034 !a.ok && a.detail.contains("never fences the box it fills"),
1035 "{a:?}"
1036 );
1037 assert!(same.did.borrow().is_empty(), "nothing ran");
1038 }
1039
1040 #[test]
1041 fn become_twin_runs_holgers_election() {
1042 let b = Fake {
1043 twin: true,
1044 ..Default::default()
1045 };
1046 assert!(
1047 run(
1048 &b,
1049 "twin",
1050 Order::BecomeTwin {
1051 primary: "main:1".into()
1052 }
1053 )
1054 .ok
1055 );
1056 assert_eq!(b.did.borrow().as_slice(), ["become_twin main:1"]);
1057 struct Bare;
1058 impl HolgerBox for Bare {
1059 fn serving(&self) -> bool {
1060 false
1061 }
1062 }
1063 let none = run(
1064 &Bare,
1065 "twin",
1066 Order::BecomeTwin {
1067 primary: "main:1".into(),
1068 },
1069 );
1070 assert!(
1071 !none.ok && none.detail.contains("cannot become"),
1072 "the default refuses by name: {none:?}"
1073 );
1074 }
1075
1076 #[test]
1077 fn fence_primary_runs_the_boxes_fence_adopt_and_review_run() {
1078 let b = Fake::default();
1079 let a = run(
1080 &b,
1081 "twin",
1082 Order::FencePrimary {
1083 primary: "old:1".into(),
1084 epoch: 3,
1085 },
1086 );
1087 assert!(
1088 a.ok && b.did.borrow().contains(&"fence old:1 3".to_string()),
1089 "{a:?}"
1090 );
1091 struct Bare;
1092 impl HolgerBox for Bare {
1093 fn serving(&self) -> bool {
1094 false
1095 }
1096 }
1097 let none = run(
1098 &Bare,
1099 "twin",
1100 Order::FencePrimary {
1101 primary: "old:1".into(),
1102 epoch: 3,
1103 },
1104 );
1105 assert!(
1106 !none.ok && none.detail.contains("cannot fence"),
1107 "the default refuses by name: {none:?}"
1108 );
1109 let a = run(
1110 &b,
1111 "main",
1112 Order::AdoptTwin {
1113 twin: "twin:1".into(),
1114 principal: "twin".into(),
1115 },
1116 );
1117 assert!(
1118 a.ok && b.did.borrow().contains(&"adopt twin:1 twin".to_string()),
1119 "{a:?}"
1120 );
1121 let a = run(&b, "twin", Order::ReviewTwin);
1122 assert!(
1123 a.ok && a.detail == "the twin is whole" && a.receipt_json.contains("complete"),
1124 "{a:?}"
1125 );
1126 }
1127}