Skip to content

Internal wallet sync hangs forever on stalled zaino gRPC stream (no read timeouts) — wallet stuck at same height for 8+ hours #45

Description

@hyphae-bot

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions