You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
zcash_local_net::validator::zebrad::Zebrad::launch contains two unconditional std::thread::sleep(Duration::from_secs(5)) calls — 10 seconds of pure wait per launch — with no commit-history rationale. They are also std::thread::sleep (not tokio::time::sleep) inside an async fn, so they park the entire tokio worker thread for the duration. On a multi_thread(2) runtime that's half the runtime offline for 10s straight per launch.
Cumulative impact for downstream consumers: zaino's integration-tests stable profile has ~25 Zebrad-backed tests. Each pays the 10s tax. That's ~4-5 minutes of pure wall-clock waste per full suite run, plus loss of runtime parallelism during the sleeps.
Where the sleeps live
#
Location (pre-fix)
Duration
Type
Context
1
zcash_local_net/src/validator/zebrad.rs:234
5 s
std::thread::sleep
Right after launch::wait returned success
2
zcash_local_net/src/validator/zebrad.rs:255
5 s
std::thread::sleep
Right after generate_blocks(1) (post-genesis-mine)
Both were introduced at file creation (bc63bdd refactor compiles). git log -L returns only the single creation commit — no rationale.
Why each is wasteful
Sleep update name, and make test compare expected deviation between lwd and… #1: launch::wait (in launch.rs) already polls zebrad's stdout for the success indicators "Opened RPC endpoint at ", "zebra_rpc::indexer::server: Opened RPC endpoint at ", and "spawned initial Zebra tasks". By the time wait() returns, the listener is bound. Adding a 5-second wall-time fudge afterward gates on nothing observable — pure "wait some more, just in case."
Sleep Break zcash_local_net into two crates #2: generate_blocks calls poll_chain_height(target_height) internally (loops getchaintips until the new tip is reflected). When generate_blocks(1) returns, the validator demonstrably sees the new block. The 5 s after is gating on nothing.
Both use std::thread::sleep: parks the worker thread instead of yielding. With multi_thread(2) (typical in zaino's integration tests) half the runtime is offline; other tasks can't make progress. tokio::time::sleep would yield correctly.
Proposed fix (work in progress on branch speed_zcash_local_net_up)
Delete sleep Break zcash_local_net into two crates #2 entirely.generate_blocks + poll_chain_height already provide a stricter "block is observable" signal than a fixed wait.
Replace sleep update name, and make test compare expected deviation between lwd and… #1 with a bounded retry inside generate_blocks around (getblocktemplate → build proposal → submitblock). Each retry re-reads the template (fresh state, fresh timestamp). Bounded so a real consensus rejection surfaces deterministically. Currently 30 attempts × 100 ms = 3 s ceiling; in the happy path the first or second attempt succeeds.
Add LaunchError::RpcReadinessTimeout for the case where the RPC framework genuinely never comes up (rather than panicking deep inside getblocktemplate).
Empirical impact
Test
Before
After (best case)
launch_zebrad (cold, full suite)
~15 s
~5-7 s
Most of the remaining ~5-7 s is zebrad's own process boot (cold binary load, listener bind), which lives behind launch::wait already.
Companion issue
A diagnostic run during this work surfaced a separate, unrelated consensus failure: \"missing lockbox disbursements for NU6.1 activation block\". That is not a sleep / readiness bug — it's a real consensus-rule gap in proposal_block_from_template for NU6.1 activation blocks. Filed separately so the two threads don't get conflated.
Out of scope (lower-priority sleep sites)
For completeness, other sleep-like sites in zcash_local_net:
validator/zcashd.rs 1.5 s tokio::time::sleep inside opt-in generate_blocks_with_delay — intentional.
validator/zcashd.rs 500 ms tick in poll_chain_height — tunable.
validator/zebrad.rs 1.5 s in generate_blocks_with_delay — intentional.
validator/zebrad.rs 100 ms tick in poll_chain_height — tunable.
validator.rs 3 s in cache_chain after stop() — test-cache build only.
launch.rs 100 ms tick in launch::wait log-poll — appropriately scoped.
These are either hot-path-irrelevant or appropriately scoped polls. The two 5 s sleeps in Zebrad::launch were the outliers worth fixing.
Summary
zcash_local_net::validator::zebrad::Zebrad::launchcontains two unconditionalstd::thread::sleep(Duration::from_secs(5))calls — 10 seconds of pure wait per launch — with no commit-history rationale. They are alsostd::thread::sleep(nottokio::time::sleep) inside anasync fn, so they park the entire tokio worker thread for the duration. On amulti_thread(2)runtime that's half the runtime offline for 10s straight per launch.Cumulative impact for downstream consumers: zaino's integration-tests
stableprofile has ~25 Zebrad-backed tests. Each pays the 10s tax. That's ~4-5 minutes of pure wall-clock waste per full suite run, plus loss of runtime parallelism during the sleeps.Where the sleeps live
zcash_local_net/src/validator/zebrad.rs:234std::thread::sleeplaunch::waitreturned successzcash_local_net/src/validator/zebrad.rs:255std::thread::sleepgenerate_blocks(1)(post-genesis-mine)Both were introduced at file creation (
bc63bdd refactor compiles).git log -Lreturns only the single creation commit — no rationale.Why each is wasteful
launch::wait(inlaunch.rs) already polls zebrad's stdout for the success indicators"Opened RPC endpoint at ","zebra_rpc::indexer::server: Opened RPC endpoint at ", and"spawned initial Zebra tasks". By the timewait()returns, the listener is bound. Adding a 5-second wall-time fudge afterward gates on nothing observable — pure "wait some more, just in case."generate_blockscallspoll_chain_height(target_height)internally (loopsgetchaintipsuntil the new tip is reflected). Whengenerate_blocks(1)returns, the validator demonstrably sees the new block. The 5 s after is gating on nothing.std::thread::sleep: parks the worker thread instead of yielding. Withmulti_thread(2)(typical in zaino's integration tests) half the runtime is offline; other tasks can't make progress.tokio::time::sleepwould yield correctly.Proposed fix (work in progress on branch
speed_zcash_local_net_up)generate_blocks+poll_chain_heightalready provide a stricter "block is observable" signal than a fixed wait.generate_blocksaround(getblocktemplate → build proposal → submitblock). Each retry re-reads the template (fresh state, fresh timestamp). Bounded so a real consensus rejection surfaces deterministically. Currently 30 attempts × 100 ms = 3 s ceiling; in the happy path the first or second attempt succeeds.LaunchError::RpcReadinessTimeoutfor the case where the RPC framework genuinely never comes up (rather than panicking deep insidegetblocktemplate).Empirical impact
launch_zebrad(cold, full suite)Most of the remaining ~5-7 s is zebrad's own process boot (cold binary load, listener bind), which lives behind
launch::waitalready.Companion issue
A diagnostic run during this work surfaced a separate, unrelated consensus failure:
\"missing lockbox disbursements for NU6.1 activation block\". That is not a sleep / readiness bug — it's a real consensus-rule gap inproposal_block_from_templatefor NU6.1 activation blocks. Filed separately so the two threads don't get conflated.Out of scope (lower-priority sleep sites)
For completeness, other sleep-like sites in
zcash_local_net:validator/zcashd.rs1.5 stokio::time::sleepinside opt-ingenerate_blocks_with_delay— intentional.validator/zcashd.rs500 ms tick inpoll_chain_height— tunable.validator/zebrad.rs1.5 s ingenerate_blocks_with_delay— intentional.validator/zebrad.rs100 ms tick inpoll_chain_height— tunable.validator.rs3 s incache_chainafterstop()— test-cache build only.launch.rs100 ms tick inlaunch::waitlog-poll — appropriately scoped.These are either hot-path-irrelevant or appropriately scoped polls. The two 5 s sleeps in
Zebrad::launchwere the outliers worth fixing.