Skip to main content

Module pool_migration

Module pool_migration 

Source
Expand description

SQLite persistence for value-pool migrations (ZIP 318).

This module implements zcash_pool_migration’s PoolMigrationRead / PoolMigrationWrite store traits over a set of SQLite tables in the wallet database, mirroring how this crate implements zcash_client_backend’s WalletRead / WalletWrite. A committed migration is a set of pre-signed PCZTs plus their schedule and lifecycle state, so a wallet resumes a migration entirely from these tables after being closed or restarted.

The schema is fully NORMALIZED: every structured value (the denomination plan, the preparation plan’s transaction inputs/outputs and direct-funding notes, the transaction kind, and the dependency graph) is stored in typed columns and child tables, so it can be queried directly. The BLOB columns are the pre-signed transaction (pczt), which is genuinely unstructured, already-versioned bytes, and the transaction’s lock_owner (an opaque fixed-size token, not a structured value); all amounts are zatoshi INTEGER columns and the broadcast txid is hex TEXT. It is also MINIMAL: values derivable from other columns get no tables of their own (the funding-note values are the crossing values plus the fee buffer, and the preparation plan’s layers/transactions grid is implied by the input and output rows’ (layer, tx_index) coordinates, since a real plan has no empty layer and no transaction without inputs and outputs).

§Structure: one generic store, one public submodule per pool

The generic, pool-agnostic store machinery (the DDL builders and the SQL store logic) lives in a private store submodule, parameterized over the per-pool table names, with the error type in a private error submodule. Because the schema is normalized, the store maps the engine types to and from typed columns and child-table rows directly (only the opaque pczt is stored as bytes), rather than through a blob codec. Each pool migration is a public submodule that instantiates the store with its own table names and exposes the concrete API; the generic store type never appears in the public surface. This lets future pool migrations reuse the same machinery under their own tables. Currently the only such submodule is orchard_ironwood (the Orchard -> Ironwood migration), whose tables are all prefixed orchard_ironwood_migration[s]_.

§Schema registration

The pool-migration tables live in the same wallet.db as everything else and share its schema versioning: a schemerz migration in crate::wallet::init::migrations (for Orchard -> Ironwood, orchard_ironwood_migration_tables) creates them, and later migrations evolve them.

Each pool submodule also exposes its tables’ CURRENT shape as an idempotent init_migration_tables. That is the shape a wallet has once every migration has run — not the DDL any released migration executes, since a published migration’s effect on an existing database must not change when the schema does. A pool whose creating migration has not shipped yet can be created from it directly.

§Chain-derived state, in both directions

A stored migration’s chain-derived state — the unsatisfiability marks, and which of its transactions are mined — follows this wallet’s scan, and neither direction is the consumer’s to drive.

FORWARD, a transaction is recorded mined because the scan has seen it: the store answers PoolMigrationRead::mined_height from the wallet’s own transactions table, bounded by the fully-scanned height, and advance_migration promotes every in-flight transaction it sweeps. A consumer records that it BROADCAST — testimony no scan can supply — and nothing more.

BACKWARD, it is rolled back with the wallet: WalletWrite::truncate_to_height (and everything routed through it) drives each stored migration’s own MigrationState::truncate_to_height at the height it ACTUALLY truncated to, in the same database transaction.

So a consumer has no hook to remember in either direction: a mark can never rest on an observation the wallet has discarded, and a transaction can neither stay recorded mined above the wallet’s own view of the chain nor lag behind it.

§Model

There is at most one migration in progress per pool per account, stored as a row in the pool’s migrations table keyed by an account_id foreign key into accounts (with ON DELETE CASCADE, so an account’s migration is removed with the account), with its transactions, denomination plan, and preparation plan in the pool’s child tables (addressed through that row’s synthetic primary key). The pool’s PoolMigrations type is the store: construct it over a rusqlite::Connection (the same one WalletDb uses) and the AccountUuid whose migration it tracks, which it resolves to that account’s row up front.

Modules§

orchard_ironwood
The SQLite pool-migration store instantiated for the Orchard -> Ironwood migration (ZIP 318); tables prefixed orchard_ironwood_migration[s]_.

Structs§

CancelOutcome
What a cancel_migration call found and did, transaction by transaction: cancel is honest about what it cannot undo, so it SUCCEEDS and reports rather than refusing once something is in flight (refusal would reintroduce the stuck state cancel exists to remove).
MigrationSummary
One row of a migration-history listing: the identity, status, and aggregate progress of a single migration an account has run, PROJECTED IN SQL from the store’s typed columns.
MigrationUuid
The stable external identity of one pool-migration record, following the AccountUuid precedent: random per record, preserved across every rewrite of the record’s state, and safe to hand across an FFI — unlike the store’s row ids, which are not stable across a restore or an export. Obtained from MigrationSummary::id, and resolved back through a pool facade’s get_migration_by_id.