Skip to content

zallet-core: add zcash_ironwood_migration_backend dependency scaffold#623

Draft
dannywillems wants to merge 22 commits into
mainfrom
feat/ironwood-migration-backend-dep
Draft

zallet-core: add zcash_ironwood_migration_backend dependency scaffold#623
dannywillems wants to merge 22 commits into
mainfrom
feat/ironwood-migration-backend-dep

Conversation

@dannywillems

Copy link
Copy Markdown
Contributor

Wires the new backend-agnostic zcash_ironwood_migration_backend crate (the Orchard -> Ironwood migration engine) into zallet as a dependency scaffold, independent of the crate's still-evolving API.

  • Adds zcash_ironwood_migration_backend pinned to the feat/ironwood-denominations git branch of zcash/librustzcash (the crate is unpublished; to be repointed at a release later).
  • Wires it through zallet-core via a thin zallet_core::migrate scaffold module (a whole-crate re-export, no coupling to the evolving API), so all three workspaces (root, backends/zebra, backends/zaino) resolve its git-sourced transitive librustzcash crates identically and check-lockstep.sh stays green.
  • Regenerates all three lockfiles via utils/sync-lockfiles.sh; CHANGELOG entry included.

Note: the separate "sync existing librustzcash deps" commit was a verified no-op (all pins already at their latest published versions), so this is a single commit. Verified: cargo test 194 passed, both backends cargo check clean, fmt/clippy clean, lockstep OK.

Draft; part of the Ironwood migration effort. Do not merge yet.

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

dannywillems and others added 8 commits July 18, 2026 10:40
Wire the backend-agnostic Orchard-to-Ironwood value-pool migration engine
into the workspace as a first, minimal integration point.

The zcash_ironwood_migration_backend crate is not yet published to
crates.io; it currently lives only on the feat/ironwood-denominations
branch of librustzcash while its API is still evolving. It is therefore
pinned to that git branch rather than to a released crates.io version like
the other librustzcash crates. Repoint it at a released version once one is
published.

The crate is added as a dependency of zallet-core, which both backend
binaries statically link, so the engine and its git-sourced transitive
librustzcash crates resolve identically across all three workspace
lockfiles and utils/check-lockstep.sh stays green. The three lockfiles were
regenerated together with utils/sync-lockfiles.sh.

The new zallet_core::migrate module is only a scaffold: it re-exports the
engine so the rest of Zallet has a single stable path to it, and does not
depend on the shape of the engine's final API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a pool-to-pool value-pool migration RPC surface to the wallet build,
following the existing json_rpc method conventions (one module per method,
each with a Response/ResultType alias, PARAM_*_DESC constants consumed by the
OpenRPC build script, and a call function invoked from WalletRpcImpl):

  z_startpoolmigration      schedule a migration between two pools
  z_getpoolmigrationstatus  report a migration's status
  z_advancepoolmigration    advance a migration by one step
  z_cancelpoolmigration     cancel a migration
  z_listpoolmigrations      list known migrations

The surface is generic, not hardcoded Orchard to Ironwood. It is parameterised
by a Pool pair ("sapling", "orchard", "ironwood"), and a single shared table,
SUPPORTED_MIGRATIONS, is the one extension point: it maps each supported pool
pair to the network upgrade that enables it (Orchard to Ironwood requires
NU6.3). Adding a pool pair there makes it selectable across the whole surface.
Pool names and the not-implemented message are named constants, not literals.

