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.

All checks passed·Tests and coverage from 6b1dcda on 16 Oct 2026·Nightly properties 17 Oct 2026·Fuzzing 16 Oct 2026·Live network 16 Oct 2026
Tests2,6072,607 passed · 0 skipped
Engine and server90.6%of 87,305 Rust lines
Checkout and POS95.0%of 1,655 browser lines
WooCommerce95.6%of 502 PHP lines
Properties nightly211plus 9 fuzz targets · 26 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 16 Oct 2026
Targets
9 passed
Executions
263 M
Corpus inputs
36,137
New last night
937
TargetTimeExecutionsCorpusNewCode edges
portfolioPlays out payments to several wallets at once and checks every wallet’s money against the model17 min3,0211,34016721,408 +110
historyPlays out whole chain histories through the real scanner and database and checks the money against the model17 min5,6742,5357416,276 +26
inputsFeeds junk text to the CPU-list setting and checks the result is sorted, has no repeats, and reads back the same7 min1,496,00325,1135326,454 +0
mempoolChecks the cache of unconfirmed payments: who owns each entry, what gets thrown out, and that it stays within its memory budget7 min81,8124,5431442,445 +2
notificationsChecks the engine is woken when there’s work and never waits longer than its time limits12 min3,165,1651,251121,115 +0
statusChecks each order’s status (paid, underpaid, expired and so on) follows correctly from the payments seen8 min5,487,4671,0218357 +0
schedulerChecks scan scheduling: retries, waits and node time-outs all stay within their bounds8 min15,508,042710142 +0
queueChecks work handed to the engine’s workers is dispatched correctly, even when queues fill up or close12 min46,130,4002540111 +0
resourcesChecks the scan budgets worked out from network speed stay inside their limits7 min191,095,2459062 +0
Why so few portfolio runs?Resources ran about 191 million times last night, portfolio 3,021. 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,021histories

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 mempool82417,263
Payment mined into a block838449
Chain extended by a block553–
Chain reorganised420–
Payment dropped from the mempool288224
Output spent elsewhere1,21558
Payment proof supplied1,898–
Clock advanced70–
Engine scan round249–
Order-event log read844–
Engine restarted974–
Fault injected1,179–

449 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 refused21,0007.0×
Node request timed out4,2001.4×
Key custody failed4,2001.4×
Every node down at once4,2001.4×
Log read resumed after a cut-off3,6271.2×
Scan worker restarted mid-history4780.16×
Database reopened mid-history9740.32×
Custody handle replaced9260.31×

Money rules checked

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

SituationRelativeTimes#/H
Proven payment settles32,43411×
Unproven payment stays pending4,8821.6×
Missing proof holds settlement6,7272.2×
Mismatching proof holds settlement5430.18×
Disputed spend keeps funds counted5690.19×
Spend confirmed by every node voids the output5870.19×
Voided output restored after reorg4840.16×
Order events read back with identical bytes3,2591.1×
Order expiry derived75,46225×
Ledger identical after reopening3,0211.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,5740.85×
3 wallets4310.14×
4 wallets160.01×
2 transactions9330.31×
3 transactions2,0340.67×
4 transactions540.02×
Direct scanning1,4170.47×
Worker scanning1,6040.53×
Recorded mainnet + synthetic transactions2,0490.68×
Synthetic transactions only9720.32×