zcash_client_sqlite: Report Ironwood outputs as pool code 4#2534
Merged
Conversation
dannywillems
changed the base branch from
dw/ironwood-note-version-migrations
to
main
July 3, 2026 12:28
dannywillems
force-pushed
the
dw/ironwood-pool-code-views
branch
3 times, most recently
from
July 3, 2026 13:19
672b9a5 to
52163bf
Compare
nuttycom
reviewed
Jul 3, 2026
nuttycom
force-pushed
the
dw/ironwood-pool-code-views
branch
from
July 3, 2026 15:58
52163bf to
4801de3
Compare
nuttycom
previously approved these changes
Jul 3, 2026
nuttycom
enabled auto-merge
July 3, 2026 15:59
dannywillems
force-pushed
the
dw/ironwood-pool-code-views
branch
from
July 3, 2026 16:14
4801de3 to
7a8f3a4
Compare
nuttycom
force-pushed
the
dw/ironwood-pool-code-views
branch
from
July 3, 2026 16:15
7a8f3a4 to
4801de3
Compare
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code` (transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This is the SQLite `output_pool` code used to distinguish Ironwood outputs from Orchard outputs, which share the `orchard_received_notes` table. Adapted from valargroup/librustzcash@d94102c3ea (adam/qleak-pr44-orchard-dummy-ciphertexts). Co-Authored-By: Adam Tucker <adam@osmosis.team> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a migration that recreates the `v_received_outputs` and `v_received_output_spends` views with an additional branch unioning in the `ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`, `v_tx_outputs`, and the balances derived from them. Ironwood notes are stored in a table separate from `orchard_received_notes` because the two pools have distinct note commitment trees and an Orchard action and an Ironwood action in the same transaction may share an action index, so the views union the separate tables rather than splitting one table by note version. Update the canonical `v_received_outputs` / `v_received_output_spends` definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4) at the same `(transaction_id, action_index)` and asserts both surface distinctly in `v_received_outputs`. Adapted from the Ironwood work on valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts. Co-Authored-By: Adam Tucker <adam@osmosis.team> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Adam Tucker <adam@osmosis.team> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dannywillems
force-pushed
the
dw/ironwood-pool-code-views
branch
from
July 3, 2026 16:17
4801de3 to
8a8bc4b
Compare
nuttycom
approved these changes
Jul 3, 2026
nuttycom
added a commit
that referenced
this pull request
Jul 6, 2026
Relying upon the note plaintext version to discriminate between Orchard
and Ironwood notes works, but it complicates reasoning about the code
and makes it difficult to understand where orchard-the-protocol is being
used vs orchard-the-pool. By adding value pool metadata to wallet
orchard-protocol notes, we thoroughly disambiguate; it's entirely
possible that in the future another note plaintext version will be
introduced (for example, for the note plaintexts of memo bundles) that
does not change the pool, and this refactoring makes such updates
straightforward to deal with.
Also lifts note pool classification to Step (Note::pool, Step input-pool predicates and counters, and Proposal::input_count_in_pool), with tests for the proposal pool-input helpers.
zcash_client_sqlite: Drop a non-leaf from CURRENT_LEAF_MIGRATIONS
`ironwood_shardtree` was listed in `CURRENT_LEAF_MIGRATIONS`, but
`witness_stabilized_notes` depends on it, so it is not a leaf of the migration
dependency graph. Migrating to the current state still reached it transitively,
so this was harmless, but the constant is documented to hold exactly the graph
leaves. Remove the stale entry and add a test that recomputes the leaves from the
dependency graph and checks them against the constant, so this cannot regress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Include Ironwood notes in the account balance
Add an Ironwood balance component to `AccountBalance`, alongside the Sapling and
Orchard components, with an `ironwood_balance` accessor and a
`with_ironwood_balance_mut` method. The Ironwood balance is included in the
account's total, spendable, pending-confirmation, pending-spendability, and
uneconomic values.
`get_wallet_summary` now computes the Ironwood balance from
`ironwood_received_notes` via the existing pool-generic `with_pool_balances`, so
received Ironwood notes are reflected in the wallet balance. The end-to-end
storage test now asserts the balance equals the received note value.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Decrypt Ironwood outputs with the Ironwood domain
Ironwood notes carry version 3 note plaintexts, which are decrypted under the
Ironwood note-encryption domain (`orchard::note_encryption::IronwoodDomain`).
That domain is distinct from `OrchardDomain`, which accepts only version 2
plaintexts and would reject every Ironwood note. The scanner previously aliased
`IronwoodDomain` to `OrchardDomain`, so it would have silently failed to detect
any Ironwood output; this corrects the alias to the real Ironwood domain.
As a consequence an Ironwood batch is no longer type-identical to an Orchard
batch, so the compact- and full-block `BatchRunners` gain a distinct
`IronwoodTasks` type parameter alongside the Sapling and Orchard task types, and
a `ScanningKeyOps<IronwoodDomain, ..>` implementation lets the account's Orchard
viewing key trial-decrypt under the Ironwood domain. The Ironwood runners and
scan paths now construct their domains with `IronwoodDomain::for_compact_action`
/ `IronwoodDomain::for_action`.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds tests: scanning detects Ironwood notes across the compact and full scanning paths, and received Ironwood notes round-trip end-to-end through zcash_client_sqlite storage.
zcash_client_backend: Persist the Ironwood note commitment tree
Build and persist the Ironwood note commitment tree during `put_blocks`,
mirroring the Orchard handling but keeping Ironwood a distinct pool. The
Ironwood tree is Orchard-shaped, so it reuses the Orchard shard height and is
updated through `with_ironwood_tree_mut`; the Ironwood block metadata
(`ironwood_commitment_tree_size` and `ironwood_action_count`) is now written to
the `blocks` table via `put_block_meta`.
Checkpoint reconciliation across the note commitment trees is generalized from
two pools to three: each tree gains a checkpoint at every height that is
checkpointed in any other pool, computed as the union of the other pools'
checkpoint heights. When Ironwood has no checkpoints this reduces exactly to the
prior Sapling/Orchard reconciliation, so existing behavior is unchanged; the
Ironwood tree gains empty checkpoints at the scanned heights, as the Orchard
tree already does before its own activation.
With the tree persisted and the block Ironwood tree size recorded, received
Ironwood notes now have witness data and are spendable, and the note positions
computed by the scanner are backed by a built tree.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also property-tests cross-pool checkpoint reconciliation:
Extract the three-pool checkpoint-height reconciliation in `put_blocks` into a
pure `cross_pool_ensure_heights` helper and add proptests for it. The tests
assert the key invariant that after reconciliation every pool is checkpointed at
exactly the union of all pools' checkpoint heights (so the note commitment trees
stay aligned for cross-pool rewinds), and that with no Ironwood checkpoints the
Sapling/Orchard reconciliation is unchanged from the prior two-pool behavior.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add the Ironwood note commitment tree to ChainState
Add a separate Ironwood note commitment tree frontier to `ChainState`, parallel
to the Sapling and Orchard frontiers, with a `final_ironwood_tree` accessor and
wiring through `new`/`empty`. Parse it from the gRPC `TreeState.ironwood_tree`
field via a new `TreeState::ironwood_tree` method; an absent field yields an
empty tree, the correct Ironwood treestate at pool activation. This is the
prior chain state that the Ironwood commitment tree will be built from, added
ahead of the Ironwood tree write path. The Ironwood tree is Orchard-shaped but
is tracked as a distinct pool.
Also complete the Ironwood threading exposed under `--all-features`: the
`sync` decryptor's `BatchRunners::for_keys` callers now pass the Ironwood batch
threshold (reusing the Orchard threshold, as Ironwood outputs are
Orchard-shaped), and the block-metadata query row, widened with the Ironwood
tree size, is factored into a `BlockMetadataRow` type alias.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Persist received Ironwood notes and nullifiers
Store received Ironwood notes during scanning and track Ironwood nullifiers for
spend detection, mirroring the Orchard receive path but keeping Ironwood a
separate pool. `put_blocks` now feeds the scanner's Ironwood output stream to a
dedicated `put_received_ironwood_note` (writing to `ironwood_received_notes`),
detects earlier spends via `detect_ironwood_spend`, records the block's Ironwood
nullifiers with `track_block_ironwood_nullifiers`, and marks the wallet's own
spent Ironwood notes with `mark_ironwood_note_spent`. `Nullifiers::unspent` and
`WalletRead::get_ironwood_nullifiers` complete the spend-detection loop.
Storage no longer routes by note plaintext version: `put_received_note` takes
the target table prefix from the caller, so the scanner routes its separate
Orchard and Ironwood output streams to their respective tables, while the
decrypted-transaction path selects the table from the note version as before.
The shared spent-note marking is factored into a prefix-parameterized helper.
This does not build the Ironwood commitment tree (that needs an Ironwood
frontier in `ChainState`, added separately), so no non-zero Ironwood tree size
is written to `blocks` and received Ironwood notes are not yet spendable. The
send-path Ironwood todos are left for a later increment.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Track Ironwood outputs in the block scanner
Extend the compact- and full-block scanners to trial-decrypt Ironwood outputs
and track the Ironwood note commitment tree, mirroring the existing Orchard
handling but keeping Ironwood as a distinct pool. `BatchRunners` feed the
Ironwood runner from `CompactTx.ironwood_actions` / the transaction's Ironwood
bundle, `PositionTracker` tracks the Ironwood tree position and final size
(activating at NU6.3 and reconciling against `ChainMetadata`'s Ironwood tree
size), and `scan_block_with_runners` collects Ironwood spends, outputs, note
commitments, and nullifiers into a separate `ScannedBundles`.
`ScannedBlock`, `ScannedBlockCommitments`, `BlockMetadata`, `WalletTx`, and
`Nullifiers` gain parallel Ironwood fields and accessors, and the sqlite
`block_metadata`/`block_max_scanned` queries read the persisted Ironwood tree
size. Ironwood note commitments are Orchard-shaped, so the Orchard commitment,
nullifier, and wallet-output types are reused through Ironwood-named aliases;
the two pools remain separate throughout.
This computes the Ironwood scan results but does not yet persist them: the
wallet does not query Ironwood nullifiers (so Ironwood spend detection is inert)
and `put_blocks` does not write received Ironwood notes or update the Ironwood
commitment tree. Those are added with the Ironwood write path.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add Ironwood scanning keys
Add a separate `ironwood` key set to `ScanningKeys`, parallel to the Sapling
and Orchard key sets, with an `ironwood()` accessor and wiring through `new`,
`empty`, and `from_account_ufvks`. Ironwood outputs are Orchard-shaped and are
trial-decrypted under the Orchard note-encryption domain using the account's
Orchard viewing keys, so the Ironwood keys are populated from the Orchard full
viewing key; the `IronwoodDomain`/`IronwoodNullifier` aliases name that reuse of
the underlying Orchard primitives.
Ironwood is kept strictly separate from Orchard as its own pool: the shared
primitives are an implementation detail expressed through aliases, not a design
in which Ironwood belongs to an "Orchard family".
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds the Ironwood compact batch runner:
Add a separate `ironwood` batch runner to the compact-block `BatchRunners`,
parallel to the Sapling and Orchard runners, wired through `for_keys` and
`flush`. Because an Ironwood batch is type-identical to an Orchard batch
(`IronwoodDomain` aliases `OrchardDomain` and the action type is shared), the
Orchard task type already satisfies the runner's bound, so no separate task
type is introduced; the `TaggedIronwoodBatchRunner` alias only names the runner
as belonging to the Ironwood pool.
The runner is constructed from the Ironwood scanning keys and flushed, but is
not yet fed block outputs or collected, so scanning behavior is unchanged. The
decryption of `CompactTx.ironwood_actions` is added in a later change together
with the commitment-tree position tracking that consumes its results.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2545 from zcash/fix/broken_patch_directive
Fix duplicate patch dependency in Cargo.toml
Update to orchard 0.15.0-pre.2
Merge pull request #2507 from nuttycom/feature/shardtree_explicit_retention
Explicit shardtree checkpoint retention (durable anchors)
Merge pull request #2535 from zcash/adam/pczt-signer-lean-parse
pczt: preverified signing parse for the low-level Signer
zcash_client_backend: add WalletCommitmentTrees::remove_retained_checkpoints_below
Adds a default-implemented `WalletCommitmentTrees` method that releases
retained "anchor" checkpoints with height below a given threshold from both
the Sapling and (when enabled) Orchard note commitment trees, so that anchors
that have aged out of the desired retention window can be pruned normally. The
default implementation is built from the `shardtree` retained-checkpoint
primitives, so all backends get it without bespoke code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: retain anchor checkpoints during scanning
During `put_blocks`, checkpoints established at heights on the
`ANCHOR_RETENTION_INTERVAL` (every 288 blocks, ~4 per day) at or above a
configured activation height are retained as durable anchors via
`ShardTree::ensure_retained`, exempting them from automatic pruning of excess
checkpoints so that their roots and anchored witnesses remain computable.
The retention floor is threaded into `put_blocks`/`update_tree` as an
`Option<BlockHeight>`; the SQLite backend computes it as the NU6.3 (Ironwood)
activation height. Anchor retention is therefore dormant until NU6.3 has an
assigned activation height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: implement explicit shardtree checkpoint retention
Implements the `add_retained_checkpoint` / `remove_retained_checkpoint` /
`retained_checkpoints` `ShardStore` methods for `SqliteShardStore`, backed by
new per-pool `{sapling,orchard}_tree_retained_checkpoints` tables created by a
new migration. Retained checkpoints are exempt from automatic pruning of
excess checkpoints, so their roots and the witnesses anchored to them remain
computable after they age more than `max_checkpoints` behind the chain tip.
Also adds a `remove_retained_checkpoints_below` helper for releasing anchors
older than a given height (wired into the wallet API in a subsequent commit).
Temporarily patches `shardtree`/`incrementalmerkletree` to
zcash/incrementalmerkletree `main`, which carries the retention API merged in
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pczt: reword the inline(never) action-parse comment
Apply @ValarDragon's review suggestion, stating the rationale for the
`#[inline(never)]` per-action parse helper more concisely: it avoids
inlining into a `.map(..).collect()` loop that would compile to a stack
frame tens of KB deep and overflow embedded signer stacks on
high-action-count transactions. Comment-only; no behavior change.
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
supply-chain: point the orchard vet exemption at the pinned rev
The orchard pin bump in e862ecef02 moved the [patch.crates-io] git rev
from 20c10904 to ab7c5fae but left the cargo-vet exemption on the old
rev, so `cargo vet --locked` failed with
orchard:0.15.0-pre.1@git:ab7c5fae... missing ["safe-to-deploy"]
Update the exemption rev to match Cargo.lock. `cargo vet --locked` now
succeeds. The exemption stays temporary and is dropped together with the
[patch] once the orchard change releases.
Merge pull request #2386 from valargroup/dojha/zcash-proofs-readme-sprout
zcash_proofs: scope README to Sprout
Apply suggestion from @ValarDragon
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
pczt: drop Bundle/Action preverified parse forks; bump orchard pin
The upstream orchard signer-only-parse work removed the redundant Bundle
and Action parse_preverified_for_signing wrappers (they were pure
pass-throughs), keeping only the spend-level entry point. Collapse the
matching Action/Bundle `if preverified { .. } else { .. }` branches here
to unconditional Action::parse / Bundle::parse; the `preverified` flag
now selects only at the spend level, the sole place it changes behavior.
Bump the orchard patch to the current head of that branch so the build
tracks the change; the previous pin still referenced the pre-cleanup
orchard that carried the removed wrappers.
Merge pull request #2534 from zcash/dw/ironwood-pool-code-views
zcash_client_sqlite: Report Ironwood outputs as pool code 4
CHANGELOG: Add Ironwood received-output views entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received notes to the output views
Add a migration that recreates the `v_received_outputs` and
`v_received_output_spends` views with an additional branch unioning in the
`ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged
with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`,
`v_tx_outputs`, and the balances derived from them.
Ironwood notes are stored in a table separate from `orchard_received_notes`
because the two pools have distinct note commitment trees and an Orchard action
and an Ironwood action in the same transaction may share an action index, so the
views union the separate tables rather than splitting one table by note version.
Update the canonical `v_received_outputs` / `v_received_output_spends`
definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a
reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The
new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4)
at the same `(transaction_id, action_index)` and asserts both surface distinctly
in `v_received_outputs`.
Adapted from the Ironwood work on
valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Assign the Ironwood shielded pool code
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code`
(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This
is the SQLite `output_pool` code used to distinguish Ironwood outputs from
Orchard outputs, which share the `orchard_received_notes` table.
Adapted from valargroup/librustzcash@d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2533 from zcash/dw/ironwood-scanning
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
zcash_client_sqlite: Fix missing CURRENT_LEAF_MIGRATIONS entries.
Fix formatting and feature-flagging errors.
CHANGELOG: Add Ironwood scan-range extension entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
When Ironwood notes are detected during scanning, extend the suggested scan
ranges to include the blocks needed to complete the Ironwood note commitment
tree subtrees containing those notes, mirroring the existing Sapling and Orchard
handling.
Ironwood notes are stored alongside Orchard notes in `orchard_received_notes`,
but their commitment tree is tracked separately (in the `ironwood_*` tables), so
`extend_range` now takes the tree table prefix directly instead of deriving it
from a `ShieldedPool`. This avoids conflating the note-table pool with the
commitment-tree tables, and lets the Sapling, Orchard, and Ironwood callers pass
their respective prefixes. The Ironwood extension is gated on NU6.3 activation.
Adapted from valargroup/librustzcash@f42001edde and @0af74dd549
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2538 from zcash/kn/ironwood-note-version-migrations
zcash_client_sqlite: Add ironwood_received_notes
CHANGELOG: Add Ironwood received-note storage entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received-note storage
Ironwood notes (ZIP 2005, NU6.3) are Orchard-protocol notes obtained
from version 3 note plaintexts, carried by the Ironwood bundle of a
transaction and committed to the Ironwood note commitment tree. Store
them separately from `orchard_received_notes`: the two pools have
distinct note commitment trees, and an Orchard action and an Ironwood
action in the same transaction may share an action index.
A new migration adds the `ironwood_received_notes` and
`ironwood_received_note_spends` tables, mirroring the corresponding
Orchard tables. `put_received_note` now records a received note in the
table for the pool implied by the note's plaintext version: version 2
note plaintexts can be obtained only under the Orchard note encryption
domain, and version 3 note plaintexts only under the Ironwood domain.
Reading Ironwood notes back for spendability, balance, and view
reporting is not yet implemented; it will accompany Ironwood scanning
support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG: Add Orchard received-note version entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add note version to Orchard received notes
The Orchard protocol revision introduced by NU6.3 (ZIP 2005) versions
Orchard note plaintexts: pre-NU6.3 note plaintexts are version 2, and
Ironwood note plaintexts are version 3. The version determines how the
note commitment trapdoor is derived from the note's rseed, so the
version observed at decryption time must be persisted in order to
reconstruct received notes.
Add a `note_version` column to `orchard_received_notes`, populated from
the note plaintext version reported by the decrypted note itself.
Existing rows are backfilled as version 2, the only version accepted by
the Orchard note encryption domain. Note reconstruction now uses the
stored version instead of assuming version 2. Ironwood notes are not
stored in this table (they belong to a distinct pool with its own note
commitment tree), so the `(transaction_id, action_index)` uniqueness
constraint is unchanged.
Adapted from valargroup/librustzcash@0555be5234 and @d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge pull request #2422 from zcash/feat/propose-transaction-can-spend-transparent-utxos
feat: propose transaction can spend transparent utxos
Merge pull request #2531 from zcash/dw/ironwood-wallet-storage-scanning
zcash_client_sqlite: Add Ironwood note commitment tree storage
Merge branch 'main' into feat/propose-transaction-can-spend-transparent-utxos
zcash_client_backend, zcash_client_sqlite: enforce TransparentSpendPolicy::FromAddresses
select_spendable_transparent_outputs gathered transparent inputs for the
entire account regardless of TransparentSpendPolicy, so FromAddresses
behaved identically to AnyAccountTaddr: a proposal could spend from (and
link) addresses the caller explicitly excluded. Post-gather filtering is
not sufficient, since excluded UTXOs would consume the value bound and
under-fill the result; the address restriction must be applied within
the gather itself.
Add an address_allow_list parameter to select_spendable_transparent_outputs,
applied inside the SQL query (via the same always-bound-flag idiom already
used for coinbase_filter) so ineligible outputs never consume the value
bound. GreedyInputSelector wires this from TransparentSpendPolicy at both
gather sites: the initial gather and the InsufficientFunds re-gather
fallback (which had the same bug independently).
Also refactors NonEmptyBTreeSet to wrap a single BTreeSet with proper
invariant-preserving constructors (singleton, from_nonempty, from_set)
and accessors (as_set, iter) instead of exposing raw head/tail fields,
and adds persistent transparent_dust tracking across the propose_transaction
retry loop so a re-gather triggered by InsufficientFunds does not
reintroduce transparent inputs already pruned as dust by a prior
DustInputs iteration.
Strengthens propose_t2t_from_addresses to actually catch the bug: the
unnamed address's UTXO now holds more value than the named address's
and is inserted first, so an unenforced policy would select it (since
it alone covers the payment) instead of the named address, making the
test fail without the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pczt: preverified signing parse for the low-level Signer
Parse the low-level Signer's bundle with orchard's
`parse_preverified_for_signing`, which skips deriving each spend's
`FullViewingKey` from its wire `fvk` bytes. The spend authorization
signature does not depend on `fvk`, so signatures are byte-identical to
the full parse.
`sign_orchard_with` and `sign_ironwood_with` snapshot each spend's
`(rk, fvk)` before parsing and restore the `fvk` after serialization so
signing leaves the wire bytes unchanged. The restore returns
`OrchardParseError::SigningClosureModifiedActions` if the closure changes
the action count or reorders actions, detected by the per-position `rk`,
and makes no changes when it rejects.
Refactor the Orchard action parse from `.map(...).collect()` into a
bounded `#[inline(never)]` loop so a constrained signer does not overflow
its task stack. Behaviorally identical; the full `parse` used by the
Verifier, Prover, and Updater is unchanged.
Temporarily patches orchard to the branch adding
`parse_preverified_for_signing`, with a matching cargo-vet exemption;
both are dropped for a version bump once that orchard change releases.
Merge pull request #2517 from nuttycom/fix/upgrade-imported-transparent-address
zcash_client_sqlite: Upgrade imported transparent addresses to their derived form on gap-limit derivation
zcash_client_backend, zcash_client_sqlite: document the transparent input-count cap in CHANGELOGs
Update the still-unreleased select_spendable_transparent_outputs and
with_shielding_block_space_percent entries to describe the max_inputs
parameter and its dual use for shielding and general transfers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: cap transparent inputs gathered for general transfers
InputSource::select_spendable_transparent_outputs now takes a max_inputs
parameter bounding the number of transparent outputs the gather may
select, independent of target_value. Without this, a wallet holding a
very large number of small (e.g. dust) UTXOs could require an unbounded
number of inputs to satisfy even a modest request, producing an
oversized transaction.
GreedyInputSelector::propose_transaction wires this to the existing
shielding_block_space_percent config (via shielding_max_inputs), reusing
the same block-space bound already used to cap shielding transactions
rather than introducing a second configuration knob. When the cap is
reached before the requested value, the caller's input-selection loop
surfaces this the same as any other value shortfall: an InsufficientFunds
error.
The SQLite implementation enforces the cap inside the gather loop itself
(breaking out once max_inputs outputs have been accumulated), rather than
gathering an unbounded set and truncating afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: fix invalid migration UUID and add missing verify_schema entry
add_transparent_value_index's MIGRATION_ID was a hand-typed UUID whose
version nibble was 6, not 4, making it invalid per the CI 'uuid' job's
random/v4 check (.github/workflows/ci.yml). Replace it with a properly
generated v4 UUID.
Separately, the migration's new idx_transparent_received_outputs_value_zat
index was never added to wallet::init::tests::verify_schema's expected
index list, nor given a db.rs constant, causing that test to fail with a
positional mismatch against every index sorted after it. Add the missing
db::INDEX_TRANSPARENT_RECEIVED_OUTPUTS_VALUE_ZAT constant and list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Address review comments on duplicate-receiver resolution
- Decode key scopes via KeyScope::decode and match on the enum rather than raw
integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
cross-account duplicate whose ephemeral record reproduces the receiver is resolved
in its favor rather than aborting. Only Foreign records remain unverifiable, having
no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: depend on fix_transparent_received_outputs, not utxos_to_txos, for the value_zat index migration
fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.
Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.
Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: adapt to upstream API changes from rebase onto main
Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:
- Update the remaining ShieldedProtocol::Sapling references (added by
this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
pass 0 from the transparent-only gather in
select_spendable_transparent_outputs, matching the existing 0 passed
for sapling_input_count/sapling_output_count/orchard_action_count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Upgrade receivers imported under a different account on derivation
The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Resolve cross-account duplicate receivers by derivation
A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: CHANGELOG for imported→derived transparent address upgrade
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Preserve earliest exposed_at_height when merging duplicate receivers
The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Upgrade imported transparent receiver in place on derivation
When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: fix changelog/doc self-review findings
- Move two changelog entries that described this PR's new work out of
already-released, dated version sections (zcash_client_backend's
[0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
Unreleased/PLANNED sections. Released changelog sections must remain
immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
discarded intermediate designs (an unbounded gather, an
Option<Zatoshis> static-fee-headroom parameter) as though they were
prior shipped behavior. Since select_spendable_transparent_outputs is
entirely new and unreleased, changelog/doc text should describe only
the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
AGENTS.md conventions file, meaningless to external readers of the
public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
non-empty BTreeSet") and document its previously-undocumented public
fields; drop a private implementation note about possibly moving it to
another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
which controls the same privacy behavior the method's doc already
describes but was never mentioned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: fix spurious InsufficientFunds when transparent re-gather succeeds without new shielded notes
GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.
Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.
Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: make transparent gather fee-aware
InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.
Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).
Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: add value-bounded transparent input gather
Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.
The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.
GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: extract shared spendable-transparent-outputs query builder
Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.
This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).
Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: Remove gather_account_transparent_inputs in favor of batched query
`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce verbosity in propose_transaction docs and related types
better documentation and testing for propose_shielding transparent UTXO spending functionality
added variant CoinbaseFilter::NonCoinbaseOnly to remove coinbase from propose_transaction
rename TransparentOutputFilter and better names for TransparentSpendPolicy methods
add tests and changelog for transparent spending in propose_transaction
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add transparent spending to propose_transaction
Co-Authored-By: fdecroix <fdecroix@eryx.co>
CHANGELOG: Add Ironwood note commitment tree storage entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2508 from zcash/adam/ironwood-wallet-fees
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Adapt Ironwood fee wiring to the new tx-builder bundle-version API
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.
Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.
Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
Merge pull request #2498 from zcash/adam/ironwood-tx-builder-completion
zcash_client_backend: Wire Ironwood anchors into wallet transaction building
wallet: revert unnecessary change
CHANGELOG: mv with_ironwood_tree_mut to appropriate changed section
Assert exact Ironwood fee values; document the routing flag
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
Route Orchard-pool change into the Ironwood bundle to match the builder
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.
Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.
Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.
Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Note the compute_balance Ironwood view in the changelog
Charge the Ironwood bundle at proposal time
Populate the Ironwood fee view introduced previously: when the builder
will route Orchard-pool outputs into a separate Ironwood bundle, mirror
that split during input selection so the proposal's per-bundle action
counts (and therefore the fee) match the transaction that gets built.
The routing decision reuses the builder's own `orchard_outputs_to_ironwood`
predicate (threading the same `proposed_version`), so the proposal and
build paths cannot drift. The Orchard bundle keeps the Orchard spends; its
outputs move to the Ironwood bundle when routing is active. Ironwood has
no spends yet, as the wallet does not select Ironwood notes until note
detection lands.
Adds a zip317 test asserting that a populated Ironwood view increases the
required fee (the Ironwood bundle's actions are counted independently of
Orchard).
Thread a separate Ironwood bundle view through the fee/change path
V6 transactions carry separate Orchard and Ironwood bundles, each padded
to its own action floor, and `FeeRule::fee_required` already takes a
separate `ironwood_action_count`. Populate that count from a dedicated
Ironwood `BundleView` rather than the previous hardcoded zero.
`ChangeStrategy::compute_balance` and the `common.rs` helpers
(`calculate_net_flows`, `single_pool_output_balance`) gain an `ironwood`
view parameter alongside `orchard` (gated on `feature = "orchard"`,
mirroring it), `NetFlows` gains `ironwood_in`/`ironwood_out`, and the
Ironwood action count is computed via
`transactional_action_count(ironwood.bundle_version(), ...)`.
All call sites currently pass an empty Ironwood view, so this is a no-op:
the wallet does not yet route inputs/outputs to the Ironwood pool at
proposal time. A follow-up populates the view from the same routing
decision the builder uses, so the separate Ironwood bundle is priced.
Verified: compiles under default features and the nu7 cfg; the 11
zip317/fixed change-strategy tests pass unchanged.
Address review findings: routing enum, dedup, docs, expect
Model the per-step Orchard/Ironwood routing as an `OrchardBuildMode` enum
computed once, replacing the three booleans and four-arm if-else. The
spend-anchor, Ironwood inputs, and output-routing decisions now derive
from a single value so they cannot drift apart, and the `LegacyV5 +
Ironwood spends` rejection is expressed at classification time.
Replace the `ironwood_spends_requested implies shielded inputs` `expect`
with a `let-else` that returns `ProposalNotSupported` rather than
panicking on the (unreachable) `None`.
De-duplicate the cfg-gated Orchard change-output block so the Orchard
path is written once, gated by a `change_to_ironwood` flag that is
`false` in builds without Ironwood.
Document `stored_ironwood_output_index`: V6 stores Orchard and Ironwood
outputs in one index space with Orchard actions first.
Wire Ironwood anchors into wallet transaction building
Merge pull request #2522 from zcash/chore/shielded_protocol_rename
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool` and add `ShieldedPool::Ironwood`
Merge pull request #2520 from zcash/feat/pczt_ironwood_completion
pczt: Complete branch-aware v6/Ironwood support
Merge pull request #2521 from nuttycom/fix/put-transparent-output-preserves-mined-height
zcash_client_sqlite: Don't clear a recorded mined_height when storing a transparent output with unknown height
pczt: Generalize bundle version derivation over the value pool
`Creator::orchard_bundle_version()` becomes `bundle_version(pool)`, and
the crate-level revision-to-version mapping takes the `ValuePool` as an
argument (mirroring `zcash_primitives`'s `bundle_version_for_branch`),
so both the Orchard and Ironwood bundle versions are derived from the
consensus branch ID and pool alone. `with_ironwood_flags` now expresses
its version-support check as pool availability rather than a
transaction-version comparison.
Also restructure the transaction-version validation in
`extract_tx_data` as a single exhaustive match, and apply two
comment-wording suggestions in the v1 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Derive the Creator's Orchard bundle version instead of storing it
The Creator now stores the validated `BranchId` (rather than the raw
consensus branch ID) and derives the Orchard-pool bundle version from it
on demand, removing a stored field that was a pure function of state the
Creator already held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add high-level Ironwood methods to the Signer, Verifier, and Updater
Mirrors of the corresponding Orchard-pool methods:
- `Signer::{sign_ironwood, apply_ironwood_signature}` (with new
`Error::{IronwoodSign, IronwoodVerify}` variants), so
hardware-wallet-style signers no longer need the low-level Signer to
authorize Ironwood actions.
- `Verifier::with_ironwood`
- `Updater::update_ironwood_with`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add v6/Ironwood support to the Creator and validate at extraction
- `Creator::new` now derives the transaction version and version group
ID from the consensus branch ID: the v6 format from NU6.3 onward, and
the v5 format for earlier upgrades. Previously the Creator could only
produce v5 PCZTs.
- New `Creator::with_ironwood_anchor` and `Creator::with_ironwood_flags`
methods configure the Ironwood bundle independently of the Orchard
bundle; both reject configuration of an Ironwood bundle for a
transaction version that does not carry one
(`Error::IronwoodNotSupported`).
- Transaction extraction now rejects v6 PCZTs whose consensus branch ID
predates NU6.3, and non-v6 PCZTs carrying non-canonical Ironwood
bundle data (`ExtractError::IronwoodNotSupported`), instead of
silently dropping the Ironwood data or extracting a consensus-invalid
transaction.
- The v1 serialization format refuses to encode v6 PCZTs, which a v1
parser could decode but never extract a transaction from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Canonicalize the empty states of the PCZT bundles
The Creator, `Creator::build_from_parts`, the v1 and v2 serialization
formats, and the IO Finalizer previously produced four different
representations of an "empty" Ironwood bundle (and inconsistent empty
Sapling/Orchard bundles), which broke `Bundle::merge` for workflows that
mix serialization-round-tripped and in-memory copies of the same PCZT.
Each bundle type now has a single canonical empty representation,
defined as a constant in its module and used consistently everywhere:
- The Creator's Ironwood placeholder no longer mirrors the Orchard
bundle's flags and anchor; it is exactly the canonical empty bundle
(whose flags are the Ironwood defaults: spends, outputs, and
cross-address transfers enabled).
- `Creator::build_from_parts` uses the canonical empty bundles (with
all-zeroes anchors, rather than the empty-tree root) for absent
bundles, and now maps `parts.ironwood` through instead of discarding
it, so the builder-to-PCZT path no longer loses Ironwood spends and
outputs.
- Empty bundle value sums are initialized as non-negative zero,
matching the form produced by re-serializing a parsed bundle.
- The IO Finalizer no longer sets `bsk` on an empty Orchard-protocol
bundle. (The Sapling bundle is still always finalized, because the
Sapling Transaction Extractor requires `bsk` even for an empty
bundle.)
- The v2 format's omission decision is now exact equality with the
slot's canonical empty bundle, so a bundle with non-default flags or
note version round-trips losslessly instead of being restored with
default values; the v1 format refuses to encode a PCZT whose Ironwood
bundle is not canonically empty, rather than silently dropping its
non-default fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Don't clear mined_height when storing an output with unknown height
put_transparent_output's transactions upsert overwrote mined_height unconditionally,
so re-storing a transparent output whose mined height is unknown to the caller (e.g.
one re-observed via the mempool, or fetched from a backend that could not locate the
transaction on the best chain) clobbered a previously-recorded mined height with NULL,
marking a mined transaction as unmined. A NULL height carries no evidence that the
recorded height is wrong; preserve it, matching the adjacent handling of the block
column. Genuine un-mining remains the responsibility of truncate_to_height.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_protocol: Add `ShieldedPool::Ironwood`
This introducese `todo!(...)` stanzas in each location in
`zcash_client_backend` and `zcash_client_sqlite` where Ironwood
support infrastructure has not yet been implemented.
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool`
It is no longer appropriate to refer to the Orchard "shielded protocol"
as there are three different variants of the Orchard shielded protocol.
`zcash_protocol::ShieldedProtocol` has historically been used as a pool
identifier; the rename here reifies this change.
Merge pull request #2516 from zcash/fix/pr2509_review_followup
zcash_primitives: Address post-merge review comments from PR #2509
zcash_client_backend: Derive the PCZT test proving key from the consensus branch
The `pczt_single_step` pool test hardcoded the post-NU6.2 fixed-circuit
proving key, which was consistent with the PCZT roles' previous pinning
of the legacy Orchard bundle version but mismatches the branch-derived
parsing, proving, and verification those roles now perform: the shared
test network fixture's most recent upgrade is NU5, under which the
Orchard pool is governed by the historical pre-NU6.2 circuit, so proof
verification failed at extraction. Derive the proving key from the
PCZT's own consensus branch ID instead. (Modernizing the test network
fixture so that wallet tests exercise current consensus is part of the
Ironwood test coverage work tracked in #2518.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add OrchardProtocolRevision to zcash_protocol; make all PCZT roles branch-aware
The consensus knowledge of which Orchard protocol revision each network
upgrade deploys now lives in a single place:
`zcash_protocol::consensus::BranchId::orchard_protocol_revision`, which
returns the new `OrchardProtocolRevision` enum (`None` for branches that
predate NU5). `zcash_primitives`'s `bundle_version_for_branch` and the
`pczt` crate both derive `orchard::bundle::BundleVersion`s from it via
trivial conversions, replacing the duplicated branch-to-version matches.
(`pczt` cannot reuse the `zcash_primitives` helper directly because its
`zcash_primitives` dependency is optional and must not be required by
the lightweight role builds.)
Building on this, the remaining PCZT roles (Updater, Verifier, Prover,
and the low-level Signer) now parse the Orchard-pool bundle using the
bundle version implied by the PCZT's consensus branch ID rather than
pinning the legacy pre-NU6.3 version, completing the branch-aware
parsing work begun with `extract_tx_data`; in particular, the Prover now
creates proofs for the correct circuit for post-NU6.3 Orchard-pool
bundles. Roles report new `UnsupportedConsensusBranchId` errors when the
branch ID is unrecognized or predates NU5, and the low-level Signer's
Orchard-protocol signing methods now bound their error parameter by
`From<OrchardParseError>`. `Bundle::into_orchard_parsed`, which pinned
the legacy version, is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address remaining review comments from PR #2509
- pczt: fix extraction and verification of post-NU6.3 Orchard-pool
bundles. `extract_tx_data` now parses the Orchard-pool bundle using
the bundle version implied by the PCZT's consensus branch ID rather
than pinning the legacy pre-NU6.3 version, and the Transaction
Extractor's bundle verification derives the circuit version from the
bundle itself rather than hardcoding the NU6.2 fixed circuit (which
could not verify post-NU6.3 Orchard-pool bundles). The
`verify_bundle`/`verify_ironwood_bundle` duplication collapses into a
single function. Other PCZT roles still parse the Orchard-pool bundle
with the legacy version pin; completing those is tracked as follow-up
work.
- zcash_primitives: note the ZIP 229 specification of the v6 Sapling
digest variants in the `txid.rs` documentation, and correct the
`BlockTxCommitmentDigester::HeaderDigest` doc comment.
- zcash_client_backend: add a ZIP 317 change-calculation test covering
the post-NU6.3 Orchard action-count policy (one action per spend and
per output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive Orchard-protocol bundle versions from consensus context
Review followups:
- Add `bundle_version_for_branch`, returning the `BundleVersion` in
effect for a given Orchard-protocol value pool under a given consensus
branch, or `None` when the pool is not supported under that branch
(the Orchard pool prior to NU5; the Ironwood pool prior to NU6.3).
- `read_v5_bundle` now takes the consensus branch ID, and
`read_v6_bundle` the consensus branch ID and the `orchard::ValuePool`
identifying the bundle slot to read, in place of caller-supplied
`orchard::bundle::BundleVersion` arguments. Callers can no longer
select a wrong-slot bundle version, and a non-empty bundle belonging
to a pool that is unsupported under the transaction's consensus branch
ID is rejected as invalid data.
- In `Builder::new`, the `None` result subsumes the explicit
NU5-activation check that previously gated construction of the Orchard
builder.
- Remove `zcash_client_backend::ANY_ORCHARD_BUNDLE_VERSION`. The fee and
change calculation call sites that used it all have access to the
proposal's target height, and now derive the Orchard bundle version
(and hence the action-count policy) from it. As a consequence,
proposals targeting heights at or beyond NU6.3 activation now count
one action per Orchard spend or output, matching the post-NU6.3
transaction builder. `EmptyBundleView` retains a fixed version, which
is sound because an empty bundle produces zero actions under every
version's action-count policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_primitives: Address post-merge review comments from PR #2509
- `read_v5_bundle` now takes the consensus branch ID, deriving the
Orchard bundle version via the new public helper
`orchard_bundle_version_for_branch` (an exhaustive match, replacing
both the wildcard-arm private helper in the builder and the inline
mapping in `Transaction::read_v5`); this also removes an incomplete
sentence from its documentation.
- Make `TxVersion::has_ironwood` an exhaustive match.
- Simplify the `arb_txdata` proptest strategies:
`arb_ironwood_bundle_for_version` already yields `None` for versions
without Ironwood support.
- Factor out `Builder::orchard_in_use` to mirror `ironwood_in_use`.
- Correct the coinbase Orchard-builder flag comment, strengthen the
`with_expiry_height` warning, and fix stale `txid.rs` documentation.
- Give NU6.3 and NU7 distinct activation heights in the nu7 builder
test network.
- Add a test checking that Orchard note commitments and nullifiers are
domain-separated by the note plaintext version (lead byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address issues found in review of the NU6.3 testnet support branch
- `zcash_protocol`: under `zcash_unstable = "nu7"`, `BranchId::Nu6_3` now
reports the NU7 activation height as the upper bound of its height
range. The cfg-gated upper-bound pattern was dropped from the `Nu6_2`
arm when the `Nu6_3` arm was introduced, causing the Nu6_3 and Nu7
epochs to overlap in nu7 builds.
- `zcash_primitives`: the transaction builder's version-compatibility
check now accounts for Orchard change outputs, matching the Ironwood
check. Previously a change-only Orchard bundle passed the compatibility
check for `TxVersion::V4`, resulting in a reachable panic in `build()`.
- `zcash_primitives`: document previously-omitted API changes in the
0.29.0-pre.0 CHANGELOG entry (`map_bundles`/`try_map_bundles` closure
semantics, `TransactionDigest::combine` `ironwood_digest` argument,
`BlockTxCommitmentDigester::HeaderDigest` type change,
`BuildConfig::orchard_builder_config` removal,
`TxVersion::has_ironwood`).
- `zcash_transparent`: restore Keep-a-Changelog section ordering in the
0.9.0-pre.0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply minor review suggestions from PR #2509
- Fix the `expected_nu7` regtest activation heights to differ from the
NU6.3 height, assert NU6.3 activation at its own height, and check NU7
non-activation against the NU6.3 height in the `zcash_protocol`
`local_consensus` tests.
- Remove the stale placeholder-branch-ID paragraph from the
`BranchId::Nu6_3` docs.
- Simplify the `BranchId::Nu6_2` height-bounds arm to …
nuttycom
added a commit
that referenced
this pull request
Jul 7, 2026
Relying upon the note plaintext version to discriminate between Orchard
and Ironwood notes works, but it complicates reasoning about the code
and makes it difficult to understand where orchard-the-protocol is being
used vs orchard-the-pool. By adding value pool metadata to wallet
orchard-protocol notes, we thoroughly disambiguate; it's entirely
possible that in the future another note plaintext version will be
introduced (for example, for the note plaintexts of memo bundles) that
does not change the pool, and this refactoring makes such updates
straightforward to deal with.
Also lifts note pool classification to Step (Note::pool, Step input-pool predicates and counters, and Proposal::input_count_in_pool), with tests for the proposal pool-input helpers.
zcash_client_sqlite: Drop a non-leaf from CURRENT_LEAF_MIGRATIONS
`ironwood_shardtree` was listed in `CURRENT_LEAF_MIGRATIONS`, but
`witness_stabilized_notes` depends on it, so it is not a leaf of the migration
dependency graph. Migrating to the current state still reached it transitively,
so this was harmless, but the constant is documented to hold exactly the graph
leaves. Remove the stale entry and add a test that recomputes the leaves from the
dependency graph and checks them against the constant, so this cannot regress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Include Ironwood notes in the account balance
Add an Ironwood balance component to `AccountBalance`, alongside the Sapling and
Orchard components, with an `ironwood_balance` accessor and a
`with_ironwood_balance_mut` method. The Ironwood balance is included in the
account's total, spendable, pending-confirmation, pending-spendability, and
uneconomic values.
`get_wallet_summary` now computes the Ironwood balance from
`ironwood_received_notes` via the existing pool-generic `with_pool_balances`, so
received Ironwood notes are reflected in the wallet balance. The end-to-end
storage test now asserts the balance equals the received note value.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Decrypt Ironwood outputs with the Ironwood domain
Ironwood notes carry version 3 note plaintexts, which are decrypted under the
Ironwood note-encryption domain (`orchard::note_encryption::IronwoodDomain`).
That domain is distinct from `OrchardDomain`, which accepts only version 2
plaintexts and would reject every Ironwood note. The scanner previously aliased
`IronwoodDomain` to `OrchardDomain`, so it would have silently failed to detect
any Ironwood output; this corrects the alias to the real Ironwood domain.
As a consequence an Ironwood batch is no longer type-identical to an Orchard
batch, so the compact- and full-block `BatchRunners` gain a distinct
`IronwoodTasks` type parameter alongside the Sapling and Orchard task types, and
a `ScanningKeyOps<IronwoodDomain, ..>` implementation lets the account's Orchard
viewing key trial-decrypt under the Ironwood domain. The Ironwood runners and
scan paths now construct their domains with `IronwoodDomain::for_compact_action`
/ `IronwoodDomain::for_action`.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds tests: scanning detects Ironwood notes across the compact and full scanning paths, and received Ironwood notes round-trip end-to-end through zcash_client_sqlite storage.
zcash_client_backend: Persist the Ironwood note commitment tree
Build and persist the Ironwood note commitment tree during `put_blocks`,
mirroring the Orchard handling but keeping Ironwood a distinct pool. The
Ironwood tree is Orchard-shaped, so it reuses the Orchard shard height and is
updated through `with_ironwood_tree_mut`; the Ironwood block metadata
(`ironwood_commitment_tree_size` and `ironwood_action_count`) is now written to
the `blocks` table via `put_block_meta`.
Checkpoint reconciliation across the note commitment trees is generalized from
two pools to three: each tree gains a checkpoint at every height that is
checkpointed in any other pool, computed as the union of the other pools'
checkpoint heights. When Ironwood has no checkpoints this reduces exactly to the
prior Sapling/Orchard reconciliation, so existing behavior is unchanged; the
Ironwood tree gains empty checkpoints at the scanned heights, as the Orchard
tree already does before its own activation.
With the tree persisted and the block Ironwood tree size recorded, received
Ironwood notes now have witness data and are spendable, and the note positions
computed by the scanner are backed by a built tree.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also property-tests cross-pool checkpoint reconciliation:
Extract the three-pool checkpoint-height reconciliation in `put_blocks` into a
pure `cross_pool_ensure_heights` helper and add proptests for it. The tests
assert the key invariant that after reconciliation every pool is checkpointed at
exactly the union of all pools' checkpoint heights (so the note commitment trees
stay aligned for cross-pool rewinds), and that with no Ironwood checkpoints the
Sapling/Orchard reconciliation is unchanged from the prior two-pool behavior.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add the Ironwood note commitment tree to ChainState
Add a separate Ironwood note commitment tree frontier to `ChainState`, parallel
to the Sapling and Orchard frontiers, with a `final_ironwood_tree` accessor and
wiring through `new`/`empty`. Parse it from the gRPC `TreeState.ironwood_tree`
field via a new `TreeState::ironwood_tree` method; an absent field yields an
empty tree, the correct Ironwood treestate at pool activation. This is the
prior chain state that the Ironwood commitment tree will be built from, added
ahead of the Ironwood tree write path. The Ironwood tree is Orchard-shaped but
is tracked as a distinct pool.
Also complete the Ironwood threading exposed under `--all-features`: the
`sync` decryptor's `BatchRunners::for_keys` callers now pass the Ironwood batch
threshold (reusing the Orchard threshold, as Ironwood outputs are
Orchard-shaped), and the block-metadata query row, widened with the Ironwood
tree size, is factored into a `BlockMetadataRow` type alias.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Persist received Ironwood notes and nullifiers
Store received Ironwood notes during scanning and track Ironwood nullifiers for
spend detection, mirroring the Orchard receive path but keeping Ironwood a
separate pool. `put_blocks` now feeds the scanner's Ironwood output stream to a
dedicated `put_received_ironwood_note` (writing to `ironwood_received_notes`),
detects earlier spends via `detect_ironwood_spend`, records the block's Ironwood
nullifiers with `track_block_ironwood_nullifiers`, and marks the wallet's own
spent Ironwood notes with `mark_ironwood_note_spent`. `Nullifiers::unspent` and
`WalletRead::get_ironwood_nullifiers` complete the spend-detection loop.
Storage no longer routes by note plaintext version: `put_received_note` takes
the target table prefix from the caller, so the scanner routes its separate
Orchard and Ironwood output streams to their respective tables, while the
decrypted-transaction path selects the table from the note version as before.
The shared spent-note marking is factored into a prefix-parameterized helper.
This does not build the Ironwood commitment tree (that needs an Ironwood
frontier in `ChainState`, added separately), so no non-zero Ironwood tree size
is written to `blocks` and received Ironwood notes are not yet spendable. The
send-path Ironwood todos are left for a later increment.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Track Ironwood outputs in the block scanner
Extend the compact- and full-block scanners to trial-decrypt Ironwood outputs
and track the Ironwood note commitment tree, mirroring the existing Orchard
handling but keeping Ironwood as a distinct pool. `BatchRunners` feed the
Ironwood runner from `CompactTx.ironwood_actions` / the transaction's Ironwood
bundle, `PositionTracker` tracks the Ironwood tree position and final size
(activating at NU6.3 and reconciling against `ChainMetadata`'s Ironwood tree
size), and `scan_block_with_runners` collects Ironwood spends, outputs, note
commitments, and nullifiers into a separate `ScannedBundles`.
`ScannedBlock`, `ScannedBlockCommitments`, `BlockMetadata`, `WalletTx`, and
`Nullifiers` gain parallel Ironwood fields and accessors, and the sqlite
`block_metadata`/`block_max_scanned` queries read the persisted Ironwood tree
size. Ironwood note commitments are Orchard-shaped, so the Orchard commitment,
nullifier, and wallet-output types are reused through Ironwood-named aliases;
the two pools remain separate throughout.
This computes the Ironwood scan results but does not yet persist them: the
wallet does not query Ironwood nullifiers (so Ironwood spend detection is inert)
and `put_blocks` does not write received Ironwood notes or update the Ironwood
commitment tree. Those are added with the Ironwood write path.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add Ironwood scanning keys
Add a separate `ironwood` key set to `ScanningKeys`, parallel to the Sapling
and Orchard key sets, with an `ironwood()` accessor and wiring through `new`,
`empty`, and `from_account_ufvks`. Ironwood outputs are Orchard-shaped and are
trial-decrypted under the Orchard note-encryption domain using the account's
Orchard viewing keys, so the Ironwood keys are populated from the Orchard full
viewing key; the `IronwoodDomain`/`IronwoodNullifier` aliases name that reuse of
the underlying Orchard primitives.
Ironwood is kept strictly separate from Orchard as its own pool: the shared
primitives are an implementation detail expressed through aliases, not a design
in which Ironwood belongs to an "Orchard family".
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds the Ironwood compact batch runner:
Add a separate `ironwood` batch runner to the compact-block `BatchRunners`,
parallel to the Sapling and Orchard runners, wired through `for_keys` and
`flush`. Because an Ironwood batch is type-identical to an Orchard batch
(`IronwoodDomain` aliases `OrchardDomain` and the action type is shared), the
Orchard task type already satisfies the runner's bound, so no separate task
type is introduced; the `TaggedIronwoodBatchRunner` alias only names the runner
as belonging to the Ironwood pool.
The runner is constructed from the Ironwood scanning keys and flushed, but is
not yet fed block outputs or collected, so scanning behavior is unchanged. The
decryption of `CompactTx.ironwood_actions` is added in a later change together
with the commitment-tree position tracking that consumes its results.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2545 from zcash/fix/broken_patch_directive
Fix duplicate patch dependency in Cargo.toml
Update to orchard 0.15.0-pre.2
Merge pull request #2507 from nuttycom/feature/shardtree_explicit_retention
Explicit shardtree checkpoint retention (durable anchors)
Merge pull request #2535 from zcash/adam/pczt-signer-lean-parse
pczt: preverified signing parse for the low-level Signer
zcash_client_backend: add WalletCommitmentTrees::remove_retained_checkpoints_below
Adds a default-implemented `WalletCommitmentTrees` method that releases
retained "anchor" checkpoints with height below a given threshold from both
the Sapling and (when enabled) Orchard note commitment trees, so that anchors
that have aged out of the desired retention window can be pruned normally. The
default implementation is built from the `shardtree` retained-checkpoint
primitives, so all backends get it without bespoke code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: retain anchor checkpoints during scanning
During `put_blocks`, checkpoints established at heights on the
`ANCHOR_RETENTION_INTERVAL` (every 288 blocks, ~4 per day) at or above a
configured activation height are retained as durable anchors via
`ShardTree::ensure_retained`, exempting them from automatic pruning of excess
checkpoints so that their roots and anchored witnesses remain computable.
The retention floor is threaded into `put_blocks`/`update_tree` as an
`Option<BlockHeight>`; the SQLite backend computes it as the NU6.3 (Ironwood)
activation height. Anchor retention is therefore dormant until NU6.3 has an
assigned activation height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: implement explicit shardtree checkpoint retention
Implements the `add_retained_checkpoint` / `remove_retained_checkpoint` /
`retained_checkpoints` `ShardStore` methods for `SqliteShardStore`, backed by
new per-pool `{sapling,orchard}_tree_retained_checkpoints` tables created by a
new migration. Retained checkpoints are exempt from automatic pruning of
excess checkpoints, so their roots and the witnesses anchored to them remain
computable after they age more than `max_checkpoints` behind the chain tip.
Also adds a `remove_retained_checkpoints_below` helper for releasing anchors
older than a given height (wired into the wallet API in a subsequent commit).
Temporarily patches `shardtree`/`incrementalmerkletree` to
zcash/incrementalmerkletree `main`, which carries the retention API merged in
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pczt: reword the inline(never) action-parse comment
Apply @ValarDragon's review suggestion, stating the rationale for the
`#[inline(never)]` per-action parse helper more concisely: it avoids
inlining into a `.map(..).collect()` loop that would compile to a stack
frame tens of KB deep and overflow embedded signer stacks on
high-action-count transactions. Comment-only; no behavior change.
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
supply-chain: point the orchard vet exemption at the pinned rev
The orchard pin bump in e862ecef02 moved the [patch.crates-io] git rev
from 20c10904 to ab7c5fae but left the cargo-vet exemption on the old
rev, so `cargo vet --locked` failed with
orchard:0.15.0-pre.1@git:ab7c5fae... missing ["safe-to-deploy"]
Update the exemption rev to match Cargo.lock. `cargo vet --locked` now
succeeds. The exemption stays temporary and is dropped together with the
[patch] once the orchard change releases.
Merge pull request #2386 from valargroup/dojha/zcash-proofs-readme-sprout
zcash_proofs: scope README to Sprout
Apply suggestion from @ValarDragon
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
pczt: drop Bundle/Action preverified parse forks; bump orchard pin
The upstream orchard signer-only-parse work removed the redundant Bundle
and Action parse_preverified_for_signing wrappers (they were pure
pass-throughs), keeping only the spend-level entry point. Collapse the
matching Action/Bundle `if preverified { .. } else { .. }` branches here
to unconditional Action::parse / Bundle::parse; the `preverified` flag
now selects only at the spend level, the sole place it changes behavior.
Bump the orchard patch to the current head of that branch so the build
tracks the change; the previous pin still referenced the pre-cleanup
orchard that carried the removed wrappers.
Merge pull request #2534 from zcash/dw/ironwood-pool-code-views
zcash_client_sqlite: Report Ironwood outputs as pool code 4
CHANGELOG: Add Ironwood received-output views entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received notes to the output views
Add a migration that recreates the `v_received_outputs` and
`v_received_output_spends` views with an additional branch unioning in the
`ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged
with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`,
`v_tx_outputs`, and the balances derived from them.
Ironwood notes are stored in a table separate from `orchard_received_notes`
because the two pools have distinct note commitment trees and an Orchard action
and an Ironwood action in the same transaction may share an action index, so the
views union the separate tables rather than splitting one table by note version.
Update the canonical `v_received_outputs` / `v_received_output_spends`
definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a
reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The
new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4)
at the same `(transaction_id, action_index)` and asserts both surface distinctly
in `v_received_outputs`.
Adapted from the Ironwood work on
valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Assign the Ironwood shielded pool code
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code`
(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This
is the SQLite `output_pool` code used to distinguish Ironwood outputs from
Orchard outputs, which share the `orchard_received_notes` table.
Adapted from valargroup/librustzcash@d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2533 from zcash/dw/ironwood-scanning
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
zcash_client_sqlite: Fix missing CURRENT_LEAF_MIGRATIONS entries.
Fix formatting and feature-flagging errors.
CHANGELOG: Add Ironwood scan-range extension entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
When Ironwood notes are detected during scanning, extend the suggested scan
ranges to include the blocks needed to complete the Ironwood note commitment
tree subtrees containing those notes, mirroring the existing Sapling and Orchard
handling.
Ironwood notes are stored alongside Orchard notes in `orchard_received_notes`,
but their commitment tree is tracked separately (in the `ironwood_*` tables), so
`extend_range` now takes the tree table prefix directly instead of deriving it
from a `ShieldedPool`. This avoids conflating the note-table pool with the
commitment-tree tables, and lets the Sapling, Orchard, and Ironwood callers pass
their respective prefixes. The Ironwood extension is gated on NU6.3 activation.
Adapted from valargroup/librustzcash@f42001edde and @0af74dd549
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2538 from zcash/kn/ironwood-note-version-migrations
zcash_client_sqlite: Add ironwood_received_notes
CHANGELOG: Add Ironwood received-note storage entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received-note storage
Ironwood notes (ZIP 2005, NU6.3) are Orchard-protocol notes obtained
from version 3 note plaintexts, carried by the Ironwood bundle of a
transaction and committed to the Ironwood note commitment tree. Store
them separately from `orchard_received_notes`: the two pools have
distinct note commitment trees, and an Orchard action and an Ironwood
action in the same transaction may share an action index.
A new migration adds the `ironwood_received_notes` and
`ironwood_received_note_spends` tables, mirroring the corresponding
Orchard tables. `put_received_note` now records a received note in the
table for the pool implied by the note's plaintext version: version 2
note plaintexts can be obtained only under the Orchard note encryption
domain, and version 3 note plaintexts only under the Ironwood domain.
Reading Ironwood notes back for spendability, balance, and view
reporting is not yet implemented; it will accompany Ironwood scanning
support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG: Add Orchard received-note version entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add note version to Orchard received notes
The Orchard protocol revision introduced by NU6.3 (ZIP 2005) versions
Orchard note plaintexts: pre-NU6.3 note plaintexts are version 2, and
Ironwood note plaintexts are version 3. The version determines how the
note commitment trapdoor is derived from the note's rseed, so the
version observed at decryption time must be persisted in order to
reconstruct received notes.
Add a `note_version` column to `orchard_received_notes`, populated from
the note plaintext version reported by the decrypted note itself.
Existing rows are backfilled as version 2, the only version accepted by
the Orchard note encryption domain. Note reconstruction now uses the
stored version instead of assuming version 2. Ironwood notes are not
stored in this table (they belong to a distinct pool with its own note
commitment tree), so the `(transaction_id, action_index)` uniqueness
constraint is unchanged.
Adapted from valargroup/librustzcash@0555be5234 and @d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge pull request #2422 from zcash/feat/propose-transaction-can-spend-transparent-utxos
feat: propose transaction can spend transparent utxos
Merge pull request #2531 from zcash/dw/ironwood-wallet-storage-scanning
zcash_client_sqlite: Add Ironwood note commitment tree storage
Merge branch 'main' into feat/propose-transaction-can-spend-transparent-utxos
zcash_client_backend, zcash_client_sqlite: enforce TransparentSpendPolicy::FromAddresses
select_spendable_transparent_outputs gathered transparent inputs for the
entire account regardless of TransparentSpendPolicy, so FromAddresses
behaved identically to AnyAccountTaddr: a proposal could spend from (and
link) addresses the caller explicitly excluded. Post-gather filtering is
not sufficient, since excluded UTXOs would consume the value bound and
under-fill the result; the address restriction must be applied within
the gather itself.
Add an address_allow_list parameter to select_spendable_transparent_outputs,
applied inside the SQL query (via the same always-bound-flag idiom already
used for coinbase_filter) so ineligible outputs never consume the value
bound. GreedyInputSelector wires this from TransparentSpendPolicy at both
gather sites: the initial gather and the InsufficientFunds re-gather
fallback (which had the same bug independently).
Also refactors NonEmptyBTreeSet to wrap a single BTreeSet with proper
invariant-preserving constructors (singleton, from_nonempty, from_set)
and accessors (as_set, iter) instead of exposing raw head/tail fields,
and adds persistent transparent_dust tracking across the propose_transaction
retry loop so a re-gather triggered by InsufficientFunds does not
reintroduce transparent inputs already pruned as dust by a prior
DustInputs iteration.
Strengthens propose_t2t_from_addresses to actually catch the bug: the
unnamed address's UTXO now holds more value than the named address's
and is inserted first, so an unenforced policy would select it (since
it alone covers the payment) instead of the named address, making the
test fail without the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pczt: preverified signing parse for the low-level Signer
Parse the low-level Signer's bundle with orchard's
`parse_preverified_for_signing`, which skips deriving each spend's
`FullViewingKey` from its wire `fvk` bytes. The spend authorization
signature does not depend on `fvk`, so signatures are byte-identical to
the full parse.
`sign_orchard_with` and `sign_ironwood_with` snapshot each spend's
`(rk, fvk)` before parsing and restore the `fvk` after serialization so
signing leaves the wire bytes unchanged. The restore returns
`OrchardParseError::SigningClosureModifiedActions` if the closure changes
the action count or reorders actions, detected by the per-position `rk`,
and makes no changes when it rejects.
Refactor the Orchard action parse from `.map(...).collect()` into a
bounded `#[inline(never)]` loop so a constrained signer does not overflow
its task stack. Behaviorally identical; the full `parse` used by the
Verifier, Prover, and Updater is unchanged.
Temporarily patches orchard to the branch adding
`parse_preverified_for_signing`, with a matching cargo-vet exemption;
both are dropped for a version bump once that orchard change releases.
Merge pull request #2517 from nuttycom/fix/upgrade-imported-transparent-address
zcash_client_sqlite: Upgrade imported transparent addresses to their derived form on gap-limit derivation
zcash_client_backend, zcash_client_sqlite: document the transparent input-count cap in CHANGELOGs
Update the still-unreleased select_spendable_transparent_outputs and
with_shielding_block_space_percent entries to describe the max_inputs
parameter and its dual use for shielding and general transfers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: cap transparent inputs gathered for general transfers
InputSource::select_spendable_transparent_outputs now takes a max_inputs
parameter bounding the number of transparent outputs the gather may
select, independent of target_value. Without this, a wallet holding a
very large number of small (e.g. dust) UTXOs could require an unbounded
number of inputs to satisfy even a modest request, producing an
oversized transaction.
GreedyInputSelector::propose_transaction wires this to the existing
shielding_block_space_percent config (via shielding_max_inputs), reusing
the same block-space bound already used to cap shielding transactions
rather than introducing a second configuration knob. When the cap is
reached before the requested value, the caller's input-selection loop
surfaces this the same as any other value shortfall: an InsufficientFunds
error.
The SQLite implementation enforces the cap inside the gather loop itself
(breaking out once max_inputs outputs have been accumulated), rather than
gathering an unbounded set and truncating afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: fix invalid migration UUID and add missing verify_schema entry
add_transparent_value_index's MIGRATION_ID was a hand-typed UUID whose
version nibble was 6, not 4, making it invalid per the CI 'uuid' job's
random/v4 check (.github/workflows/ci.yml). Replace it with a properly
generated v4 UUID.
Separately, the migration's new idx_transparent_received_outputs_value_zat
index was never added to wallet::init::tests::verify_schema's expected
index list, nor given a db.rs constant, causing that test to fail with a
positional mismatch against every index sorted after it. Add the missing
db::INDEX_TRANSPARENT_RECEIVED_OUTPUTS_VALUE_ZAT constant and list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Address review comments on duplicate-receiver resolution
- Decode key scopes via KeyScope::decode and match on the enum rather than raw
integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
cross-account duplicate whose ephemeral record reproduces the receiver is resolved
in its favor rather than aborting. Only Foreign records remain unverifiable, having
no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: depend on fix_transparent_received_outputs, not utxos_to_txos, for the value_zat index migration
fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.
Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.
Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: adapt to upstream API changes from rebase onto main
Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:
- Update the remaining ShieldedProtocol::Sapling references (added by
this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
pass 0 from the transparent-only gather in
select_spendable_transparent_outputs, matching the existing 0 passed
for sapling_input_count/sapling_output_count/orchard_action_count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Upgrade receivers imported under a different account on derivation
The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Resolve cross-account duplicate receivers by derivation
A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: CHANGELOG for imported→derived transparent address upgrade
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Preserve earliest exposed_at_height when merging duplicate receivers
The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Upgrade imported transparent receiver in place on derivation
When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: fix changelog/doc self-review findings
- Move two changelog entries that described this PR's new work out of
already-released, dated version sections (zcash_client_backend's
[0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
Unreleased/PLANNED sections. Released changelog sections must remain
immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
discarded intermediate designs (an unbounded gather, an
Option<Zatoshis> static-fee-headroom parameter) as though they were
prior shipped behavior. Since select_spendable_transparent_outputs is
entirely new and unreleased, changelog/doc text should describe only
the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
AGENTS.md conventions file, meaningless to external readers of the
public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
non-empty BTreeSet") and document its previously-undocumented public
fields; drop a private implementation note about possibly moving it to
another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
which controls the same privacy behavior the method's doc already
describes but was never mentioned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: fix spurious InsufficientFunds when transparent re-gather succeeds without new shielded notes
GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.
Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.
Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: make transparent gather fee-aware
InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.
Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).
Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: add value-bounded transparent input gather
Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.
The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.
GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: extract shared spendable-transparent-outputs query builder
Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.
This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).
Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: Remove gather_account_transparent_inputs in favor of batched query
`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce verbosity in propose_transaction docs and related types
better documentation and testing for propose_shielding transparent UTXO spending functionality
added variant CoinbaseFilter::NonCoinbaseOnly to remove coinbase from propose_transaction
rename TransparentOutputFilter and better names for TransparentSpendPolicy methods
add tests and changelog for transparent spending in propose_transaction
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add transparent spending to propose_transaction
Co-Authored-By: fdecroix <fdecroix@eryx.co>
CHANGELOG: Add Ironwood note commitment tree storage entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2508 from zcash/adam/ironwood-wallet-fees
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Adapt Ironwood fee wiring to the new tx-builder bundle-version API
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.
Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.
Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
Merge pull request #2498 from zcash/adam/ironwood-tx-builder-completion
zcash_client_backend: Wire Ironwood anchors into wallet transaction building
wallet: revert unnecessary change
CHANGELOG: mv with_ironwood_tree_mut to appropriate changed section
Assert exact Ironwood fee values; document the routing flag
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
Route Orchard-pool change into the Ironwood bundle to match the builder
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.
Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.
Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.
Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Note the compute_balance Ironwood view in the changelog
Charge the Ironwood bundle at proposal time
Populate the Ironwood fee view introduced previously: when the builder
will route Orchard-pool outputs into a separate Ironwood bundle, mirror
that split during input selection so the proposal's per-bundle action
counts (and therefore the fee) match the transaction that gets built.
The routing decision reuses the builder's own `orchard_outputs_to_ironwood`
predicate (threading the same `proposed_version`), so the proposal and
build paths cannot drift. The Orchard bundle keeps the Orchard spends; its
outputs move to the Ironwood bundle when routing is active. Ironwood has
no spends yet, as the wallet does not select Ironwood notes until note
detection lands.
Adds a zip317 test asserting that a populated Ironwood view increases the
required fee (the Ironwood bundle's actions are counted independently of
Orchard).
Thread a separate Ironwood bundle view through the fee/change path
V6 transactions carry separate Orchard and Ironwood bundles, each padded
to its own action floor, and `FeeRule::fee_required` already takes a
separate `ironwood_action_count`. Populate that count from a dedicated
Ironwood `BundleView` rather than the previous hardcoded zero.
`ChangeStrategy::compute_balance` and the `common.rs` helpers
(`calculate_net_flows`, `single_pool_output_balance`) gain an `ironwood`
view parameter alongside `orchard` (gated on `feature = "orchard"`,
mirroring it), `NetFlows` gains `ironwood_in`/`ironwood_out`, and the
Ironwood action count is computed via
`transactional_action_count(ironwood.bundle_version(), ...)`.
All call sites currently pass an empty Ironwood view, so this is a no-op:
the wallet does not yet route inputs/outputs to the Ironwood pool at
proposal time. A follow-up populates the view from the same routing
decision the builder uses, so the separate Ironwood bundle is priced.
Verified: compiles under default features and the nu7 cfg; the 11
zip317/fixed change-strategy tests pass unchanged.
Address review findings: routing enum, dedup, docs, expect
Model the per-step Orchard/Ironwood routing as an `OrchardBuildMode` enum
computed once, replacing the three booleans and four-arm if-else. The
spend-anchor, Ironwood inputs, and output-routing decisions now derive
from a single value so they cannot drift apart, and the `LegacyV5 +
Ironwood spends` rejection is expressed at classification time.
Replace the `ironwood_spends_requested implies shielded inputs` `expect`
with a `let-else` that returns `ProposalNotSupported` rather than
panicking on the (unreachable) `None`.
De-duplicate the cfg-gated Orchard change-output block so the Orchard
path is written once, gated by a `change_to_ironwood` flag that is
`false` in builds without Ironwood.
Document `stored_ironwood_output_index`: V6 stores Orchard and Ironwood
outputs in one index space with Orchard actions first.
Wire Ironwood anchors into wallet transaction building
Merge pull request #2522 from zcash/chore/shielded_protocol_rename
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool` and add `ShieldedPool::Ironwood`
Merge pull request #2520 from zcash/feat/pczt_ironwood_completion
pczt: Complete branch-aware v6/Ironwood support
Merge pull request #2521 from nuttycom/fix/put-transparent-output-preserves-mined-height
zcash_client_sqlite: Don't clear a recorded mined_height when storing a transparent output with unknown height
pczt: Generalize bundle version derivation over the value pool
`Creator::orchard_bundle_version()` becomes `bundle_version(pool)`, and
the crate-level revision-to-version mapping takes the `ValuePool` as an
argument (mirroring `zcash_primitives`'s `bundle_version_for_branch`),
so both the Orchard and Ironwood bundle versions are derived from the
consensus branch ID and pool alone. `with_ironwood_flags` now expresses
its version-support check as pool availability rather than a
transaction-version comparison.
Also restructure the transaction-version validation in
`extract_tx_data` as a single exhaustive match, and apply two
comment-wording suggestions in the v1 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Derive the Creator's Orchard bundle version instead of storing it
The Creator now stores the validated `BranchId` (rather than the raw
consensus branch ID) and derives the Orchard-pool bundle version from it
on demand, removing a stored field that was a pure function of state the
Creator already held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add high-level Ironwood methods to the Signer, Verifier, and Updater
Mirrors of the corresponding Orchard-pool methods:
- `Signer::{sign_ironwood, apply_ironwood_signature}` (with new
`Error::{IronwoodSign, IronwoodVerify}` variants), so
hardware-wallet-style signers no longer need the low-level Signer to
authorize Ironwood actions.
- `Verifier::with_ironwood`
- `Updater::update_ironwood_with`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add v6/Ironwood support to the Creator and validate at extraction
- `Creator::new` now derives the transaction version and version group
ID from the consensus branch ID: the v6 format from NU6.3 onward, and
the v5 format for earlier upgrades. Previously the Creator could only
produce v5 PCZTs.
- New `Creator::with_ironwood_anchor` and `Creator::with_ironwood_flags`
methods configure the Ironwood bundle independently of the Orchard
bundle; both reject configuration of an Ironwood bundle for a
transaction version that does not carry one
(`Error::IronwoodNotSupported`).
- Transaction extraction now rejects v6 PCZTs whose consensus branch ID
predates NU6.3, and non-v6 PCZTs carrying non-canonical Ironwood
bundle data (`ExtractError::IronwoodNotSupported`), instead of
silently dropping the Ironwood data or extracting a consensus-invalid
transaction.
- The v1 serialization format refuses to encode v6 PCZTs, which a v1
parser could decode but never extract a transaction from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Canonicalize the empty states of the PCZT bundles
The Creator, `Creator::build_from_parts`, the v1 and v2 serialization
formats, and the IO Finalizer previously produced four different
representations of an "empty" Ironwood bundle (and inconsistent empty
Sapling/Orchard bundles), which broke `Bundle::merge` for workflows that
mix serialization-round-tripped and in-memory copies of the same PCZT.
Each bundle type now has a single canonical empty representation,
defined as a constant in its module and used consistently everywhere:
- The Creator's Ironwood placeholder no longer mirrors the Orchard
bundle's flags and anchor; it is exactly the canonical empty bundle
(whose flags are the Ironwood defaults: spends, outputs, and
cross-address transfers enabled).
- `Creator::build_from_parts` uses the canonical empty bundles (with
all-zeroes anchors, rather than the empty-tree root) for absent
bundles, and now maps `parts.ironwood` through instead of discarding
it, so the builder-to-PCZT path no longer loses Ironwood spends and
outputs.
- Empty bundle value sums are initialized as non-negative zero,
matching the form produced by re-serializing a parsed bundle.
- The IO Finalizer no longer sets `bsk` on an empty Orchard-protocol
bundle. (The Sapling bundle is still always finalized, because the
Sapling Transaction Extractor requires `bsk` even for an empty
bundle.)
- The v2 format's omission decision is now exact equality with the
slot's canonical empty bundle, so a bundle with non-default flags or
note version round-trips losslessly instead of being restored with
default values; the v1 format refuses to encode a PCZT whose Ironwood
bundle is not canonically empty, rather than silently dropping its
non-default fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Don't clear mined_height when storing an output with unknown height
put_transparent_output's transactions upsert overwrote mined_height unconditionally,
so re-storing a transparent output whose mined height is unknown to the caller (e.g.
one re-observed via the mempool, or fetched from a backend that could not locate the
transaction on the best chain) clobbered a previously-recorded mined height with NULL,
marking a mined transaction as unmined. A NULL height carries no evidence that the
recorded height is wrong; preserve it, matching the adjacent handling of the block
column. Genuine un-mining remains the responsibility of truncate_to_height.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_protocol: Add `ShieldedPool::Ironwood`
This introducese `todo!(...)` stanzas in each location in
`zcash_client_backend` and `zcash_client_sqlite` where Ironwood
support infrastructure has not yet been implemented.
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool`
It is no longer appropriate to refer to the Orchard "shielded protocol"
as there are three different variants of the Orchard shielded protocol.
`zcash_protocol::ShieldedProtocol` has historically been used as a pool
identifier; the rename here reifies this change.
Merge pull request #2516 from zcash/fix/pr2509_review_followup
zcash_primitives: Address post-merge review comments from PR #2509
zcash_client_backend: Derive the PCZT test proving key from the consensus branch
The `pczt_single_step` pool test hardcoded the post-NU6.2 fixed-circuit
proving key, which was consistent with the PCZT roles' previous pinning
of the legacy Orchard bundle version but mismatches the branch-derived
parsing, proving, and verification those roles now perform: the shared
test network fixture's most recent upgrade is NU5, under which the
Orchard pool is governed by the historical pre-NU6.2 circuit, so proof
verification failed at extraction. Derive the proving key from the
PCZT's own consensus branch ID instead. (Modernizing the test network
fixture so that wallet tests exercise current consensus is part of the
Ironwood test coverage work tracked in #2518.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add OrchardProtocolRevision to zcash_protocol; make all PCZT roles branch-aware
The consensus knowledge of which Orchard protocol revision each network
upgrade deploys now lives in a single place:
`zcash_protocol::consensus::BranchId::orchard_protocol_revision`, which
returns the new `OrchardProtocolRevision` enum (`None` for branches that
predate NU5). `zcash_primitives`'s `bundle_version_for_branch` and the
`pczt` crate both derive `orchard::bundle::BundleVersion`s from it via
trivial conversions, replacing the duplicated branch-to-version matches.
(`pczt` cannot reuse the `zcash_primitives` helper directly because its
`zcash_primitives` dependency is optional and must not be required by
the lightweight role builds.)
Building on this, the remaining PCZT roles (Updater, Verifier, Prover,
and the low-level Signer) now parse the Orchard-pool bundle using the
bundle version implied by the PCZT's consensus branch ID rather than
pinning the legacy pre-NU6.3 version, completing the branch-aware
parsing work begun with `extract_tx_data`; in particular, the Prover now
creates proofs for the correct circuit for post-NU6.3 Orchard-pool
bundles. Roles report new `UnsupportedConsensusBranchId` errors when the
branch ID is unrecognized or predates NU5, and the low-level Signer's
Orchard-protocol signing methods now bound their error parameter by
`From<OrchardParseError>`. `Bundle::into_orchard_parsed`, which pinned
the legacy version, is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address remaining review comments from PR #2509
- pczt: fix extraction and verification of post-NU6.3 Orchard-pool
bundles. `extract_tx_data` now parses the Orchard-pool bundle using
the bundle version implied by the PCZT's consensus branch ID rather
than pinning the legacy pre-NU6.3 version, and the Transaction
Extractor's bundle verification derives the circuit version from the
bundle itself rather than hardcoding the NU6.2 fixed circuit (which
could not verify post-NU6.3 Orchard-pool bundles). The
`verify_bundle`/`verify_ironwood_bundle` duplication collapses into a
single function. Other PCZT roles still parse the Orchard-pool bundle
with the legacy version pin; completing those is tracked as follow-up
work.
- zcash_primitives: note the ZIP 229 specification of the v6 Sapling
digest variants in the `txid.rs` documentation, and correct the
`BlockTxCommitmentDigester::HeaderDigest` doc comment.
- zcash_client_backend: add a ZIP 317 change-calculation test covering
the post-NU6.3 Orchard action-count policy (one action per spend and
per output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive Orchard-protocol bundle versions from consensus context
Review followups:
- Add `bundle_version_for_branch`, returning the `BundleVersion` in
effect for a given Orchard-protocol value pool under a given consensus
branch, or `None` when the pool is not supported under that branch
(the Orchard pool prior to NU5; the Ironwood pool prior to NU6.3).
- `read_v5_bundle` now takes the consensus branch ID, and
`read_v6_bundle` the consensus branch ID and the `orchard::ValuePool`
identifying the bundle slot to read, in place of caller-supplied
`orchard::bundle::BundleVersion` arguments. Callers can no longer
select a wrong-slot bundle version, and a non-empty bundle belonging
to a pool that is unsupported under the transaction's consensus branch
ID is rejected as invalid data.
- In `Builder::new`, the `None` result subsumes the explicit
NU5-activation check that previously gated construction of the Orchard
builder.
- Remove `zcash_client_backend::ANY_ORCHARD_BUNDLE_VERSION`. The fee and
change calculation call sites that used it all have access to the
proposal's target height, and now derive the Orchard bundle version
(and hence the action-count policy) from it. As a consequence,
proposals targeting heights at or beyond NU6.3 activation now count
one action per Orchard spend or output, matching the post-NU6.3
transaction builder. `EmptyBundleView` retains a fixed version, which
is sound because an empty bundle produces zero actions under every
version's action-count policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_primitives: Address post-merge review comments from PR #2509
- `read_v5_bundle` now takes the consensus branch ID, deriving the
Orchard bundle version via the new public helper
`orchard_bundle_version_for_branch` (an exhaustive match, replacing
both the wildcard-arm private helper in the builder and the inline
mapping in `Transaction::read_v5`); this also removes an incomplete
sentence from its documentation.
- Make `TxVersion::has_ironwood` an exhaustive match.
- Simplify the `arb_txdata` proptest strategies:
`arb_ironwood_bundle_for_version` already yields `None` for versions
without Ironwood support.
- Factor out `Builder::orchard_in_use` to mirror `ironwood_in_use`.
- Correct the coinbase Orchard-builder flag comment, strengthen the
`with_expiry_height` warning, and fix stale `txid.rs` documentation.
- Give NU6.3 and NU7 distinct activation heights in the nu7 builder
test network.
- Add a test checking that Orchard note commitments and nullifiers are
domain-separated by the note plaintext version (lead byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address issues found in review of the NU6.3 testnet support branch
- `zcash_protocol`: under `zcash_unstable = "nu7"`, `BranchId::Nu6_3` now
reports the NU7 activation height as the upper bound of its height
range. The cfg-gated upper-bound pattern was dropped from the `Nu6_2`
arm when the `Nu6_3` arm was introduced, causing the Nu6_3 and Nu7
epochs to overlap in nu7 builds.
- `zcash_primitives`: the transaction builder's version-compatibility
check now accounts for Orchard change outputs, matching the Ironwood
check. Previously a change-only Orchard bundle passed the compatibility
check for `TxVersion::V4`, resulting in a reachable panic in `build()`.
- `zcash_primitives`: document previously-omitted API changes in the
0.29.0-pre.0 CHANGELOG entry (`map_bundles`/`try_map_bundles` closure
semantics, `TransactionDigest::combine` `ironwood_digest` argument,
`BlockTxCommitmentDigester::HeaderDigest` type change,
`BuildConfig::orchard_builder_config` removal,
`TxVersion::has_ironwood`).
- `zcash_transparent`: restore Keep-a-Changelog section ordering in the
0.9.0-pre.0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply minor review suggestions from PR #2509
- Fix the `expected_nu7` regtest activation heights to differ from the
NU6.3 height, assert NU6.3 activation at its own height, and check NU7
non-activation against the NU6.3 height in the `zcash_protocol`
`local_consensus` tests.
- Remove the stale placeholder-branch-ID paragraph from the
`BranchId::Nu6_3` docs.
- Simplify the `BranchId::Nu6_2` height-bounds arm to …
dannywillems
pushed a commit
that referenced
this pull request
Jul 7, 2026
Relying upon the note plaintext version to discriminate between Orchard
and Ironwood notes works, but it complicates reasoning about the code
and makes it difficult to understand where orchard-the-protocol is being
used vs orchard-the-pool. By adding value pool metadata to wallet
orchard-protocol notes, we thoroughly disambiguate; it's entirely
possible that in the future another note plaintext version will be
introduced (for example, for the note plaintexts of memo bundles) that
does not change the pool, and this refactoring makes such updates
straightforward to deal with.
Also lifts note pool classification to Step (Note::pool, Step input-pool predicates and counters, and Proposal::input_count_in_pool), with tests for the proposal pool-input helpers.
zcash_client_sqlite: Drop a non-leaf from CURRENT_LEAF_MIGRATIONS
`ironwood_shardtree` was listed in `CURRENT_LEAF_MIGRATIONS`, but
`witness_stabilized_notes` depends on it, so it is not a leaf of the migration
dependency graph. Migrating to the current state still reached it transitively,
so this was harmless, but the constant is documented to hold exactly the graph
leaves. Remove the stale entry and add a test that recomputes the leaves from the
dependency graph and checks them against the constant, so this cannot regress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Include Ironwood notes in the account balance
Add an Ironwood balance component to `AccountBalance`, alongside the Sapling and
Orchard components, with an `ironwood_balance` accessor and a
`with_ironwood_balance_mut` method. The Ironwood balance is included in the
account's total, spendable, pending-confirmation, pending-spendability, and
uneconomic values.
`get_wallet_summary` now computes the Ironwood balance from
`ironwood_received_notes` via the existing pool-generic `with_pool_balances`, so
received Ironwood notes are reflected in the wallet balance. The end-to-end
storage test now asserts the balance equals the received note value.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Decrypt Ironwood outputs with the Ironwood domain
Ironwood notes carry version 3 note plaintexts, which are decrypted under the
Ironwood note-encryption domain (`orchard::note_encryption::IronwoodDomain`).
That domain is distinct from `OrchardDomain`, which accepts only version 2
plaintexts and would reject every Ironwood note. The scanner previously aliased
`IronwoodDomain` to `OrchardDomain`, so it would have silently failed to detect
any Ironwood output; this corrects the alias to the real Ironwood domain.
As a consequence an Ironwood batch is no longer type-identical to an Orchard
batch, so the compact- and full-block `BatchRunners` gain a distinct
`IronwoodTasks` type parameter alongside the Sapling and Orchard task types, and
a `ScanningKeyOps<IronwoodDomain, ..>` implementation lets the account's Orchard
viewing key trial-decrypt under the Ironwood domain. The Ironwood runners and
scan paths now construct their domains with `IronwoodDomain::for_compact_action`
/ `IronwoodDomain::for_action`.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds tests: scanning detects Ironwood notes across the compact and full scanning paths, and received Ironwood notes round-trip end-to-end through zcash_client_sqlite storage.
zcash_client_backend: Persist the Ironwood note commitment tree
Build and persist the Ironwood note commitment tree during `put_blocks`,
mirroring the Orchard handling but keeping Ironwood a distinct pool. The
Ironwood tree is Orchard-shaped, so it reuses the Orchard shard height and is
updated through `with_ironwood_tree_mut`; the Ironwood block metadata
(`ironwood_commitment_tree_size` and `ironwood_action_count`) is now written to
the `blocks` table via `put_block_meta`.
Checkpoint reconciliation across the note commitment trees is generalized from
two pools to three: each tree gains a checkpoint at every height that is
checkpointed in any other pool, computed as the union of the other pools'
checkpoint heights. When Ironwood has no checkpoints this reduces exactly to the
prior Sapling/Orchard reconciliation, so existing behavior is unchanged; the
Ironwood tree gains empty checkpoints at the scanned heights, as the Orchard
tree already does before its own activation.
With the tree persisted and the block Ironwood tree size recorded, received
Ironwood notes now have witness data and are spendable, and the note positions
computed by the scanner are backed by a built tree.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also property-tests cross-pool checkpoint reconciliation:
Extract the three-pool checkpoint-height reconciliation in `put_blocks` into a
pure `cross_pool_ensure_heights` helper and add proptests for it. The tests
assert the key invariant that after reconciliation every pool is checkpointed at
exactly the union of all pools' checkpoint heights (so the note commitment trees
stay aligned for cross-pool rewinds), and that with no Ironwood checkpoints the
Sapling/Orchard reconciliation is unchanged from the prior two-pool behavior.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add the Ironwood note commitment tree to ChainState
Add a separate Ironwood note commitment tree frontier to `ChainState`, parallel
to the Sapling and Orchard frontiers, with a `final_ironwood_tree` accessor and
wiring through `new`/`empty`. Parse it from the gRPC `TreeState.ironwood_tree`
field via a new `TreeState::ironwood_tree` method; an absent field yields an
empty tree, the correct Ironwood treestate at pool activation. This is the
prior chain state that the Ironwood commitment tree will be built from, added
ahead of the Ironwood tree write path. The Ironwood tree is Orchard-shaped but
is tracked as a distinct pool.
Also complete the Ironwood threading exposed under `--all-features`: the
`sync` decryptor's `BatchRunners::for_keys` callers now pass the Ironwood batch
threshold (reusing the Orchard threshold, as Ironwood outputs are
Orchard-shaped), and the block-metadata query row, widened with the Ironwood
tree size, is factored into a `BlockMetadataRow` type alias.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Persist received Ironwood notes and nullifiers
Store received Ironwood notes during scanning and track Ironwood nullifiers for
spend detection, mirroring the Orchard receive path but keeping Ironwood a
separate pool. `put_blocks` now feeds the scanner's Ironwood output stream to a
dedicated `put_received_ironwood_note` (writing to `ironwood_received_notes`),
detects earlier spends via `detect_ironwood_spend`, records the block's Ironwood
nullifiers with `track_block_ironwood_nullifiers`, and marks the wallet's own
spent Ironwood notes with `mark_ironwood_note_spent`. `Nullifiers::unspent` and
`WalletRead::get_ironwood_nullifiers` complete the spend-detection loop.
Storage no longer routes by note plaintext version: `put_received_note` takes
the target table prefix from the caller, so the scanner routes its separate
Orchard and Ironwood output streams to their respective tables, while the
decrypted-transaction path selects the table from the note version as before.
The shared spent-note marking is factored into a prefix-parameterized helper.
This does not build the Ironwood commitment tree (that needs an Ironwood
frontier in `ChainState`, added separately), so no non-zero Ironwood tree size
is written to `blocks` and received Ironwood notes are not yet spendable. The
send-path Ironwood todos are left for a later increment.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Track Ironwood outputs in the block scanner
Extend the compact- and full-block scanners to trial-decrypt Ironwood outputs
and track the Ironwood note commitment tree, mirroring the existing Orchard
handling but keeping Ironwood as a distinct pool. `BatchRunners` feed the
Ironwood runner from `CompactTx.ironwood_actions` / the transaction's Ironwood
bundle, `PositionTracker` tracks the Ironwood tree position and final size
(activating at NU6.3 and reconciling against `ChainMetadata`'s Ironwood tree
size), and `scan_block_with_runners` collects Ironwood spends, outputs, note
commitments, and nullifiers into a separate `ScannedBundles`.
`ScannedBlock`, `ScannedBlockCommitments`, `BlockMetadata`, `WalletTx`, and
`Nullifiers` gain parallel Ironwood fields and accessors, and the sqlite
`block_metadata`/`block_max_scanned` queries read the persisted Ironwood tree
size. Ironwood note commitments are Orchard-shaped, so the Orchard commitment,
nullifier, and wallet-output types are reused through Ironwood-named aliases;
the two pools remain separate throughout.
This computes the Ironwood scan results but does not yet persist them: the
wallet does not query Ironwood nullifiers (so Ironwood spend detection is inert)
and `put_blocks` does not write received Ironwood notes or update the Ironwood
commitment tree. Those are added with the Ironwood write path.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add Ironwood scanning keys
Add a separate `ironwood` key set to `ScanningKeys`, parallel to the Sapling
and Orchard key sets, with an `ironwood()` accessor and wiring through `new`,
`empty`, and `from_account_ufvks`. Ironwood outputs are Orchard-shaped and are
trial-decrypted under the Orchard note-encryption domain using the account's
Orchard viewing keys, so the Ironwood keys are populated from the Orchard full
viewing key; the `IronwoodDomain`/`IronwoodNullifier` aliases name that reuse of
the underlying Orchard primitives.
Ironwood is kept strictly separate from Orchard as its own pool: the shared
primitives are an implementation detail expressed through aliases, not a design
in which Ironwood belongs to an "Orchard family".
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds the Ironwood compact batch runner:
Add a separate `ironwood` batch runner to the compact-block `BatchRunners`,
parallel to the Sapling and Orchard runners, wired through `for_keys` and
`flush`. Because an Ironwood batch is type-identical to an Orchard batch
(`IronwoodDomain` aliases `OrchardDomain` and the action type is shared), the
Orchard task type already satisfies the runner's bound, so no separate task
type is introduced; the `TaggedIronwoodBatchRunner` alias only names the runner
as belonging to the Ironwood pool.
The runner is constructed from the Ironwood scanning keys and flushed, but is
not yet fed block outputs or collected, so scanning behavior is unchanged. The
decryption of `CompactTx.ironwood_actions` is added in a later change together
with the commitment-tree position tracking that consumes its results.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2545 from zcash/fix/broken_patch_directive
Fix duplicate patch dependency in Cargo.toml
Update to orchard 0.15.0-pre.2
Merge pull request #2507 from nuttycom/feature/shardtree_explicit_retention
Explicit shardtree checkpoint retention (durable anchors)
Merge pull request #2535 from zcash/adam/pczt-signer-lean-parse
pczt: preverified signing parse for the low-level Signer
zcash_client_backend: add WalletCommitmentTrees::remove_retained_checkpoints_below
Adds a default-implemented `WalletCommitmentTrees` method that releases
retained "anchor" checkpoints with height below a given threshold from both
the Sapling and (when enabled) Orchard note commitment trees, so that anchors
that have aged out of the desired retention window can be pruned normally. The
default implementation is built from the `shardtree` retained-checkpoint
primitives, so all backends get it without bespoke code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: retain anchor checkpoints during scanning
During `put_blocks`, checkpoints established at heights on the
`ANCHOR_RETENTION_INTERVAL` (every 288 blocks, ~4 per day) at or above a
configured activation height are retained as durable anchors via
`ShardTree::ensure_retained`, exempting them from automatic pruning of excess
checkpoints so that their roots and anchored witnesses remain computable.
The retention floor is threaded into `put_blocks`/`update_tree` as an
`Option<BlockHeight>`; the SQLite backend computes it as the NU6.3 (Ironwood)
activation height. Anchor retention is therefore dormant until NU6.3 has an
assigned activation height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: implement explicit shardtree checkpoint retention
Implements the `add_retained_checkpoint` / `remove_retained_checkpoint` /
`retained_checkpoints` `ShardStore` methods for `SqliteShardStore`, backed by
new per-pool `{sapling,orchard}_tree_retained_checkpoints` tables created by a
new migration. Retained checkpoints are exempt from automatic pruning of
excess checkpoints, so their roots and the witnesses anchored to them remain
computable after they age more than `max_checkpoints` behind the chain tip.
Also adds a `remove_retained_checkpoints_below` helper for releasing anchors
older than a given height (wired into the wallet API in a subsequent commit).
Temporarily patches `shardtree`/`incrementalmerkletree` to
zcash/incrementalmerkletree `main`, which carries the retention API merged in
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pczt: reword the inline(never) action-parse comment
Apply @ValarDragon's review suggestion, stating the rationale for the
`#[inline(never)]` per-action parse helper more concisely: it avoids
inlining into a `.map(..).collect()` loop that would compile to a stack
frame tens of KB deep and overflow embedded signer stacks on
high-action-count transactions. Comment-only; no behavior change.
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
supply-chain: point the orchard vet exemption at the pinned rev
The orchard pin bump in e862ecef02 moved the [patch.crates-io] git rev
from 20c10904 to ab7c5fae but left the cargo-vet exemption on the old
rev, so `cargo vet --locked` failed with
orchard:0.15.0-pre.1@git:ab7c5fae... missing ["safe-to-deploy"]
Update the exemption rev to match Cargo.lock. `cargo vet --locked` now
succeeds. The exemption stays temporary and is dropped together with the
[patch] once the orchard change releases.
Merge pull request #2386 from valargroup/dojha/zcash-proofs-readme-sprout
zcash_proofs: scope README to Sprout
Apply suggestion from @ValarDragon
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
pczt: drop Bundle/Action preverified parse forks; bump orchard pin
The upstream orchard signer-only-parse work removed the redundant Bundle
and Action parse_preverified_for_signing wrappers (they were pure
pass-throughs), keeping only the spend-level entry point. Collapse the
matching Action/Bundle `if preverified { .. } else { .. }` branches here
to unconditional Action::parse / Bundle::parse; the `preverified` flag
now selects only at the spend level, the sole place it changes behavior.
Bump the orchard patch to the current head of that branch so the build
tracks the change; the previous pin still referenced the pre-cleanup
orchard that carried the removed wrappers.
Merge pull request #2534 from zcash/dw/ironwood-pool-code-views
zcash_client_sqlite: Report Ironwood outputs as pool code 4
CHANGELOG: Add Ironwood received-output views entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received notes to the output views
Add a migration that recreates the `v_received_outputs` and
`v_received_output_spends` views with an additional branch unioning in the
`ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged
with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`,
`v_tx_outputs`, and the balances derived from them.
Ironwood notes are stored in a table separate from `orchard_received_notes`
because the two pools have distinct note commitment trees and an Orchard action
and an Ironwood action in the same transaction may share an action index, so the
views union the separate tables rather than splitting one table by note version.
Update the canonical `v_received_outputs` / `v_received_output_spends`
definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a
reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The
new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4)
at the same `(transaction_id, action_index)` and asserts both surface distinctly
in `v_received_outputs`.
Adapted from the Ironwood work on
valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Assign the Ironwood shielded pool code
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code`
(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This
is the SQLite `output_pool` code used to distinguish Ironwood outputs from
Orchard outputs, which share the `orchard_received_notes` table.
Adapted from valargroup/librustzcash@d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2533 from zcash/dw/ironwood-scanning
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
zcash_client_sqlite: Fix missing CURRENT_LEAF_MIGRATIONS entries.
Fix formatting and feature-flagging errors.
CHANGELOG: Add Ironwood scan-range extension entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
When Ironwood notes are detected during scanning, extend the suggested scan
ranges to include the blocks needed to complete the Ironwood note commitment
tree subtrees containing those notes, mirroring the existing Sapling and Orchard
handling.
Ironwood notes are stored alongside Orchard notes in `orchard_received_notes`,
but their commitment tree is tracked separately (in the `ironwood_*` tables), so
`extend_range` now takes the tree table prefix directly instead of deriving it
from a `ShieldedPool`. This avoids conflating the note-table pool with the
commitment-tree tables, and lets the Sapling, Orchard, and Ironwood callers pass
their respective prefixes. The Ironwood extension is gated on NU6.3 activation.
Adapted from valargroup/librustzcash@f42001edde and @0af74dd549
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2538 from zcash/kn/ironwood-note-version-migrations
zcash_client_sqlite: Add ironwood_received_notes
CHANGELOG: Add Ironwood received-note storage entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received-note storage
Ironwood notes (ZIP 2005, NU6.3) are Orchard-protocol notes obtained
from version 3 note plaintexts, carried by the Ironwood bundle of a
transaction and committed to the Ironwood note commitment tree. Store
them separately from `orchard_received_notes`: the two pools have
distinct note commitment trees, and an Orchard action and an Ironwood
action in the same transaction may share an action index.
A new migration adds the `ironwood_received_notes` and
`ironwood_received_note_spends` tables, mirroring the corresponding
Orchard tables. `put_received_note` now records a received note in the
table for the pool implied by the note's plaintext version: version 2
note plaintexts can be obtained only under the Orchard note encryption
domain, and version 3 note plaintexts only under the Ironwood domain.
Reading Ironwood notes back for spendability, balance, and view
reporting is not yet implemented; it will accompany Ironwood scanning
support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG: Add Orchard received-note version entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add note version to Orchard received notes
The Orchard protocol revision introduced by NU6.3 (ZIP 2005) versions
Orchard note plaintexts: pre-NU6.3 note plaintexts are version 2, and
Ironwood note plaintexts are version 3. The version determines how the
note commitment trapdoor is derived from the note's rseed, so the
version observed at decryption time must be persisted in order to
reconstruct received notes.
Add a `note_version` column to `orchard_received_notes`, populated from
the note plaintext version reported by the decrypted note itself.
Existing rows are backfilled as version 2, the only version accepted by
the Orchard note encryption domain. Note reconstruction now uses the
stored version instead of assuming version 2. Ironwood notes are not
stored in this table (they belong to a distinct pool with its own note
commitment tree), so the `(transaction_id, action_index)` uniqueness
constraint is unchanged.
Adapted from valargroup/librustzcash@0555be5234 and @d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge pull request #2422 from zcash/feat/propose-transaction-can-spend-transparent-utxos
feat: propose transaction can spend transparent utxos
Merge pull request #2531 from zcash/dw/ironwood-wallet-storage-scanning
zcash_client_sqlite: Add Ironwood note commitment tree storage
Merge branch 'main' into feat/propose-transaction-can-spend-transparent-utxos
zcash_client_backend, zcash_client_sqlite: enforce TransparentSpendPolicy::FromAddresses
select_spendable_transparent_outputs gathered transparent inputs for the
entire account regardless of TransparentSpendPolicy, so FromAddresses
behaved identically to AnyAccountTaddr: a proposal could spend from (and
link) addresses the caller explicitly excluded. Post-gather filtering is
not sufficient, since excluded UTXOs would consume the value bound and
under-fill the result; the address restriction must be applied within
the gather itself.
Add an address_allow_list parameter to select_spendable_transparent_outputs,
applied inside the SQL query (via the same always-bound-flag idiom already
used for coinbase_filter) so ineligible outputs never consume the value
bound. GreedyInputSelector wires this from TransparentSpendPolicy at both
gather sites: the initial gather and the InsufficientFunds re-gather
fallback (which had the same bug independently).
Also refactors NonEmptyBTreeSet to wrap a single BTreeSet with proper
invariant-preserving constructors (singleton, from_nonempty, from_set)
and accessors (as_set, iter) instead of exposing raw head/tail fields,
and adds persistent transparent_dust tracking across the propose_transaction
retry loop so a re-gather triggered by InsufficientFunds does not
reintroduce transparent inputs already pruned as dust by a prior
DustInputs iteration.
Strengthens propose_t2t_from_addresses to actually catch the bug: the
unnamed address's UTXO now holds more value than the named address's
and is inserted first, so an unenforced policy would select it (since
it alone covers the payment) instead of the named address, making the
test fail without the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pczt: preverified signing parse for the low-level Signer
Parse the low-level Signer's bundle with orchard's
`parse_preverified_for_signing`, which skips deriving each spend's
`FullViewingKey` from its wire `fvk` bytes. The spend authorization
signature does not depend on `fvk`, so signatures are byte-identical to
the full parse.
`sign_orchard_with` and `sign_ironwood_with` snapshot each spend's
`(rk, fvk)` before parsing and restore the `fvk` after serialization so
signing leaves the wire bytes unchanged. The restore returns
`OrchardParseError::SigningClosureModifiedActions` if the closure changes
the action count or reorders actions, detected by the per-position `rk`,
and makes no changes when it rejects.
Refactor the Orchard action parse from `.map(...).collect()` into a
bounded `#[inline(never)]` loop so a constrained signer does not overflow
its task stack. Behaviorally identical; the full `parse` used by the
Verifier, Prover, and Updater is unchanged.
Temporarily patches orchard to the branch adding
`parse_preverified_for_signing`, with a matching cargo-vet exemption;
both are dropped for a version bump once that orchard change releases.
Merge pull request #2517 from nuttycom/fix/upgrade-imported-transparent-address
zcash_client_sqlite: Upgrade imported transparent addresses to their derived form on gap-limit derivation
zcash_client_backend, zcash_client_sqlite: document the transparent input-count cap in CHANGELOGs
Update the still-unreleased select_spendable_transparent_outputs and
with_shielding_block_space_percent entries to describe the max_inputs
parameter and its dual use for shielding and general transfers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: cap transparent inputs gathered for general transfers
InputSource::select_spendable_transparent_outputs now takes a max_inputs
parameter bounding the number of transparent outputs the gather may
select, independent of target_value. Without this, a wallet holding a
very large number of small (e.g. dust) UTXOs could require an unbounded
number of inputs to satisfy even a modest request, producing an
oversized transaction.
GreedyInputSelector::propose_transaction wires this to the existing
shielding_block_space_percent config (via shielding_max_inputs), reusing
the same block-space bound already used to cap shielding transactions
rather than introducing a second configuration knob. When the cap is
reached before the requested value, the caller's input-selection loop
surfaces this the same as any other value shortfall: an InsufficientFunds
error.
The SQLite implementation enforces the cap inside the gather loop itself
(breaking out once max_inputs outputs have been accumulated), rather than
gathering an unbounded set and truncating afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: fix invalid migration UUID and add missing verify_schema entry
add_transparent_value_index's MIGRATION_ID was a hand-typed UUID whose
version nibble was 6, not 4, making it invalid per the CI 'uuid' job's
random/v4 check (.github/workflows/ci.yml). Replace it with a properly
generated v4 UUID.
Separately, the migration's new idx_transparent_received_outputs_value_zat
index was never added to wallet::init::tests::verify_schema's expected
index list, nor given a db.rs constant, causing that test to fail with a
positional mismatch against every index sorted after it. Add the missing
db::INDEX_TRANSPARENT_RECEIVED_OUTPUTS_VALUE_ZAT constant and list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Address review comments on duplicate-receiver resolution
- Decode key scopes via KeyScope::decode and match on the enum rather than raw
integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
cross-account duplicate whose ephemeral record reproduces the receiver is resolved
in its favor rather than aborting. Only Foreign records remain unverifiable, having
no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: depend on fix_transparent_received_outputs, not utxos_to_txos, for the value_zat index migration
fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.
Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.
Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: adapt to upstream API changes from rebase onto main
Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:
- Update the remaining ShieldedProtocol::Sapling references (added by
this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
pass 0 from the transparent-only gather in
select_spendable_transparent_outputs, matching the existing 0 passed
for sapling_input_count/sapling_output_count/orchard_action_count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Upgrade receivers imported under a different account on derivation
The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Resolve cross-account duplicate receivers by derivation
A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: CHANGELOG for imported→derived transparent address upgrade
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Preserve earliest exposed_at_height when merging duplicate receivers
The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Upgrade imported transparent receiver in place on derivation
When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: fix changelog/doc self-review findings
- Move two changelog entries that described this PR's new work out of
already-released, dated version sections (zcash_client_backend's
[0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
Unreleased/PLANNED sections. Released changelog sections must remain
immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
discarded intermediate designs (an unbounded gather, an
Option<Zatoshis> static-fee-headroom parameter) as though they were
prior shipped behavior. Since select_spendable_transparent_outputs is
entirely new and unreleased, changelog/doc text should describe only
the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
AGENTS.md conventions file, meaningless to external readers of the
public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
non-empty BTreeSet") and document its previously-undocumented public
fields; drop a private implementation note about possibly moving it to
another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
which controls the same privacy behavior the method's doc already
describes but was never mentioned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: fix spurious InsufficientFunds when transparent re-gather succeeds without new shielded notes
GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.
Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.
Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: make transparent gather fee-aware
InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.
Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).
Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: add value-bounded transparent input gather
Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.
The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.
GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: extract shared spendable-transparent-outputs query builder
Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.
This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).
Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: Remove gather_account_transparent_inputs in favor of batched query
`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce verbosity in propose_transaction docs and related types
better documentation and testing for propose_shielding transparent UTXO spending functionality
added variant CoinbaseFilter::NonCoinbaseOnly to remove coinbase from propose_transaction
rename TransparentOutputFilter and better names for TransparentSpendPolicy methods
add tests and changelog for transparent spending in propose_transaction
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add transparent spending to propose_transaction
Co-Authored-By: fdecroix <fdecroix@eryx.co>
CHANGELOG: Add Ironwood note commitment tree storage entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2508 from zcash/adam/ironwood-wallet-fees
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Adapt Ironwood fee wiring to the new tx-builder bundle-version API
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.
Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.
Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
Merge pull request #2498 from zcash/adam/ironwood-tx-builder-completion
zcash_client_backend: Wire Ironwood anchors into wallet transaction building
wallet: revert unnecessary change
CHANGELOG: mv with_ironwood_tree_mut to appropriate changed section
Assert exact Ironwood fee values; document the routing flag
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
Route Orchard-pool change into the Ironwood bundle to match the builder
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.
Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.
Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.
Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Note the compute_balance Ironwood view in the changelog
Charge the Ironwood bundle at proposal time
Populate the Ironwood fee view introduced previously: when the builder
will route Orchard-pool outputs into a separate Ironwood bundle, mirror
that split during input selection so the proposal's per-bundle action
counts (and therefore the fee) match the transaction that gets built.
The routing decision reuses the builder's own `orchard_outputs_to_ironwood`
predicate (threading the same `proposed_version`), so the proposal and
build paths cannot drift. The Orchard bundle keeps the Orchard spends; its
outputs move to the Ironwood bundle when routing is active. Ironwood has
no spends yet, as the wallet does not select Ironwood notes until note
detection lands.
Adds a zip317 test asserting that a populated Ironwood view increases the
required fee (the Ironwood bundle's actions are counted independently of
Orchard).
Thread a separate Ironwood bundle view through the fee/change path
V6 transactions carry separate Orchard and Ironwood bundles, each padded
to its own action floor, and `FeeRule::fee_required` already takes a
separate `ironwood_action_count`. Populate that count from a dedicated
Ironwood `BundleView` rather than the previous hardcoded zero.
`ChangeStrategy::compute_balance` and the `common.rs` helpers
(`calculate_net_flows`, `single_pool_output_balance`) gain an `ironwood`
view parameter alongside `orchard` (gated on `feature = "orchard"`,
mirroring it), `NetFlows` gains `ironwood_in`/`ironwood_out`, and the
Ironwood action count is computed via
`transactional_action_count(ironwood.bundle_version(), ...)`.
All call sites currently pass an empty Ironwood view, so this is a no-op:
the wallet does not yet route inputs/outputs to the Ironwood pool at
proposal time. A follow-up populates the view from the same routing
decision the builder uses, so the separate Ironwood bundle is priced.
Verified: compiles under default features and the nu7 cfg; the 11
zip317/fixed change-strategy tests pass unchanged.
Address review findings: routing enum, dedup, docs, expect
Model the per-step Orchard/Ironwood routing as an `OrchardBuildMode` enum
computed once, replacing the three booleans and four-arm if-else. The
spend-anchor, Ironwood inputs, and output-routing decisions now derive
from a single value so they cannot drift apart, and the `LegacyV5 +
Ironwood spends` rejection is expressed at classification time.
Replace the `ironwood_spends_requested implies shielded inputs` `expect`
with a `let-else` that returns `ProposalNotSupported` rather than
panicking on the (unreachable) `None`.
De-duplicate the cfg-gated Orchard change-output block so the Orchard
path is written once, gated by a `change_to_ironwood` flag that is
`false` in builds without Ironwood.
Document `stored_ironwood_output_index`: V6 stores Orchard and Ironwood
outputs in one index space with Orchard actions first.
Wire Ironwood anchors into wallet transaction building
Merge pull request #2522 from zcash/chore/shielded_protocol_rename
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool` and add `ShieldedPool::Ironwood`
Merge pull request #2520 from zcash/feat/pczt_ironwood_completion
pczt: Complete branch-aware v6/Ironwood support
Merge pull request #2521 from nuttycom/fix/put-transparent-output-preserves-mined-height
zcash_client_sqlite: Don't clear a recorded mined_height when storing a transparent output with unknown height
pczt: Generalize bundle version derivation over the value pool
`Creator::orchard_bundle_version()` becomes `bundle_version(pool)`, and
the crate-level revision-to-version mapping takes the `ValuePool` as an
argument (mirroring `zcash_primitives`'s `bundle_version_for_branch`),
so both the Orchard and Ironwood bundle versions are derived from the
consensus branch ID and pool alone. `with_ironwood_flags` now expresses
its version-support check as pool availability rather than a
transaction-version comparison.
Also restructure the transaction-version validation in
`extract_tx_data` as a single exhaustive match, and apply two
comment-wording suggestions in the v1 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Derive the Creator's Orchard bundle version instead of storing it
The Creator now stores the validated `BranchId` (rather than the raw
consensus branch ID) and derives the Orchard-pool bundle version from it
on demand, removing a stored field that was a pure function of state the
Creator already held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add high-level Ironwood methods to the Signer, Verifier, and Updater
Mirrors of the corresponding Orchard-pool methods:
- `Signer::{sign_ironwood, apply_ironwood_signature}` (with new
`Error::{IronwoodSign, IronwoodVerify}` variants), so
hardware-wallet-style signers no longer need the low-level Signer to
authorize Ironwood actions.
- `Verifier::with_ironwood`
- `Updater::update_ironwood_with`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add v6/Ironwood support to the Creator and validate at extraction
- `Creator::new` now derives the transaction version and version group
ID from the consensus branch ID: the v6 format from NU6.3 onward, and
the v5 format for earlier upgrades. Previously the Creator could only
produce v5 PCZTs.
- New `Creator::with_ironwood_anchor` and `Creator::with_ironwood_flags`
methods configure the Ironwood bundle independently of the Orchard
bundle; both reject configuration of an Ironwood bundle for a
transaction version that does not carry one
(`Error::IronwoodNotSupported`).
- Transaction extraction now rejects v6 PCZTs whose consensus branch ID
predates NU6.3, and non-v6 PCZTs carrying non-canonical Ironwood
bundle data (`ExtractError::IronwoodNotSupported`), instead of
silently dropping the Ironwood data or extracting a consensus-invalid
transaction.
- The v1 serialization format refuses to encode v6 PCZTs, which a v1
parser could decode but never extract a transaction from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Canonicalize the empty states of the PCZT bundles
The Creator, `Creator::build_from_parts`, the v1 and v2 serialization
formats, and the IO Finalizer previously produced four different
representations of an "empty" Ironwood bundle (and inconsistent empty
Sapling/Orchard bundles), which broke `Bundle::merge` for workflows that
mix serialization-round-tripped and in-memory copies of the same PCZT.
Each bundle type now has a single canonical empty representation,
defined as a constant in its module and used consistently everywhere:
- The Creator's Ironwood placeholder no longer mirrors the Orchard
bundle's flags and anchor; it is exactly the canonical empty bundle
(whose flags are the Ironwood defaults: spends, outputs, and
cross-address transfers enabled).
- `Creator::build_from_parts` uses the canonical empty bundles (with
all-zeroes anchors, rather than the empty-tree root) for absent
bundles, and now maps `parts.ironwood` through instead of discarding
it, so the builder-to-PCZT path no longer loses Ironwood spends and
outputs.
- Empty bundle value sums are initialized as non-negative zero,
matching the form produced by re-serializing a parsed bundle.
- The IO Finalizer no longer sets `bsk` on an empty Orchard-protocol
bundle. (The Sapling bundle is still always finalized, because the
Sapling Transaction Extractor requires `bsk` even for an empty
bundle.)
- The v2 format's omission decision is now exact equality with the
slot's canonical empty bundle, so a bundle with non-default flags or
note version round-trips losslessly instead of being restored with
default values; the v1 format refuses to encode a PCZT whose Ironwood
bundle is not canonically empty, rather than silently dropping its
non-default fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Don't clear mined_height when storing an output with unknown height
put_transparent_output's transactions upsert overwrote mined_height unconditionally,
so re-storing a transparent output whose mined height is unknown to the caller (e.g.
one re-observed via the mempool, or fetched from a backend that could not locate the
transaction on the best chain) clobbered a previously-recorded mined height with NULL,
marking a mined transaction as unmined. A NULL height carries no evidence that the
recorded height is wrong; preserve it, matching the adjacent handling of the block
column. Genuine un-mining remains the responsibility of truncate_to_height.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_protocol: Add `ShieldedPool::Ironwood`
This introducese `todo!(...)` stanzas in each location in
`zcash_client_backend` and `zcash_client_sqlite` where Ironwood
support infrastructure has not yet been implemented.
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool`
It is no longer appropriate to refer to the Orchard "shielded protocol"
as there are three different variants of the Orchard shielded protocol.
`zcash_protocol::ShieldedProtocol` has historically been used as a pool
identifier; the rename here reifies this change.
Merge pull request #2516 from zcash/fix/pr2509_review_followup
zcash_primitives: Address post-merge review comments from PR #2509
zcash_client_backend: Derive the PCZT test proving key from the consensus branch
The `pczt_single_step` pool test hardcoded the post-NU6.2 fixed-circuit
proving key, which was consistent with the PCZT roles' previous pinning
of the legacy Orchard bundle version but mismatches the branch-derived
parsing, proving, and verification those roles now perform: the shared
test network fixture's most recent upgrade is NU5, under which the
Orchard pool is governed by the historical pre-NU6.2 circuit, so proof
verification failed at extraction. Derive the proving key from the
PCZT's own consensus branch ID instead. (Modernizing the test network
fixture so that wallet tests exercise current consensus is part of the
Ironwood test coverage work tracked in #2518.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add OrchardProtocolRevision to zcash_protocol; make all PCZT roles branch-aware
The consensus knowledge of which Orchard protocol revision each network
upgrade deploys now lives in a single place:
`zcash_protocol::consensus::BranchId::orchard_protocol_revision`, which
returns the new `OrchardProtocolRevision` enum (`None` for branches that
predate NU5). `zcash_primitives`'s `bundle_version_for_branch` and the
`pczt` crate both derive `orchard::bundle::BundleVersion`s from it via
trivial conversions, replacing the duplicated branch-to-version matches.
(`pczt` cannot reuse the `zcash_primitives` helper directly because its
`zcash_primitives` dependency is optional and must not be required by
the lightweight role builds.)
Building on this, the remaining PCZT roles (Updater, Verifier, Prover,
and the low-level Signer) now parse the Orchard-pool bundle using the
bundle version implied by the PCZT's consensus branch ID rather than
pinning the legacy pre-NU6.3 version, completing the branch-aware
parsing work begun with `extract_tx_data`; in particular, the Prover now
creates proofs for the correct circuit for post-NU6.3 Orchard-pool
bundles. Roles report new `UnsupportedConsensusBranchId` errors when the
branch ID is unrecognized or predates NU5, and the low-level Signer's
Orchard-protocol signing methods now bound their error parameter by
`From<OrchardParseError>`. `Bundle::into_orchard_parsed`, which pinned
the legacy version, is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address remaining review comments from PR #2509
- pczt: fix extraction and verification of post-NU6.3 Orchard-pool
bundles. `extract_tx_data` now parses the Orchard-pool bundle using
the bundle version implied by the PCZT's consensus branch ID rather
than pinning the legacy pre-NU6.3 version, and the Transaction
Extractor's bundle verification derives the circuit version from the
bundle itself rather than hardcoding the NU6.2 fixed circuit (which
could not verify post-NU6.3 Orchard-pool bundles). The
`verify_bundle`/`verify_ironwood_bundle` duplication collapses into a
single function. Other PCZT roles still parse the Orchard-pool bundle
with the legacy version pin; completing those is tracked as follow-up
work.
- zcash_primitives: note the ZIP 229 specification of the v6 Sapling
digest variants in the `txid.rs` documentation, and correct the
`BlockTxCommitmentDigester::HeaderDigest` doc comment.
- zcash_client_backend: add a ZIP 317 change-calculation test covering
the post-NU6.3 Orchard action-count policy (one action per spend and
per output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive Orchard-protocol bundle versions from consensus context
Review followups:
- Add `bundle_version_for_branch`, returning the `BundleVersion` in
effect for a given Orchard-protocol value pool under a given consensus
branch, or `None` when the pool is not supported under that branch
(the Orchard pool prior to NU5; the Ironwood pool prior to NU6.3).
- `read_v5_bundle` now takes the consensus branch ID, and
`read_v6_bundle` the consensus branch ID and the `orchard::ValuePool`
identifying the bundle slot to read, in place of caller-supplied
`orchard::bundle::BundleVersion` arguments. Callers can no longer
select a wrong-slot bundle version, and a non-empty bundle belonging
to a pool that is unsupported under the transaction's consensus branch
ID is rejected as invalid data.
- In `Builder::new`, the `None` result subsumes the explicit
NU5-activation check that previously gated construction of the Orchard
builder.
- Remove `zcash_client_backend::ANY_ORCHARD_BUNDLE_VERSION`. The fee and
change calculation call sites that used it all have access to the
proposal's target height, and now derive the Orchard bundle version
(and hence the action-count policy) from it. As a consequence,
proposals targeting heights at or beyond NU6.3 activation now count
one action per Orchard spend or output, matching the post-NU6.3
transaction builder. `EmptyBundleView` retains a fixed version, which
is sound because an empty bundle produces zero actions under every
version's action-count policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_primitives: Address post-merge review comments from PR #2509
- `read_v5_bundle` now takes the consensus branch ID, deriving the
Orchard bundle version via the new public helper
`orchard_bundle_version_for_branch` (an exhaustive match, replacing
both the wildcard-arm private helper in the builder and the inline
mapping in `Transaction::read_v5`); this also removes an incomplete
sentence from its documentation.
- Make `TxVersion::has_ironwood` an exhaustive match.
- Simplify the `arb_txdata` proptest strategies:
`arb_ironwood_bundle_for_version` already yields `None` for versions
without Ironwood support.
- Factor out `Builder::orchard_in_use` to mirror `ironwood_in_use`.
- Correct the coinbase Orchard-builder flag comment, strengthen the
`with_expiry_height` warning, and fix stale `txid.rs` documentation.
- Give NU6.3 and NU7 distinct activation heights in the nu7 builder
test network.
- Add a test checking that Orchard note commitments and nullifiers are
domain-separated by the note plaintext version (lead byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address issues found in review of the NU6.3 testnet support branch
- `zcash_protocol`: under `zcash_unstable = "nu7"`, `BranchId::Nu6_3` now
reports the NU7 activation height as the upper bound of its height
range. The cfg-gated upper-bound pattern was dropped from the `Nu6_2`
arm when the `Nu6_3` arm was introduced, causing the Nu6_3 and Nu7
epochs to overlap in nu7 builds.
- `zcash_primitives`: the transaction builder's version-compatibility
check now accounts for Orchard change outputs, matching the Ironwood
check. Previously a change-only Orchard bundle passed the compatibility
check for `TxVersion::V4`, resulting in a reachable panic in `build()`.
- `zcash_primitives`: document previously-omitted API changes in the
0.29.0-pre.0 CHANGELOG entry (`map_bundles`/`try_map_bundles` closure
semantics, `TransactionDigest::combine` `ironwood_digest` argument,
`BlockTxCommitmentDigester::HeaderDigest` type change,
`BuildConfig::orchard_builder_config` removal,
`TxVersion::has_ironwood`).
- `zcash_transparent`: restore Keep-a-Changelog section ordering in the
0.9.0-pre.0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply minor review suggestions from PR #2509
- Fix the `expected_nu7` regtest activation heights to differ from the
NU6.3 height, assert NU6.3 activation at its own height, and check NU7
non-activation against the NU6.3 height in the `zcash_protocol`
`local_consensus` tests.
- Remove the stale placeholder-branch-ID paragraph from the
`BranchId::Nu6_3` docs.
- Simplify the `BranchId::Nu6_2` height-bounds arm to …
dannywillems
pushed a commit
that referenced
this pull request
Jul 7, 2026
Relying upon the note plaintext version to discriminate between Orchard
and Ironwood notes works, but it complicates reasoning about the code
and makes it difficult to understand where orchard-the-protocol is being
used vs orchard-the-pool. By adding value pool metadata to wallet
orchard-protocol notes, we thoroughly disambiguate; it's entirely
possible that in the future another note plaintext version will be
introduced (for example, for the note plaintexts of memo bundles) that
does not change the pool, and this refactoring makes such updates
straightforward to deal with.
Also lifts note pool classification to Step (Note::pool, Step input-pool predicates and counters, and Proposal::input_count_in_pool), with tests for the proposal pool-input helpers.
zcash_client_sqlite: Drop a non-leaf from CURRENT_LEAF_MIGRATIONS
`ironwood_shardtree` was listed in `CURRENT_LEAF_MIGRATIONS`, but
`witness_stabilized_notes` depends on it, so it is not a leaf of the migration
dependency graph. Migrating to the current state still reached it transitively,
so this was harmless, but the constant is documented to hold exactly the graph
leaves. Remove the stale entry and add a test that recomputes the leaves from the
dependency graph and checks them against the constant, so this cannot regress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Include Ironwood notes in the account balance
Add an Ironwood balance component to `AccountBalance`, alongside the Sapling and
Orchard components, with an `ironwood_balance` accessor and a
`with_ironwood_balance_mut` method. The Ironwood balance is included in the
account's total, spendable, pending-confirmation, pending-spendability, and
uneconomic values.
`get_wallet_summary` now computes the Ironwood balance from
`ironwood_received_notes` via the existing pool-generic `with_pool_balances`, so
received Ironwood notes are reflected in the wallet balance. The end-to-end
storage test now asserts the balance equals the received note value.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Decrypt Ironwood outputs with the Ironwood domain
Ironwood notes carry version 3 note plaintexts, which are decrypted under the
Ironwood note-encryption domain (`orchard::note_encryption::IronwoodDomain`).
That domain is distinct from `OrchardDomain`, which accepts only version 2
plaintexts and would reject every Ironwood note. The scanner previously aliased
`IronwoodDomain` to `OrchardDomain`, so it would have silently failed to detect
any Ironwood output; this corrects the alias to the real Ironwood domain.
As a consequence an Ironwood batch is no longer type-identical to an Orchard
batch, so the compact- and full-block `BatchRunners` gain a distinct
`IronwoodTasks` type parameter alongside the Sapling and Orchard task types, and
a `ScanningKeyOps<IronwoodDomain, ..>` implementation lets the account's Orchard
viewing key trial-decrypt under the Ironwood domain. The Ironwood runners and
scan paths now construct their domains with `IronwoodDomain::for_compact_action`
/ `IronwoodDomain::for_action`.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds tests: scanning detects Ironwood notes across the compact and full scanning paths, and received Ironwood notes round-trip end-to-end through zcash_client_sqlite storage.
zcash_client_backend: Persist the Ironwood note commitment tree
Build and persist the Ironwood note commitment tree during `put_blocks`,
mirroring the Orchard handling but keeping Ironwood a distinct pool. The
Ironwood tree is Orchard-shaped, so it reuses the Orchard shard height and is
updated through `with_ironwood_tree_mut`; the Ironwood block metadata
(`ironwood_commitment_tree_size` and `ironwood_action_count`) is now written to
the `blocks` table via `put_block_meta`.
Checkpoint reconciliation across the note commitment trees is generalized from
two pools to three: each tree gains a checkpoint at every height that is
checkpointed in any other pool, computed as the union of the other pools'
checkpoint heights. When Ironwood has no checkpoints this reduces exactly to the
prior Sapling/Orchard reconciliation, so existing behavior is unchanged; the
Ironwood tree gains empty checkpoints at the scanned heights, as the Orchard
tree already does before its own activation.
With the tree persisted and the block Ironwood tree size recorded, received
Ironwood notes now have witness data and are spendable, and the note positions
computed by the scanner are backed by a built tree.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also property-tests cross-pool checkpoint reconciliation:
Extract the three-pool checkpoint-height reconciliation in `put_blocks` into a
pure `cross_pool_ensure_heights` helper and add proptests for it. The tests
assert the key invariant that after reconciliation every pool is checkpointed at
exactly the union of all pools' checkpoint heights (so the note commitment trees
stay aligned for cross-pool rewinds), and that with no Ironwood checkpoints the
Sapling/Orchard reconciliation is unchanged from the prior two-pool behavior.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add the Ironwood note commitment tree to ChainState
Add a separate Ironwood note commitment tree frontier to `ChainState`, parallel
to the Sapling and Orchard frontiers, with a `final_ironwood_tree` accessor and
wiring through `new`/`empty`. Parse it from the gRPC `TreeState.ironwood_tree`
field via a new `TreeState::ironwood_tree` method; an absent field yields an
empty tree, the correct Ironwood treestate at pool activation. This is the
prior chain state that the Ironwood commitment tree will be built from, added
ahead of the Ironwood tree write path. The Ironwood tree is Orchard-shaped but
is tracked as a distinct pool.
Also complete the Ironwood threading exposed under `--all-features`: the
`sync` decryptor's `BatchRunners::for_keys` callers now pass the Ironwood batch
threshold (reusing the Orchard threshold, as Ironwood outputs are
Orchard-shaped), and the block-metadata query row, widened with the Ironwood
tree size, is factored into a `BlockMetadataRow` type alias.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Persist received Ironwood notes and nullifiers
Store received Ironwood notes during scanning and track Ironwood nullifiers for
spend detection, mirroring the Orchard receive path but keeping Ironwood a
separate pool. `put_blocks` now feeds the scanner's Ironwood output stream to a
dedicated `put_received_ironwood_note` (writing to `ironwood_received_notes`),
detects earlier spends via `detect_ironwood_spend`, records the block's Ironwood
nullifiers with `track_block_ironwood_nullifiers`, and marks the wallet's own
spent Ironwood notes with `mark_ironwood_note_spent`. `Nullifiers::unspent` and
`WalletRead::get_ironwood_nullifiers` complete the spend-detection loop.
Storage no longer routes by note plaintext version: `put_received_note` takes
the target table prefix from the caller, so the scanner routes its separate
Orchard and Ironwood output streams to their respective tables, while the
decrypted-transaction path selects the table from the note version as before.
The shared spent-note marking is factored into a prefix-parameterized helper.
This does not build the Ironwood commitment tree (that needs an Ironwood
frontier in `ChainState`, added separately), so no non-zero Ironwood tree size
is written to `blocks` and received Ironwood notes are not yet spendable. The
send-path Ironwood todos are left for a later increment.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Track Ironwood outputs in the block scanner
Extend the compact- and full-block scanners to trial-decrypt Ironwood outputs
and track the Ironwood note commitment tree, mirroring the existing Orchard
handling but keeping Ironwood as a distinct pool. `BatchRunners` feed the
Ironwood runner from `CompactTx.ironwood_actions` / the transaction's Ironwood
bundle, `PositionTracker` tracks the Ironwood tree position and final size
(activating at NU6.3 and reconciling against `ChainMetadata`'s Ironwood tree
size), and `scan_block_with_runners` collects Ironwood spends, outputs, note
commitments, and nullifiers into a separate `ScannedBundles`.
`ScannedBlock`, `ScannedBlockCommitments`, `BlockMetadata`, `WalletTx`, and
`Nullifiers` gain parallel Ironwood fields and accessors, and the sqlite
`block_metadata`/`block_max_scanned` queries read the persisted Ironwood tree
size. Ironwood note commitments are Orchard-shaped, so the Orchard commitment,
nullifier, and wallet-output types are reused through Ironwood-named aliases;
the two pools remain separate throughout.
This computes the Ironwood scan results but does not yet persist them: the
wallet does not query Ironwood nullifiers (so Ironwood spend detection is inert)
and `put_blocks` does not write received Ironwood notes or update the Ironwood
commitment tree. Those are added with the Ironwood write path.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add Ironwood scanning keys
Add a separate `ironwood` key set to `ScanningKeys`, parallel to the Sapling
and Orchard key sets, with an `ironwood()` accessor and wiring through `new`,
`empty`, and `from_account_ufvks`. Ironwood outputs are Orchard-shaped and are
trial-decrypted under the Orchard note-encryption domain using the account's
Orchard viewing keys, so the Ironwood keys are populated from the Orchard full
viewing key; the `IronwoodDomain`/`IronwoodNullifier` aliases name that reuse of
the underlying Orchard primitives.
Ironwood is kept strictly separate from Orchard as its own pool: the shared
primitives are an implementation detail expressed through aliases, not a design
in which Ironwood belongs to an "Orchard family".
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds the Ironwood compact batch runner:
Add a separate `ironwood` batch runner to the compact-block `BatchRunners`,
parallel to the Sapling and Orchard runners, wired through `for_keys` and
`flush`. Because an Ironwood batch is type-identical to an Orchard batch
(`IronwoodDomain` aliases `OrchardDomain` and the action type is shared), the
Orchard task type already satisfies the runner's bound, so no separate task
type is introduced; the `TaggedIronwoodBatchRunner` alias only names the runner
as belonging to the Ironwood pool.
The runner is constructed from the Ironwood scanning keys and flushed, but is
not yet fed block outputs or collected, so scanning behavior is unchanged. The
decryption of `CompactTx.ironwood_actions` is added in a later change together
with the commitment-tree position tracking that consumes its results.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2545 from zcash/fix/broken_patch_directive
Fix duplicate patch dependency in Cargo.toml
Update to orchard 0.15.0-pre.2
Merge pull request #2507 from nuttycom/feature/shardtree_explicit_retention
Explicit shardtree checkpoint retention (durable anchors)
Merge pull request #2535 from zcash/adam/pczt-signer-lean-parse
pczt: preverified signing parse for the low-level Signer
zcash_client_backend: add WalletCommitmentTrees::remove_retained_checkpoints_below
Adds a default-implemented `WalletCommitmentTrees` method that releases
retained "anchor" checkpoints with height below a given threshold from both
the Sapling and (when enabled) Orchard note commitment trees, so that anchors
that have aged out of the desired retention window can be pruned normally. The
default implementation is built from the `shardtree` retained-checkpoint
primitives, so all backends get it without bespoke code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: retain anchor checkpoints during scanning
During `put_blocks`, checkpoints established at heights on the
`ANCHOR_RETENTION_INTERVAL` (every 288 blocks, ~4 per day) at or above a
configured activation height are retained as durable anchors via
`ShardTree::ensure_retained`, exempting them from automatic pruning of excess
checkpoints so that their roots and anchored witnesses remain computable.
The retention floor is threaded into `put_blocks`/`update_tree` as an
`Option<BlockHeight>`; the SQLite backend computes it as the NU6.3 (Ironwood)
activation height. Anchor retention is therefore dormant until NU6.3 has an
assigned activation height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: implement explicit shardtree checkpoint retention
Implements the `add_retained_checkpoint` / `remove_retained_checkpoint` /
`retained_checkpoints` `ShardStore` methods for `SqliteShardStore`, backed by
new per-pool `{sapling,orchard}_tree_retained_checkpoints` tables created by a
new migration. Retained checkpoints are exempt from automatic pruning of
excess checkpoints, so their roots and the witnesses anchored to them remain
computable after they age more than `max_checkpoints` behind the chain tip.
Also adds a `remove_retained_checkpoints_below` helper for releasing anchors
older than a given height (wired into the wallet API in a subsequent commit).
Temporarily patches `shardtree`/`incrementalmerkletree` to
zcash/incrementalmerkletree `main`, which carries the retention API merged in
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pczt: reword the inline(never) action-parse comment
Apply @ValarDragon's review suggestion, stating the rationale for the
`#[inline(never)]` per-action parse helper more concisely: it avoids
inlining into a `.map(..).collect()` loop that would compile to a stack
frame tens of KB deep and overflow embedded signer stacks on
high-action-count transactions. Comment-only; no behavior change.
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
supply-chain: point the orchard vet exemption at the pinned rev
The orchard pin bump in e862ecef02 moved the [patch.crates-io] git rev
from 20c10904 to ab7c5fae but left the cargo-vet exemption on the old
rev, so `cargo vet --locked` failed with
orchard:0.15.0-pre.1@git:ab7c5fae... missing ["safe-to-deploy"]
Update the exemption rev to match Cargo.lock. `cargo vet --locked` now
succeeds. The exemption stays temporary and is dropped together with the
[patch] once the orchard change releases.
Merge pull request #2386 from valargroup/dojha/zcash-proofs-readme-sprout
zcash_proofs: scope README to Sprout
Apply suggestion from @ValarDragon
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
pczt: drop Bundle/Action preverified parse forks; bump orchard pin
The upstream orchard signer-only-parse work removed the redundant Bundle
and Action parse_preverified_for_signing wrappers (they were pure
pass-throughs), keeping only the spend-level entry point. Collapse the
matching Action/Bundle `if preverified { .. } else { .. }` branches here
to unconditional Action::parse / Bundle::parse; the `preverified` flag
now selects only at the spend level, the sole place it changes behavior.
Bump the orchard patch to the current head of that branch so the build
tracks the change; the previous pin still referenced the pre-cleanup
orchard that carried the removed wrappers.
Merge pull request #2534 from zcash/dw/ironwood-pool-code-views
zcash_client_sqlite: Report Ironwood outputs as pool code 4
CHANGELOG: Add Ironwood received-output views entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received notes to the output views
Add a migration that recreates the `v_received_outputs` and
`v_received_output_spends` views with an additional branch unioning in the
`ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged
with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`,
`v_tx_outputs`, and the balances derived from them.
Ironwood notes are stored in a table separate from `orchard_received_notes`
because the two pools have distinct note commitment trees and an Orchard action
and an Ironwood action in the same transaction may share an action index, so the
views union the separate tables rather than splitting one table by note version.
Update the canonical `v_received_outputs` / `v_received_output_spends`
definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a
reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The
new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4)
at the same `(transaction_id, action_index)` and asserts both surface distinctly
in `v_received_outputs`.
Adapted from the Ironwood work on
valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Assign the Ironwood shielded pool code
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code`
(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This
is the SQLite `output_pool` code used to distinguish Ironwood outputs from
Orchard outputs, which share the `orchard_received_notes` table.
Adapted from valargroup/librustzcash@d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2533 from zcash/dw/ironwood-scanning
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
zcash_client_sqlite: Fix missing CURRENT_LEAF_MIGRATIONS entries.
Fix formatting and feature-flagging errors.
CHANGELOG: Add Ironwood scan-range extension entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
When Ironwood notes are detected during scanning, extend the suggested scan
ranges to include the blocks needed to complete the Ironwood note commitment
tree subtrees containing those notes, mirroring the existing Sapling and Orchard
handling.
Ironwood notes are stored alongside Orchard notes in `orchard_received_notes`,
but their commitment tree is tracked separately (in the `ironwood_*` tables), so
`extend_range` now takes the tree table prefix directly instead of deriving it
from a `ShieldedPool`. This avoids conflating the note-table pool with the
commitment-tree tables, and lets the Sapling, Orchard, and Ironwood callers pass
their respective prefixes. The Ironwood extension is gated on NU6.3 activation.
Adapted from valargroup/librustzcash@f42001edde and @0af74dd549
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2538 from zcash/kn/ironwood-note-version-migrations
zcash_client_sqlite: Add ironwood_received_notes
CHANGELOG: Add Ironwood received-note storage entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received-note storage
Ironwood notes (ZIP 2005, NU6.3) are Orchard-protocol notes obtained
from version 3 note plaintexts, carried by the Ironwood bundle of a
transaction and committed to the Ironwood note commitment tree. Store
them separately from `orchard_received_notes`: the two pools have
distinct note commitment trees, and an Orchard action and an Ironwood
action in the same transaction may share an action index.
A new migration adds the `ironwood_received_notes` and
`ironwood_received_note_spends` tables, mirroring the corresponding
Orchard tables. `put_received_note` now records a received note in the
table for the pool implied by the note's plaintext version: version 2
note plaintexts can be obtained only under the Orchard note encryption
domain, and version 3 note plaintexts only under the Ironwood domain.
Reading Ironwood notes back for spendability, balance, and view
reporting is not yet implemented; it will accompany Ironwood scanning
support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG: Add Orchard received-note version entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add note version to Orchard received notes
The Orchard protocol revision introduced by NU6.3 (ZIP 2005) versions
Orchard note plaintexts: pre-NU6.3 note plaintexts are version 2, and
Ironwood note plaintexts are version 3. The version determines how the
note commitment trapdoor is derived from the note's rseed, so the
version observed at decryption time must be persisted in order to
reconstruct received notes.
Add a `note_version` column to `orchard_received_notes`, populated from
the note plaintext version reported by the decrypted note itself.
Existing rows are backfilled as version 2, the only version accepted by
the Orchard note encryption domain. Note reconstruction now uses the
stored version instead of assuming version 2. Ironwood notes are not
stored in this table (they belong to a distinct pool with its own note
commitment tree), so the `(transaction_id, action_index)` uniqueness
constraint is unchanged.
Adapted from valargroup/librustzcash@0555be5234 and @d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge pull request #2422 from zcash/feat/propose-transaction-can-spend-transparent-utxos
feat: propose transaction can spend transparent utxos
Merge pull request #2531 from zcash/dw/ironwood-wallet-storage-scanning
zcash_client_sqlite: Add Ironwood note commitment tree storage
Merge branch 'main' into feat/propose-transaction-can-spend-transparent-utxos
zcash_client_backend, zcash_client_sqlite: enforce TransparentSpendPolicy::FromAddresses
select_spendable_transparent_outputs gathered transparent inputs for the
entire account regardless of TransparentSpendPolicy, so FromAddresses
behaved identically to AnyAccountTaddr: a proposal could spend from (and
link) addresses the caller explicitly excluded. Post-gather filtering is
not sufficient, since excluded UTXOs would consume the value bound and
under-fill the result; the address restriction must be applied within
the gather itself.
Add an address_allow_list parameter to select_spendable_transparent_outputs,
applied inside the SQL query (via the same always-bound-flag idiom already
used for coinbase_filter) so ineligible outputs never consume the value
bound. GreedyInputSelector wires this from TransparentSpendPolicy at both
gather sites: the initial gather and the InsufficientFunds re-gather
fallback (which had the same bug independently).
Also refactors NonEmptyBTreeSet to wrap a single BTreeSet with proper
invariant-preserving constructors (singleton, from_nonempty, from_set)
and accessors (as_set, iter) instead of exposing raw head/tail fields,
and adds persistent transparent_dust tracking across the propose_transaction
retry loop so a re-gather triggered by InsufficientFunds does not
reintroduce transparent inputs already pruned as dust by a prior
DustInputs iteration.
Strengthens propose_t2t_from_addresses to actually catch the bug: the
unnamed address's UTXO now holds more value than the named address's
and is inserted first, so an unenforced policy would select it (since
it alone covers the payment) instead of the named address, making the
test fail without the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pczt: preverified signing parse for the low-level Signer
Parse the low-level Signer's bundle with orchard's
`parse_preverified_for_signing`, which skips deriving each spend's
`FullViewingKey` from its wire `fvk` bytes. The spend authorization
signature does not depend on `fvk`, so signatures are byte-identical to
the full parse.
`sign_orchard_with` and `sign_ironwood_with` snapshot each spend's
`(rk, fvk)` before parsing and restore the `fvk` after serialization so
signing leaves the wire bytes unchanged. The restore returns
`OrchardParseError::SigningClosureModifiedActions` if the closure changes
the action count or reorders actions, detected by the per-position `rk`,
and makes no changes when it rejects.
Refactor the Orchard action parse from `.map(...).collect()` into a
bounded `#[inline(never)]` loop so a constrained signer does not overflow
its task stack. Behaviorally identical; the full `parse` used by the
Verifier, Prover, and Updater is unchanged.
Temporarily patches orchard to the branch adding
`parse_preverified_for_signing`, with a matching cargo-vet exemption;
both are dropped for a version bump once that orchard change releases.
Merge pull request #2517 from nuttycom/fix/upgrade-imported-transparent-address
zcash_client_sqlite: Upgrade imported transparent addresses to their derived form on gap-limit derivation
zcash_client_backend, zcash_client_sqlite: document the transparent input-count cap in CHANGELOGs
Update the still-unreleased select_spendable_transparent_outputs and
with_shielding_block_space_percent entries to describe the max_inputs
parameter and its dual use for shielding and general transfers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: cap transparent inputs gathered for general transfers
InputSource::select_spendable_transparent_outputs now takes a max_inputs
parameter bounding the number of transparent outputs the gather may
select, independent of target_value. Without this, a wallet holding a
very large number of small (e.g. dust) UTXOs could require an unbounded
number of inputs to satisfy even a modest request, producing an
oversized transaction.
GreedyInputSelector::propose_transaction wires this to the existing
shielding_block_space_percent config (via shielding_max_inputs), reusing
the same block-space bound already used to cap shielding transactions
rather than introducing a second configuration knob. When the cap is
reached before the requested value, the caller's input-selection loop
surfaces this the same as any other value shortfall: an InsufficientFunds
error.
The SQLite implementation enforces the cap inside the gather loop itself
(breaking out once max_inputs outputs have been accumulated), rather than
gathering an unbounded set and truncating afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: fix invalid migration UUID and add missing verify_schema entry
add_transparent_value_index's MIGRATION_ID was a hand-typed UUID whose
version nibble was 6, not 4, making it invalid per the CI 'uuid' job's
random/v4 check (.github/workflows/ci.yml). Replace it with a properly
generated v4 UUID.
Separately, the migration's new idx_transparent_received_outputs_value_zat
index was never added to wallet::init::tests::verify_schema's expected
index list, nor given a db.rs constant, causing that test to fail with a
positional mismatch against every index sorted after it. Add the missing
db::INDEX_TRANSPARENT_RECEIVED_OUTPUTS_VALUE_ZAT constant and list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Address review comments on duplicate-receiver resolution
- Decode key scopes via KeyScope::decode and match on the enum rather than raw
integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
cross-account duplicate whose ephemeral record reproduces the receiver is resolved
in its favor rather than aborting. Only Foreign records remain unverifiable, having
no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: depend on fix_transparent_received_outputs, not utxos_to_txos, for the value_zat index migration
fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.
Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.
Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: adapt to upstream API changes from rebase onto main
Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:
- Update the remaining ShieldedProtocol::Sapling references (added by
this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
pass 0 from the transparent-only gather in
select_spendable_transparent_outputs, matching the existing 0 passed
for sapling_input_count/sapling_output_count/orchard_action_count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Upgrade receivers imported under a different account on derivation
The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Resolve cross-account duplicate receivers by derivation
A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: CHANGELOG for imported→derived transparent address upgrade
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Preserve earliest exposed_at_height when merging duplicate receivers
The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Upgrade imported transparent receiver in place on derivation
When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: fix changelog/doc self-review findings
- Move two changelog entries that described this PR's new work out of
already-released, dated version sections (zcash_client_backend's
[0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
Unreleased/PLANNED sections. Released changelog sections must remain
immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
discarded intermediate designs (an unbounded gather, an
Option<Zatoshis> static-fee-headroom parameter) as though they were
prior shipped behavior. Since select_spendable_transparent_outputs is
entirely new and unreleased, changelog/doc text should describe only
the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
AGENTS.md conventions file, meaningless to external readers of the
public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
non-empty BTreeSet") and document its previously-undocumented public
fields; drop a private implementation note about possibly moving it to
another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
which controls the same privacy behavior the method's doc already
describes but was never mentioned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: fix spurious InsufficientFunds when transparent re-gather succeeds without new shielded notes
GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.
Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.
Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: make transparent gather fee-aware
InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.
Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).
Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: add value-bounded transparent input gather
Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.
The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.
GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: extract shared spendable-transparent-outputs query builder
Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.
This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).
Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: Remove gather_account_transparent_inputs in favor of batched query
`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce verbosity in propose_transaction docs and related types
better documentation and testing for propose_shielding transparent UTXO spending functionality
added variant CoinbaseFilter::NonCoinbaseOnly to remove coinbase from propose_transaction
rename TransparentOutputFilter and better names for TransparentSpendPolicy methods
add tests and changelog for transparent spending in propose_transaction
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add transparent spending to propose_transaction
Co-Authored-By: fdecroix <fdecroix@eryx.co>
CHANGELOG: Add Ironwood note commitment tree storage entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2508 from zcash/adam/ironwood-wallet-fees
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Adapt Ironwood fee wiring to the new tx-builder bundle-version API
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.
Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.
Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
Merge pull request #2498 from zcash/adam/ironwood-tx-builder-completion
zcash_client_backend: Wire Ironwood anchors into wallet transaction building
wallet: revert unnecessary change
CHANGELOG: mv with_ironwood_tree_mut to appropriate changed section
Assert exact Ironwood fee values; document the routing flag
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
Route Orchard-pool change into the Ironwood bundle to match the builder
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.
Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.
Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.
Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Note the compute_balance Ironwood view in the changelog
Charge the Ironwood bundle at proposal time
Populate the Ironwood fee view introduced previously: when the builder
will route Orchard-pool outputs into a separate Ironwood bundle, mirror
that split during input selection so the proposal's per-bundle action
counts (and therefore the fee) match the transaction that gets built.
The routing decision reuses the builder's own `orchard_outputs_to_ironwood`
predicate (threading the same `proposed_version`), so the proposal and
build paths cannot drift. The Orchard bundle keeps the Orchard spends; its
outputs move to the Ironwood bundle when routing is active. Ironwood has
no spends yet, as the wallet does not select Ironwood notes until note
detection lands.
Adds a zip317 test asserting that a populated Ironwood view increases the
required fee (the Ironwood bundle's actions are counted independently of
Orchard).
Thread a separate Ironwood bundle view through the fee/change path
V6 transactions carry separate Orchard and Ironwood bundles, each padded
to its own action floor, and `FeeRule::fee_required` already takes a
separate `ironwood_action_count`. Populate that count from a dedicated
Ironwood `BundleView` rather than the previous hardcoded zero.
`ChangeStrategy::compute_balance` and the `common.rs` helpers
(`calculate_net_flows`, `single_pool_output_balance`) gain an `ironwood`
view parameter alongside `orchard` (gated on `feature = "orchard"`,
mirroring it), `NetFlows` gains `ironwood_in`/`ironwood_out`, and the
Ironwood action count is computed via
`transactional_action_count(ironwood.bundle_version(), ...)`.
All call sites currently pass an empty Ironwood view, so this is a no-op:
the wallet does not yet route inputs/outputs to the Ironwood pool at
proposal time. A follow-up populates the view from the same routing
decision the builder uses, so the separate Ironwood bundle is priced.
Verified: compiles under default features and the nu7 cfg; the 11
zip317/fixed change-strategy tests pass unchanged.
Address review findings: routing enum, dedup, docs, expect
Model the per-step Orchard/Ironwood routing as an `OrchardBuildMode` enum
computed once, replacing the three booleans and four-arm if-else. The
spend-anchor, Ironwood inputs, and output-routing decisions now derive
from a single value so they cannot drift apart, and the `LegacyV5 +
Ironwood spends` rejection is expressed at classification time.
Replace the `ironwood_spends_requested implies shielded inputs` `expect`
with a `let-else` that returns `ProposalNotSupported` rather than
panicking on the (unreachable) `None`.
De-duplicate the cfg-gated Orchard change-output block so the Orchard
path is written once, gated by a `change_to_ironwood` flag that is
`false` in builds without Ironwood.
Document `stored_ironwood_output_index`: V6 stores Orchard and Ironwood
outputs in one index space with Orchard actions first.
Wire Ironwood anchors into wallet transaction building
Merge pull request #2522 from zcash/chore/shielded_protocol_rename
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool` and add `ShieldedPool::Ironwood`
Merge pull request #2520 from zcash/feat/pczt_ironwood_completion
pczt: Complete branch-aware v6/Ironwood support
Merge pull request #2521 from nuttycom/fix/put-transparent-output-preserves-mined-height
zcash_client_sqlite: Don't clear a recorded mined_height when storing a transparent output with unknown height
pczt: Generalize bundle version derivation over the value pool
`Creator::orchard_bundle_version()` becomes `bundle_version(pool)`, and
the crate-level revision-to-version mapping takes the `ValuePool` as an
argument (mirroring `zcash_primitives`'s `bundle_version_for_branch`),
so both the Orchard and Ironwood bundle versions are derived from the
consensus branch ID and pool alone. `with_ironwood_flags` now expresses
its version-support check as pool availability rather than a
transaction-version comparison.
Also restructure the transaction-version validation in
`extract_tx_data` as a single exhaustive match, and apply two
comment-wording suggestions in the v1 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Derive the Creator's Orchard bundle version instead of storing it
The Creator now stores the validated `BranchId` (rather than the raw
consensus branch ID) and derives the Orchard-pool bundle version from it
on demand, removing a stored field that was a pure function of state the
Creator already held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add high-level Ironwood methods to the Signer, Verifier, and Updater
Mirrors of the corresponding Orchard-pool methods:
- `Signer::{sign_ironwood, apply_ironwood_signature}` (with new
`Error::{IronwoodSign, IronwoodVerify}` variants), so
hardware-wallet-style signers no longer need the low-level Signer to
authorize Ironwood actions.
- `Verifier::with_ironwood`
- `Updater::update_ironwood_with`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add v6/Ironwood support to the Creator and validate at extraction
- `Creator::new` now derives the transaction version and version group
ID from the consensus branch ID: the v6 format from NU6.3 onward, and
the v5 format for earlier upgrades. Previously the Creator could only
produce v5 PCZTs.
- New `Creator::with_ironwood_anchor` and `Creator::with_ironwood_flags`
methods configure the Ironwood bundle independently of the Orchard
bundle; both reject configuration of an Ironwood bundle for a
transaction version that does not carry one
(`Error::IronwoodNotSupported`).
- Transaction extraction now rejects v6 PCZTs whose consensus branch ID
predates NU6.3, and non-v6 PCZTs carrying non-canonical Ironwood
bundle data (`ExtractError::IronwoodNotSupported`), instead of
silently dropping the Ironwood data or extracting a consensus-invalid
transaction.
- The v1 serialization format refuses to encode v6 PCZTs, which a v1
parser could decode but never extract a transaction from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Canonicalize the empty states of the PCZT bundles
The Creator, `Creator::build_from_parts`, the v1 and v2 serialization
formats, and the IO Finalizer previously produced four different
representations of an "empty" Ironwood bundle (and inconsistent empty
Sapling/Orchard bundles), which broke `Bundle::merge` for workflows that
mix serialization-round-tripped and in-memory copies of the same PCZT.
Each bundle type now has a single canonical empty representation,
defined as a constant in its module and used consistently everywhere:
- The Creator's Ironwood placeholder no longer mirrors the Orchard
bundle's flags and anchor; it is exactly the canonical empty bundle
(whose flags are the Ironwood defaults: spends, outputs, and
cross-address transfers enabled).
- `Creator::build_from_parts` uses the canonical empty bundles (with
all-zeroes anchors, rather than the empty-tree root) for absent
bundles, and now maps `parts.ironwood` through instead of discarding
it, so the builder-to-PCZT path no longer loses Ironwood spends and
outputs.
- Empty bundle value sums are initialized as non-negative zero,
matching the form produced by re-serializing a parsed bundle.
- The IO Finalizer no longer sets `bsk` on an empty Orchard-protocol
bundle. (The Sapling bundle is still always finalized, because the
Sapling Transaction Extractor requires `bsk` even for an empty
bundle.)
- The v2 format's omission decision is now exact equality with the
slot's canonical empty bundle, so a bundle with non-default flags or
note version round-trips losslessly instead of being restored with
default values; the v1 format refuses to encode a PCZT whose Ironwood
bundle is not canonically empty, rather than silently dropping its
non-default fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Don't clear mined_height when storing an output with unknown height
put_transparent_output's transactions upsert overwrote mined_height unconditionally,
so re-storing a transparent output whose mined height is unknown to the caller (e.g.
one re-observed via the mempool, or fetched from a backend that could not locate the
transaction on the best chain) clobbered a previously-recorded mined height with NULL,
marking a mined transaction as unmined. A NULL height carries no evidence that the
recorded height is wrong; preserve it, matching the adjacent handling of the block
column. Genuine un-mining remains the responsibility of truncate_to_height.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_protocol: Add `ShieldedPool::Ironwood`
This introducese `todo!(...)` stanzas in each location in
`zcash_client_backend` and `zcash_client_sqlite` where Ironwood
support infrastructure has not yet been implemented.
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool`
It is no longer appropriate to refer to the Orchard "shielded protocol"
as there are three different variants of the Orchard shielded protocol.
`zcash_protocol::ShieldedProtocol` has historically been used as a pool
identifier; the rename here reifies this change.
Merge pull request #2516 from zcash/fix/pr2509_review_followup
zcash_primitives: Address post-merge review comments from PR #2509
zcash_client_backend: Derive the PCZT test proving key from the consensus branch
The `pczt_single_step` pool test hardcoded the post-NU6.2 fixed-circuit
proving key, which was consistent with the PCZT roles' previous pinning
of the legacy Orchard bundle version but mismatches the branch-derived
parsing, proving, and verification those roles now perform: the shared
test network fixture's most recent upgrade is NU5, under which the
Orchard pool is governed by the historical pre-NU6.2 circuit, so proof
verification failed at extraction. Derive the proving key from the
PCZT's own consensus branch ID instead. (Modernizing the test network
fixture so that wallet tests exercise current consensus is part of the
Ironwood test coverage work tracked in #2518.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add OrchardProtocolRevision to zcash_protocol; make all PCZT roles branch-aware
The consensus knowledge of which Orchard protocol revision each network
upgrade deploys now lives in a single place:
`zcash_protocol::consensus::BranchId::orchard_protocol_revision`, which
returns the new `OrchardProtocolRevision` enum (`None` for branches that
predate NU5). `zcash_primitives`'s `bundle_version_for_branch` and the
`pczt` crate both derive `orchard::bundle::BundleVersion`s from it via
trivial conversions, replacing the duplicated branch-to-version matches.
(`pczt` cannot reuse the `zcash_primitives` helper directly because its
`zcash_primitives` dependency is optional and must not be required by
the lightweight role builds.)
Building on this, the remaining PCZT roles (Updater, Verifier, Prover,
and the low-level Signer) now parse the Orchard-pool bundle using the
bundle version implied by the PCZT's consensus branch ID rather than
pinning the legacy pre-NU6.3 version, completing the branch-aware
parsing work begun with `extract_tx_data`; in particular, the Prover now
creates proofs for the correct circuit for post-NU6.3 Orchard-pool
bundles. Roles report new `UnsupportedConsensusBranchId` errors when the
branch ID is unrecognized or predates NU5, and the low-level Signer's
Orchard-protocol signing methods now bound their error parameter by
`From<OrchardParseError>`. `Bundle::into_orchard_parsed`, which pinned
the legacy version, is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address remaining review comments from PR #2509
- pczt: fix extraction and verification of post-NU6.3 Orchard-pool
bundles. `extract_tx_data` now parses the Orchard-pool bundle using
the bundle version implied by the PCZT's consensus branch ID rather
than pinning the legacy pre-NU6.3 version, and the Transaction
Extractor's bundle verification derives the circuit version from the
bundle itself rather than hardcoding the NU6.2 fixed circuit (which
could not verify post-NU6.3 Orchard-pool bundles). The
`verify_bundle`/`verify_ironwood_bundle` duplication collapses into a
single function. Other PCZT roles still parse the Orchard-pool bundle
with the legacy version pin; completing those is tracked as follow-up
work.
- zcash_primitives: note the ZIP 229 specification of the v6 Sapling
digest variants in the `txid.rs` documentation, and correct the
`BlockTxCommitmentDigester::HeaderDigest` doc comment.
- zcash_client_backend: add a ZIP 317 change-calculation test covering
the post-NU6.3 Orchard action-count policy (one action per spend and
per output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive Orchard-protocol bundle versions from consensus context
Review followups:
- Add `bundle_version_for_branch`, returning the `BundleVersion` in
effect for a given Orchard-protocol value pool under a given consensus
branch, or `None` when the pool is not supported under that branch
(the Orchard pool prior to NU5; the Ironwood pool prior to NU6.3).
- `read_v5_bundle` now takes the consensus branch ID, and
`read_v6_bundle` the consensus branch ID and the `orchard::ValuePool`
identifying the bundle slot to read, in place of caller-supplied
`orchard::bundle::BundleVersion` arguments. Callers can no longer
select a wrong-slot bundle version, and a non-empty bundle belonging
to a pool that is unsupported under the transaction's consensus branch
ID is rejected as invalid data.
- In `Builder::new`, the `None` result subsumes the explicit
NU5-activation check that previously gated construction of the Orchard
builder.
- Remove `zcash_client_backend::ANY_ORCHARD_BUNDLE_VERSION`. The fee and
change calculation call sites that used it all have access to the
proposal's target height, and now derive the Orchard bundle version
(and hence the action-count policy) from it. As a consequence,
proposals targeting heights at or beyond NU6.3 activation now count
one action per Orchard spend or output, matching the post-NU6.3
transaction builder. `EmptyBundleView` retains a fixed version, which
is sound because an empty bundle produces zero actions under every
version's action-count policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_primitives: Address post-merge review comments from PR #2509
- `read_v5_bundle` now takes the consensus branch ID, deriving the
Orchard bundle version via the new public helper
`orchard_bundle_version_for_branch` (an exhaustive match, replacing
both the wildcard-arm private helper in the builder and the inline
mapping in `Transaction::read_v5`); this also removes an incomplete
sentence from its documentation.
- Make `TxVersion::has_ironwood` an exhaustive match.
- Simplify the `arb_txdata` proptest strategies:
`arb_ironwood_bundle_for_version` already yields `None` for versions
without Ironwood support.
- Factor out `Builder::orchard_in_use` to mirror `ironwood_in_use`.
- Correct the coinbase Orchard-builder flag comment, strengthen the
`with_expiry_height` warning, and fix stale `txid.rs` documentation.
- Give NU6.3 and NU7 distinct activation heights in the nu7 builder
test network.
- Add a test checking that Orchard note commitments and nullifiers are
domain-separated by the note plaintext version (lead byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address issues found in review of the NU6.3 testnet support branch
- `zcash_protocol`: under `zcash_unstable = "nu7"`, `BranchId::Nu6_3` now
reports the NU7 activation height as the upper bound of its height
range. The cfg-gated upper-bound pattern was dropped from the `Nu6_2`
arm when the `Nu6_3` arm was introduced, causing the Nu6_3 and Nu7
epochs to overlap in nu7 builds.
- `zcash_primitives`: the transaction builder's version-compatibility
check now accounts for Orchard change outputs, matching the Ironwood
check. Previously a change-only Orchard bundle passed the compatibility
check for `TxVersion::V4`, resulting in a reachable panic in `build()`.
- `zcash_primitives`: document previously-omitted API changes in the
0.29.0-pre.0 CHANGELOG entry (`map_bundles`/`try_map_bundles` closure
semantics, `TransactionDigest::combine` `ironwood_digest` argument,
`BlockTxCommitmentDigester::HeaderDigest` type change,
`BuildConfig::orchard_builder_config` removal,
`TxVersion::has_ironwood`).
- `zcash_transparent`: restore Keep-a-Changelog section ordering in the
0.9.0-pre.0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply minor review suggestions from PR #2509
- Fix the `expected_nu7` regtest activation heights to differ from the
NU6.3 height, assert NU6.3 activation at its own height, and check NU7
non-activation against the NU6.3 height in the `zcash_protocol`
`local_consensus` tests.
- Remove the stale placeholder-branch-ID paragraph from the
`BranchId::Nu6_3` docs.
- Simplify the `BranchId::Nu6_2` height-bounds arm to …
dannywillems
pushed a commit
that referenced
this pull request
Jul 7, 2026
Relying upon the note plaintext version to discriminate between Orchard
and Ironwood notes works, but it complicates reasoning about the code
and makes it difficult to understand where orchard-the-protocol is being
used vs orchard-the-pool. By adding value pool metadata to wallet
orchard-protocol notes, we thoroughly disambiguate; it's entirely
possible that in the future another note plaintext version will be
introduced (for example, for the note plaintexts of memo bundles) that
does not change the pool, and this refactoring makes such updates
straightforward to deal with.
Also lifts note pool classification to Step (Note::pool, Step input-pool predicates and counters, and Proposal::input_count_in_pool), with tests for the proposal pool-input helpers.
zcash_client_sqlite: Drop a non-leaf from CURRENT_LEAF_MIGRATIONS
`ironwood_shardtree` was listed in `CURRENT_LEAF_MIGRATIONS`, but
`witness_stabilized_notes` depends on it, so it is not a leaf of the migration
dependency graph. Migrating to the current state still reached it transitively,
so this was harmless, but the constant is documented to hold exactly the graph
leaves. Remove the stale entry and add a test that recomputes the leaves from the
dependency graph and checks them against the constant, so this cannot regress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Include Ironwood notes in the account balance
Add an Ironwood balance component to `AccountBalance`, alongside the Sapling and
Orchard components, with an `ironwood_balance` accessor and a
`with_ironwood_balance_mut` method. The Ironwood balance is included in the
account's total, spendable, pending-confirmation, pending-spendability, and
uneconomic values.
`get_wallet_summary` now computes the Ironwood balance from
`ironwood_received_notes` via the existing pool-generic `with_pool_balances`, so
received Ironwood notes are reflected in the wallet balance. The end-to-end
storage test now asserts the balance equals the received note value.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Decrypt Ironwood outputs with the Ironwood domain
Ironwood notes carry version 3 note plaintexts, which are decrypted under the
Ironwood note-encryption domain (`orchard::note_encryption::IronwoodDomain`).
That domain is distinct from `OrchardDomain`, which accepts only version 2
plaintexts and would reject every Ironwood note. The scanner previously aliased
`IronwoodDomain` to `OrchardDomain`, so it would have silently failed to detect
any Ironwood output; this corrects the alias to the real Ironwood domain.
As a consequence an Ironwood batch is no longer type-identical to an Orchard
batch, so the compact- and full-block `BatchRunners` gain a distinct
`IronwoodTasks` type parameter alongside the Sapling and Orchard task types, and
a `ScanningKeyOps<IronwoodDomain, ..>` implementation lets the account's Orchard
viewing key trial-decrypt under the Ironwood domain. The Ironwood runners and
scan paths now construct their domains with `IronwoodDomain::for_compact_action`
/ `IronwoodDomain::for_action`.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds tests: scanning detects Ironwood notes across the compact and full scanning paths, and received Ironwood notes round-trip end-to-end through zcash_client_sqlite storage.
zcash_client_backend: Persist the Ironwood note commitment tree
Build and persist the Ironwood note commitment tree during `put_blocks`,
mirroring the Orchard handling but keeping Ironwood a distinct pool. The
Ironwood tree is Orchard-shaped, so it reuses the Orchard shard height and is
updated through `with_ironwood_tree_mut`; the Ironwood block metadata
(`ironwood_commitment_tree_size` and `ironwood_action_count`) is now written to
the `blocks` table via `put_block_meta`.
Checkpoint reconciliation across the note commitment trees is generalized from
two pools to three: each tree gains a checkpoint at every height that is
checkpointed in any other pool, computed as the union of the other pools'
checkpoint heights. When Ironwood has no checkpoints this reduces exactly to the
prior Sapling/Orchard reconciliation, so existing behavior is unchanged; the
Ironwood tree gains empty checkpoints at the scanned heights, as the Orchard
tree already does before its own activation.
With the tree persisted and the block Ironwood tree size recorded, received
Ironwood notes now have witness data and are spendable, and the note positions
computed by the scanner are backed by a built tree.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also property-tests cross-pool checkpoint reconciliation:
Extract the three-pool checkpoint-height reconciliation in `put_blocks` into a
pure `cross_pool_ensure_heights` helper and add proptests for it. The tests
assert the key invariant that after reconciliation every pool is checkpointed at
exactly the union of all pools' checkpoint heights (so the note commitment trees
stay aligned for cross-pool rewinds), and that with no Ironwood checkpoints the
Sapling/Orchard reconciliation is unchanged from the prior two-pool behavior.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add the Ironwood note commitment tree to ChainState
Add a separate Ironwood note commitment tree frontier to `ChainState`, parallel
to the Sapling and Orchard frontiers, with a `final_ironwood_tree` accessor and
wiring through `new`/`empty`. Parse it from the gRPC `TreeState.ironwood_tree`
field via a new `TreeState::ironwood_tree` method; an absent field yields an
empty tree, the correct Ironwood treestate at pool activation. This is the
prior chain state that the Ironwood commitment tree will be built from, added
ahead of the Ironwood tree write path. The Ironwood tree is Orchard-shaped but
is tracked as a distinct pool.
Also complete the Ironwood threading exposed under `--all-features`: the
`sync` decryptor's `BatchRunners::for_keys` callers now pass the Ironwood batch
threshold (reusing the Orchard threshold, as Ironwood outputs are
Orchard-shaped), and the block-metadata query row, widened with the Ironwood
tree size, is factored into a `BlockMetadataRow` type alias.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Persist received Ironwood notes and nullifiers
Store received Ironwood notes during scanning and track Ironwood nullifiers for
spend detection, mirroring the Orchard receive path but keeping Ironwood a
separate pool. `put_blocks` now feeds the scanner's Ironwood output stream to a
dedicated `put_received_ironwood_note` (writing to `ironwood_received_notes`),
detects earlier spends via `detect_ironwood_spend`, records the block's Ironwood
nullifiers with `track_block_ironwood_nullifiers`, and marks the wallet's own
spent Ironwood notes with `mark_ironwood_note_spent`. `Nullifiers::unspent` and
`WalletRead::get_ironwood_nullifiers` complete the spend-detection loop.
Storage no longer routes by note plaintext version: `put_received_note` takes
the target table prefix from the caller, so the scanner routes its separate
Orchard and Ironwood output streams to their respective tables, while the
decrypted-transaction path selects the table from the note version as before.
The shared spent-note marking is factored into a prefix-parameterized helper.
This does not build the Ironwood commitment tree (that needs an Ironwood
frontier in `ChainState`, added separately), so no non-zero Ironwood tree size
is written to `blocks` and received Ironwood notes are not yet spendable. The
send-path Ironwood todos are left for a later increment.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Track Ironwood outputs in the block scanner
Extend the compact- and full-block scanners to trial-decrypt Ironwood outputs
and track the Ironwood note commitment tree, mirroring the existing Orchard
handling but keeping Ironwood as a distinct pool. `BatchRunners` feed the
Ironwood runner from `CompactTx.ironwood_actions` / the transaction's Ironwood
bundle, `PositionTracker` tracks the Ironwood tree position and final size
(activating at NU6.3 and reconciling against `ChainMetadata`'s Ironwood tree
size), and `scan_block_with_runners` collects Ironwood spends, outputs, note
commitments, and nullifiers into a separate `ScannedBundles`.
`ScannedBlock`, `ScannedBlockCommitments`, `BlockMetadata`, `WalletTx`, and
`Nullifiers` gain parallel Ironwood fields and accessors, and the sqlite
`block_metadata`/`block_max_scanned` queries read the persisted Ironwood tree
size. Ironwood note commitments are Orchard-shaped, so the Orchard commitment,
nullifier, and wallet-output types are reused through Ironwood-named aliases;
the two pools remain separate throughout.
This computes the Ironwood scan results but does not yet persist them: the
wallet does not query Ironwood nullifiers (so Ironwood spend detection is inert)
and `put_blocks` does not write received Ironwood notes or update the Ironwood
commitment tree. Those are added with the Ironwood write path.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: Add Ironwood scanning keys
Add a separate `ironwood` key set to `ScanningKeys`, parallel to the Sapling
and Orchard key sets, with an `ironwood()` accessor and wiring through `new`,
`empty`, and `from_account_ufvks`. Ironwood outputs are Orchard-shaped and are
trial-decrypted under the Orchard note-encryption domain using the account's
Orchard viewing keys, so the Ironwood keys are populated from the Orchard full
viewing key; the `IronwoodDomain`/`IronwoodNullifier` aliases name that reuse of
the underlying Orchard primitives.
Ironwood is kept strictly separate from Orchard as its own pool: the shared
primitives are an implementation detail expressed through aliases, not a design
in which Ironwood belongs to an "Orchard family".
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also adds the Ironwood compact batch runner:
Add a separate `ironwood` batch runner to the compact-block `BatchRunners`,
parallel to the Sapling and Orchard runners, wired through `for_keys` and
`flush`. Because an Ironwood batch is type-identical to an Orchard batch
(`IronwoodDomain` aliases `OrchardDomain` and the action type is shared), the
Orchard task type already satisfies the runner's bound, so no separate task
type is introduced; the `TaggedIronwoodBatchRunner` alias only names the runner
as belonging to the Ironwood pool.
The runner is constructed from the Ironwood scanning keys and flushed, but is
not yet fed block outputs or collected, so scanning behavior is unchanged. The
decryption of `CompactTx.ironwood_actions` is added in a later change together
with the commitment-tree position tracking that consumes its results.
Part of the Ironwood upstreaming tracked in zcash/librustzcash#2530.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2545 from zcash/fix/broken_patch_directive
Fix duplicate patch dependency in Cargo.toml
Update to orchard 0.15.0-pre.2
Merge pull request #2507 from nuttycom/feature/shardtree_explicit_retention
Explicit shardtree checkpoint retention (durable anchors)
Merge pull request #2535 from zcash/adam/pczt-signer-lean-parse
pczt: preverified signing parse for the low-level Signer
zcash_client_backend: add WalletCommitmentTrees::remove_retained_checkpoints_below
Adds a default-implemented `WalletCommitmentTrees` method that releases
retained "anchor" checkpoints with height below a given threshold from both
the Sapling and (when enabled) Orchard note commitment trees, so that anchors
that have aged out of the desired retention window can be pruned normally. The
default implementation is built from the `shardtree` retained-checkpoint
primitives, so all backends get it without bespoke code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend: retain anchor checkpoints during scanning
During `put_blocks`, checkpoints established at heights on the
`ANCHOR_RETENTION_INTERVAL` (every 288 blocks, ~4 per day) at or above a
configured activation height are retained as durable anchors via
`ShardTree::ensure_retained`, exempting them from automatic pruning of excess
checkpoints so that their roots and anchored witnesses remain computable.
The retention floor is threaded into `put_blocks`/`update_tree` as an
`Option<BlockHeight>`; the SQLite backend computes it as the NU6.3 (Ironwood)
activation height. Anchor retention is therefore dormant until NU6.3 has an
assigned activation height.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: implement explicit shardtree checkpoint retention
Implements the `add_retained_checkpoint` / `remove_retained_checkpoint` /
`retained_checkpoints` `ShardStore` methods for `SqliteShardStore`, backed by
new per-pool `{sapling,orchard}_tree_retained_checkpoints` tables created by a
new migration. Retained checkpoints are exempt from automatic pruning of
excess checkpoints, so their roots and the witnesses anchored to them remain
computable after they age more than `max_checkpoints` behind the chain tip.
Also adds a `remove_retained_checkpoints_below` helper for releasing anchors
older than a given height (wired into the wallet API in a subsequent commit).
Temporarily patches `shardtree`/`incrementalmerkletree` to
zcash/incrementalmerkletree `main`, which carries the retention API merged in
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pczt: reword the inline(never) action-parse comment
Apply @ValarDragon's review suggestion, stating the rationale for the
`#[inline(never)]` per-action parse helper more concisely: it avoids
inlining into a `.map(..).collect()` loop that would compile to a stack
frame tens of KB deep and overflow embedded signer stacks on
high-action-count transactions. Comment-only; no behavior change.
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
supply-chain: point the orchard vet exemption at the pinned rev
The orchard pin bump in e862ecef02 moved the [patch.crates-io] git rev
from 20c10904 to ab7c5fae but left the cargo-vet exemption on the old
rev, so `cargo vet --locked` failed with
orchard:0.15.0-pre.1@git:ab7c5fae... missing ["safe-to-deploy"]
Update the exemption rev to match Cargo.lock. `cargo vet --locked` now
succeeds. The exemption stays temporary and is dropped together with the
[patch] once the orchard change releases.
Merge pull request #2386 from valargroup/dojha/zcash-proofs-readme-sprout
zcash_proofs: scope README to Sprout
Apply suggestion from @ValarDragon
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
pczt: drop Bundle/Action preverified parse forks; bump orchard pin
The upstream orchard signer-only-parse work removed the redundant Bundle
and Action parse_preverified_for_signing wrappers (they were pure
pass-throughs), keeping only the spend-level entry point. Collapse the
matching Action/Bundle `if preverified { .. } else { .. }` branches here
to unconditional Action::parse / Bundle::parse; the `preverified` flag
now selects only at the spend level, the sole place it changes behavior.
Bump the orchard patch to the current head of that branch so the build
tracks the change; the previous pin still referenced the pre-cleanup
orchard that carried the removed wrappers.
Merge pull request #2534 from zcash/dw/ironwood-pool-code-views
zcash_client_sqlite: Report Ironwood outputs as pool code 4
CHANGELOG: Add Ironwood received-output views entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received notes to the output views
Add a migration that recreates the `v_received_outputs` and
`v_received_output_spends` views with an additional branch unioning in the
`ironwood_received_notes` and `ironwood_received_note_spends` tables, tagged
with the Ironwood pool code 4. Ironwood notes now appear in `v_transactions`,
`v_tx_outputs`, and the balances derived from them.
Ironwood notes are stored in a table separate from `orchard_received_notes`
because the two pools have distinct note commitment trees and an Orchard action
and an Ironwood action in the same transaction may share an action index, so the
views union the separate tables rather than splitting one table by note version.
Update the canonical `v_received_outputs` / `v_received_output_spends`
definitions in `db.rs` so `verify_schema` matches the migrated schema, and add a
reusable `arb_ironwood_note` proptest strategy alongside `arb_orchard_note`. The
new migration test inserts an Orchard note (pool 3) and an Ironwood note (pool 4)
at the same `(transaction_id, action_index)` and asserts both surface distinctly
in `v_received_outputs`.
Adapted from the Ironwood work on
valargroup/librustzcash@adam/qleak-pr44-orchard-dummy-ciphertexts.
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Assign the Ironwood shielded pool code
Assign pool code 4 to the Ironwood pool in `pool_code`/`parse_pool_code`
(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the `todo!`. This
is the SQLite `output_pool` code used to distinguish Ironwood outputs from
Orchard outputs, which share the `orchard_received_notes` table.
Adapted from valargroup/librustzcash@d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2533 from zcash/dw/ironwood-scanning
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
zcash_client_sqlite: Fix missing CURRENT_LEAF_MIGRATIONS entries.
Fix formatting and feature-flagging errors.
CHANGELOG: Add Ironwood scan-range extension entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Extend scan ranges for Ironwood subtrees
When Ironwood notes are detected during scanning, extend the suggested scan
ranges to include the blocks needed to complete the Ironwood note commitment
tree subtrees containing those notes, mirroring the existing Sapling and Orchard
handling.
Ironwood notes are stored alongside Orchard notes in `orchard_received_notes`,
but their commitment tree is tracked separately (in the `ironwood_*` tables), so
`extend_range` now takes the tree table prefix directly instead of deriving it
from a `ShieldedPool`. This avoids conflating the note-table pool with the
commitment-tree tables, and lets the Sapling, Orchard, and Ironwood callers pass
their respective prefixes. The Ironwood extension is gated on NU6.3 activation.
Adapted from valargroup/librustzcash@f42001edde and @0af74dd549
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2538 from zcash/kn/ironwood-note-version-migrations
zcash_client_sqlite: Add ironwood_received_notes
CHANGELOG: Add Ironwood received-note storage entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood received-note storage
Ironwood notes (ZIP 2005, NU6.3) are Orchard-protocol notes obtained
from version 3 note plaintexts, carried by the Ironwood bundle of a
transaction and committed to the Ironwood note commitment tree. Store
them separately from `orchard_received_notes`: the two pools have
distinct note commitment trees, and an Orchard action and an Ironwood
action in the same transaction may share an action index.
A new migration adds the `ironwood_received_notes` and
`ironwood_received_note_spends` tables, mirroring the corresponding
Orchard tables. `put_received_note` now records a received note in the
table for the pool implied by the note's plaintext version: version 2
note plaintexts can be obtained only under the Orchard note encryption
domain, and version 3 note plaintexts only under the Ironwood domain.
Reading Ironwood notes back for spendability, balance, and view
reporting is not yet implemented; it will accompany Ironwood scanning
support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG: Add Orchard received-note version entry
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Add note version to Orchard received notes
The Orchard protocol revision introduced by NU6.3 (ZIP 2005) versions
Orchard note plaintexts: pre-NU6.3 note plaintexts are version 2, and
Ironwood note plaintexts are version 3. The version determines how the
note commitment trapdoor is derived from the note's rseed, so the
version observed at decryption time must be persisted in order to
reconstruct received notes.
Add a `note_version` column to `orchard_received_notes`, populated from
the note plaintext version reported by the decrypted note itself.
Existing rows are backfilled as version 2, the only version accepted by
the Orchard note encryption domain. Note reconstruction now uses the
stored version instead of assuming version 2. Ironwood notes are not
stored in this table (they belong to a distinct pool with its own note
commitment tree), so the `(transaction_id, action_index)` uniqueness
constraint is unchanged.
Adapted from valargroup/librustzcash@0555be5234 and @d94102c3ea
(adam/qleak-pr44-orchard-dummy-ciphertexts).
Co-Authored-By: Adam Tucker <adam@osmosis.team>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge pull request #2422 from zcash/feat/propose-transaction-can-spend-transparent-utxos
feat: propose transaction can spend transparent utxos
Merge pull request #2531 from zcash/dw/ironwood-wallet-storage-scanning
zcash_client_sqlite: Add Ironwood note commitment tree storage
Merge branch 'main' into feat/propose-transaction-can-spend-transparent-utxos
zcash_client_backend, zcash_client_sqlite: enforce TransparentSpendPolicy::FromAddresses
select_spendable_transparent_outputs gathered transparent inputs for the
entire account regardless of TransparentSpendPolicy, so FromAddresses
behaved identically to AnyAccountTaddr: a proposal could spend from (and
link) addresses the caller explicitly excluded. Post-gather filtering is
not sufficient, since excluded UTXOs would consume the value bound and
under-fill the result; the address restriction must be applied within
the gather itself.
Add an address_allow_list parameter to select_spendable_transparent_outputs,
applied inside the SQL query (via the same always-bound-flag idiom already
used for coinbase_filter) so ineligible outputs never consume the value
bound. GreedyInputSelector wires this from TransparentSpendPolicy at both
gather sites: the initial gather and the InsufficientFunds re-gather
fallback (which had the same bug independently).
Also refactors NonEmptyBTreeSet to wrap a single BTreeSet with proper
invariant-preserving constructors (singleton, from_nonempty, from_set)
and accessors (as_set, iter) instead of exposing raw head/tail fields,
and adds persistent transparent_dust tracking across the propose_transaction
retry loop so a re-gather triggered by InsufficientFunds does not
reintroduce transparent inputs already pruned as dust by a prior
DustInputs iteration.
Strengthens propose_t2t_from_addresses to actually catch the bug: the
unnamed address's UTXO now holds more value than the named address's
and is inserted first, so an unenforced policy would select it (since
it alone covers the payment) instead of the named address, making the
test fail without the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pczt: preverified signing parse for the low-level Signer
Parse the low-level Signer's bundle with orchard's
`parse_preverified_for_signing`, which skips deriving each spend's
`FullViewingKey` from its wire `fvk` bytes. The spend authorization
signature does not depend on `fvk`, so signatures are byte-identical to
the full parse.
`sign_orchard_with` and `sign_ironwood_with` snapshot each spend's
`(rk, fvk)` before parsing and restore the `fvk` after serialization so
signing leaves the wire bytes unchanged. The restore returns
`OrchardParseError::SigningClosureModifiedActions` if the closure changes
the action count or reorders actions, detected by the per-position `rk`,
and makes no changes when it rejects.
Refactor the Orchard action parse from `.map(...).collect()` into a
bounded `#[inline(never)]` loop so a constrained signer does not overflow
its task stack. Behaviorally identical; the full `parse` used by the
Verifier, Prover, and Updater is unchanged.
Temporarily patches orchard to the branch adding
`parse_preverified_for_signing`, with a matching cargo-vet exemption;
both are dropped for a version bump once that orchard change releases.
Merge pull request #2517 from nuttycom/fix/upgrade-imported-transparent-address
zcash_client_sqlite: Upgrade imported transparent addresses to their derived form on gap-limit derivation
zcash_client_backend, zcash_client_sqlite: document the transparent input-count cap in CHANGELOGs
Update the still-unreleased select_spendable_transparent_outputs and
with_shielding_block_space_percent entries to describe the max_inputs
parameter and its dual use for shielding and general transfers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: cap transparent inputs gathered for general transfers
InputSource::select_spendable_transparent_outputs now takes a max_inputs
parameter bounding the number of transparent outputs the gather may
select, independent of target_value. Without this, a wallet holding a
very large number of small (e.g. dust) UTXOs could require an unbounded
number of inputs to satisfy even a modest request, producing an
oversized transaction.
GreedyInputSelector::propose_transaction wires this to the existing
shielding_block_space_percent config (via shielding_max_inputs), reusing
the same block-space bound already used to cap shielding transactions
rather than introducing a second configuration knob. When the cap is
reached before the requested value, the caller's input-selection loop
surfaces this the same as any other value shortfall: an InsufficientFunds
error.
The SQLite implementation enforces the cap inside the gather loop itself
(breaking out once max_inputs outputs have been accumulated), rather than
gathering an unbounded set and truncating afterward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: fix invalid migration UUID and add missing verify_schema entry
add_transparent_value_index's MIGRATION_ID was a hand-typed UUID whose
version nibble was 6, not 4, making it invalid per the CI 'uuid' job's
random/v4 check (.github/workflows/ci.yml). Replace it with a properly
generated v4 UUID.
Separately, the migration's new idx_transparent_received_outputs_value_zat
index was never added to wallet::init::tests::verify_schema's expected
index list, nor given a db.rs constant, causing that test to fail with a
positional mismatch against every index sorted after it. Add the missing
db::INDEX_TRANSPARENT_RECEIVED_OUTPUTS_VALUE_ZAT constant and list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Address review comments on duplicate-receiver resolution
- Decode key scopes via KeyScope::decode and match on the enum rather than raw
integer codes, and replace the address-record tuple with a bespoke struct.
- Verify ephemeral-scope records by derivation: the ephemeral scope has a genuine
derivation relationship to the account (AccountPubKey::derive_ephemeral_ivk), so a
cross-account duplicate whose ephemeral record reproduces the receiver is resolved
in its favor rather than aborting. Only Foreign records remain unverifiable, having
no derivation relationship to their account.
- Fix a stale child-index reference in a test comment.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: depend on fix_transparent_received_outputs, not utxos_to_txos, for the value_zat index migration
fix_transparent_received_outputs rebuilds transparent_received_outputs
via DROP TABLE + ALTER TABLE ... RENAME TO, which destroys any index
created on the old table. add_transparent_value_index previously
depended only on utxos_to_txos (the migration that first creates the
table), which does not guarantee it runs after that rebuild; depending
on schemerz's topological ordering among otherwise-independent
migrations, the index could have been created before the rebuild and
then silently lost, with no indication that anything had gone wrong.
Depend on fix_transparent_received_outputs directly instead, which
transitively depends on utxos_to_txos, guaranteeing the index is always
created on the table that will actually persist.
Also corrects the migration's own doc comment, which incorrectly
asserted that the index "remains valid across the rebuild" so long as
column names were preserved -- SQLite indexes do not survive a
DROP TABLE regardless of the recreated table's schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: adapt to upstream API changes from rebase onto main
Rebasing onto main (which has moved substantially, including the
ShieldedProtocol -> ShieldedPool rename and Ironwood fee/change wiring)
surfaced two required adaptations beyond conflict resolution:
- Update the remaining ShieldedProtocol::Sapling references (added by
this branch's own tests) to ShieldedPool::Sapling, avoiding deprecated-item
usage now that ShieldedProtocol is a deprecated alias for ShieldedPool.
- FeeRule::fee_required gained a new ironwood_action_count parameter;
pass 0 from the transparent-only gather in
select_spendable_transparent_outputs, matching the existing 0 passed
for sapling_input_count/sapling_output_count/orchard_action_count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: Upgrade receivers imported under a different account on derivation
The runtime reconcile-on-derive path in store_address_range only matched a Foreign row
in the deriving account, so deriving an address that had been imported standalone under a
*different* account fell through to the INSERT and failed hard on the receiver-uniqueness
index (the upsert's conflict target does not cover it), wedging gap-limit generation and
anything that drives it. Deriving the address is itself proof that the deriving account
owns it, so the lookup now matches the Foreign row regardless of account and the upgrade
moves the row's account attribution — and that of any outputs received at the address —
to the deriving account, mirroring the migration's derivation-based cross-account
resolution. Retargeting cannot violate the address-tuple constraint: a row already held
by the deriving account at that (scope, child index) would be this same receiver, which
the uniqueness index makes impossible.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Resolve cross-account duplicate receivers by derivation
A transparent receiver duplicated across accounts was previously an unconditional
migration abort. Resolve it instead when derivation definitively identifies the correct
record: if exactly one record reproduces the receiver when derived from its own account's
viewing key at its recorded child index, that record is retained, and the received
outputs of the other records are repointed to it along with their account attribution
(the reason cross-account merges were previously unsafe). No account is privileged in
this determination: an address genuinely derived under the ZIP 32 account index
0x7FFFFFFF used for the zcashd legacy account wins over another account's imported
record just as any other verified derivation would. Cross-account duplicates for which
no unique record verifies still abort.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: CHANGELOG for imported→derived transparent address upgrade
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Preserve earliest exposed_at_height when merging duplicate receivers
The add_transparent_receiver_address_index migration already collapses a transparent
receiver duplicated as both a derived and an imported record. It now also carries the
minimum exposed_at_height across the merged records onto the surviving canonical record,
so an earlier exposure recorded against the imported record is not lost.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Upgrade imported transparent receiver in place on derivation
When gap-limit generation derives a transparent address that already exists as a
standalone (Foreign) import, store_address_range now upgrades the existing addresses
row in place to its derived form instead of inserting a duplicate row for the same
receiver (which the UNIQUE index on cached_transparent_receiver_address rejects). The
row id is preserved, so UTXOs already attached to the imported receiver become spendable.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: fix changelog/doc self-review findings
- Move two changelog entries that described this PR's new work out of
already-released, dated version sections (zcash_client_backend's
[0.22.0] and zcash_client_sqlite's [0.20.0]) into the correct
Unreleased/PLANNED sections. Released changelog sections must remain
immutable; both frozen sections now match main exactly.
- Reword changelog entries and doc comments that described this PR's own
discarded intermediate designs (an unbounded gather, an
Option<Zatoshis> static-fee-headroom parameter) as though they were
prior shipped behavior. Since select_spendable_transparent_outputs is
entirely new and unreleased, changelog/doc text should describe only
the final capability, not the path taken to arrive at it.
- Remove a doc comment's direct reference to this repo's internal
AGENTS.md conventions file, meaningless to external readers of the
public rustdoc.
- Improve NonEmptyBTreeSet's doc comment (was "A quick and easy
non-empty BTreeSet") and document its previously-undocumented public
fields; drop a private implementation note about possibly moving it to
another crate someday.
- Document the spend_policy parameter on InputSelector::propose_transaction,
which controls the same privacy behavior the method's doc already
describes but was never mentioned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: fix spurious InsufficientFunds when transparent re-gather succeeds without new shielded notes
GreedyInputSelector::propose_transaction's retry loop tracked progress
solely via growth in the shielded note total (prior_available vs.
new_available), even though the same loop can also grow the transparent
input set via a re-gather when the initial (fee-only) estimate proves
insufficient. For an account with no spendable shielded notes at all (or
none beyond what's already excluded), shielded-side "progress" can never
be made, so the loop bailed out with InsufficientFunds immediately after
the first failed balance computation, even when the freshly re-gathered
transparent inputs would have been sufficient on the very next iteration.
Track whether the transparent gather grew this iteration
(transparent_gather_grew) and treat that as valid progress alongside
shielded note growth, so the loop retries compute_balance with the
enlarged transparent input set instead of failing prematurely.
Adds a regression test (propose_t2shielded_requires_transparent_regather)
that funds a transparent-only account with many small UTXOs and pays a
shielded recipient, forcing the initial transparent-only fee estimate
(which cannot account for the additional shielded action) to undershoot
and require exactly this re-gather-without-shielded-progress path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: make transparent gather fee-aware
InputSource::select_spendable_transparent_outputs now takes a concrete
fee_rule: &StandardFeeRule parameter in place of the previous
estimated_additional_fees: Option<Zatoshis> static headroom. The SQLite
gather recomputes the cumulative ZIP 317 marginal fee cost of the
transparent inputs examined so far at each step, maintaining a running
total of input sizes so the recomputation stays O(1) per candidate UTXO,
and stops once the post-fee accumulated value meets the requested
TargetValue. This removes the need for callers to guess a fee headroom
up front and avoids the previously-common second round trip to correct
an under-estimated one.
Using a concrete fee rule (rather than a generic FeeRuleT bounded by
FeeRuleT::Error: Into<Self::Error>) keeps the change localized: no other
InputSelector/ChangeStrategy signatures need updating, since the gather's
fee estimate is a heuristic bound decoupled from the caller's actual
change strategy (real fee/balance correctness is still enforced by that
change strategy, and the existing retry-on-InsufficientFunds fallback is
unchanged).
Adds SqliteClientError::FeeRuleError to carry FeeRule errors from the
gather (zip317::FeeError doesn't implement std::error::Error, so it's
wrapped).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend, zcash_client_sqlite: add value-bounded transparent input gather
Add InputSource::select_spendable_transparent_outputs, a value-bounded
counterpart to get_spendable_transparent_outputs[_for_addresses]: rather
than returning every spendable transparent output for an account, it
returns only enough (ordered by descending value) to cover a requested
TargetValue, optionally padded by a caller-supplied
estimated_additional_fees headroom. This is intended to scale to wallets
with large numbers of transparent addresses and UTXOs, which would
otherwise need to materialize their entire UTXO set just to build a
small transfer.
The SQLite implementation backs the value-descending ordering with a new
idx_transparent_received_outputs_value_zat index (added by a new
migration), and reuses the spendable_transparent_outputs_query/
coinbase_filter_encoding helpers introduced in the preceding commit.
GreedyInputSelector::propose_transaction now uses this method (instead
of gathering every account transparent receiver via get_transparent_receivers
and querying get_spendable_transparent_outputs_for_addresses) when the
TransparentSpendPolicy requires transparent inputs, bounded initially by
the requested payment total and re-gathered if a subsequent
ChangeError::InsufficientFunds reveals the bound was too low. This also
lets GreedyInputSelector's trait bound on DbT simplify from
WalletRead + InputSource<...> down to just InputSource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_sqlite: extract shared spendable-transparent-outputs query builder
Factor the SQL query body of get_spendable_transparent_outputs_for_addresses
out into a new spendable_transparent_outputs_query helper, parameterized over
the address-predicate and ORDER BY fragments, and factor the coinbase-filter
encoding out into coinbase_filter_encoding. This is a pure refactor with no
behavior change: get_spendable_transparent_outputs_for_addresses now builds
its query via these helpers instead of inlining the SQL and match directly.
This is preparatory: a following commit adds a new
select_spendable_transparent_outputs query that reuses the same query body
with a different address predicate and ordering (by account, ordered by
value descending, for the value-bounded transparent gather).
Also tightens the redeem-script row read to use a single
row.get::<_, Option<Vec<u8>>>(...) call instead of a separate row.get(...)?
followed by an Option check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zcash_client_backend: Remove gather_account_transparent_inputs in favor of batched query
`gather_account_transparent_inputs` issued one `get_spendable_transparent_outputs`
query per source address. Now that `InputSource` exposes the batched
`get_spendable_transparent_outputs_for_addresses`, the per-address loop is
unnecessary: inline a single batched call (filtered to `NonCoinbaseOnly`, then
account-redacted) at the sole call site in `propose_transaction` and delete the
function. This matches the batched approach already used for shielding and gives
general transfers the same single-query behavior for wallets with many
transparent addresses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce verbosity in propose_transaction docs and related types
better documentation and testing for propose_shielding transparent UTXO spending functionality
added variant CoinbaseFilter::NonCoinbaseOnly to remove coinbase from propose_transaction
rename TransparentOutputFilter and better names for TransparentSpendPolicy methods
add tests and changelog for transparent spending in propose_transaction
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree. The Ironwood shard store and tree are exposed as
`IronwoodShardStore` / `IronwoodCommitmentTree` aliases so Ironwood usage is
self-documenting at call sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add transparent spending to propose_transaction
Co-Authored-By: fdecroix <fdecroix@eryx.co>
CHANGELOG: Add Ironwood note commitment tree storage entry
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zcash_client_sqlite: Add Ironwood note commitment tree storage
Ironwood note commitments are Orchard-shaped, so this persists them using the
same `shardtree` machinery as Orchard, under a separate `ironwood` table
prefix.
Adds a migration that creates the `ironwood_tree_shards`, `ironwood_tree_cap`,
`ironwood_tree_checkpoints`, and `ironwood_tree_checkpoint_marks_removed`
tables, the `ironwood_commitment_tree_size` and `ironwood_action_count`
columns on `blocks`, and the `v_ironwood_shard_scan_ranges`,
`v_ironwood_shard_unscanned_ranges`, and `v_ironwood_shards_scan_state` views,
each mirroring the Orchard equivalents but keyed on NU6.3 (Ironwood)
activation. When a wallet has already scanned NU6.3-era blocks, the migration
requeues them from activation so they are rescanned with Ironwood tree state
enabled.
Wires an `ironwood_tree` accessor over the `ironwood` prefix and overrides
`WalletCommitmentTrees::with_ironwood_tree_mut` in both `WalletDb`
implementations to provide the persisted tree, replacing the default that
reported no Ironwood tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge pull request #2508 from zcash/adam/ironwood-wallet-fees
zcash_client_backend: Account for the Ironwood bundle in fee and change calculation
Adapt Ironwood fee wiring to the new tx-builder bundle-version API
This stack now sits on a tx-builder branch that replaced the crate-wide
`ANY_ORCHARD_BUNDLE_VERSION` constant with a height-derived
`orchard_bundle_version_for_height` helper, and added an Orchard/Ironwood
routing view (and a routing flag) to `ChangeStrategy::compute_balance`.
Point the proposal's Orchard bundle view at the height-derived version,
which resolves the placeholder TODO left in the "Thread a separate
Ironwood bundle view" commit. Update the zip317 fee tests to the current
`ShieldedPool` constructor name, the concrete `orchard_v2` bundle version
(the value the old constant carried), and the current `compute_balance`
signature.
Also drop the `any(zcash_unstable = "nu6.3", zcash_unstable = "nu7")`
compile-time gate around the proposal path's `orchard_outputs_are_ironwood`
decision. The build path in `wallet.rs` gates Ironwood routing purely on
runtime NU6.3 activation, so the compile-time gate made the fee estimate
diverge from the built transaction in stable builds, and `nu6.3` is not a
recognized `zcash_unstable` value, so it warned on every build. The
proposal path now mirrors the builder via `orchard_outputs_to_ironwood`.
Merge pull request #2498 from zcash/adam/ironwood-tx-builder-completion
zcash_client_backend: Wire Ironwood anchors into wallet transaction building
wallet: revert unnecessary change
CHANGELOG: mv with_ironwood_tree_mut to appropriate changed section
Assert exact Ironwood fee values; document the routing flag
Pin the expected fees in the Ironwood fee tests instead of asserting only
that one is larger, so a change to the ZIP 317 per-bundle action floor is
caught. Also note in the `compute_balance` docs why
`orchard_change_to_ironwood` is a boolean (it carries the builder's
already-computed routing decision).
Route Orchard-pool change into the Ironwood bundle to match the builder
The payment-output split already routed Orchard-pool payments into the
Ironwood view, but the change output is computed in `single_pool_output_
balance`, which counted it in the Orchard bundle even when Ironwood is
active. The builder routes Orchard-pool change into the Ironwood bundle,
so the proposal's per-bundle action counts could diverge from the built
transaction (each bundle is padded to its own action floor), defeating
the consistency this PR is meant to provide.
Thread an `orchard_change_to_ironwood` flag through
`ChangeStrategy::compute_balance` (carrying the same routing decision the
caller already computes for payments) and route the Orchard-pool change
count to the Ironwood action count when it is set.
Adds `orchard_change_routes_to_ironwood_bundle`, asserting that toggling
the flag moves the change between bundles and changes the fee.
Also: document the `ironwood`/`orchard_change_to_ironwood` parameters in
the `compute_balance` rustdoc, correct the `NetFlows` Ironwood-fields
comment, and restore the dropped note that the Orchard view still uses a
placeholder `BundleVersion`.
Note the compute_balance Ironwood view in the changelog
Charge the Ironwood bundle at proposal time
Populate the Ironwood fee view introduced previously: when the builder
will route Orchard-pool outputs into a separate Ironwood bundle, mirror
that split during input selection so the proposal's per-bundle action
counts (and therefore the fee) match the transaction that gets built.
The routing decision reuses the builder's own `orchard_outputs_to_ironwood`
predicate (threading the same `proposed_version`), so the proposal and
build paths cannot drift. The Orchard bundle keeps the Orchard spends; its
outputs move to the Ironwood bundle when routing is active. Ironwood has
no spends yet, as the wallet does not select Ironwood notes until note
detection lands.
Adds a zip317 test asserting that a populated Ironwood view increases the
required fee (the Ironwood bundle's actions are counted independently of
Orchard).
Thread a separate Ironwood bundle view through the fee/change path
V6 transactions carry separate Orchard and Ironwood bundles, each padded
to its own action floor, and `FeeRule::fee_required` already takes a
separate `ironwood_action_count`. Populate that count from a dedicated
Ironwood `BundleView` rather than the previous hardcoded zero.
`ChangeStrategy::compute_balance` and the `common.rs` helpers
(`calculate_net_flows`, `single_pool_output_balance`) gain an `ironwood`
view parameter alongside `orchard` (gated on `feature = "orchard"`,
mirroring it), `NetFlows` gains `ironwood_in`/`ironwood_out`, and the
Ironwood action count is computed via
`transactional_action_count(ironwood.bundle_version(), ...)`.
All call sites currently pass an empty Ironwood view, so this is a no-op:
the wallet does not yet route inputs/outputs to the Ironwood pool at
proposal time. A follow-up populates the view from the same routing
decision the builder uses, so the separate Ironwood bundle is priced.
Verified: compiles under default features and the nu7 cfg; the 11
zip317/fixed change-strategy tests pass unchanged.
Address review findings: routing enum, dedup, docs, expect
Model the per-step Orchard/Ironwood routing as an `OrchardBuildMode` enum
computed once, replacing the three booleans and four-arm if-else. The
spend-anchor, Ironwood inputs, and output-routing decisions now derive
from a single value so they cannot drift apart, and the `LegacyV5 +
Ironwood spends` rejection is expressed at classification time.
Replace the `ironwood_spends_requested implies shielded inputs` `expect`
with a `let-else` that returns `ProposalNotSupported` rather than
panicking on the (unreachable) `None`.
De-duplicate the cfg-gated Orchard change-output block so the Orchard
path is written once, gated by a `change_to_ironwood` flag that is
`false` in builds without Ironwood.
Document `stored_ironwood_output_index`: V6 stores Orchard and Ironwood
outputs in one index space with Orchard actions first.
Wire Ironwood anchors into wallet transaction building
Merge pull request #2522 from zcash/chore/shielded_protocol_rename
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool` and add `ShieldedPool::Ironwood`
Merge pull request #2520 from zcash/feat/pczt_ironwood_completion
pczt: Complete branch-aware v6/Ironwood support
Merge pull request #2521 from nuttycom/fix/put-transparent-output-preserves-mined-height
zcash_client_sqlite: Don't clear a recorded mined_height when storing a transparent output with unknown height
pczt: Generalize bundle version derivation over the value pool
`Creator::orchard_bundle_version()` becomes `bundle_version(pool)`, and
the crate-level revision-to-version mapping takes the `ValuePool` as an
argument (mirroring `zcash_primitives`'s `bundle_version_for_branch`),
so both the Orchard and Ironwood bundle versions are derived from the
consensus branch ID and pool alone. `with_ironwood_flags` now expresses
its version-support check as pool availability rather than a
transaction-version comparison.
Also restructure the transaction-version validation in
`extract_tx_data` as a single exhaustive match, and apply two
comment-wording suggestions in the v1 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Derive the Creator's Orchard bundle version instead of storing it
The Creator now stores the validated `BranchId` (rather than the raw
consensus branch ID) and derives the Orchard-pool bundle version from it
on demand, removing a stored field that was a pure function of state the
Creator already held.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add high-level Ironwood methods to the Signer, Verifier, and Updater
Mirrors of the corresponding Orchard-pool methods:
- `Signer::{sign_ironwood, apply_ironwood_signature}` (with new
`Error::{IronwoodSign, IronwoodVerify}` variants), so
hardware-wallet-style signers no longer need the low-level Signer to
authorize Ironwood actions.
- `Verifier::with_ironwood`
- `Updater::update_ironwood_with`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Add v6/Ironwood support to the Creator and validate at extraction
- `Creator::new` now derives the transaction version and version group
ID from the consensus branch ID: the v6 format from NU6.3 onward, and
the v5 format for earlier upgrades. Previously the Creator could only
produce v5 PCZTs.
- New `Creator::with_ironwood_anchor` and `Creator::with_ironwood_flags`
methods configure the Ironwood bundle independently of the Orchard
bundle; both reject configuration of an Ironwood bundle for a
transaction version that does not carry one
(`Error::IronwoodNotSupported`).
- Transaction extraction now rejects v6 PCZTs whose consensus branch ID
predates NU6.3, and non-v6 PCZTs carrying non-canonical Ironwood
bundle data (`ExtractError::IronwoodNotSupported`), instead of
silently dropping the Ironwood data or extracting a consensus-invalid
transaction.
- The v1 serialization format refuses to encode v6 PCZTs, which a v1
parser could decode but never extract a transaction from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pczt: Canonicalize the empty states of the PCZT bundles
The Creator, `Creator::build_from_parts`, the v1 and v2 serialization
formats, and the IO Finalizer previously produced four different
representations of an "empty" Ironwood bundle (and inconsistent empty
Sapling/Orchard bundles), which broke `Bundle::merge` for workflows that
mix serialization-round-tripped and in-memory copies of the same PCZT.
Each bundle type now has a single canonical empty representation,
defined as a constant in its module and used consistently everywhere:
- The Creator's Ironwood placeholder no longer mirrors the Orchard
bundle's flags and anchor; it is exactly the canonical empty bundle
(whose flags are the Ironwood defaults: spends, outputs, and
cross-address transfers enabled).
- `Creator::build_from_parts` uses the canonical empty bundles (with
all-zeroes anchors, rather than the empty-tree root) for absent
bundles, and now maps `parts.ironwood` through instead of discarding
it, so the builder-to-PCZT path no longer loses Ironwood spends and
outputs.
- Empty bundle value sums are initialized as non-negative zero,
matching the form produced by re-serializing a parsed bundle.
- The IO Finalizer no longer sets `bsk` on an empty Orchard-protocol
bundle. (The Sapling bundle is still always finalized, because the
Sapling Transaction Extractor requires `bsk` even for an empty
bundle.)
- The v2 format's omission decision is now exact equality with the
slot's canonical empty bundle, so a bundle with non-default flags or
note version round-trips losslessly instead of being restored with
default values; the v1 format refuses to encode a PCZT whose Ironwood
bundle is not canonically empty, rather than silently dropping its
non-default fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_client_sqlite: Don't clear mined_height when storing an output with unknown height
put_transparent_output's transactions upsert overwrote mined_height unconditionally,
so re-storing a transparent output whose mined height is unknown to the caller (e.g.
one re-observed via the mempool, or fetched from a backend that could not locate the
transaction on the best chain) clobbered a previously-recorded mined height with NULL,
marking a mined transaction as unmined. A NULL height carries no evidence that the
recorded height is wrong; preserve it, matching the adjacent handling of the block
column. Genuine un-mining remains the responsibility of truncate_to_height.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
zcash_protocol: Add `ShieldedPool::Ironwood`
This introducese `todo!(...)` stanzas in each location in
`zcash_client_backend` and `zcash_client_sqlite` where Ironwood
support infrastructure has not yet been implemented.
zcash_protocol: Rename `ShieldedProtocol` to `ShieldedPool`
It is no longer appropriate to refer to the Orchard "shielded protocol"
as there are three different variants of the Orchard shielded protocol.
`zcash_protocol::ShieldedProtocol` has historically been used as a pool
identifier; the rename here reifies this change.
Merge pull request #2516 from zcash/fix/pr2509_review_followup
zcash_primitives: Address post-merge review comments from PR #2509
zcash_client_backend: Derive the PCZT test proving key from the consensus branch
The `pczt_single_step` pool test hardcoded the post-NU6.2 fixed-circuit
proving key, which was consistent with the PCZT roles' previous pinning
of the legacy Orchard bundle version but mismatches the branch-derived
parsing, proving, and verification those roles now perform: the shared
test network fixture's most recent upgrade is NU5, under which the
Orchard pool is governed by the historical pre-NU6.2 circuit, so proof
verification failed at extraction. Derive the proving key from the
PCZT's own consensus branch ID instead. (Modernizing the test network
fixture so that wallet tests exercise current consensus is part of the
Ironwood test coverage work tracked in #2518.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add OrchardProtocolRevision to zcash_protocol; make all PCZT roles branch-aware
The consensus knowledge of which Orchard protocol revision each network
upgrade deploys now lives in a single place:
`zcash_protocol::consensus::BranchId::orchard_protocol_revision`, which
returns the new `OrchardProtocolRevision` enum (`None` for branches that
predate NU5). `zcash_primitives`'s `bundle_version_for_branch` and the
`pczt` crate both derive `orchard::bundle::BundleVersion`s from it via
trivial conversions, replacing the duplicated branch-to-version matches.
(`pczt` cannot reuse the `zcash_primitives` helper directly because its
`zcash_primitives` dependency is optional and must not be required by
the lightweight role builds.)
Building on this, the remaining PCZT roles (Updater, Verifier, Prover,
and the low-level Signer) now parse the Orchard-pool bundle using the
bundle version implied by the PCZT's consensus branch ID rather than
pinning the legacy pre-NU6.3 version, completing the branch-aware
parsing work begun with `extract_tx_data`; in particular, the Prover now
creates proofs for the correct circuit for post-NU6.3 Orchard-pool
bundles. Roles report new `UnsupportedConsensusBranchId` errors when the
branch ID is unrecognized or predates NU5, and the low-level Signer's
Orchard-protocol signing methods now bound their error parameter by
`From<OrchardParseError>`. `Bundle::into_orchard_parsed`, which pinned
the legacy version, is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address remaining review comments from PR #2509
- pczt: fix extraction and verification of post-NU6.3 Orchard-pool
bundles. `extract_tx_data` now parses the Orchard-pool bundle using
the bundle version implied by the PCZT's consensus branch ID rather
than pinning the legacy pre-NU6.3 version, and the Transaction
Extractor's bundle verification derives the circuit version from the
bundle itself rather than hardcoding the NU6.2 fixed circuit (which
could not verify post-NU6.3 Orchard-pool bundles). The
`verify_bundle`/`verify_ironwood_bundle` duplication collapses into a
single function. Other PCZT roles still parse the Orchard-pool bundle
with the legacy version pin; completing those is tracked as follow-up
work.
- zcash_primitives: note the ZIP 229 specification of the v6 Sapling
digest variants in the `txid.rs` documentation, and correct the
`BlockTxCommitmentDigester::HeaderDigest` doc comment.
- zcash_client_backend: add a ZIP 317 change-calculation test covering
the post-NU6.3 Orchard action-count policy (one action per spend and
per output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derive Orchard-protocol bundle versions from consensus context
Review followups:
- Add `bundle_version_for_branch`, returning the `BundleVersion` in
effect for a given Orchard-protocol value pool under a given consensus
branch, or `None` when the pool is not supported under that branch
(the Orchard pool prior to NU5; the Ironwood pool prior to NU6.3).
- `read_v5_bundle` now takes the consensus branch ID, and
`read_v6_bundle` the consensus branch ID and the `orchard::ValuePool`
identifying the bundle slot to read, in place of caller-supplied
`orchard::bundle::BundleVersion` arguments. Callers can no longer
select a wrong-slot bundle version, and a non-empty bundle belonging
to a pool that is unsupported under the transaction's consensus branch
ID is rejected as invalid data.
- In `Builder::new`, the `None` result subsumes the explicit
NU5-activation check that previously gated construction of the Orchard
builder.
- Remove `zcash_client_backend::ANY_ORCHARD_BUNDLE_VERSION`. The fee and
change calculation call sites that used it all have access to the
proposal's target height, and now derive the Orchard bundle version
(and hence the action-count policy) from it. As a consequence,
proposals targeting heights at or beyond NU6.3 activation now count
one action per Orchard spend or output, matching the post-NU6.3
transaction builder. `EmptyBundleView` retains a fixed version, which
is sound because an empty bundle produces zero actions under every
version's action-count policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_primitives: Address post-merge review comments from PR #2509
- `read_v5_bundle` now takes the consensus branch ID, deriving the
Orchard bundle version via the new public helper
`orchard_bundle_version_for_branch` (an exhaustive match, replacing
both the wildcard-arm private helper in the builder and the inline
mapping in `Transaction::read_v5`); this also removes an incomplete
sentence from its documentation.
- Make `TxVersion::has_ironwood` an exhaustive match.
- Simplify the `arb_txdata` proptest strategies:
`arb_ironwood_bundle_for_version` already yields `None` for versions
without Ironwood support.
- Factor out `Builder::orchard_in_use` to mirror `ironwood_in_use`.
- Correct the coinbase Orchard-builder flag comment, strengthen the
`with_expiry_height` warning, and fix stale `txid.rs` documentation.
- Give NU6.3 and NU7 distinct activation heights in the nu7 builder
test network.
- Add a test checking that Orchard note commitments and nullifiers are
domain-separated by the note plaintext version (lead byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address issues found in review of the NU6.3 testnet support branch
- `zcash_protocol`: under `zcash_unstable = "nu7"`, `BranchId::Nu6_3` now
reports the NU7 activation height as the upper bound of its height
range. The cfg-gated upper-bound pattern was dropped from the `Nu6_2`
arm when the `Nu6_3` arm was introduced, causing the Nu6_3 and Nu7
epochs to overlap in nu7 builds.
- `zcash_primitives`: the transaction builder's version-compatibility
check now accounts for Orchard change outputs, matching the Ironwood
check. Previously a change-only Orchard bundle passed the compatibility
check for `TxVersion::V4`, resulting in a reachable panic in `build()`.
- `zcash_primitives`: document previously-omitted API changes in the
0.29.0-pre.0 CHANGELOG entry (`map_bundles`/`try_map_bundles` closure
semantics, `TransactionDigest::combine` `ironwood_digest` argument,
`BlockTxCommitmentDigester::HeaderDigest` type change,
`BuildConfig::orchard_builder_config` removal,
`TxVersion::has_ironwood`).
- `zcash_transparent`: restore Keep-a-Changelog section ordering in the
0.9.0-pre.0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply minor review suggestions from PR #2509
- Fix the `expected_nu7` regtest activation heights to differ from the
NU6.3 height, assert NU6.3 activation at its own height, and check NU7
non-activation against the NU6.3 height in the `zcash_protocol`
`local_consensus` tests.
- Remove the stale placeholder-branch-ID paragraph from the
`BranchId::Nu6_3` docs.
- Simplify the `BranchId::Nu6_2` height-bounds arm to …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ironwood notes are stored alongside Orchard notes inorchard_received_notes,distinguished by
note_version(added in #2532).This assigns Ironwood the
distinct SQLite
output_poolcode 4 and reports Ironwood outputs under it in thewallet-facing views.
Changes
pool_code/parse_pool_code(transparent 0, Sapling 2, Orchard 3, Ironwood 4), replacing the
todo!.v_received_outputsandv_received_output_spendsderive the pool code fromnote_version(Orchard = 3, Ironwood = 4).v_transactionsandv_tx_outputspropagate the pool code, so they pick this up automatically.
ironwood_pool_code_views) drops and recreates the four viewsat their updated definitions, and repoints any existing sent note that
corresponds to a received Ironwood note from pool code 3 to 4. It depends on all
current DAG leaves so the views are recreated at their latest definition.
Tests
pool_coderound-trip and distinctness.arb_orchard_noteandarb_ironwood_notestrategies) asserting that,regardless of note payload, an Orchard note is reported by
v_received_outputsunder pool code 3 and an Ironwood note under pool code 4.
verify_schemaconfirms the recreated views match their definitions.Adapted from valargroup/librustzcash@d94102c3ea and the fork's
ironwood_pool_code_viewsmigration(
adam/qleak-pr44-orchard-dummy-ciphertexts).🤖 Generated with Claude Code