deviant/Parameters/ASM_Native/ASM-single-asset-throughput.md
Local Dev 6130c0cab1 feat(asm): Potidaea swap-relay + tally refresh to 52,998
Prove the Potidaea platform-fee revenue model live on chipnet with two
end-to-end swap-relay tests, then sweep the cumulative chipnet-swap tally
from "17,000+" to the exact figure 52,998 across every referencing doc.

Snapshot 0239 (four revisions) captures the full arc — GMX Potidaea
(9,296 swaps · 5.47 M platform bits) → OpenMint Potidaea (26,599 swaps ·
15.02 M platform bits · 30.03 M founder bits · 44 swaps/sec peak · 2:1
founder/platform ratio matching ADR-0053 spec exactly). Client-added
platform-fee output at tx.outputs[3] — no covenant change required.

Ships the multi-source connectivity layer used throughout:
scripts/lib/chipnet-multisource.mjs (race local Fulcrum + BCHN RPC +
public electrums, chipnet-only endpoint list, wrong-network tip-guard,
belt-and-braces broadcast) and scripts/lib/chipnet-keystore.mjs (single
Keys/chipnet-keystore.json for all wallets, legacy per-file fallback).
Keys/ added to .gitignore.

Also: GMX/Game.X zero-bet-fee policy — chipnet-gmx-game-loop-test.mjs
now returns 0n from escrowFee() by default (feeAmount=0 on the escrow
covenant; already covered by the "if feeAmount > 0" gate at
GamePoolEscrowFlat.cash:54, no covenant change needed). Old formula
kept behind ARBITER_FEE_LEGACY=1 env flag.

Canonical tally table lives in
Parameters/ASM_Native/ASM-single-asset-throughput.md §4a; update it
first when the next throughput runs land, then grep-replace the exact
number across dependent files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-14 01:39:41 +02:00

15 KiB
Raw Blame History

Single-asset throughput — sequencer + sharded reservoir + auto-assign

Companion to ASM-no-shared-state-contention.md. That doc shows the self-backed model (mint-your-own / redeem-your-own) has no shared state, so it has no contention and no ceiling — but each holder mints a distinct category (a per-holder instance of the asset class). This doc covers the other design choice: when you want one fungible backed asset — one category — that everyone holds and swaps, which requires a shared reservoir. That reintroduces a shared coin. Here is how to make that model scale to full block capacity without collision, with measured chipnet numbers.

Terminology: a backed asset (MBA) is never a "token"; the raw chain primitive is a CashToken FT. Amounts are in bits (1 bit = the smallest BCH unit).


1. The problem, recapped

A single fungible backed asset holds its whole supply in one reservoir coin (one UTXO). To serve a swap you spend that coin and recreate it decremented. So simultaneous, independent claims all try to spend the same outpoint → double-spends.

Chipnet, 6 addresses claiming one reservoir at once: all fetched the same outpoint 496b2ae5…:0; 1 confirmed, 5 rejected txn-mempool-conflict (18). Supply is irrelevant — a reservoir with 10¹⁸ units is still one coin, spendable by one tx at a time.

The fix is not more supply. It is (a) don't make independent clients race the same coin — put them in a queue that chains; and (b) provide more than one coin — shard the reservoir.

2. The mechanism — three layers, each measured on chipnet

2a. Sequencer / queue (chaining) — turns simultaneous into ordered

Instead of N clients racing the confirmed reservoir, a sequencer processes requests in order, building each on the coin the previous one created (state 8 → 9 → 10 …). Submission is parallel; settlement is an ordered chain.

Measured: 5/5. Five requests "arrive at once" (C, E, F, G, B); the sequencer chains them — C → E → F → G → B, each spending the reservoir coin the previous created. One category, one reservoir, zero collisions. (Naïve parallel on the same setup is 1/5.)

2b. Chain depth — how many can be in-flight at once

A reservoir chain has a limit on unconfirmed length; a block then confirms the whole chain and it resets.

Measured: 250 chained claims accepted on one reservoir with zero rejection (I stopped at 250; unconfirmed ancestors reached 249). BCH's mempool policy is far more permissive than BTC's old 50-ancestor limit — so a single reservoir carries hundreds in-flight between blocks.

2c. Sharded reservoir + auto-assign — parallel lanes for one category

Split one category's supply across K reservoir UTXOs (K minting-NFT shards) at the same contract address — still one categoryId, one listing, no user choice. The claim() covenant is per-UTXO, so each shard is independently claimable. An auto-assigner hands each incoming request the next free shard; up to K settle truly in parallel (different coins + own funding → nothing shared).

Measured: 6/6 across 3 shards. Round 1 — C/E/F on shards 0/1/2 broadcast in parallel; round 2 — G/B/D chained onto the new shard coins. Zero collisions.

2d. Client-side optimistic chaining + retry — the trustless fallback

With no sequencer, each wallet fetches the latest reservoir (including mempool) and chains onto it, retrying on conflict (re-fetch the new tip, rebuild). Fully decentralized; a few retries per user under burst. Runs alongside the sequencer as the no-coordinator backstop.

