Files
teahubxyzandClaude Opus 5.5 5a7f11e49d Orgchain v1.14.1: a belt counts only on its seat's own sat
Placement is now a rule of lineage/v0, not just how Orgchain inscribes:
an award counts only if it is inscribed on its recipient's own sat, so any
indexer can check that a belt is soulbound. The reducer compares sats
(satOf): the lab's sat numbers, ord's, or, without ord's sat index, its
satpoints, which two inscriptions share exactly when they share a sat. ord
events now carry the sat number when ord has one. Belts inscribed anywhere
else, as before v1.14, are rejected.

A promotion is another reinscription on the same sat, over the belt before
it: each badge keeps its skill's earlier belts, and Inspect shows the
ladder ("re-inscribed over white (beat 5)"). The sample's sol now goes
white, then green. An award and its recipient's own close no longer
conflict within a block, since both spend the recipient's inscription.

Rehearsed again on Bitcoin Core regtest from height 178: sixteen beats and
133 transactions; ord shows sol's sat carrying sol, its white belt and its
green belt, in order, each belt charmed reinscription and vindicated, and
the compare matches. The fixture is re-recorded.

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

103 lines
8.6 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, 35);
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 on the same sat
// (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, 17);
// Belts ride their seats' own sats, or they wouldn't count: 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). Sol's launch belt went white, then green: two reinscriptions on its sat, and the record keeps
// the ladder.
const on = new Map(events.map(e => [e.id, e])), charmed = ['reinscription', 'vindicated'];
assert.deepEqual(s.log.filter(e => e.op === 'award').map(e => [s.nodes.get(e.meta.to).name, e.meta.belt, on.get(e.id).satpoint === on.get(e.meta.to).satpoint, on.get(e.id).charms]),
[['sol', 'white', true, charmed], ['sol', 'green', true, charmed], ['luna', 'white', true, charmed], ['scout', 'white', true, charmed]]);
assert.deepEqual(s.nodes.get(id('sol')).badges.map(b => [b.skill, b.belt, b.earlier.map(x => x.belt)]), [['launch', 'green', ['white']]]);
});
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
});