Files
orgchain/tests/indexer.test.js
T
teahubxyzandClaude Opus 5.5 c8c18b073e Orgchain v1.14: soulbound belts ride their seats' sats
A belt is soulbound: the reducer already bound it to the seat it names,
and now the lab inscribes it on that seat's own sat, so by ordinal theory
it goes wherever the seat goes and never sells apart from it. One reveal
carries an award, three coins in: the seat's, which the pointer puts the
belt in and the seat's own key signs, accepting it; the issuer's, the
parent; and the commit, which pays the fee. Both coins come back unchanged,
so an award needs no postage and costs two fees (1,000 sats). ord marks it
a reinscription and vindicated, since a pointer and an envelope past the
first input would have cursed it before the jubilee. The engine takes that
three-input reveal and nothing looser: the parent spent from the
inscriber's key, both coins returned unchanged, the pointer on an inscribed
sat of the first.

A belt is never a lot, and an award can't land in a tree that is listed or
sold. Trees count and split by inscribed sat, so a seat's belts stay on it
through every sale. Inspect shows a node's belts and ord's charms, and the
award form says whose sat the belt will ride. Belts from before v1.14 stay
with their issuers and count the same.

Rehearsed on Bitcoin Core regtest from height 161: sixteen beats and 131
transactions; ord charms the three belts as the lab does, and after two
whole-tree sales each belt shares its seat's satpoint. The compare now
checks satpoints and charms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
2026-10-15 15:48:31 -07:00

