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!("this box holds no entitlements, so the fact for {} is not applied", fact.tenant.0))
74 }
75 fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
77 Err(format!("this box purges no repositories, so {} is not purged", p.tenant))
78 }
79 fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
82 let _ = (&s.to, &s.host, s.ttl_secs);
83 Err(format!("this box mints no sign-in links, so it cannot run sign_in_again for {}", s.account))
84 }
85 fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
88 Err(format!("this box keeps no estate keys fed again, so {} is not kept", r.keys.keys().cloned().collect::<Vec<_>>().join(", ")))
89 }
90 fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
93 Err(format!("this box makes no repositories for a signup, so {} is not made", a.username))
94 }
95 fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
97 Err(format!("this box grows no data set, so {} bytes wanted cannot be kept", g.need_bytes))
98 }
99 fn grow_carried(&self, data_gib: u64) -> Result<bool, String> {
101 Err(format!("this box grows no data set, so it cannot grow to {data_gib} GiB"))
102 }
103 fn twin_holds(&self, data_gib: u64) -> Result<String, String> {
105 Err(format!("this box cannot see whether its twin holds {data_gib} GiB a disk"))
106 }
107 fn grow_start(&self, data_gib: u64, nonce: &str) -> Result<GrowState, String> {
109 let _ = (data_gib, nonce);
110 Err("this box grows no data set".into())
111 }
112 fn grow_settle(&self, past_waiting: bool) -> GrowState {
114 let _ = past_waiting;
115 GrowState::Failed("this box grows no data set".into())
116 }
117 fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
119 let _ = give_up;
120 Err("this box grows no data set".into())
121 }
122 fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
124 Err(format!("this box runs no recharge lane, so {} is not filled from {}", r.main, r.twin))
125 }
126 fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
129 let _ = epoch;
130 Err(format!("this box cannot fence the primary at {primary}: the guardian fences it at the cloud (the recharge's `fenced` proof)"))
131 }
132 fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
134 let _ = principal;
135 Err(format!("this box cannot certify the twin at {twin}: the front's certification does"))
136 }
137 fn become_twin(&self, primary: &str) -> Result<String, String> {
139 Err(format!("this box cannot become the twin of {primary}"))
140 }
141 fn review(&self) -> Result<serde_json::Value, String> {
143 Err("this box cannot review a twin".into())
144 }
145}
146
147pub use monetize_estate_side::pump::Ctx;
149
150fn said(seq: u64, r: Result<String, String>) -> OrderAck {
151 match r {
152 Ok(detail) => OrderAck { seq, ok: true, detail, receipt_json: String::new() },
153 Err(detail) => OrderAck { seq, ok: false, detail, receipt_json: String::new() },
154 }
155}
156
157pub fn execute<B: HolgerBox + ?Sized>(b: &B, ctx: &Ctx, seq: u64, order: &Order, ask: &mut dyn GrowSet) -> OrderAck {
160 let not_serving = || Err::<String, String>("this box does not serve as the primary (a twin, or awaiting its recharge): nothing applied".into());
161 match order {
162 Order::Entitlement(fact) => {
163 if !b.serving() {
164 return said(seq, not_serving());
165 }
166 if let Err(e) = monetize_embed::signing::verify_fact(fact, ctx.key) {
167 return said(seq, Err(format!("the fact for {} does not verify under this box's monetize key ({e}): nothing moved", fact.tenant.0)));
168 }
169 said(seq, b.entitle(fact).map(|a| match a {
170 Applied::New(w) | Applied::Held(w) => w,
171 }))
172 }
173 Order::RestateEntitlements(snap) => {
174 if !b.serving() {
175 return said(seq, not_serving());
176 }
177 if let Err(e) = monetize_embed::signing::verify_snapshot(snap, ctx.key) {
178 return said(seq, Err(format!("the snapshot does not verify under this box's monetize key ({e}): nothing restated")));
179 }
180 let (mut new, mut held) = (0usize, 0usize);
181 for fact in &snap.facts {
182 match b.entitle(fact) {
183 Ok(Applied::New(_)) => new += 1,
184 Ok(Applied::Held(_)) => held += 1,
185 Err(e) => return said(seq, Err(format!("restated {new} before {} was refused: {e}", fact.tenant.0))),
186 }
187 }
188 said(seq, Ok(format!("restated {new} entitlement fact(s) ({held} already held as new or newer); nothing deleted")))
189 }
190 Order::PurgeTenant(p) => {
191 if !b.serving() {
192 return said(seq, not_serving());
193 }
194 match b.purge(p) {
195 Ok(answer) => OrderAck { seq, ok: true, detail: serde_json::to_string(&answer).unwrap_or_default(), receipt_json: String::new() },
196 Err(e) => said(seq, Err(e)),
197 }
198 }
199 Order::GrowRoom(g) => said(seq, b.want_room(g)),
200 Order::AddPrincipal(a) => {
201 if !b.serving() {
202 return said(seq, not_serving());
203 }
204 said(seq, b.add_principal(a))
205 }
206 Order::SignInAgain(s) => {
207 if !b.serving() {
208 return said(seq, not_serving());
209 }
210 said(seq, b.sign_in_again(s))
211 }
212 Order::Refeed(r) => said(seq, b.keep_refeed(r)),
215 Order::GrowGoAhead(g) => grow(b, ctx, seq, g, ask),
216 Order::RechargeFromTwin(r) => {
217 let fenced = match r.fence.as_deref().map(str::trim) {
223 Some(old) if old == r.main.trim() => {
224 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)));
225 }
226 Some(old) => match b.fence(old, r.epoch) {
227 Ok(_) => Some(old.to_string()),
228 Err(e) => return said(seq, Err(format!("order {seq} fences {old} first and that fence did not hold ({e}): nothing was recharged"))),
229 },
230 None => None,
231 };
232 let prefix = match (&fenced, r.fenced.as_deref()) {
233 (Some(f), _) => format!("fenced {f}; "),
234 (None, Some(p)) => format!("cloud-fenced ({p}); "),
235 (None, None) => String::new(),
236 };
237 said(seq, b.recharge(r).map(|w| format!("{prefix}{w}")).map_err(|e| format!("{prefix}{e}")))
238 }
239 Order::FencePrimary { primary, epoch } => said(seq, b.fence(primary, *epoch)),
240 Order::AdoptTwin { twin, principal } => said(seq, b.adopt(twin, principal)),
241 Order::BecomeTwin { primary } => said(seq, b.become_twin(primary)),
242 Order::ReviewTwin => match b.review() {
243 Ok(v) => OrderAck { seq, ok: true, detail: v.get("verdict").and_then(|w| w.as_str()).unwrap_or("reviewed").to_string(), receipt_json: v.to_string() },
244 Err(e) => said(seq, Err(e)),
245 },
246 }
247}
248
249fn grow<B: HolgerBox + ?Sized>(b: &B, ctx: &Ctx, seq: u64, g: &GrowGoAhead, ask: &mut dyn GrowSet) -> OrderAck {
253 let fail = |w: String| said(seq, Err(w));
254 let step = match monetize_embed::signing::verify_estate_growth(g.go_ahead.as_bytes(), ctx.key) {
255 Ok(s) => s,
256 Err(e) => return fail(format!("the paid step does not verify under this box's monetize key ({e}): nothing moved")),
257 };
258 if step.estate != ctx.me.instance {
259 return fail(format!("the paid step is estate {}'s; this box pulls as {}: nothing moved", step.estate, ctx.me.instance));
260 }
261 if step.until_unix_ms <= ctx.now_unix_ms as i64 {
262 return fail(format!("the paid step was good until {} (unix ms): its period is over, nothing moved", step.until_unix_ms));
263 }
264 let twin = ctx.me.role == "twin";
265 let main_gib = step.main_data_gib_per_member;
266 if !twin && g.data_gib != main_gib {
267 return fail(format!("the order sizes main's disks to {} GiB, the paid step to {main_gib} GiB: nothing moved", g.data_gib));
268 }
269 if twin && g.data_gib < main_gib {
270 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));
271 }
272 match b.grow_carried(g.data_gib) {
273 Ok(true) => return said(seq, Ok(format!("already {} GiB a data disk: nothing moved", g.data_gib))),
274 Ok(false) => {}
275 Err(e) => return fail(e),
276 }
277 if !twin {
278 if let Err(e) = b.twin_holds(g.data_gib) {
279 return fail(e);
280 }
281 }
282 match b.grow_start(g.data_gib, &format!("pull-{seq}-{}", step.reference)) {
283 Ok(GrowState::Done(d)) => return said(seq, Ok(d)),
284 Ok(GrowState::Failed(f)) => return fail(f),
285 Ok(_) => {}
286 Err(e) => return fail(e),
287 }
288 match b.grow_settle(false) {
289 GrowState::WaitingMembers(_) => {}
290 GrowState::Done(d) => return said(seq, Ok(d)),
291 GrowState::Failed(f) | GrowState::Running(f) => return fail(f),
292 }
293 let asked = ask.grow_set(g.data_gib);
294 if let Err(e) = b.grow_look_again(asked.is_err()) {
295 return fail(format!("the grow machine would not look again: {e}"));
296 }
297 let end = b.grow_settle(true);
298 match (asked, end) {
299 (Ok(words), GrowState::Done(d)) => said(seq, Ok(format!("{d} (monetize: {words})"))),
300 (Ok(words), GrowState::Failed(f) | GrowState::Running(f) | GrowState::WaitingMembers(f)) => fail(format!("{f} (monetize: {words})")),
301 (Err(e), GrowState::Done(s) | GrowState::Failed(s) | GrowState::Running(s) | GrowState::WaitingMembers(s)) => {
302 fail(format!("monetize did not size this box's disks ({e}); /data: {s}"))
303 }
304 }
305}
306
307#[cfg(test)]
308pub(crate) mod tests {
309 use super::*;
310 use crate::order::tests::{me, monetize_key, MONETIZE_SEED};
311 use ed25519_dalek::Signer as _;
312 use monetize_product::{State, TenantId};
313 use std::cell::RefCell;
314
315 #[derive(Default)]
317 pub struct Fake {
318 pub twin: bool,
319 pub facts: RefCell<BTreeMap<String, EntitlementFact>>,
320 pub did: RefCell<Vec<String>>,
321 pub carried: bool,
322 pub settle: RefCell<Vec<GrowState>>,
323 }
324
325 impl HolgerBox for Fake {
326 fn serving(&self) -> bool {
327 !self.twin
328 }
329 fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
330 let mut f = self.facts.borrow_mut();
331 if f.get(&fact.tenant.0).is_some_and(|h| h.issued_unix_ms >= fact.issued_unix_ms) {
332 return Ok(Applied::Held(format!("{}: held", fact.tenant.0)));
333 }
334 f.insert(fact.tenant.0.clone(), fact.clone());
335 Ok(Applied::New(format!("{} is {:?}", fact.tenant.0, fact.state)))
336 }
337 fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
338 self.did.borrow_mut().push(format!("purge {}", p.tenant));
339 Ok(PurgeAnswer { job: format!("purge:{}", p.tenant), detail: "3 artifacts".into(), ..Default::default() })
340 }
341 fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
342 self.did.borrow_mut().push(format!("room {}", g.need_bytes));
343 Ok("kept".into())
344 }
345 fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
346 self.did.borrow_mut().push(format!("sign-in {} {} {} {}", s.account, s.to, s.host, s.ttl_secs));
347 Ok(format!("a new sign-in link for {} was mailed to {}", s.account, s.to))
348 }
349 fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
350 self.did.borrow_mut().push(format!("refeed {}", r.keys.keys().cloned().collect::<Vec<_>>().join(",")));
351 Ok(format!("kept {}", r.keys.keys().cloned().collect::<Vec<_>>().join(", ")))
352 }
353 fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
354 self.did.borrow_mut().push(format!("signup {} {}", a.username, a.signup));
355 if a.username == "theirs" { Err("taken: theirs is another signup's".into()) } else { Ok(a.username.clone()) }
356 }
357 fn grow_carried(&self, _: u64) -> Result<bool, String> {
358 Ok(self.carried)
359 }
360 fn twin_holds(&self, gib: u64) -> Result<String, String> {
361 self.did.borrow_mut().push(format!("twin_holds {gib}"));
362 Ok("the twin holds it".into())
363 }
364 fn grow_start(&self, gib: u64, nonce: &str) -> Result<GrowState, String> {
365 self.did.borrow_mut().push(format!("start {gib} {nonce}"));
366 Ok(GrowState::Running("draining".into()))
367 }
368 fn grow_settle(&self, past: bool) -> GrowState {
369 self.did.borrow_mut().push(format!("settle {past}"));
370 let mut s = self.settle.borrow_mut();
371 if s.is_empty() {
372 if past { GrowState::Done("grown".into()) } else { GrowState::WaitingMembers("waiting-members".into()) }
373 } else {
374 s.remove(0)
375 }
376 }
377 fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
378 self.did.borrow_mut().push(format!("look_again {give_up}"));
379 Ok(())
380 }
381 fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
382 self.did.borrow_mut().push(format!("recharge {} from {} lost={}", r.main, r.twin, r.accept_lost));
383 Ok("recharged".into())
384 }
385 fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
386 self.did.borrow_mut().push(format!("fence {primary} {epoch}"));
387 if primary.starts_with("stuck") { Err(format!("{primary} answers nothing")) } else { Ok(format!("{primary} is fenced at epoch {epoch}")) }
388 }
389 fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
390 self.did.borrow_mut().push(format!("adopt {twin} {principal}"));
391 Ok("follows".into())
392 }
393 fn review(&self) -> Result<serde_json::Value, String> {
394 Ok(serde_json::json!({"verdict": "the twin is whole", "complete": true}))
395 }
396 fn become_twin(&self, primary: &str) -> Result<String, String> {
397 self.did.borrow_mut().push(format!("become_twin {primary}"));
398 Ok("elected".into())
399 }
400 }
401
402 pub fn fact(repo: &str, issued: u64, signer: [u8; 32]) -> EntitlementFact {
403 let mut f = EntitlementFact {
404 tenant: TenantId(repo.into()),
405 plan: "o-1".into(),
406 state: State::Paid,
407 paid_until_unix_ms: Some(99_000_000),
408 caps: Default::default(),
409 source: "payment:stripe:o-1".into(),
410 signature: Vec::new(),
411 issued_unix_ms: (issued != 0).then_some(issued),
412 issued_signature: Vec::new(),
413 };
414 let key = ed25519_dalek::SigningKey::from_bytes(&signer);
415 f.signature = key.sign(&monetize_embed::signing::fact_message(&f)).to_bytes().to_vec();
416 if issued != 0 {
417 f.issued_signature = key.sign(&monetize_embed::signing::fact_message_issued(&f)).to_bytes().to_vec();
418 }
419 f
420 }
421
422 fn run(b: &dyn HolgerBox, role: &str, o: Order) -> OrderAck {
423 let (m, k) = (me(role), monetize_key());
424 execute(b, &Ctx { me: &m, key: &k, now_unix_ms: 10_000 }, 5, &o, &mut NoAsk)
425 }
426
427 struct Asked(Vec<u64>, bool);
428 impl GrowSet for Asked {
429 fn grow_set(&mut self, gib: u64) -> Result<String, String> {
430 self.0.push(gib);
431 if self.1 { Ok("resized".into()) } else { Err("no answer".into()) }
432 }
433 }
434
435 fn growth(estate: &str, gib: u64, until: i64) -> String {
436 let mut g = monetize_embed::signing::EstateGrowth { v: 1, signer: "shop".into(), estate: estate.into(), main_data_gib_per_member: gib, until_unix_ms: until, reference: "o-7".into(), issued_unix_ms: 1, signature: String::new() };
437 let sig = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).sign(&monetize_embed::signing::estate_growth_message(&g));
438 g.signature = monetize_embed::signing::base64_encode(&sig.to_bytes());
439 serde_json::to_string(&g).unwrap()
440 }
441
442 #[test]
443 fn entitlement_is_applied_newer_wins_and_a_forgery_moves_nothing() {
444 let b = Fake::default();
445 let a = run(&b, "main", Order::Entitlement(fact("libs", 5, MONETIZE_SEED)));
446 assert!(a.ok, "{a:?}");
447 assert!(b.facts.borrow().contains_key("libs"));
448 let again = run(&b, "main", Order::Entitlement(fact("libs", 4, MONETIZE_SEED)));
449 assert!(again.ok && again.detail.contains("held"), "an older fact is held, not refused: {again:?}");
450 let forged = run(&b, "main", Order::Entitlement(fact("other", 6, [9u8; 32])));
451 assert!(!forged.ok && forged.detail.contains("does not verify"), "{forged:?}");
452 assert!(!b.facts.borrow().contains_key("other"));
453 let twin = Fake { twin: true, ..Default::default() };
454 let refused = run(&twin, "twin", Order::Entitlement(fact("libs", 5, MONETIZE_SEED)));
455 assert!(!refused.ok && refused.detail.contains("does not serve"), "{refused:?}");
456 }
457
458 #[test]
461 fn a_sign_in_again_reaches_the_serving_box_and_a_twin_mints_nothing() {
462 let s = crate::order::SignInAgain { account: "acme".into(), to: "buyer@mock.invalid".into(), host: "acme.holger.mock.invalid".into(), ttl_secs: 86_400 };
463 let b = Fake::default();
464 let a = run(&b, "main", Order::SignInAgain(s.clone()));
465 assert!(a.ok && a.detail.contains("mailed to buyer@mock.invalid"), "{a:?}");
466 assert_eq!(b.did.borrow().as_slice(), ["sign-in acme buyer@mock.invalid acme.holger.mock.invalid 86400"]);
467 let twin = Fake { twin: true, ..Default::default() };
468 let refused = run(&twin, "twin", Order::SignInAgain(s.clone()));
469 assert!(!refused.ok && refused.detail.contains("does not serve") && twin.did.borrow().is_empty(), "{refused:?}");
470 struct Bare;
471 impl HolgerBox for Bare {
472 fn serving(&self) -> bool {
473 true
474 }
475 }
476 let none = run(&Bare, "main", Order::SignInAgain(s));
477 assert!(!none.ok && none.detail.contains("mints no sign-in link"), "{none:?}");
478 }
479
480 #[test]
482 fn a_refeed_reaches_the_boxes_keep_and_a_box_that_keeps_none_refuses() {
483 let r = crate::order::Refeed { keys: [("secret.smtp_url", "smtps://u:tok@relay.mock.invalid:465"), ("secret.smtp_from", "holger@mock.invalid"), ("public_repositories", "crates")].iter().map(|(k, v)| (k.to_string(), v.to_string())).collect() };
484 let b = Fake::default();
485 let a = run(&b, "main", Order::Refeed(r.clone()));
486 assert!(a.ok && a.detail.contains("public_repositories") && !a.detail.contains("tok@"), "{a:?}");
487 assert_eq!(b.did.borrow().as_slice(), ["refeed public_repositories,secret.smtp_from,secret.smtp_url"]);
488 struct Bare;
489 impl HolgerBox for Bare {
490 fn serving(&self) -> bool {
491 true
492 }
493 }
494 let none = run(&Bare, "main", Order::Refeed(r));
495 assert!(!none.ok && none.detail.contains("keeps no estate keys"), "{none:?}");
496 }
497
498 #[test]
499 fn restate_entitlements_verifies_the_snapshot_and_counts_new_and_held() {
500 let b = Fake::default();
501 run(&b, "main", Order::Entitlement(fact("a", 5, MONETIZE_SEED)));
502 let facts = vec![fact("a", 5, MONETIZE_SEED), fact("b", 5, MONETIZE_SEED)];
503 let mut snap = monetize_embed::Snapshot { issued_unix_ms: 7, facts, signature: Vec::new() };
504 snap.signature = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED).sign(&monetize_embed::signing::snapshot_message(snap.issued_unix_ms, &snap.facts)).to_bytes().to_vec();
505 let a = run(&b, "main", Order::RestateEntitlements(snap.clone()));
506 assert!(a.ok && a.detail.contains("restated 1") && a.detail.contains("1 already held"), "{a:?}");
507 snap.signature = vec![0; 64];
508 let bad = run(&b, "main", Order::RestateEntitlements(snap));
509 assert!(!bad.ok && bad.detail.contains("does not verify"), "{bad:?}");
510 }
511
512 #[test]
513 fn purge_tenant_answers_monetizes_purge_answer() {
514 let b = Fake::default();
515 let a = run(&b, "main", Order::PurgeTenant(PurgeTenant { tenant: "libs".into(), reason: "nonpay".into(), job: String::new() }));
516 assert!(a.ok, "{a:?}");
517 let answer: PurgeAnswer = serde_json::from_str(&a.detail).unwrap();
518 assert_eq!(answer.job, "purge:libs");
519 assert_eq!(b.did.borrow().as_slice(), ["purge libs"]);
520 }
521
522 #[test]
523 fn add_principal_makes_a_repository_on_a_plan_and_says_taken_by_name() {
524 let b = Fake::default();
525 let a = run(&b, "main", Order::AddPrincipal(AddPrincipal { username: "crates".into(), kind: "org".into(), signup: "s-1".into() }));
526 assert!(a.ok && a.detail == "crates", "the namespace made is the repository: {a:?}");
527 let t = run(&b, "main", Order::AddPrincipal(AddPrincipal { username: "theirs".into(), kind: "org".into(), signup: "s-2".into() }));
528 assert!(!t.ok && t.detail.starts_with("taken"), "{t:?}");
529 let twin = Fake { twin: true, ..Default::default() };
530 assert!(!run(&twin, "twin", Order::AddPrincipal(AddPrincipal { username: "crates".into(), kind: "org".into(), signup: "s-1".into() })).ok, "only the main that serves");
531 }
532
533 #[test]
534 fn grow_room_is_kept_for_the_operator() {
535 let b = Fake::default();
536 let a = run(&b, "main", Order::GrowRoom(GrowRoom { need_bytes: 1 << 30, reason: "libs".into() }));
537 assert!(a.ok, "{a:?}");
538 assert_eq!(b.did.borrow().as_slice(), ["room 1073741824"]);
539 }
540
541 #[test]
542 fn grow_go_ahead_runs_twin_first_asks_monetize_and_remounts() {
543 let b = Fake::default();
544 let (m, k) = (me("main"), monetize_key());
545 let mut ask = Asked(Vec::new(), true);
546 let o = Order::GrowGoAhead(GrowGoAhead { go_ahead: growth("acme", 120, 99_000), data_gib: 120 });
547 let a = execute(&b, &Ctx { me: &m, key: &k, now_unix_ms: 10_000 }, 6, &o, &mut ask);
548 assert!(a.ok, "{a:?}");
549 assert_eq!(b.did.borrow().as_slice(), ["twin_holds 120", "start 120 pull-6-o-7", "settle false", "look_again false", "settle true"]);
550 assert_eq!(ask.0, [120]);
551
552 let b = Fake::default();
554 let mut deaf = Asked(Vec::new(), false);
555 b.settle.borrow_mut().extend([GrowState::WaitingMembers("w".into()), GrowState::Failed("mounted back".into())]);
556 let a = execute(&b, &Ctx { me: &m, key: &k, now_unix_ms: 10_000 }, 7, &o, &mut deaf);
557 assert!(!a.ok && a.detail.contains("no answer") && a.detail.contains("mounted back"), "{a:?}");
558 assert!(b.did.borrow().contains(&"look_again true".to_string()));
559 }
560
561 #[test]
562 fn grow_go_ahead_refuses_another_estate_a_lapsed_step_and_a_forgery_and_skips_a_done_one() {
563 let b = Fake::default();
564 for (go, why) in [(growth("other", 120, 99_000), "estate other"), (growth("acme", 120, 5_000), "period is over"), (growth("acme", 120, 99_000).replace("\"o-7\"", "\"o-8\""), "does not verify")] {
565 let a = run(&b, "main", Order::GrowGoAhead(GrowGoAhead { go_ahead: go, data_gib: 120 }));
566 assert!(!a.ok && a.detail.contains(why), "{why}: {a:?}");
567 }
568 assert!(b.did.borrow().is_empty(), "nothing moved");
569 let done = Fake { carried: true, ..Default::default() };
570 let a = run(&done, "main", Order::GrowGoAhead(GrowGoAhead { go_ahead: growth("acme", 120, 99_000), data_gib: 120 }));
571 assert!(a.ok && a.detail.contains("nothing moved"), "{a:?}");
572 let twin = Fake { twin: true, ..Default::default() };
573 let a = run(&twin, "twin", Order::GrowGoAhead(GrowGoAhead { go_ahead: growth("acme", 120, 99_000), data_gib: 240 }));
574 assert!(a.detail.contains("no pull channel"), "{a:?}");
575 assert!(!twin.did.borrow().iter().any(|d| d.starts_with("twin_holds")), "the twin grows first, asking no twin: {a:?}");
576 }
577
578 #[test]
579 fn recharge_from_twin_runs_the_lane_on_a_cloud_fence_proof() {
580 let b = Fake::default();
581 let r = RechargeAsk { main: "main:1".into(), twin: "twin:1".into(), accept_lost: true, fenced: Some("upcloud: gone".into()), ..Default::default() };
582 let a = run(&b, "twin", Order::RechargeFromTwin(r));
583 assert!(a.ok && a.detail.starts_with("cloud-fenced"), "{a:?}");
584 assert_eq!(b.did.borrow().as_slice(), ["recharge main:1 from twin:1 lost=true"]);
585 }
586
587 #[test]
592 fn recharge_from_twin_fences_the_old_primary_first() {
593 let b = Fake::default();
594 let r = RechargeAsk { main: "main:2".into(), twin: "twin:1".into(), accept_lost: false, epoch: 4, fence: Some("old:1".into()), ..Default::default() };
595 let a = run(&b, "twin", Order::RechargeFromTwin(r.clone()));
596 assert!(a.ok && a.detail.starts_with("fenced old:1; "), "{a:?}");
597 assert_eq!(b.did.borrow().as_slice(), ["fence old:1 4", "recharge main:2 from twin:1 lost=false"]);
598
599 let stuck = Fake::default();
600 let a = run(&stuck, "twin", Order::RechargeFromTwin(RechargeAsk { fence: Some("stuck:1".into()), ..r.clone() }));
601 assert!(!a.ok && a.detail.contains("stuck:1 answers nothing") && a.detail.contains("nothing was recharged"), "{a:?}");
602 assert_eq!(stuck.did.borrow().as_slice(), ["fence stuck:1 4"], "no recharge beside an unfenced primary");
603
604 let same = Fake::default();
605 let a = run(&same, "twin", Order::RechargeFromTwin(RechargeAsk { fence: Some("main:2".into()), ..r }));
606 assert!(!a.ok && a.detail.contains("never fences the box it fills"), "{a:?}");
607 assert!(same.did.borrow().is_empty(), "nothing ran");
608 }
609
610 #[test]
611 fn become_twin_runs_holgers_election() {
612 let b = Fake { twin: true, ..Default::default() };
613 assert!(run(&b, "twin", Order::BecomeTwin { primary: "main:1".into() }).ok);
614 assert_eq!(b.did.borrow().as_slice(), ["become_twin main:1"]);
615 struct Bare;
616 impl HolgerBox for Bare {
617 fn serving(&self) -> bool {
618 false
619 }
620 }
621 let none = run(&Bare, "twin", Order::BecomeTwin { primary: "main:1".into() });
622 assert!(!none.ok && none.detail.contains("cannot become"), "the default refuses by name: {none:?}");
623 }
624
625 #[test]
626 fn fence_primary_runs_the_boxes_fence_adopt_and_review_run() {
627 let b = Fake::default();
628 let a = run(&b, "twin", Order::FencePrimary { primary: "old:1".into(), epoch: 3 });
629 assert!(a.ok && b.did.borrow().contains(&"fence old:1 3".to_string()), "{a:?}");
630 struct Bare;
631 impl HolgerBox for Bare {
632 fn serving(&self) -> bool {
633 false
634 }
635 }
636 let none = run(&Bare, "twin", Order::FencePrimary { primary: "old:1".into(), epoch: 3 });
637 assert!(!none.ok && none.detail.contains("cannot fence"), "the default refuses by name: {none:?}");
638 let a = run(&b, "main", Order::AdoptTwin { twin: "twin:1".into(), principal: "twin".into() });
639 assert!(a.ok && b.did.borrow().contains(&"adopt twin:1 twin".to_string()), "{a:?}");
640 let a = run(&b, "twin", Order::ReviewTwin);
641 assert!(a.ok && a.detail == "the twin is whole" && a.receipt_json.contains("complete"), "{a:?}");
642 }
643}