| 1 | #!/usr/bin/env node |
| 2 | // 🤖 Verify a ledger stream end-to-end (issue #1224): fetch HEAD, walk the |
| 3 | // prev-hash chain newest→oldest re-hashing every entry's exact bytes, and |
| 4 | // fail loudly on the first broken link. Needs no credentials — the whole |
| 5 | // walk rides the bucket's public https door, so anyone can run it. |
| 6 | // |
| 7 | // node tools/ledger-verify.mjs <ledger> |
| 8 | import { fetchPublic, getHead } from "./ledgerstream.mjs"; |
| 9 | import { verifyLink } from "./ledgerchain.mjs"; |
| 10 | |
| 11 | const ledger = process.argv[2]; |
| 12 | if (!ledger) { console.error("usage: node tools/ledger-verify.mjs <ledger>"); process.exit(1); } |
| 13 | |
| 14 | const head = await getHead(ledger); |
| 15 | if (!head) { console.error(ledger + ": no HEAD — stream doesn't exist"); process.exit(1); } |
| 16 | |
| 17 | let key = head.head_key, claimedSha = head.head_sha256, walked = 0; |
| 18 | while (key) { |
| 19 | const bytes = await fetchPublic(key); |
| 20 | if (!bytes) { console.error("BROKEN: " + key + " is missing"); process.exit(1); } |
| 21 | const v = verifyLink({ bytes, key, claimedSha }); |
| 22 | if (!v.ok) { console.error("BROKEN at " + key + ": " + v.reason); process.exit(1); } |
| 23 | walked++; |
| 24 | key = v.entry.prev_key; |
| 25 | claimedSha = v.entry.prev_sha256; |
| 26 | } |
| 27 | if (walked !== head.count) { |
| 28 | console.error("BROKEN: HEAD counts " + head.count + " but the chain walks " + walked); |
| 29 | process.exit(1); |
| 30 | } |
| 31 | console.log(ledger + ": chain OK — " + walked + " entr" + (walked === 1 ? "y" : "ies") + ", head " + head.head_sha256.slice(0, 12) + "…"); |
| 32 | |