Move WalletMigrationAddTxViews to a submodule.
This commit is contained in:
parent
7842e6274f
commit
b327bf7073
|
@ -9,11 +9,8 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{self, BlockHeight, BranchId},
|
consensus::{self, BlockHeight},
|
||||||
transaction::{
|
transaction::components::amount::BalanceError,
|
||||||
components::amount::{Amount, BalanceError},
|
|
||||||
Transaction,
|
|
||||||
},
|
|
||||||
zip32::AccountId,
|
zip32::AccountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,9 +214,15 @@ struct WalletMigration2<P> {
|
||||||
seed: Option<SecretVec<u8>>,
|
seed: Option<SecretVec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P> WalletMigration2<P> {
|
||||||
|
fn id() -> Uuid {
|
||||||
|
Uuid::parse_str("be57ef3b-388e-42ea-97e2-678dafcf9754").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P> Migration for WalletMigration2<P> {
|
impl<P> Migration for WalletMigration2<P> {
|
||||||
fn id(&self) -> Uuid {
|
fn id(&self) -> Uuid {
|
||||||
::uuid::Uuid::parse_str("be57ef3b-388e-42ea-97e2-678dafcf9754").unwrap()
|
WalletMigration2::<P>::id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dependencies(&self) -> HashSet<Uuid> {
|
fn dependencies(&self) -> HashSet<Uuid> {
|
||||||
|
@ -449,180 +452,6 @@ impl<P: consensus::Parameters> RusqliteMigration for WalletMigration2<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WalletMigrationAddTxViews<P> {
|
|
||||||
params: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> Migration for WalletMigrationAddTxViews<P> {
|
|
||||||
fn id(&self) -> Uuid {
|
|
||||||
::uuid::Uuid::parse_str("282fad2e-8372-4ca0-8bed-71821320909f").unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dependencies(&self) -> HashSet<Uuid> {
|
|
||||||
["be57ef3b-388e-42ea-97e2-678dafcf9754"]
|
|
||||||
.iter()
|
|
||||||
.map(|uuidstr| ::uuid::Uuid::parse_str(uuidstr).unwrap())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> &'static str {
|
|
||||||
"Add transaction summary views & add fee information to transactions."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: consensus::Parameters> RusqliteMigration for WalletMigrationAddTxViews<P> {
|
|
||||||
type Error = WalletMigrationError;
|
|
||||||
|
|
||||||
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
|
||||||
transaction.execute_batch("ALTER TABLE transactions ADD COLUMN fee INTEGER;")?;
|
|
||||||
|
|
||||||
let mut stmt_list_txs =
|
|
||||||
transaction.prepare("SELECT id_tx, raw, block FROM transactions")?;
|
|
||||||
|
|
||||||
let mut stmt_set_fee =
|
|
||||||
transaction.prepare("UPDATE transactions SET fee = ? WHERE id_tx = ?")?;
|
|
||||||
|
|
||||||
let mut stmt_find_utxo_value = transaction
|
|
||||||
.prepare("SELECT value_zat FROM utxos WHERE prevout_txid = ? AND prevout_idx = ?")?;
|
|
||||||
|
|
||||||
let mut tx_rows = stmt_list_txs.query(NO_PARAMS)?;
|
|
||||||
while let Some(row) = tx_rows.next()? {
|
|
||||||
let id_tx: i64 = row.get(0)?;
|
|
||||||
let tx_bytes: Vec<u8> = row.get(1)?;
|
|
||||||
let h: u32 = row.get(2)?;
|
|
||||||
let block_height = BlockHeight::from(h);
|
|
||||||
|
|
||||||
let tx = Transaction::read(
|
|
||||||
&tx_bytes[..],
|
|
||||||
BranchId::for_height(&self.params, block_height),
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
WalletMigrationError::CorruptedData(format!(
|
|
||||||
"Parsing failed for transaction {:?}: {:?}",
|
|
||||||
id_tx, e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let fee_paid = tx.fee_paid(|op| {
|
|
||||||
stmt_find_utxo_value
|
|
||||||
.query_row(&[op.hash().to_sql()?, op.n().to_sql()?], |row| {
|
|
||||||
row.get(0).map(|i| Amount::from_i64(i).unwrap())
|
|
||||||
})
|
|
||||||
.map_err(WalletMigrationError::DbError)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
stmt_set_fee.execute(&[i64::from(fee_paid), id_tx])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.execute_batch(
|
|
||||||
"CREATE VIEW v_tx_sent AS
|
|
||||||
SELECT transactions.id_tx AS id_tx,
|
|
||||||
transactions.block AS mined_height,
|
|
||||||
transactions.tx_index AS tx_index,
|
|
||||||
transactions.txid AS txid,
|
|
||||||
transactions.expiry_height AS expiry_height,
|
|
||||||
transactions.raw AS raw,
|
|
||||||
SUM(sent_notes.value) AS sent_total,
|
|
||||||
COUNT(sent_notes.id_note) AS sent_note_count,
|
|
||||||
SUM(
|
|
||||||
CASE
|
|
||||||
WHEN sent_notes.memo IS NULL THEN 0
|
|
||||||
WHEN SUBSTR(sent_notes.memo, 0, 2) = X'F6' THEN 0
|
|
||||||
ELSE 1
|
|
||||||
END
|
|
||||||
) AS memo_count,
|
|
||||||
blocks.time AS block_time
|
|
||||||
FROM transactions
|
|
||||||
JOIN sent_notes
|
|
||||||
ON transactions.id_tx = sent_notes.tx
|
|
||||||
LEFT JOIN blocks
|
|
||||||
ON transactions.block = blocks.height
|
|
||||||
GROUP BY sent_notes.tx;
|
|
||||||
CREATE VIEW v_tx_received AS
|
|
||||||
SELECT transactions.id_tx AS id_tx,
|
|
||||||
transactions.block AS mined_height,
|
|
||||||
transactions.tx_index AS tx_index,
|
|
||||||
transactions.txid AS txid,
|
|
||||||
SUM(received_notes.value) AS received_total,
|
|
||||||
COUNT(received_notes.id_note) AS received_note_count,
|
|
||||||
SUM(
|
|
||||||
CASE
|
|
||||||
WHEN received_notes.memo IS NULL THEN 0
|
|
||||||
WHEN SUBSTR(received_notes.memo, 0, 2) = X'F6' THEN 0
|
|
||||||
ELSE 1
|
|
||||||
END
|
|
||||||
) AS memo_count,
|
|
||||||
blocks.time AS block_time
|
|
||||||
FROM transactions
|
|
||||||
JOIN received_notes
|
|
||||||
ON transactions.id_tx = received_notes.tx
|
|
||||||
LEFT JOIN blocks
|
|
||||||
ON transactions.block = blocks.height
|
|
||||||
GROUP BY received_notes.tx;
|
|
||||||
CREATE VIEW v_transactions AS
|
|
||||||
SELECT id_tx,
|
|
||||||
mined_height,
|
|
||||||
tx_index,
|
|
||||||
txid,
|
|
||||||
expiry_height,
|
|
||||||
raw,
|
|
||||||
SUM(value) + MAX(fee) AS net_value,
|
|
||||||
SUM(is_change) > 0 AS has_change,
|
|
||||||
SUM(memo_present) AS memo_count
|
|
||||||
FROM (
|
|
||||||
SELECT transactions.id_tx AS id_tx,
|
|
||||||
transactions.block AS mined_height,
|
|
||||||
transactions.tx_index AS tx_index,
|
|
||||||
transactions.txid AS txid,
|
|
||||||
transactions.expiry_height AS expiry_height,
|
|
||||||
transactions.raw AS raw,
|
|
||||||
0 AS fee,
|
|
||||||
CASE
|
|
||||||
WHEN received_notes.is_change THEN 0
|
|
||||||
ELSE value
|
|
||||||
END AS value,
|
|
||||||
received_notes.is_change AS is_change,
|
|
||||||
CASE
|
|
||||||
WHEN received_notes.memo IS NULL THEN 0
|
|
||||||
WHEN SUBSTR(received_notes.memo, 0, 2) = X'F6' THEN 0
|
|
||||||
ELSE 1
|
|
||||||
END AS memo_present
|
|
||||||
FROM transactions
|
|
||||||
JOIN received_notes ON transactions.id_tx = received_notes.tx
|
|
||||||
UNION
|
|
||||||
SELECT transactions.id_tx AS id_tx,
|
|
||||||
transactions.block AS mined_height,
|
|
||||||
transactions.tx_index AS tx_index,
|
|
||||||
transactions.txid AS txid,
|
|
||||||
transactions.expiry_height AS expiry_height,
|
|
||||||
transactions.raw AS raw,
|
|
||||||
transactions.fee AS fee,
|
|
||||||
-sent_notes.value AS value,
|
|
||||||
false AS is_change,
|
|
||||||
CASE
|
|
||||||
WHEN sent_notes.memo IS NULL THEN 0
|
|
||||||
WHEN SUBSTR(sent_notes.memo, 0, 2) = X'F6' THEN 0
|
|
||||||
ELSE 1
|
|
||||||
END AS memo_present
|
|
||||||
FROM transactions
|
|
||||||
JOIN sent_notes ON transactions.id_tx = sent_notes.tx
|
|
||||||
)
|
|
||||||
GROUP BY id_tx;",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn down(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
|
||||||
transaction.execute_batch(
|
|
||||||
"DROP VIEW v_tx_sent_notes;
|
|
||||||
DROP VIEW v_tx_received_notes;
|
|
||||||
DROP VIEW v_tx_notes;",
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up the internal structure of the data database.
|
/// Sets up the internal structure of the data database.
|
||||||
///
|
///
|
||||||
/// This procedure will automatically perform migration operations to update the wallet database to
|
/// This procedure will automatically perform migration operations to update the wallet database to
|
||||||
|
@ -661,6 +490,14 @@ impl<P: consensus::Parameters> RusqliteMigration for WalletMigrationAddTxViews<P
|
||||||
pub fn init_wallet_db<P: consensus::Parameters + 'static>(
|
pub fn init_wallet_db<P: consensus::Parameters + 'static>(
|
||||||
wdb: &mut WalletDb<P>,
|
wdb: &mut WalletDb<P>,
|
||||||
seed: Option<SecretVec<u8>>,
|
seed: Option<SecretVec<u8>>,
|
||||||
|
) -> Result<(), MigratorError<WalletMigrationError>> {
|
||||||
|
init_wallet_db_internal(wdb, seed, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_wallet_db_internal<P: consensus::Parameters + 'static>(
|
||||||
|
wdb: &mut WalletDb<P>,
|
||||||
|
seed: Option<SecretVec<u8>>,
|
||||||
|
target_migration: Option<Uuid>,
|
||||||
) -> Result<(), MigratorError<WalletMigrationError>> {
|
) -> Result<(), MigratorError<WalletMigrationError>> {
|
||||||
wdb.conn
|
wdb.conn
|
||||||
.execute("PRAGMA foreign_keys = OFF", NO_PARAMS)
|
.execute("PRAGMA foreign_keys = OFF", NO_PARAMS)
|
||||||
|
@ -669,25 +506,10 @@ pub fn init_wallet_db<P: consensus::Parameters + 'static>(
|
||||||
adapter.init().expect("Migrations table setup succeeds.");
|
adapter.init().expect("Migrations table setup succeeds.");
|
||||||
|
|
||||||
let mut migrator = Migrator::new(adapter);
|
let mut migrator = Migrator::new(adapter);
|
||||||
let migration0 = Box::new(WalletMigration0 {});
|
|
||||||
let migration1 = Box::new(WalletMigration1 {});
|
|
||||||
let migration2 = Box::new(WalletMigration2 {
|
|
||||||
params: wdb.params.clone(),
|
|
||||||
seed,
|
|
||||||
});
|
|
||||||
let migration3 = Box::new(WalletMigrationAddTxViews {
|
|
||||||
params: wdb.params.clone(),
|
|
||||||
});
|
|
||||||
let migration4 = Box::new(migrations::AddressesTableMigration {
|
|
||||||
params: wdb.params.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
migrator
|
migrator
|
||||||
.register_multiple(vec![
|
.register_multiple(migrations::all_migrations(&wdb.params, seed))
|
||||||
migration0, migration1, migration2, migration3, migration4,
|
|
||||||
])
|
|
||||||
.expect("Wallet migration registration should have been successful.");
|
.expect("Wallet migration registration should have been successful.");
|
||||||
migrator.up(None)?;
|
migrator.up(target_migration)?;
|
||||||
wdb.conn
|
wdb.conn
|
||||||
.execute("PRAGMA foreign_keys = ON", NO_PARAMS)
|
.execute("PRAGMA foreign_keys = ON", NO_PARAMS)
|
||||||
.map_err(|e| MigratorError::Adapter(WalletMigrationError::from(e)))?;
|
.map_err(|e| MigratorError::Adapter(WalletMigrationError::from(e)))?;
|
||||||
|
@ -1539,82 +1361,6 @@ mod tests {
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).unwrap();
|
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(&mut db_data, None).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 transactions (block, id_tx, txid) VALUES (0, 0, '');
|
|
||||||
|
|
||||||
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
|
|
||||||
VALUES (0, 2, 0, 0, '', 2);
|
|
||||||
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
|
||||||
VALUES (0, 2, 1, 0, '', 3, X'61');
|
|
||||||
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
|
||||||
VALUES (0, 2, 2, 0, '', 0, X'f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000');
|
|
||||||
|
|
||||||
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change, memo)
|
|
||||||
VALUES (0, 0, 0, '', 5, '', 'a', false, X'62');
|
|
||||||
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change, memo)
|
|
||||||
VALUES (0, 1, 0, '', 7, '', 'b', true, X'63');",
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let mut q = db_data
|
|
||||||
.conn
|
|
||||||
.prepare("SELECT received_total, received_note_count, memo_count FROM v_tx_received")
|
|
||||||
.unwrap();
|
|
||||||
let mut rows = q.query(NO_PARAMS).unwrap();
|
|
||||||
let mut row_count = 0;
|
|
||||||
while let Some(row) = rows.next().unwrap() {
|
|
||||||
row_count += 1;
|
|
||||||
let total: i64 = row.get(0).unwrap();
|
|
||||||
let count: i64 = row.get(1).unwrap();
|
|
||||||
let memo_count: i64 = row.get(2).unwrap();
|
|
||||||
assert_eq!(total, 12);
|
|
||||||
assert_eq!(count, 2);
|
|
||||||
assert_eq!(memo_count, 2);
|
|
||||||
}
|
|
||||||
assert_eq!(row_count, 1);
|
|
||||||
|
|
||||||
let mut q = db_data
|
|
||||||
.conn
|
|
||||||
.prepare("SELECT sent_total, sent_note_count, memo_count FROM v_tx_sent")
|
|
||||||
.unwrap();
|
|
||||||
let mut rows = q.query(NO_PARAMS).unwrap();
|
|
||||||
let mut row_count = 0;
|
|
||||||
while let Some(row) = rows.next().unwrap() {
|
|
||||||
row_count += 1;
|
|
||||||
let total: i64 = row.get(0).unwrap();
|
|
||||||
let count: i64 = row.get(1).unwrap();
|
|
||||||
let memo_count: i64 = row.get(2).unwrap();
|
|
||||||
assert_eq!(total, 5);
|
|
||||||
assert_eq!(count, 3);
|
|
||||||
assert_eq!(memo_count, 1);
|
|
||||||
}
|
|
||||||
assert_eq!(row_count, 1);
|
|
||||||
|
|
||||||
let mut q = db_data
|
|
||||||
.conn
|
|
||||||
.prepare("SELECT net_value, has_change, memo_count FROM v_transactions")
|
|
||||||
.unwrap();
|
|
||||||
let mut rows = q.query(NO_PARAMS).unwrap();
|
|
||||||
let mut row_count = 0;
|
|
||||||
while let Some(row) = rows.next().unwrap() {
|
|
||||||
row_count += 1;
|
|
||||||
let net_value: i64 = row.get(0).unwrap();
|
|
||||||
let has_change: bool = row.get(1).unwrap();
|
|
||||||
let memo_count: i64 = row.get(2).unwrap();
|
|
||||||
assert_eq!(net_value, 0);
|
|
||||||
assert!(has_change);
|
|
||||||
assert_eq!(memo_count, 3);
|
|
||||||
}
|
|
||||||
assert_eq!(row_count, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_accounts_table_only_works_once() {
|
fn init_accounts_table_only_works_once() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
|
|
|
@ -1,2 +1,30 @@
|
||||||
mod addresses_table;
|
mod addresses_table;
|
||||||
pub(super) use addresses_table::AddressesTableMigration;
|
pub(super) use addresses_table::AddressesTableMigration;
|
||||||
|
|
||||||
|
mod add_transaction_views;
|
||||||
|
|
||||||
|
use schemer_rusqlite::RusqliteMigration;
|
||||||
|
use secrecy::SecretVec;
|
||||||
|
use zcash_primitives::consensus;
|
||||||
|
|
||||||
|
use super::{WalletMigration0, WalletMigration1, WalletMigration2, WalletMigrationError};
|
||||||
|
|
||||||
|
pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
|
||||||
|
params: &P,
|
||||||
|
seed: Option<SecretVec<u8>>,
|
||||||
|
) -> Vec<Box<dyn RusqliteMigration<Error = WalletMigrationError>>> {
|
||||||
|
vec![
|
||||||
|
Box::new(WalletMigration0 {}),
|
||||||
|
Box::new(WalletMigration1 {}),
|
||||||
|
Box::new(WalletMigration2 {
|
||||||
|
params: params.clone(),
|
||||||
|
seed,
|
||||||
|
}),
|
||||||
|
Box::new(AddressesTableMigration {
|
||||||
|
params: params.clone(),
|
||||||
|
}),
|
||||||
|
Box::new(add_transaction_views::Migration {
|
||||||
|
params: params.clone(),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
//! Functions for initializing the various databases.
|
||||||
|
use rusqlite::{self, types::ToSql, NO_PARAMS};
|
||||||
|
use schemer::{self};
|
||||||
|
use schemer_rusqlite::RusqliteMigration;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use zcash_primitives::{
|
||||||
|
consensus::{self, BlockHeight, BranchId},
|
||||||
|
transaction::{components::amount::Amount, Transaction},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::{WalletMigration2, WalletMigrationError};
|
||||||
|
|
||||||
|
pub(super) struct Migration<P> {
|
||||||
|
pub(super) params: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Migration<P> {
|
||||||
|
fn id() -> Uuid {
|
||||||
|
Uuid::parse_str("282fad2e-8372-4ca0-8bed-71821320909f").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> schemer::Migration for Migration<P> {
|
||||||
|
fn id(&self) -> Uuid {
|
||||||
|
Migration::<P>::id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dependencies(&self) -> HashSet<Uuid> {
|
||||||
|
let mut deps = HashSet::new();
|
||||||
|
deps.insert(WalletMigration2::<P>::id());
|
||||||
|
deps
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Add transaction summary views & add fee information to transactions."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
|
type Error = WalletMigrationError;
|
||||||
|
|
||||||
|
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
||||||
|
transaction.execute_batch("ALTER TABLE transactions ADD COLUMN fee INTEGER;")?;
|
||||||
|
|
||||||
|
let mut stmt_list_txs =
|
||||||
|
transaction.prepare("SELECT id_tx, raw, block FROM transactions")?;
|
||||||
|
|
||||||
|
let mut stmt_set_fee =
|
||||||
|
transaction.prepare("UPDATE transactions SET fee = ? WHERE id_tx = ?")?;
|
||||||
|
|
||||||
|
let mut stmt_find_utxo_value = transaction
|
||||||
|
.prepare("SELECT value_zat FROM utxos WHERE prevout_txid = ? AND prevout_idx = ?")?;
|
||||||
|
|
||||||
|
let mut tx_rows = stmt_list_txs.query(NO_PARAMS)?;
|
||||||
|
while let Some(row) = tx_rows.next()? {
|
||||||
|
let id_tx: i64 = row.get(0)?;
|
||||||
|
let tx_bytes: Vec<u8> = row.get(1)?;
|
||||||
|
let h: u32 = row.get(2)?;
|
||||||
|
let block_height = BlockHeight::from(h);
|
||||||
|
|
||||||
|
let tx = Transaction::read(
|
||||||
|
&tx_bytes[..],
|
||||||
|
BranchId::for_height(&self.params, block_height),
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
WalletMigrationError::CorruptedData(format!(
|
||||||
|
"Parsing failed for transaction {:?}: {:?}",
|
||||||
|
id_tx, e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let fee_paid = tx.fee_paid(|op| {
|
||||||
|
stmt_find_utxo_value
|
||||||
|
.query_row(&[op.hash().to_sql()?, op.n().to_sql()?], |row| {
|
||||||
|
row.get(0).map(|i| Amount::from_i64(i).unwrap())
|
||||||
|
})
|
||||||
|
.map_err(WalletMigrationError::DbError)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
stmt_set_fee.execute(&[i64::from(fee_paid), id_tx])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.execute_batch(
|
||||||
|
"CREATE VIEW v_tx_sent AS
|
||||||
|
SELECT transactions.id_tx AS id_tx,
|
||||||
|
transactions.block AS mined_height,
|
||||||
|
transactions.tx_index AS tx_index,
|
||||||
|
transactions.txid AS txid,
|
||||||
|
transactions.expiry_height AS expiry_height,
|
||||||
|
transactions.raw AS raw,
|
||||||
|
SUM(sent_notes.value) AS sent_total,
|
||||||
|
COUNT(sent_notes.id_note) AS sent_note_count,
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN sent_notes.memo IS NULL THEN 0
|
||||||
|
WHEN SUBSTR(sent_notes.memo, 0, 2) = X'F6' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END
|
||||||
|
) AS memo_count,
|
||||||
|
blocks.time AS block_time
|
||||||
|
FROM transactions
|
||||||
|
JOIN sent_notes
|
||||||
|
ON transactions.id_tx = sent_notes.tx
|
||||||
|
LEFT JOIN blocks
|
||||||
|
ON transactions.block = blocks.height
|
||||||
|
GROUP BY sent_notes.tx;
|
||||||
|
CREATE VIEW v_tx_received AS
|
||||||
|
SELECT transactions.id_tx AS id_tx,
|
||||||
|
transactions.block AS mined_height,
|
||||||
|
transactions.tx_index AS tx_index,
|
||||||
|
transactions.txid AS txid,
|
||||||
|
SUM(received_notes.value) AS received_total,
|
||||||
|
COUNT(received_notes.id_note) AS received_note_count,
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN received_notes.memo IS NULL THEN 0
|
||||||
|
WHEN SUBSTR(received_notes.memo, 0, 2) = X'F6' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END
|
||||||
|
) AS memo_count,
|
||||||
|
blocks.time AS block_time
|
||||||
|
FROM transactions
|
||||||
|
JOIN received_notes
|
||||||
|
ON transactions.id_tx = received_notes.tx
|
||||||
|
LEFT JOIN blocks
|
||||||
|
ON transactions.block = blocks.height
|
||||||
|
GROUP BY received_notes.tx;
|
||||||
|
CREATE VIEW v_transactions AS
|
||||||
|
SELECT id_tx,
|
||||||
|
mined_height,
|
||||||
|
tx_index,
|
||||||
|
txid,
|
||||||
|
expiry_height,
|
||||||
|
raw,
|
||||||
|
SUM(value) + MAX(fee) AS net_value,
|
||||||
|
SUM(is_change) > 0 AS has_change,
|
||||||
|
SUM(memo_present) AS memo_count
|
||||||
|
FROM (
|
||||||
|
SELECT transactions.id_tx AS id_tx,
|
||||||
|
transactions.block AS mined_height,
|
||||||
|
transactions.tx_index AS tx_index,
|
||||||
|
transactions.txid AS txid,
|
||||||
|
transactions.expiry_height AS expiry_height,
|
||||||
|
transactions.raw AS raw,
|
||||||
|
0 AS fee,
|
||||||
|
CASE
|
||||||
|
WHEN received_notes.is_change THEN 0
|
||||||
|
ELSE value
|
||||||
|
END AS value,
|
||||||
|
received_notes.is_change AS is_change,
|
||||||
|
CASE
|
||||||
|
WHEN received_notes.memo IS NULL THEN 0
|
||||||
|
WHEN SUBSTR(received_notes.memo, 0, 2) = X'F6' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END AS memo_present
|
||||||
|
FROM transactions
|
||||||
|
JOIN received_notes ON transactions.id_tx = received_notes.tx
|
||||||
|
UNION
|
||||||
|
SELECT transactions.id_tx AS id_tx,
|
||||||
|
transactions.block AS mined_height,
|
||||||
|
transactions.tx_index AS tx_index,
|
||||||
|
transactions.txid AS txid,
|
||||||
|
transactions.expiry_height AS expiry_height,
|
||||||
|
transactions.raw AS raw,
|
||||||
|
transactions.fee AS fee,
|
||||||
|
-sent_notes.value AS value,
|
||||||
|
false AS is_change,
|
||||||
|
CASE
|
||||||
|
WHEN sent_notes.memo IS NULL THEN 0
|
||||||
|
WHEN SUBSTR(sent_notes.memo, 0, 2) = X'F6' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END AS memo_present
|
||||||
|
FROM transactions
|
||||||
|
JOIN sent_notes ON transactions.id_tx = sent_notes.tx
|
||||||
|
)
|
||||||
|
GROUP BY id_tx;",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(&self, _transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
||||||
|
// TODO: something better than just panic?
|
||||||
|
panic!("Cannot revert this migration.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rusqlite::{self, NO_PARAMS};
|
||||||
|
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tests::{self},
|
||||||
|
wallet::init::init_wallet_db,
|
||||||
|
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(&mut db_data, None).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 transactions (block, id_tx, txid) VALUES (0, 0, '');
|
||||||
|
|
||||||
|
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
|
||||||
|
VALUES (0, 2, 0, 0, '', 2);
|
||||||
|
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
||||||
|
VALUES (0, 2, 1, 0, '', 3, X'61');
|
||||||
|
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
||||||
|
VALUES (0, 2, 2, 0, '', 0, X'f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000');
|
||||||
|
|
||||||
|
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change, memo)
|
||||||
|
VALUES (0, 0, 0, '', 5, '', 'a', false, X'62');
|
||||||
|
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change, memo)
|
||||||
|
VALUES (0, 1, 0, '', 7, '', 'b', true, X'63');",
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let mut q = db_data
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT received_total, received_note_count, memo_count FROM v_tx_received")
|
||||||
|
.unwrap();
|
||||||
|
let mut rows = q.query(NO_PARAMS).unwrap();
|
||||||
|
let mut row_count = 0;
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
row_count += 1;
|
||||||
|
let total: i64 = row.get(0).unwrap();
|
||||||
|
let count: i64 = row.get(1).unwrap();
|
||||||
|
let memo_count: i64 = row.get(2).unwrap();
|
||||||
|
assert_eq!(total, 12);
|
||||||
|
assert_eq!(count, 2);
|
||||||
|
assert_eq!(memo_count, 2);
|
||||||
|
}
|
||||||
|
assert_eq!(row_count, 1);
|
||||||
|
|
||||||
|
let mut q = db_data
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT sent_total, sent_note_count, memo_count FROM v_tx_sent")
|
||||||
|
.unwrap();
|
||||||
|
let mut rows = q.query(NO_PARAMS).unwrap();
|
||||||
|
let mut row_count = 0;
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
row_count += 1;
|
||||||
|
let total: i64 = row.get(0).unwrap();
|
||||||
|
let count: i64 = row.get(1).unwrap();
|
||||||
|
let memo_count: i64 = row.get(2).unwrap();
|
||||||
|
assert_eq!(total, 5);
|
||||||
|
assert_eq!(count, 3);
|
||||||
|
assert_eq!(memo_count, 1);
|
||||||
|
}
|
||||||
|
assert_eq!(row_count, 1);
|
||||||
|
|
||||||
|
let mut q = db_data
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT net_value, has_change, memo_count FROM v_transactions")
|
||||||
|
.unwrap();
|
||||||
|
let mut rows = q.query(NO_PARAMS).unwrap();
|
||||||
|
let mut row_count = 0;
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
row_count += 1;
|
||||||
|
let net_value: i64 = row.get(0).unwrap();
|
||||||
|
let has_change: bool = row.get(1).unwrap();
|
||||||
|
let memo_count: i64 = row.get(2).unwrap();
|
||||||
|
assert_eq!(net_value, 0);
|
||||||
|
assert!(has_change);
|
||||||
|
assert_eq!(memo_count, 3);
|
||||||
|
}
|
||||||
|
assert_eq!(row_count, 1);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue