How Monokulo is tested

Monokulo handles other people’s money, so it gets tested hard. Every change runs more than 2,600 automatic tests. Every night, two kinds of robot tester invent thousands of strange situations and check that no payment goes missing. This page rebuilds itself from those runs. Open any section to see the details.

Some checks failed·Tests and coverage from 4176f6b on 18 Oct 2026·Nightly properties 23 Oct 2026·Fuzzing 23 Oct 2026·Live network 22 Oct 2026
Tests2,6572,657 passed · 0 skipped
Engine and server90.5%of 92,270 Rust lines
Checkout and POS95.1%of 1,666 browser lines
Properties nightly212plus 10 fuzz targets · 27 live daily

Fuzzing tests

A fuzzer throws strange inputs at the code, keeps the ones that reach code it hasn’t seen before, and mutates those to go further. It isn’t only looking for crashes. Every target is wrapped in checks: the two big ones, portfolio and history, play a whole run of payments through the real engine and compare every balance with the money model, and the smaller ones each check one specific rule. If any check fails, the fuzzer counts it as a crash and saves the input that caused it.

Run of 23 Oct 2026
Targets
10 passed
Executions
261 M
Corpus inputs
54,358
New last night
439
TargetTimeExecutionsCorpusNewCode edges
portfolioPlays out payments to several wallets at once and checks every wallet’s money against the model insufficient-exploration39 min3,7262,721021,632 +0
historyPlays out whole chain histories through the real scanner and database and checks the money against the model34 min6,7233,9783816,518 +22
inputsFeeds junk text to the CPU-list setting and checks the result is sorted, has no repeats, and reads back the same11 min1,855,12836,1642536,584 +0
mempoolChecks the cache of unconfirmed payments: who owns each entry, what gets thrown out, and that it stays within its memory budget9 min74,8376,746882,476 +15
notificationsChecks the engine is woken when there’s work and never waits longer than its time limits16 min2,712,5521,39011,115 +0
statusChecks each order’s status (paid, underpaid, expired and so on) follows correctly from the payments seen11 min5,094,7751,1616357 +0
dns_message9 min4,205,6971,84853290 +0
schedulerChecks scan scheduling: retries, waits and node time-outs all stay within their bounds9 min27,214,721740142 +0
queueChecks work handed to the engine’s workers is dispatched correctly, even when queues fill up or close15 min30,588,4492670112 +0
resourcesChecks the scan budgets worked out from network speed stay inside their limits9 min189,398,7239062 +0
Why so few portfolio runs?Resources ran about 189 million times last night, portfolio 3,726. Each portfolio input builds a whole multi-wallet chain and runs the real scanner over it, a bit like playing a full game rather than pressing one button. Code edges counts the different paths through the code the fuzzer has found; the + is how many were new last night.

What the inputs reached

The targets that play out histories count the situations they land in, like the property tests. Open one to see last night’s.

portfolio3,726histories

Chain events generated

Each history is a random list of these steps. Sometimes a step can’t happen (there’s no waiting payment to drop, say), so it’s skipped. At the end the model works out what every wallet should have, and the engine has to match.

AppliedPicked but skipped
StepPickedAppliedSkipped
Payment arrives in the mempool1,36823,970
Payment mined into a block1,069815
Chain extended by a block1,623–
Chain reorganised888–
Payment dropped from the mempool428387
Output spent elsewhere1,743132
Payment proof supplied2,495–
Clock advanced339–
Engine scan round641–
Order-event log read1,309–
Engine restarted1,800–
Fault injected2,349–

815 skipped “mine” steps had nothing to mine and extended the chain with an empty block instead.

Faults injected

Things broken on purpose in the middle of a history. After each one the engine has to report the problem, keep every balance right, and carry on.

SituationRelativeTimes#/H
Database write refused30,3758.2×
Node request timed out6,0751.6×
Key custody failed6,0751.6×
Every node down at once6,0751.6×
Log read resumed after a cut-off4,6551.2×
Scan worker restarted mid-history8780.24×
Database reopened mid-history1,8000.48×
Custody handle replaced1,4510.39×

Money rules checked

Results the model insists on. One mismatch fails the whole run.

SituationRelativeTimes#/H
Proven payment settles55,39015×
Unproven payment stays pending7,0921.9×
Missing proof holds settlement9,0082.4×
Mismatching proof holds settlement9450.25×
Disputed spend keeps funds counted8140.22×
Spend confirmed by every node voids the output8170.22×
Voided output restored after reorg5570.15×
Order events read back with identical bytes4,1061.1×
Order expiry derived119,38932×
Ledger identical after reopening3,7261.0×

Setups

How each history started: how many wallets and transactions, which way of scanning, and whether it used real recorded mainnet transactions.

SituationRelativeTimes#/H
2 wallets2,9090.78×
3 wallets6510.17×
4 wallets1660.04×
2 transactions1,1510.31×
3 transactions2,3280.62×
4 transactions2470.07×
Direct scanning1,7770.48×
Worker scanning1,9490.52×
Recorded mainnet + synthetic transactions2,4290.65×
Synthetic transactions only1,2970.35×