open-standards/wai/wai-wasmq/tests/sweep.rs
Transaction Science e52bf1154f
Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 34s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 38s
WAI operation catalogue / does this change touch it (push) Successful in 29s
wai-fleet service / does this change touch it (push) Successful in 28s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 48s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 1m9s
wai-quantum command line / does this change touch it (push) Successful in 53s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 1m38s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 14s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m24s
wai-quantum editors / does this change touch it (push) Successful in 58s
wai-quantum contract / does this change touch it (push) Successful in 1m13s
wai-ledger service / does this change touch it (push) Successful in 1m14s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m25s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 15s
wai-quantum resource estimation / does this change touch it (push) Successful in 23s
wai-quantum for JavaScript / does this change touch it (push) Successful in 16s
wai-quantum other formats / does this change touch it (push) Successful in 15s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 24s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 29s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 43s
wai-quantum service / does this change touch it (push) Successful in 23s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 35s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 22s
wai-quantum service / service energy · linux packages (push) Failing after 17s
wai-sink-swift native sink package / does this change touch it (push) Successful in 28s
WAI Studio / does this change touch it (push) Successful in 24s
wai-quantum wasmQ / does this change touch it (push) Successful in 23s
wai-quantum for Python / does this change touch it (push) Successful in 1m30s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m3s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 1m24s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 1m13s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 1m9s
wai-sink-android native sink library / does this change touch it (push) Successful in 1m9s
wai-quantum extension points / does this change touch it (push) Successful in 1m22s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Failing after 2m14s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m17s
wai-fleet service / service · macos (push) Successful in 2m29s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 2m36s
wai-quantum editors / editors · linux (push) Successful in 3m6s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 54s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 2m47s
wai-quantum command line / command line · macos (push) Successful in 4m54s
wai-quantum contract / API compatibility · linux (push) Successful in 4m45s
wai-audit collector and player integration / collector · linux (push) Successful in 4m55s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 4m30s
wai-fleet service / service · linux (push) Successful in 6m0s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 3m23s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 5m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 8m14s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 1m29s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 1m49s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 4m51s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 6m43s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 2m0s
wai-ledger service / service · linux (push) Successful in 2m9s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Successful in 5m1s
wai-quantum editors / editors · macos (push) Successful in 2m15s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 7m23s
wai-ledger service / service · macos (push) Successful in 1m37s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 54s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 1m8s
wai-quantum for JavaScript / javascript · linux (push) Successful in 6m21s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 2m58s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 31s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 4m19s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Successful in 2m31s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 8m45s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Successful in 3m53s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 3m49s
wai-quantum service / service · macos (push) Failing after 4m1s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 11m53s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Successful in 3m33s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Successful in 2m0s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 10m6s
wai-quantum for Python / python · linux (push) Successful in 4m31s
wai-quantum views in agent hosts / views · the page and its engine (push) Successful in 4m42s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 13m30s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 5m22s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Successful in 2m21s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 15m23s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 4m18s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m55s
wai-quantum extension points / the registry and the example · linux (push) Successful in 6m54s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 8m4s
wai-quantum contract / schemas and the committed tool lists · linux (push) Successful in 16m13s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 15m19s
wai-quantum views in agent hosts / views · a scripted host (push) Successful in 7m21s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 17m3s
wai-quantum service / service · linux (push) Successful in 11m54s
wai-quantum for Python / python · macos (push) Successful in 4m45s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 5m10s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 10m35s
WAI Studio / Studio in WebKit · linux (push) Successful in 12m22s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 20m38s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 10m5s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 5m51s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 23m18s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 20m39s
wai-quantum command line / command line · linux (push) Successful in 29m19s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 41m7s
wasmQ/2: the whole-number state vector, angle arguments, and the run's energy in the receipt
The profile's second version, wai.quantum.wasmq/2 (extensions/quantum-wasmq.md §11),
beside the first, which runs, seals and verifies as it did: its 83 vectors are unchanged.

- The method `exact`: t, tdg, p, rz, rx, ry, ch, cp, crz, rzz, ccx, ccz and cswap beside
  the Clifford gates, on the engine's whole-number state vector (circuit2's arithmetic and
  phase table), the same bits on every host. An angle is the engine's dyadic reading of
  the reader's double, taken only within the double's own rounding (qasm::dyadic_exact);
  a measurement draws 1 when floor(x*W/2^64) < W1, exactly, and renormalises in whole
  numbers. At most 26 qubits: 27 cannot be one allocation on a 32-bit host, and the
  largest bundle at 26 runs under wasm32 beside every other bound (2.1 GB resident).
  A gate weighs 8 units of §4.8 an amplitude, from measured times (H: 20.7 ns an
  amplitude under wasm32 on x86_64, 11.0 on arm64), so a run at the bound ends in
  about three hours on either.
- Angle arguments: `angle[k]` (k <= 64) passed as the m of 2*pi*m/2^k, a u64, exactly or
  refused; no float crosses into a module.
- Energy: a /2 result seals the run's figure (a reading where the host meters it, the
  unmetered form with why where it does not, never 0 J) and each callout's (unmetered:
  no meter resolves one call). `wai-quantum run` runs a /2 envelope in a worker process
  and seals its whole reading (macOS process CPU energy, Linux packages). Rule 36 holds
  the form; engine::wasmq2 holds a /2 job to rules 1-36 for every surface that verifies.
- Vectors v2-* (6 runs, 8 refusals): one digest on macOS arm64 and Linux x86_64, each
  native on three engines and under wasm32-wasip2: 3567857873430427cf50589739321b7c435
  80d549efbb6f461789135e386980b.
- wai.quantum.wasmq/2 is now a defined contract revision in the registry, and
  wai.quantum.wasmq.result/2 its result's format; a /1 job that names /2 still breaks
  rule 4 or 11, since the result's format decides the rules.

Also: tests/reading.rs counts one reading at a time, since its two tests shared the
process's allocation counters and could count each other's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-10 13:39:33 -05:00

796 lines
44 KiB
Rust