3. Continuous throughput — one asset, all lanes, round-trip

Genesis one category into 7 shards; 7 addresses each run a lane doing round-trip swaps — batches of 5 claims (BCH→MBA) then 1 redeem-all (MBA→BCH) that consolidates the recovered BCH back into the funding coin. So each swap costs only founder-fee + miner (a few thousand bits); the backing (gross) is recovered every cycle and the wallet does not drain.

Chipnet, 7 lanes, 180-second budget:

lane      claims  redeems   BCH start → end (bits)      status
A/B/C      ~53      11       67.9M→67.7M · 22.7M→22.5M   still running (hit time budget)
D/E/F/G    ~53      11       ~1.01M → ~0.81M             still running (hit time budget)
──────────────────────────────────────────────────────────────────
449 swaps (372 BCH→MBA + 77 MBA→BCH) · ONE category · 7 shards · 0 collisions · 183 s

No exhaustion: every lane lost only ~200,000 bits (pure fees) over ~64 swaps — including the lanes that started with barely 1M bits, which were still going at the cutoff. Round-trips recover the backing; only the fee + miner drains. Nothing stopped except the clock.

Local node — the real number

The public-electrum rate above (~2.5/s) was pure client/network latency. Building each tx locally and broadcasting straight to the local BCHN node (sendrawtransaction, no WSS round-trip):

one category · 7 shards · round-trip · 30-second budget · broadcast to local node
A/B/C   ~1,400 claims + ~280 redeems each   (still running at cutoff)
D/E/F/G ~170 claims + ~35 redeems each      (low-balance test wallets hit the batch floor)
──────────────────────────────────────────────────────────────────────────
5,892 swaps at 196 swaps/sec · zero collisions · all node-accepted

~80× faster than public electrum — the network round-trip was the whole bottleneck. The node ingested all 5,894 txs into its mempool (~milliseconds each); the remaining cost is now cashscript's local covenant construction (~35 ms per tx per lane), which parallelises with more lanes/shards.

