Skip to content

Receipt proof D1 cost: cached verified checkpoint, indexed receipt_id, single scan, hardcoded /verify - #105

Merged
AzielEliab merged 2 commits into
mainfrom
fix/receipt-proof-d1-cost
Oct 11, 2026
Merged

AzielEliab merged 2 commits into
mainfrom
fix/receipt-proof-d1-cost

Conversation

@AzielEliab

@AzielEliab AzielEliab commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #104 (a7bc992), covering AZBot's D1-cost review. Revised after the review at 61485ce.

  1. Checkpoint cache: the verified checkpoint {seq, hash, count, at, hashes[]} is cached per isolate and in the Cache API (__ledger-checkpoint/v1, 1 day). No KV writes. hashes[] holds the verified entry_hash for every seq, about 4.5 KB today. TODO: when the ledger grows large, replace it with a Merkle root plus per-row inclusion paths. Each request runs a one-row head probe (ORDER BY sequence DESC LIMIT 1).
  2. Forward-only verify: only rows after the checkpoint are verified. If the checkpoint is ahead of the head or forked away from it, the chain is re-verified once from genesis. Receipt rows are read by an exact match on the indexed json_extract(payload_json,'$.receipt_id').
  3. One indexed read: a single read feeds both ledger and proof. A read fails closed if its row count differs from head.seq - checkpoint.seq or it doesn't end at the probed head.
  4. Fixed link: the /verify link is hardcoded and never rendered from the server.

Fixes the fail-open AZBot found: a receipt row now counts as verified only if its entry_hash equals the checkpoint hash at its seq and its previous_hash equals the checkpoint hash at seq-1. It also has to recompute to its own hash. A row below the checkpoint that was rewritten with a recomputed hash, with head and count unchanged, now shows unverified with no seal. A test covers it.

Migration: workers/godlock-uk/migrations/0001_ledger_receipt_id_index.sql, with migrations_dir = "migrations" set in wrangler.toml. Apply it with wrangler d1 migrations apply godlock-uk --remote. The per-isolate CREATE INDEX IF NOT EXISTS stays only as a fallback.

Behavior change (caused_by): the old substring scan also matched RESCORE entries whose caused_by was this receipt, i.e. entries written when this receipt triggered a rescore of an older one. The exact receipt_id match no longer lists those on the causing receipt's page or proof. They still appear on the rescored receipt, whose receipt_id they carry, and in the full chain at /verify. That's a deliberate narrowing. If the causing receipt should still show them, add a second indexed lookup on $.caused_by.

Hash-linked and sealed, not signed. Tests: 283/283. Not merged, not deployed.

@AzielEliab
AzielEliab merged commit 865c5fc into main Oct 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant