Orgchain v1.9: lineage/v0 as an ordinals metaprotocol, rehearsed on regtest
SPEC.md writes lineage/v0 down as an ordinals metaprotocol: the envelope fields, chain order, authority by verified parents, every rule, ownership by sat. bin/lineage-index.mjs reads lineage events from a real ord server and folds them with the same reducer the lab uses. tools/regtest rehearses the sample on Bitcoin Core regtest with standardness on: the lab's own action code signs on a chain funded only by coins imported from regtest (LocalChain.importCoin), each beat's transactions are broadcast byte for byte and mined, ord indexes them, and --compare checks the same events in the same beats, the same metadata and parents, every inscription in the same output and the same trees. tests/indexer.test.js replays a recorded run. The first rehearsal found that reveals committed to tapscript's re-encoded tapleaf, which real Bitcoin rejects. They now commit to the exact script bytes, and the local chain checks script paths per BIP-341/342, still loading reveals made before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
This commit is contained in:
1 parent
7d981341ff
commit
7c9306402d
21 files changed
+602
-21
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"from":120,"ord":{"/blockheight":{"status":200,"body":"131"},"/inscriptions/block/120/0":{"status":200,"body":"{\"ids\":[],\"more\":false,\"page_index\":0}"},"/inscriptions/block/121/0":{"status":200,"body":"{\"ids\":[],\"more\":false,\"page_index\":0}"},"/inscriptions/block/122/0":{"status":200,"body":"{\"ids\":[],\"more\":false,\"page_index\":0}"},"/inscriptions/block/123/0":{"status":200,"body":"{\"ids\":[\"ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0\"],\"more\":false,\"page_index\":0}"},"/inscription/ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0":{"status":200,"body":"{\"address\":\"bcrt1pmhndmcwyd3qt3lzq7x2fyxa62r8fpj9rn4gr05un6fxt35ft2uzs3tdaf7\",\"charms\":[],\"child_count\":3,\"children\":[\"ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0\",\"4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0\",\"c7d79e81fb104160f3843649d958ab9f56d49df4390fb02b93828021d8dd34dci0\"],\"content_length\":17,\"content_type\":\"text/plain;charset=utf-8\",\"effective_content_type\":\"text/plain;charset=utf-8\",\"fee\":500,\"height\":123,\"id\":\"ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0\",\"metaprotocol\":\"lineage/v0\",\"next\":\"ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0\",\"number\":0,\"parents\":[],\"previous\":null,\"properties\":{\"gallery\":[],\"attributes\":{\"title\":null,\"traits\":{}}},\"rune\":null,\"sat\":null,\"satpoint\":\"c7d79e81fb104160f3843649d958ab9f56d49df4390fb02b93828021d8dd34dc:0:0\",\"timestamp\":1792054032,\"value\":1000}"},"/r/metadata/ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0":{"status":200,"body":"\"a2626f7065666f756e64646e616d656b796f6c6f7072656e657572\""},"/inscriptions/block/124/0":{"status":200,"body":"{\"ids\":[\"ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0\",\"4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0\"],\"more\":false,\"page_index\":0}"},"/inscription/ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0":{"status":200,"body":"{\"address\":\"bcrt1p436wl2jp50ceq825lm0em2mtvyafeqvf09ggsa9ku6z9rusqat7q40jzp5\",\"charms\":[\"vindicated\"],\"child_count\":4,\"children\":[\"e29b26c17d48c09e72109ae42a833fb24e000cf89790ebbaebea6d3a884fc1bdi0\",\"3d9b514edc1956d5f4fef3edec7267c4920ce81449b8c239e317a53fe0a81885i0\",\"1c8f2b1f187d04c6fd68cb8d2075739253a0ec8642cf6a21e6dfa89a04c61f45i0\",\"e31369d6146a043c538280f0db13d8301a49269835a78ef5dd1f74fbd3769ffei0\"],\"content_length\":16,\"content_type\":\"text/plain;charset=utf-8\",\"effective_content_type\":\"text/plain;charset=utf-8\",\"fee\":500,\"height\":124,\"id\":\"ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0\",\"metaprotocol\":\"lineage/v0\",\"next\":\"4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0\",\"number\":1,\"parents\":[\"ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0\"],\"previous\":\"ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0\",\"properties\":{\"gallery\":[],\"attributes\":{\"title\":null,\"traits\":{}}},\"rune\":null,\"sat\":null,\"satpoint\":\"e31369d6146a043c538280f0db13d8301a49269835a78ef5dd1f74fbd3769ffe:0:0\",\"timestamp\":1792054033,\"value\":1000}"},"/r/metadata/ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0":{"status":200,"body":"\"a3626f70656269727468646e616d656a77656c6c2d6b6e6f776e646b696e64636f7267\""},"/inscription/4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0":{"status":200,"body":"{\"address\":\"bcrt1ps07srr40g8v2t4e9larkeecct56xj67ue5ead0tuq50lj8888cms3d2xx6\",\"charms\":[\"vindicated\"],\"child_count\":9,\"children\":[\"82d9ba7ae7dd4252fd5bae99d04c70feafcc4f93b0e716e8f8738f914bbea1f6i0\",\"97cd35c5cb52333b987dcea052cd82a7bc9c64a5d2a5fb8c831b42fa63565c8fi0\",\"9414b00451ba60700c236e36c4ead1b86e73d9a76b1e96a46e2f851cc07c6e5fi0\",\"1b48e841f2dbee9cd49a59872ba3ad880d2498c29fed351f54870379982bd10ai0\"],\"content_length\":10,\"content_type\":\"text/plain;charset=utf-8\",\"effective_content_type\":\"text/plain;charset=utf-8\",\"fee\":500,\"height\":124,\"id\":\"4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0\",\"metaprotocol\":\"lineage/v0\",\"next\":\"82d9ba7ae7dd4252fd5bae99d04c70feafcc4f93b0e716e8f8738f914bbea1f6i0\",\"number\":2,\"parents\":[\"ee6cabd275c53011248727e064c0be3841799b7391bc337520d9aac1ebdc60d1i0\"],\"previous\":\"ac1d4d0cfaeb19d99bc4c6e829d64100b598f2a3cfa538348f230ae3fe09e685i0\",\"properties\":{\"gallery\":[],\"attributes\":{\"title\":null,\"traits\":{}}},\"rune\":null,\"sat\":null,\"satpoint\":\"5eaebae86b7957b427121d471de2ff14099467eaa16fded137f7b641ca55788f:0:0\",\"timestamp\":1792054033,\"value\":1000}"},"/r/metadata/4375d999401ea1996d031bddfc2192170e2f8519ada1ff2a5bc77a5eb3b8a514i0":{"status":200,"body":"\"a3626f70656269727468646e616d656474696d65646b696e64636f7267\""},"/inscriptions/block/125/0":{"status":200,"body":"{\"ids\":[\"82d9ba7ae7dd4252fd5bae99Line truncated
|
||||
@@ -0,0 +1,78 @@
|
||||
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.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, 21);
|
||||
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', 'acme']);
|
||||
assert.deepEqual(draw(s, id('yolopreneur')), ['● yolopreneur', '└─ orgs', " ├─ 0' well-known", ' │ └─ orgs', " │ ├─ 0' mailr",
|
||||
" │ └─ 1' lineage · graduated", " └─ 1' time", ' ├─ agents', " │ ├─ 0' luna", " │ ├─ 1' sol", " │ └─ 2' astra",
|
||||
' └─ orgs', " ├─ 0' docscope", " ├─ 1' dollhouse", " ├─ 2' doodleloop", " └─ 3' hearthframe"]);
|
||||
assert.deepEqual(census(s, id('lineage')), { orgs: 0, agents: 0, belts: 0, 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']]);
|
||||
// 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 lineage's deal put it). Only the miner's order within blocks differs.
|
||||
const { problems, reordered } = compareWithLab({ chain: new LocalChain(fixture.chain) }, events);
|
||||
assert.deepEqual(problems, []);
|
||||
assert.equal(reordered, 9);
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
Reference in new issue
Block a user