registrar: an older commit can still register a name inside its contest window

A front-runner's registration makes the name look taken, and the
registrar refused to reveal - exactly when revealing is what wins the
name back. The availability check now says whether the contest is open
and what a challenger must beat, and a reveal whose commit is older goes
ahead. Index entries record the commit a registration revealed, so that
comparison can be made.
This commit is contained in:
Local Dev 2026-10-05 22:34:00 +02:00
parent d19cd6bde7
commit cc683130b5
3 changed files with 26 additions and 4 deletions

View file

@ -296,6 +296,7 @@ export async function buildIndex(provider, { enforceTld = false, cosignGate = tr
const entry = {
name, category: mint.tokenData.category, records: payload.r ?? {},
txid: tx_hash, height, updatedTxid: null,
...(won.commit ? { commit: { height: won.commit.height, txid: won.commit.txid } } : {}),
};
names.set(name, entry);
byCategory.set(entry.category, entry);

View file

@ -109,11 +109,21 @@ export async function checkAvailability(client, name) {
// (PROTOCOL-ADDENDUM-release.md, the one-block gap).
const releasedAt = index.released?.get(key);
const releasePending = releasedAt !== undefined && !(releasedAt > 0);
// A registration can still be displaced, for contestBlocks after it is
// mined, by one whose commit is older (PROTOCOL-ADDENDUM-commit-reveal.md).
// `contest` says whether that window is open and what a challenger must beat.
const contestBlocks = NETWORK.commitReveal?.contestBlocks ?? 0;
const contest = entry ? {
open: !(entry.height > 0) || (index.height ?? 0) + 1 <= entry.height + contestBlocks,
finalAt: entry.height > 0 ? entry.height + contestBlocks + 1 : null,
rank: entry.commit ? [entry.commit.height, entry.commit.txid] : [entry.height > 0 ? entry.height : Infinity, entry.txid],
} : null;
return {
name: key,
rawName: raw,
available: !entry && !releasePending,
entry,
contest,
releasedAt: releasedAt ?? null,
releasePending,
reason: entry ? "registered" : releasePending ? "release-pending" : null,
@ -332,19 +342,27 @@ export async function registerWithBuiltInWallet(client, {
commit = null,
onProgress = () => {},
}) {
let committed = null;
if (commit) {
if (normalizeName(commit.name) !== normalizeName(name)) throw new Error("this commit was made for another name");
if (commit.lock !== binToHex(addressToLockingBytecode(wallet.address))) throw new Error("this commit was made for another owner");
const state = await commitState(client, commit);
const state = committed = await commitState(client, commit);
if (!state.mature) throw new Error(state.expired
? "this commit is too old to reveal — commit again"
: "this commit is not in a block yet — the registration can be sent after the next block");
}
onProgress("checking-availability");
const availability = await checkAvailability(client, name);
if (!availability.available) throw new Error(availability.releasePending
? `"${availability.name}" was just released — it can be registered once that release is in a block`
: `"${availability.name}" is already registered`);
if (!availability.available) {
// Somebody registered it first — perhaps by copying this very name out of
// the mempool. An older commit still takes it while the contest is open.
const c = availability.contest;
const older = committed && c && (committed.height < c.rank[0] || (committed.height === c.rank[0] && commit.txid < c.rank[1]));
if (!(c?.open && older)) throw new Error(availability.releasePending
? `"${availability.name}" was just released — it can be registered once that release is in a block`
: `"${availability.name}" is already registered`);
onProgress("contesting", { incumbent: availability.entry.txid, finalAt: c.finalAt });
}
onProgress("loading-coins");
let utxos = await getUtxosForAddresses(client, wallet.watchedAddresses);

View file

@ -811,6 +811,8 @@ export async function buildIndex({ network = NETWORK, electrum = getNetwork(netw
names.set(key, {
name: key, category: mint.tokenData.category, records: payload.r ?? {},
txid: tx_hash, height, updatedTxid: null,
// the commit this registration revealed, when it had one
...(won.commit ? { commit: { height: won.commit.height, txid: won.commit.txid } } : {}),
// The mint output's scriptPubKey.address is the initial owner —
// needed for the signed-records-manifest write path (POST
// /api/records/<name>) so the gateway can reject a signed
@ -918,6 +920,7 @@ export function buildIndexFromSnapshot({ snapshot, enforceTld = false, tldMap =
names.set(key, {
name: key, category: mint.tokenData.category, records: payload.r ?? {},
txid: tx_hash, height, updatedTxid: null,
...(won.commit ? { commit: { height: won.commit.height, txid: won.commit.txid } } : {}),
owner: ownerFromVout(mint),
});
// registered straight into the registry covenant?