Add a migration to add account ID to the utxos table.

This commit is contained in:
Kris Nuttycombe 2022-09-30 19:03:17 -06:00 committed by Kris Nuttycombe
parent ab6f7929b4
commit 14787f574f
6 changed files with 169 additions and 24 deletions

View File

@ -252,7 +252,7 @@ impl<P: consensus::Parameters> WalletReadTransparent for WalletDb<P> {
&self,
account: AccountId,
) -> Result<HashSet<TransparentAddress>, Self::Error> {
wallet::get_transparent_receivers(self, account)
wallet::get_transparent_receivers(&self.params, &self.conn, account)
}
fn get_unspent_transparent_outputs(

View File

@ -12,9 +12,6 @@ use rusqlite::{named_params, OptionalExtension, ToSql};
use std::collections::HashMap;
use std::convert::TryFrom;
#[cfg(feature = "transparent-inputs")]
use std::collections::HashSet;
use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight, BranchId, NetworkUpgrade, Parameters},
@ -41,7 +38,8 @@ use zcash_primitives::legacy::TransparentAddress;
#[cfg(feature = "transparent-inputs")]
use {
crate::UtxoId,
rusqlite::params,
rusqlite::{params, Connection},
std::collections::HashSet,
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
zcash_primitives::{
legacy::{keys::IncomingViewingKey, Script},
@ -276,20 +274,19 @@ pub(crate) fn get_current_address<P: consensus::Parameters>(
#[cfg(feature = "transparent-inputs")]
pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
wdb: &WalletDb<P>,
params: &P,
conn: &Connection,
account: AccountId,
) -> Result<HashSet<TransparentAddress>, SqliteClientError> {
let mut ret = HashSet::new();
// Get all UAs derived
let mut ua_query = wdb
.conn
.prepare("SELECT address FROM addresses WHERE account = :account")?;
let mut ua_query = conn.prepare("SELECT address FROM addresses WHERE account = :account")?;
let mut rows = ua_query.query(named_params![":account": &u32::from(account)])?;
while let Some(row) = rows.next()? {
let ua_str: String = row.get(0)?;
let ua = RecipientAddress::decode(&wdb.params, &ua_str)
let ua = RecipientAddress::decode(params, &ua_str)
.ok_or_else(|| {
SqliteClientError::CorruptedData("Not a valid Zcash recipient address".to_owned())
})
@ -306,12 +303,12 @@ pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
}
// Get the UFVK for the account.
let ufvk_str: String = wdb.conn.query_row(
let ufvk_str: String = conn.query_row(
"SELECT ufvk FROM accounts WHERE account = :account",
[u32::from(account)],
|row| row.get(0),
)?;
let ufvk = UnifiedFullViewingKey::decode(&wdb.params, &ufvk_str)
let ufvk = UnifiedFullViewingKey::decode(params, &ufvk_str)
.map_err(SqliteClientError::CorruptedData)?;
// Derive the default transparent address (if it wasn't already part of a derived UA).

View File

@ -394,8 +394,9 @@ mod tests {
fee INTEGER,
FOREIGN KEY (block) REFERENCES blocks(height)
)",
"CREATE TABLE utxos (
"CREATE TABLE \"utxos\" (
id_utxo INTEGER PRIMARY KEY,
received_by_account INTEGER NOT NULL,
address TEXT NOT NULL,
prevout_txid BLOB NOT NULL,
prevout_idx INTEGER NOT NULL,
@ -403,6 +404,7 @@ mod tests {
value_zat INTEGER NOT NULL,
height INTEGER NOT NULL,
spent_in_tx INTEGER,
FOREIGN KEY (received_by_account) REFERENCES accounts(account),
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
)",

View File

@ -1,4 +1,5 @@
mod add_transaction_views;
mod add_utxo_account;
mod addresses_table;
mod initial_setup;
mod ufvk_support;
@ -27,5 +28,8 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
Box::new(add_transaction_views::Migration {
params: params.clone(),
}),
Box::new(add_utxo_account::Migration {
_params: params.clone(),
}),
]
}

View File

@ -214,13 +214,22 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
#[cfg(test)]
mod tests {
use rusqlite::{self, params};
use tempfile::NamedTempFile;
use zcash_client_backend::keys::UnifiedSpendingKey;
use zcash_primitives::zip32::AccountId;
use crate::{
tests,
wallet::init::{init_wallet_db, init_wallet_db_internal, migrations::addresses_table},
WalletDb,
};
#[cfg(feature = "transparent-inputs")]
use {
crate::wallet::init::migrations::ufvk_support,
rusqlite::params,
zcash_client_backend::{encoding::AddressCodec, keys::UnifiedSpendingKey},
zcash_client_backend::encoding::AddressCodec,
zcash_primitives::{
consensus::{BlockHeight, BranchId},
legacy::{keys::IncomingViewingKey, Script},
@ -231,25 +240,29 @@ mod tests {
},
TransactionData, TxVersion,
},
zip32::AccountId,
},
};
use crate::{
tests,
wallet::init::{init_wallet_db, init_wallet_db_internal, migrations::addresses_table},
WalletDb,
};
#[test]
fn transaction_views() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
init_wallet_db_internal(&mut db_data, None, Some(addresses_table::MIGRATION_ID)).unwrap();
let usk =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk = usk.to_unified_full_viewing_key();
db_data
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
params![ufvk.encode(&tests::network())],
)
.unwrap();
db_data.conn.execute_batch(
"INSERT INTO accounts (account, ufvk) VALUES (0, '');
INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '');
"INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '');
INSERT INTO transactions (block, id_tx, txid) VALUES (0, 0, '');
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)

