Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge hotfix/zcash_client_sqlite-0.12.x back to main. #1598

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions zcash_client_sqlite/src/wallet/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use zcash_client_backend::{
};
use zcash_primitives::{consensus, transaction::components::amount::BalanceError};

use self::migrations::verify_network_compatibility;

use super::commitment_tree;
use crate::{error::SqliteClientError, WalletDb};

Expand All @@ -24,6 +26,8 @@ mod migrations;
const SQLITE_MAJOR_VERSION: u32 = 3;
const MIN_SQLITE_MINOR_VERSION: u32 = 35;

const MIGRATIONS_TABLE: &str = "schemer_migrations";

#[derive(Debug)]
pub enum WalletMigrationError {
/// A feature required by the wallet database is not supported by the version of
Expand Down Expand Up @@ -330,9 +334,28 @@ pub(crate) fn init_wallet_db_internal<P: consensus::Parameters + 'static>(
wdb.conn
.execute_batch("PRAGMA foreign_keys = OFF;")
.map_err(|e| MigratorError::Adapter(WalletMigrationError::from(e)))?;
let adapter = RusqliteAdapter::new(&mut wdb.conn, Some("schemer_migrations".to_string()));
adapter.init().expect("Migrations table setup succeeds.");

// Temporarily take ownership of the connection in a wrapper to perform the initial migration
// table setup. This extra adapter creation could be omitted if `RusqliteAdapter` provided an
// accessor for the connection that it wraps, or if it provided a mechanism to query to
// determine whether a given migration has been applied. (see
// https://github.com/zcash/schemerz/issues/6)
{
let adapter = RusqliteAdapter::<'_, WalletMigrationError>::new(
&mut wdb.conn,
Some(MIGRATIONS_TABLE.to_string()),
);
adapter.init().expect("Migrations table setup succeeds.");
}

// Now that we are certain that the migrations table exists, verify that if the database
// already contains account data, any stored UFVKs correspond to the same network that the
// migrations are being run for.
verify_network_compatibility(&wdb.conn, &wdb.params).map_err(MigratorError::Adapter)?;

// Now create the adapter that we're actually going to use to perform the migrations, and
// proceed.
let adapter = RusqliteAdapter::new(&mut wdb.conn, Some(MIGRATIONS_TABLE.to_string()));
let mut migrator = Migrator::new(adapter);
migrator
.register_multiple(migrations::all_migrations(&wdb.params, seed.clone()).into_iter())
Expand Down
48 changes: 48 additions & 0 deletions zcash_client_sqlite/src/wallet/init/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@

use std::rc::Rc;

use rusqlite::{named_params, OptionalExtension};
use schemerz_rusqlite::RusqliteMigration;
use secrecy::SecretVec;
use uuid::Uuid;
use zcash_address::unified::{Encoding as _, Ufvk};
use zcash_protocol::consensus;

use super::WalletMigrationError;
Expand Down Expand Up @@ -206,6 +208,52 @@
/// Leaf migrations in the 0.11.1 release.
const V_0_11_1: &[Uuid] = &[tx_retrieval_queue::MIGRATION_ID];

pub(super) fn verify_network_compatibility<P: consensus::Parameters>(
conn: &rusqlite::Connection,
params: &P,
) -> Result<(), WalletMigrationError> {
// Ensure that the `ufvk_support` migration has been applied; if it hasn't, we won't be able to
// validate that the UFVKs in the wallet correspond to the network type that the wallet is
// being migrated for.
let has_ufvk = conn
.query_row(
&format!(
"SELECT 1 FROM {} WHERE id = :migration_id",
super::MIGRATIONS_TABLE
),
named_params![":migration_id": &ufvk_support::MIGRATION_ID.as_bytes()[..]],
|row| row.get::<_, bool>(0),
)
.optional()?
== Some(true);

Check warning on line 228 in zcash_client_sqlite/src/wallet/init/migrations.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations.rs#L228

Added line #L228 was not covered by tests

if has_ufvk {

Check warning on line 230 in zcash_client_sqlite/src/wallet/init/migrations.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations.rs#L230

Added line #L230 was not covered by tests
let mut fvks_stmt = conn.prepare("SELECT ufvk FROM accounts")?;
let mut rows = fvks_stmt.query([])?;
while let Some(row) = rows.next()? {
let ufvk_str = row.get::<_, String>(0)?;
let (network, _) = Ufvk::decode(&ufvk_str).map_err(|e| {
WalletMigrationError::CorruptedData(format!("Unable to parse UFVK: {e}"))

Check warning on line 236 in zcash_client_sqlite/src/wallet/init/migrations.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations.rs#L236

Added line #L236 was not covered by tests
})?;

if network != params.network_type() {
let network_name = |n| match n {
consensus::NetworkType::Main => "mainnet",
consensus::NetworkType::Test => "testnet",
consensus::NetworkType::Regtest => "regtest",

Check warning on line 243 in zcash_client_sqlite/src/wallet/init/migrations.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations.rs#L239-L243

Added lines #L239 - L243 were not covered by tests
};
return Err(WalletMigrationError::CorruptedData(format!(
"Network type mismatch: account UFVK is for {} but attempting to initialize for {}.",
network_name(network),
network_name(params.network_type())

Check warning on line 248 in zcash_client_sqlite/src/wallet/init/migrations.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations.rs#L245-L248

Added lines #L245 - L248 were not covered by tests
)));
}
}
}

Ok(())
}

#[cfg(test)]
mod tests {
use std::collections::HashSet;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,21 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {

#[cfg(test)]
mod tests {
use rusqlite::named_params;
use secrecy::Secret;
use tempfile::NamedTempFile;
use zcash_keys::keys::UnifiedSpendingKey;
use zcash_protocol::consensus::Network;
use zip32::AccountId;

use super::{DEPENDENCIES, MIGRATION_ID};
use crate::{wallet::init::init_wallet_db_internal, WalletDb};

#[test]
fn migrate() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
let network = Network::TestNetwork;
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();

let seed_bytes = vec![0xab; 32];
init_wallet_db_internal(
Expand All @@ -96,9 +100,16 @@ mod tests {
)
.unwrap();

let usk =
UnifiedSpendingKey::from_seed(&network, &seed_bytes[..], AccountId::ZERO).unwrap();
let ufvk_str = usk.to_unified_full_viewing_key().encode(&network);

db_data
.conn
.execute_batch(r#"INSERT INTO accounts (account, ufvk) VALUES (0, 'not_a_real_ufvk');"#)
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, :ufvk_str)",
named_params![":ufvk_str": ufvk_str],
)
.unwrap();
db_data
.conn
Expand Down
13 changes: 10 additions & 3 deletions zcash_client_sqlite/src/wallet/transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,16 @@
let (network, uivk) = Uivk::decode(&uivk_str)
.map_err(|e| SqliteClientError::CorruptedData(format!("Unable to parse UIVK: {e}")))?;
if params.network_type() != network {
return Err(SqliteClientError::CorruptedData(
"Network type mismatch".to_owned(),
));
let network_name = |n| match n {
consensus::NetworkType::Main => "mainnet",
consensus::NetworkType::Test => "testnet",
consensus::NetworkType::Regtest => "regtest",

Check warning on line 148 in zcash_client_sqlite/src/wallet/transparent.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/transparent.rs#L145-L148

Added lines #L145 - L148 were not covered by tests
};
return Err(SqliteClientError::CorruptedData(format!(
"Network type mismatch: account UIVK is for {} but a {} address was requested.",
network_name(network),
network_name(params.network_type())

Check warning on line 153 in zcash_client_sqlite/src/wallet/transparent.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/transparent.rs#L150-L153

Added lines #L150 - L153 were not covered by tests
)));
}

// Derive the default transparent address (if it wasn't already part of a derived UA).
Expand Down
Loading