//! `wai.delivery.ledger` (extensions/receipt-ledger.md): the committed corpus //! `live-conformance/ledger/vectors.json` regenerates byte for byte from its seeds and the sources //! of the corpora it reads, and is the only file there besides its README; every vector gets the //! outcome it states; every reason the extension adds refuses a vector, and so does each source //! kind's own extension; the accepted vectors are the cases §11 names; under a tariff of version 8, //! a holder's storage billed by byte-days from records its snapshots recompute, and every rule of //! §4.13 refusing a control; and §10 lists exactly the reasons the reference reports. #![cfg(feature = "receipt_ledger")] use std::collections::BTreeSet; use std::path::PathBuf; use wai::receipt_ledger::{corpus, Reason}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(p: &str) -> String { std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) } fn committed() -> String { read("live-conformance/ledger/vectors.json") } #[test] fn the_committed_corpus_regenerates_from_its_seeds_and_sources() { let generated = corpus::generate(&wai_dir()).expect("the corpus generates"); assert!(committed() == generated, "live-conformance/ledger/vectors.json differs from `wai_receipt_ledger_vectors gen`"); let mut files: Vec = std::fs::read_dir(wai_dir().join("live-conformance/ledger")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).collect(); files.sort(); assert_eq!(files, ["README.md", "vectors.json"]); } #[test] fn every_vector_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// Every reason this extension adds and every code it reuses refuses a vector; each source kind /// is refused once by its own extension; the accepted invoices are §11's. #[test] fn the_corpus_covers_every_reason_and_case() { let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let seen: BTreeSet<&str> = vectors.iter().filter_map(|v| v["outcome"]["reason"].as_str()).collect(); for r in Reason::ALL { assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); } for r in Reason::REUSED { assert!(seen.contains(r), "nothing in the corpus is refused {r}"); } let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); // A refusal of each source kind by its own extension, at the source's place. for (n, reason, at) in [ ("room_receipt_refused", "bad_signature", serde_json::json!(["source", 0, "receipt"])), ("statement_refused", "statement_mismatch", serde_json::json!(["source", 1, "statement"])), ("package_refused", "receipt-index-count", serde_json::json!(["source", 4, "receipt"])), ("ads_refused", "funds_exceeded", serde_json::json!(["source", 5, "receipt", 1])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } // Each source kind's scope, period and tariff rule refuses a vector of its own. for (n, reason, at) in [ ("room_outside_scope", "outside_scope", serde_json::json!(["source", 0])), ("party_outside_scope", "outside_scope", serde_json::json!(["source", 2])), ("package_outside_scope", "outside_scope", serde_json::json!(["source", 4])), ("decider_outside_scope", "outside_scope", serde_json::json!(["source", 5])), ("room_under_another_tariff", "tariff_conflict", serde_json::json!(["source", 0])), ("statement_under_another_tariff", "tariff_conflict", serde_json::json!(["source", 1])), ("room_outside_period", "outside_period", serde_json::json!(["source", 7])), ("statement_outside_period", "outside_period", serde_json::json!(["source", 0])), ("ad_break_outside_period", "outside_period", serde_json::json!(["source", 0])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } let a = get("a_day"); assert_eq!(a["outcome"], "accept"); let kinds: BTreeSet<&str> = a["lines"].as_array().unwrap().iter().map(|l| l["source_kind"].as_str().unwrap()).collect(); assert_eq!(kinds, ["ads", "delivery", "package", "room"].into_iter().collect(), "a line of every source kind"); let units: BTreeSet<&str> = a["lines"].as_array().unwrap().iter().map(|l| l["unit"].as_str().unwrap()).collect(); assert!(units.contains("presented_impressions") && units.contains("billable_impressions"), "ads under both rules"); assert!(a["lines"].as_array().unwrap().iter().all(|l| !l["cites"].as_array().unwrap().is_empty()), "every line cites its records"); let delivery = &a["reports"]["delivery"]; assert_eq!(delivery["parties"].as_array().unwrap().len(), 3, "two delivering parties and the forwarder"); assert!(!delivery["overlaps"].as_array().unwrap().is_empty(), "one viewer acknowledged one object from both parties"); assert!(delivery["parties"].as_array().unwrap().iter().any(|p| p["declared"]["bytes"].as_u64().unwrap() > 0), "declared bytes, never billed"); assert!(delivery["parties"].as_array().unwrap().iter().any(|p| p["free_bytes"].as_u64().unwrap() > 0), "a retrieval served free"); let ads = a["reports"]["ads"].as_array().unwrap(); assert!(ads.iter().any(|r| r["claims_required"] == true && !r["not_counted"].as_object().unwrap().is_empty()), "billed impressions the players' claims did not count"); let scopes: BTreeSet<&str> = a["energy"].as_array().unwrap().iter().map(|r| r["scope"].as_str().unwrap()).collect(); assert!(scopes.contains("room_hops") && scopes.contains("delivery_hops") && scopes.contains("ad_decisions"), "{scopes:?}"); let s = get("streams_stitched_by_others"); assert_eq!(s["outcome"], "accept"); assert!(s["lines"].as_array().unwrap().iter().all(|l| l["unit"] == "presented_impressions"), "stitched presentations bill as presented impressions"); let statements = s["reports"]["stitched"].as_array().unwrap(); assert_eq!(statements.len(), 3, "one stitched-presentation statement per stitched source"); assert!( statements.iter().flat_map(|st| st["record_facts"].as_array().unwrap()).filter_map(|r| r["ads"].as_array()).flatten().any(|a| a["own_claims"].as_array().unwrap().len() as u64 > a["presentations"].as_u64().unwrap()), "an ad whose own claims are not all presented: only presentations bill" ); assert!( statements.iter().flat_map(|st| st["record_facts"].as_array().unwrap()).any(|r| r["stated_spend_micros"].is_u64()), "a form-1 receipt's own spend stated beside the presented one" ); assert_eq!(get("stitched_source_given_twice")["reason"], "record_duplicate"); assert_eq!(get("stitcher_outside_scope")["reason"], "quantity_not_recorded"); let b = get("the_next_invoice"); assert_eq!(b["outcome"], "accept"); assert_eq!(b["coverage"]["room_hops"], "partial", "a relay's unmetered hop"); let m = get("room_hops_of_three_classes"); let rows: Vec<&str> = m["energy"].as_array().unwrap().iter().filter(|r| r["scope"] == "room_hops").map(|r| r["class"].as_str().unwrap()).collect(); assert_eq!(rows, ["ModelBased", "Estimator", "OnChipCounter"], "one row per class, never one across them"); } /// Version 3 (§4.6, §6.9): one capability's encode and decode at their own prices; a re-run billed /// in the next invoice of its chain, the output receipt the first invoice holds cited again; one /// output under two hops of one invoice, cited once and billed for each; an operation the tariff /// does not price billing nothing; a replayed hop receipt and a hop two jobs claim refused; and /// version 2 billing an output once, as it did. #[test] fn version_3_prices_each_operation_and_bills_the_work() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); let line = |o: &Value, cap: &str, op: &str, unit: &str| -> Value { o["lines"] .as_array() .unwrap() .iter() .find(|l| l["source_kind"] == "job" && l["capability"] == cap && l["operation"] == op && l["unit"] == unit) .cloned() .unwrap_or_else(|| panic!("no {cap} {op} {unit} line")) }; let cites = |l: &Value, kind: u64| -> Vec { l["cites"].as_array().unwrap().iter().filter(|c| c[0] == kind).map(|c| c[1].as_str().unwrap().to_string()).collect() }; const IM: &str = "wai.video.int_motion"; let k = get("jobs_priced_per_operation"); assert_eq!(k["outcome"], "accept"); let (enc, dec) = (line(&k, IM, "media.encode", "subpixels"), line(&k, IM, "media.decode", "subpixels")); assert_eq!((enc["price"].as_u64(), dec["price"].as_u64()), (Some(20_000), Some(10_000)), "an encode and a decode at their own prices"); assert_eq!(enc["quantity"].as_u64().unwrap(), 2 * dec["quantity"].as_u64().unwrap(), "two encodes and one decode of one clip"); assert_eq!((line(&k, IM, "media.encode", "jobs")["quantity"].as_u64(), line(&k, IM, "media.decode", "jobs")["quantity"].as_u64()), (Some(2), Some(1))); assert_eq!(line(&k, "wai.video.av1", "media.encode", "jobs")["quantity"], 1, "the failover, at the fallback's encode price"); let r = get("a_rerun_billed"); assert_eq!(r["outcome"], "accept"); let rerun = line(&r, IM, "media.encode", "subpixels"); let output = cites(&rerun, 15); assert_eq!(output.len(), 1); assert!(cites(&enc, 15).contains(&output[0]), "the re-run's output receipt is one the first invoice billed with job-0001"); assert!(cites(&rerun, 14).iter().all(|h| !cites(&enc, 14).contains(h)), "the re-run's hop receipt is new"); let both = get("one_output_under_two_hops"); assert_eq!(both["outcome"], "accept"); let b = line(&both, IM, "media.encode", "subpixels"); assert_eq!((cites(&b, 14).len(), cites(&b, 15), cites(&b, 16).len()), (2, output.clone(), 2), "two hops and two records, one output cited once"); assert_eq!(b["quantity"].as_u64().unwrap(), 2 * rerun["quantity"].as_u64().unwrap(), "its subpixels billed for each hop"); assert_eq!(line(&both, IM, "media.encode", "jobs")["quantity"], 2); let n = get("an_operation_the_tariff_does_not_price"); assert_eq!(n["outcome"], "accept"); assert!(n["lines"].as_array().unwrap().iter().all(|l| l["operation"] == "media.encode"), "the decode bills nothing"); assert_eq!(n["energy"].as_array().unwrap().iter().filter(|e| e["scope"] == "jobs").map(|e| e["figures"].as_u64().unwrap()).sum::(), 2, "both hops' energy stated"); for (name, reason, at) in [ ("a_hop_receipt_replayed", "record_rebilled", json!(["source", 0])), ("one_hop_under_two_lines", "record_duplicate", json!(["source", 1])), ("job_prices_unordered_by_operation", "tariff_invalid", json!(["tariff"])), ("version_2_invoice_under_a_version_3_tariff", "tariff_mismatch", json!(["invoice"])), ("version_2_bills_an_output_once", "record_rebilled", json!(["source", 0])), ("output_claimed_by_two_jobs", "record_duplicate", json!(["source", 1])), ("job_routed_twice", "record_overlap", json!(["source", 6])), ] { let o = get(name); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{name}"); } } /// Jobs routed under the route record's version 2 (§5.1): billed under version 3 at the endpoint /// that served, each output receipt of an object its hop states it delivered, and refused /// `output_not_delivered` when it is another or given twice; under version 4 a record of /// version 1 holds its outputs to none. #[test] fn a_jobs_output_is_what_its_hop_delivered() { use serde_json::json; let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); let r = get("version_2_routes_under_version_3"); assert_eq!(r["outcome"], "accept"); let jobs: u64 = r["lines"].as_array().unwrap().iter().filter(|l| l["unit"] == "jobs").map(|l| l["quantity"].as_u64().unwrap()).sum(); assert_eq!(jobs, 4, "an encode, a failover, a decode and a failover whose failed hop states no class; the late and the failed jobs bill nothing"); assert!(r["energy"].as_array().unwrap().iter().all(|e| e["scope"] == "jobs"), "version 3 states the billed hops' energy alone"); let record = |n: &str| -> serde_json::Value { serde_json::from_str(doc["sources"][n]["record"].as_str().unwrap()).unwrap() }; assert_eq!(record("job_v2_encode")["version"], 2, "a route record of version 2"); assert!(record("job_v2_failover")["candidates"].as_array().unwrap().iter().any(|c| c["failures"].is_object()), "measured failures stated"); for (n, at) in [ ("an_output_the_hop_did_not_deliver", json!(["source", 0, "outputs", 0])), ("one_output_given_twice", json!(["source", 0, "outputs", 1])), ("version_4_an_output_the_hop_did_not_deliver", json!(["source", 0, "outputs", 0])), ("version_4_an_output_of_a_version_1_record", json!(["source", 0, "outputs", 0])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some("output_not_delivered"), &at), "{n}"); } } /// Version 4 (§4.7, §6.6): a room's bytes out at its price per 10^9 bytes; a package's work /// billed once per run that made it, its receipt cited by each, and its storage by byte-days for /// each period it is held; every hop's energy, the hops not billed in a scope of their own; and /// each control refused at its place. #[test] fn version_4_bills_the_bytes_the_work_and_the_storage() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); let lines = |o: &Value, unit: &str| -> Vec { o["lines"].as_array().unwrap().iter().filter(|l| l["unit"] == unit).cloned().collect() }; let metering: Value = serde_json::from_str(&read("live-conformance/metering/vectors.json")).unwrap(); let bytes = metering["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "bytes_out_priced").unwrap()["units"]["bytes_out"].as_u64().unwrap(); let package = |n: &str| -> Value { serde_json::from_str(doc["sources"][n]["receipt"].as_str().unwrap()).unwrap() }; let base = package("package_base"); let cites = |l: &Value, kind: u64| -> Vec { l["cites"].as_array().unwrap().iter().filter(|c| c[0] == kind).map(|c| c[1].as_str().unwrap().to_string()).collect() }; let a = get("version_4_by_the_byte_and_the_work"); assert_eq!(a["outcome"], "accept"); let out = lines(&a, "bytes_out"); assert_eq!(out.len(), 1); assert_eq!(out[0]["quantity"].as_u64(), Some(bytes), "the receipt's bytes out"); assert_eq!(out[0]["amount"].as_u64().unwrap(), (bytes as u128 * out[0]["price"].as_u64().unwrap() as u128 / 1_000_000_000) as u64); let work_a = lines(&a, "work_units"); assert_eq!((work_a.len(), &work_a[0]["quantity"], &work_a[0]["source_kind"]), (1, &base["work_units"], &json!("job")), "the run that made the package, by its work units"); assert_eq!(cites(&work_a[0], 5), vec![base["receipt_hash"].as_str().unwrap().to_string()], "the package cited as the run's output"); assert!(a["lines"].as_array().unwrap().iter().all(|l| l["source_kind"] != "package"), "a package first listed bills no storage, and a package source no work"); let scope = |o: &Value, s: &str| -> Vec { o["energy"].as_array().unwrap().iter().filter(|e| e["scope"] == s).cloned().collect() }; assert_eq!(scope(&a, "jobs").iter().map(|e| e["figures"].as_u64().unwrap()).sum::(), 4, "four billed hops, the packaging run's among them"); assert!(scope(&a, "packaging").is_empty(), "a package's energy is its run's, stated by its hop"); assert_eq!(scope(&a, "unbilled_hops").iter().map(|e| e["figures"].as_u64().unwrap()).sum::(), 4, "the failover's, the late hop's and two of the three failed hops' figures"); assert_eq!((a["unmetered"]["unbilled_hops"].as_u64(), a["coverage"]["unbilled_hops"].as_str()), (Some(1), Some("partial")), "one failed hop unmetered"); let b = get("version_4_storage_held_through_a_period"); assert_eq!(b["outcome"], "accept"); let days = lines(&b, "byte_days"); assert_eq!(days.len(), 1); let period = b["end"].as_u64().unwrap() - b["start"].as_u64().unwrap(); assert_eq!(days[0]["quantity"].as_u64().unwrap() as u128, base["stored_bytes"].as_u64().unwrap() as u128 * period as u128 / 86_400_000_000, "its bytes over the invoice's period, in days"); let work_b = lines(&b, "work_units"); assert_eq!((work_b.len(), &work_b[0]["quantity"]), (1, &base["work_units"]), "the same package made again, its work billed for the new run"); assert_eq!(cites(&work_b[0], 5), cites(&work_a[0], 5), "the package the first invoice lists, cited again"); assert!(cites(&work_b[0], 14).iter().all(|h| !cites(&work_a[0], 14).contains(h)), "a new run's hop receipt"); for (n, reason, at) in [ ("version_4_bytes_at_another_price", "tariff_conflict", json!(["source", 0])), ("version_4_a_receipt_that_prices_no_bytes", "tariff_conflict", json!(["source", 0])), ("version_4_bytes_out_above_the_receipt", "quantity_not_recorded", json!(["invoice"])), ("version_4_storage_of_a_package_first_billed", "quantity_not_recorded", json!(["invoice"])), ("version_4_a_package_run_replayed", "record_rebilled", json!(["source", 2])), ("version_4_one_package_run_under_two_jobs", "record_duplicate", json!(["source", 1])), ("version_4_storage_over_the_tariffs_period", "quantity_not_recorded", json!(["invoice"])), ("version_4_unbilled_hops_left_out", "invoice_mismatch", json!(["invoice"])), ("version_4_unbilled_hops_in_the_jobs_scope", "invoice_mismatch", json!(["invoice"])), ("version_4_a_failed_hop_billed_as_a_job", "quantity_not_recorded", json!(["invoice"])), ("version_4_a_job_line_at_the_routers_price", "invoice_mismatch", json!(["invoice"])), ("version_4_unbilled_hop_with_no_class", "job_figure_invalid", json!(["source", 0, "receipts", 0])), ("version_3_invoice_under_a_version_4_tariff", "tariff_mismatch", json!(["invoice"])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } } /// §10 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. /// §4.9: a third party's usage per endpoint, and what re-derives an upscale and a tone mapping. #[test] fn version_6_bills_a_usage_per_endpoint_and_what_it_re_derives() { use serde_json::Value; let text = committed(); let doc: Value = serde_json::from_str(&text).unwrap(); let vector = |name: &str| doc["vectors"].as_array().unwrap().iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no vector {name}")); let line = |o: &Value, endpoint: &str, usage: &str| -> Value { o["lines"].as_array().unwrap().iter().find(|l| l["endpoint"] == endpoint && l["usage_unit"] == usage).cloned().unwrap_or(Value::Null) }; let o = &vector("version_6_a_third_partys_usage_per_endpoint")["outcome"]; assert_eq!(o["outcome"], "accept"); // Each endpoint at its own price. gen.alpha's usage is the served hop's alone: the failover's // failed hop's billed second, and the job given no record, reach no line. assert_eq!((&line(o, "gen.alpha", "seconds")["quantity"], &line(o, "gen.alpha", "seconds")["price"]), (&Value::from(8), &Value::from(40_000_000))); assert_eq!((&line(o, "gen.beta", "seconds")["quantity"], &line(o, "gen.beta", "seconds")["price"]), (&Value::from(8), &Value::from(25_000_000))); // Another unit at that unit's price, and a unit no price names, which bills by the job alone. assert_eq!(line(o, "gen.alpha", "images")["quantity"], 2); assert_eq!(o["lines"].as_array().unwrap().iter().filter(|l| l["unit"] == "usage").count(), 3); let jobs = o["lines"].as_array().unwrap().iter().find(|l| l["unit"] == "jobs").unwrap(); assert_eq!((&jobs["quantity"], &jobs["endpoint"]), (&Value::from(6), &Value::from(""))); // A usage line cites its record; a line per job does not need to. let cites_kind = |l: &Value, k: u64| l["cites"].as_array().unwrap().iter().filter(|c| c[0] == k).count(); assert_eq!(cites_kind(&line(o, "gen.beta", "seconds"), 17), 2); assert_eq!(cites_kind(jobs, 17), 0); let o = &vector("version_6_an_upscale_and_a_tone_mapping")["outcome"]; assert_eq!(o["outcome"], "accept"); let by_capability = |c: &str| o["lines"].as_array().unwrap().iter().find(|l| l["capability"] == c).unwrap().clone(); // 12×8 and 8×6 pictures, three samples a pixel; three pictures and two pictures of 8×4. assert_eq!(by_capability("wai.media.upscale")["quantity"], (12 * 8 + 8 * 6) * 3); assert_eq!(by_capability("wai.media.tonemap")["quantity"], 5 * 8 * 4 * 3); assert_eq!(cites_kind(&by_capability("wai.media.upscale"), 18), 2); assert_eq!(cites_kind(&by_capability("wai.media.tonemap"), 19), 2); // The chain cites an upscale's records again for a new hop's work. assert_eq!(vector("version_6_an_upscale_run_again")["outcome"]["outcome"], "accept"); for (name, reason) in [ ("version_6_a_price_of_any_endpoint_beside_one", "tariff_invalid"), ("version_6_prices_out_of_order_by_endpoint", "tariff_invalid"), ("version_6_a_usage_price_naming_no_unit", "tariff_invalid"), ("version_6_a_jobs_price_naming_a_unit", "tariff_invalid"), ("version_5_a_price_of_usage", "tariff_invalid"), ("version_5_an_invoice_listing_a_third_partys_record", "invoice_invalid"), ("version_5_invoice_under_a_version_6_tariff", "tariff_mismatch"), ("version_5_a_job_given_a_third_partys_records", "malformed"), ("version_6_another_jobs_record", "third_party_mismatch"), ("version_6_a_failed_hops_usage_billed", "quantity_not_recorded"), ("version_6_usage_no_record_states", "quantity_not_recorded"), ("version_6_usage_at_another_endpoints_price", "invoice_mismatch"), ("version_6_a_record_left_out", "invoice_mismatch"), ("version_6_a_usage_line_not_citing_its_record", "citation_mismatch"), ("version_6_a_picture_altered", "output_invalid"), ("version_6_an_upscale_under_another_prior", "output_invalid"), ("version_6_a_record_of_another_output", "output_invalid"), ("version_6_a_picture_its_hop_did_not_deliver", "output_not_delivered"), ("version_6_an_upscale_output_twice", "output_not_delivered"), ("version_6_a_rendition_altered", "output_invalid"), ("version_6_a_tone_mapping_of_another_envelope", "output_invalid"), ("version_6_a_rendition_its_hop_did_not_deliver", "output_not_delivered"), ("version_6_a_tone_mapping_output_twice", "output_not_delivered"), ("version_6_a_tone_mapping_without_its_proofs", "malformed"), ] { assert_eq!(vector(name)["outcome"]["reason"], reason, "{name}"); } } /// §4.10: a month of a broadcaster's transmissions, four invoices chained a week apart, each /// entry of a standing schedule billed in octets sent and in pass time at the price of its class /// whose ceiling is the least at or above its pass rate, nothing in a unit above every ceiling, /// and each declaration's energy once; every amount below is worked by hand from the schedules. #[test] fn version_7_bills_a_transmission_per_class_unit_and_ceiling() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); // (bearer, unit, ceiling, quantity, price, amount) of each line, in line order. let lines = |o: &Value| -> Vec<(String, String, u64, u64, u64, u64)> { o["lines"] .as_array() .unwrap() .iter() .map(|l| { assert_eq!(l["source_kind"], "group"); let n = |k: &str| l[k].as_u64().unwrap(); (l["bearer"].as_str().unwrap().into(), l["unit"].as_str().unwrap().into(), n("max_bps"), n("quantity"), n("price"), n("amount")) }) .collect() }; let row = |b: &str, u: &str, c: u64, q: u64, p: u64, a: u64| (b.to_string(), u.to_string(), c, q, p, a); let energy = |o: &Value| -> Vec<(String, u64, u64)> { o["energy"].as_array().unwrap().iter().map(|r| (r["scope"].as_str().unwrap().to_string() + " " + r["class"].as_str().unwrap(), r["figures"].as_u64().unwrap(), r["joules"].as_u64().unwrap())).collect() }; // Week 1. The multicast's object 3 at 5,440 bit/s and object 0 at 563,200; the broadcast's // three at 9,472, 5,120 and 2,304, all within its pass-time ceiling of 100,000. let w1 = get("version_7_week_1"); assert_eq!(w1["outcome"], "accept"); assert_eq!( lines(&w1), [ row("multicast", "bytes_sent", 50_000, 1_360, 4_000_000, 5), row("multicast", "bytes_sent", 2_000_000, 14_080, 6_000_000, 84), row("multicast", "pass_us", 2_000_000, 2_200_000, 300, 660), row("unidirectional", "bytes_sent", 1_000_000, 23_680 + 3_840 + 1_152, 2_500_000, 71), row("unidirectional", "pass_us", 100_000, 30_000_000, 150, 4_500), ] ); assert_eq!(w1["amount"], 5 + 84 + 660 + 71 + 4_500); assert_eq!(energy(&w1), [("transmissions ModelBased".into(), 1, 1_200_000), ("transmissions OnChipCounter".into(), 1, 2_500_000)]); assert_eq!((&w1["coverage"]["transmissions"], &w1["unmetered"]["transmissions"]), (&json!("whole"), &json!(0))); let cites = |l: &Value, kind: u64| l["cites"].as_array().unwrap().iter().filter(|c| c[0] == kind).count(); for l in w1["lines"].as_array().unwrap() { assert_eq!((cites(l, 20), cites(l, 21), cites(l, 22)), (1, 1, 1), "the group, its instance's declaration and its schedule"); } // Week 2: the faster broadcast's object 1 at 1,024,000 bit/s is above the octets' ceiling of // 1,000,000 and bills no octets; its pass time and its object 0's, at 947,200, at the // ceiling of 10,000,000; the first week's group again on a new instance. let w2 = get("version_7_week_2"); assert_eq!(w2["outcome"], "accept"); assert_eq!( lines(&w2), [ row("unidirectional", "bytes_sent", 1_000_000, 14_208 + 2_040, 2_500_000, 40), row("unidirectional", "pass_us", 100_000, 3_000_000, 150, 450), row("unidirectional", "pass_us", 10_000_000, 140_000, 900, 126), ] ); let report = &w2["reports"]["transmission"]; assert_eq!(report.as_array().unwrap().len(), 2, "two groups, one of them the first week's again"); let late = &report[0]["entries"][1]; assert_eq!((&late["rate_bps"], &late["bytes_sent_price"], &late["pass_us_price"]), (&json!(1_024_000), &Value::Null, &json!(6))); let unhex = |s: &str| -> Vec { (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() }; let records = |n: &str| -> Vec<(u8, [u8; 32])> { let b = unhex(vectors.iter().find(|v| v["name"] == n).unwrap()["invoice"].as_str().unwrap()); wai::receipt_ledger::Invoice::parse(&b).unwrap().records }; let (r1, r2) = (records("version_7_week_1"), records("version_7_week_2")); let groups = |r: &[(u8, [u8; 32])]| -> Vec<[u8; 32]> { r.iter().filter(|(k, _)| *k == 20).map(|(_, id)| *id).collect() }; assert!(groups(&r2).iter().any(|g| groups(&r1).contains(g)), "the first week's group receipt listed again"); assert!(r2.iter().filter(|(k, _)| *k != 20).all(|r| !r1.contains(r)), "and no declaration or schedule twice"); // Week 3: the multicast's object 0 at 3,788,800 bit/s is above every ceiling of its class // and bills nothing; its object 2, 49,994 octets in 7,999 ms, is at 50,000.25 bit/s, which // rounds up past the ceiling of 50,000; its declaration states no figure. let w3 = get("version_7_week_3"); assert_eq!(w3["outcome"], "accept"); assert_eq!( lines(&w3), [ row("multicast", "bytes_sent", 50_000, 864, 4_000_000, 3), row("multicast", "bytes_sent", 2_000_000, 49_994, 6_000_000, 299), row("multicast", "pass_us", 2_000_000, 1_500_000 + 7_999_000, 300, 2_849), ] ); let above = &w3["reports"]["transmission"][0]["entries"][0]; assert_eq!((&above["rate_bps"], &above["bytes_sent_price"], &above["pass_us_price"]), (&json!(3_788_800), &Value::Null, &Value::Null)); let edge = &w3["reports"]["transmission"][0]["entries"][2]; assert_eq!((&edge["rate_bps"], &edge["bytes_sent_price"]), (&json!(50_001), &json!(2)), "rounded up, at the next ceiling"); assert_eq!((&w3["coverage"]["transmissions"], &w3["unmetered"]["transmissions"]), (&json!("partial"), &json!(1))); assert!(energy(&w3).is_empty()); // Week 4: a relay chain declared and scheduled nowhere bills nothing and states its energy. let w4 = get("version_7_week_4"); assert_eq!(w4["outcome"], "accept"); assert_eq!(lines(&w4), [row("unidirectional", "bytes_sent", 1_000_000, 4_080, 2_500_000, 10), row("unidirectional", "pass_us", 100_000, 6_000_000, 150, 900)]); assert_eq!(energy(&w4), [("transmissions Estimator".into(), 1, 400_000), ("transmissions OnChipCounter".into(), 1, 3_000_000)]); // The month: four invoices a week apart, each naming the one before it. let month: Vec<(u64, u64)> = ["version_7_week_1", "version_7_week_2", "version_7_week_3", "version_7_week_4"].iter().map(|n| (get(n)["start"].as_u64().unwrap(), get(n)["end"].as_u64().unwrap())).collect(); assert_eq!((month[0].0, month[3].1), (1_790_812_800_000_000, 1_793_491_200_000_000), "October 2026"); assert!(month.windows(2).all(|w| w[0].1 == w[1].0)); for (n, reason, at) in [ ("version_7_a_line_over_its_ceiling", "quantity_not_recorded", json!(["invoice"])), ("version_7_a_line_above_its_least_ceiling", "quantity_not_recorded", json!(["invoice"])), ("version_7_an_entry_above_every_ceiling_billed", "quantity_not_recorded", json!(["invoice"])), ("version_7_transmissions_energy_left_out", "invoice_mismatch", json!(["invoice"])), ("version_7_an_unmetered_transmission_left_out", "invoice_mismatch", json!(["invoice"])), ("version_7_a_line_not_citing_its_declaration", "citation_mismatch", json!(["invoice"])), ("version_7_a_publisher_outside_scope", "outside_scope", json!(["source", 0])), ("version_7_a_group_outside_the_period", "outside_period", json!(["source", 0])), ("version_7_a_group_given_twice", "record_duplicate", json!(["source", 1])), ("version_7_a_declaration_billed_again", "record_rebilled", json!(["source", 0])), ("version_7_a_group_under_version_6", "malformed", json!(["source", 0])), ("version_6_invoice_under_a_version_7_tariff", "tariff_mismatch", json!(["invoice"])), ("version_6_an_invoice_listing_a_group_receipt", "invoice_invalid", json!(["invoice"])), ("version_7_transmission_prices_out_of_order", "tariff_invalid", json!(["tariff"])), ("version_7_a_price_of_one_ceiling_twice", "tariff_invalid", json!(["tariff"])), ("version_7_a_transmission_price_on_a_relay", "tariff_invalid", json!(["tariff"])), ("version_7_a_transmission_unit_past_pass_time", "tariff_invalid", json!(["tariff"])), ("version_7_a_ceiling_of_zero", "tariff_invalid", json!(["tariff"])), ("version_7_a_ceiling_past_2_53", "tariff_invalid", json!(["tariff"])), ("version_7_a_price_past_2_53", "tariff_invalid", json!(["tariff"])), ("version_6_a_publisher_in_scope", "tariff_invalid", json!(["tariff"])), ("version_7_a_schedule_contradicted", "schedule_contradicted", json!(["source", 0, "claims", 1])), ("version_7_a_schedule_not_declared", "bearer_not_declared", json!(["source", 0, "claims", 0])), ("version_7_a_schedule_on_a_relay", "schedule_on_bidirectional", json!(["source", 0, "claims", 1])), ("version_7_declarations_contradicted", "bearer_contradicted", json!(["source", 0, "claims", 0])), ("version_7_a_schedule_of_another_group", "schedule_subject_mismatch", json!(["source", 0, "claims", 1])), ("version_7_a_chain_not_held", "parent_not_held", json!(["source", 0, "claims", 0])), ("version_7_a_chain_broken", "chain_break", json!(["source", 0, "claims", 0])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } assert_eq!(get("version_7_a_group_receipt_altered")["at"], json!(["source", 0, "group"])); assert_eq!(get("version_7_an_object_of_another_group")["at"], json!(["source", 0, "objects", 0])); } /// §4.10 to §4.12: the invoice after the month, every amount worked by hand: a broadcast whose /// schedule a later one extends, billed in the last schedule's passes alone; a node's own instance, /// an object it scheduled and did not send billing nothing; an aggregated count in its own unit; /// and each object an agent's claims name. #[test] fn version_7_bills_a_node_s_instance_an_aggregated_count_and_a_consumed_object() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); let w5 = get("version_7_week_5"); assert_eq!(w5["outcome"], "accept"); let keys = &doc["keys"]; let lines = |kind: &str, source: &Value| -> Vec<(String, u64, u64, u64, u64)> { let of = |l: &&Value| l["source_kind"] == kind && l["source"] == *source; let n = |l: &Value, k: &str| l[k].as_u64().unwrap_or(0); w5["lines"].as_array().unwrap().iter().filter(of).map(|l| (l["unit"].as_str().unwrap().to_string(), n(l, "max_bps"), n(l, "quantity"), n(l, "price"), n(l, "amount"))).collect() }; let row = |u: &str, c: u64, q: u64, p: u64, a: u64| (u.to_string(), c, q, p, a); // Five passes of 4,736 octets in 4 s, at 9,472 bit/s: the schedule of two it extends adds none. assert_eq!(lines("group", &keys["broadcaster"]), [row("bytes_sent", 1_000_000, 23_680, 2_500_000, 59), row("pass_us", 100_000, 20_000_000, 150, 3_000)]); // Objects 0 and 2 of the three the node planned: 563,200 and 2,304 bit/s. assert_eq!( lines("group", &keys["cache_node"]), [row("bytes_sent", 50_000, 1_152, 4_000_000, 4), row("bytes_sent", 2_000_000, 14_080, 6_000_000, 84), row("pass_us", 2_000_000, 200_000 + 4_000_000, 300, 1_260)] ); // Fifty reports in each of two receipts, on one line of the collector's. assert_eq!(lines("aggregated", &keys["collector"]), [row("aggregated_impressions", 0, 100, 800, 80_000)]); // Three objects and two. assert_eq!(lines("consumption", &keys["agent"]), [row("consumed_objects", 0, 5, 5_000, 25_000)]); assert_eq!(w5["amount"], 59 + 3_000 + 4 + 84 + 1_260 + 80_000 + 25_000); let cites = |kind: &str, of: u64| -> usize { let l = w5["lines"].as_array().unwrap().iter().find(|l| l["source_kind"] == kind).unwrap(); l["cites"].as_array().unwrap().iter().filter(|c| c[0] == of).count() }; assert_eq!((cites("aggregated", 24), cites("consumption", 25)), (2, 2), "each receipt and each claim"); let node = w5["lines"].as_array().unwrap().iter().find(|l| l["source"] == keys["cache_node"]).unwrap(); let by = |kind: u64| node["cites"].as_array().unwrap().iter().filter(|c| c[0] == kind).count(); assert_eq!((by(20), by(22), by(23), by(4)), (1, 1, 1, 1), "its group, its schedule, its claim and its settlement declaration"); let e = &w5["energy"].as_array().unwrap()[0]; assert_eq!((e["class"].as_str(), e["figures"].as_u64(), e["joules"].as_u64()), (Some("OnChipCounter"), Some(2), Some(2_700_000)), "each instance's figure, the publisher's and the node's"); let report = w5["reports"]["transmission"].as_array().unwrap(); assert_eq!((&report[0]["cache"], &report[1]["cache"]), (&json!(false), &json!(true))); let leaves: Vec = report[1]["entries"].as_array().unwrap().iter().map(|x| x["leaf_index"].as_u64().unwrap()).collect(); assert_eq!(leaves, [0, 2], "the object not sent is billed nowhere"); for (n, reason, at) in [ ("version_7_aggregated_billed_as_presented", "quantity_not_recorded", json!(["invoice"])), ("version_7_aggregated_billed_as_billable", "quantity_not_recorded", json!(["invoice"])), ("version_7_aggregated_over_its_count", "quantity_not_recorded", json!(["invoice"])), ("version_7_cache_billed_an_object_not_sent", "quantity_not_recorded", json!(["invoice"])), ("version_7_consumption_over_its_objects", "quantity_not_recorded", json!(["invoice"])), ("version_7_aggregated_outside_scope", "outside_scope", json!(["source", 0])), ("version_7_consumption_outside_scope", "outside_scope", json!(["source", 0])), ("version_7_aggregated_batches_overlap", "record_overlap", json!(["source", 1])), ("version_7_aggregated_billed_again", "outside_period", json!(["source", 0])), ("version_7_aggregated_billed_two_invoices_on", "outside_period", json!(["source", 0])), ("version_7_aggregated_overlap_in_the_next_invoice", "outside_period", json!(["source", 0])), ("version_7_aggregated_before_its_period", "outside_period", json!(["source", 0])), ("version_7_aggregated_past_its_period", "outside_period", json!(["source", 0])), ("version_7_aggregated_past_any_instant", "outside_period", json!(["source", 0])), ("version_7_aggregated_statement_magic", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_version", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_version_at_version_1_length", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_of_version_1_stating_a_count", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_role", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_short", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_trailing_byte", "statement_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_statement_of_the_helper", "wrong_signer", json!(["source", 0, "leader"])), ("version_7_aggregated_batch_of_no_time", "batch_invalid", json!(["source", 0, "leader"])), ("version_7_aggregated_task_of_no_precision", "task_invalid", json!(["source", 0, "task"])), ("version_7_aggregated_task_cut_short", "task_invalid", json!(["source", 0, "task"])), ("version_7_one_schedule_in_two_cache_sources", "record_duplicate", json!(["source", 1])), ("version_7_one_instance_in_two_cache_sources", "record_duplicate", json!(["source", 1])), ("version_7_one_instance_in_two_group_sources", "record_duplicate", json!(["source", 1])), ("version_7_a_node_billing_the_publishers_instance", "bearer_contradicted", json!(["source", 1])), ("version_7_a_publisher_after_a_node_on_its_instance", "bearer_contradicted", json!(["source", 1])), ("version_7_two_nodes_on_one_instance", "bearer_contradicted", json!(["source", 1])), ("version_7_a_node_s_sent_entry_past_the_period", "outside_period", json!(["source", 0])), ("version_7_consumption_billed_again", "outside_period", json!(["source", 0])), ("version_7_a_schedule_extended_after_it_was_billed", "outside_period", json!(["source", 0])), ("version_7_consumption_past_the_period", "outside_period", json!(["source", 0])), ("version_7_cache_declaration_outside_the_period", "outside_period", json!(["source", 0])), ("version_7_cache_without_its_declaration", "record_missing", json!(["source", 0, "claims", 0])), ("version_7_cache_declaration_of_another_claim", "context_mismatch", json!(["source", 0, "declarations", 0])), ("version_7_cache_declaration_by_another_key", "context_mismatch", json!(["source", 0, "declarations", 0])), ("version_7_cache_claims_contradicted", "bearer_contradicted", json!(["source", 0, "claims", 0])), ("version_7_cache_schedule_contradicted", "schedule_contradicted", json!(["source", 0, "claims", 1])), ("version_7_cache_schedule_not_declared", "bearer_not_declared", json!(["source", 0, "claims", 0])), ("version_7_cache_claim_by_the_publisher", "cache_transmission_by_publisher", json!(["source", 0, "claims", 0])), ("version_7_cache_schedule_by_another_key", "context_mismatch", json!(["source", 0, "claims", 1])), ("version_7_cache_given_a_declaration_of_the_publisher", "context_mismatch", json!(["source", 0, "claims", 0])), ("version_7_aggregated_count_wrong", "count_mismatch", json!(["source", 0, "result"])), ("version_7_aggregated_signature", "bad_signature", json!(["source", 0, "receipt"])), ("version_7_aggregated_truncated", "truncated", json!(["source", 0, "receipt"])), ("version_7_aggregated_trailing_byte", "malformed", json!(["source", 0, "receipt"])), ("version_7_aggregated_version", "unknown_version", json!(["source", 0, "receipt"])), ("version_7_aggregated_magic", "malformed", json!(["source", 0, "receipt"])), ("version_7_consumption_without_its_parent", "parent_not_held", json!(["source", 0, "claims", 0])), ("version_7_consumption_after_another_claim", "chain_break", json!(["source", 0, "claims", 0])), ("version_7_consumption_of_two_agents", "context_mismatch", json!(["source", 0, "claims", 1])), ("version_7_consumption_given_another_class", "context_mismatch", json!(["source", 0, "claims", 0])), ("version_7_consumption_of_no_claim", "malformed", json!(["source", 0])), ("version_6_an_aggregated_source", "malformed", json!(["source", 0])), ("version_6_a_consumption_source", "malformed", json!(["source", 0])), ("version_6_a_cache_source", "malformed", json!(["source", 0])), ("version_7_an_aggregated_price_past_2_53", "tariff_invalid", json!(["tariff"])), ("version_7_a_consumption_price_past_2_53", "tariff_invalid", json!(["tariff"])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } // Two sources of one group, each of an instance of its own: its receipt is cited by both, and // listed once. let two = get("version_7_one_group_on_two_instances"); assert_eq!(two["outcome"], "accept"); assert_eq!(two["reports"]["transmission"].as_array().unwrap().len(), 2); // Two groups on one broadcast service: an instance of a group is that group's. let service = get("version_7_two_groups_on_one_service"); assert_eq!(service["outcome"], "accept"); assert_eq!(service["reports"]["transmission"].as_array().unwrap().len(), 2); // A batch fills the period that holds it, from its first instant to the end of its last. let hour = get("version_7_aggregated_its_batch_is_the_period"); assert_eq!((hour["outcome"].as_str(), hour["amount"].as_u64()), (Some("accept"), Some(50 * 800))); // A leader's statement of version 1 states no rejected count, and its count bills the same. let version_1 = get("version_7_aggregated_of_statements_of_version_1"); assert_eq!((version_1["outcome"].as_str(), version_1["amount"].as_u64()), (Some("accept"), Some(50 * 800))); // A plan's entry past the period that the claim does not say was sent is no pass of what the // node bills, so it bills what the fifth week's node bills. let unsent = get("version_7_a_node_s_unsent_entry_past_the_period"); assert_eq!((unsent["outcome"].as_str(), unsent["amount"].as_u64()), (Some("accept"), Some(4 + 84 + 1_260))); } /// §4.13: a day of a holder's storage, a window and a clip, billed by byte-days from records each /// equal to what its snapshots state, rounded down once per record and once per line; the next /// day's, chained; a price of 0; a record under a byte-day; and version 7's sources beside. Each /// planted fault is refused where §4.13 and §7.2 say: byte-seconds over what the snapshots /// support, an interval overlapping the set's previous record in one invoice or the one before /// it, a snapshot after the interval, a unit mismatch, and a signature over other bytes. #[test] fn version_8_bills_storage_held_over_time_by_its_snapshots() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); let holder = &doc["keys"]["holder"]; let line = |o: &Value| -> (String, u64, u64, u64, usize) { let l = o["lines"].as_array().unwrap().iter().find(|l| l["source_kind"] == "storage").unwrap(); (l["unit"].as_str().unwrap().into(), l["quantity"].as_u64().unwrap(), l["price"].as_u64().unwrap(), l["amount"].as_u64().unwrap(), l["cites"].as_array().unwrap().len()) }; let held = |o: &Value| -> Vec { o["reports"]["storage"].as_array().unwrap().iter().map(|r| r["byte_seconds"].as_u64().unwrap()).collect() }; // Day 1: the window's 263,818,360,797.94 byte-seconds rounded down (a snapshot a microsecond // after noon), the clip's 3,047,926 bytes for eleven hours; 4,450,419 byte-days at 2,000,000 // per 10^9. Five snapshots and two records cited. let d1 = get("version_8_storage_day_1"); assert_eq!(d1["outcome"], "accept"); assert_eq!(held(&d1), [263_818_360_797, 3_047_926 * 39_600]); assert_eq!(line(&d1), ("byte_days".into(), 4_450_419, 2_000_000, 8_900, 7)); assert_eq!(d1["lines"][0]["source"], *holder); assert_eq!((d1["records"].as_u64(), d1["amount"].as_u64()), (Some(7), Some(8_900))); assert!(d1["energy"].as_array().unwrap().is_empty(), "a snapshot states no energy"); let report = &d1["reports"]["storage"][0]; assert_eq!((report["snapshots"].as_u64(), report["end"].as_u64().unwrap() - report["start"].as_u64().unwrap()), (Some(4), 86_400_000_000)); // Day 2: the window from its snapshot at the day's start, the clip until its deletion at 05:00. let d2 = get("version_8_storage_day_2"); assert_eq!(held(&d2), [481_782_182_400, 3_047_926 * 18_000]); assert_eq!(line(&d2), ("byte_days".into(), 6_211_167, 2_000_000, 12_422, 5)); assert_eq!(line(&get("version_8_storage_at_a_price_of_0")), ("byte_days".into(), 4_450_419, 0, 0, 7), "a price of 0 bills a line of amount 0"); let moment = get("version_8_storage_under_a_byte_day"); assert_eq!((moment["outcome"].as_str(), moment["lines"].as_array().unwrap().len(), moment["records"].as_u64()), (Some("accept"), 0, Some(2)), "1,000 byte-seconds: no line, its records listed"); let beside = get("version_8_bills_what_version_7_bills"); let kinds: Vec<&str> = beside["lines"].as_array().unwrap().iter().map(|l| l["source_kind"].as_str().unwrap()).collect(); assert_eq!(kinds, ["aggregated", "consumption", "storage"]); assert_eq!(line(&beside).1, 263_818_360_797 / 86_400); assert!(beside["reports"]["transmission"].as_array().unwrap().is_empty(), "version 8 states version 7's report"); for (n, reason, at) in [ // Byte-seconds over what the snapshots support, and a line over its records. ("version_8_storage_over_its_snapshots", "storage_mismatch", json!(["source", 0, "record"])), ("version_8_storage_line_over_its_records", "quantity_not_recorded", json!(["invoice"])), // An interval that overlaps the set's previous record: in one invoice, and in the one before. ("version_8_storage_overlapping_in_one_invoice", "record_overlap", json!(["source", 1])), ("version_8_storage_overlapping_the_previous_invoice", "outside_period", json!(["source", 0])), ("version_8_storage_billed_again", "outside_period", json!(["source", 0])), ("version_8_storage_outside_the_period", "outside_period", json!(["source", 0])), // A snapshot from after the interval, and the interval not covered from its start or in order. ("version_8_storage_snapshot_after_the_interval", "outside_interval", json!(["source", 0, "snapshots", 4])), ("version_8_storage_first_snapshot_after_the_start", "outside_interval", json!(["source", 0, "snapshots", 0])), ("version_8_storage_snapshots_out_of_order", "outside_interval", json!(["source", 0, "snapshots", 2])), // A unit mismatch: bytes stated as byte-seconds, bytes billed as byte-days, another unit. ("version_8_storage_bytes_as_byte_seconds", "storage_mismatch", json!(["source", 0, "record"])), ("version_8_storage_bytes_billed_as_byte_days", "quantity_not_recorded", json!(["invoice"])), ("version_8_storage_line_in_another_unit", "invoice_invalid", json!(["invoice"])), // A signature over other bytes. ("version_8_storage_snapshot_signed_over_other_bytes", "bad_signature", json!(["source", 0, "snapshots", 2])), ("version_8_storage_record_signed_over_other_bytes", "bad_signature", json!(["source", 0, "record"])), ("version_8_storage_snapshot_signed_as_a_record", "bad_signature", json!(["source", 0, "snapshots", 1])), // The snapshots given are the record's. ("version_8_storage_a_snapshot_left_out", "snapshot_mismatch", json!(["source", 0, "snapshots"])), ("version_8_storage_another_snapshot_in_its_place", "snapshot_mismatch", json!(["source", 0, "snapshots", 1])), ("version_8_storage_snapshot_of_another_set", "snapshot_mismatch", json!(["source", 0, "snapshots", 1])), ("version_8_storage_snapshot_of_another_holder", "snapshot_mismatch", json!(["source", 0, "snapshots", 1])), // Their rules and their reading. ("version_8_storage_record_of_no_time", "storage_invalid", json!(["source", 0, "record"])), ("version_8_storage_snapshot_stating_bytes_of_no_objects", "storage_invalid", json!(["source", 0, "snapshots", 0])), ("version_8_storage_record_truncated", "truncated", json!(["source", 0, "record"])), ("version_8_storage_record_version", "unknown_version", json!(["source", 0, "record"])), ("version_8_storage_not_a_record", "malformed", json!(["source", 0, "record"])), ("version_8_storage_snapshot_trailing_byte", "malformed", json!(["source", 0, "snapshots", 1])), ("version_8_storage_without_its_snapshots", "malformed", json!(["source", 0])), ("version_8_storage_given_twice", "record_duplicate", json!(["source", 1])), ("version_8_storage_outside_scope", "outside_scope", json!(["source", 0])), ("version_8_a_storage_line_not_citing_its_snapshots", "citation_mismatch", json!(["invoice"])), ("version_8_a_snapshot_left_out_of_the_records", "invoice_mismatch", json!(["invoice"])), ("version_8_a_storage_price_past_2_53", "tariff_invalid", json!(["tariff"])), ("version_7_a_holder_in_scope", "tariff_invalid", json!(["tariff"])), ("version_7_a_storage_source", "malformed", json!(["source", 0])), ("version_7_an_invoice_listing_a_storage_record", "invoice_invalid", json!(["invoice"])), ("version_7_invoice_under_a_version_8_tariff", "tariff_mismatch", json!(["invoice"])), ] { let o = get(n); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } } /// §4.14: one chain across three tariffs of version 9, each following the one before it, the /// package the first day lists billed its storage under each tariff after, beside signer 2's /// stream under the tariff that adds its key; a tariff that follows none begins a chain of its /// own; and each rule of a following tariff refuses its control at its place. #[test] fn version_9_continues_a_chain_across_a_key_made_and_retired() { use serde_json::json; let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}")).clone(); let units = |o: &serde_json::Value| -> Vec<(String, String)> { o["lines"].as_array().unwrap().iter().map(|l| (l["source_kind"].as_str().unwrap().to_string(), l["unit"].as_str().unwrap().to_string())).collect() }; let first = get("version_9_first_day"); let made = get("version_9_a_key_made"); let retired = get("version_9_a_key_retired"); let alone = get("version_9_a_tariff_following_none"); assert_eq!(first["outcome"]["follows"], serde_json::Value::Null, "the first follows none"); assert_eq!(units(&first["outcome"]), [("ads".into(), "presented_impressions".into())], "a package first listed bills no storage"); assert_eq!(made["previous"], first["invoice"], "the next tariff's first invoice names the first day's"); assert_eq!(made["outcome"]["follows"], first["outcome"]["tariff"]); assert!(units(&made["outcome"]).contains(&("package".into(), "byte_days".into())), "the package held across the tariffs"); assert_eq!(retired["outcome"]["follows"], made["outcome"]["tariff"]); assert_eq!(units(&retired["outcome"]), [("package".into(), "byte_days".into())], "held through a third tariff's period"); assert!(!units(&alone["outcome"]).contains(&("package".into(), "byte_days".into())), "a chain of its own holds nothing"); for (n, reason, at) in [ ("version_9_a_chain_restarted", "chain_restarted", json!(["previous"])), ("version_9_billed_again_across_tariffs", "record_rebilled", json!(["source", 2])), ("version_9_after_another_tariffs_invoice", "tariff_mismatch", json!(["previous"])), ("version_9_overlapping_the_chain_it_follows", "invoice_overlap", json!(["previous"])), ("version_9_a_retired_keys_records_billed", "quantity_not_recorded", json!(["invoice"])), ("version_8_invoice_under_a_version_9_tariff", "tariff_mismatch", json!(["invoice"])), ("invoice_version", "unknown_version", json!(["invoice"])), ] { let o = get(n)["outcome"].clone(); assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); } assert!(corpus::month(&committed()).is_empty(), "{:?}", corpus::month(&committed())); } /// A changed copy of a storage day is refused, whichever part changed: a byte of its invoice, a /// source left out, a snapshot left out, a snapshot's or a record's byte, or the invoice before it. #[test] fn version_8_a_changed_copy_of_a_storage_day_is_refused() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let flip = |h: &str, at: usize| -> String { let mut b: Vec = (0..h.len()).step_by(2).map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap()).collect(); let n = b.len(); b[at % n] ^= 1; b.iter().map(|x| format!("{x:02x}")).collect() }; let outcome = |d: &Value| wai::receipt_ledger::outcome_json(&wai::receipt_ledger::verify(d)); for name in corpus::STORAGE_VECTORS { let v = vectors.iter().find(|v| v["name"] == name).unwrap(); let whole = corpus::document(&doc, v).unwrap(); assert_eq!(outcome(&whole)["outcome"], "accept", "{name}"); let refused = |d: Value, what: &str| assert_eq!(outcome(&d)["outcome"], "refuse", "{name}: {what}"); let invoice = whole["invoice"].as_str().unwrap(); for at in [5, 72, 137, 150, invoice.len() / 2 - 70, invoice.len() / 2 - 1] { let mut d = whole.clone(); d["invoice"] = json!(flip(invoice, at)); refused(d, "a byte of its invoice"); } for k in 0..whole["sources"].as_array().unwrap().len() { let mut d = whole.clone(); d["sources"].as_array_mut().unwrap().remove(k); refused(d, "a source left out"); let snapshots = whole["sources"][k]["snapshots"].as_array().unwrap(); let mut d = whole.clone(); d["sources"][k]["snapshots"] = json!(snapshots[..snapshots.len() - 1]); refused(d, "its last snapshot left out"); for at in [40, 72, 80, 100] { let mut d = whole.clone(); d["sources"][k]["snapshots"][0] = json!(flip(snapshots[0].as_str().unwrap(), at)); refused(d, "a byte of its first snapshot"); let mut d = whole.clone(); d["sources"][k]["record"] = json!(flip(whole["sources"][k]["record"].as_str().unwrap(), at)); refused(d, "a byte of its record"); } } if !whole["previous"].is_null() { let mut d = whole.clone(); d["previous"] = Value::Null; refused(d, "the invoice before it not given"); } } } /// §11: the month's invoices and the two days of storage, made again from their tariff, the /// invoice before each and their sources, are the corpus's byte for byte, and every vector of /// versions 7 and 8 gets its outcome. #[test] fn version_7_the_month_recomputes_byte_for_byte() { let fails = corpus::month(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// A changed copy of the month is refused, whichever part of it changed: any byte of an invoice's /// fixed fields or signature, a source left out, a source's claim left out, the invoice before it /// not given, or another tariff's bytes. #[test] fn version_7_a_changed_copy_of_the_month_is_refused() { use serde_json::{json, Value}; let doc: Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let flip = |h: &str, at: usize| -> String { let mut b: Vec = (0..h.len()).step_by(2).map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap()).collect(); b[at] ^= 1; b.iter().map(|x| format!("{x:02x}")).collect() }; let outcome = |d: &Value| wai::receipt_ledger::outcome_json(&wai::receipt_ledger::verify(d)); for name in corpus::MONTH_VECTORS { let v = vectors.iter().find(|v| v["name"] == name).unwrap(); let whole = corpus::document(&doc, v).unwrap(); assert_eq!(outcome(&whole)["outcome"], "accept", "{name}"); let refused = |d: Value, what: &str| assert_eq!(outcome(&d)["outcome"], "refuse", "{name}: {what}"); let invoice = whole["invoice"].as_str().unwrap(); for at in [5, 40, 72, 100, 137, 150, invoice.len() / 2 - 40, invoice.len() / 2 - 1] { let mut d = whole.clone(); d["invoice"] = json!(flip(invoice, at)); refused(d, "a byte of its invoice"); } let mut d = whole.clone(); d["tariff"] = json!(flip(whole["tariff"].as_str().unwrap(), 12)); refused(d, "a byte of its tariff"); for k in 0..whole["sources"].as_array().unwrap().len() { let mut d = whole.clone(); d["sources"].as_array_mut().unwrap().remove(k); refused(d, "a source left out"); if let Some(claims) = whole["sources"][k]["claims"].as_array() { let mut d = whole.clone(); d["sources"][k]["claims"] = json!(claims[..claims.len() - 1]); refused(d, "a source's last claim left out"); } } if !whole["previous"].is_null() { let mut d = whole.clone(); d["previous"] = Value::Null; refused(d, "the invoice before it not given"); } } } /// §4.11: the ledger reads an aggregate receipt as data, so the corpus holds what it bills to the /// text that verifies it: each receipt is accepted by impression-aggregates §8.1 against its /// catalogue, and the line's quantity is the sum of the flight's table's presentations. #[cfg(feature = "impression_aggregate")] #[test] fn each_aggregate_receipt_billed_stands_by_its_own_text() { use serde_json::Value; let unhex = |v: &Value| -> Vec { let s = v.as_str().unwrap(); (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() }; let doc: Value = serde_json::from_str(&committed()).unwrap(); let flights: Value = serde_json::from_str(&read("live-conformance/impressions/vectors.json")).unwrap(); let catalogue = unhex(&flights["fixtures"]["catalogues"]["flight"]); let mut count = 0; for name in ["aggregated_r0", "aggregated_r12"] { let v = wai::impression_aggregate::verify_receipt(&unhex(&doc["sources"][name]["receipt"]), None, &catalogue).unwrap_or_else(|e| panic!("{name}: {e:?}")); assert_eq!(v.receipt.collector.to_vec(), unhex(&doc["keys"]["collector"]), "the collector the tariff names"); count += v.receipt.report_count; } let w5 = doc["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "version_7_week_5").unwrap(); let line = w5["outcome"]["lines"].as_array().unwrap().iter().find(|l| l["source_kind"] == "aggregated").unwrap(); assert_eq!(line["quantity"].as_u64(), Some(count)); // What the ledger does not read: a receipt signed again with a wrong share reads here, and // its own text refuses it, which is why the buyer is given the receipt. let wrong = flights["receipts"].as_array().unwrap().iter().find(|r| r["name"] == "count_mismatch").unwrap(); assert!(wai::impression_aggregate::verify_receipt(&unhex(&wrong["receipt"]), None, &catalogue).is_err()); } #[test] fn the_extension_lists_the_reasons() { let text = read("extensions/receipt-ledger.md"); let at = text.find("\n## 10. Reasons").expect("a §10"); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); assert_eq!(listed, want, "§10's table and Reason::ALL differ"); } /// §5.2 is the one registry of record kinds: its rows are the kinds 1 to the last, each once and /// in order; the sentence after them names the first kind left unassigned; and the reference's /// constants are distinct, each a row, the last of them the last row. Several texts assign kinds /// at once, each the next free one, so a row added without its constant, or two texts taking one /// number, fails here. #[test] fn section_5_2_registers_every_kind_once_and_in_order() { use wai::receipt_ledger::record_kind as k; let text = read("extensions/receipt-ledger.md"); let at = text.find("\n### 5.2 What a source contributes").expect("a §5.2"); let end = text[at + 1..].find("\n### ").map_or(text.len(), |e| at + 1 + e); let section = &text[at..end]; let rows: Vec = section .lines() .filter_map(|l| l.strip_prefix("| ")) .filter_map(|l| l.split(" |").next()) .filter_map(|n| n.trim().parse().ok()) .collect(); let want: Vec = (1..=rows.len() as u32).collect(); assert_eq!(rows, want, "§5.2's rows are not the kinds 1 to the last, each once and in order"); let last = *rows.last().expect("a row"); assert!(section.contains(&format!("Kinds {} to 127 are unassigned.", last + 1)), "§5.2 names another first unassigned kind than {}", last + 1); let constants = [ k::SESSION, k::HOP, k::STATEMENT, k::ACK, k::PACKAGE, k::AD_RECEIPT, k::CLAIM, k::POD_INPUT, k::STITCHED_CLAIM, k::RUN, k::INVOICE, k::CUE, k::ROOM_POLICY, k::OER_RECEIPT, k::VIDEO_RECEIPT, k::ROUTE_RECORD, k::THIRD_PARTY, k::UPSCALE_RECORD, k::RENDITION_RECORD, k::GROUP_RECEIPT, k::TRANSMISSION_CLAIM, k::SCHEDULE_CLAIM, k::CACHE_CLAIM, k::AGGREGATE_RECEIPT, k::CONSUMPTION_CLAIM, k::STORAGE_RECORD, k::SNAPSHOT, k::SINK_SESSION, k::SINK_SEGMENT, k::SPLIT_CLAIM, k::FACTOR_TABLE, k::CERT_REFUSAL, k::CERT_PUBLICATION, k::AUDIT_ENTRY, k::AUDIT_CHECKPOINT, k::DATA_EXPORT, k::DATA_DELETION, k::PRICE_DOCUMENT, k::HLS_WINDOW, k::HLS_CLIP, k::OPPORTUNITY_REPORT, k::STATEMENT_CHECK, k::ORIGIN_CLAIM, k::MARKING, ]; let distinct: BTreeSet = constants.iter().copied().collect(); assert_eq!(distinct.len(), constants.len(), "two constants of record_kind name one kind"); let have: Vec = distinct.iter().map(|&c| u32::from(c)).collect(); assert_eq!(have, want, "record_kind's constants and §5.2's rows differ"); assert!(u32::from(k::PRIVATE) == 128 && last < 128, "the private kinds begin at 128, above every row"); }