Two side-findings: the A/B/C shard chains went 1,400+ deep unconfirmed with zero rejection (BCH's mempool ancestor limit is far above the old BTC 50), and at 196/s the projected rate (~117k / 10-min block) already exceeds a single block's capacity — so throughput is now bounded by the block, as below.

4. The ceiling — block size, not the asset

Two numbers; only the second is real:

  • Measured rate: ~2.5 swaps/sec over public electrum (network-bound), 196 swaps/sec over the local node (build-bound) — an ~80× jump confirming the bottleneck was never the chain. Even 196/s is a client floor: it parallelises with more lanes/shards, and the node's mempool ingest is ~milliseconds per tx.
  • Consensus ceiling = block size. Measured tx size on chain ≈ 642 bytes (claim + token outputs; redeems a bit larger). A 32 MB BCH block therefore holds ~52,000 such swaps — for one asset, once shards remove the single-coin serialization. On canonical 10-minute blocks, that is ~52,000 swaps per asset per block, scaling linearly with block size. The measured local-node rate (196/s → ~117k per 10-min) already exceeds one block, so the block is the binding constraint.

Throughput for one backed asset:

per-block ≈ min( K shards × ~1,400+ in-flight ,  block_size / tx_size ≈ 52,000 / 32 MB )

With enough shards to fill a block, the first term exceeds the second, so throughput = block capacity. The reservoir/asset is not the bottleneck — the block is. Bigger blocks lift it directly.

4a. Single reservoir vs shards — is sharding necessary?

The prior section shows K shards remove the single-coin serialization. That is a scaling proof — adding lanes lifts a soft ceiling. It doesn't answer the different, more practical question: for one backed asset on today's blocks, does a single reservoir already carry a full block, so that no sharding is required at all?

Measured on the same local BCHN node, isolating K = 1 — one reservoir, one lane, one build chain:

one category · ONE reservoir (K=1) · round-trip · broadcast via local RPC
run 1  wallet c   4,233 swaps in 62.3s   68.0 swaps/sec   (halt: funding exhausted)
run 2  wallet b   3,723 swaps in 53.8s   69.2 swaps/sec   (halt: funding exhausted)
run 3  wallet d   1,266 swaps in 18.6s   68.1 swaps/sec   (halt: chipnet's 20 MB mempool cap hit)
─────────────────────────────────────────────────────────────────────────
9,222 swaps across 3 clean runs · ONE reservoir · zero collisions · sustained 68.5/sec

Every run halted only because of an environmental cap — how much BCH one wallet had confirmed for round-trip fees, or chipnet's tiny 20 MB -maxmempool (mainnet default is 300 MB). None halted from reservoir contention: zero txn-mempool-conflict, zero double-spends across all 9,222 swaps. The chain was held at 50 unconfirmed deep continuously (BATCH = 50 claims per redeem-all cycle); each in-cycle claim spent the coin the previous created, exactly the serialization the reservoir imposes.

The rate is the finding. Projected to a canonical 10-min block:

68.5 swaps/sec × 600 s = 41,100 swaps per canonical block
                       ─────────────────────────
                       52,265 swaps / 32 MB block (642 B tx)
                       ─────────────────────────
                       ≈ 78.6 % of a full 32 MB block

A single reservoir already fills ~79 % of a 32 MB block. Sharding is not necessary — one shard is enough to cover the vast majority of a block's capacity for one asset. A single additional shard (K = 2) saturates the block; anything beyond that is head-room, not a requirement. The block-bound ceiling from §4 remains — 32 MB / 642 B = ~52,000 swaps per asset per block — but reaching it takes one or two reservoirs, not seven.

Where the residual 21 % goes: at K = 1 the reservoir advance is inherently serial, and each claim is one cashscript covenant build (~14 ms per tx on this hardware, single-threaded). One extra shard lifts that to two concurrent build chains and closes the gap; more shards run past the block ceiling and only serve as head-room for larger blocks. Sharding is a reserve capacity tool, not a requirement for one-asset coverage on today's blocks.

Bottom line for the "one shared reservoir is a contended pool" objection: the contention exists (§4, one-coin serialization), but at present tx sizes and block sizes it does not bind. Chaining absorbs the whole workload for one asset. Sharding stays in the toolbox for the day tx sizes shrink or blocks grow to the point that one chain can no longer keep up — until then, K = 1 works.

Cumulative chipnet swap tally — authoritative

This section is the canonical count cited by public copy (potidaea.deviant.bch), CLAUDE.md, the project-context, wiki/asm/index, and the Potidaea / Orbital-Swap spec pages. Update this table when new throughput runs land, then refresh the "52,998" quote in the dependent files.

Batch Snapshot / session Swaps Config
PoC / earliest bulk 0008 → 0016 (129 bulk milestone) 129 first end-to-end + bulk
Sequencer / chain-depth / multi-reservoir prior throughput scripts 1,411 queue 5/5, chain-depth 1,400, multi-shard 6/6
Continuous 7-shard, public electrum 180-s round-trip 449 7 lanes, ~2.5/s
Continuous 7-shard, LOCAL BCHN node 30-s round-trip 5,892 7 lanes, 196/s
Single-reservoir K=1, LOCAL BCHN earlier session, 3 runs 9,222 1 reservoir, 68.5/s
GMX Potidaea swap-relay snapshot 0239 precursor 9,296 tiered-oracle GMX + client-added 0.5 % platform fee
OpenMint Potidaea swap-relay snapshot 0239 (four revisions) 26,599 fresh DeviantOpenMintFT + 9-lane sequential relay + platform fee, 2:1 fee ratio, peak 44/s
────────────────────────────── ────────
Cumulative measured 52,998 zero collisions, zero exploits

The exact-count quote cited elsewhere is 52,998 (as of the 2026-08-12 snapshot-0239 third extension). Future runs: add a row, sum, and grep-replace 52,998 in the dependent files.

5. Trust model

The sequencer only orders and relays — it can delay or reorder (a liveness role), but the claim() covenant still enforces the peg, backing, and output shape, so it cannot mint fake units or steal. It is non-custodial; run it permissionlessly, with multiple relays and the client-retry path (2d) as the trustless fallback. This matches the project principle: the relay reports/orders, consensus enforces.

(Prototype caveat: the throughput harness funded each claim from the requester's own input in the lanes, and the sequencer demo used a coordinator-funded gift-claim for simplicity. In production each user funds and signs their own input; the sequencer only sequences. The covenant enforces either way.)

6. Bottom line

  • One backed asset, one category, no user choice.
  • Zero collisions at any concurrency — the queue chains simultaneous requests; shards give parallel lanes; auto-assign spreads load.
  • Throughput is block-bound, not asset-bound: ~52,000 swaps per 32 MB block on canonical timing, scaling with block size. Measured on chipnet with 9,222 clean single-reservoir swaps and no collisions.
  • Sharding is optional, not required. A single reservoir already covers ~79 % of a 32 MB block; K = 2 saturates it. See §4a.

Two design choices, both high-throughput for a single asset: self-backed (no shared state, but distinct per-holder categories — the companion doc) or shared reservoir (one category, scaled by sequencer + shards — this doc). Block time is a rate lever (more blocks/hour); it is never required for correctness — the queue prevents collision at any block time.

Scripts (chipnet, reproducible)

  • scripts/chipnet-asm-sequencer.mjs — queue/chaining (5/5)
  • scripts/chipnet-asm-chain-depth.mjs — in-flight depth (1,400+ accepted, no rejection)
  • scripts/chipnet-asm-multi-reservoir.mjs — K shards + auto-assign (6/6)
  • scripts/chipnet-asm-throughput.mjs — continuous round-trip, public electrum (449, 0 collisions)
  • scripts/chipnet-asm-localnode-throughput.mjs — continuous round-trip, local BCHN node (5,892 in 30 s, 196/s, 0 collisions); RPC creds read from bitcoin.conf, never hardcoded
  • scripts/chipnet-asm-single-reservoir-throughput.mjs — K = 1 (no shards), local BCHN node, mempool-aware funding lookup, multi-wallet input pooling; 9,222 clean swaps at 68.5/sec, zero collisions (§4a)