This is a scaffold. Each method validates its inputs (pool parsing, the
supported-pair table, and that the enabling network upgrade is active at the
wallet's chain height), but the call into the migration engine returns a clear
"not implemented yet" error. The engine (zcash_ironwood_migration_backend) is
still evolving upstream, so nothing here depends on the shape of its final API.
The response types are defined so the rpc.discover schema documents the intended
shapes; MigrationPhase is marked with expect(dead_code) until the engine
constructs it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the read-only planning slice of the pool-migration surface: a new
`z_previewpoolmigration` RPC that reads an account's spendable source-pool
balance and runs the note-split planner
(`zcash_ironwood_migration_backend::note_splitting`) to return the proposed
decomposition into self-funding notes: the crossing denominations, the residual
left behind, and the reserved prep fee. It plans only; nothing is scheduled,
built, proved, or broadcast, so unlike the rest of the migration surface it does
not depend on the unreleased Ironwood PCZT builder.

Inputs are validated (the supported pool pair and its enabling network upgrade,
the strategy name, and the account); the response is a documented, schema-typed
plan. The strategy object is not `Send`, so the name is validated before any
wallet I/O and the strategy is built after the last await.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the migration engine dependency from zcash_ironwood_migration_backend
to zcash_pool_migration_backend (renamed upstream to a pool-agnostic name) and
repoint it, for local development, at a relative path to the checked-out
worktree of the feat/pool-migration-engine branch. This is not portable and
must not be pushed as-is; the Cargo.toml comment records the git-rev form to
switch to first, and its prerequisite (the engine commit that introduces the
current MigrationBackend trait is not yet on any remote).

Rewrite z_previewpoolmigration against the engine's planning slice. It now
enumerates the account's individual spendable Orchard notes (mirroring
z_listunspent's selection and confirmation filter) and calls
engine::plan_migration over SpendableSnapshot, Zallet's MigrationBackend
implementation. The response reports the reconciled funding notes with each
note's crossing denomination, its transfer broadcast height and expiry, the
raw note-split summary, and the note-preparation summary. The old strategy
parameter is dropped: the engine no longer exposes a strategy choice, so the
method takes only account, from_pool, to_pool, and minconf.

SpendableSnapshot is planning-only: the engine's persistence methods
(store/load/update) are not supported, so committing a migration stays behind
the not-implemented start/status/advance/cancel/list scaffold until the engine
adds its build, sign, and persist slices.

The prep fee reserved for the note split and preparation planner is derived
from the crate's own constants (PREP_TX_ACTIONS times the ZIP-317 marginal
fee), not a hardcoded literal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the migrate integration entry for the renamed engine crate and its
MigrationBackend impl, and record z_previewpoolmigration as the fully-wired
planning preview of the pool-migration surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the local-development path dependency on the migration engine crate
with a git dependency pinned to the commit that carries the engine API this
integration uses (the MigrationBackend trait plus engine::plan_migration),
now that it is pushed to librustzcash. Regenerate the workspace and backend
lockfiles for the git source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point the git dependency at the engine commit that adds commit_preparation
and commit_transfers (the two-phase build/sign path). The MigrationBackend
trait the preview integration uses is unchanged, so this is a rev bump plus
lockfile regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI resolves the git dependency via the canonical https://...librustzcash.git
URL (GitHub Actions rewrites git URLs), so a lock recorded against the
no-.git form is seen as out of date under --locked (the vet and Docker build
jobs failed with "the lock file needs to be updated"). Use the .git form,
matching the repository convention (the age patch), and regenerate the
workspace and backend lockfiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dannywillems
dannywillems force-pushed the feat/ironwood-migration-backend-dep branch from 3fff95d to 526b6de Compare July 18, 2026 08:44
dannywillems and others added 7 commits July 18, 2026 11:14
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump the librustzcash pin to the rev that carries the S1/S2/S3 slices: the
PoolMigrationRead/PoolMigrationWrite store traits, the zcash_pool_migration_sqlite
store crate, and the WalletMigration wallet-backed adapter (the `wallet`
feature). This is the foundation for wiring z_startpoolmigration and the
companion RPCs onto the real engine commit path.

S1 split persistence out of MigrationBackend into the store traits, so the
planning-only SpendableSnapshot no longer implements store_migration /
load_migration / update_transaction; it now implements just the two query
methods and is infallible. The preview RPC's error mapping and its test backend
follow suit. zallet-core builds against the new pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the not_implemented scaffolds for z_startpoolmigration,
z_getpoolmigrationstatus, z_listpoolmigrations, and z_cancelpoolmigration
with real bodies over the migration engine and the SQLite store.

start plans and pre-signs the preparation transactions over the account's
wallet (WalletMigration adapter) and persists the committed migration (its
pre-signed PCZTs plus metadata) to the store; it derives the spending key
before the blocking build section, runs the engine against an in-memory store,
then persists the returned state to SQLite over one connection sequentially,
and refuses to overwrite a migration that is still in progress. status and list
read the persisted state; cancel marks it cancelled. Nothing is proved or
broadcast here, so no transaction reaches the chain.

The store holds one migration at a time, named by a fixed identifier. Proving
and broadcasting the pre-signed transactions (z_advancepoolmigration) is the
remaining step; until it is wired a committed migration cannot make on-chain
progress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The zebra and zaino backend workspaces patch the librustzcash stack
independently of the main workspace; bump their pins to match so they build
against zallet-core's re-pinned dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Advance drives a committed migration forward one step per call: it detects
newly mined transactions (get_tx_height, which reports only a tx mined at or
below the tip), proves and extracts the next due pre-signed transaction, and
broadcasts it; once every preparation is mined it builds and pre-signs the
phase-2 transfers. Proving runs in the blocking database section and broadcast
runs async, so no await occurs under the write lock.