//! What a sweep of the review's families found beyond the review (extensions/quantum-wasmq.md):
//! bundles a host could not hold or finish, which fuel does not meter; a job signed by any
//! key whose result this profile never writes; records that replay though no run made them.
use serde_json::{Value, json};
use wai_quantum::ed25519_dalek::SigningKey;
use wai_wasmq::envelope::{Bundle, Callout, Module, Run, open, pack};
use wai_wasmq::run::{Call, EngineKind, replay, run};
fn wasm(wat: &str) -> Vec<u8> {
wat::parse_str(wat).unwrap()
}
fn kinds() -> Vec<EngineKind> {
wai_wasmq::engine::available().iter().map(|s| EngineKind::parse(s).unwrap()).collect()
}
const IDENTITY: &str = r#"(module (func (export "f") (param i64) (result i64) local.get 0))"#;
const SPIN: &str = r#"(module (func (export "f") (param i64) (result i64) loop br 0 end i64.const 0))"#;
fn one(program: String, module: Vec<u8>, shots: u64) -> Bundle {
Bundle {
program,
inputs: Default::default(),
modules: vec![Module { name: "m".into(), bytes: module, priors: vec![] }],
callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }],
run: Run::new(shots, 1),
}
}
/// A program of `qubits` qubits and `bits` bits in all, with `body` after a call.
fn program(qubits: u64, bits: u64, body: &str) -> String {
let rest = if bits > 1 { format!("bit[{}] c;\n", bits - 1) } else { String::new() };
format!("OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nqubit[{qubits}] q;\nbit[1] d;\n{rest}d = f(d);\n{body}")
}
fn code(b: &Bundle) -> Option<&'static str> {
pack(b).err().map(|e| e.code)
}
// ---------------------------------------------------------------------------
// What fuel does not meter: a host must be able to hold and finish what it opens
// ---------------------------------------------------------------------------
/// `n` modules of `pages` pages each, one callout for each.
fn modules_of(n: usize, pages: u64) -> Bundle {
let mut p = String::from("OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[1] q;\nbit[1] c;\n");
let bytes = wasm(&format!(r#"(module (memory (export "memory") {pages} {pages}) (func (export "f") (param i64) (result i64) local.get 0))"#));
let (mut modules, mut callouts) = (vec![], vec![]);
for i in 0..n {
p.push_str(&format!("extern f{i}(bit[1]) -> bit[1];\n"));
}
for i in 0..n {
p.push_str(&format!("c = f{i}(c);\n"));
modules.push(Module { name: format!("m{i}"), bytes: bytes.clone(), priors: vec![] });
callouts.push(Callout { name: format!("f{i}"), module: i, export: "f".into() });
}
Bundle { program: p, inputs: Default::default(), modules, callouts, run: Run::new(1, 1) }
}
#[test]
fn the_code_among_a_bundle_s_modules_is_bounded() {
// The interpreter holds a module an instruction at a time, some fifty bytes each, and
// the native engine compiles each prepared body whole: thirty modules of the most bytes
// each, one envelope, would hold gigabytes on any engine (§3.2).
let of = |bytes: usize| {
let pad = format!("(data (i32.const 0) \"{}\")", "a".repeat(bytes));
format!(r#"(module (memory 8 8) {pad} (func (export "f") (param i64) (result i64) local.get 0) (func (export "g") (param i64) (result i64) local.get 0))"#)
};
let two = |bytes: usize| {
let mut b = Bundle {
program: "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nextern g(bit[1]) -> bit[1];\nqubit[1] q;\nbit[1] d;\nd = f(d);\nd = g(d);\n".into(),
inputs: Default::default(),
modules: vec![Module { name: "a".into(), bytes: wasm(&of(bytes)), priors: vec![] }, Module { name: "b".into(), bytes: wasm(&of(bytes)), priors: vec![] }],
callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }, Callout { name: "g".into(), module: 1, export: "g".into() }],
run: Run::new(1, 1),
};
b.run.per_init = 1 << 24;
b
};
let half = wai_wasmq::envelope::MAX_CODE_BYTES / 2;
assert_eq!(code(&two(half - 200)), None, "two modules of half the bytes each");
assert_eq!(code(&two(half + 200)), Some("W010"), "two modules each within a module's bytes, past a bundle's together");
}
#[test]
fn the_modules_of_a_bundle_hold_no_more_memory_among_them_than_one_may() {
// Twenty modules of 256 MiB sealed `ok` on a native host, and on wasm32, whose address
// space cannot hold them, ended with no receipt: two outcomes for one envelope.
assert_eq!(code(&modules_of(20, 4096)), Some("W003"), "twenty modules of the most memory one may have");
assert_eq!(code(&modules_of(2, 2049)), Some("W003"), "4098 pages among two");
assert_eq!(code(&modules_of(2, 2048)), None, "4096 pages among two");
}
/// One bit of the program's register measured.
const ONE_MEASURED: &str = "c[0] = measure q[0];\n";
#[test]
fn a_bundle_whose_result_a_host_could_not_hold_is_refused() {
// A register of a million bits and 4096 shots: under 900 bytes, a job of 4 GB and 15 GB
// of memory on a native host, and on wasm32 the process aborts.
assert_eq!(code(&one(program(17, 1_000_000, ONE_MEASURED), wasm(IDENTITY), 4096)), Some("W017"));
// Shots times bits, at most 2^28.
assert_eq!(code(&one(program(1, 1 << 16, ONE_MEASURED), wasm(IDENTITY), 1 << 12)), None, "2^28 bits of results");
assert_eq!(code(&one(program(1, (1 << 16) + 1, ONE_MEASURED), wasm(IDENTITY), 1 << 12)), Some("W017"), "one bit more in each shot");
assert_eq!(code(&one(program(1, 1 << 20, ONE_MEASURED), wasm(IDENTITY), 1 << 8)), None, "2^28 bits, of the most bits a program holds");
assert_eq!(code(&one(program(1, 1 << 20, ONE_MEASURED), wasm(IDENTITY), (1 << 8) + 1)), Some("W017"), "a shot more");
// And a program holds at most 2^20 bits: past that its reader refuses it.
assert_eq!(code(&one(program(1, (1 << 20) + 1, ONE_MEASURED), wasm(IDENTITY), 1)), Some("W016"), "a bit more in a program");
}
#[test]
fn a_run_no_host_could_finish_is_refused_though_it_spends_no_fuel() {
// A measurement at the most qubits takes the host some milliseconds and no fuel: these
// would take a host far longer than anyone would wait, and it would seal nothing until then.
let measures = "h q;\nfor uint i in [0:99999] { c[0] = measure q[0]; }\n";
assert_eq!(code(&one(program(32768, 2, measures), wasm(IDENTITY), 1 << 20)), Some("W017"));
// A fresh tableau at the most qubits, each shot.
assert_eq!(code(&one(program(32768, 2, ONE_MEASURED), wasm(IDENTITY), 1 << 20)), Some("W017"), "the tableaux");
// A module's memory is made again each shot, and once for a run of one instance.
let mut b = one(program(1, 2, ""), wasm(r#"(module (memory (export "memory") 4096 4096) (func (export "f") (param i64) (result i64) local.get 0))"#), 1 << 20);
assert_eq!(code(&b), Some("W017"), "the memory of a million instances");
b.run.state = "job".into();
assert_eq!(code(&b), None, "the memory of one instance, for a million shots");
b.run.state = "shot".into();
b.run.shots = 1 << 10;
assert_eq!(code(&b), None, "the memory of a thousand instances");
// Even once, a hundred thousand of them at the most qubits.
assert_eq!(code(&one(program(32768, 2, measures), wasm(IDENTITY), 1)), Some("W017"), "these measurements, once");
// And programs of the same kinds at a size a host finishes pack.
let fewer = "h q;\nfor uint i in [0:1999] { c[0] = measure q[0]; }\n";
assert_eq!(code(&one(program(32768, 2, fewer), wasm(IDENTITY), 1)), None, "two thousand measurements, once");
assert_eq!(code(&one(program(32768, 2, ONE_MEASURED), wasm(IDENTITY), 1 << 11)), None, "these tableaux, two thousand times");
// A call is the host's work too, whatever fuel it uses.
let calls = "for uint i in [0:1999] { d = f(d); }\nc[0] = measure q[0];\n";
assert_eq!(code(&one(program(1, 2, calls), wasm(IDENTITY), 1 << 20)), Some("W017"), "two thousand calls, a million times");
assert_eq!(code(&one(program(1, 2, calls), wasm(IDENTITY), 1 << 10)), None, "two thousand calls, a thousand times");
}
#[test]
fn memory_a_module_grows_is_paid_for_in_fuel_as_memory_it_starts_with_is_in_the_work() {
// Declared at no pages and grown to the most in `init`, each instance zeroes as much as
// one that starts with it: fuel pays for each page grown as the work counts each page
// a module starts with, so a run's budget bounds the one as the work bounds the other.
let grows = r#"(module (memory (export "memory") 0 4096)
(func (export "init") i32.const 4096 memory.grow drop)
(func (export "f") (param i64) (result i64) local.get 0))"#;
let starts = r#"(module (memory (export "memory") 4096 4096) (func (export "f") (param i64) (result i64) local.get 0))"#;
assert_eq!(code(&one(program(1, 2, ONE_MEASURED), wasm(starts), 1 << 20)), Some("W017"), "a million instances that start with the most memory");
let o = open(&pack(&one(program(1, 2, ONE_MEASURED), wasm(grows), 4)).unwrap()).unwrap();
let key = SigningKey::from_bytes(&[7; 32]);
for k in kinds() {
let (job, _) = run(&o, k, &key, false).unwrap();
assert!(job["result"]["fuel"]["instances"].as_u64().unwrap() >= 4 * 4096 * wai_wasmq::meter::PAGE_FUEL, "{k:?}: {}", job["result"]["fuel"]);
}
// A million of them pack: their budget, which the work counts, runs out first.
let b = one(program(1, 2, ONE_MEASURED), wasm(grows), 1 << 20);
assert_eq!(code(&b), None);
assert!(u128::from(b.run.per_job) < 1 << 20 << 22, "the run's budget pays for fewer than a million instances' growth");
}
#[test]
fn a_run_s_fuel_is_bounded_with_its_work() {
// Fuel is the modules' work, and a host runs all of it: a budget of 2^53 is months of
// a loop that never ends, before the fuel fault is sealed.
let mut b = one(program(1, 2, ONE_MEASURED), wasm(SPIN), 1);
b.run.per_call = (1 << 53) - 1;
b.run.per_job = (1 << 53) - 1;
assert_eq!(code(&b), Some("W017"), "the most fuel a receipt can bind");
// The budgets a run is given by default pack, and so does the most fuel the work allows.
assert_eq!(code(&one(program(1, 2, ONE_MEASURED), wasm(SPIN), 1)), None, "the default budgets");
// Work of exactly the limit: gates of one each, so that the rest is whole units of fuel.
use wai_wasmq::envelope::{FUEL_WORK, MAX_HOST_WORK, host_work};
let facts = [wai_wasmq::profile::check(&wasm(SPIN)).unwrap()];
let with = |gates: u128| one(program(1, 2, &format!("{ONE_MEASURED}{}", "x q[0];\n".repeat(gates as usize))), wasm(SPIN), 1);
let work = |b: &Bundle| {
let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap();
host_work(&h, &facts, b)
};
let without_fuel = |b: &Bundle| work(&Bundle { run: Run { per_job: 0, ..b.run.clone() }, ..b.clone() });
let mut most = with((MAX_HOST_WORK - without_fuel(&with(0))) % FUEL_WORK);
most.run.per_job = ((MAX_HOST_WORK - without_fuel(&most)) / FUEL_WORK) as u64;
most.run.per_call = most.run.per_job;
assert_eq!(work(&most), MAX_HOST_WORK, "work of exactly the limit");
assert_eq!(code(&most), None, "the most fuel the work allows");
most.run.per_job += 1;
assert_eq!(code(&most), Some("W017"), "one more");
}
#[test]
fn a_callout_s_work_counts_every_call_into_its_module() {
// A list argument is a call of cabi_realloc, the export is one, and cabi_post another: a
// callout of three list arguments with a post call is five calls of the host's.
let m = wasm(r#"(module (memory (export "memory") 1 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024)
(func (export "f") (param i32 i32 i32 i32 i32 i32) (result i32) i32.const 2048)
(func (export "cabi_post_f") (param i32)))"#);
let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[65], bit[65], bit[65]) -> bit[65];\nqubit[1] q;\nbit[65] a;\nbit[65] r;\nr = f(a, a, a);\n";
let b = one(src.into(), m, 1);
let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap();
let facts = [wai_wasmq::profile::check(&b.modules[0].bytes).unwrap()];
let one_call = wai_wasmq::envelope::host_work(&h, &facts, &b);
let mut twice = b.clone();
twice.program = src.replace("r = f(a, a, a);", "r = f(a, a, a);\nr = f(a, a, a);");
let (h2, _) = wai_wasmq::envelope::read(&twice.program, &twice.inputs).unwrap();
let call = wai_wasmq::envelope::host_work(&h2, &facts, &twice) - one_call;
assert_eq!(call, 5 * wai_wasmq::envelope::CALL_WORK + 4 * 65 * wai_wasmq::envelope::BIT_WORK + 1, "five calls of the host's and 260 bits, and the step");
// A prior is two: cabi_realloc and wasmq-prior.
let p = wasm(TAKES_PRIORS);
let mut with = one(program(1, 1, ""), p.clone(), 1);
let facts = [wai_wasmq::profile::check(&p).unwrap()];
let (h, _) = wai_wasmq::envelope::read(&with.program, &with.inputs).unwrap();
let none = wai_wasmq::envelope::host_work(&h, &facts, &with);
with.modules[0].priors.push(("p".into(), vec![0; 64]));
assert_eq!(wai_wasmq::envelope::host_work(&h, &facts, &with) - none, 2 * wai_wasmq::envelope::CALL_WORK);
}
#[test]
fn the_work_a_run_asks_of_its_host_is_the_text_s_sum() {
use wai_wasmq::envelope::{BIT_WORK, CALL_WORK, CODE_WORK, FUEL_WORK, MEASURE_WORK};
// §4.8, by hand: 64 qubits take r = 2 words a qubit. For a shot: a tableau of
// 2 * 64 * 2; the call, one of the host's with its two bits and its step; a gate of 2,
// twice; the measurement, 32 * 64 * 2; the jump, 1. For an instance: making it, one
// call, its module's bytes, and three pages of 8192. For the run: its module's bytes,
// compiled, and the fuel it may be given.
let src = program(64, 2, "x q[0];\nc[0] = measure q[0];\nif (c[0]) x q[1];\n");
let m = wasm(r#"(module (memory (export "memory") 3 9) (func (export "f") (param i64) (result i64) local.get 0))"#);
let (h, _) = wai_wasmq::envelope::read(&src, &Default::default()).unwrap();
let work = |b: &Bundle| wai_wasmq::envelope::host_work(&h, &[wai_wasmq::profile::check(&b.modules[0].bytes).unwrap()], b);
let shot = 2 * 64 * 2 + (CALL_WORK + 2 * BIT_WORK + 1) + 2 + MEASURE_WORK * 64 * 2 + 1 + 2;
let len = m.len() as u128;
let instance = CALL_WORK + len + 3 * 8192;
let mut b = one(src.clone(), m, 5);
let run = CODE_WORK * len + FUEL_WORK * u128::from(b.run.per_job);
assert_eq!(work(&b), 5 * shot + 5 * instance + run, "an instance for each shot");
b.run.state = "job".into();
assert_eq!(work(&b), 5 * shot + instance + run, "one instance for the run");
// A seed, an init and a prior are four more calls: a prior is cabi_realloc and wasmq-prior.
let m = wasm(r#"(module (memory (export "memory") 3 9)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024)
(func (export "wasmq-prior") (param i32 i32 i32))
(func (export "wasmq-seed") (param i64))
(func (export "init"))
(func (export "f") (param i64) (result i64) local.get 0))"#);
let len = m.len() as u128;
let mut b = one(src, m, 5);
b.modules[0].priors.push(("p".into(), vec![0; 17]));
assert_eq!(work(&b), 5 * shot + 5 * (5 * CALL_WORK + len + 3 * 8192) + CODE_WORK * len + FUEL_WORK * u128::from(b.run.per_job));
}
#[test]
fn the_work_limit_admits_its_largest_and_refuses_one_more() {
// A million shots of an instance of 480 pages, and of gates of one word each, as many
// as the work leaves room for.
let m = wasm(r#"(module (memory (export "memory") 480 480) (func (export "f") (param i64) (result i64) local.get 0))"#);
let with = |gates: u64| one(program(1, 2, &format!("c[0] = measure q[0];\nfor uint i in [0:{}] {{ x q[0]; }}\n", gates - 1)), m.clone(), 1 << 20);
let facts = [wai_wasmq::profile::check(&m).unwrap()];
let work = |g: u64| {
let b = with(g);
let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap();
wai_wasmq::envelope::host_work(&h, &facts, &b)
};
let per_gate = work(2) - work(1);
assert_eq!(per_gate, 1 << 20, "a gate of one word, in each of a million shots");
let most = 1 + ((wai_wasmq::envelope::MAX_HOST_WORK - work(1)) / per_gate) as u64;
assert!(most > 1000 && work(most) <= wai_wasmq::envelope::MAX_HOST_WORK && work(most + 1) > wai_wasmq::envelope::MAX_HOST_WORK);
assert_eq!(code(&with(most)), None, "the most the work admits");
assert_eq!(code(&with(most + 1)), Some("W017"), "a gate more in each shot");
}
#[test]
fn the_steps_of_a_program_are_bounded_at_their_limit() {
// A call and its loop's body: a step each. The program's first call makes one more.
let of = |calls: u64| one(program(1, 2, &format!("for uint i in [0:{}] {{ d = f(d); }}\nc[0] = measure q[0];\n", calls - 1)), wasm(IDENTITY), 1);
let room = (1 << 21) - 2;
assert_eq!(code(&of(room)), None, "2^21 steps");
assert_eq!(code(&of(room + 1)), Some("W016"), "one more");
// Steps of jumps alone past the bound: an `if` and an `else` with nothing in them are
// two jumps, which no step of a gate, a measurement or a call counts as it is made.
let jumps = |calls: u64| one(program(1, 2, &format!("for uint i in [0:{}] {{ d = f(d); }}\nc[0] = measure q[0];\nif (d == 1) {{ }} else {{ }}\n", calls - 1)), wasm(IDENTITY), 1);
let (h, _) = wai_wasmq::envelope::read(&jumps(room - 2).program, &Default::default()).unwrap();
assert_eq!(h.steps.len(), 1 << 21, "the empty branches are two steps");
let past = jumps(room - 1);
let e = pack(&past).unwrap_err();
assert!(e.code == "W016" && e.message.contains("steps; it is held to"), "{e}");
}
/// A module that takes priors, so that a long prior is an envelope's only defect.
const TAKES_PRIORS: &str = r#"(module (memory (export "memory") 1 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024)
(func (export "wasmq-prior") (param i32 i32 i32))
(func (export "f") (param i64) (result i64) local.get 0))"#;
fn with_prior(bytes: usize) -> Bundle {
let mut b = one(program(1, 1, ""), wasm(TAKES_PRIORS), 1);
b.modules[0].priors.push(("long".into(), vec![0; bytes]));
b
}
#[test]
fn an_envelope_past_what_a_host_must_hold_is_refused() {
// A reader holds an envelope's bytes and its frames at once, so what one host opens
// another must be able to.
// Frames of the whole limit are refused by the check itself, before they are written.
let whole = with_prior(1 << 27);
assert_eq!(wai_wasmq::envelope::check(&whole).err().map(|e| e.code), Some("W013"), "frames of the whole limit");
assert_eq!(code(&whole), Some("W013"));
// Frames just within it, and with the contract and the manifest past it: the reader's.
let rest = with_prior(0);
let others = rest.program.len() + rest.modules[0].bytes.len();
let just_within = with_prior((1 << 27) - 1 - others);
assert!(wai_wasmq::envelope::check(&just_within).is_ok(), "the frames alone fit");
assert_eq!(code(&just_within), Some("W013"), "the envelope they make does not");
let env = pack(&with_prior((1 << 27) - (1 << 16))).unwrap();
assert!(env.len() <= 1 << 27);
assert!(open(&env).is_ok(), "an envelope within the limit");
}
/// A module of the most memory and the most code a bundle holds: the exports a prior
/// needs, `f`, and functions of one-byte instructions up to the bundle's bytes of module,
/// which the interpreter holds an instruction at a time.
fn the_most_code() -> Vec<u8> {
let head = r#"(memory (export "memory") 4096 4096)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024)
(func (export "wasmq-prior") (param i32 i32 i32))
(func (export "f") (param i64) (result i64) local.get 0)"#;
let filler = format!("(func i32.const 0 {}drop)", "i32.eqz ".repeat(wai_wasmq::profile::MAX_BODY_BYTES as usize - 8));
let mut fillers = 0;
loop {
let m = wasm(&format!("(module {head} {})", filler.repeat(fillers + 1)));
if m.len() > wai_wasmq::envelope::MAX_CODE_BYTES {
let m = wasm(&format!("(module {head} {})", filler.repeat(fillers)));
assert!(m.len() > wai_wasmq::envelope::MAX_CODE_BYTES - (20 << 10), "{} bytes", m.len());
return m;
}
fillers += 1;
}
}
/// A bundle at every limit a run holds in memory at once: the most memory and code, a
/// prior of the rest an envelope holds, the most qubits and bits, and a determined
/// measurement; with `calls` calls that take and give two bits each.
fn the_largest(calls: u64) -> wai_wasmq::Opened {
let src = format!(
"OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[2]) -> bit[2];\nqubit[32768] q;\nbit[2] d;\nbit[{}] c;\nd = f(d);\nh q[0];\nc[0] = measure q[0];\nreset q[0];\nfor uint i in [0:{}] {{ d = f(d); }}\n",
(1 << 20) - 2,
calls - 1
);
let m = the_most_code();
let room = wai_wasmq::envelope::MAX_ENVELOPE_BYTES - m.len() - src.len() - (8 << 10);
let mut b = one(src, m, 2);
b.modules[0].priors.push(("long".into(), vec![1; room]));
// Fuel counts a prior's bytes: the budget lets it through, so that the run goes on to
// hold everything else at once, as it would not if it stopped at the prior.
b.run.per_init = 1 << 28;
open(&pack(&b).unwrap()).unwrap()
}
#[test]
fn the_largest_bundle_the_limits_admit_runs_on_every_engine() {
let o = the_largest(1);
let key = SigningKey::from_bytes(&[7; 32]);
let jobs: Vec<Value> = kinds().into_iter().map(|k| run(&o, k, &key, false).unwrap().0).collect();
assert_eq!(jobs[0]["result"]["status"], "ok", "{}", jobs[0]["result"]["fault"]);
assert!(jobs.iter().all(|j| j == &jobs[0]), "the same job on every engine");
}
#[test]
fn the_largest_bundle_with_the_most_steps_fits_a_32_bit_host() {
// And with the most steps and the most bits of calls as well, on the interpreter, which
// is the engine wasm32 has.
let calls = (1u64 << 21) - 8;
assert!(4 * (calls + 1) <= wai_wasmq::envelope::MAX_CALLOUT_BITS && 4 * (calls + 1) > wai_wasmq::envelope::MAX_CALLOUT_BITS - 64);
let o = the_largest(calls);
let (job, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap();
assert_eq!(job["result"]["status"], "ok", "{}", job["result"]["fault"]);
}
/// [`the_largest`] by the second version's `exact` method (§11.3), its state the most qubits
/// the method holds: the most memory and code, a prior of the rest an envelope holds, the most
/// bits, a `t` gate and a random measurement on the whole-number state vector, and a reset.
pub fn the_largest_exact(qubits: u32) -> wai_wasmq::Opened {
let src = format!(
"OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[2]) -> bit[2];\nqubit[{qubits}] q;\nbit[2] d;\nbit[{}] c;\nd = f(d);\nh q[0];\nt q[0];\nh q[0];\nc[0] = measure q[0];\nreset q[0];\nd = f(d);\n",
(1 << 20) - 2,
);
let m = the_most_code();
let room = wai_wasmq::envelope::MAX_ENVELOPE_BYTES - m.len() - src.len() - (8 << 10);
let mut b = one(src, m, 2);
b.run.version = 2;
b.run.method = wai_wasmq::envelope::EXACT.into();
b.modules[0].priors.push(("long".into(), vec![1; room]));
b.run.per_init = 1 << 28;
open(&pack(&b).unwrap()).unwrap()
}
#[test]
fn the_largest_exact_bundle_fits_a_32_bit_host() {
// On the interpreter, the engine wasm32 has: a state of 2^24 amplitudes (256 MiB) beside
// a module's 256 MiB of memory, an envelope of 128 MiB and 2^20 bits a shot (§11.3).
let o = the_largest_exact(wai_wasmq::envelope::EXACT_MAX_QUBITS);
let (job, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap();
assert_eq!(job["result"]["status"], "ok", "{}", job["result"]["fault"]);
assert_eq!(job["result"]["method"], "exact");
}
#[test]
fn the_most_result_a_run_may_have_runs_on_every_engine() {
// 256 shots of the most bits a program holds, nine of them random: 2^28 bits of results,
// in hundreds of outcomes of a million bits each.
let measures: String = (0..9).map(|k| format!("c[{k}] = measure q[{k}];\n")).collect();
let b = one(program(9, 1 << 20, &format!("h q;\n{measures}")), wasm(IDENTITY), 1 << 8);
let o = open(&pack(&b).unwrap()).unwrap();
let key = SigningKey::from_bytes(&[7; 32]);
let jobs: Vec<Value> = kinds().into_iter().map(|k| run(&o, k, &key, false).unwrap().0).collect();
assert!(jobs[0]["result"]["counts"].as_object().unwrap().len() > 100);
assert!(jobs.iter().all(|j| j == &jobs[0]), "the same job on every engine");
}
#[test]
fn every_jump_of_a_program_goes_forward_so_a_run_ends() {
// Fuel meters no step of the program, so a run's end rests on this: the reader unrolls
// every loop, a `while` included, and a step only ever jumps ahead.
let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nqubit[1] q;\nbit[1] c;\nc[0] = measure q[0];\n@wai.max_iterations 5\nwhile (c == 0) { x q[0]; c = f(c); c[0] = measure q[0]; }\nfor uint i in [0:3] { if (c == 1) x q[0]; else h q[0]; }\n";
let h = wai_quantum::qasm::read_callouts(src, &Default::default()).unwrap();
let mut jumps = 0;
for (pc, s) in h.steps.iter().enumerate() {
if let wai_quantum::qasm::Step::Instr(wai_quantum::qasm::Instr::JumpUnless(_, to) | wai_quantum::qasm::Instr::Jump(to)) = s {
jumps += 1;
assert!(*to > pc, "step {pc} jumps back to {to}");
}
}
assert!(jumps > 0, "this test can fail: the program has jumps");
}
// ---------------------------------------------------------------------------
// A job sealed by any key: its result is one this profile writes, or verify says not
// ---------------------------------------------------------------------------
fn a_job(module: &str, per_call: u64) -> (wai_wasmq::Opened, Value) {
let mut b = one(program(3, 3, "h q;\nc[0] = measure q[0];\nc[1] = measure q[1];\n"), wasm(module), 8);
b.run.per_call = per_call;
let o = open(&pack(&b).unwrap()).unwrap();
let (body, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap();
(o, body)
}
/// The job as it would be sealed again under another key, its body agreeing with its result.
fn sealed_again(job: &Value) -> Value {
let mut result = job["result"].clone();
if let Some(o) = result.as_object_mut() {
o.remove("receipt");
}
let receipt = wai_quantum::engine::receipt::seal(&SigningKey::from_bytes(&[9; 32]), &job["request"], &result);
if result.is_object() {
result["receipt"] = receipt;
}
let status = if result["status"] == "ok" { "done" } else { "fault" };
json!({ "status": status, "method": result["method"], "request": job["request"], "result": result })
}
/// Each edit of an `ok` job's result or request, and of a faulted one's, that makes it one
/// this profile never writes.
type Edit = (&'static str, fn(&mut Value));
const EDITS_OF_OK: &[Edit] = &[
("another profile", |j| j["result"]["profile"] = json!("wai.quantum.wasmq/2")),
("a method the contract does not give", |j| j["result"]["method"] = json!("statevector")),
("shots the contract does not give", |j| {
j["result"]["shots"] = json!(9);
j["result"]["shots_run"] = json!(9);
let k = j["result"]["counts"].as_object().unwrap().keys().next().unwrap().clone();
let n = j["result"]["counts"][&k].as_u64().unwrap();
j["result"]["counts"][&k] = json!(n + 1);
}),
("a seed the contract does not give", |j| j["result"]["seed"] = json!(2)),
("more qubits than a program may have", |j| j["result"]["qubits"] = json!(40000)),
("qubits that are not a number", |j| j["result"]["qubits"] = json!("three")),
("a count that is not a number", |j| j["result"]["counts"]["1"] = json!("many")),
("a count of nothing", |j| j["result"]["counts"]["1"] = json!(0)),
("an outcome that is not bits", |j| {
let c = j["result"]["counts"].as_object_mut().unwrap();
let (k, n) = c.iter().next().map(|(k, n)| (k.clone(), n.clone())).unwrap();
c.remove(&k);
c.insert("2".repeat(k.len()), n);
}),
("outcomes of two widths", |j| {
let c = j["result"]["counts"].as_object_mut().unwrap();
let (k, n) = c.iter().next().map(|(k, n)| (k.clone(), n.clone())).unwrap();
c.remove(&k);
c.insert(format!("{k}0"), n);
}),
("counts that do not sum to the shots run", |j| {
let k = j["result"]["counts"].as_object().unwrap().keys().next().unwrap().clone();
j["result"]["counts"] = json!({ k: 7 });
}),
("counts that overflow to the shots run", |j| {
j["result"]["counts"] = json!({ "0": u64::MAX, "1": 9 });
}),
("a member the text does not define", |j| j["result"]["note"] = json!("checked by a person of standing")),
("no transcript", |j| {
j["result"].as_object_mut().unwrap().remove("transcript_blake3");
}),
("no accuracy", |j| {
j["result"].as_object_mut().unwrap().remove("accuracy");
}),
("no energy", |j| {
j["result"].as_object_mut().unwrap().remove("energy");
}),
("no count of the callouts", |j| {
j["result"].as_object_mut().unwrap().remove("callouts");
}),
("another accuracy", |j| j["result"]["accuracy"] = json!({ "class": "approximate" })),
("an energy reading", |j| j["result"]["energy"] = json!({ "joules": 0.001 })),
("a transcript that is no digest", |j| j["result"]["transcript_blake3"] = json!("abc")),
("callouts that are not the calls counted", |j| {
let n = j["result"]["callouts"].as_u64().unwrap();
j["result"]["callouts"] = json!(n + 1);
}),
("fuel used that is not the fuel counted", |j| {
let n = j["result"]["fuel"]["used"].as_u64().unwrap();
j["result"]["fuel"]["used"] = json!(n + 1);
}),
("instances' fuel that is not the fuel counted", |j| {
let n = j["result"]["fuel"]["instances"].as_u64().unwrap();
j["result"]["fuel"]["instances"] = json!(n + 1);
}),
("a budget the contract does not give", |j| j["result"]["fuel"]["budget"]["per_call"] = json!(1)),
("a member of the fuel the text does not define", |j| j["result"]["fuel"]["note"] = json!(1)),
("fuel for a callout the contract does not name", |j| j["result"]["fuel"]["callouts"]["g"] = json!({ "calls": 1, "fuel": 0, "max": 0 })),
("a call counted that was not", |j| {
let n = j["result"]["fuel"]["callouts"]["f"]["calls"].as_u64().unwrap();
j["result"]["fuel"]["callouts"]["f"]["calls"] = json!(n + 1);
}),
("a largest call past the callout's fuel", |j| {
let n = j["result"]["fuel"]["callouts"]["f"]["fuel"].as_u64().unwrap();
j["result"]["fuel"]["callouts"]["f"]["max"] = json!(n + 1);
}),
("a member of a callout's fuel the text does not define", |j| j["result"]["fuel"]["callouts"]["f"]["note"] = json!(1)),
("a member of a module the text does not define", |j| j["result"]["modules"][0]["note"] = json!(1)),
("an ok run that names a fault", |j| j["result"]["fault"] = json!({ "reason": "unreachable", "shot": 0, "phase": "call", "module": "m", "fuel": 0, "callout": "f" })),
("a member of the request the text does not define", |j| j["request"]["note"] = json!(1)),
("an envelope that is no digest", |j| j["request"]["envelope_blake3"] = json!("abc")),
];
const EDITS_OF_FAULT: &[Edit] = &[
("a reason §6.2 does not name", |j| j["result"]["fault"]["reason"] = json!("cosmic_ray")),
("a fault in a shot other than the one that stopped", |j| j["result"]["fault"]["shot"] = json!(5)),
("a phase §6.2 does not name", |j| j["result"]["fault"]["phase"] = json!("nowhere")),
("a module the contract does not name", |j| j["result"]["fault"]["module"] = json!("nobody")),
("a callout the contract does not name", |j| j["result"]["fault"]["callout"] = json!("nobody")),
("a call's fault with no callout", |j| {
j["result"]["fault"].as_object_mut().unwrap().remove("callout");
}),
("a callout named where the phase is not a call", |j| j["result"]["fault"]["phase"] = json!("init")),
("a budget of no one", |j| j["result"]["fault"]["budget"] = json!("nobody's")),
("a fuel fault with no budget", |j| {
j["result"]["fault"].as_object_mut().unwrap().remove("budget");
}),
("a budget on a fault of another reason", |j| j["result"]["fault"]["reason"] = json!("unreachable")),
("a fault's fuel that is not a number", |j| j["result"]["fault"]["fuel"] = json!("some")),
("a member of a fault the text does not define", |j| j["result"]["fault"]["detail"] = json!("free text under the signature")),
("a fault after every shot finished", |j| {
// The fault's shot follows them, so that nothing but this is out of place.
j["result"]["shots_run"] = json!(8);
j["result"]["counts"] = json!({ "0": 8 });
j["result"]["fault"]["shot"] = json!(8);
}),
];
/// Each edit of an `ok` job's contract that makes it one this profile never writes, with
/// the result agreeing with it wherever the result repeats it (§2.2, §6.4).
const EDITS_OF_CONTRACT: &[Edit] = &[
("a method this version does not have, in the contract and the result", |j| {
j["request"]["wasmq"]["run"]["method"] = json!("statevector");
j["result"]["method"] = json!("statevector");
}),
("no shots, in the contract and the result", |j| {
j["request"]["wasmq"]["run"]["shots"] = json!(0);
j["result"]["shots"] = json!(0);
j["result"]["shots_run"] = json!(0);
j["result"]["counts"] = json!({});
}),
("more shots than a run takes, in the contract and the result", |j| {
j["request"]["wasmq"]["run"]["shots"] = json!((1u64 << 20) + 1);
j["result"]["shots"] = json!((1u64 << 20) + 1);
}),
("a seed that is no number, in the contract and the result", |j| {
j["request"]["wasmq"]["run"]["seed"] = json!(null);
j["result"]["seed"] = json!(null);
}),
("a contract of another profile", |j| j["request"]["wasmq"]["profile"] = json!("wai.quantum.wasmq/2")),
("a contract with a member the text does not define", |j| j["request"]["wasmq"]["note"] = json!(1)),
("a contract with no program", |j| {
j["request"]["wasmq"].as_object_mut().unwrap().remove("program");
}),
("a program that is no digest", |j| j["request"]["wasmq"]["program"]["blake3"] = json!("abc")),
("a program's inputs that are not integers", |j| j["request"]["wasmq"]["program"]["inputs"] = json!({ "n": "two" })),
("instances of no kind", |j| j["request"]["wasmq"]["run"]["state"] = json!("forever")),
("a run with a member the text does not define", |j| j["request"]["wasmq"]["run"]["note"] = json!(1)),
("a budget that is no number, in the contract and the result", |j| {
j["request"]["wasmq"]["run"]["fuel"]["per_instance"] = json!("lots");
j["result"]["fuel"]["budget"]["per_instance"] = json!("lots");
}),
("a module that is no digest, in the contract and the result", |j| {
j["request"]["wasmq"]["modules"][0]["blake3"] = json!(null);
j["result"]["modules"][0]["blake3"] = json!(null);
}),
("a module of no length", |j| j["request"]["wasmq"]["modules"][0]["bytes"] = json!(0)),
("a callout of a module the contract does not hold", |j| j["request"]["wasmq"]["callouts"][0]["module"] = json!(5)),
("a callout of no export", |j| j["request"]["wasmq"]["callouts"][0]["export"] = json!(7)),
("two callouts for one extern", |j| {
let c = j["request"]["wasmq"]["callouts"][0].clone();
j["request"]["wasmq"]["callouts"].as_array_mut().unwrap().push(c);
}),
("instances' fuel past what the instances could be given", |j| {
// Two lifecycle calls a module (its seed and its init) and one for each prior, for
// each instance: one for each shot, and one more for a shot that stopped.
let per = j["request"]["wasmq"]["run"]["fuel"]["per_instance"].as_u64().unwrap();
let shots = j["result"]["shots"].as_u64().unwrap();
let over = per * 2 * (shots + 1) + 1;
j["result"]["fuel"]["instances"] = json!(over);
let used = j["result"]["fuel"]["used"].as_u64().unwrap();
j["result"]["fuel"]["used"] = json!(used + over);
j["request"]["wasmq"]["run"]["fuel"]["per_job"] = json!(used + over);
j["result"]["fuel"]["budget"]["per_job"] = json!(used + over);
}),
("a callout's fuel past its calls times its largest", |j| {
let f = &j["result"]["fuel"]["callouts"]["f"];
let (calls, max, fuel) = (f["calls"].as_u64().unwrap(), f["max"].as_u64().unwrap(), f["fuel"].as_u64().unwrap());
let more = calls * max + 1 - fuel;
j["result"]["fuel"]["callouts"]["f"]["fuel"] = json!(calls * max + 1);
let used = j["result"]["fuel"]["used"].as_u64().unwrap();
j["result"]["fuel"]["used"] = json!(used + more);
}),
("fuel used past the run's budget, in the contract and the result", |j| {
let used = j["result"]["fuel"]["used"].as_u64().unwrap();
j["request"]["wasmq"]["run"]["fuel"]["per_job"] = json!(used - 1);
j["result"]["fuel"]["budget"]["per_job"] = json!(used - 1);
}),
("a call past the call's budget, in the contract and the result", |j| {
let max = j["result"]["fuel"]["callouts"]["f"]["max"].as_u64().unwrap();
j["request"]["wasmq"]["run"]["fuel"]["per_call"] = json!(max - 1);
j["result"]["fuel"]["budget"]["per_call"] = json!(max - 1);
}),
("callouts' fuel that is no object, and no calls", |j| {
j["result"]["callouts"] = json!(0);
j["result"]["fuel"]["callouts"] = json!(0);
j["result"]["fuel"]["used"] = j["result"]["fuel"]["instances"].clone();
}),
];
/// Edits of a fault job that make it one this profile never writes.
const EDITS_OF_FAULT_FUEL: &[Edit] = &[("a fault's fuel past the budget of its phase", |j| {
// One more than the call's budget, and one more used, counted to the instances: nothing
// but the fault's own fuel is out of place.
let per = j["request"]["wasmq"]["run"]["fuel"]["per_call"].as_u64().unwrap();
j["result"]["fault"]["fuel"] = json!(per + 1);
for at in ["used", "instances"] {
let n = j["result"]["fuel"][at].as_u64().unwrap();
j["result"]["fuel"][at] = json!(n + 1);
}
})];
/// Edits of a sealed job's receipt, after it was sealed.
const EDITS_OF_RECEIPT: &[Edit] = &[
("a receipt of another algorithm", |j| j["result"]["receipt"]["alg"] = json!("none")),
("a receipt that names no algorithm", |j| {
j["result"]["receipt"].as_object_mut().unwrap().remove("alg");
}),
("a receipt of the first form", |j| {
j["result"]["receipt"].as_object_mut().unwrap().remove("canon");
}),
("a key that is not hexadecimal", |j| j["result"]["receipt"]["key"] = json!("€€")),
("a key in capitals", |j| {
let k = j["result"]["receipt"]["key"].as_str().unwrap().to_uppercase();
j["result"]["receipt"]["key"] = json!(k);
}),
("a signature that is not hexadecimal", |j| j["result"]["receipt"]["signature"] = json!("€".repeat(64))),
];
#[test]
fn a_job_whose_contract_or_receipt_this_profile_never_writes_is_not_one_of_its_jobs() {
use std::panic::{AssertUnwindSafe, catch_unwind};
let (_, ok) = a_job(IDENTITY, 500);
let (_, fault) = a_job(SPIN, 500);
let mut let_through = Vec::new();
let mut judge = |name: &'static str, job: &Value| {
let v = catch_unwind(AssertUnwindSafe(|| wai_wasmq::verify(job, None, EngineKind::Interpreter))).unwrap_or_else(|_| panic!("{name}: verify panicked"));
if v.as_ref().is_ok_and(|v| v.ok()) {
let_through.push(name);
}
};
for (job, edits) in [(&ok, EDITS_OF_CONTRACT), (&fault, EDITS_OF_FAULT_FUEL)] {
for (name, edit) in edits {
let mut edited = job.clone();
edit(&mut edited);
assert_ne!(&edited, job, "{name} changes the job");
judge(name, &sealed_again(&edited));
}
}
let sealed = sealed_again(&ok);
assert!(wai_wasmq::verify(&sealed, None, EngineKind::Interpreter).unwrap().ok(), "the positive control");
for (name, edit) in EDITS_OF_RECEIPT {
let mut edited = sealed.clone();
edit(&mut edited);
judge(name, &edited);
}
assert!(let_through.is_empty(), "verify calls these jobs of this profile: {let_through:#?}");
}
#[test]
fn a_job_whose_result_this_profile_never_writes_is_not_one_of_its_jobs_under_any_key() {
let (_, ok) = a_job(IDENTITY, 500);
let (_, fault) = a_job(SPIN, 500);
assert_eq!(fault["result"]["fault"]["reason"], "fuel_exhausted");
let mut let_through = Vec::new();
for (job, edits) in [(&ok, EDITS_OF_OK), (&fault, EDITS_OF_FAULT)] {
// The positive control: sealed again unchanged, it is a job of this profile.
let v = wai_wasmq::verify(&sealed_again(job), None, EngineKind::Interpreter).unwrap();
assert!(v.signature && v.well_formed.is_empty(), "sealed again unchanged: {:?}", v.well_formed);
for (name, edit) in edits {
let mut edited = job.clone();
edit(&mut edited);
assert_ne!(&edited, job, "{name} changes the job");
let v = wai_wasmq::verify(&sealed_again(&edited), None, EngineKind::Interpreter).unwrap();
assert!(v.signature, "{name}: a seal under any key is a valid seal, so this is the form's to catch");
if v.well_formed.is_empty() {
let_through.push(*name);
}
}
}
assert!(let_through.is_empty(), "verify calls these jobs of this profile: {let_through:#?}");
}
// ---------------------------------------------------------------------------
// A record replays as one a run could have made
// ---------------------------------------------------------------------------
fn a_record() -> (wai_wasmq::Opened, Vec<Call>) {
let o = open(&pack(&one(program(3, 1, "h q;\n"), wasm(IDENTITY), 8)).unwrap()).unwrap();
let (_, log) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), true).unwrap();
(o, log.unwrap())
}
#[test]
fn a_record_of_a_shot_the_run_does_not_have_or_out_of_order_does_not_replay() {
let (o, log) = a_record();
assert_eq!(log.len(), 8);
assert!(replay(&o, &log, EngineKind::Interpreter).unwrap().is_empty(), "the positive control");
let mut past = log.clone();
past[7].shot = 8;
assert!(!replay(&o, &past, EngineKind::Interpreter).unwrap().is_empty(), "a shot past the run's eight");
let mut far = log.clone();
far[7].shot = u64::MAX;
assert!(!replay(&o, &far, EngineKind::Interpreter).unwrap().is_empty(), "a shot no run has");
let mut swapped = log.clone();
swapped.swap(2, 5);
assert!(!replay(&o, &swapped, EngineKind::Interpreter).unwrap().is_empty(), "shots out of order");
}
#[test]
fn the_transcript_s_digest_is_the_text_s_layout() {
// §6.4, written out here from the text, for a run that ends ok and one that faults: the
// receipt's digest is this one, computed from the record alone.
fn layout(calls: &[Call], func: u32, fault: Option<(u64, &str, u64)>) -> String {
let mut h = blake3::Hasher::new();
h.update(b"wai:wasmq-transcript\x01");
for c in calls {
h.update(&c.shot.to_le_bytes());
h.update(&func.to_le_bytes());
h.update(&(c.input.len() as u32).to_le_bytes());
h.update(&c.input);
h.update(&(c.output.len() as u32).to_le_bytes());
h.update(&c.output);
h.update(&c.fuel.to_le_bytes());
}
if let Some((shot, reason, fuel)) = fault {
h.update(b"fault");
h.update(&shot.to_le_bytes());
h.update(reason.as_bytes());
h.update(&fuel.to_le_bytes());
}
h.finalize().to_hex().to_string()
}
let key = SigningKey::from_bytes(&[7; 32]);
for (module, per_call) in [(IDENTITY, 500), (SPIN, 500)] {
let mut b = one(program(3, 3, "h q;\nc[0] = measure q[0];\n"), wasm(module), 8);
b.run.per_call = per_call;
let o = open(&pack(&b).unwrap()).unwrap();
let (job, log) = run(&o, EngineKind::Interpreter, &key, true).unwrap();
let r = &job["result"];
let fault = r.get("fault").map(|f| (f["shot"].as_u64().unwrap(), f["reason"].as_str().unwrap(), f["fuel"].as_u64().unwrap()));
assert_eq!(r["transcript_blake3"].as_str().unwrap(), layout(&log.unwrap(), 0, fault), "{module}");
}
}