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

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 17 Oct 2026
Properties
211 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,3957.6×
Timesthe harness refused a database write 4,395 times in total#/Htimes per history: about 7.6 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 mempool120138
Payment mined into a block26529
Chain extended by a block253–
Chain reorganised289–
Payment dropped from the mempool112156
Output spent elsewhere31833
Payment proof supplied353–
Clock advanced251–
Engine scan round260–
Order-event log read292–
Engine restarted306–
Fault injected302–

29 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 wallets2340.41×
3 wallets1870.32×
4 wallets1560.27×
2 transactions1090.19×
3 transactions2380.41×
4 transactions2300.40×
Direct scanning3020.52×
Worker scanning2750.48×
Recorded mainnet + synthetic transactions3060.53×
Synthetic transactions only2710.47×

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,3957.6×
Node request timed out8791.5×
Key custody failed8791.5×
Every node down at once8791.5×
Log read resumed after a cut-off7331.3×
Scan worker restarted mid-history1420.25×
Database reopened mid-history3060.53×
Custody handle replaced3150.55×

Money rules checked

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

SituationRelativeTimes#/H
Proven payment settles3,1325.4×
Unproven payment stays pending7041.2×
Missing proof holds settlement9151.6×
Mismatching proof holds settlement640.11×
Disputed spend keeps funds counted1500.26×
Spend confirmed by every node voids the output1460.25×
Voided output restored after reorg920.16×
Order events read back with identical bytes7131.2×
Order expiry derived11,63520×
Ledger identical after reopening5771.0×
GroupPropertiesTime
work::properties::money::nodes2133 min
  • ✓Real verifier and scanner reconcile verified payments across forks and restarts with a forged primary16 min
  • ✓Real verifier and scanner reconcile verified payments across forks and restarts14 min
  • ✓Multi node money histories survive conflicting branches hangs outages and restarts43.0 s
  • ✓Tenant pages yield to other tiers and resume without losing money27.9 s
  • ✓False mining locations without block membership cannot unlock verified settlement26.6 s
  • ✓All node outages preserve money and recover once one node returns22.6 s
  • ✓False spent votes and last reachable node policy preserve payment identity on recovery21.8 s
  • ✓Inflated tips and false attestations cannot bypass enabled verification21.6 s
  • ✓Malformed missing and reordered bodies never publish an unfinished block17.4 s
  • ✓Hanging secondary retracts double spends within the settlement budget14.7 s
  • ✓Zero confirmation and unverified height trust boundaries are explicit13.4 s
  • ✓Omitted pool transactions delay detection without creating false payments13.0 s
  • ✓Cancellation of any pinned rpc records failure without mixing node answers7.5 s
  • ✓Every malformed response shape is checked with a restart383 ms
  • ✓Sole configured and sole reachable votes have the same explicit trust limit107 ms
  • ✓An outer timeout cools the pinned node and the next money round uses a fallback48 ms
  • ✓Responses at transport deadline boundaries preserve failover and recovery21 ms
  • ✓Failover histories bound hangs and recover the highest priority eligible node20 ms
  • ✓Corroborated key images match an independent vote model even with hangs19 ms
  • ✓Corroborated locations preserve positive evidence and bound unreachable nodes19 ms
  • ✓Hanging peers do not discard healthy location or spent answers8 ms
proof::properties632 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 outage10 min
  • ✓Verifier rpc timeouts during anchor reads and samples use healthy peers4 min
  • ✓Anchoring requires a configured majority even when only one node answers4 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::expansions1515 min
  • ✓Reorg collection of mined outputs crosses full queue pages without losing outputs4 min
  • ✓Reorg collection of pool outputs crosses full queue pages without losing outputs3 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
  • ✓Stored amount boundaries survive scanning mining and restarts35.9 s
  • ✓Production worker crashes preserve atomic money34.7 s
  • ✓Process death during sqlite work recovers exactly once money19.8 s
  • ✓Unrepresentable amounts fail without corrupting stored money12.2 s
  • ✓Oversized invoices do not consume an address or idempotency key5.5 s
  • ✓Every worker crash boundary and the full recovery pipeline run621 ms
  • ✓Reorg collection of a 255 output pool payment faulted at once447 ms
  • ✓Named money durability boundaries recover after process death335 ms
  • ✓An aggregate one piconero above the storage limit rolls back38 ms
  • ✓Crash process child7 ms
work::properties::money176 min
  • ✓Independent payments are routed and credited once across reorgs49.1 s
  • ✓Late key registration cannot starve healthy catch up tenants43.8 s
  • ✓Concurrent fast scans rounds and order creation preserve exactly once credit35.3 s
  • ✓Reused output keys never create extra spendable money and winners follow reorgs34.9 s
  • ✓Fast pool passes and rounds share exactly once credit across restarts29.1 s
  • ✓Every output in a transaction is routed once and uses its orders policy22.8 s
  • ✓Transaction paging and fetch failures cannot credit unfinished blocks22.6 s
  • ✓Distinct wallets cannot credit each others outputs22.5 s
  • ✓One networks rounds cannot scan or credit another networks orders21.9 s
  • ✓Late payments do not unsettle an order already covered by confirmed funds16.0 s
  • ✓Deadlines wait for backlogged scans and do not hide a payment15.6 s
  • ✓Settlement waits for verified depth and the payments attested block15.5 s
  • ✓Closed order grace boundary controls late pool scanning14.1 s
  • ✓Missing or ambiguous evidence cannot void money13.7 s
  • ✓A new order is found in an already cached pool transaction12.6 s
  • ✓A zero confirmation settlement reopens when its output key conflict loses its winner91 ms
  • ✓An unregistered tenant cannot hide a healthy tenant in the next catch up page61 ms
work::properties236 min
  • ✓Portfolio histories match independent ledger2 min
  • ✓Coverage guided histories recover with real scanner58.5 s
  • ✓Scanner histories converge after reorgs failures and restarts49.9 s
  • ✓Failing tenants do not starve healthy tenants and catch up after restart49.0 s
  • ✓Reorg jobs survive outages and restore payments when evidence changes39.0 s
  • ✓Replacement chains of different lengths recompute payment depth31.8 s
  • ✓An interrupted block replaced by a fork never leaks staged payments20.2 s
  • ✓Every recorded ringct variant runs complete money histories10.5 s
  • ✓Combined portfolio interactions have fixed positive controls1.6 s
  • ✓Portfolio selects every sql operation class and position1.6 s
  • ✓Reviewed engine history seeds replay650 ms
  • ✓Portfolio reorg retains pending settlement until branch is proven529 ms
  • ✓Portfolio composes arrival extension reorg proof expiry and faults366 ms
  • ✓Portfolio reports selected applied and skipped commands separately168 ms
  • ✓A shorter branch reopens a settled payment below the fork112 ms
  • ✓A replaced payment block recorded from its header cannot settle the order102 ms
  • ✓A shorter tip above scanned history recomputes depth after restart87 ms
  • ✓A fork after rewind before rescanning cannot keep a discarded payment height77 ms
  • ✓A second fork after reconciliation cannot keep a discarded payment height69 ms
  • ✓A single page budget eventually serves all tenants despite a custody failure68 ms
  • ✓A shorter branch recomputes confirmations even when the payment height is unchanged62 ms
  • ✓Recorded ringct shapes and recipient expectations are frozen41 ms
  • ✓Reviewed portfolio seeds are the encoded reviewed scenarios9 ms
work::mempool::properties::scale63 min
  • ✓Each transaction visits each tenant independently2 min
  • ✓Parallel pool backlogs recover and release cache38.8 s
  • ✓Expanded windows are served during continuous new transaction floods1.9 s
  • ✓Arrivals cannot increase existing work wait1.4 s
  • ✓Transaction cursors cannot be shared between three transactions474 ms
  • ✓Production body caps reject overflow and reuse freed space91 ms
work::properties::lifecycle31 min
  • ✓Forks at and beyond retention edges preserve the documented evidence limit31.4 s
  • ✓Genesis divergence recovers through generated failures and restarts29.2 s
  • ✓Bootstrap histories recover from empty short and unservable chains28.6 s
work::properties::concurrency41 min
  • ✓Combined worker histories preserve money49.7 s
  • ✓Proof config and generation histories preserve settlement24.5 s
  • ✓Every proof config shutdown admission schedule recovers4.9 s
  • ✓Every overlap admission order and abandoned caller recovers896 ms
node_events::properties61 min
  • ✓Actual mempool loop detects payments despite missing wrong or burst notifications1 min
  • ✓All polling recovery modes run640 ms
  • ✓Notification histories match pending permits82 ms
  • ✓Stale generation announcements do not wake replacements26 ms
  • ✓Arbitrary topics match exact prefixes23 ms
  • ✓Reviewed notification histories replay8 ms
http::properties131 min
  • ✓Order histories preserve tenant scoped idempotency and addresses24.5 s
  • ✓Requests without keys create distinct purchases13.0 s
  • ✓Concurrent creation and retries use the production database worker9.3 s
  • ✓Process death at order commit preserves atomic allocation9.2 s
  • ✓Cancelling during derivation preserves the address for retry4.1 s
  • ✓Sql failures and lost results never burn addresses3.4 s
  • ✓Every purchase identity field rejects conflicting replays3.4 s
  • ✓Disabling a tenant during derivation cannot publish an order3.2 s
  • ✓Request validation is atomic at integer and key boundaries3.0 s
  • ✓Exhaustion and mismatched claims cannot corrupt allocation2.7 s
  • ✓Every sql denial during order creation is atomic and retryable136 ms
  • ✓Exhausted counter still allows idempotent replays and never wraps46 ms
  • ✓Order creation crash child7 ms
http::properties::authorization1153.2 s
  • ✓Tenant ownership and revocation histories8.6 s
  • ✓Payment lookup cannot read or credit another tenants outputs8.5 s
  • ✓Concurrent rejections leave no writes or stream slot leaks6.6 s
  • ✓Cross tenant refund writes are atomic6.4 s
  • ✓Lists and live events expose only the authenticated tenant5.4 s
  • ✓Body identity cannot steer own tenant operations4.8 s
  • ✓Permission matrix rejects credentials without data or writes4.6 s
  • ✓Repeated headers bind only the first presented capability4.6 s
  • ✓Bearer syntax cannot expand tenant authority3.7 s
  • ✓Every route runs the engine and tenant rejection matrix114 ms
  • ✓Named revocation and cross tenant history replays45 ms
store::properties642.1 s
  • ✓Historic upgrades recover from process death20.6 s
  • ✓Upgrades preserve money queues and interrupted work16.9 s
  • ✓Migration process death before and after commit preserves money and schema2.3 s
  • ✓Every reached upgrade fault recovers without partial schema1.6 s
  • ✓Every historic schema upgrades with existing money and work873 ms
  • ✓Migration crash child7 ms
plain::properties835.9 s
  • ✓Sparse window histories match fresh scans without rebuilding kept keys22.2 s
  • ✓Lookup ranges match fresh scans and do not poison the live window7.0 s
  • ✓Concurrent wallet scans keep independent caches and matches3.9 s
  • ✓Cancelling queued scans preserves both caches2.0 s
  • ✓Invalid material and oversized ranges cannot poison a live wallet525 ms
  • ✓Idempotent registration sealing and removal preserve wallet identity231 ms
  • ✓Table build batches keep exact progress across window changes53 ms
  • ✓Invalid registration ids never allocate wallets35 ms
node_events::properties::zmq223.5 s
  • ✓Real subscriber recovers and tracks generated publisher configuration23.4 s
  • ✓Publisher switch disable reenable and duplicate endpoints86 ms
store::db::properties::scale121.2 s
  • ✓Repeated saturation retains fifo fairness and cancellation21.2 s
work::mempool::properties1616.9 s
  • ✓Real fast and round callers share reservations9.3 s
  • ✓Mixed tenant success failure and empty matches retry independently4.5 s
  • ✓Batch reservations and cache histories2.1 s
  • ✓Overlapping thread batches have exactly one owner per key583 ms
  • ✓Every mixed batch failure boundary runs in both orders113 ms
  • ✓Both entry points cover owner success and cancellation80 ms
  • ✓Competing scans release on success failure and cancellation66 ms
  • ✓Reservation histories match independent ownership51 ms
  • ✓Cache accounting obeys full width budgets21 ms
  • ✓Reservation decision grid checks completed busy and changed windows11 ms
  • ✓A completion for another window cannot publish or release its owner9 ms
  • ✓Ownership survives pool eviction and serializes changed windows8 ms
  • ✓Partially completed batches and panics release only unfinished work8 ms
  • ✓Reviewed fuzz histories remain valid regressions8 ms
  • ✓Simultaneous threads cannot both reserve the same scan8 ms
  • ✓Unrelated transactions and tenants are not serialized7 ms
store::db::properties46.4 s
  • ✓Closing all senders drains accepted jobs3.4 s
  • ✓Bounded queues preserve fifo fairness and accepted work3.0 s
  • ✓Class selection matches independent round robin model12 ms
  • ✓Continuously ready classes are served within one turn each8 ms
store::work::properties14.9 s
  • ✓Generated reorg staging is scoped and durable4.9 s
loops::properties44.9 s
  • ✓Real network manager follows generated node configuration4.6 s
  • ✓Generations stop before replacement and recover from panics174 ms
  • ✓Stopping during factory or loop restart backoff is immediate68 ms
  • ✓Dropping a manager generation stops its children22 ms
work::blocks::properties33.2 s
  • ✓Accepted block effects recheck durable prerequisites3.1 s
  • ✓Every late commit boundary is exercised with and without staging155 ms
  • ✓Cache histories preserve accounting and protected blocks17 ms
router::properties62.7 s
  • ✓Registration removal and backend reload histories never cross wallets1.9 s
  • ✓Registrations finishing after replacement are freed and retry on the new backend526 ms
  • ✓Cancelled registration results are recovered by the same registration id133 ms
  • ✓Backend epoch changes invalidate only the restarted backend98 ms
  • ✓Transient backend failures preserve ownership until recovery95 ms
  • ✓Two maximum backend epochs do not panic or wrap5 ms
exploration::properties8170 ms
  • ✓Application input boundaries handle arbitrary bytes53 ms
  • ✓Cpu ranges match an independent set model45 ms
  • ✓Queue byte histories preserve dispatch and drain contracts27 ms
  • ✓Event byte histories preserve scheduler invariants20 ms
  • ✓Sizing and retry boundaries handle all float bit patterns7 ms
  • ✓Special float patterns are always exercised7 ms
  • ✓Hostile cpu range lengths are rejected before allocation6 ms
  • ✓Maximum cpu range is rejected without expansion5 ms
work::scheduler::properties11156 ms
  • ✓Retry histories reset and expire independently79 ms
  • ✓Scheduler matches the two pass contract11 ms
  • ✓Retry policy decision grid checks free attempts and deadlines9 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 rejected7 ms
  • ✓Outstanding and duplicate completions cannot change progress7 ms
  • ✓Retry boundaries saturate and never wrap7 ms
  • ✓Retry count at u32 max stays capped7 ms
  • ✓Retry expiry boundaries use production upkeep7 ms
  • ✓Backwards clock observations cannot extend an expired share6 ms
status::properties10138 ms
  • ✓Fuzz histories match status specification27 ms
  • ✓Adding payments cannot undo settlement20 ms
  • ✓Splitting a payment with the same evidence preserves status15 ms
  • ✓Agrees with aggregate specification14 ms
  • ✓Funded orders are independent of the clock14 ms
  • ✓Increasing mined confirmations preserves settlement14 ms
  • ✓Payment order does not change status14 ms
  • ✓Expiry is strictly after the deadline7 ms
  • ✓Reviewed status fuzz seeds replay7 ms
  • ✓Status decision boundaries match independent grid6 ms
scaling::properties677 ms
  • ✓Progress histories survive clock jumps and extreme counters39 ms
  • ✓Request sizes remain bounded across numeric inputs9 ms
  • ✓Larger resources and faster links do not reduce request size8 ms
  • ✓Maximum timeout and wall clock boundaries do not panic7 ms
  • ✓Stopped and backwards clocks cannot grow telemetry without bound7 ms
  • ✓Timeout bounds hold for extreme measurements7 ms