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

Property tests

A normal test checks one example written by hand: pay 1 XMR, expect the order to be paid. A property test checks a rule instead: however the payments, reorgs and crashes happen, every wallet ends up with exactly what it was sent. The computer invents hundreds of random histories, runs the real engine through each one, and compares the result with a separate, much simpler model that works out the right answer on its own. If they ever disagree, the history is shrunk to the smallest version that still fails and saved, so every future run checks it again.

Run of 23 Oct 2026
Properties
212 passed
Cases per property
512
Histories modelled
577
CPU time
1.7 h

Reading the bars below

While a history plays out, the engine keeps a tally of every situation it lands in, like a refused database write or the chain reorganising. Times is the tally across all 577 histories from last night. One history can hit the same situation many times, so a tally can be bigger than 577. #/H is the average per history.

The bars only compare rows in the same box. What matters is that no row is zero. A zero would mean the random histories had stopped reaching that situation, and the rule would still pass without really being tested.

SituationRelativeTimes#/H
Database write refused4,7958.3×
Timesthe harness refused a database write 4,795 times in total#/Htimes per history: about 8.3 refusals in each historyRelativethe most frequent fault, so its bar is fullPassedafter every refusal the engine reported the problem and every balance stayed right

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 mempool140130
Payment mined into a block26827
Chain extended by a block276–
Chain reorganised303–
Payment dropped from the mempool111168
Output spent elsewhere34228
Payment proof supplied343–
Clock advanced256–
Engine scan round306–
Order-event log read321–
Engine restarted306–
Fault injected382–

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

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 wallets1920.33×
3 wallets1990.34×
4 wallets1860.32×
2 transactions1000.17×
3 transactions2740.47×
4 transactions2030.35×
Direct scanning2770.48×
Worker scanning3000.52×
Recorded mainnet + synthetic transactions3160.55×
Synthetic transactions only2610.45×

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 refused4,7958.3×
Node request timed out9591.7×
Key custody failed9591.7×
Every node down at once9591.7×
Log read resumed after a cut-off7471.3×
Scan worker restarted mid-history1620.28×
Database reopened mid-history3060.53×
Custody handle replaced2950.51×

Money rules checked

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

SituationRelativeTimes#/H
Proven payment settles3,0035.2×
Unproven payment stays pending6081.1×
Missing proof holds settlement7661.3×
Mismatching proof holds settlement1160.20×
Disputed spend keeps funds counted1540.27×
Spend confirmed by every node voids the output1720.30×
Voided output restored after reorg930.16×
Order events read back with identical bytes7281.3×
Order expiry derived12,59422×
Ledger identical after reopening5771.0×
GroupPropertiesTime
proof::properties630 min
  • ✓Verifier histories with restarts never prove forged branches and recover from total outage10 min
  • ✓Verifier histories never prove forged branches and recover from total outage9 min
  • ✓Anchoring requires a configured majority even when only one node answers4 min
  • ✓Verifier rpc timeouts during anchor reads and samples use healthy peers4 min
  • ✓Malformed or wrong block samples cannot block a healthy anchor majority4 min
  • ✓A member failing to send sampled blocks does not block a healthy anchor majority2.1 s
work::properties::money::nodes2129 min
  • ✓Real verifier and scanner reconcile verified payments across forks and restarts with a forged primary13 min
  • ✓Real verifier and scanner reconcile verified payments across forks and restarts12 min
  • ✓Multi node money histories survive conflicting branches hangs outages and restarts47.4 s
  • ✓Tenant pages yield to other tiers and resume without losing money32.5 s
  • ✓False mining locations without block membership cannot unlock verified settlement27.6 s
  • ✓Inflated tips and false attestations cannot bypass enabled verification21.5 s
  • ✓False spent votes and last reachable node policy preserve payment identity on recovery20.1 s
  • ✓All node outages preserve money and recover once one node returns18.2 s
  • ✓Malformed missing and reordered bodies never publish an unfinished block16.5 s
  • ✓Zero confirmation and unverified height trust boundaries are explicit14.5 s
  • ✓Omitted pool transactions delay detection without creating false payments13.5 s
  • ✓Hanging secondary retracts double spends within the settlement budget13.2 s
  • ✓Cancellation of any pinned rpc records failure without mixing node answers6.1 s
  • ✓Every malformed response shape is checked with a restart414 ms
  • ✓Sole configured and sole reachable votes have the same explicit trust limit159 ms
  • ✓An outer timeout cools the pinned node and the next money round uses a fallback67 ms
  • ✓Responses at transport deadline boundaries preserve failover and recovery26 ms
  • ✓Corroborated key images match an independent vote model even with hangs24 ms
  • ✓Corroborated locations preserve positive evidence and bound unreachable nodes23 ms
  • ✓Failover histories bound hangs and recover the highest priority eligible node23 ms
  • ✓Hanging peers do not discard healthy location or spent answers9 ms
work::properties::money::expansions1517 min
  • ✓Reorg collection of mined outputs crosses full queue pages without losing outputs5 min
  • ✓Reorg collection of pool outputs crosses full queue pages without losing outputs4 min
  • ✓Repeated multi order forks faults and restarts preserve money3 min
  • ✓Settlement queue wraparound recomputes every order once2 min
  • ✓Repeated recovery process deaths preserve obligations1 min
  • ✓Production worker crashes preserve atomic money37.1 s
  • ✓Process death during sqlite work recovers exactly once money26.6 s
  • ✓Stored amount boundaries survive scanning mining and restarts20.7 s
  • ✓Unrepresentable amounts fail without corrupting stored money6.1 s
  • ✓Oversized invoices do not consume an address or idempotency key6.0 s
  • ✓Every worker crash boundary and the full recovery pipeline run833 ms
  • ✓Reorg collection of a 255 output pool payment faulted at once488 ms
  • ✓Named money durability boundaries recover after process death457 ms
  • ✓An aggregate one piconero above the storage limit rolls back52 ms
  • ✓Crash process child9 ms
work::properties::money177 min
  • ✓Independent payments are routed and credited once across reorgs58.1 s
  • ✓Late key registration cannot starve healthy catch up tenants52.5 s
  • ✓Reused output keys never create extra spendable money and winners follow reorgs44.5 s
  • ✓Concurrent fast scans rounds and order creation preserve exactly once credit41.8 s
  • ✓Fast pool passes and rounds share exactly once credit across restarts34.7 s
  • ✓Every output in a transaction is routed once and uses its orders policy29.3 s
  • ✓Transaction paging and fetch failures cannot credit unfinished blocks27.4 s
  • ✓One networks rounds cannot scan or credit another networks orders25.6 s
  • ✓Distinct wallets cannot credit each others outputs22.6 s
  • ✓Settlement waits for verified depth and the payments attested block21.4 s
  • ✓Deadlines wait for backlogged scans and do not hide a payment20.9 s
  • ✓Late payments do not unsettle an order already covered by confirmed funds19.1 s
  • ✓Missing or ambiguous evidence cannot void money17.3 s
  • ✓Closed order grace boundary controls late pool scanning14.7 s
  • ✓A new order is found in an already cached pool transaction14.1 s
  • ✓A zero confirmation settlement reopens when its output key conflict loses its winner118 ms
  • ✓An unregistered tenant cannot hide a healthy tenant in the next catch up page90 ms
work::properties247 min
  • ✓Portfolio histories match independent ledger2 min
  • ✓Coverage guided histories recover with real scanner1 min
  • ✓Scanner histories converge after reorgs failures and restarts57.1 s
  • ✓Reorg jobs survive outages and restore payments when evidence changes50.6 s
  • ✓Failing tenants do not starve healthy tenants and catch up after restart50.0 s
  • ✓Replacement chains of different lengths recompute payment depth39.7 s
  • ✓An interrupted block replaced by a fork never leaks staged payments23.6 s
  • ✓Every recorded ringct variant runs complete money histories15.3 s
  • ✓Combined portfolio interactions have fixed positive controls1.9 s
  • ✓Portfolio selects every sql operation class and position1.3 s
  • ✓Reviewed engine history seeds replay1.1 s
  • ✓Portfolio reorg retains pending settlement until branch is proven687 ms
  • ✓Portfolio composes arrival extension reorg proof expiry and faults552 ms
  • ✓Portfolio reports selected applied and skipped commands separately185 ms
  • ✓A replaced payment block recorded from its header cannot settle the order161 ms
  • ✓A shorter tip above scanned history recomputes depth after restart127 ms
  • ✓A fork after rewind before rescanning cannot keep a discarded payment height103 ms
  • ✓A shorter branch reopens a settled payment below the fork102 ms
  • ✓A second fork after reconciliation cannot keep a discarded payment height101 ms
  • ✓A single page budget eventually serves all tenants despite a custody failure100 ms
  • ✓A shorter branch recomputes confirmations even when the payment height is unchanged95 ms
  • ✓A payment block dropped by a shorter chain unconfirms the payment92 ms
  • ✓Recorded ringct shapes and recipient expectations are frozen58 ms
  • ✓Reviewed portfolio seeds are the encoded reviewed scenarios11 ms
work::mempool::properties::scale63 min
  • ✓Each transaction visits each tenant independently2 min
  • ✓Parallel pool backlogs recover and release cache46.1 s
  • ✓Expanded windows are served during continuous new transaction floods2.6 s
  • ✓Arrivals cannot increase existing work wait1.7 s
  • ✓Transaction cursors cannot be shared between three transactions603 ms
  • ✓Production body caps reject overflow and reuse freed space120 ms
work::properties::lifecycle32 min
  • ✓Forks at and beyond retention edges preserve the documented evidence limit37.1 s
  • ✓Genesis divergence recovers through generated failures and restarts33.2 s
  • ✓Bootstrap histories recover from empty short and unservable chains32.6 s
node_events::properties::zmq21 min
  • ✓Real subscriber recovers and tracks generated publisher configuration1 min
  • ✓Publisher switch disable reenable and duplicate endpoints261 ms
work::properties::concurrency41 min
  • ✓Combined worker histories preserve money52.5 s
  • ✓Proof config and generation histories preserve settlement23.3 s
  • ✓Every proof config shutdown admission schedule recovers6.6 s
  • ✓Every overlap admission order and abandoned caller recovers1.3 s
http::properties131 min
  • ✓Order histories preserve tenant scoped idempotency and addresses27.6 s
  • ✓Concurrent creation and retries use the production database worker11.9 s
  • ✓Requests without keys create distinct purchases10.9 s
  • ✓Process death at order commit preserves atomic allocation9.7 s
  • ✓Sql failures and lost results never burn addresses4.0 s
  • ✓Disabling a tenant during derivation cannot publish an order3.9 s
  • ✓Cancelling during derivation preserves the address for retry3.8 s
  • ✓Every purchase identity field rejects conflicting replays3.7 s
  • ✓Exhaustion and mismatched claims cannot corrupt allocation2.8 s
  • ✓Request validation is atomic at integer and key boundaries2.6 s
  • ✓Every sql denial during order creation is atomic and retryable179 ms
  • ✓Exhausted counter still allows idempotent replays and never wraps55 ms
  • ✓Order creation crash child9 ms
node_events::properties61 min
  • ✓Actual mempool loop detects payments despite missing wrong or burst notifications1 min
  • ✓All polling recovery modes run642 ms
  • ✓Notification histories match pending permits104 ms
  • ✓Stale generation announcements do not wake replacements31 ms
  • ✓Arbitrary topics match exact prefixes29 ms
  • ✓Reviewed notification histories replay9 ms
http::properties::authorization111 min
  • ✓Payment lookup cannot read or credit another tenants outputs10.7 s
  • ✓Tenant ownership and revocation histories9.6 s
  • ✓Lists and live events expose only the authenticated tenant6.3 s
  • ✓Concurrent rejections leave no writes or stream slot leaks6.2 s
  • ✓Permission matrix rejects credentials without data or writes5.9 s
  • ✓Cross tenant refund writes are atomic5.8 s
  • ✓Body identity cannot steer own tenant operations5.6 s
  • ✓Repeated headers bind only the first presented capability5.3 s
  • ✓Bearer syntax cannot expand tenant authority4.6 s
  • ✓Every route runs the engine and tenant rejection matrix178 ms
  • ✓Named revocation and cross tenant history replays65 ms
store::properties655.8 s
  • ✓Historic upgrades recover from process death26.6 s
  • ✓Upgrades preserve money queues and interrupted work23.3 s
  • ✓Migration process death before and after commit preserves money and schema3.2 s
  • ✓Every reached upgrade fault recovers without partial schema1.4 s
  • ✓Every historic schema upgrades with existing money and work1.3 s
  • ✓Migration crash child9 ms
plain::properties840.9 s
  • ✓Sparse window histories match fresh scans without rebuilding kept keys21.4 s
  • ✓Lookup ranges match fresh scans and do not poison the live window9.8 s
  • ✓Concurrent wallet scans keep independent caches and matches5.7 s
  • ✓Cancelling queued scans preserves both caches2.7 s
  • ✓Invalid material and oversized ranges cannot poison a live wallet786 ms
  • ✓Idempotent registration sealing and removal preserve wallet identity472 ms
  • ✓Invalid registration ids never allocate wallets78 ms
  • ✓Table build batches keep exact progress across window changes48 ms
store::db::properties::scale122.5 s
  • ✓Repeated saturation retains fifo fairness and cancellation22.5 s
work::mempool::properties1620.2 s
  • ✓Real fast and round callers share reservations10.1 s
  • ✓Mixed tenant success failure and empty matches retry independently5.7 s
  • ✓Batch reservations and cache histories3.1 s
  • ✓Overlapping thread batches have exactly one owner per key755 ms
  • ✓Every mixed batch failure boundary runs in both orders152 ms
  • ✓Both entry points cover owner success and cancellation112 ms
  • ✓Competing scans release on success failure and cancellation89 ms
  • ✓Reservation histories match independent ownership62 ms
  • ✓Cache accounting obeys full width budgets25 ms
  • ✓Ownership survives pool eviction and serializes changed windows11 ms
  • ✓Partially completed batches and panics release only unfinished work10 ms
  • ✓A completion for another window cannot publish or release its owner9 ms
  • ✓Reviewed fuzz histories remain valid regressions9 ms
  • ✓Simultaneous threads cannot both reserve the same scan9 ms
  • ✓Reservation decision grid checks completed busy and changed windows8 ms
  • ✓Unrelated transactions and tenants are not serialized8 ms
store::db::properties47.5 s
  • ✓Closing all senders drains accepted jobs3.9 s
  • ✓Bounded queues preserve fifo fairness and accepted work3.6 s
  • ✓Class selection matches independent round robin model13 ms
  • ✓Continuously ready classes are served within one turn each9 ms
store::work::properties16.5 s
  • ✓Generated reorg staging is scoped and durable6.5 s
loops::properties46.0 s
  • ✓Real network manager follows generated node configuration5.6 s
  • ✓Generations stop before replacement and recover from panics226 ms
  • ✓Stopping during factory or loop restart backoff is immediate87 ms
  • ✓Dropping a manager generation stops its children24 ms
work::blocks::properties34.3 s
  • ✓Accepted block effects recheck durable prerequisites4.1 s
  • ✓Every late commit boundary is exercised with and without staging220 ms
  • ✓Cache histories preserve accounting and protected blocks21 ms
router::properties63.5 s
  • ✓Registration removal and backend reload histories never cross wallets2.4 s
  • ✓Registrations finishing after replacement are freed and retry on the new backend611 ms
  • ✓Transient backend failures preserve ownership until recovery231 ms
  • ✓Cancelled registration results are recovered by the same registration id143 ms
  • ✓Backend epoch changes invalidate only the restarted backend101 ms
  • ✓Two maximum backend epochs do not panic or wrap7 ms
exploration::properties8238 ms
  • ✓Application input boundaries handle arbitrary bytes75 ms
  • ✓Cpu ranges match an independent set model60 ms
  • ✓Queue byte histories preserve dispatch and drain contracts36 ms
  • ✓Event byte histories preserve scheduler invariants30 ms
  • ✓Hostile cpu range lengths are rejected before allocation10 ms
  • ✓Sizing and retry boundaries handle all float bit patterns10 ms
  • ✓Maximum cpu range is rejected without expansion9 ms
  • ✓Special float patterns are always exercised8 ms
work::scheduler::properties11189 ms
  • ✓Retry histories reset and expire independently100 ms
  • ✓Scheduler matches the two pass contract13 ms
  • ✓Outstanding and duplicate completions cannot change progress9 ms
  • ✓Retry boundaries saturate and never wrap9 ms
  • ✓Retry count at u32 max stays capped9 ms
  • ✓Retry policy decision grid checks free attempts and deadlines9 ms
  • ✓Backwards clock observations cannot extend an expired share8 ms
  • ✓Every combination of tier results preserves the progress floor8 ms
  • ✓Full width round budgets preserve share deadlines8 ms
  • ✓Late completions from another round are rejected8 ms
  • ✓Retry expiry boundaries use production upkeep8 ms
status::properties10186 ms
  • ✓Fuzz histories match status specification38 ms
  • ✓Adding payments cannot undo settlement26 ms
  • ✓Payment order does not change status20 ms
  • ✓Agrees with aggregate specification19 ms
  • ✓Funded orders are independent of the clock19 ms
  • ✓Increasing mined confirmations preserves settlement19 ms
  • ✓Splitting a payment with the same evidence preserves status19 ms
  • ✓Expiry is strictly after the deadline9 ms
  • ✓Status decision boundaries match independent grid9 ms
  • ✓Reviewed status fuzz seeds replay8 ms
scaling::properties6100 ms
  • ✓Progress histories survive clock jumps and extreme counters52 ms
  • ✓Request sizes remain bounded across numeric inputs11 ms
  • ✓Larger resources and faster links do not reduce request size10 ms
  • ✓Maximum timeout and wall clock boundaries do not panic9 ms
  • ✓Stopped and backwards clocks cannot grow telemetry without bound9 ms
  • ✓Timeout bounds hold for extreme measurements9 ms