mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-08 05:43:04 +00:00
zaps: end-to-end verify test + plan checklist status (#47)
Adds an integration test exercising the REAL verifier against a genuinely valid, amount-bearing bolt11 (real bech32 decode + nip57 amount parse + real preimage/description-hash math, no injected fields) — proves the tally pieces compose. Marks the shipped slices in docs/zap-tally-plan.md §11 and lists the remaining v1 work: profile-zap #p counting + channel-page UI, the retry-path publish hook, and the popularity chart (§5.9). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
41e533e675
commit
1d732479ed
2 files changed
+69
-16
No files matched your search
+26
-16
@@ -453,22 +453,32 @@ tallies simply stop being counted. No data migration.
|
||||
|
||||
## 11. Task checklist (ordered — pick up here across sessions)
|
||||
|
||||
- [ ] **D1** decide `ZAP_TALLY_KIND`; add constant + `docs/nostr-event-schemas.md` row.
|
||||
- [ ] `constants.js`: `FEATURE_ZAP_TALLY` (default off) + runtime-flag plumbing.
|
||||
- [ ] `nostrEventSchemas.js`: `NOTE_TYPES.ZAP_TALLY` schema + `buildZapTallyEvent`.
|
||||
- [ ] `zapReceiptValidator.js`: `extractBolt11Fields` (payment_hash),
|
||||
`verifyPaymentPreimage`, `verifyBitvidZapTally`. Unit tests first (vectors).
|
||||
- [ ] `zapSplit.js`: expose `receipt.preimage`.
|
||||
- [ ] `zapController.js`: extend `onZapSuccess` payload with per-share proof; wire
|
||||
the retry path.
|
||||
- [ ] `ModalManager.js` + a thin `app.publishZapTally`: build→sign→publish
|
||||
(best-effort, flag-gated) → `store.ingestVerifiedZap`.
|
||||
- [ ] `zapTotals.js`: query `ZAP_TALLY_KIND`, verify + dedup by payment_hash
|
||||
(`paymentHashes` set), `ingestVerifiedZap`, flag-gate counting.
|
||||
- [ ] Tests: verify, schema, cross-source dedup, wiring, flag-off; Playwright
|
||||
seeded-event count/rank.
|
||||
- [ ] `lint` + `build` + suites green; push to `unstable`.
|
||||
- [ ] Rollout per §9; TODO entry updated.
|
||||
- [x] **D1** `ZAP_TALLY_KIND = 30081` (addressable, d=payment_hash) + constant +
|
||||
`docs/nostr-event-schemas.md` row. (commit a6b2996f)
|
||||
- [x] `constants.js`: `FEATURE_ZAP_TALLY` (default ON per D4) + runtime plumbing.
|
||||
- [x] `nostrEventSchemas.js`: `NOTE_TYPES.ZAP_TALLY` schema + `buildZapTallyEvent`.
|
||||
- [x] `zapReceiptValidator.js`: `extractBolt11Fields`, `verifyPaymentPreimage`,
|
||||
`verifyBitvidZapTally`. Tests incl. a real amount-bearing bolt11 e2e.
|
||||
- [x] `zapSplit.js`: expose `receipt.preimage`. (commit 41e533e6)
|
||||
- [x] `zapController.js`: `onZapSuccess` forwards per-share proof.
|
||||
*(Retry path still TODO — see below.)*
|
||||
- [x] `zapTallyPublisher.js` (buildTallyFromShare + publishZapTallies) +
|
||||
`ModalManager.js` wiring: build→sign→publish (best-effort, flag-gated).
|
||||
No `ingestVerifiedZap` needed — the store's existing "relay wins" prune
|
||||
reconciles the optimistic ledger when the published tally is fetched back.
|
||||
- [x] `zapTotals.js`: query `[9735, ZAP_TALLY_KIND]`, verify + dedup by
|
||||
payment_hash (`paymentHashes` set), flag-gated counting. (commit 025ab348)
|
||||
- [x] Tests: verify, schema, publisher, cross-source dedup, flag-off, e2e verify.
|
||||
- [x] `lint` + `build` + suites green; pushed to `unstable`.
|
||||
- [ ] **Remaining v1 work:**
|
||||
- [ ] **Profile-zap counting:** the store queries `#a`/`#e` only — add
|
||||
`#p` batches so a profile-only tally (no video pointer) is fetched,
|
||||
and a channel-page total UI surface (D2 chose video+profile).
|
||||
- [ ] **Retry path:** `zapController.executeRetry` doesn't fire
|
||||
`onZapSuccess`; wire it so retried shares also publish tallies.
|
||||
- [ ] **Popularity chart §5.9:** orange zaps line + legend + date axis.
|
||||
- [ ] Live end-to-end on unstable (real zap → tally on relay → second
|
||||
browser counts it); rollout per §9.
|
||||
|
||||
**Popularity chart (§5.9 — can start once the store's verify + dedup helpers
|
||||
exist; the renderer/legend/axis parts have no dependency on publishing tallies):**
|
||||
|
||||
@@ -219,6 +219,49 @@ test("buildZapTallyEvent: addressable by payment_hash + carries the proof tags",
|
||||
assert.deepEqual(tag("client"), ["client", "bitvid"]);
|
||||
});
|
||||
|
||||
// End-to-end with the REAL verifier + a REAL amount-bearing bolt11: proves the
|
||||
// pieces compose (no injected extractFields/getSats). The invoice encodes an
|
||||
// amount (lnbc21u = 2100 sats), a payment_hash = sha256(preimage), and a
|
||||
// description_hash = sha256(zap request); the real nip57 amount parser reads it.
|
||||
test("verifyBitvidZapTally: accepts a fully valid tally end to end (real decode + amount)", async () => {
|
||||
const { nip57 } = await import("nostr-tools");
|
||||
const preimage = randomBytes(32);
|
||||
const preimageHex = preimage.toString("hex");
|
||||
const paymentHashBytes = createHash("sha256").update(preimage).digest();
|
||||
const reqJson = zapRequestJson();
|
||||
const descHashBytes = createHash("sha256").update(Buffer.from(reqJson, "utf8")).digest();
|
||||
|
||||
const pWords = bech32.toWords(new Uint8Array(paymentHashBytes));
|
||||
const hWords = bech32.toWords(new Uint8Array(descHashBytes));
|
||||
const words = [
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
1, ...lenWords(pWords.length), ...pWords, // 'p' payment_hash
|
||||
23, ...lenWords(hWords.length), ...hWords, // 'h' description_hash
|
||||
...new Array(104).fill(0),
|
||||
];
|
||||
const bolt11 = bech32.encode("lnbc21u", words, 2000); // 21 micro-BTC = 2100 sats
|
||||
|
||||
const event = buildZapTallyEvent({
|
||||
pubkey: PUB,
|
||||
created_at: 1000,
|
||||
paymentHash: paymentHashBytes.toString("hex"),
|
||||
recipientPubkey: CREATOR,
|
||||
eventId: EVENT_ID,
|
||||
coordinate: COORD,
|
||||
amountMsats: 2100000,
|
||||
bolt11,
|
||||
preimage: preimageHex,
|
||||
zapRequestJson: reqJson,
|
||||
});
|
||||
|
||||
const result = verifyBitvidZapTally(event, {
|
||||
getSats: nip57.getSatoshisAmountFromBolt11, // real parser, no injection of fields
|
||||
});
|
||||
assert.equal(result.ok, true, "real verifier accepts a genuinely valid tally");
|
||||
assert.equal(result.sats, 2100, "amount parsed from the real bolt11 hrp");
|
||||
assert.equal(result.paymentHash, paymentHashBytes.toString("hex"));
|
||||
});
|
||||
|
||||
test("buildZapTallyEvent: profile-only zap has p but no a/e", () => {
|
||||
const event = buildZapTallyEvent({
|
||||
pubkey: PUB,
|
||||
|
||||
Reference in new issue
Block a user