Merge pull request #619 from zcash/sqlite-prepared-statement-type-safety
`zcash_client_sqlite`: Improve type safety for prepared statements
This commit is contained in:
commit
b4fc235a2c
|
@ -36,7 +36,7 @@ use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use rusqlite::{Connection, Statement, NO_PARAMS};
|
use rusqlite::{Connection, NO_PARAMS};
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
|
@ -69,6 +69,9 @@ use {
|
||||||
zcash_primitives::legacy::TransparentAddress,
|
zcash_primitives::legacy::TransparentAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod prepared;
|
||||||
|
pub use prepared::DataConnStmtCache;
|
||||||
|
|
||||||
pub mod chain;
|
pub mod chain;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
|
@ -116,92 +119,7 @@ impl<P: consensus::Parameters> WalletDb<P> {
|
||||||
/// for that database. This operation may eagerly initialize and cache sqlite
|
/// for that database. This operation may eagerly initialize and cache sqlite
|
||||||
/// prepared statements that are used in write operations.
|
/// prepared statements that are used in write operations.
|
||||||
pub fn get_update_ops(&self) -> Result<DataConnStmtCache<'_, P>, SqliteClientError> {
|
pub fn get_update_ops(&self) -> Result<DataConnStmtCache<'_, P>, SqliteClientError> {
|
||||||
Ok(
|
DataConnStmtCache::new(self)
|
||||||
DataConnStmtCache {
|
|
||||||
wallet_db: self,
|
|
||||||
stmt_insert_block: self.conn.prepare(
|
|
||||||
"INSERT INTO blocks (height, hash, time, sapling_tree)
|
|
||||||
VALUES (?, ?, ?, ?)",
|
|
||||||
)?,
|
|
||||||
stmt_insert_tx_meta: self.conn.prepare(
|
|
||||||
"INSERT INTO transactions (txid, block, tx_index)
|
|
||||||
VALUES (?, ?, ?)",
|
|
||||||
)?,
|
|
||||||
stmt_update_tx_meta: self.conn.prepare(
|
|
||||||
"UPDATE transactions
|
|
||||||
SET block = ?, tx_index = ? WHERE txid = ?",
|
|
||||||
)?,
|
|
||||||
stmt_insert_tx_data: self.conn.prepare(
|
|
||||||
"INSERT INTO transactions (txid, created, expiry_height, raw)
|
|
||||||
VALUES (?, ?, ?, ?)",
|
|
||||||
)?,
|
|
||||||
stmt_update_tx_data: self.conn.prepare(
|
|
||||||
"UPDATE transactions
|
|
||||||
SET expiry_height = ?, raw = ? WHERE txid = ?",
|
|
||||||
)?,
|
|
||||||
stmt_select_tx_ref: self.conn.prepare(
|
|
||||||
"SELECT id_tx FROM transactions WHERE txid = ?",
|
|
||||||
)?,
|
|
||||||
stmt_mark_sapling_note_spent: self.conn.prepare(
|
|
||||||
"UPDATE received_notes SET spent = ? WHERE nf = ?"
|
|
||||||
)?,
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_mark_transparent_utxo_spent: self.conn.prepare(
|
|
||||||
"UPDATE utxos SET spent_in_tx = :spent_in_tx
|
|
||||||
WHERE prevout_txid = :prevout_txid
|
|
||||||
AND prevout_idx = :prevout_idx"
|
|
||||||
)?,
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_insert_received_transparent_utxo: self.conn.prepare(
|
|
||||||
"INSERT INTO utxos (address, prevout_txid, prevout_idx, script, value_zat, height)
|
|
||||||
VALUES (:address, :prevout_txid, :prevout_idx, :script, :value_zat, :height)"
|
|
||||||
)?,
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_delete_utxos: self.conn.prepare(
|
|
||||||
"DELETE FROM utxos WHERE address = :address AND height > :above_height"
|
|
||||||
)?,
|
|
||||||
stmt_insert_received_note: self.conn.prepare(
|
|
||||||
"INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, memo, nf, is_change)
|
|
||||||
VALUES (:tx, :output_index, :account, :diversifier, :value, :rcm, :memo, :nf, :is_change)",
|
|
||||||
)?,
|
|
||||||
stmt_update_received_note: self.conn.prepare(
|
|
||||||
"UPDATE received_notes
|
|
||||||
SET account = :account,
|
|
||||||
diversifier = :diversifier,
|
|
||||||
value = :value,
|
|
||||||
rcm = :rcm,
|
|
||||||
nf = IFNULL(:nf, nf),
|
|
||||||
memo = IFNULL(:memo, memo),
|
|
||||||
is_change = IFNULL(:is_change, is_change)
|
|
||||||
WHERE tx = :tx AND output_index = :output_index",
|
|
||||||
)?,
|
|
||||||
stmt_select_received_note: self.conn.prepare(
|
|
||||||
"SELECT id_note FROM received_notes WHERE tx = ? AND output_index = ?"
|
|
||||||
)?,
|
|
||||||
stmt_update_sent_note: self.conn.prepare(
|
|
||||||
"UPDATE sent_notes
|
|
||||||
SET from_account = ?, address = ?, value = ?, memo = ?
|
|
||||||
WHERE tx = ? AND output_pool = ? AND output_index = ?",
|
|
||||||
)?,
|
|
||||||
stmt_insert_sent_note: self.conn.prepare(
|
|
||||||
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
)?,
|
|
||||||
stmt_insert_witness: self.conn.prepare(
|
|
||||||
"INSERT INTO sapling_witnesses (note, block, witness)
|
|
||||||
VALUES (?, ?, ?)",
|
|
||||||
)?,
|
|
||||||
stmt_prune_witnesses: self.conn.prepare(
|
|
||||||
"DELETE FROM sapling_witnesses WHERE block < ?"
|
|
||||||
)?,
|
|
||||||
stmt_update_expired: self.conn.prepare(
|
|
||||||
"UPDATE received_notes SET spent = NULL WHERE EXISTS (
|
|
||||||
SELECT id_tx FROM transactions
|
|
||||||
WHERE id_tx = received_notes.spent AND block IS NULL AND expiry_height < ?
|
|
||||||
)",
|
|
||||||
)?,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,44 +244,6 @@ impl<P: consensus::Parameters> WalletReadTransparent for WalletDb<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The primary type used to implement [`WalletWrite`] for the SQLite database.
|
|
||||||
///
|
|
||||||
/// A data structure that stores the SQLite prepared statements that are
|
|
||||||
/// required for the implementation of [`WalletWrite`] against the backing
|
|
||||||
/// store.
|
|
||||||
///
|
|
||||||
/// [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite
|
|
||||||
pub struct DataConnStmtCache<'a, P> {
|
|
||||||
wallet_db: &'a WalletDb<P>,
|
|
||||||
stmt_insert_block: Statement<'a>,
|
|
||||||
|
|
||||||
stmt_insert_tx_meta: Statement<'a>,
|
|
||||||
stmt_update_tx_meta: Statement<'a>,
|
|
||||||
|
|
||||||
stmt_insert_tx_data: Statement<'a>,
|
|
||||||
stmt_update_tx_data: Statement<'a>,
|
|
||||||
stmt_select_tx_ref: Statement<'a>,
|
|
||||||
|
|
||||||
stmt_mark_sapling_note_spent: Statement<'a>,
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_mark_transparent_utxo_spent: Statement<'a>,
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_insert_received_transparent_utxo: Statement<'a>,
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
stmt_delete_utxos: Statement<'a>,
|
|
||||||
stmt_insert_received_note: Statement<'a>,
|
|
||||||
stmt_update_received_note: Statement<'a>,
|
|
||||||
stmt_select_received_note: Statement<'a>,
|
|
||||||
|
|
||||||
stmt_insert_sent_note: Statement<'a>,
|
|
||||||
stmt_update_sent_note: Statement<'a>,
|
|
||||||
|
|
||||||
stmt_insert_witness: Statement<'a>,
|
|
||||||
stmt_prune_witnesses: Statement<'a>,
|
|
||||||
stmt_update_expired: Statement<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
|
impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
|
||||||
type Error = SqliteClientError;
|
type Error = SqliteClientError;
|
||||||
type NoteRef = NoteId;
|
type NoteRef = NoteId;
|
||||||
|
|
|
@ -0,0 +1,557 @@
|
||||||
|
//! Prepared SQL statements used by the wallet.
|
||||||
|
//!
|
||||||
|
//! Some `rusqlite` crate APIs are only available on prepared statements; these are stored
|
||||||
|
//! inside the [`DataConnStmtCache`]. When adding a new prepared statement:
|
||||||
|
//!
|
||||||
|
//! - Add it as a private field of `DataConnStmtCache`.
|
||||||
|
//! - Build the statement in [`DataConnStmtCache::new`].
|
||||||
|
//! - Add a crate-private helper method to `DataConnStmtCache` for running the statement.
|
||||||
|
|
||||||
|
use rusqlite::{params, Statement, ToSql};
|
||||||
|
use zcash_primitives::{
|
||||||
|
block::BlockHash,
|
||||||
|
consensus::{self, BlockHeight},
|
||||||
|
memo::MemoBytes,
|
||||||
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||||
|
sapling::{Diversifier, Node, Nullifier},
|
||||||
|
transaction::{components::Amount, TxId},
|
||||||
|
zip32::AccountId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{error::SqliteClientError, wallet::PoolType, NoteId, WalletDb};
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
use {
|
||||||
|
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||||
|
zcash_primitives::{
|
||||||
|
legacy::TransparentAddress, transaction::components::transparent::OutPoint,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The primary type used to implement [`WalletWrite`] for the SQLite database.
|
||||||
|
///
|
||||||
|
/// A data structure that stores the SQLite prepared statements that are
|
||||||
|
/// required for the implementation of [`WalletWrite`] against the backing
|
||||||
|
/// store.
|
||||||
|
///
|
||||||
|
/// [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite
|
||||||
|
pub struct DataConnStmtCache<'a, P> {
|
||||||
|
pub(crate) wallet_db: &'a WalletDb<P>,
|
||||||
|
stmt_insert_block: Statement<'a>,
|
||||||
|
|
||||||
|
stmt_insert_tx_meta: Statement<'a>,
|
||||||
|
stmt_update_tx_meta: Statement<'a>,
|
||||||
|
|
||||||
|
stmt_insert_tx_data: Statement<'a>,
|
||||||
|
stmt_update_tx_data: Statement<'a>,
|
||||||
|
stmt_select_tx_ref: Statement<'a>,
|
||||||
|
|
||||||
|
stmt_mark_sapling_note_spent: Statement<'a>,
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_mark_transparent_utxo_spent: Statement<'a>,
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_insert_received_transparent_utxo: Statement<'a>,
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_delete_utxos: Statement<'a>,
|
||||||
|
stmt_insert_received_note: Statement<'a>,
|
||||||
|
stmt_update_received_note: Statement<'a>,
|
||||||
|
stmt_select_received_note: Statement<'a>,
|
||||||
|
|
||||||
|
stmt_insert_sent_note: Statement<'a>,
|
||||||
|
stmt_update_sent_note: Statement<'a>,
|
||||||
|
|
||||||
|
stmt_insert_witness: Statement<'a>,
|
||||||
|
stmt_prune_witnesses: Statement<'a>,
|
||||||
|
stmt_update_expired: Statement<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P> DataConnStmtCache<'a, P> {
|
||||||
|
pub(crate) fn new(wallet_db: &'a WalletDb<P>) -> Result<Self, SqliteClientError> {
|
||||||
|
Ok(
|
||||||
|
DataConnStmtCache {
|
||||||
|
wallet_db,
|
||||||
|
stmt_insert_block: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO blocks (height, hash, time, sapling_tree)
|
||||||
|
VALUES (?, ?, ?, ?)",
|
||||||
|
)?,
|
||||||
|
stmt_insert_tx_meta: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO transactions (txid, block, tx_index)
|
||||||
|
VALUES (?, ?, ?)",
|
||||||
|
)?,
|
||||||
|
stmt_update_tx_meta: wallet_db.conn.prepare(
|
||||||
|
"UPDATE transactions
|
||||||
|
SET block = ?, tx_index = ? WHERE txid = ?",
|
||||||
|
)?,
|
||||||
|
stmt_insert_tx_data: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO transactions (txid, created, expiry_height, raw)
|
||||||
|
VALUES (?, ?, ?, ?)",
|
||||||
|
)?,
|
||||||
|
stmt_update_tx_data: wallet_db.conn.prepare(
|
||||||
|
"UPDATE transactions
|
||||||
|
SET expiry_height = ?, raw = ? WHERE txid = ?",
|
||||||
|
)?,
|
||||||
|
stmt_select_tx_ref: wallet_db.conn.prepare(
|
||||||
|
"SELECT id_tx FROM transactions WHERE txid = ?",
|
||||||
|
)?,
|
||||||
|
stmt_mark_sapling_note_spent: wallet_db.conn.prepare(
|
||||||
|
"UPDATE received_notes SET spent = ? WHERE nf = ?"
|
||||||
|
)?,
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_mark_transparent_utxo_spent: wallet_db.conn.prepare(
|
||||||
|
"UPDATE utxos SET spent_in_tx = :spent_in_tx
|
||||||
|
WHERE prevout_txid = :prevout_txid
|
||||||
|
AND prevout_idx = :prevout_idx"
|
||||||
|
)?,
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_insert_received_transparent_utxo: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO utxos (address, prevout_txid, prevout_idx, script, value_zat, height)
|
||||||
|
VALUES (:address, :prevout_txid, :prevout_idx, :script, :value_zat, :height)"
|
||||||
|
)?,
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
stmt_delete_utxos: wallet_db.conn.prepare(
|
||||||
|
"DELETE FROM utxos WHERE address = :address AND height > :above_height"
|
||||||
|
)?,
|
||||||
|
stmt_insert_received_note: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, memo, nf, is_change)
|
||||||
|
VALUES (:tx, :output_index, :account, :diversifier, :value, :rcm, :memo, :nf, :is_change)",
|
||||||
|
)?,
|
||||||
|
stmt_update_received_note: wallet_db.conn.prepare(
|
||||||
|
"UPDATE received_notes
|
||||||
|
SET account = :account,
|
||||||
|
diversifier = :diversifier,
|
||||||
|
value = :value,
|
||||||
|
rcm = :rcm,
|
||||||
|
nf = IFNULL(:nf, nf),
|
||||||
|
memo = IFNULL(:memo, memo),
|
||||||
|
is_change = IFNULL(:is_change, is_change)
|
||||||
|
WHERE tx = :tx AND output_index = :output_index",
|
||||||
|
)?,
|
||||||
|
stmt_select_received_note: wallet_db.conn.prepare(
|
||||||
|
"SELECT id_note FROM received_notes WHERE tx = ? AND output_index = ?"
|
||||||
|
)?,
|
||||||
|
stmt_update_sent_note: wallet_db.conn.prepare(
|
||||||
|
"UPDATE sent_notes
|
||||||
|
SET from_account = ?, address = ?, value = ?, memo = ?
|
||||||
|
WHERE tx = ? AND output_pool = ? AND output_index = ?",
|
||||||
|
)?,
|
||||||
|
stmt_insert_sent_note: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
)?,
|
||||||
|
stmt_insert_witness: wallet_db.conn.prepare(
|
||||||
|
"INSERT INTO sapling_witnesses (note, block, witness)
|
||||||
|
VALUES (?, ?, ?)",
|
||||||
|
)?,
|
||||||
|
stmt_prune_witnesses: wallet_db.conn.prepare(
|
||||||
|
"DELETE FROM sapling_witnesses WHERE block < ?"
|
||||||
|
)?,
|
||||||
|
stmt_update_expired: wallet_db.conn.prepare(
|
||||||
|
"UPDATE received_notes SET spent = NULL WHERE EXISTS (
|
||||||
|
SELECT id_tx FROM transactions
|
||||||
|
WHERE id_tx = received_notes.spent AND block IS NULL AND expiry_height < ?
|
||||||
|
)",
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts information about a scanned block into the database.
|
||||||
|
pub fn stmt_insert_block(
|
||||||
|
&mut self,
|
||||||
|
block_height: BlockHeight,
|
||||||
|
block_hash: BlockHash,
|
||||||
|
block_time: u32,
|
||||||
|
commitment_tree: &CommitmentTree<Node>,
|
||||||
|
) -> Result<(), SqliteClientError> {
|
||||||
|
let mut encoded_tree = Vec::new();
|
||||||
|
commitment_tree.write(&mut encoded_tree).unwrap();
|
||||||
|
|
||||||
|
self.stmt_insert_block.execute(params![
|
||||||
|
u32::from(block_height),
|
||||||
|
&block_hash.0[..],
|
||||||
|
block_time,
|
||||||
|
encoded_tree
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts the given transaction and its block metadata into the wallet.
|
||||||
|
///
|
||||||
|
/// Returns the database row for the newly-inserted transaction, or an error if the
|
||||||
|
/// transaction exists.
|
||||||
|
pub(crate) fn stmt_insert_tx_meta(
|
||||||
|
&mut self,
|
||||||
|
txid: &TxId,
|
||||||
|
height: BlockHeight,
|
||||||
|
tx_index: usize,
|
||||||
|
) -> Result<i64, SqliteClientError> {
|
||||||
|
self.stmt_insert_tx_meta.execute(params![
|
||||||
|
&txid.as_ref()[..],
|
||||||
|
u32::from(height),
|
||||||
|
(tx_index as i64),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(self.wallet_db.conn.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the block metadata for the given transaction.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||||
|
pub(crate) fn stmt_update_tx_meta(
|
||||||
|
&mut self,
|
||||||
|
height: BlockHeight,
|
||||||
|
tx_index: usize,
|
||||||
|
txid: &TxId,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
match self.stmt_update_tx_meta.execute(params![
|
||||||
|
u32::from(height),
|
||||||
|
(tx_index as i64),
|
||||||
|
&txid.as_ref()[..],
|
||||||
|
])? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("txid column is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts the given transaction and its data into the wallet.
|
||||||
|
///
|
||||||
|
/// Returns the database row for the newly-inserted transaction, or an error if the
|
||||||
|
/// transaction exists.
|
||||||
|
pub(crate) fn stmt_insert_tx_data(
|
||||||
|
&mut self,
|
||||||
|
txid: &TxId,
|
||||||
|
created_at: Option<time::OffsetDateTime>,
|
||||||
|
expiry_height: BlockHeight,
|
||||||
|
raw_tx: &[u8],
|
||||||
|
) -> Result<i64, SqliteClientError> {
|
||||||
|
self.stmt_insert_tx_data.execute(params![
|
||||||
|
&txid.as_ref()[..],
|
||||||
|
created_at,
|
||||||
|
u32::from(expiry_height),
|
||||||
|
raw_tx
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(self.wallet_db.conn.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the data for the given transaction.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||||
|
pub(crate) fn stmt_update_tx_data(
|
||||||
|
&mut self,
|
||||||
|
expiry_height: BlockHeight,
|
||||||
|
raw_tx: &[u8],
|
||||||
|
txid: &TxId,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
match self.stmt_update_tx_data.execute(params![
|
||||||
|
u32::from(expiry_height),
|
||||||
|
raw_tx,
|
||||||
|
&txid.as_ref()[..],
|
||||||
|
])? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("txid column is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the database row for the given `txid`, if the transaction is in the wallet.
|
||||||
|
pub(crate) fn stmt_select_tx_ref(&mut self, txid: &TxId) -> Result<i64, SqliteClientError> {
|
||||||
|
self.stmt_select_tx_ref
|
||||||
|
.query_row(&[&txid.as_ref()[..]], |row| row.get(0))
|
||||||
|
.map_err(SqliteClientError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks a given nullifier as having been revealed in the construction of the
|
||||||
|
/// specified transaction.
|
||||||
|
///
|
||||||
|
/// Marking a note spent in this fashion does NOT imply that the spending transaction
|
||||||
|
/// has been mined.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the nullifier does not correspond to any received note.
|
||||||
|
pub(crate) fn stmt_mark_sapling_note_spent(
|
||||||
|
&mut self,
|
||||||
|
tx_ref: i64,
|
||||||
|
nf: &Nullifier,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
match self
|
||||||
|
.stmt_mark_sapling_note_spent
|
||||||
|
.execute(params![tx_ref, &nf.0[..]])?
|
||||||
|
{
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("nf column is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks the given UTXO as having been spent.
|
||||||
|
///
|
||||||
|
/// Returns `false` if `outpoint` does not correspond to any tracked UTXO.
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
pub(crate) fn stmt_mark_transparent_utxo_spent(
|
||||||
|
&mut self,
|
||||||
|
tx_ref: i64,
|
||||||
|
outpoint: &OutPoint,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||||
|
(":spent_in_tx", &tx_ref),
|
||||||
|
(":prevout_txid", &outpoint.hash().to_vec()),
|
||||||
|
(":prevout_idx", &outpoint.n()),
|
||||||
|
];
|
||||||
|
|
||||||
|
match self
|
||||||
|
.stmt_mark_transparent_utxo_spent
|
||||||
|
.execute_named(sql_args)?
|
||||||
|
{
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("tx_outpoint constraint is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
|
||||||
|
/// Adds the given received UTXO to the datastore.
|
||||||
|
///
|
||||||
|
/// Returns the database row for the newly-inserted UTXO, or an error if the UTXO
|
||||||
|
/// exists.
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
pub(crate) fn stmt_insert_received_transparent_utxo(
|
||||||
|
&mut self,
|
||||||
|
output: &WalletTransparentOutput,
|
||||||
|
) -> Result<i64, SqliteClientError> {
|
||||||
|
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||||
|
(":address", &output.address().encode(&self.wallet_db.params)),
|
||||||
|
(":prevout_txid", &output.outpoint.hash().to_vec()),
|
||||||
|
(":prevout_idx", &output.outpoint.n()),
|
||||||
|
(":script", &output.txout.script_pubkey.0),
|
||||||
|
(":value_zat", &i64::from(output.txout.value)),
|
||||||
|
(":height", &u32::from(output.height)),
|
||||||
|
];
|
||||||
|
|
||||||
|
self.stmt_insert_received_transparent_utxo
|
||||||
|
.execute_named(sql_args)?;
|
||||||
|
|
||||||
|
Ok(self.wallet_db.conn.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all records of UTXOs that were recorded as having been received at block
|
||||||
|
/// heights greater than the given height.
|
||||||
|
///
|
||||||
|
/// Returns the number of UTXOs that were removed.
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
pub(crate) fn stmt_delete_utxos(
|
||||||
|
&mut self,
|
||||||
|
taddr: &TransparentAddress,
|
||||||
|
height: BlockHeight,
|
||||||
|
) -> Result<usize, SqliteClientError> {
|
||||||
|
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||||
|
(":address", &taddr.encode(&self.wallet_db.params)),
|
||||||
|
(":above_height", &u32::from(height)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let rows = self.stmt_delete_utxos.execute_named(sql_args)?;
|
||||||
|
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P> DataConnStmtCache<'a, P> {
|
||||||
|
/// Inserts the given received note into the wallet.
|
||||||
|
///
|
||||||
|
/// This implementation relies on the facts that:
|
||||||
|
/// - A transaction will not contain more than 2^63 shielded outputs.
|
||||||
|
/// - A note value will never exceed 2^63 zatoshis.
|
||||||
|
///
|
||||||
|
/// Returns the database row for the newly-inserted note, or an error if the note
|
||||||
|
/// exists.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn stmt_insert_received_note(
|
||||||
|
&mut self,
|
||||||
|
tx_ref: i64,
|
||||||
|
output_index: usize,
|
||||||
|
account: AccountId,
|
||||||
|
diversifier: &Diversifier,
|
||||||
|
value: u64,
|
||||||
|
rcm: [u8; 32],
|
||||||
|
nf: &Option<Nullifier>,
|
||||||
|
memo: Option<&MemoBytes>,
|
||||||
|
is_change: Option<bool>,
|
||||||
|
) -> Result<NoteId, SqliteClientError> {
|
||||||
|
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||||
|
(":tx", &tx_ref),
|
||||||
|
(":output_index", &(output_index as i64)),
|
||||||
|
(":account", &u32::from(account)),
|
||||||
|
(":diversifier", &diversifier.0.as_ref()),
|
||||||
|
(":value", &(value as i64)),
|
||||||
|
(":rcm", &rcm.as_ref()),
|
||||||
|
(":nf", &nf.as_ref().map(|nf| nf.0.as_ref())),
|
||||||
|
(":memo", &memo.map(|m| m.as_slice())),
|
||||||
|
(":is_change", &is_change),
|
||||||
|
];
|
||||||
|
|
||||||
|
self.stmt_insert_received_note.execute_named(sql_args)?;
|
||||||
|
|
||||||
|
Ok(NoteId::ReceivedNoteId(
|
||||||
|
self.wallet_db.conn.last_insert_rowid(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the data for the given transaction.
|
||||||
|
///
|
||||||
|
/// This implementation relies on the facts that:
|
||||||
|
/// - A transaction will not contain more than 2^63 shielded outputs.
|
||||||
|
/// - A note value will never exceed 2^63 zatoshis.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn stmt_update_received_note(
|
||||||
|
&mut self,
|
||||||
|
account: AccountId,
|
||||||
|
diversifier: &Diversifier,
|
||||||
|
value: u64,
|
||||||
|
rcm: [u8; 32],
|
||||||
|
nf: &Option<Nullifier>,
|
||||||
|
memo: Option<&MemoBytes>,
|
||||||
|
is_change: Option<bool>,
|
||||||
|
tx_ref: i64,
|
||||||
|
output_index: usize,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||||
|
(":account", &u32::from(account)),
|
||||||
|
(":diversifier", &diversifier.0.as_ref()),
|
||||||
|
(":value", &(value as i64)),
|
||||||
|
(":rcm", &rcm.as_ref()),
|
||||||
|
(":nf", &nf.as_ref().map(|nf| nf.0.as_ref())),
|
||||||
|
(":memo", &memo.map(|m| m.as_slice())),
|
||||||
|
(":is_change", &is_change),
|
||||||
|
(":tx", &tx_ref),
|
||||||
|
(":output_index", &(output_index as i64)),
|
||||||
|
];
|
||||||
|
|
||||||
|
match self.stmt_update_received_note.execute_named(sql_args)? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("tx_output constraint is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the database row for the given `txid`, if the transaction is in the wallet.
|
||||||
|
pub(crate) fn stmt_select_received_note(
|
||||||
|
&mut self,
|
||||||
|
tx_ref: i64,
|
||||||
|
output_index: usize,
|
||||||
|
) -> Result<NoteId, SqliteClientError> {
|
||||||
|
self.stmt_select_received_note
|
||||||
|
.query_row(params![tx_ref, (output_index as i64)], |row| {
|
||||||
|
row.get(0).map(NoteId::ReceivedNoteId)
|
||||||
|
})
|
||||||
|
.map_err(SqliteClientError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a sent note into the wallet database.
|
||||||
|
///
|
||||||
|
/// `output_index` is the index within the transaction that contains the recipient output:
|
||||||
|
///
|
||||||
|
/// - If `to` is a Sapling address, this is an index into the Sapling outputs of the
|
||||||
|
/// transaction.
|
||||||
|
/// - If `to` is a transparent address, this is an index into the transparent outputs of
|
||||||
|
/// the transaction.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn stmt_insert_sent_note(
|
||||||
|
&mut self,
|
||||||
|
tx_ref: i64,
|
||||||
|
pool_type: PoolType,
|
||||||
|
output_index: usize,
|
||||||
|
account: AccountId,
|
||||||
|
to_str: &str,
|
||||||
|
value: Amount,
|
||||||
|
memo: Option<&MemoBytes>,
|
||||||
|
) -> Result<(), SqliteClientError> {
|
||||||
|
let ivalue: i64 = value.into();
|
||||||
|
self.stmt_insert_sent_note.execute(params![
|
||||||
|
tx_ref,
|
||||||
|
pool_type.typecode(),
|
||||||
|
(output_index as i64),
|
||||||
|
u32::from(account),
|
||||||
|
to_str,
|
||||||
|
ivalue,
|
||||||
|
memo.map(|m| m.as_slice()),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the data for the given sent note.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn stmt_update_sent_note(
|
||||||
|
&mut self,
|
||||||
|
account: AccountId,
|
||||||
|
to_str: &str,
|
||||||
|
value: Amount,
|
||||||
|
memo: Option<&MemoBytes>,
|
||||||
|
tx_ref: i64,
|
||||||
|
pool_type: PoolType,
|
||||||
|
output_index: usize,
|
||||||
|
) -> Result<bool, SqliteClientError> {
|
||||||
|
let ivalue: i64 = value.into();
|
||||||
|
match self.stmt_update_sent_note.execute(params![
|
||||||
|
u32::from(account),
|
||||||
|
to_str,
|
||||||
|
ivalue,
|
||||||
|
&memo.map(|m| m.as_slice()),
|
||||||
|
tx_ref,
|
||||||
|
pool_type.typecode(),
|
||||||
|
output_index as i64,
|
||||||
|
])? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => unreachable!("tx_output constraint is marked as UNIQUE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records the incremental witness for the specified note, as of the given block
|
||||||
|
/// height.
|
||||||
|
///
|
||||||
|
/// Returns `SqliteClientError::InvalidNoteId` if the note ID is for a sent note.
|
||||||
|
pub(crate) fn stmt_insert_witness(
|
||||||
|
&mut self,
|
||||||
|
note_id: NoteId,
|
||||||
|
height: BlockHeight,
|
||||||
|
witness: &IncrementalWitness<Node>,
|
||||||
|
) -> Result<(), SqliteClientError> {
|
||||||
|
let note_id = match note_id {
|
||||||
|
NoteId::ReceivedNoteId(note_id) => Ok(note_id),
|
||||||
|
NoteId::SentNoteId(_) => Err(SqliteClientError::InvalidNoteId),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let mut encoded = Vec::new();
|
||||||
|
witness.write(&mut encoded).unwrap();
|
||||||
|
|
||||||
|
self.stmt_insert_witness
|
||||||
|
.execute(params![note_id, u32::from(height), encoded])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes old incremental witnesses up to the given block height.
|
||||||
|
pub(crate) fn stmt_prune_witnesses(
|
||||||
|
&mut self,
|
||||||
|
below_height: BlockHeight,
|
||||||
|
) -> Result<(), SqliteClientError> {
|
||||||
|
self.stmt_prune_witnesses
|
||||||
|
.execute(&[u32::from(below_height)])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks notes that have not been mined in transactions as expired, up to the given
|
||||||
|
/// block height.
|
||||||
|
pub fn stmt_update_expired(&mut self, height: BlockHeight) -> Result<(), SqliteClientError> {
|
||||||
|
self.stmt_update_expired.execute(&[u32::from(height)])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
//! [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite
|
//! [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite
|
||||||
|
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use rusqlite::{params, OptionalExtension, ToSql, NO_PARAMS};
|
use rusqlite::{OptionalExtension, ToSql, NO_PARAMS};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ use zcash_primitives::legacy::TransparentAddress;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
crate::UtxoId,
|
crate::UtxoId,
|
||||||
|
rusqlite::params,
|
||||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||||
zcash_primitives::{
|
zcash_primitives::{
|
||||||
legacy::Script,
|
legacy::Script,
|
||||||
|
@ -48,13 +49,13 @@ use {
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod transact;
|
pub mod transact;
|
||||||
|
|
||||||
enum PoolType {
|
pub(crate) enum PoolType {
|
||||||
Transparent,
|
Transparent,
|
||||||
Sapling,
|
Sapling,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoolType {
|
impl PoolType {
|
||||||
fn typecode(&self) -> i64 {
|
pub(crate) fn typecode(&self) -> i64 {
|
||||||
// These constants are *incidentally* shared with the typecodes
|
// These constants are *incidentally* shared with the typecodes
|
||||||
// for unified addresses, but this is exclusively an internal
|
// for unified addresses, but this is exclusively an internal
|
||||||
// implementation detail.
|
// implementation detail.
|
||||||
|
@ -822,17 +823,7 @@ pub fn insert_block<'a, P>(
|
||||||
block_time: u32,
|
block_time: u32,
|
||||||
commitment_tree: &CommitmentTree<Node>,
|
commitment_tree: &CommitmentTree<Node>,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let mut encoded_tree = Vec::new();
|
stmts.stmt_insert_block(block_height, block_hash, block_time, commitment_tree)
|
||||||
commitment_tree.write(&mut encoded_tree).unwrap();
|
|
||||||
|
|
||||||
stmts.stmt_insert_block.execute(params![
|
|
||||||
u32::from(block_height),
|
|
||||||
&block_hash.0[..],
|
|
||||||
block_time,
|
|
||||||
encoded_tree
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts information about a mined transaction that was observed to
|
/// Inserts information about a mined transaction that was observed to
|
||||||
|
@ -845,24 +836,12 @@ pub fn put_tx_meta<'a, P, N>(
|
||||||
tx: &WalletTx<N>,
|
tx: &WalletTx<N>,
|
||||||
height: BlockHeight,
|
height: BlockHeight,
|
||||||
) -> Result<i64, SqliteClientError> {
|
) -> Result<i64, SqliteClientError> {
|
||||||
let txid = tx.txid.as_ref().to_vec();
|
if !stmts.stmt_update_tx_meta(height, tx.index, &tx.txid)? {
|
||||||
if stmts
|
|
||||||
.stmt_update_tx_meta
|
|
||||||
.execute(params![u32::from(height), (tx.index as i64), txid])?
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
// It isn't there, so insert our transaction into the database.
|
// It isn't there, so insert our transaction into the database.
|
||||||
stmts
|
stmts.stmt_insert_tx_meta(&tx.txid, height, tx.index)
|
||||||
.stmt_insert_tx_meta
|
|
||||||
.execute(params![txid, u32::from(height), (tx.index as i64),])?;
|
|
||||||
|
|
||||||
Ok(stmts.wallet_db.conn.last_insert_rowid())
|
|
||||||
} else {
|
} else {
|
||||||
// It was there, so grab its row number.
|
// It was there, so grab its row number.
|
||||||
stmts
|
stmts.stmt_select_tx_ref(&tx.txid)
|
||||||
.stmt_select_tx_ref
|
|
||||||
.query_row(&[txid], |row| row.get(0))
|
|
||||||
.map_err(SqliteClientError::from)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -875,31 +854,17 @@ pub fn put_tx_data<'a, P>(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
created_at: Option<time::OffsetDateTime>,
|
created_at: Option<time::OffsetDateTime>,
|
||||||
) -> Result<i64, SqliteClientError> {
|
) -> Result<i64, SqliteClientError> {
|
||||||
let txid = tx.txid().as_ref().to_vec();
|
let txid = tx.txid();
|
||||||
|
|
||||||
let mut raw_tx = vec![];
|
let mut raw_tx = vec![];
|
||||||
tx.write(&mut raw_tx)?;
|
tx.write(&mut raw_tx)?;
|
||||||
|
|
||||||
if stmts
|
if !stmts.stmt_update_tx_data(tx.expiry_height(), &raw_tx, &txid)? {
|
||||||
.stmt_update_tx_data
|
|
||||||
.execute(params![u32::from(tx.expiry_height()), raw_tx, txid,])?
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
// It isn't there, so insert our transaction into the database.
|
// It isn't there, so insert our transaction into the database.
|
||||||
stmts.stmt_insert_tx_data.execute(params![
|
stmts.stmt_insert_tx_data(&txid, created_at, tx.expiry_height(), &raw_tx)
|
||||||
txid,
|
|
||||||
created_at,
|
|
||||||
u32::from(tx.expiry_height()),
|
|
||||||
raw_tx
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(stmts.wallet_db.conn.last_insert_rowid())
|
|
||||||
} else {
|
} else {
|
||||||
// It was there, so grab its row number.
|
// It was there, so grab its row number.
|
||||||
stmts
|
stmts.stmt_select_tx_ref(&txid)
|
||||||
.stmt_select_tx_ref
|
|
||||||
.query_row(&[txid], |row| row.get(0))
|
|
||||||
.map_err(SqliteClientError::from)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,9 +881,7 @@ pub fn mark_sapling_note_spent<'a, P>(
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
nf: &Nullifier,
|
nf: &Nullifier,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
stmts
|
stmts.stmt_mark_sapling_note_spent(tx_ref, nf)?;
|
||||||
.stmt_mark_sapling_note_spent
|
|
||||||
.execute(&[tx_ref.to_sql()?, nf.0.to_sql()?])?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,15 +892,7 @@ pub(crate) fn mark_transparent_utxo_spent<'a, P>(
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
outpoint: &OutPoint,
|
outpoint: &OutPoint,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
stmts.stmt_mark_transparent_utxo_spent(tx_ref, outpoint)?;
|
||||||
(":spent_in_tx", &tx_ref),
|
|
||||||
(":prevout_txid", &outpoint.hash().to_vec()),
|
|
||||||
(":prevout_idx", &outpoint.n()),
|
|
||||||
];
|
|
||||||
|
|
||||||
stmts
|
|
||||||
.stmt_mark_transparent_utxo_spent
|
|
||||||
.execute_named(sql_args)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -948,23 +903,9 @@ pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>(
|
||||||
stmts: &mut DataConnStmtCache<'a, P>,
|
stmts: &mut DataConnStmtCache<'a, P>,
|
||||||
output: &WalletTransparentOutput,
|
output: &WalletTransparentOutput,
|
||||||
) -> Result<UtxoId, SqliteClientError> {
|
) -> Result<UtxoId, SqliteClientError> {
|
||||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
|
||||||
(
|
|
||||||
":address",
|
|
||||||
&output.address().encode(&stmts.wallet_db.params),
|
|
||||||
),
|
|
||||||
(":prevout_txid", &output.outpoint.hash().to_vec()),
|
|
||||||
(":prevout_idx", &output.outpoint.n()),
|
|
||||||
(":script", &output.txout.script_pubkey.0),
|
|
||||||
(":value_zat", &i64::from(output.txout.value)),
|
|
||||||
(":height", &u32::from(output.height)),
|
|
||||||
];
|
|
||||||
|
|
||||||
stmts
|
stmts
|
||||||
.stmt_insert_received_transparent_utxo
|
.stmt_insert_received_transparent_utxo(output)
|
||||||
.execute_named(sql_args)?;
|
.map(UtxoId)
|
||||||
|
|
||||||
Ok(UtxoId(stmts.wallet_db.conn.last_insert_rowid()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all records of UTXOs that were recorded as having been received
|
/// Removes all records of UTXOs that were recorded as having been received
|
||||||
|
@ -978,14 +919,7 @@ pub fn delete_utxos_above<'a, P: consensus::Parameters>(
|
||||||
taddr: &TransparentAddress,
|
taddr: &TransparentAddress,
|
||||||
height: BlockHeight,
|
height: BlockHeight,
|
||||||
) -> Result<usize, SqliteClientError> {
|
) -> Result<usize, SqliteClientError> {
|
||||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
stmts.stmt_delete_utxos(taddr, height)
|
||||||
(":address", &taddr.encode(&stmts.wallet_db.params)),
|
|
||||||
(":above_height", &u32::from(height)),
|
|
||||||
];
|
|
||||||
|
|
||||||
let rows = stmts.stmt_delete_utxos.execute_named(sql_args)?;
|
|
||||||
|
|
||||||
Ok(rows)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records the specified shielded output as having been received.
|
/// Records the specified shielded output as having been received.
|
||||||
|
@ -1003,44 +937,41 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
) -> Result<NoteId, SqliteClientError> {
|
) -> Result<NoteId, SqliteClientError> {
|
||||||
let rcm = output.note().rcm().to_repr();
|
let rcm = output.note().rcm().to_repr();
|
||||||
let account = u32::from(output.account());
|
let account = output.account();
|
||||||
let diversifier = output.to().diversifier().0.to_vec();
|
let diversifier = output.to().diversifier();
|
||||||
let value = output.note().value as i64;
|
let value = output.note().value;
|
||||||
let rcm = rcm.as_ref();
|
let memo = output.memo();
|
||||||
let memo = output.memo().map(|m| m.as_slice());
|
|
||||||
let is_change = output.is_change();
|
let is_change = output.is_change();
|
||||||
let tx = tx_ref;
|
let output_index = output.index();
|
||||||
let output_index = output.index() as i64;
|
let nf = output.nullifier();
|
||||||
let nf_bytes = output.nullifier().map(|nf| nf.0.to_vec());
|
|
||||||
|
|
||||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
|
||||||
(":account", &account),
|
|
||||||
(":diversifier", &diversifier),
|
|
||||||
(":value", &value),
|
|
||||||
(":rcm", &rcm),
|
|
||||||
(":nf", &nf_bytes),
|
|
||||||
(":memo", &memo),
|
|
||||||
(":is_change", &is_change),
|
|
||||||
(":tx", &tx),
|
|
||||||
(":output_index", &output_index),
|
|
||||||
];
|
|
||||||
|
|
||||||
// First try updating an existing received note into the database.
|
// First try updating an existing received note into the database.
|
||||||
if stmts.stmt_update_received_note.execute_named(sql_args)? == 0 {
|
if !stmts.stmt_update_received_note(
|
||||||
|
account,
|
||||||
|
diversifier,
|
||||||
|
value,
|
||||||
|
rcm,
|
||||||
|
&nf,
|
||||||
|
memo,
|
||||||
|
is_change,
|
||||||
|
tx_ref,
|
||||||
|
output_index,
|
||||||
|
)? {
|
||||||
// It isn't there, so insert our note into the database.
|
// It isn't there, so insert our note into the database.
|
||||||
stmts.stmt_insert_received_note.execute_named(sql_args)?;
|
stmts.stmt_insert_received_note(
|
||||||
|
tx_ref,
|
||||||
Ok(NoteId::ReceivedNoteId(
|
output_index,
|
||||||
stmts.wallet_db.conn.last_insert_rowid(),
|
account,
|
||||||
))
|
diversifier,
|
||||||
|
value,
|
||||||
|
rcm,
|
||||||
|
&nf,
|
||||||
|
memo,
|
||||||
|
is_change,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// It was there, so grab its row number.
|
// It was there, so grab its row number.
|
||||||
stmts
|
stmts.stmt_select_received_note(tx_ref, output.index())
|
||||||
.stmt_select_received_note
|
|
||||||
.query_row(params![tx_ref, (output.index() as i64)], |row| {
|
|
||||||
row.get(0).map(NoteId::ReceivedNoteId)
|
|
||||||
})
|
|
||||||
.map_err(SqliteClientError::from)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1055,14 +986,7 @@ pub fn insert_witness<'a, P>(
|
||||||
witness: &IncrementalWitness<Node>,
|
witness: &IncrementalWitness<Node>,
|
||||||
height: BlockHeight,
|
height: BlockHeight,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let mut encoded = Vec::new();
|
stmts.stmt_insert_witness(NoteId::ReceivedNoteId(note_id), height, witness)
|
||||||
witness.write(&mut encoded).unwrap();
|
|
||||||
|
|
||||||
stmts
|
|
||||||
.stmt_insert_witness
|
|
||||||
.execute(params![note_id, u32::from(height), encoded])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes old incremental witnesses up to the given block height.
|
/// Removes old incremental witnesses up to the given block height.
|
||||||
|
@ -1073,10 +997,7 @@ pub fn prune_witnesses<P>(
|
||||||
stmts: &mut DataConnStmtCache<'_, P>,
|
stmts: &mut DataConnStmtCache<'_, P>,
|
||||||
below_height: BlockHeight,
|
below_height: BlockHeight,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
stmts
|
stmts.stmt_prune_witnesses(below_height)
|
||||||
.stmt_prune_witnesses
|
|
||||||
.execute(&[u32::from(below_height)])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks notes that have not been mined in transactions
|
/// Marks notes that have not been mined in transactions
|
||||||
|
@ -1088,8 +1009,7 @@ pub fn update_expired_notes<P>(
|
||||||
stmts: &mut DataConnStmtCache<'_, P>,
|
stmts: &mut DataConnStmtCache<'_, P>,
|
||||||
height: BlockHeight,
|
height: BlockHeight,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
stmts.stmt_update_expired.execute(&[u32::from(height)])?;
|
stmts.stmt_update_expired(height)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records information about a note that your wallet created.
|
/// Records information about a note that your wallet created.
|
||||||
|
@ -1106,18 +1026,16 @@ pub fn put_sent_note<'a, P: consensus::Parameters>(
|
||||||
value: Amount,
|
value: Amount,
|
||||||
memo: Option<&MemoBytes>,
|
memo: Option<&MemoBytes>,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let ivalue: i64 = value.into();
|
|
||||||
// Try updating an existing sent note.
|
// Try updating an existing sent note.
|
||||||
if stmts.stmt_update_sent_note.execute(params![
|
if !stmts.stmt_update_sent_note(
|
||||||
u32::from(account),
|
account,
|
||||||
encode_payment_address_p(&stmts.wallet_db.params, to),
|
&encode_payment_address_p(&stmts.wallet_db.params, to),
|
||||||
ivalue,
|
value,
|
||||||
&memo.map(|m| m.as_slice()),
|
memo,
|
||||||
tx_ref,
|
tx_ref,
|
||||||
PoolType::Sapling.typecode(),
|
PoolType::Sapling,
|
||||||
output_index as i64,
|
output_index,
|
||||||
])? == 0
|
)? {
|
||||||
{
|
|
||||||
// It isn't there, so insert.
|
// It isn't there, so insert.
|
||||||
insert_sent_note(stmts, tx_ref, output_index, account, to, value, memo)?
|
insert_sent_note(stmts, tx_ref, output_index, account, to, value, memo)?
|
||||||
}
|
}
|
||||||
|
@ -1141,18 +1059,16 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>(
|
||||||
to: &TransparentAddress,
|
to: &TransparentAddress,
|
||||||
value: Amount,
|
value: Amount,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let ivalue: i64 = value.into();
|
|
||||||
// Try updating an existing sent UTXO.
|
// Try updating an existing sent UTXO.
|
||||||
if stmts.stmt_update_sent_note.execute(params![
|
if !stmts.stmt_update_sent_note(
|
||||||
u32::from(account),
|
account,
|
||||||
encode_transparent_address_p(&stmts.wallet_db.params, to),
|
&encode_transparent_address_p(&stmts.wallet_db.params, to),
|
||||||
ivalue,
|
value,
|
||||||
(None::<&[u8]>),
|
None,
|
||||||
tx_ref,
|
tx_ref,
|
||||||
PoolType::Transparent.typecode(),
|
PoolType::Transparent,
|
||||||
output_index as i64,
|
output_index,
|
||||||
])? == 0
|
)? {
|
||||||
{
|
|
||||||
// It isn't there, so insert.
|
// It isn't there, so insert.
|
||||||
insert_sent_utxo(stmts, tx_ref, output_index, account, to, value)?
|
insert_sent_utxo(stmts, tx_ref, output_index, account, to, value)?
|
||||||
}
|
}
|
||||||
|
@ -1181,18 +1097,16 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>(
|
||||||
memo: Option<&MemoBytes>,
|
memo: Option<&MemoBytes>,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let to_str = encode_payment_address_p(&stmts.wallet_db.params, to);
|
let to_str = encode_payment_address_p(&stmts.wallet_db.params, to);
|
||||||
let ivalue: i64 = value.into();
|
|
||||||
stmts.stmt_insert_sent_note.execute(params![
|
|
||||||
tx_ref,
|
|
||||||
PoolType::Sapling.typecode(),
|
|
||||||
(output_index as i64),
|
|
||||||
u32::from(account),
|
|
||||||
to_str,
|
|
||||||
ivalue,
|
|
||||||
memo.map(|m| m.as_slice().to_vec()),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(())
|
stmts.stmt_insert_sent_note(
|
||||||
|
tx_ref,
|
||||||
|
PoolType::Sapling,
|
||||||
|
output_index,
|
||||||
|
account,
|
||||||
|
&to_str,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts information about a sent transparent UTXO into the wallet database.
|
/// Inserts information about a sent transparent UTXO into the wallet database.
|
||||||
|
@ -1210,18 +1124,16 @@ pub fn insert_sent_utxo<'a, P: consensus::Parameters>(
|
||||||
value: Amount,
|
value: Amount,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
|
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
|
||||||
let ivalue: i64 = value.into();
|
|
||||||
stmts.stmt_insert_sent_note.execute(params![
|
|
||||||
tx_ref,
|
|
||||||
PoolType::Transparent.typecode(),
|
|
||||||
output_index as i64,
|
|
||||||
u32::from(account),
|
|
||||||
to_str,
|
|
||||||
ivalue,
|
|
||||||
(None::<&[u8]>),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(())
|
stmts.stmt_insert_sent_note(
|
||||||
|
tx_ref,
|
||||||
|
PoolType::Transparent,
|
||||||
|
output_index,
|
||||||
|
account,
|
||||||
|
&to_str,
|
||||||
|
value,
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue