diff --git a/zcash_client_sqlite/src/wallet/db.rs b/zcash_client_sqlite/src/wallet/db.rs index 64b45acc0f..65ce3b148e 100644 --- a/zcash_client_sqlite/src/wallet/db.rs +++ b/zcash_client_sqlite/src/wallet/db.rs @@ -106,8 +106,8 @@ CREATE INDEX "addresses_accounts" ON "addresses" ( /// /// All but the last `GAP_LIMIT` addresses are defined to be "reserved" addresses. Since the next /// index to reserve is determined by dead reckoning from the last stored address, we use dummy -/// entries after the maximum valid index in order to allow the last `GAP_LIMIT` addresses at the -/// end of the index range to be used. +/// entries having `NULL` for the value of the `address` column after the maximum valid index in +/// order to allow the last `GAP_LIMIT` addresses at the end of the index range to be used. /// /// Note that the fact that `used_in_tx` references a specific transaction is just a debugging aid. /// The same is mostly true of `seen_in_tx`, but we also take into account whether the referenced @@ -116,6 +116,7 @@ pub(super) const TABLE_EPHEMERAL_ADDRESSES: &str = r#" CREATE TABLE ephemeral_addresses ( account_id INTEGER NOT NULL, address_index INTEGER NOT NULL, + -- nullability of this column is controlled by the index_range_and_address_nullity check address TEXT, used_in_tx INTEGER, seen_in_tx INTEGER, @@ -123,6 +124,7 @@ CREATE TABLE ephemeral_addresses ( FOREIGN KEY (used_in_tx) REFERENCES transactions(id_tx), FOREIGN KEY (seen_in_tx) REFERENCES transactions(id_tx), PRIMARY KEY (account_id, address_index), + CONSTRAINT ephemeral_addr_uniq UNIQUE (address), CONSTRAINT used_implies_seen CHECK ( used_in_tx IS NULL OR seen_in_tx IS NOT NULL ), @@ -135,10 +137,6 @@ CREATE TABLE ephemeral_addresses ( // libsqlite3-sys requires at least version 3.14.0. // "WITHOUT ROWID" tells SQLite to use a clustered index on the (composite) primary key. const_assert_eq!(GAP_LIMIT, 20); -pub(super) const INDEX_EPHEMERAL_ADDRESSES_ADDRESS: &str = r#" -CREATE INDEX ephemeral_addresses_address ON ephemeral_addresses ( - address ASC -)"#; /// Stores information about every block that the wallet has scanned. /// diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index b14ecd9e5b..7c5a27f73e 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -422,7 +422,6 @@ mod tests { db::INDEX_ACCOUNTS_UIVK, db::INDEX_HD_ACCOUNT, db::INDEX_ADDRESSES_ACCOUNTS, - db::INDEX_EPHEMERAL_ADDRESSES_ADDRESS, db::INDEX_NF_MAP_LOCATOR_IDX, db::INDEX_ORCHARD_RECEIVED_NOTES_ACCOUNT, db::INDEX_ORCHARD_RECEIVED_NOTES_TX, diff --git a/zcash_client_sqlite/src/wallet/init/migrations/ephemeral_addresses.rs b/zcash_client_sqlite/src/wallet/init/migrations/ephemeral_addresses.rs index 7f68792b33..d9dffaf89b 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/ephemeral_addresses.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/ephemeral_addresses.rs @@ -45,6 +45,7 @@ impl RusqliteMigration for Migration

{ "CREATE TABLE ephemeral_addresses ( account_id INTEGER NOT NULL, address_index INTEGER NOT NULL, + -- nullability of this column is controlled by the index_range_and_address_nullity check address TEXT, used_in_tx INTEGER, seen_in_tx INTEGER, @@ -52,6 +53,7 @@ impl RusqliteMigration for Migration

{ FOREIGN KEY (used_in_tx) REFERENCES transactions(id_tx), FOREIGN KEY (seen_in_tx) REFERENCES transactions(id_tx), PRIMARY KEY (account_id, address_index), + CONSTRAINT ephemeral_addr_uniq UNIQUE (address), CONSTRAINT used_implies_seen CHECK ( used_in_tx IS NULL OR seen_in_tx IS NOT NULL ), @@ -59,10 +61,7 @@ impl RusqliteMigration for Migration

{ (address_index BETWEEN 0 AND 0x7FFFFFFF AND address IS NOT NULL) OR (address_index BETWEEN 0x80000000 AND 0x7FFFFFFF + 20 AND address IS NULL AND used_in_tx IS NULL AND seen_in_tx IS NULL) ) - ) WITHOUT ROWID; - CREATE INDEX ephemeral_addresses_address ON ephemeral_addresses ( - address ASC - );", + ) WITHOUT ROWID;" )?; // Make sure that at least `GAP_LIMIT` ephemeral transparent addresses are diff --git a/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs b/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs index 461261de6e..017ad002f9 100644 --- a/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs +++ b/zcash_client_sqlite/src/wallet/transparent/ephemeral.rs @@ -282,14 +282,18 @@ fn reserve_until( )?; for raw_index in range_to_store { - let address_str_opt = match NonHardenedChildIndex::from_index(raw_index) { - Some(address_index) => Some( + // The range to store may contain indicies that are out of the valid range of non hardened + // child indices; we still store explicit rows in the ephemeral_addresses table for these + // so that it's possible to find the first unused address using dead reckoning with the gap + // limit. + let address_str_opt = NonHardenedChildIndex::from_index(raw_index) + .map(|address_index| { ephemeral_ivk - .derive_ephemeral_address(address_index)? - .encode(params), - ), - None => None, - }; + .derive_ephemeral_address(address_index) + .map(|addr| addr.encode(params)) + }) + .transpose()?; + stmt_insert_ephemeral_address.execute(named_params![ ":account_id": account_id.0, ":address_index": raw_index,