bns: the blocklist consumer, shipped with an empty list
A resolver that learns about blocklists only after the first scam needs a release before it can warn anyone. The consumer ships now, pointed at a list with no entries, so the first real report costs a transaction instead. A list is an address, named by a BNS name whose bl record holds it. Entries are RPT (the report), BLK and UBK. A flag counts only when it names a valid report in the same list for the same name and reason, and only when its transaction is spent FROM the list address: anyone can pay an address, so being in its history proves nothing. That spend rule is what lets the authority change hands later without the resolver learning the difference. resolveName attaches blocked, blockReason, blockReportTxid and blockedBy to a flagged name and leaves every other answer untouched. With no subscription it is the old resolver exactly. A list that cannot be read never stops a name from resolving. Lists are read from full electrum servers: the project's beacon-only indexers answer 'no history' for any other address, which would look like an empty list. The default list is sirius-blocklist.x, a two-part name because a three-part one collapses to its parent. Address conversion is dependency-free so this file stays a single-file drop-in.
This commit is contained in:
parent
fe62da3e5e
commit
34e336258b
3 changed files with 312 additions and 4 deletions
|
|
@ -93,6 +93,14 @@ once per index build. Implemented identically in `src/lib/resolver-web.js` and
|
|||
`src/lib/bns.js` — **the two must stay in lockstep**, since a disagreement
|
||||
between them is a consensus split between clients.
|
||||
|
||||
## Blocklist (resolver-side)
|
||||
|
||||
Not a chain rule. A resolver may subscribe to lists of flagged names and warn
|
||||
about, or refuse, a name a list flags; the name stays registered and any
|
||||
resolver that does not subscribe resolves it as before. The messages (`RPT`,
|
||||
`BLK`, `UBK`), the authority rule and the client behaviour are in
|
||||
`PROTOCOL-ADDENDUM-blocklist.md`.
|
||||
|
||||
## Honest limitations (prototype vs production)
|
||||
|
||||
- Uniqueness is **indexer policy**, not consensus. Production: a covenant-run
|
||||
|
|
|
|||
14
src/lib/resolver-web.d.ts
vendored
14
src/lib/resolver-web.d.ts
vendored
|
|
@ -56,11 +56,25 @@ export interface BnsNetwork {
|
|||
operatorAddress: string | null;
|
||||
accountPath: string;
|
||||
bchnRpcPort: number;
|
||||
blocklists: { name: string; beacon: string; scripthash?: string }[];
|
||||
}
|
||||
/** REL: does `tx` burn `entry`'s certificate, spending it from a beacon transaction in `txs`? */
|
||||
export function burnsCertificate(tx: any, entry: { category: string }, txs: Map<string, any>): boolean;
|
||||
/** One-block gap after a release. `releasedAt` undefined = never released. */
|
||||
export function mayReRegister(releasedAt: number | undefined, height: number): boolean;
|
||||
export interface BlockFlag {
|
||||
name: string; reason: string; reportTxid: string; txid: string; height: number;
|
||||
evidence: string | null; vote: string | null; source: string | null; address: string;
|
||||
}
|
||||
export function sha256(bytes: Uint8Array): Uint8Array;
|
||||
export function addressToLockHex(address: string): string;
|
||||
export function addressToScripthash(address: string): string;
|
||||
export const BLOCK_REASONS: Set<string>;
|
||||
/** Reduce one list's history to its current flags (pure). */
|
||||
export function reduceBlocklist(o: { history: { tx_hash: string; height: number }[]; txs: Map<string, any> | Record<string, any>; address: string; source?: string | null }): Map<string, BlockFlag>;
|
||||
export function blocklistAddress(list: { name?: string; beacon?: string }, index?: Map<string, any> | null): string | null;
|
||||
export function buildBlocklists(o?: { network?: any; lists?: { name?: string; beacon?: string }[]; index?: Map<string, any> | null; electrum?: any; WebSocket?: any; client?: any; directIP?: boolean }): Promise<Map<string, BlockFlag & { sources: BlockFlag[] }>>;
|
||||
export function withBlockFlag<T extends { name: string }>(entry: T, flags: Map<string, BlockFlag> | null): T;
|
||||
export const NETWORKS: { chipnet: BnsNetwork; mainnet: BnsNetwork };
|
||||
export const DEFAULT_NETWORK_ID: "chipnet";
|
||||
export function networkIdFromEnv(): string;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const NETWORKS = {
|
|||
operatorAddress: "bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu",
|
||||
accountPath: "m/44'/145'/0'",
|
||||
bchnRpcPort: 48332,
|
||||
// Blocklists this network's clients subscribe to by default
|
||||
// (Decentralized.DNS/PROTOCOL-ADDENDUM-blocklist.md). A list is a NAME:
|
||||
// its `bl` record holds the list address, so the authority can rotate
|
||||
// on-chain; `beacon` is the address baked in for a client that cannot
|
||||
// read the name yet. An entry counts only when it is spent FROM that
|
||||
// address. The list is empty by design until a first real report.
|
||||
blocklists: [
|
||||
{
|
||||
name: "sirius-blocklist.x",
|
||||
beacon: "bchtest:qrrnwvuasq5fy63mdvp3umx5qfavtu7mnq950rdssw",
|
||||
scripthash: "fa14c5fd7a22bd1856b44168823a1ab3ff50961c917dafbcba749aeaa7b26646",
|
||||
},
|
||||
],
|
||||
},
|
||||
mainnet: {
|
||||
id: "mainnet",
|
||||
|
|
@ -121,6 +134,9 @@ export const NETWORKS = {
|
|||
operatorAddress: null,
|
||||
accountPath: "m/44'/145'/0'",
|
||||
bchnRpcPort: 8332,
|
||||
// NOT PINNED: the blocklist beacon is one of the addresses reserved before
|
||||
// the mainnet beacon goes live (ROADMAP-MAINNET.md §6).
|
||||
blocklists: [],
|
||||
},
|
||||
};
|
||||
export const DEFAULT_NETWORK_ID = "chipnet";
|
||||
|
|
@ -301,15 +317,20 @@ function parseOpReturnHex(hex) {
|
|||
// (Decentralized.DNS/PROTOCOL-ADDENDUM-release.md)
|
||||
// TREG / TUPD — TLD registrations & updates on the TLD beacon
|
||||
// (Decentralized.DNS/DESIGN-tld-registry.md §3)
|
||||
// RPT / BLK / UBK — blocklist report, block and unblock, on a list's
|
||||
// own address (PROTOCOL-ADDENDUM-blocklist.md). An RPT
|
||||
// covers several names, so its `n` is an array.
|
||||
// KEEP IN LOCKSTEP with bns.js parseOpReturn — memory rule.
|
||||
if (KNOWN_OPS.has(p.o) && typeof p.n === "string") return p;
|
||||
if (!KNOWN_OPS.has(p.o)) return null;
|
||||
if (typeof p.n === "string") return p;
|
||||
if (p.o === "RPT" && Array.isArray(p.n) && p.n.every((x) => typeof x === "string")) return p;
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The set of payload opcodes this codec recognises. New opcodes go here AND in
|
||||
// bns.js — divergence is a consensus split.
|
||||
export const KNOWN_OPS = new Set(["REG", "UPD", "REL", "TREG", "TUPD"]);
|
||||
export const KNOWN_OPS = new Set(["REG", "UPD", "REL", "TREG", "TUPD", "RPT", "BLK", "UBK"]);
|
||||
|
||||
// ---- ordering ----
|
||||
|
||||
|
|
@ -694,10 +715,275 @@ export function buildIndexFromSnapshot({ snapshot, enforceTld = false, tldMap =
|
|||
return names;
|
||||
}
|
||||
|
||||
// ---- blocklists (Decentralized.DNS/PROTOCOL-ADDENDUM-blocklist.md) ----------
|
||||
// A resolver-side advisory, not a chain rule: a flagged name is still
|
||||
// registered, the resolver just says so. The consumer ships with an empty
|
||||
// list so the first real report needs a transaction, not a client release.
|
||||
|
||||
// SHA-256 and cashaddr decoding, dependency-free: this file is a single-file
|
||||
// drop-in, and a list is published as an ADDRESS (in a name's `bl` record)
|
||||
// while electrum indexes by scripthash.
|
||||
const SHA256_K = new Uint32Array([
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
]);
|
||||
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
||||
export function sha256(bytes) {
|
||||
const H = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
||||
const len = bytes.length;
|
||||
const padded = new Uint8Array(((len + 9 + 63) >> 6) << 6);
|
||||
padded.set(bytes);
|
||||
padded[len] = 0x80;
|
||||
const dv = new DataView(padded.buffer);
|
||||
dv.setUint32(padded.length - 8, Math.floor((len * 8) / 0x100000000));
|
||||
dv.setUint32(padded.length - 4, (len * 8) >>> 0);
|
||||
const w = new Uint32Array(64);
|
||||
for (let off = 0; off < padded.length; off += 64) {
|
||||
for (let t = 0; t < 16; t++) w[t] = dv.getUint32(off + t * 4);
|
||||
for (let t = 16; t < 64; t++) {
|
||||
const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ (w[t - 15] >>> 3);
|
||||
const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ (w[t - 2] >>> 10);
|
||||
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) | 0;
|
||||
}
|
||||
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
||||
for (let t = 0; t < 64; t++) {
|
||||
const t1 = (h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + SHA256_K[t] + w[t]) | 0;
|
||||
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
||||
h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
|
||||
}
|
||||
H[0] += a; H[1] += b; H[2] += c; H[3] += d; H[4] += e; H[5] += f; H[6] += g; H[7] += h;
|
||||
}
|
||||
const out = new Uint8Array(32);
|
||||
const odv = new DataView(out.buffer);
|
||||
for (let i = 0; i < 8; i++) odv.setUint32(i * 4, H[i]);
|
||||
return out;
|
||||
}
|
||||
const bytesToHex = (b) => [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
||||
|
||||
const CASHADDR_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
function cashaddrPolymod(values) {
|
||||
const GEN = [0x98f2bc8e61n, 0x79b76d99e2n, 0xf33e5fb3c4n, 0xae2eabe2a8n, 0x1e4f43e470n];
|
||||
let c = 1n;
|
||||
for (const v of values) {
|
||||
const c0 = c >> 35n;
|
||||
c = ((c & 0x07ffffffffn) << 5n) ^ BigInt(v);
|
||||
for (let i = 0; i < 5; i++) if ((c0 >> BigInt(i)) & 1n) c ^= GEN[i];
|
||||
}
|
||||
return c ^ 1n;
|
||||
}
|
||||
// cashaddr -> locking bytecode hex. Throws on anything that is not a valid
|
||||
// P2PKH / P2SH address (token-aware forms decode to the same script).
|
||||
export function addressToLockHex(address) {
|
||||
const text = String(address).trim().toLowerCase();
|
||||
const colon = text.indexOf(":");
|
||||
if (colon < 1) throw new Error(`address needs its network prefix: "${address}"`);
|
||||
const prefix = text.slice(0, colon);
|
||||
const data = [...text.slice(colon + 1)].map((ch) => CASHADDR_CHARSET.indexOf(ch));
|
||||
if (data.length < 9 || data.some((v) => v < 0)) throw new Error(`not a cashaddr: "${address}"`);
|
||||
if (cashaddrPolymod([...[...prefix].map((ch) => ch.charCodeAt(0) & 31), 0, ...data]) !== 0n) {
|
||||
throw new Error(`bad cashaddr checksum: "${address}"`);
|
||||
}
|
||||
const payload = [];
|
||||
let acc = 0, bits = 0;
|
||||
for (const v of data.slice(0, -8)) {
|
||||
acc = (acc << 5) | v; bits += 5;
|
||||
while (bits >= 8) { bits -= 8; payload.push((acc >> bits) & 0xff); }
|
||||
}
|
||||
const version = payload[0];
|
||||
const type = (version >> 3) & 0x0f;
|
||||
const hash = bytesToHex(payload.slice(1));
|
||||
const want = [20, 24, 28, 32, 40, 48, 56, 64][version & 7];
|
||||
if (hash.length !== want * 2) throw new Error(`cashaddr hash length mismatch: "${address}"`);
|
||||
if ((type === 0 || type === 2) && want === 20) return "76a914" + hash + "88ac";
|
||||
if ((type === 1 || type === 3) && want === 20) return "a914" + hash + "87";
|
||||
if ((type === 1 || type === 3) && want === 32) return "aa20" + hash + "87";
|
||||
throw new Error(`unsupported address type: "${address}"`);
|
||||
}
|
||||
// The key electrum indexes an address under: sha256(lockingBytecode), reversed.
|
||||
export function addressToScripthash(address) {
|
||||
return bytesToHex(sha256(hexToBytes(addressToLockHex(address))).reverse());
|
||||
}
|
||||
|
||||
// The fixed reason vocabulary (addendum §4). Nothing else is a valid reason.
|
||||
export const BLOCK_REASONS = new Set(["phishing", "impersonation", "malware", "scam-financial", "csam", "stolen"]);
|
||||
|
||||
// AUTHORITY. Anyone can pay dust to an address, so "it is in the list's
|
||||
// history" proves nothing. An entry counts only when its transaction spends
|
||||
// an output locked to the list address — only the holder of the list's key
|
||||
// (or, later, the covenant standing at that address) can produce one.
|
||||
function spendsFromLock(tx, lockHex, txs) {
|
||||
return (tx.vin || []).some((vin) => {
|
||||
const out = spentOutput(vin, txs);
|
||||
return !!(out && out.scriptPubKey && out.scriptPubKey.hex === lockHex);
|
||||
});
|
||||
}
|
||||
const isTxid = (v) => typeof v === "string" && /^[0-9a-f]{64}$/.test(v);
|
||||
function canonicalOrNull(raw) {
|
||||
return typeof raw === "string" && RAWNAME_RE.test(raw) ? canonicalKey(raw) : null;
|
||||
}
|
||||
|
||||
// Reduce ONE list's history to its current flags. Pure: no I/O.
|
||||
// history [{ tx_hash, height }] the list address's electrum history
|
||||
// txs Map(txid -> verbose tx) (or a plain object)
|
||||
// address the list address source: the list's name, for display
|
||||
// Returns Map(name -> { name, reason, reportTxid, txid, height, evidence, vote, source, address }).
|
||||
//
|
||||
// Rules (addendum §3, §5, §6):
|
||||
// - RPT, BLK and UBK all need the authority proof above.
|
||||
// - A report is valid when its type is "block" or "unblock", it lists valid
|
||||
// names, and a "block" report carries a reason from the vocabulary.
|
||||
// - BLK is applied only if `p` names a valid "block" report in this list
|
||||
// that covers the same name with the same reason; UBK only if `p` names a
|
||||
// valid "unblock" report covering the name. The resolver never flags a
|
||||
// name on the word of the address alone.
|
||||
// - Entries apply in causal order; the latest valid one wins.
|
||||
export function reduceBlocklist({ history, txs, address, source = null }) {
|
||||
const lockHex = addressToLockHex(address);
|
||||
const txMap = txs instanceof Map ? txs : new Map(Object.entries(txs || {}));
|
||||
const ordered = orderByDependency([...(history || [])].filter((h) => txMap.has(h.tx_hash)).sort(byHeightThenTxid), txMap);
|
||||
|
||||
const payloadOf = (tx) => {
|
||||
let payload = null;
|
||||
for (const v of tx.vout || []) payload = parseOpReturnHex(v.scriptPubKey?.hex) ?? payload;
|
||||
return payload;
|
||||
};
|
||||
|
||||
// Pass 1: the reports. Looked up by txid, so their position does not matter.
|
||||
const reports = new Map();
|
||||
for (const { tx_hash, height } of ordered) {
|
||||
const tx = txMap.get(tx_hash);
|
||||
const p = payloadOf(tx);
|
||||
if (!p || p.o !== "RPT") continue;
|
||||
if (!spendsFromLock(tx, lockHex, txMap)) continue;
|
||||
if (p.t !== "block" && p.t !== "unblock") continue;
|
||||
const names = (p.n || []).map(canonicalOrNull);
|
||||
if (!names.length || names.some((n) => !n)) continue;
|
||||
if (p.t === "block" && !BLOCK_REASONS.has(p.r)) continue;
|
||||
reports.set(tx_hash, { type: p.t, names: new Set(names), reason: p.r ?? null, evidence: p.e ?? null, vote: p.v ?? null, height });
|
||||
}
|
||||
|
||||
// Pass 2: the flags, latest valid entry per name wins.
|
||||
const flags = new Map();
|
||||
for (const { tx_hash, height } of ordered) {
|
||||
const tx = txMap.get(tx_hash);
|
||||
const p = payloadOf(tx);
|
||||
if (!p || (p.o !== "BLK" && p.o !== "UBK")) continue;
|
||||
if (!spendsFromLock(tx, lockHex, txMap)) continue;
|
||||
const name = canonicalOrNull(p.n);
|
||||
if (!name || !isTxid(p.p)) continue;
|
||||
const report = reports.get(p.p);
|
||||
if (!report || !report.names.has(name)) continue;
|
||||
if (p.o === "BLK") {
|
||||
if (report.type !== "block" || !BLOCK_REASONS.has(p.r) || report.reason !== p.r) continue;
|
||||
flags.set(name, {
|
||||
name, reason: p.r, reportTxid: p.p, txid: tx_hash, height,
|
||||
evidence: p.e ?? report.evidence, vote: report.vote, source, address,
|
||||
});
|
||||
} else {
|
||||
if (report.type !== "unblock") continue;
|
||||
flags.delete(name);
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
// The address a list is read from: the `bl` record of its name when that is a
|
||||
// valid address, else the address baked into the network table.
|
||||
export function blocklistAddress(list, index) {
|
||||
const fromName = list && list.name && index && typeof index.get === "function" ? index.get(list.name)?.records?.bl : null;
|
||||
if (typeof fromName === "string") {
|
||||
try { addressToLockHex(fromName); return fromName; } catch { /* malformed record: fall back */ }
|
||||
}
|
||||
return list?.beacon ?? null;
|
||||
}
|
||||
|
||||
// Read every subscribed list and return the union of their flags:
|
||||
// Map(name -> flag), where `flag.sources` holds one flag per list that blocks
|
||||
// the name (the first is the one shown). `lists` are { name, beacon } records;
|
||||
// pass `index` so a list's address can be taken from its name's `bl` record.
|
||||
// An empty list costs one history call and nothing else.
|
||||
//
|
||||
// A list is an ordinary address, so it must be read from a FULL electrum
|
||||
// server: the project's own indexers serve the name beacons only and would
|
||||
// answer "no history" for it, which reads as an empty list. Without an explicit
|
||||
// `client` or `electrum`, the beacon-only servers are left out of the pool.
|
||||
export async function buildBlocklists({ network = NETWORK, lists, index = null, electrum, WebSocket: WS, client, directIP = false } = {}) {
|
||||
const net = getNetwork(network);
|
||||
const subscribed = lists ?? net.blocklists ?? [];
|
||||
const out = new Map();
|
||||
if (!subscribed.length) return out;
|
||||
const pool = electrum ?? net.electrum.filter((s) => !net.beaconOnly.test(typeof s === "string" ? s : s.url));
|
||||
const el = client ?? (await connectAny(pool, WS, { directIP }));
|
||||
try {
|
||||
for (const list of subscribed) {
|
||||
const address = blocklistAddress(list, index);
|
||||
if (!address) continue;
|
||||
const scripthash = addressToScripthash(address);
|
||||
const history = typeof el.unionHistory === "function"
|
||||
? (await el.unionHistory(scripthash)).history
|
||||
: await el.call("blockchain.scripthash.get_history", [scripthash]);
|
||||
if (!history || !history.length) continue;
|
||||
const txs = new Map();
|
||||
for (const { tx_hash } of history) {
|
||||
try { txs.set(tx_hash, await el.call("blockchain.transaction.get", [tx_hash, true])); }
|
||||
catch { /* unreadable tx — skip it entirely */ }
|
||||
}
|
||||
for (const [name, flag] of reduceBlocklist({ history, txs, address, source: list.name ?? address })) {
|
||||
const existing = out.get(name);
|
||||
if (existing) existing.sources.push(flag);
|
||||
else out.set(name, { ...flag, sources: [flag] });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
} finally {
|
||||
if (!client) el.close();
|
||||
}
|
||||
}
|
||||
|
||||
// An entry as a client should see it: unchanged when no list flags the name,
|
||||
// a COPY carrying the flag when one does. Never mutates the index.
|
||||
export function withBlockFlag(entry, flags) {
|
||||
const flag = entry && flags && typeof flags.get === "function" ? flags.get(entry.name) : null;
|
||||
if (!flag) return entry;
|
||||
return { ...entry, blocked: true, blockReason: flag.reason, blockReportTxid: flag.reportTxid, blockedBy: flag.source };
|
||||
}
|
||||
|
||||
// Resolve a name. With no subscription (`blocklists: []` or `false`) this is
|
||||
// exactly the pre-blocklist resolver. With one, a flagged name comes back with
|
||||
// `blocked`, `blockReason`, `blockReportTxid` and `blockedBy`; what to do about
|
||||
// it — warn, refuse, ignore — is the caller's policy. A list that cannot be
|
||||
// read never stops a name from resolving.
|
||||
export async function resolveName(input, opts = {}) {
|
||||
const name = normalizeName(input);
|
||||
const index = await buildIndex(opts);
|
||||
return index.get(name) ?? null;
|
||||
const net = getNetwork(opts.network);
|
||||
const lists = opts.blocklists === undefined ? (net.blocklists ?? []) : (opts.blocklists || []);
|
||||
if (!lists.length) {
|
||||
const index = await buildIndex(opts);
|
||||
return index.get(name) ?? null;
|
||||
}
|
||||
const el = opts.client ?? (await connectAny(opts.electrum ?? net.electrum, opts.WebSocket, { directIP: !!opts.directIP }));
|
||||
try {
|
||||
const index = await buildIndex({ ...opts, client: el });
|
||||
const entry = index.get(name) ?? null;
|
||||
if (!entry) return null;
|
||||
let flags = null;
|
||||
// The caller's own client is reused; otherwise the list gets its own
|
||||
// connection to a full server (see buildBlocklists).
|
||||
try {
|
||||
flags = await buildBlocklists(opts.client
|
||||
? { network: net, lists, index, client: opts.client }
|
||||
: { network: net, lists, index, WebSocket: opts.WebSocket, directIP: !!opts.directIP });
|
||||
} catch { /* advisory only: an unreadable list must not block resolution */ }
|
||||
return withBlockFlag(entry, flags);
|
||||
} finally {
|
||||
if (!opts.client) el.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Open one electrum connection against the pool and return the raw client, so
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue