test(simulation): halve sim wall time (31→15.6 min median)
Halves test-simulation wall time in CI: the median goes from 31.2 to 15.6 min (−50%), with no coverage dropped. Every change is either gated to mocknet builds or touches only the test harness or CI config, so non-mocknet consensus and bifrost behavior are unchanged.
Results
CI, before vs after. Successful runs only. "Before" is every other branch's sim since !5147 (merged) landed.
| Before | After (final branch) | Change | |
|---|---|---|---|
| All runners | median 31.2 min (27.0-57.9, n=30) | median 15.6 min (15.4-17.4, n=5) | −15.6 min (−50%) |
| Finland | median 33.3 min (n=10) | 17.1 min (n=1) | −49% |
| harry | median 31.9 min (n=9) | 15.4 / 15.6 / 15.4 min | −52% |
| hermonie | median 27.9 min (n=11) | 17.4 min (n=1) | −38% |
| Original baseline: job 16972355155, Finland, !5166 (merged) | 29.0 min | 17.1 min (Finland) | −41% |
Local A/B. Same host, runs interleaved, CI-equivalent build (golang 1.26 bookworm container driving the host docker daemon), PARALLELISM as configured for CI. This is the least noisy comparison.
| Before (develop) | After (final branch) | Change | |
|---|---|---|---|
| Whole run | 25.4 / 25.6 min | 13.7 / 13.5 min | −47% |
| Sim phase (actors) | 1411 / 1439s | 729 / 748s | −48% |
Where the time came from. Baseline CI job 16972355155 compared with the latest Finland (16983971920) and harry (16987040973) jobs.
| Phase | Before | After | Main changes |
|---|---|---|---|
| Setup (job start → sim start) | 328s | 106-199s | 5, 6, 10, 12 |
| Before the actors start | 128s | 101-144s | 3, 7, 11 |
| Bootstrap | 79s | 46-47s | 1 |
| Swaps + memoless swaps | 418s | 183-214s | 1, 8 |
| Memoless refunds | 74s | 35-37s | 1, 8 |
| Churn (both) | 370s | 222-246s | 4, 9 |
| Ragnarok | 162s | 18-22s | 2 |
| Sim phase total | 1403s | 809-818s | −42% |
Progression (CI job time):
- 29.0 min: baseline
- 22.5-22.9 min: changes 1-3
- 21.2 min: change 4
- 20.6 min: change 5
- 15.4-17.1 min: changes 6-8
- 15.4-17.4 min: changes 9-12
Reliability. This MR's sims failed 4 of 14 times (29%), against 18 of 48 (38%) on other branches over the same period. None of the four traces to a change that is still in the branch: one registry-auth infra error, one run with the since-dropped faster-Monero-mining change, and two hits of the pre-existing refund bug #3383 (see Related). The latest merged-result pipeline, run against develop 6d14167e98, passed every job on the first attempt.
Runner time. About 15 min saved per sim job. At the recent rate (~40 sim jobs a day on other branches) that is roughly 10 runner-hours a day.
Caveats.
- Five successful CI runs have the final branch: three on harry, one on Finland, one on hermonie.
- CI run time varies by about ±30s, so the later 20-40s changes can't each be shown in CI alone. They are backed by the local A/B and per-phase timings. The overall halving is far outside that noise.
Changes
Mocknet-gated product code. Non-mocknet builds are unchanged.
- POL inbound confirmations 15 → 3 (
69f4a3ae65,bifrost/pkg/chainclients/evm, build-tag constant). Mocknet bor seals a block every 2s (--dev.period 2) and never reorgs. At 15 confirmations every POL inbound waited ~30s, and POL actors took 39-49s where other chains took 5-15s. 3 matches BSC, so inbounds still go through a pre-confirmation observation before the final one. THORChain does not enforce the finalise gap. - Genesis XMR monovault keygen at block 64 instead of 128 (
a4ce38ef32). Mocknet builds readXMR_MONOVAULT_GENESIS_DELAY_BLOCKS, which defaults to 128, so regtest and its exports are unchanged. Compose sets 64 in the sharedthornode-environment, so every validator replays the same genesis. bifrost is scanning THOR blocks by about block 6. The old delay held the DAG start ~47s after funding finished. - Pipelined EVM contract deploys (
6b55730d0f,build/scripts/evm/evm-tool.py, deploy action only). Thornode genesis waits for the router deploys, and each chain used to wait a block per transaction (up to ~26s on BASE's 3s blocks). The setup transactions now go out back to back in the original nonce order and are confirmed together, so the contract addresses are unchanged: token at admin nonce 1, router at 3, aggregator at 4. The token transfers sent before the token is mined take their target from the CREATE formula, checked againstrlpfor 304 nonces, and use a fixed gas limit. Every receipt is checked for success and for the expected contract address. Deploys went from 26s to 6-7s, and thornode start → healthy from 36s to 15s in CI.
Mocknet compose and CI configuration
4. FUND_MIGRATION_INTERVAL 30 → 15 (26c60adaaa). Each churn ran three 30-block migration rounds with ~30 idle blocks between one round being observed and the next. Since ca561d0c, a chain whose previous migrate is still unobserved skips the round, so 15 still exercises multi-round migration: the skip fired 11-27 times per run, against 2 at 30. Runs at 15 showed no stale-migrate retries, sequence mismatches or missing target vaults. The 10 → 30 bump in 7e1c2cc4 predates that per-chain gate.
5. Base images built while Go compiles (3888e73ff1, Makefile). The runtime image (apt and pip in an empty dind), the monero image and the XMR signer pulls need no Go binaries, but they ran one after another after the ~100s native build. They now run alongside the build and alongside each other, and the three thin binary images build in parallel. On harry, make → thornode up went from 3:19 to 1:49; on Finland, from 4:03 to 1:56.
6. Dependency-proxy probe uses docker buildx imagetools inspect (4e54fd30d0, .dind-registries). docker manifest inspect golang:1.26.6 took 16-32s in every dind job (test-simulation, generate, build-thornode). imagetools resolves the same manifest in 1-3s and fails the same way for a missing tag, and every job that uses the probe ships buildx.
8. PARALLELISM 4 → 8 (034bc8591a). At 4 the swap stage kept every slot busy (95%), and the memoless-refund, inactive-vault-refund and ragnarok stages are slot-bound too. At 8 the actor phase went from 910s to 717s on Finland.
10. Base images pulled before the L1 chains; two Go builds in parallel (f5e898982c). With ~16 L1 image pulls running at the same time, the runtime image's debian base took ~60s on the critical path, against ~4s on its own. Warm Go builds at 8 CPUs go from 44s to 26s when run in parallel. This helps where pulls compete for bandwidth (Finland); harry, where the Go build is the critical path, showed no change.
Simulation harness only
2. Ragnarok stage votes FundMigrationInterval=5 (2f5f99e760). checkPoolRagnarok only acts on interval boundaries, so each pool took a fixed ~39s, and gas pools behind a token pool twice that. Churn migration has finished by this stage. The stage votes from every active node, waits for the mimir to apply, then runs the unchanged pool actors and Ragnarok-Verify. features.NewMultiNodeMimirActor is exported for this; its op now ends with Continue so callers can append ops, and existing callers behave the same.
7. XMR and SOL users funded without serial waits (914c37f7eb). XMR funding ran inline in the per-chain loop while it waited for faucet outputs to unlock, which held back POL and ZEC funding by ~30s. SOL funded users one at a time, ~13s each, waiting for finalized. XMR now runs in its own goroutine like the other chains, and SOL funds all users concurrently (SOL transfers have no account nonce).
9. Churns scheduled 8 blocks ahead instead of 20 (d2f7a2683f). The CHURNINTERVAL votes land 1-2 blocks after they are sent (all 8 churns in four runs), so each churn sat idle ~25s. The schedule now waits for the vote to apply and warns if it has not by the scheduled height. It does not reschedule, so it cannot trigger a second churn.
11. EVM funding sent without per-transfer receipt waits (095e362eb2). The sim's EVM client waited ~4s for each funding transfer's receipt, and at P=8 POL (16 transfers) was the last chain funded. Funding now sends every transfer back to back (the pending nonce keeps them consecutive), then waits for all receipts and fails on any revert. The pre-actor phase went from 82-98s to 66s locally.
Tried or investigated, not changed
- Faster Monero mining (
XMR_BLOCK_TICK_SECONDS2 → 0.5): dropped. It saved ~35s, but 2 of 4 runs failed. On CI Finland the XMR scanners fell behind and caught up in bursts while the runner stalled; locallyXMRSpentRefVerifytimed out. Four times the blocks means four times the scanning work for the ~11 containers that scan XMR. - XMR observation confirmations 10 → 3 (mocknet-gated): dropped. It sped up the early stages (XMR coverage 68s → 38s, swaps 218s → 163s), but it broke XMR migration. Observed inbounds were not yet spendable, so outbounds hit "insufficient xmr balance … locked", and one XMR migrate's finalized observation stalled for ~10 minutes. The retiring XMR vault blocked the next churn and the run ended in a solvency mismatch. Doing this safely needs unlock-aware spending in bifrost or THORChain.
- PARALLELISM 12. The swap stage gained 22s, but funding 12 users cost 24s up front, a net of zero. It stays at 8.
- Overlapping other stages with the churn. Memoless refunds must run before every churn (#2839 Section B). Inactive-vault refunds set
HaltChurning, which would block BondYield's churn. Swaps during a migration risk outbounds failing on insufficient funds (see the comment inmigrateFunds). XMR coverage withdraws half the XMR LP, which would move the pool under the swap stage's expected-output checks. - A migration interval below 15. The per-chain pending gate already skips chains at 15, so a lower interval would save only ~10s per churn.
- ETH migrate confirmations are value-based (the 1-conf-value rule) and now set when each churn ends. That is real coverage, so it is left alone.
Related
Two pre-existing failures surfaced while measuring. Both are filed and fixed in separate MRs, and neither is caused by this branch:
- #3383: a refund out of an inactive UTXO vault holding only the inbound UTXO cannot be signed, because bifrost reserves
amount + MaxGaswhile THORChain only deducted the outbound fee. This shows up asInactiveVaultRefund-BTCtimeouts on many branches. Fix: !5169 (merged). - #3384: a failed XMR migrate keysign is not retried until stale-migrate pruning, which shows up as
BondYield-ChurnOuttimeouts. Fix: !5170 (merged).
Measurement log
CI (merged-result pipelines; change numbers refer to the list above)
| Job | Runner | Changes | Result |
|---|---|---|---|
| 16972355155 (!5166 (merged)) | Finland | none (baseline) | pass, 29.0 min |
| 16976239745 | harry | 1-3 | fail: registry auth error pulling xmr-frost-signer (infra) |
| 16976932084 | harry | 1-3 | pass, 22.9 min |
| 16977950842 | Finland | 1-4 | pass, 22.5 min |
| 16978922788 | Finland | 1-4 | pass, 21.2 min |
| 16980046022 | Finland | 1-5 + Monero tick (dropped) | fail: runner stall during XMR catch-up |
| 16980586521 | harry | 1-5 + Monero tick (dropped) | pass, 18.0 min |
| 16981955487 | Finland | 1-6 | fail: InactiveVaultRefund-BTC (#3383) |
| 16982583118 | Finland | 1-6 | pass, 20.6 min |
| 16983971920 | Finland | 1-8 | pass, 17.1 min |
| 16984264379 | harry | 1-8 | pass, 15.4 min |
| 16985112682 | harry | 1-11 | pass, 15.6 min |
| 16986677618 | harry | 1-12 | fail: InactiveVaultRefund-BTC (#3383) |
| 16987040973 | harry | 1-12 | pass, 15.4 min |
| 16987623192 | hermonie | 1-12, against develop 6d14167e98 |
pass, 17.4 min |
Local
| Run | Changes | Result | Wall | Sim phase |
|---|---|---|---|---|
| base1, base2 | develop | pass | 25.4 / 25.6 min | 1411 / 1439s |
| cand2 | 1-3 | pass | 20.0 min | 1100s |
| exp15, exp15b, full1 | 1-4 | pass | 16.8-18.7 min | 917-1027s |
| p8b, p8d | 1-8 | pass | 14.3-14.8 min | 770-785s |
| r3b | 1-11 | pass | 13.7 min | 729s |
| r4c | 1-12 | pass | 13.5 min | 748s |
Local failures and their causes:
- cand1: the SOL aggregator inbound was never observed after finalizing on the 59th of 60 polls.
- full2: XMR migrate keysign
frost_sharetimeout (#3384). - p8c, p12a:
InactiveVaultRefund-BTC(#3383). - cpu05 and r4a: dropped experiments (Monero tick, XMR confirmations), described above.