Summary
The internal wallet's sync loop (zebra-crosslink/wallet/src/lib.rs) performs all its gRPC stream reads with no timeout. If zaino accepts a request and then never yields the next stream message, the wallet awaits forever: no error branch fires, nothing is printed, and wallet sync silently freezes while the node itself stays perfectly healthy. There is no way to see this from the logs — the loop just stops emitting.
Evidence (s2v9 + PR #29/#42, testnet CLT0)
GUI wallet stuck at ~85% for 8+ hours (twice). After a restart with snapshot resume:
loaded wallet snapshot from ".../wallet.snapshot"
Downloading t txs in 199488-200512 for 0.0/[addr-redacted]
Downloading t txs in 199488-200512 for 0.1/[addr-redacted]
Downloading t txs in 199488-200512 for 1.0/[addr-redacted]
Downloading t txs in 199488-200512 for 1.1/[addr-redacted]
…and then zero further wallet output for 40+ minutes (a healthy loop iterates every ~250ms). No Failed to get ..., no torn-read message, no reorg message — the loop is parked inside an await on a stream .message() that never resolves. Meanwhile zebrad itself is at 100% sync, finalizing via BFT, serving RPC normally.
Affected awaits include (line numbers from the s2v9 tag):
tx_stream.message().await (transparent tx stream, ~L3801)
block_stream.message().await (compact block stream, ~L3643)
The code itself anticipates indexer flakiness — the comment above the t-tx request reads: "TODO IMPORTANT: the indexer can 'succeed' without actually giving us all the txs in the range we requested..." — but a stream that stalls mid-iteration is unhandled.
Suggested fix
Wrap the stream reads in tokio::time::timeout (e.g. 30s) and route the timeout into the existing error arms (which already log, mark t_failed_at_h / break, and retry on the next loop iteration). A once-per-N-iterations heartbeat print would also make any future stall visible in minutes instead of hours.
We're running exactly this locally (timeout → tonic::Status::deadline_exceeded → existing error path) and can submit it as a PR if that's welcome.
Environment
Summary
The internal wallet's sync loop (
zebra-crosslink/wallet/src/lib.rs) performs all its gRPC stream reads with no timeout. If zaino accepts a request and then never yields the next stream message, the walletawaits forever: no error branch fires, nothing is printed, and wallet sync silently freezes while the node itself stays perfectly healthy. There is no way to see this from the logs — the loop just stops emitting.Evidence (s2v9 + PR #29/#42, testnet CLT0)
GUI wallet stuck at ~85% for 8+ hours (twice). After a restart with snapshot resume:
…and then zero further wallet output for 40+ minutes (a healthy loop iterates every ~250ms). No
Failed to get ..., no torn-read message, no reorg message — the loop is parked inside anawaiton a stream.message()that never resolves. Meanwhile zebrad itself is at 100% sync, finalizing via BFT, serving RPC normally.Affected awaits include (line numbers from the s2v9 tag):
tx_stream.message().await(transparent tx stream, ~L3801)block_stream.message().await(compact block stream, ~L3643)The code itself anticipates indexer flakiness — the comment above the t-tx request reads: "TODO IMPORTANT: the indexer can 'succeed' without actually giving us all the txs in the range we requested..." — but a stream that stalls mid-iteration is unhandled.
Suggested fix
Wrap the stream reads in
tokio::time::timeout(e.g. 30s) and route the timeout into the existing error arms (which already log, markt_failed_at_h/ break, and retry on the next loop iteration). A once-per-N-iterations heartbeat print would also make any future stall visible in minutes instead of hours.We're running exactly this locally (timeout →
tonic::Status::deadline_exceeded→ existing error path) and can submit it as a PR if that's welcome.Environment