78 lines
5.9 KiB
JavaScript
78 lines
5.9 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.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
|
||
|
|
});
|