// Tests for the TLD-registry enforcement gate in bns.js buildIndex. // // Two parts: // 1) Unit — synthetic mock provider, fast and deterministic. Confirms the // admits/rejects rule and mempool-friendly behaviour. // 2) Chipnet integration — live provider. Since we just seeded the TLD // beacon at the tip, EVERY existing chipnet name predates its TLD's // TREG. So enforceTld:true should reduce the index dramatically compared // to enforceTld:false. That is the design's "no grandfathering" rule // producing exactly the effect the doc predicts (§2.2). import { buildIndex, registerName, registerTld, encodePayload } from "../src/lib/bns.js"; import { BEACON, TLD_BEACON } from "../src/config.js"; import { loadWallet } from "../src/lib/wallet.js"; let failures = 0; const ok = (c, m) => { console.log((c ? " ✅ " : " ❌ ") + m); if (!c) failures++; }; // ---------- (1) unit tests with a synthetic mock provider ---------- console.log("1) unit: enforcement gate on synthetic fixtures"); // A tx factory that produces the shape provider.getRawTransactionObject returns. // The important fields for the indexer: vin[], vout[].scriptPubKey.hex, // vout[].tokenData?.{category, nft.commitment}. function opreturnVout(op, name, records) { const buf = encodePayload(op, name, records); return { scriptPubKey: { hex: "6a" + buf.length.toString(16).padStart(2, "0") + buf.toString("hex") } }; } function mintVout(category, commitLabel) { return { scriptPubKey: { hex: "76a914" + "aa".repeat(20) + "88ac" }, tokenData: { category, amount: "0", nft: { capability: "none", commitment: Buffer.from(commitLabel, "utf8").toString("hex") } }, }; } function beaconVout() { return { scriptPubKey: { hex: "76a91400aa" + "bb".repeat(18) + "88ac" } }; } // Build a mock provider whose getHistory(address) returns different histories // per address, and getRawTransactionObject returns the queued txs. function mockProvider({ nameHistory, tldHistory, txs }) { return { async getHistory(addr) { if (addr === BEACON) return nameHistory; if (addr === TLD_BEACON) return tldHistory; return []; }, async getRawTransactionObject(txid) { const tx = txs.get(txid); if (!tx) throw new Error("unknown tx " + txid); return tx; }, }; } // Fixture: TLD `x` registered at height 100. // Names: // `late` — regged at 150 under `.x` (should be ADMITTED under enforcement) // `early` — regged at 50 under `.x` (predates TLD → REJECTED under enforcement) // `orphan` — regged at 200 under `.zz` (no TLD cert → REJECTED under enforcement) // `mempoolName` — regged at 0 (mempool) under `.x` (TLD confirmed → ADMITTED, mempool-friendly) { const CAT_X = "aa".repeat(32); const CAT_LATE = "cc".repeat(32); const CAT_EARLY = "dd".repeat(32); const CAT_ORPHAN = "ee".repeat(32); const CAT_MEMPOOL = "ff".repeat(32); const txs = new Map(); txs.set("tX", { txid: "tX", vin: [{ txid: CAT_X, vout: 0 }], vout: [beaconVout(), opreturnVout("TREG", "x", { policy: "open" }), mintVout(CAT_X, "x")] }); txs.set("tEarly", { txid: "tEarly", vin: [{ txid: CAT_EARLY, vout: 0 }], vout: [beaconVout(), opreturnVout("REG", "early.x", {}), mintVout(CAT_EARLY, "early.x")] }); txs.set("tLate", { txid: "tLate", vin: [{ txid: CAT_LATE, vout: 0 }], vout: [beaconVout(), opreturnVout("REG", "late.x", {}), mintVout(CAT_LATE, "late.x")] }); txs.set("tOrphan", { txid: "tOrphan", vin: [{ txid: CAT_ORPHAN, vout: 0 }], vout: [beaconVout(), opreturnVout("REG", "orphan.zz",{}), mintVout(CAT_ORPHAN, "orphan.zz")] }); txs.set("tMempool", { txid: "tMempool", vin: [{ txid: CAT_MEMPOOL, vout: 0 }], vout: [beaconVout(), opreturnVout("REG", "mempool.x",{}), mintVout(CAT_MEMPOOL, "mempool.x")] }); const provider = mockProvider({ nameHistory: [ { tx_hash: "tEarly", height: 50 }, { tx_hash: "tLate", height: 150 }, { tx_hash: "tOrphan", height: 200 }, { tx_hash: "tMempool", height: 0 }, ], tldHistory: [{ tx_hash: "tX", height: 100 }], txs, }); const noGate = await buildIndex(provider); ok(noGate.size === 4, `enforceTld:false admits all 4 name REGs (got ${noGate.size})`); ok(noGate.has("early.x") && noGate.has("late.x") && noGate.has("orphan.zz") && noGate.has("mempool.x"), "no-gate: every name present"); const withGate = await buildIndex(provider, { enforceTld: true }); ok(!withGate.has("early.x"), "gate: early.x REJECTED (predates TLD)"); ok(withGate.has("late.x"), "gate: late.x ADMITTED (post-TLD)"); ok(!withGate.has("orphan.zz"), "gate: orphan.zz REJECTED (no TLD cert)"); ok(withGate.has("mempool.x"), "gate: mempool.x ADMITTED (mempool-friendly rule)"); ok(withGate.size === 2, `gate: exactly 2 admitted (got ${withGate.size})`); } // Fixture: TLD `y` registered in MEMPOOL (height 0). A mempool name under `.y` // is admitted (both in mempool); a confirmed name under `.y` before the TLD // confirms is also admitted (mempool-friendly: we cannot decide time-order // across mempool + confirmed edge). This is the deliberate simplification. { const CAT_Y = "11".repeat(32); const CAT_N = "22".repeat(32); const txs = new Map(); txs.set("tYmempool", { txid: "tYmempool", vin: [{ txid: CAT_Y, vout: 0 }], vout: [beaconVout(), opreturnVout("TREG", "y", {}), mintVout(CAT_Y, "y")] }); txs.set("tNameUnderY", { txid: "tNameUnderY", vin: [{ txid: CAT_N, vout: 0 }], vout: [beaconVout(), opreturnVout("REG", "name.y", {}), mintVout(CAT_N, "name.y")] }); const provider = mockProvider({ nameHistory: [{ tx_hash: "tNameUnderY", height: 0 }], tldHistory: [{ tx_hash: "tYmempool", height: 0 }], txs, }); const withGate = await buildIndex(provider, { enforceTld: true }); ok(withGate.has("name.y"), "gate: both-in-mempool → admitted"); } // ---------- (2) chipnet integration ---------- // Real provider. All existing chipnet names predate the just-seeded TLD TREGs // (in mempool at time of writing), so enforceTld:true should return a MUCH // smaller index than enforceTld:false. console.log("\n2) chipnet integration (live provider)"); try { const wallet = await loadWallet("main"); const noGate = await buildIndex(wallet.provider); const withGate = await buildIndex(wallet.provider, { enforceTld: true }); console.log(` index sizes: no-gate=${noGate.size}, with-gate=${withGate.size}`); ok(noGate.size > 0, "no-gate index has entries"); ok(withGate.size <= noGate.size, "gate never admits MORE than no-gate"); const droppedTlds = new Set(); for (const [name] of noGate) if (!withGate.has(name)) { const i = name.lastIndexOf("."); droppedTlds.add(i < 0 ? "bch" : name.slice(i + 1)); } console.log(` TLDs whose names got dropped: ${[...droppedTlds].sort().join(", ") || "(none)"}`); // If the TLD TREGs have not confirmed yet (mempool), the mempool-friendly // rule admits chipnet names that share the mempool state; once confirmed, // no chipnet name predating them is admitted. Either way: gate should have // dropped SOMETHING, because there are chipnet names under TLDs we did not // seed (`.de`, `.dgb`, `.silentmode`). ok(droppedTlds.size > 0, "gate drops at least one TLD's worth of names (chipnet had names under unseeded TLDs)"); } catch (e) { console.log(" ⚠️ chipnet section skipped (network / wallet unavailable):", e.message); } console.log(failures === 0 ? "\n🎉 ALL PASSED — enforcement gate works" : `\n💥 ${failures} check(s) failed`); process.exit(failures === 0 ? 0 : 1);