Migration transactions are Orchard-shaped, so both the Orchard and Ironwood
bundles prove with one orchard::circuit::ProvingKey and verify with one
VerifyingKey; the circuit version is the Orchard bundle version for the tip's
consensus branch (NU6.3 = revision 3). Adds the pczt dependency (prover,
tx-extractor, orchard).

Known: the lifecycle is compile-checked here and must still be driven end to
end on a funded regtest migration; expiry/reorg recovery is not yet handled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The zebra and zaino backend workspaces have their own lockfiles; regenerate
them so the bumped patch pins resolve under --locked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engine now commits a multi-layer preparation phase by phase: start builds
and pre-signs only the first layer, and each later layer is built once its
predecessor is mined. Re-pin onto that engine revision and teach the advance
state machine the intermediate step: after nothing is left to broadcast but
before the transfers, build the next preparation layer whose dependencies are
mined (commit_pending_preparation_over_wallet), so its transactions become
broadcastable. The transfers still build only once every preparation layer is
mined.

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

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless. Can be removed.

dannywillems and others added 7 commits July 18, 2026 19:48
Multi-layer preparation is supported now (committed layer by layer), so the
CommitFailure::UnsupportedMultiLayer variant and its "not yet supported" RPC
message are dead and misleading; remove them (the residual engine variant maps
to a generic build failure). Refresh the module docs that still described the
surface as a scaffold with an unwired advance and a not_implemented start, drop
the now-broken intra-doc link to the removed not_implemented helper, and remove
the "stubbed" notes on MigrationPlan/MigrationProgress (both are real). Remove
the pool_migration unit tests, which only restated the table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align zcash_note_encryption across the three workspaces: root and zebra resolve
0.4.1 while zaino had floated to 0.4.2, tripping the wallet-critical lockstep
check. Pin zaino back to 0.4.1 (already audited safe-to-deploy in every
workspace), the minimal change that restores lockstep without a new vet gap.
Exempt darling and serde_with (pulled in transitively by pczt, which the
migration advance uses to prove and extract) in the root and zaino supply-chain
configs; zebra already exempted them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engine dropped CommitError::UnsupportedMultiLayer (multi-layer preparation
is fully supported), so remove the corresponding map_commit_error arm and re-pin
to that revision. The revision changes no dependencies, so the lockfiles move
only the git rev and the wallet-critical lockstep is unchanged.

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

A multi-layer migration cannot be pre-signed up front (later layers become
witnessable only as earlier layers mine) and a mobile wallet may be restarted
between layers, so it needs to decide the next transaction to sign or broadcast
from persisted state alone. z_getpoolmigrationstatus now returns, per
transaction, its kind, layer (anchor bucket), lifecycle state, dependencies,
schedule, whether it is ready to act on now and with which action
(build-and-sign or prove-and-broadcast), or otherwise what it is waiting on
(its dependencies mining, or its scheduled height). The status RPC reads the
chain tip to decide which scheduled transactions are due.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration state logic now lives in the engine as methods on MigrationState
(the mobile wallet drives it the same way). Reduce zallet to a thin consumer:
advance_blocking detects mining and does the wallet I/O around the engine's
decision (state.next_step -> prove+broadcast / build the next preparation layer
/ build the transfers / wait), using state.mark_mined and state.mark_broadcast
for the transitions; the per-transaction status RPC maps state.transaction_
statuses into the JSON-RPC shape. Remove the state helpers now owned by the
engine (deps_mined, all_preparations_mined, has_ready_prep_layer,
next_broadcastable, recompute_status, is_terminal) and re-pin to that engine
revision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend lockfile regeneration in an earlier re-pin floated many of zaino's
transitive dependencies (aws-lc-rs, tokio, and others) ahead of the versions
its supply-chain config vets, so cargo-vet reported 81 unvetted crates. Rebase
the zaino lockfile on main's vetted versions and add only the migration engine
dependencies on top, and restore the root and zebra lockfiles to their minimal
pre-churn state, all still pinned to the engine revision. Wallet-critical
lockstep and zcash_note_encryption 0.4.1 are preserved; cargo-vet passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-pin to the engine that preserves a terminal status, and guard advance so a
terminal migration (complete, or cancelled/failed) is not driven: it reports its
status and does nothing, so a cancelled migration cannot be resurrected. The
revision changes no dependencies, so lockstep is unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant