Skip to content

wallet sync task crashes with "Missing Orchard tree state" when a shielded pool is active but its tree is still empty #454

@oxarbitrage

Description

@oxarbitrage

Summary

fetch_chain_state (zallet/src/components/sync/steps.rs) returns a "Missing Orchard tree state" error — which exits the wallet sync task — whenever a shielded pool is active at the requested height but the indexer reports no tree state for it. This happens on any chain where a pool is active while its note commitment tree is still empty, most reliably on regtest, where NU5 is active from height 1 but the Orchard tree has no commitments yet.

Steps to reproduce

  1. Run zallet against a regtest node + Zaino (NU5 active from height 1).
  2. Start wallet sync over the early regtest chain (Orchard tree empty).
  3. Sync task exits with:
    IndexerError::InvalidData { message: "Missing Orchard tree state" }
    

(Reproduced in the integration-tests suite: the wallet.py and wallet_orchard_init.py regtest scenarios.)

Expected behaviour

An empty Orchard (or Sapling) tree is a valid state; sync should proceed treating it as an empty note commitment tree, not crash.

Root cause

fetch_chain_state assumes that if a pool is active, the indexer always returns Some(tree_state):

let final_orchard_tree = if params.is_nu_active(NetworkUpgrade::Nu5, height.0.into()) {
    read_frontier_v0(orchard.ok_or_else(|| IndexerError::InvalidData {
        message: "Missing Orchard tree state".into(),
    })?.as_slice()) ...
} else {
    Frontier::empty()
};

But an empty incremental-Merkle tree has no frontier, so the node reports None for it: zebra_rpc::z_get_treestate builds the field via tree.map(|t| t.to_rpc_bytes()), and zebra-state's read::orchard_tree() returns None when no tree is stored at that height. Zaino faithfully relays this None. So None ⟺ "empty tree" — it is not an error signal (genuine fetch failures surface via the ? on z_get_treestate). Treating None as fatal is therefore incorrect.

This is a zallet-side issue; Zaino/Zebra are behaving correctly (there is no empty frontier to return).

Scope

The Sapling path has the identical latent bug (sapling.ok_or_else(|| "Missing Sapling tree state")?).

Proposed fix

Treat an absent tree state the same as an inactive pool — an empty frontier — for both pools. Branch (no PR yet): fix/empty-shielded-tree-state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-syncArea: Sync engineC-bugCategory: This is a bug

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions