Skip to content

sync: Treat an absent shielded tree state as an empty note commitment tree#455

Open
oxarbitrage wants to merge 1 commit into
mainfrom
fix/empty-shielded-tree-state
Open

sync: Treat an absent shielded tree state as an empty note commitment tree#455
oxarbitrage wants to merge 1 commit into
mainfrom
fix/empty-shielded-tree-state

Conversation

@oxarbitrage

Copy link
Copy Markdown
Contributor

Closes #454

Problem

fetch_chain_state errored with "Missing Sapling/Orchard tree state" — exiting the wallet sync task — whenever a shielded pool was active at the requested height but the indexer returned no tree state for it. This crashes sync on any chain where a pool is active while its note commitment tree is still empty, most reliably on regtest (NU5 active from height 1, Orchard tree empty).

Root cause

An absent tree state is not an error: an empty incremental-Merkle tree has no frontier to serialize, so the node reports None for it (zebra_rpc::z_get_treestate maps the tree 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 relays this None faithfully. Genuine fetch failures surface separately via the ? on z_get_treestate. So None ⟺ "empty tree", and treating it as fatal was incorrect — this is a zallet-side bug; Zaino/Zebra are behaving correctly.

Fix

Treat "pool active but tree state absent" the same as "pool not yet active": an empty frontier. Both the Sapling and Orchard paths had this bug; both are fixed symmetrically.

Testing

fetch_chain_state requires a live indexer connection and has no unit coverage. Validated end-to-end against the integration-tests suite — the wallet.py and wallet_orchard_init.py regtest scenarios sync to completion with this change (previously they hit "Missing Orchard tree state").

🤖 Generated with Claude Code

oxarbitrage added a commit to zcash/integration-tests that referenced this pull request Jun 5, 2026
…s in wallet.py

The getwalletstatus sync barrier indexed `wallet_tip` unconditionally, but the
RPC omits that field (Option + skip_serializing_if) until the wallet has a
committed tip — which happens transiently right after blocks are mined. The
barrier therefore raised `KeyError: 'wallet_tip'` ~1 run in 4, exactly during
the window it is meant to poll through. Treat an absent wallet_tip as "not
synced yet" and keep polling, in sync_blocks, sync_mempools, and rebuild_cache.

Also make wallet.py use the now wallet-aware self.sync_all() instead of an
immediate read and a fixed time.sleep(1), removing the remaining flaky
wallet-scan race (and the unused `import time`). This supersedes the bespoke
pollers in #106.

Validated 10/10 green locally against a zallet carrying zcash/wallet#367
(getwalletstatus) + zcash/wallet#455 (empty shielded tree fix).

Closes #105

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tree

`fetch_chain_state` errored with "Missing Sapling/Orchard tree state" whenever a
shielded pool was active at the requested height but the indexer returned no tree
state for it. This crashed the wallet sync task on any chain where a pool is
active while its note commitment tree is still empty -- most notably regtest,
where NU5 is active from height 1 but the Orchard tree has no commitments yet.

An absent tree state is not an error: an empty incremental Merkle tree has no
frontier to serialize, so the node (`zebra_rpc::z_get_treestate` maps the tree
through `tree.map(|t| t.to_rpc_bytes())`) and the indexer both report `None` for
an empty tree. Genuine fetch failures are surfaced separately via the `?` on the
`z_get_treestate` call. We therefore treat "pool active but tree state absent"
the same as "pool not yet active": an empty frontier.

Both the Sapling and Orchard paths had this bug; both are fixed symmetrically.

This path requires a live indexer connection and has no unit coverage; it was
validated end-to-end against the integration-tests suite (the `wallet.py` and
`wallet_orchard_init.py` regtest scenarios sync to completion with this change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oxarbitrage oxarbitrage force-pushed the fix/empty-shielded-tree-state branch from 3ab7bc7 to 00ef236 Compare June 8, 2026 20:22
@oxarbitrage

Copy link
Copy Markdown
Contributor Author

FYI: this is no longer blocking integration-tests. After zcash/integration-tests#104 aligned regtest NU5 activation to height 1, the wallet suite syncs to completion on main without this change, so CI no longer hits "Missing Orchard tree state."

That said, I think this is still worth fixing: an absent tree state (None) means the pool's note-commitment tree is empty, not an error, and the current code treats it as fatal. zcash/integration-tests#104 made the crash stop reproducing in our harness, but the underlying assumption (None ⟺ empty) holds on any chain, so it's worth landing as a small robustness fix (symmetric across Sapling and Orchard).

Rebased onto current main.

dannywillems pushed a commit to zcash/integration-tests that referenced this pull request Jun 10, 2026
…s in wallet.py

The getwalletstatus sync barrier indexed `wallet_tip` unconditionally, but the
RPC omits that field (Option + skip_serializing_if) until the wallet has a
committed tip — which happens transiently right after blocks are mined. The
barrier therefore raised `KeyError: 'wallet_tip'` ~1 run in 4, exactly during
the window it is meant to poll through. Treat an absent wallet_tip as "not
synced yet" and keep polling, in sync_blocks, sync_mempools, and rebuild_cache.

Also make wallet.py use the now wallet-aware self.sync_all() instead of an
immediate read and a fixed time.sleep(1), removing the remaining flaky
wallet-scan race (and the unused `import time`). This supersedes the bespoke
pollers in #106.

Validated 10/10 green locally against a zallet carrying zcash/wallet#367
(getwalletstatus) + zcash/wallet#455 (empty shielded tree fix).

Closes #105

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant