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
- Run zallet against a regtest node + Zaino (NU5 active from height 1).
- Start wallet sync over the early regtest chain (Orchard tree empty).
- 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.
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
(Reproduced in the integration-tests suite: the
wallet.pyandwallet_orchard_init.pyregtest 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_stateassumes that if a pool is active, the indexer always returnsSome(tree_state):But an empty incremental-Merkle tree has no frontier, so the node reports
Nonefor it:zebra_rpc::z_get_treestatebuilds the field viatree.map(|t| t.to_rpc_bytes()), andzebra-state'sread::orchard_tree()returnsNonewhen no tree is stored at that height. Zaino faithfully relays thisNone. SoNone⟺ "empty tree" — it is not an error signal (genuine fetch failures surface via the?onz_get_treestate). TreatingNoneas 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.