Merge pull request #672 from nuttycom/wallet/autodetect_utxo_account

Remove `received_by_account` field from WalletTransparentOutput
This commit is contained in:
Kris Nuttycombe 2022-10-13 13:08:01 -06:00 committed by GitHub
commit 2a00d65226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 62 deletions

View File

@ -31,7 +31,6 @@ pub struct WalletTx<N> {
#[cfg(feature = "transparent-inputs")]
pub struct WalletTransparentOutput {
pub received_by_account: AccountId,
pub outpoint: OutPoint,
pub txout: TxOut,
pub height: BlockHeight,

View File

@ -33,6 +33,53 @@ use {
zcash_primitives::transaction::components::transparent::OutPoint,
};
pub(crate) struct InsertAddress<'a> {
stmt: Statement<'a>,
}
impl<'a> InsertAddress<'a> {
pub(crate) fn new(conn: &'a rusqlite::Connection) -> Result<Self, rusqlite::Error> {
Ok(InsertAddress {
stmt: conn.prepare(
"INSERT INTO addresses (
account,
diversifier_index_be,
address,
cached_transparent_receiver_address
)
VALUES (
:account,
:diversifier_index_be,
:address,
:cached_transparent_receiver_address
)",
)?,
})
}
/// Adds the given address and diversifier index to the addresses table.
///
/// Returns the database row for the newly-inserted address.
pub(crate) fn execute<P: consensus::Parameters>(
&mut self,
params: &P,
account: AccountId,
mut diversifier_index: DiversifierIndex,
address: &UnifiedAddress,
) -> Result<(), rusqlite::Error> {
// the diversifier index is stored in big-endian order to allow sorting
diversifier_index.0.reverse();
self.stmt.execute(named_params![
":account": &u32::from(account),
":diversifier_index_be": &&diversifier_index.0[..],
":address": &address.encode(params),
":cached_transparent_receiver_address": &address.transparent().map(|r| r.encode(params)),
])?;
Ok(())
}
}
/// The primary type used to implement [`WalletWrite`] for the SQLite database.
///
/// A data structure that stores the SQLite prepared statements that are
@ -70,7 +117,7 @@ pub struct DataConnStmtCache<'a, P> {
stmt_prune_witnesses: Statement<'a>,
stmt_update_expired: Statement<'a>,
stmt_insert_address: Statement<'a>,
stmt_insert_address: InsertAddress<'a>,
}
impl<'a, P> DataConnStmtCache<'a, P> {
@ -119,22 +166,26 @@ impl<'a, P> DataConnStmtCache<'a, P> {
received_by_account, address,
prevout_txid, prevout_idx, script,
value_zat, height)
VALUES (
:received_by_account, :address,
SELECT
addresses.account, :address,
:prevout_txid, :prevout_idx, :script,
:value_zat, :height)
:value_zat, :height
FROM addresses
WHERE addresses.cached_transparent_receiver_address = :address
RETURNING id_utxo"
)?,
#[cfg(feature = "transparent-inputs")]
stmt_update_received_transparent_utxo: wallet_db.conn.prepare(
"UPDATE utxos
SET received_by_account = :received_by_account,
SET received_by_account = addresses.account,
height = :height,
address = :address,
script = :script,
value_zat = :value_zat
FROM addresses
WHERE prevout_txid = :prevout_txid
AND prevout_idx = :prevout_idx
AND addresses.cached_transparent_receiver_address = :address
RETURNING id_utxo"
)?,
stmt_insert_received_note: wallet_db.conn.prepare(
@ -187,10 +238,7 @@ impl<'a, P> DataConnStmtCache<'a, P> {
WHERE id_tx = received_notes.spent AND block IS NULL AND expiry_height < ?
)",
)?,
stmt_insert_address: wallet_db.conn.prepare(
"INSERT INTO addresses (account, diversifier_index_be, address)
VALUES (:account, :diversifier_index_be, :address)",
)?,
stmt_insert_address: InsertAddress::new(&wallet_db.conn)?
}
)
}
@ -465,7 +513,6 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
self.stmt_insert_received_transparent_utxo
.query_row(
named_params![
":received_by_account": &u32::from(output.received_by_account),
":address": &output.address().encode(&self.wallet_db.params),
":prevout_txid": &output.outpoint.hash().to_vec(),
":prevout_idx": &output.outpoint.n(),
@ -495,7 +542,6 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
named_params![
":prevout_txid": &output.outpoint.hash().to_vec(),
":prevout_idx": &output.outpoint.n(),
":received_by_account": &u32::from(output.received_by_account),
":address": &output.address().encode(&self.wallet_db.params),
":script": &output.txout.script_pubkey.0,
":value_zat": &i64::from(output.txout.value),
@ -516,19 +562,17 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
pub(crate) fn stmt_insert_address(
&mut self,
account: AccountId,
mut diversifier_index: DiversifierIndex,
diversifier_index: DiversifierIndex,
address: &UnifiedAddress,
) -> Result<i64, SqliteClientError> {
diversifier_index.0.reverse();
let sql_args: &[(&str, &dyn ToSql)] = &[
(":account", &u32::from(account)),
(":diversifier_index_be", &&diversifier_index.0[..]),
(":address", &address.encode(&self.wallet_db.params)),
];
) -> Result<(), SqliteClientError> {
self.stmt_insert_address.execute(
&self.wallet_db.params,
account,
diversifier_index,
address,
)?;
self.stmt_insert_address.execute(sql_args)?;
Ok(self.wallet_db.conn.last_insert_rowid())
Ok(())
}
}

View File

@ -33,7 +33,10 @@ use zcash_client_backend::{
DecryptedOutput,
};
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT};
use crate::{
error::SqliteClientError, prepared::InsertAddress, DataConnStmtCache, NoteId, WalletDb,
PRUNING_HEIGHT,
};
#[cfg(feature = "transparent-inputs")]
use {
@ -207,19 +210,8 @@ pub(crate) fn add_account_internal<P: consensus::Parameters, E: From<rusqlite::E
)?;
// Always derive the default Unified Address for the account.
let (address, mut idx) = key.default_address();
let address_str: String = address.encode(network);
// the diversifier index is stored in big-endian order to allow sorting
idx.0.reverse();
conn.execute(
"INSERT INTO addresses (account, diversifier_index_be, address)
VALUES (:account, :diversifier_index_be, :address)",
named_params![
":account": &<u32>::from(account),
":diversifier_index_be": &&idx.0[..],
":address": &address_str,
],
)?;
let (address, d_idx) = key.default_address();
InsertAddress::new(conn)?.execute(network, account, d_idx, &address)?;
Ok(())
}
@ -929,8 +921,7 @@ pub(crate) fn get_unspent_transparent_outputs<P: consensus::Parameters>(
max_height: BlockHeight,
) -> Result<Vec<WalletTransparentOutput>, SqliteClientError> {
let mut stmt_blocks = wdb.conn.prepare(
"SELECT u.received_by_account,
u.prevout_txid, u.prevout_idx, u.script,
"SELECT u.prevout_txid, u.prevout_idx, u.script,
u.value_zat, u.height, tx.block as block
FROM utxos u
LEFT OUTER JOIN transactions tx
@ -943,19 +934,16 @@ pub(crate) fn get_unspent_transparent_outputs<P: consensus::Parameters>(
let addr_str = address.encode(&wdb.params);
let rows = stmt_blocks.query_map(params![addr_str, u32::from(max_height)], |row| {
let received_by_account: u32 = row.get(0)?;
let txid: Vec<u8> = row.get(1)?;
let txid: Vec<u8> = row.get(0)?;
let mut txid_bytes = [0u8; 32];
txid_bytes.copy_from_slice(&txid);
let index: u32 = row.get(2)?;
let script_pubkey = Script(row.get(3)?);
let value = Amount::from_i64(row.get(4)?).unwrap();
let height: u32 = row.get(5)?;
let index: u32 = row.get(1)?;
let script_pubkey = Script(row.get(2)?);
let value = Amount::from_i64(row.get(3)?).unwrap();
let height: u32 = row.get(4)?;
Ok(WalletTransparentOutput {
received_by_account: AccountId::from(received_by_account),
outpoint: OutPoint::new(txid_bytes, index),
txout: TxOut {
value,
@ -1224,7 +1212,9 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::{data_api::WalletWrite, wallet::WalletTransparentOutput},
zcash_client_backend::{
data_api::WalletWrite, encoding::AddressCodec, wallet::WalletTransparentOutput,
},
zcash_primitives::{
consensus::BlockHeight,
transaction::components::{OutPoint, TxOut},
@ -1267,12 +1257,11 @@ mod tests {
// Add an account to the wallet
let mut ops = db_data.get_update_ops().unwrap();
let seed = Secret::new([0u8; 32].to_vec());
let (account_id, usk) = ops.create_account(&seed).unwrap();
let (uaddr, _) = usk.to_unified_full_viewing_key().default_address();
let (account_id, _usk) = ops.create_account(&seed).unwrap();
let uaddr = db_data.get_current_address(account_id).unwrap().unwrap();
let taddr = uaddr.transparent().unwrap();
let mut utxo = WalletTransparentOutput {
received_by_account: account_id,
outpoint: OutPoint::new([1u8; 32], 1),
txout: TxOut {
value: Amount::from_u64(100000).unwrap(),
@ -1310,5 +1299,18 @@ mod tests {
utxos.iter().any(|rutxo| rutxo.height == utxo.height)
}
));
// Artificially delete the address from the addresses table so that
// we can ensure the update fails if the join doesn't work.
db_data
.conn
.execute(
"DELETE FROM addresses WHERE cached_transparent_receiver_address = ?",
[Some(taddr.encode(&db_data.params))],
)
.unwrap();
let res2 = super::put_received_transparent_utxo(&mut ops, &utxo);
assert!(matches!(res2, Err(_)));
}
}

View File

@ -299,8 +299,6 @@ mod tests {
use std::collections::HashMap;
use tempfile::NamedTempFile;
use zcash_address::test_vectors;
use zcash_client_backend::{
address::RecipientAddress,
encoding::{encode_extended_full_viewing_key, encode_payment_address},
@ -309,27 +307,30 @@ mod tests {
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId, Network, Parameters},
consensus::{BlockHeight, BranchId, Parameters},
transaction::{TransactionData, TxVersion},
zip32::{
sapling::{DiversifiableFullViewingKey, ExtendedFullViewingKey},
DiversifierIndex,
},
zip32::sapling::{DiversifiableFullViewingKey, ExtendedFullViewingKey},
};
use crate::{
error::SqliteClientError,
tests::{self, network},
wallet::{self, get_address},
AccountId, WalletDb, WalletWrite,
wallet::get_address,
AccountId, WalletDb,
};
use super::{init_accounts_table, init_blocks_table, init_wallet_db};
#[cfg(feature = "transparent-inputs")]
use {
crate::wallet::{pool_code, PoolType},
zcash_primitives::legacy::keys as transparent,
crate::{
wallet::{self, pool_code, PoolType},
WalletWrite,
},
zcash_address::test_vectors,
zcash_primitives::{
consensus::Network, legacy::keys as transparent, zip32::DiversifierIndex,
},
};
#[test]
@ -350,6 +351,7 @@ mod tests {
account INTEGER NOT NULL,
diversifier_index_be BLOB NOT NULL,
address TEXT NOT NULL,
cached_transparent_receiver_address TEXT,
FOREIGN KEY (account) REFERENCES accounts(account),
CONSTRAINT diversification UNIQUE (account, diversifier_index_be)
)",

View File

@ -52,6 +52,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
account INTEGER NOT NULL,
diversifier_index_be BLOB NOT NULL,
address TEXT NOT NULL,
cached_transparent_receiver_address TEXT,
FOREIGN KEY (account) REFERENCES accounts(account),
CONSTRAINT diversification UNIQUE (account, diversifier_index_be)
);