147 lines
7.3 KiB
JavaScript
147 lines
7.3 KiB
JavaScript
|
|
// 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: [], vout: [beaconVout(), opreturnVout("TREG", "x", { policy: "open" }), mintVout(CAT_X, "x")] });
|
||
|
|
txs.set("tEarly", { txid: "tEarly", vin: [], vout: [beaconVout(), opreturnVout("REG", "early.x", {}), mintVout(CAT_EARLY, "early.x")] });
|
||
|
|
txs.set("tLate", { txid: "tLate", vin: [], vout: [beaconVout(), opreturnVout("REG", "late.x", {}), mintVout(CAT_LATE, "late.x")] });
|
||
|
|
txs.set("tOrphan", { txid: "tOrphan", vin: [], vout: [beaconVout(), opreturnVout("REG", "orphan.zz",{}), mintVout(CAT_ORPHAN, "orphan.zz")] });
|
||
|
|
txs.set("tMempool", { txid: "tMempool", vin: [], 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: [], vout: [beaconVout(), opreturnVout("TREG", "y", {}), mintVout(CAT_Y, "y")] });
|
||
|
|
txs.set("tNameUnderY", { txid: "tNameUnderY", vin: [], 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);
|