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> @
97 lines
4.9 KiB
JavaScript
97 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* chipnet-asm-parallel-test-genesis.mjs — the CORRECTED parallel ASM test.
|
||
*
|
||
* Prior test (chipnet-asm-parallel-test.mjs) used the SHARED-RESERVOIR claim path:
|
||
* every op spent the one reservoir UTXO → 1 accepted, N-1 rejected (contention).
|
||
* That contention is a property of the shared-reservoir DESIGN, not of BCH→MBA.
|
||
*
|
||
* This test uses the per-address model: each wallet SWAPS BCH → its OWN unique
|
||
* self-backed MBA via a CashToken genesis. The token category is derived from
|
||
* that wallet's own input[0] outpoint, so every tx spends a DIFFERENT UTXO and
|
||
* mints a DIFFERENT category. No shared state → N parallel broadcasts, N accepted,
|
||
* ZERO collisions — regardless of concurrent count.
|
||
*
|
||
* Each genesis output is self-backed: amount units carrying amount×RATE sats.
|
||
* Broadcast in parallel via Promise.all. No retries. Chipnet only.
|
||
*/
|
||
import { readFileSync } from 'node:fs';
|
||
import { join } from 'node:path';
|
||
import { TestNetWallet } from 'mainnet-js';
|
||
|
||
const ROOT = join(import.meta.dirname, '..');
|
||
const WALLETS = (process.env.WALLETS ?? 'a,c,d,e').split(',').map((s) => s.trim().toLowerCase());
|
||
const UNITS = BigInt(process.env.UNITS ?? '100'); // FT units to mint
|
||
const RATE = BigInt(process.env.RATE ?? '1000'); // sats backing per unit (self-backed)
|
||
const BACKING = UNITS * RATE; // sats carried on the FT output
|
||
|
||
const KEYS = Object.fromEntries(['a', 'b', 'c', 'd', 'e', 'f', 'g'].map((w) => [
|
||
w, join(ROOT, w === 'a' ? '.chipnet-keys.json' : `.chipnet-keys-${w}.json`),
|
||
]));
|
||
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
|
||
async function main() {
|
||
// Load each wallet.
|
||
const wallets = [];
|
||
for (const w of WALLETS) {
|
||
const { wif } = JSON.parse(readFileSync(KEYS[w], 'utf8'));
|
||
wallets.push({ label: w.toUpperCase(), wallet: await TestNetWallet.fromWIF(wif) });
|
||
}
|
||
|
||
// tip at start (via the first wallet's provider)
|
||
let tip = 0;
|
||
try { tip = await wallets[0].wallet.provider.getBlockHeight(); } catch { /* best effort */ }
|
||
|
||
const batchISO = new Date().toISOString();
|
||
const t0 = Date.now();
|
||
console.log(`\nCONVERGENT-BY-DESIGN? NO. Each address mints its OWN unique MBA.`);
|
||
console.log(`Batch broadcast @ ${batchISO} (chipnet tip ${tip})`);
|
||
console.log(`Each: SWAP ${BACKING} sats BCH → ${UNITS} units of a NEW self-backed MBA (rate ${RATE})\n`);
|
||
|
||
// ── PARALLEL BROADCAST (Promise.all) — each is an independent genesis tx ──
|
||
const results = await Promise.all(wallets.map(async ({ label, wallet }) => {
|
||
const started = Date.now() - t0;
|
||
try {
|
||
const res = await wallet.tokenGenesis({
|
||
cashaddr: wallet.tokenaddr, // FT lands on this wallet's own token address
|
||
amount: UNITS, // fungible units
|
||
value: BACKING, // sats carried inside → self-backed MBA
|
||
});
|
||
const txid = res?.txId ?? res?.txid;
|
||
const category = (res?.tokenIds && res.tokenIds[0]) ?? '(unknown)';
|
||
return { label, started, txid, category, error: null };
|
||
} catch (e) {
|
||
return { label, started, txid: null, category: null, error: (e?.message ?? String(e)).slice(0, 200) };
|
||
}
|
||
}));
|
||
|
||
// ── STATUS POLL (5s) ──
|
||
await sleep(5000);
|
||
const prov = wallets[0].wallet.provider;
|
||
for (const r of results) {
|
||
if (!r.txid) { r.at5s = 'rejected-at-broadcast'; continue; }
|
||
try { await prov.getRawTransaction(r.txid); r.at5s = 'in-mempool/confirmed'; }
|
||
catch { r.at5s = 'not-found'; }
|
||
}
|
||
|
||
// ── REPORT ──
|
||
console.log(`=== Per-address parallel genesis (BCH → unique self-backed MBA) ===`);
|
||
console.log(`Batch timestamp: ${batchISO} Chipnet tip at start: ${tip}\n`);
|
||
console.log(`Wallet | Δms | new MBA category (tokenId) | TXID or error | 5s later`);
|
||
for (const r of results) {
|
||
console.log(`${r.label} | +${r.started} | ${r.category ?? '—'} | ${r.txid ?? 'ERR: ' + r.error} | ${r.at5s}`);
|
||
}
|
||
const accepted = results.filter((r) => r.txid).length;
|
||
const rejected = results.filter((r) => !r.txid).length;
|
||
const categories = results.filter((r) => r.category).map((r) => r.category);
|
||
const uniqueCats = new Set(categories).size;
|
||
console.log(`\nAccepted: ${accepted} Rejected: ${rejected} (of ${results.length})`);
|
||
console.log(`Distinct MBA categories minted: ${uniqueCats} / ${categories.length} (all unique = no collision)`);
|
||
if (rejected) { console.log('Rejection reasons:'); for (const r of results.filter((x) => !x.txid)) console.log(` ${r.label}: ${r.error}`); }
|
||
console.log(`\nInterpretation: ${accepted === results.length && uniqueCats === categories.length
|
||
? 'ALL parallel broadcasts accepted, every address minted a DISTINCT self-backed MBA — zero collisions. Per-address MBA generation has no shared contested UTXO.'
|
||
: 'See per-row results above.'}`);
|
||
}
|
||
|
||
main().then(() => process.exit(0)).catch((err) => { console.error('HARNESS ERROR:', err?.message ?? err); if (err?.stack) console.error(err.stack); process.exit(1); });
|