One operator (lumosd, postgres) and ten clients
(waved, lwwallet on sqlite) on a regtest chain that advances at
mainnet pace: 18 blocks per 3-hour epoch, mined only by the harness. Every
epoch runs 30 random client-to-client OOR payments (25–100k sat) and a
wallet-realistic refresh of whatever is due (near batch expiry or deep OOR
lineage), on top of everything prior epochs created. The question is not how
fast the stack is on a clean network, but what the same payments and rounds
cost once the operator's round history, indexer and mailbox have real volume
in them.
Wall clock per case, measured separately. Because state is never reset, an upward slope is the cost of accumulated history rather than run-to-run noise. The refresh number includes the harness-mined confirmation, so its floor is set by the round protocol, not the daemons.
Per-payment time from submitting the send to the OOR session reaching its terminal state, across the 30 sends of the epoch. Sends run ten at a time (every client sends at once), so the spread between the median and the slowest send is the contention signal.
Per-client time from queueing a refresh to that client's round reaching a terminal state. From epoch 14 the policy is wallet-realistic: a client refreshes only vtxos within 300 blocks of batch expiry or with an OOR chain depth of 6+ (a lineage that keeps growing eventually exceeds the operator's 25k vB cap and stops being spendable over OOR). Clients with nothing due skip; skips and fee-quote rejections ("insufficient_residual") count in the epoch detail as their own buckets, not as failures, and are excluded from the percentiles. Epochs 1-13 refreshed every client's whole fleet every epoch, which is why their round times and fee drain run far hotter. Epochs where every client skips are left out of the timings: nothing was due, so there is no round to time.
Three views of the system's money. Deployed is the
operator ledger's deployed_capital: every sat locked in
unexpired on-chain batch outputs, including the operator's own refresh
liquidity and fully-forfeited history the sweeper has not reclaimed yet.
User spendable is the sum of all ten clients' confirmed offchain
balances. Live vtxo value is the operator indexer's total for
live vtxos, an independent measurement that should track user spendable
exactly. The gap between deployed and the other two is capital the
operator has committed beyond what users can spend; batch expiry (1008
blocks after a round confirms) is when the sweeper starts winning it
back.
Locked capital divided by user-owned capital. The numerator is only what sits in unexpired batch outputs and cannot be moved by the operator: value locked in previous rounds (forfeited but co-locked under the tree covenant until that batch's 1008-block expiry) plus the current rounds backing live vtxos. The operator's idle wallet is not in it. The denominator is everything users own, spendable or in flight (the indexer's live vtxo value). Every refresh re-locks fresh capital while the forfeited batch waits out its expiry, so the ratio climbs between sweeps and falls when the sweeper reclaims expired batches; a ratio that only ever climbs means reclamation is not keeping up.
Every sat enters through a known boarding deposit, so the books can be closed exactly. Against the operator's own indexer and ledger the identity deposited = live + in-flight + expired + fees holds to the sat in every epoch of this series, so the conservation residual is flat zero: the protocol loses nothing. What does go wrong is visibility. Invisible to owners is value the operator holds live that no client counts, because a dropped incoming transfer leaves the recipient unaware it was ever paid; those sats are unspendable by their owner until a seed-restore rescan finds them. Swept from owners is value in batches that expired before being refreshed, which the sweeper reclaims permanently. Client balances cannot close these books themselves: a vtxo locked in a queued refresh is counted in both a client's confirmed balance and its pending-out.
Reading a flat line here. All three series sitting on one value is the healthy result, not a missing chart: in this series they are pinned at zero for every epoch.
Total size postgres reports for the lumos database.
Postgres carries a fixed multi-megabyte catalog baseline: read the slope, not
the offset. The largest tables behind this number are in the
snapshot.
Marginal cost of one epoch's work: the operator's postgres growth against the summed sqlite growth of all ten clients (main files plus WAL). Flat means the cost per unit of work is holding; a rising line means each epoch stores more than the last for the same 30 payments and one round.
Summed sqlite footprint of all ten clients, and
wb-client01 alone as the representative client (clients are
symmetric by construction). Includes each database's WAL, so a stalled
checkpoint shows up here as growth the main file never sees.
Resident set sampled once after the epoch's cases finish, so it
is where memory settles rather than a peak during the run. The high water
mark is memory.peak from the container's cgroup: it cannot be
reset without root, so it only ever rises, and a mark that keeps climbing
epoch after epoch is the leak signal.
Same measurements on the observed client. One client stands for all ten: they run the same binary against the same operator with the same workload shape.
Live goroutine count on the operator and the observed client at the end of each epoch. A count that grows with the series and never settles is a leaked-goroutine signal; growth proportional to load that returns to a floor is normal.