#!/usr/bin/env bash # smoke.sh BASE_URL: the fleet service's promises, checked over HTTP against a running instance # started with the corpus's organisation key (CI starts one locally; see README.md). # # bash smoke.sh http://127.0.0.1:18252 # # From the committed corpus (../live-conformance/fleet/vectors.json): it enrols the week's nodes, # gives the service the week's records, asks for the week's report and requires the corpus's report # byte for byte with the corpus's outcome; checks the report's bundle with the independent verifier, # fleet_verify.py; requires a record held already, and a record of a node no enrolment names, to be # refused; requires the lesson room's per-hop records back in the ledger's source form; and requires # an altered bundle to be refused by POST /v1/fleet/verify. Each check prints one line and the script # fails on the first that does not hold. Needs curl and python3. set -euo pipefail U=${1:?usage: smoke.sh BASE_URL} U=${U%/} HERE=$(cd "$(dirname "$0")" && pwd) CORPUS="$HERE/../live-conformance/fleet/vectors.json" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT post() { curl -sS --max-time 300 -o "$3" -w '%{http_code}' -X POST "$U$1" -H 'content-type: application/json' --data-binary "@$2"; } py() { python3 -I -c "$@"; } curl -sS --max-time 30 "$U/v1/fleet" > "$tmp/describe.json" py 'import json,sys d=json.load(open(sys.argv[1])); c=json.load(open(sys.argv[2])) assert d["service"]=="wai-fleet" and d["capability"]=="wai.delivery.fleet", d assert d["organisation_key"]==c["keys"]["organisation"], "start the service with the corpus organisation key (README.md)" print("describe: ok, organisation", d["organisation_key"][:16]+"…")' "$tmp/describe.json" "$CORPUS" # The week's enrolments, then its records in one request. py 'import json,sys c=json.load(open(sys.argv[1])); v=next(x for x in c["vectors"] if x["name"]=="a_week") for k,e in enumerate(v["bundle"]["enrolments"]): json.dump({"enrolment": e}, open(sys.argv[2]+"/enrol-%02d.json" % k,"w")) json.dump({"records": v["bundle"]["records"]}, open(sys.argv[2]+"/records.json","w")) json.dump({"records": v["bundle"]["records"][:1]}, open(sys.argv[2]+"/again.json","w")) s=next(x for x in c["vectors"] if x["name"]=="signer_not_enrolled") json.dump({"records": [s["bundle"]["records"][8]]}, open(sys.argv[2]+"/outsider.json","w")) json.dump({"start": v["outcome"]["start"], "end": v["outcome"]["end"]}, open(sys.argv[2]+"/period.json","w")) print(v["outcome"]["rooms"][0]["room"])' "$CORPUS" "$tmp" > "$tmp/lesson" for f in "$tmp"/enrol-*.json; do code=$(post /v1/fleet/enrolments "$f" "$f.out") [ "$code" = 201 ] || { echo "enrolment $(basename "$f"): HTTP $code: $(cat "$f.out")"; exit 1; } done echo "enrolments: ok, $(ls "$tmp"/enrol-*.json | wc -l | tr -d ' ') enrolled" code=$(post /v1/fleet/records "$tmp/records.json" "$tmp/records.out") [ "$code" = 200 ] || { echo "records: HTTP $code: $(cat "$tmp/records.out")"; exit 1; } py 'import json,sys a=json.load(open(sys.argv[1])); assert a["refused"]==0 and a["held"]>0, a print("records: ok,", a["held"], "held")' "$tmp/records.out" # A record held already, and one of a node no enrolment names, are refused. post /v1/fleet/records "$tmp/again.json" "$tmp/again.out" > /dev/null post /v1/fleet/records "$tmp/outsider.json" "$tmp/outsider.out" > /dev/null py 'import json,sys a=json.load(open(sys.argv[1])); b=json.load(open(sys.argv[2])) assert a["results"]==[{"refused":"record_duplicate"}], a assert b["results"]==[{"refused":"signer_not_enrolled"}], b print("refusals: ok, record_duplicate and signer_not_enrolled")' "$tmp/again.out" "$tmp/outsider.out" # The week's report: the corpus's, byte for byte, with its outcome. code=$(post /v1/fleet/reports "$tmp/period.json" "$tmp/report.out") [ "$code" = 201 ] || { echo "report: HTTP $code: $(cat "$tmp/report.out")"; exit 1; } py 'import json,sys c=json.load(open(sys.argv[1])); v=next(x for x in c["vectors"] if x["name"]=="a_week"); r=json.load(open(sys.argv[2])) assert r["bundle"]["report"]==v["bundle"]["report"], "the report differs from the corpus" assert r["outcome"]==v["outcome"], "the outcome differs from the corpus" json.dump(r["bundle"], open(sys.argv[3]+"/week.wai-fleet.json","w")) print("report: ok, the corpus report, digest", r["report"][:16]+"…")' "$CORPUS" "$tmp/report.out" "$tmp" D=$(py 'import json,sys; print(json.load(open(sys.argv[1]))["report"])' "$tmp/report.out") curl -sS --max-time 60 "$U/v1/fleet/reports/$D" > "$tmp/served.json" py 'import json,sys a=json.load(open(sys.argv[1])); b=json.load(open(sys.argv[2])) assert a["bundle"]==b["bundle"] and a["outcome"]==b["outcome"] print("report served: ok")' "$tmp/served.json" "$tmp/report.out" # The bundle, checked by the second implementation, which shares no code with the service. python3 -I "$HERE/../live-conformance/fleet_verify.py" --bundle "$tmp/week.wai-fleet.json" > "$tmp/independent.out" \ || { echo "fleet_verify.py refused the bundle:"; cat "$tmp/independent.out"; exit 1; } echo "independent verifier: ok, the bundle verifies" # The lesson room's per-hop records, in the ledger's source form. curl -sS --max-time 60 "$U/v1/fleet/rooms/$(cat "$tmp/lesson")/hops" > "$tmp/hops.json" py 'import json,sys h=json.load(open(sys.argv[1]))["hops"] assert len(h)==3 and all(sorted(x)==["ledger","record"] for x in h), h print("room hops: ok, 3 records with their ledgers")' "$tmp/hops.json" # An altered bundle is refused. py 'import json,sys b=json.load(open(sys.argv[1])); r=bytearray(bytes.fromhex(b["report"])); r[-1]^=1; b["report"]=r.hex() json.dump(b, open(sys.argv[2],"w"))' "$tmp/week.wai-fleet.json" "$tmp/altered.json" post /v1/fleet/verify "$tmp/altered.json" "$tmp/altered.out" > /dev/null py 'import json,sys o=json.load(open(sys.argv[1])); assert o["outcome"]=="refuse" and o["reason"]=="bad_signature", o print("verify: ok, an altered bundle is refused", o["reason"])' "$tmp/altered.out" echo "smoke: all checks held"