View File

@ -0,0 +1,129 @@
//! A migration that adds an identifier for the account that received a UTXO to the utxos table
use std::collections::HashSet;
use rusqlite;
use schemer;
use schemer_rusqlite::RusqliteMigration;
use uuid::Uuid;
use zcash_primitives::consensus;
use super::{addresses_table, utxos_table};
use crate::wallet::init::WalletMigrationError;
#[cfg(feature = "transparent-inputs")]
use {
crate::{error::SqliteClientError, wallet::get_transparent_receivers},
rusqlite::named_params,
zcash_client_backend::encoding::AddressCodec,
zcash_primitives::zip32::AccountId,
};
/// This migration adds an account identifier column to the UTXOs table.
///
/// 761884d6-30d8-44ef-b204-0b82551c4ca1
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
0x761884d6,
0x30d8,
0x44ef,
b"\xb2\x04\x0b\x82\x55\x1c\x4c\xa1",
);
pub(super) struct Migration<P> {
pub(super) _params: P,
}
impl<P> schemer::Migration for Migration<P> {
fn id(&self) -> Uuid {
MIGRATION_ID
}
fn dependencies(&self) -> HashSet<Uuid> {
[utxos_table::MIGRATION_ID, addresses_table::MIGRATION_ID]
.into_iter()
.collect()
}
fn description(&self) -> &'static str {
"Adds an identifier for the account that received a UTXO to the utxos table"
}
}
impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
type Error = WalletMigrationError;
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
transaction.execute_batch("ALTER TABLE utxos ADD COLUMN received_by_account INTEGER;")?;
#[cfg(feature = "transparent-inputs")]
{
let mut stmt_update_utxo_account = transaction.prepare(
"UPDATE utxos SET received_by_account = :account WHERE address = :address",
)?;
let mut stmt_fetch_accounts = transaction.prepare("SELECT account FROM accounts")?;
let mut rows = stmt_fetch_accounts.query([])?;
while let Some(row) = rows.next()? {
let account: u32 = row.get(0)?;
let taddrs =
get_transparent_receivers(&self._params, transaction, AccountId::from(account))
.map_err(|e| match e {
SqliteClientError::DbError(e) => WalletMigrationError::DbError(e),
SqliteClientError::CorruptedData(s) => {
WalletMigrationError::CorruptedData(s)
}
other => WalletMigrationError::CorruptedData(format!(
"Unexpected error in migration: {}",
other
)),
})?;
for taddr in taddrs {
stmt_update_utxo_account.execute(named_params![
":account": &account,
":address": &taddr.encode(&self._params),
])?;
}
}
}
transaction.execute_batch(
"CREATE TABLE utxos_new (
id_utxo INTEGER PRIMARY KEY,
received_by_account INTEGER NOT NULL,
address TEXT NOT NULL,
prevout_txid BLOB NOT NULL,
prevout_idx INTEGER NOT NULL,
script BLOB NOT NULL,
value_zat INTEGER NOT NULL,
height INTEGER NOT NULL,
spent_in_tx INTEGER,
FOREIGN KEY (received_by_account) REFERENCES accounts(account),
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
);
INSERT INTO utxos_new (
id_utxo, received_by_account, address,
prevout_txid, prevout_idx, script, value_zat,
height, spent_in_tx)
SELECT
id_utxo, received_by_account, address,
prevout_txid, prevout_idx, script, value_zat,
height, spent_in_tx
FROM utxos;",
)?;
transaction.execute_batch(
"DROP TABLE utxos;
ALTER TABLE utxos_new RENAME TO utxos;",
)?;
Ok(())
}
fn down(&self, _transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
// TODO: something better than just panic?
panic!("Cannot revert this migration.");
}
}