chore: checkpoint in-flight work across domains Sweep of accumulated uncommitted work on design/dex-p2p-architecture into one checkpoint (multiple sessions share this tree). Main areas: - anthemus: covenant-wiring follow-ups (engine, ExchangePanel), AgoraScreen seam - spv: ServerPool + p2p header transport (pool.ts, core/spv/*), wallet.ts wiring - walletconnect: responder/useConnect updates + connect-responder-compat test - wallet: send-tier-a rework + swap.ts, plus send-tier-a unit tests - asm: chipnet throughput/stress harness (scripts/chipnet-asm-*) + ASM_Native docs - app UI: screens/* and shell.css touch-ups; sat -> bits copy migration - docs: new snapshots (INDEX regenerated), Parameters (WalletConnect/SSH/ASM), KnowledgeBase, WALLET-COORDINATION changelog Checkpoint only; not a release. Secrets (Keys/, .chipnet-keys*, .env) remain gitignored and were verified absent from the staged set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
75 lines
4.9 KiB
JavaScript
75 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* chipnet-asm-chain-depth.mjs — measure how many CHAINED claims stack in the
|
|
* mempool before the node rejects (the per-reservoir throughput ceiling between
|
|
* blocks). One wallet claims from the reservoir repeatedly WITHOUT waiting for
|
|
* confirmations: each claim spends the previous claim's new reservoir coin AND
|
|
* its own BCH change, so every tx's only parent is the previous one — a single
|
|
* linear unconfirmed chain. We keep going until a broadcast is rejected and
|
|
* report the depth reached + the verbatim rejection. Chipnet only.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { TestNetWallet } from 'mainnet-js';
|
|
import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
|
|
|
|
const ROOT = join(import.meta.dirname, '..');
|
|
const ARTIFACT = JSON.parse(readFileSync(join(ROOT, 'contracts/dist/DeviantOpenMintFT.json'), 'utf8'));
|
|
const ASM = JSON.parse(readFileSync(join(ROOT, 'ChipnetTests', '.chipnet-asm-token.json'), 'utf8'));
|
|
const W = (process.env.WALLET ?? 'a').toLowerCase();
|
|
const AMOUNT = 550n, RATE = BigInt(ASM.rateSats), FEE_BPS = BigInt(ASM.founderFeeBps), MINER = 3000n;
|
|
const MAX = Number(process.env.MAX ?? '70');
|
|
|
|
const KEY = (w) => join(ROOT, w === 'a' ? '.chipnet-keys.json' : `.chipnet-keys-${w}.json`);
|
|
const hexToBin = (h) => { const o = new Uint8Array(h.length / 2); for (let i = 0; i < o.length; i++) o[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16); return o; };
|
|
const rev = (h) => { const d = hexToBin(h); const o = new Uint8Array(d.length); for (let i = 0; i < d.length; i++) o[i] = d[d.length - 1 - i]; return o; };
|
|
const p2 = (pkh) => { const o = new Uint8Array(25); o[0] = 0x76; o[1] = 0xa9; o[2] = 0x14; o.set(pkh, 3); o[23] = 0x88; o[24] = 0xac; return o; };
|
|
|
|
async function main() {
|
|
const { wif } = JSON.parse(readFileSync(KEY(W), 'utf8'));
|
|
const wallet = await TestNetWallet.fromWIF(wif);
|
|
const sig = new SignatureTemplate(wallet.privateKey);
|
|
const provider = new ElectrumNetworkProvider('chipnet');
|
|
const contract = new Contract(ARTIFACT, [rev(ASM.categoryId), RATE, FEE_BPS, hexToBin(ASM.founderLockingBytecodeHex)], { provider });
|
|
const recipLB = p2(wallet.publicKeyHash);
|
|
const gross = AMOUNT * RATE, fee = (gross * FEE_BPS) / 10_000n;
|
|
|
|
let reservoir = (await provider.getUtxos(contract.tokenAddress)).find((u) => u.token?.nft?.capability === 'minting' && u.token?.category === ASM.categoryId && u.token?.amount > 0n);
|
|
if (!reservoir) throw new Error('reservoir not found');
|
|
let bch = (await provider.getUtxos(wallet.cashaddr)).filter((u) => !u.token).sort((a, b) => Number(b.satoshis) - Number(a.satoshis))[0];
|
|
if (!bch) throw new Error('no BCH');
|
|
console.log(`Chaining claims from reservoir ${reservoir.txid.slice(0, 12)}…:${reservoir.vout}, funding UTXO ${BigInt(bch.satoshis)} sats\n`);
|
|
|
|
let depth = 0, rejection = null;
|
|
for (let i = 1; i <= MAX; i++) {
|
|
const change = BigInt(bch.satoshis) - gross - fee - MINER; // reservoir 1000 in/out cancels
|
|
if (change < 546n) { rejection = `ran out of BCH (change ${change} < dust) at depth ${i}`; break; }
|
|
const tb = new TransactionBuilder({ provider });
|
|
tb.addInput(reservoir, contract.unlock.claim(AMOUNT, recipLB));
|
|
tb.addInput(bch, sig.unlockP2PKH());
|
|
tb.addOutput({ to: contract.tokenAddress, amount: 1000n, token: { amount: reservoir.token.amount - AMOUNT, category: ASM.categoryId, nft: { capability: 'minting', commitment: '' } } });
|
|
tb.addOutput({ to: wallet.tokenaddr, amount: gross, token: { amount: AMOUNT, category: ASM.categoryId } });
|
|
tb.addOutput({ to: ASM.founderAddress, amount: fee });
|
|
tb.addOutput({ to: wallet.cashaddr, amount: change });
|
|
try {
|
|
const sent = await tb.send();
|
|
const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
|
depth = i;
|
|
if (i % 5 === 0 || i <= 3) console.log(` depth ${i}: ✓ ${txid.slice(0, 16)}… (unconfirmed ancestors: ${i - 1})`);
|
|
reservoir = { txid, vout: 0, satoshis: 1000n, token: { amount: reservoir.token.amount - AMOUNT, category: ASM.categoryId, nft: { capability: 'minting', commitment: '' } } };
|
|
bch = { txid, vout: 3, satoshis: change };
|
|
} catch (e) {
|
|
rejection = (e?.message ?? String(e)).replace(/\s+/g, ' ').slice(0, 200);
|
|
console.log(` depth ${i}: ✗ REJECTED — ${rejection}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
console.log(`\n=== CHAIN DEPTH RESULT ===`);
|
|
console.log(` Max chained UNCONFIRMED claims accepted on one reservoir: ${depth}`);
|
|
console.log(` Stopped by: ${rejection ?? `reached MAX=${MAX} without rejection`}`);
|
|
console.log(` → this is the per-reservoir in-flight ceiling between blocks; a block confirms the`);
|
|
console.log(` chain and it resets. K reservoirs multiply this ceiling by K (and a sequencer`);
|
|
console.log(` keeps the chain collision-free).`);
|
|
}
|
|
main().then(() => process.exit(0)).catch((e) => { console.error('ERROR:', e?.message ?? e); process.exit(1); });
|