Skip to content

initialize_chain hangs forever: no-timeout wallet-sync loop + zebrad nodes left at divergent heights after final restart #136

Description

@zancas

Summary

full_test_suite.py (and any rpc-test using the cached chain) hangs forever during initialize_chain cache generation. The wait loop in qa/rpc-tests/test_framework/util.py that blocks on all wallets reaching the node tip has no timeout, and the 8 zebrad nodes it waits on end up stuck at divergent block heights that never converge — so the loop can never exit and the suite hangs instead of failing.

Symptom

Running:

uv run ./qa/zcash/full_test_suite.py

hangs with zallet repeatedly logging Reached chain tip, streaming mempool and re-scanning the same block range, with no further progress and no error.

Diagnosis

The hang is in initialize_chain, in the final wallet-sync wait:

https://github.com/zcash/integration-tests/blob/main/qa/rpc-tests/test_framework/util.py#L485

# Wait for zallets to synchronize with the nodes.
while True:                                    # <-- no timeout
    wallet_status = [ x.getwalletstatus() for x in wallets ]
    if all('wallet_tip' in w for w in wallet_status):
        tips = [ (w['node_tip']['blockhash'], w['wallet_tip']['blockhash']) for w in wallet_status ]
        if tips == [ tips[0] ]*len(tips) and tips[0][0] == tips[0][1]:
            break
    time.sleep(0.25)

This loop only breaks when all 8 wallets share one identical node_tip (tips == [tips[0]]*len(tips)) and each wallet's wallet_tip == node_tip. Unlike sync_blocks/sync_mempools (which have a 60s timeout and raise AssertionError), this one has no escape hatch.

Live evidence

Probing getwalletstatus on the hung run (5 samples, 1s apart — all stable):

wallet node_tip.height wallet_tip.height
0 162 162
1 162 162
2 162 162
3 180 180
4 177 177
5 176 176
6 176 176
7 186 186

Each wallet's wallet_tip exactly matches its own node_tip and is stable — i.e. the wallets are fully synced; zallet is not the problem. The 8 zebrad nodes are stuck at different heights (162–186) and never converge, so the all-equal condition can never hold.

Why the nodes diverge

The block-generation loop mines on one peer, then stops and restarts all 8 zebrad nodes every round as a workaround for ZcashFoundation/zebra#10329 / #10332 ("zebrad won't broadcast received blocks to other connected nodes"):

https://github.com/zcash/integration-tests/blob/main/qa/rpc-tests/test_framework/util.py#L424-L459

for i in range(2):
    for peer in range(4):
        # Connect the other nodes to the mining peer.
        for i in range(0, MAX_NODES):
            if i != peer:
                connect_nodes_bi(rpcs, i, peer)
        # Mine the blocks
        for j in range(25):
            rpcs[peer].generate(1)
            block_time += PRE_BLOSSOM_BLOCK_TARGET_SPACING
        # Must sync before next peer starts generating blocks
        sync_blocks(rpcs)
        # Shut down and restart every zebrad node. (workaround for zebra#10329/#10332)
        stop_nodes(rpcs)
        wait_bitcoinds()
        for i in range(MAX_NODES):
            ...restart each node...
        for i in range(MAX_NODES):
            rpcs.append(get_rpc_proxy(rpc_url(i), i))   # (1) appends instead of replacing

Two structural problems compound here:

  1. No reconnect / final sync_blocks after the last restart. connect_nodes_bi runs only at the top of each round. After the final round's stop_nodes + restart, the nodes come back up not peered to each other and with no final sync_blocks — so they sit at divergent heights forever. (Confirmed: there is no connect_nodes/sync_blocks/generate between the loop and the wallet startup at lines 460–485.) This likely interacts with the ephemeral node state ([state] cache_dir = "", [network] cache_dir = false in qa/defaults/zebrad/config.toml): on restart each node has nothing to re-sync from.

  2. rpcs.append(...) grows the list unbounded (line marked (1)) instead of replacing it — after 8 restarts rpcs holds ~72 proxy objects (all pointing at the same 8 ports). Latent, but it makes sync_blocks(rpcs) and rpcs[peer] increasingly confusing.

Note: the exact mechanism by which the heights end up different (rather than equal-but-isolated) after the final restart still needs confirmation — it may be the ephemeral-state + no-reconnect interaction above, or a deeper restart/IBD issue. The two facts that are fully verified: (a) the nodes are stable at divergent heights, and (b) the wait loop has no timeout so the suite hangs rather than failing.

Suggested fixes

  1. Give the :485 loop a timeout (mirroring sync_blocks) so a non-converging chain fails loudly with the tip table printed, instead of hanging. This alone makes every future occurrence debuggable.
  2. Reconnect + sync_blocks(rpcs) after the final restart (or restructure so the last round ends in a converged, synced state) so the 8 nodes actually share a tip before the wallets start.
  3. Replace, don't append, rpcs after each restart (rpcs[:] = [...] or rebuild the list).

Environment

  • integration-tests @ 175585714 (main)
  • zallet 0.1.0-alpha.4, built from zallet main
  • zebrad / zainod from current main
  • Linux x86_64

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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