open-standards/wai/wai-rs/tests/receipt_ledger.rs
Transaction Science e31be30b30 Record kinds 43 and 44: an origin claim and a marking, named by WAI's numbers
receipt-ledger §5.2 registers kind 43, an origin claim (wai-origin), and kind 44, a marking
(wai-marking), each named by its link hash (synthesis §2) and no ledger source's, so a record that
names what an output carried by a kind, a data export's record lines among them (data-export
§3.2), names them by WAI's numbers rather than a private one. synthesis §2 states the two kinds;
kinds 45 to 127 are unassigned. The reference's constants are record_kind::ORIGIN_CLAIM and
MARKING, held to §5.2's table by section_5_2_registers_every_kind_once_and_in_order and
receipt_ledger_verify.py's kinds_registered. The two records' formats, verifiers and corpora are
unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 04:43:14 -05:00

932 lines
64 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! `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<String> = 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<String> { 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::<u64>(), 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<Value> { 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<String> { 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<Value> { 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::<u64>(), 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::<u64>(), 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<u8> { (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<u64> = 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<u64> { 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<u8> = (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<u8> = (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<u8> {
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<String> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
let want: Vec<String> = 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<u32> = 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<u32> = (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<u8> = constants.iter().copied().collect();
assert_eq!(distinct.len(), constants.len(), "two constants of record_kind name one kind");
let have: Vec<u32> = 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");
}