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
103 lines
8.6 KiB
JavaScript
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
|
|
});
|