1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Adds tables for storage of an in-progress Orchard -> Ironwood value-pool migration.
//!
//! A pool migration ([ZIP 318]) decomposes a spendable Orchard balance into self-funding notes and
//! crosses their value into the Ironwood pool as a set of pre-signed transactions. This migration
//! adds the `orchard_ironwood_migrations` and `orchard_ironwood_migration_transactions` tables that
//! persist that in-progress migration, so a wallet resumes it after being closed or restarted. Each
//! account's migration is tracked independently: `orchard_ironwood_migrations` keys by an
//! `account_id` foreign key into `accounts` (with `ON DELETE CASCADE`), enforced unique so an
//! account has at most one migration in progress.
//!
//! The store implementation over these tables lives in [`crate::pool_migration`]. This migration is
//! the registration that creates them inside the wallet schema, and it depends on
//! [`ironwood_received_notes`] because the migration writes Ironwood notes, whose tables must exist.
//!
//! The DDL below is a FROZEN COPY of the text a released build of this migration actually ran, not
//! a call into the store's canonical DDL. This migration is published, so its effect is observable
//! by anything that anchors to it: an external [`schemerz`] migration may declare this migration as
//! a dependency and a later one as a dependent, and it then runs against exactly the schema these
//! statements create — down to the `tx_id` column that
//! [`orchard_ironwood_migration_unsatisfiability`] renames to `transfer_id`. Sourcing the text from
//! a shared builder would let an edit made for a future database silently change what an
//! already-published migration does to an existing one. The canonical DDL in
//! `crate::pool_migration::store` states the FINAL shape instead; a fresh database reaches that
//! shape by running this migration and then the later ones, and the two are pinned together by
//! `verify_schema` and `canonical_pool_migration_ddl_matches_the_migration_path`.
//!
//! [ZIP 318]: https://zips.z.cash/zip-0318
//! [`orchard_ironwood_migration_unsatisfiability`]:
//! super::orchard_ironwood_migration_unsatisfiability
use HashSet;
use RusqliteMigration;
use Uuid;
use crateWalletMigrationError;
use ironwood_received_notes;
/// Adds tables for storage of an in-progress Orchard -> Ironwood value-pool migration.
pub const MIGRATION_ID: Uuid = from_u128;
// The pool-migration tables have no foreign keys into the note or shardtree tables, but the engine
// works over both pools at runtime: it spends Orchard source notes (and their witnesses) and crosses
// into Ironwood. `ironwood_received_notes` is chosen as the single frontier dependency because its DAG
// closure transitively guarantees the whole stack the engine needs (Orchard received notes and
// shardtree, Ironwood received notes and shardtree), so listing those explicitly would be redundant
// under the codebase's minimal-frontier convention. If a future migration reshuffles the DAG such that
// `ironwood_received_notes` no longer pulls in the Orchard source infrastructure, add the Orchard
// dependencies here explicitly.
const DEPENDENCIES: & = &;
/// The pool-migration table and index DDL byte-for-byte as `zcash_client_sqlite 0.22.0-rc.6` ran
/// it — the store's `init` batch of that release, rendered for this pool's table names, down to the
/// `tx_id` column name and the `144` ZIP 318 anchor grid as a literal rather than the constant it
/// was interpolated from. Every release that ships these tables ran this text but for one
/// character-level difference: `0.22.0-rc.2` through `0.22.0-rc.5` created `anchor_bucket_interval`
/// without its `DEFAULT 144`, which `0.22.0-rc.6` added.
///
/// This MUST NEVER CHANGE. It is not a description of the current schema and must not be updated to
/// track one: see this module's documentation for why an already-published migration's effect
/// cannot follow the store's evolving DDL. The only edit it may ever take is a correction to what a
/// released build actually ran. The statements are idempotent (`IF NOT EXISTS`) and ordered so each
/// foreign-key target is created before the table referencing it.
pub const CREATE_TABLES_SQL: &str = "CREATE TABLE IF NOT EXISTS orchard_ironwood_migrations (
id INTEGER PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
status TEXT NOT NULL,
note_split_fee_buffer INTEGER NOT NULL,
note_split_change INTEGER,
note_split_prep_fees INTEGER NOT NULL,
note_split_total_input INTEGER NOT NULL,
note_split_total_migratable INTEGER NOT NULL,
anchor_bucket_interval INTEGER NOT NULL DEFAULT 144
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_crossing_values (
migration_id INTEGER NOT NULL REFERENCES orchard_ironwood_migrations(id) ON DELETE CASCADE,
ordinal INTEGER NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (migration_id, ordinal)
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_prep_inputs (
migration_id INTEGER NOT NULL REFERENCES orchard_ironwood_migrations(id) ON DELETE CASCADE,
layer INTEGER NOT NULL,
tx_index INTEGER NOT NULL,
ordinal INTEGER NOT NULL,
source TEXT NOT NULL,
wallet_index INTEGER,
prior_layer INTEGER,
prior_transaction INTEGER,
prior_output INTEGER,
value INTEGER NOT NULL,
PRIMARY KEY (migration_id, layer, tx_index, ordinal)
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_prep_outputs (
migration_id INTEGER NOT NULL REFERENCES orchard_ironwood_migrations(id) ON DELETE CASCADE,
layer INTEGER NOT NULL,
tx_index INTEGER NOT NULL,
ordinal INTEGER NOT NULL,
role TEXT NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (migration_id, layer, tx_index, ordinal)
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_prep_direct_funding (
migration_id INTEGER NOT NULL REFERENCES orchard_ironwood_migrations(id) ON DELETE CASCADE,
ordinal INTEGER NOT NULL,
wallet_index INTEGER NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (migration_id, ordinal)
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_transactions (
migration_id INTEGER NOT NULL REFERENCES orchard_ironwood_migrations(id) ON DELETE CASCADE,
tx_id INTEGER NOT NULL,
kind TEXT NOT NULL,
kind_layer INTEGER,
kind_index INTEGER,
kind_crossing INTEGER,
pczt BLOB NOT NULL,
scheduled_height INTEGER NOT NULL,
expiry_height INTEGER NOT NULL,
anchor_boundary INTEGER,
state TEXT NOT NULL,
txid TEXT,
mined_height INTEGER,
lock_owner BLOB,
PRIMARY KEY (migration_id, tx_id)
);
CREATE TABLE IF NOT EXISTS orchard_ironwood_migration_transaction_deps (
migration_id INTEGER NOT NULL,
tx_id INTEGER NOT NULL,
ordinal INTEGER NOT NULL,
depends_on_tx_id INTEGER NOT NULL,
PRIMARY KEY (migration_id, tx_id, ordinal),
FOREIGN KEY (migration_id, tx_id)
REFERENCES orchard_ironwood_migration_transactions(migration_id, tx_id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_orchard_ironwood_migration_tx_due ON orchard_ironwood_migration_transactions (state, scheduled_height);
CREATE UNIQUE INDEX IF NOT EXISTS idx_orchard_ironwood_migrations_account ON orchard_ironwood_migrations (account_id);";
pub ;