argus/test/tld-enforcement.test.mjs

147 lines
7.5 KiB
JavaScript
Raw Permalink Normal View History

BNS: per-TLD on-chain registry, multi-beacon indexer, enforcement gate Per-TLD certificates on a distinct TLD beacon supersede the legacy list-in-one-NFT model. TLD label is the NFT commitment; TREG/TUPD payloads on the TLD beacon mirror REG/UPD on the name beacon. Chipnet TLD beacon: bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh scripthash c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8 * config.js, resolver-web.{js,d.ts} — TLD_BEACON + TLD_BEACON_SCRIPTHASH * bns.js — KNOWN_OPS { REG, UPD, TREG, TUPD }, registerTld/updateTld/normalizeTld * bns.js + resolver-web.js — buildIndex({enforceTld:false}) drops name REGs whose TLD lacks a live TLD cert at their block height (mempool-friendly). Lockstep across both files. * lib/tld-index.js — buildTldSnapshot({history,txs}) → deterministic root over canonicalised tlds[]. Pure, importable, and stable across insertion order. * indexer/bns-indexer.js — BeaconIndex is multi-beacon; ws handler dispatches by scripthash; new HTTP endpoint GET /tld-list.json + /health behind BNS_HTTP_PORT; state file v1 → v2 migrates in fromJSON. * publish-tld-mirror.mjs — S3 (floating + immutable content-addressed) plus NIP-33 kind 30078 d:bns-tld-list to Nostr relays, signed via nostr-tools (libauth's Schnorr differs from BIP-340 and relays reject it). * seed-tld-beacon.mjs — idempotent chipnet seed. Current set on the beacon: bch p2p bit nav test x asm neo gt sc sia com dex cex nt (15). `.com` is held by the operator: cert on-chain, excluded from public registrar UI. Design + docs: * Decentralized.DNS/DESIGN-tld-registry.md — schema, enforcement, operator-countersig gate, USD-tiered pricing floors, fee splits, tracker/mirror mechanics, migration/grandfathering. * INSTRUCTIONS.md §3 rewritten for the 4-step flow (TREG → UI → deploy → republish snapshot). * site/tlds/ — public docs page listing current TLDs + verification paths. * site/sirius/register.html — dropdown removed; parallel search across every TLD in the TLDS array, sorted available → taken → not-on-registry. Config: * ariadne.config.json tlds → [bch, p2p, bit, nav, test, x] (CA constraints; on-chain registry is now the authoritative source, config is fallback). * setup/install.ps1 — reads TLD list via fetchTldList (per-TLD registry) with fallback to fetchBcnrTlds (legacy tls.bch list-NFT) then config. Tests: * test/tld-index.test.mjs — 30 self-contained checks (multi-beacon dispatch, state migration, ws routing, snapshot shape + root recomputability). * test/tld-enforcement.test.mjs — synthetic + live chipnet integration (with-gate correctly drops all pre-seed names).
2026-08-29 17:11:02 +02:00
// 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")] });
BNS: per-TLD on-chain registry, multi-beacon indexer, enforcement gate Per-TLD certificates on a distinct TLD beacon supersede the legacy list-in-one-NFT model. TLD label is the NFT commitment; TREG/TUPD payloads on the TLD beacon mirror REG/UPD on the name beacon. Chipnet TLD beacon: bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh scripthash c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8 * config.js, resolver-web.{js,d.ts} — TLD_BEACON + TLD_BEACON_SCRIPTHASH * bns.js — KNOWN_OPS { REG, UPD, TREG, TUPD }, registerTld/updateTld/normalizeTld * bns.js + resolver-web.js — buildIndex({enforceTld:false}) drops name REGs whose TLD lacks a live TLD cert at their block height (mempool-friendly). Lockstep across both files. * lib/tld-index.js — buildTldSnapshot({history,txs}) → deterministic root over canonicalised tlds[]. Pure, importable, and stable across insertion order. * indexer/bns-indexer.js — BeaconIndex is multi-beacon; ws handler dispatches by scripthash; new HTTP endpoint GET /tld-list.json + /health behind BNS_HTTP_PORT; state file v1 → v2 migrates in fromJSON. * publish-tld-mirror.mjs — S3 (floating + immutable content-addressed) plus NIP-33 kind 30078 d:bns-tld-list to Nostr relays, signed via nostr-tools (libauth's Schnorr differs from BIP-340 and relays reject it). * seed-tld-beacon.mjs — idempotent chipnet seed. Current set on the beacon: bch p2p bit nav test x asm neo gt sc sia com dex cex nt (15). `.com` is held by the operator: cert on-chain, excluded from public registrar UI. Design + docs: * Decentralized.DNS/DESIGN-tld-registry.md — schema, enforcement, operator-countersig gate, USD-tiered pricing floors, fee splits, tracker/mirror mechanics, migration/grandfathering. * INSTRUCTIONS.md §3 rewritten for the 4-step flow (TREG → UI → deploy → republish snapshot). * site/tlds/ — public docs page listing current TLDs + verification paths. * site/sirius/register.html — dropdown removed; parallel search across every TLD in the TLDS array, sorted available → taken → not-on-registry. Config: * ariadne.config.json tlds → [bch, p2p, bit, nav, test, x] (CA constraints; on-chain registry is now the authoritative source, config is fallback). * setup/install.ps1 — reads TLD list via fetchTldList (per-TLD registry) with fallback to fetchBcnrTlds (legacy tls.bch list-NFT) then config. Tests: * test/tld-index.test.mjs — 30 self-contained checks (multi-beacon dispatch, state migration, ws routing, snapshot shape + root recomputability). * test/tld-enforcement.test.mjs — synthetic + live chipnet integration (with-gate correctly drops all pre-seed names).
2026-08-29 17:11:02 +02:00
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")] });
BNS: per-TLD on-chain registry, multi-beacon indexer, enforcement gate Per-TLD certificates on a distinct TLD beacon supersede the legacy list-in-one-NFT model. TLD label is the NFT commitment; TREG/TUPD payloads on the TLD beacon mirror REG/UPD on the name beacon. Chipnet TLD beacon: bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh scripthash c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8 * config.js, resolver-web.{js,d.ts} — TLD_BEACON + TLD_BEACON_SCRIPTHASH * bns.js — KNOWN_OPS { REG, UPD, TREG, TUPD }, registerTld/updateTld/normalizeTld * bns.js + resolver-web.js — buildIndex({enforceTld:false}) drops name REGs whose TLD lacks a live TLD cert at their block height (mempool-friendly). Lockstep across both files. * lib/tld-index.js — buildTldSnapshot({history,txs}) → deterministic root over canonicalised tlds[]. Pure, importable, and stable across insertion order. * indexer/bns-indexer.js — BeaconIndex is multi-beacon; ws handler dispatches by scripthash; new HTTP endpoint GET /tld-list.json + /health behind BNS_HTTP_PORT; state file v1 → v2 migrates in fromJSON. * publish-tld-mirror.mjs — S3 (floating + immutable content-addressed) plus NIP-33 kind 30078 d:bns-tld-list to Nostr relays, signed via nostr-tools (libauth's Schnorr differs from BIP-340 and relays reject it). * seed-tld-beacon.mjs — idempotent chipnet seed. Current set on the beacon: bch p2p bit nav test x asm neo gt sc sia com dex cex nt (15). `.com` is held by the operator: cert on-chain, excluded from public registrar UI. Design + docs: * Decentralized.DNS/DESIGN-tld-registry.md — schema, enforcement, operator-countersig gate, USD-tiered pricing floors, fee splits, tracker/mirror mechanics, migration/grandfathering. * INSTRUCTIONS.md §3 rewritten for the 4-step flow (TREG → UI → deploy → republish snapshot). * site/tlds/ — public docs page listing current TLDs + verification paths. * site/sirius/register.html — dropdown removed; parallel search across every TLD in the TLDS array, sorted available → taken → not-on-registry. Config: * ariadne.config.json tlds → [bch, p2p, bit, nav, test, x] (CA constraints; on-chain registry is now the authoritative source, config is fallback). * setup/install.ps1 — reads TLD list via fetchTldList (per-TLD registry) with fallback to fetchBcnrTlds (legacy tls.bch list-NFT) then config. Tests: * test/tld-index.test.mjs — 30 self-contained checks (multi-beacon dispatch, state migration, ws routing, snapshot shape + root recomputability). * test/tld-enforcement.test.mjs — synthetic + live chipnet integration (with-gate correctly drops all pre-seed names).
2026-08-29 17:11:02 +02:00
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);