100 lines
8.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { Script, Tap, Tx } from '@cmdcode/tapscript';
import { LocalChain, tapscriptValid } from '../src/adapters/chain.js';
import { TestWallet } from '../src/adapters/wallet.js';
import { InscriptionAdapter, POSTAGE, FEE, envelope } from '../src/adapters/inscription.js';
import { MetadataIndex } from '../src/adapters/metadata-index.js';
import { getVin, getVout } from '../src/upstream/primitives.js';
import { census, rows } from '../src/orgchain/lineage.js';
import { ordClient, ordEvents, ordState } from '../src/indexer/ord.js';
import { compareWithLab } from '../src/indexer/compare.js';
// What ord answered after tools/regtest/rehearse.mjs, and the lab's public chain (tools/regtest/record.mjs).
const fixture = JSON.parse(readFileSync(new URL('./fixtures/regtest-rehearsal.json', import.meta.url), 'utf8'));
const replay = async url => { const hit = fixture.ord[new URL(url).pathname]; return new Response(hit?.body ?? '', { status: hit?.status ?? 404 }); };
const draw = (s, root) => rows(s, root).map(r => (r.type === 'group' ? `${r.guide}${r.label}` : `${r.guide}${r.path} ${r.node.name}${r.moved ? ' · moved' : r.away ? ' · graduated' : ''}`));
const NUMS = ['OP_1', '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0'];
const hex = script => Script.encode(script, false).hex;
test('real ord indexed the regtest rehearsal, and the same reducer folds it into the trees the lab drew', async () => {
const events = await ordEvents(ordClient('http://ord.test', replay), { from: fixture.from });
assert.equal(events.length, 34);
assert.ok(events.every(e => e.protocol === 'lineage/v0' && e.parents <= 1 && /^[0-9a-f]{64}:\d+$/.test(e.location)));
const s = ordState(events), id = name => [...s.nodes.values()].find(n => n.name === name).id;
assert.ok(s.log.every(e => e.ok));
assert.deepEqual(s.roots.map(r => r.name), ['yolopreneur', 'lineage']);
assert.deepEqual(draw(s, id('yolopreneur')), ['● yolopreneur', '└─ orgs', " ├─ 0' well-known", ' │ └─ orgs', " │ ├─ 0' mailr · moved",
" │ └─ 1' lineage · graduated", " └─ 1' time · moved"]);
// Four trees changed hands: mailr on well-known's ask, which lineage took; docscope on acme's offer, which time
// accepted; time with its six below it, on acme's offer, which yolopreneur accepted; and acme whole, on its owner's
// ask, which lineage took. Each joined its buyer's tree at the buyer's next index, its tree with it.
assert.deepEqual(draw(s, id('lineage')), ['● lineage', '└─ orgs', " ├─ 0' mailr", " └─ 1' acme", ' ├─ agents', " │ └─ 0' scout",
' └─ orgs', " ├─ 0' docscope", " └─ 1' time", ' ├─ agents', " │ ├─ 0' luna", " │ ├─ 1' sol",
" │ └─ 2' astra", ' └─ orgs', " ├─ 0' docscope · moved", " ├─ 1' dollhouse",
" ├─ 2' doodleloop", " └─ 3' hearthframe"]);
assert.deepEqual([...s.nodes.values()].flatMap(n => n.moves.map(m => [n.name, s.nodes.get(m.from)?.name ?? null, s.nodes.get(m.to).name])),
[['time', 'yolopreneur', 'acme'], ['mailr', 'well-known', 'lineage'], ['docscope', 'time', 'acme'], ['acme', null, 'lineage']]);
assert.deepEqual(census(s, id('lineage')), { orgs: 7, agents: 4, belts: 3, decisions: 1, closed: 0 });
assert.deepEqual(s.nodes.get(id('scout')).badges.map(b => [b.skill, b.belt, s.nodes.get(b.by).name]), [['listen', 'white', 'well-known']]);
assert.deepEqual([...s.items.values()].map(i => [i.name, s.nodes.get(i.maker).name]), [['launch-kit', 'sol']]);
// Five keyrings, their proofs checked from ord's metadata (two 520-byte pushes each): lineage's first retired when it
// graduated, and the one it published a block later stands; sol's and scout's retired when their trees changed hands,
// since their keys changed with them. Two keyrings, four keys each, are bound.
assert.deepEqual(s.log.filter(e => e.op === 'keyring').map(e => s.nodes.get(e.node).name).sort(), ['lineage', 'lineage', 'scout', 'sol', 'yolopreneur']);
assert.deepEqual([...s.nodes.values()].filter(n => n.keyring).map(n => [n.name, n.keyring.retired?.cause ?? null]).sort(),
[['lineage', null], ['scout', 'move'], ['sol', 'move'], ['yolopreneur', null]]);
assert.equal(s.bound.size, 8);
// Byte for byte: the same events in the same beats, the same metadata and parents, every inscription in the same
// output (the launch kit where scout's take of lineage's ask put it, and with scout's tree split back at lineage's
// keys). Only the miner's order within blocks differs, a join and its adoption included.
const { problems, reordered } = compareWithLab({ chain: new LocalChain(fixture.chain) }, events);
assert.deepEqual(problems, []);
assert.equal(reordered, 15);
// Belts ride their seats' own sats: on ord, each award shares its recipient's satpoint, through both trees' sales and
// their splits, and is marked ♻️ reinscription (and ❤️‍🔥 vindicated: it was inscribed from the reveal's third input).
const on = new Map(events.map(e => [e.id, e]));
assert.deepEqual(s.log.filter(e => e.op === 'award').map(e => [s.nodes.get(e.meta.to).name, on.get(e.id).satpoint === on.get(e.meta.to).satpoint, on.get(e.id).charms]),
[['sol', true, ['reinscription', 'vindicated']], ['luna', true, ['reinscription', 'vindicated']], ['scout', true, ['reinscription', 'vindicated']]]);
});
test('a reveal commits to its exact script (BIP-341), and the lab still loads reveals made the old way', () => {
const chain = new LocalChain(), w = TestWallet.create();
chain.mine(w.script);
new InscriptionAdapter(chain, w).inscribe('found x', null, { op: 'found', name: 'x' }, { metaprotocol: 'lineage/v0' });
const prevout = raw => { const out = Tx.decode(raw).vout[0]; return { value: Number(out.value), scriptPubKey: out.scriptPubKey }; };
const reveal = Tx.decode(chain.mempool[1]);
reveal.vin[0].prevout = prevout(chain.mempool[0]);
assert.equal(tapscriptValid(reveal, 0), true);
// Before v1.9 the leaf came from tapscript's encodeScript, which re-encodes the hex script; real Bitcoin rejected that.
const old = new LocalChain();
old.mine(w.script);
const [coin] = old.coins(w.script), script = envelope(w.pubkey, 'found y', null, { op: 'found', name: 'y' }, { metaprotocol: 'lineage/v0' });
const leaf = Tap.encodeScript(script), [tapkey, control] = Tap.getPubKey(w.pubkey, { target: leaf });
const commit = Tx.create({ vin: [getVin(coin.txid, coin.vout, coin.value, coin.scriptPubKey)],
vout: [getVout(POSTAGE + FEE, ['OP_1', tapkey]), getVout(coin.value - POSTAGE - 2 * FEE, w.script)] });
commit.vin[0].witness = [w.sign(commit)];
const legacy = Tx.create({ vin: [getVin(Tx.util.getTxid(commit), 0, POSTAGE + FEE, ['OP_1', tapkey])], vout: [getVout(POSTAGE, w.script)] });
legacy.vin[0].witness = [w.sign(legacy, leaf, 0), Script.encode(script, false).hex, control];
const raws = [Tx.encode(commit).hex, Tx.encode(legacy).hex], check = Tx.decode(raws[1]);
check.vin[0].prevout = prevout(raws[0]);
assert.equal(tapscriptValid(check, 0), false);
old.submitPackage(raws); // still accepted, so labs saved before v1.9 load
assert.equal(new MetadataIndex(old).events.length, 1);
});
test('a chain can start from coins imported from a real chain, and keeps them through a rebuild', () => {
const chain = new LocalChain(), w = TestWallet.create(), coin = { txid: 'ab'.repeat(32), vout: 1, value: 2e8, script: hex(w.script) };
chain.importCoin(coin);
assert.throws(() => chain.importCoin(coin), /already in this chain/);
assert.throws(() => chain.importCoin({ ...coin, vout: 2, value: 51e8 }), /Invalid imported coin/);
const id = new InscriptionAdapter(chain, w).inscribe('found x', null, { op: 'found', name: 'x' }, { metaprotocol: 'lineage/v0' });
chain.mine(NUMS);
const copy = new LocalChain(chain.snapshot());
assert.deepEqual(copy.imports, [coin]);
assert.deepEqual(copy.imported(coin.txid, 1), { value: 2e8, scriptPubKey: coin.script });
assert.deepEqual(new MetadataIndex(copy).events.map(e => [e.id, e.status]), [[id, 'confirmed']]);
assert.equal(copy.coins(w.script).length, 2); // the reveal's postage and the commit's change
});