Add more information to v_transactions, v_sent_tx, and v_received_tx

This adds sent and received note count information, transaction fees,
account information, and makes the information returned about sent
notes and received notes consistent with one another.
This commit is contained in:
Kris Nuttycombe 2022-10-05 12:59:52 -06:00
parent f689018fcd
commit 85390cb8ce
5 changed files with 129 additions and 61 deletions

View File

@ -380,7 +380,7 @@ mod tests {
"CREATE TABLE \"sent_notes\" ( "CREATE TABLE \"sent_notes\" (
id_note INTEGER PRIMARY KEY, id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL, tx INTEGER NOT NULL,
output_pool INTEGER NOT NULL , output_pool INTEGER NOT NULL,
output_index INTEGER NOT NULL, output_index INTEGER NOT NULL,
from_account INTEGER NOT NULL, from_account INTEGER NOT NULL,
to_address TEXT, to_address TEXT,
@ -438,16 +438,22 @@ mod tests {
} }
let expected_views = vec![ let expected_views = vec![
// v_transactions
"CREATE VIEW v_transactions AS "CREATE VIEW v_transactions AS
SELECT id_tx, SELECT notes.id_tx,
mined_height, notes.mined_height,
tx_index, notes.tx_index,
txid, notes.txid,
expiry_height, notes.expiry_height,
raw, notes.raw,
SUM(value) + MAX(fee) AS net_value, SUM(notes.value) + MAX(notes.fee) AS net_value,
SUM(is_change) > 0 AS has_change, MAX(notes.fee) AS fee_paid,
SUM(memo_present) AS memo_count SUM(notes.is_wallet_internal) > 0 AS is_wallet_internal,
SUM(notes.is_change) > 0 AS has_change,
SUM(notes.sent_count) AS sent_note_count,
SUM(notes.received_count) AS received_note_count,
SUM(notes.memo_present) AS memo_count,
blocks.time AS block_time
FROM ( FROM (
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
@ -460,7 +466,16 @@ mod tests {
WHEN received_notes.is_change THEN 0 WHEN received_notes.is_change THEN 0
ELSE value ELSE value
END AS value, END AS value,
received_notes.is_change AS is_change, 0 AS is_wallet_internal,
CASE
WHEN received_notes.is_change THEN 1
ELSE 0
END AS is_change,
CASE
WHEN received_notes.is_change THEN 0
ELSE 1
END AS received_count,
0 AS sent_count,
CASE CASE
WHEN received_notes.memo IS NULL THEN 0 WHEN received_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
@ -476,20 +491,31 @@ mod tests {
transactions.raw AS raw, transactions.raw AS raw,
transactions.fee AS fee, transactions.fee AS fee,
-sent_notes.value AS value, -sent_notes.value AS value,
false AS is_change, CASE
WHEN sent_notes.from_account = sent_notes.to_account THEN 1
ELSE 0
END AS is_wallet_internal,
0 AS is_change,
0 AS received_count,
1 AS sent_count,
CASE CASE
WHEN sent_notes.memo IS NULL THEN 0 WHEN sent_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
END AS memo_present END AS memo_present
FROM transactions FROM transactions
JOIN sent_notes ON transactions.id_tx = sent_notes.tx JOIN sent_notes ON transactions.id_tx = sent_notes.tx
) ) AS notes
GROUP BY id_tx", LEFT JOIN blocks ON notes.mined_height = blocks.height
GROUP BY notes.id_tx",
// v_tx_received
"CREATE VIEW v_tx_received AS "CREATE VIEW v_tx_received AS
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
transactions.tx_index AS tx_index, transactions.tx_index AS tx_index,
transactions.txid AS txid, transactions.txid AS txid,
transactions.expiry_height AS expiry_height,
transactions.raw AS raw,
MAX(received_notes.account) AS received_by_account,
SUM(received_notes.value) AS received_total, SUM(received_notes.value) AS received_total,
COUNT(received_notes.id_note) AS received_note_count, COUNT(received_notes.id_note) AS received_note_count,
SUM( SUM(
@ -504,29 +530,31 @@ mod tests {
ON transactions.id_tx = received_notes.tx ON transactions.id_tx = received_notes.tx
LEFT JOIN blocks LEFT JOIN blocks
ON transactions.block = blocks.height ON transactions.block = blocks.height
GROUP BY received_notes.tx", GROUP BY received_notes.tx, received_notes.account",
// v_tx_received
"CREATE VIEW v_tx_sent AS "CREATE VIEW v_tx_sent AS
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
transactions.tx_index AS tx_index, transactions.tx_index AS tx_index,
transactions.txid AS txid, transactions.txid AS txid,
transactions.expiry_height AS expiry_height, transactions.expiry_height AS expiry_height,
transactions.raw AS raw, transactions.raw AS raw,
SUM(sent_notes.value) AS sent_total, MAX(sent_notes.from_account) AS sent_from_account,
COUNT(sent_notes.id_note) AS sent_note_count, SUM(sent_notes.value) AS sent_total,
COUNT(sent_notes.id_note) AS sent_note_count,
SUM( SUM(
CASE CASE
WHEN sent_notes.memo IS NULL THEN 0 WHEN sent_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
END END
) AS memo_count, ) AS memo_count,
blocks.time AS block_time blocks.time AS block_time
FROM transactions FROM transactions
JOIN sent_notes JOIN sent_notes
ON transactions.id_tx = sent_notes.tx ON transactions.id_tx = sent_notes.tx
LEFT JOIN blocks LEFT JOIN blocks
ON transactions.block = blocks.height ON transactions.block = blocks.height
GROUP BY sent_notes.tx", GROUP BY sent_notes.tx, sent_notes.from_account",
]; ];
let mut views_query = db_data let mut views_query = db_data

View File

@ -16,6 +16,15 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
params: &P, params: &P,
seed: Option<SecretVec<u8>>, seed: Option<SecretVec<u8>>,
) -> Vec<Box<dyn RusqliteMigration<Error = WalletMigrationError>>> { ) -> Vec<Box<dyn RusqliteMigration<Error = WalletMigrationError>>> {
// initial_setup
// / \
// utxos_table ufvk_support ----------
// \ \ \
// \ addresses_table sent_notes_to_internal
// \ / /
// add_utxo_account /
// \ /
// add_transaction_views
vec![ vec![
Box::new(initial_setup::Migration {}), Box::new(initial_setup::Migration {}),
Box::new(utxos_table::Migration {}), Box::new(utxos_table::Migration {}),
@ -26,12 +35,12 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
Box::new(addresses_table::Migration { Box::new(addresses_table::Migration {
params: params.clone(), params: params.clone(),
}), }),
Box::new(add_transaction_views::Migration {
params: params.clone(),
}),
Box::new(add_utxo_account::Migration { Box::new(add_utxo_account::Migration {
_params: params.clone(), _params: params.clone(),
}), }),
Box::new(sent_notes_to_internal::Migration {}), Box::new(sent_notes_to_internal::Migration {}),
Box::new(add_transaction_views::Migration {
params: params.clone(),
}),
] ]
} }

View File

@ -14,7 +14,7 @@ use zcash_primitives::{
}, },
}; };
use super::{ufvk_support, utxos_table}; use super::{add_utxo_account, sent_notes_to_internal};
use crate::wallet::init::WalletMigrationError; use crate::wallet::init::WalletMigrationError;
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields( pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
@ -34,7 +34,7 @@ impl<P> schemer::Migration for Migration<P> {
} }
fn dependencies(&self) -> HashSet<Uuid> { fn dependencies(&self) -> HashSet<Uuid> {
[ufvk_support::MIGRATION_ID, utxos_table::MIGRATION_ID] [add_utxo_account::MIGRATION_ID, sent_notes_to_internal::MIGRATION_ID]
.into_iter() .into_iter()
.collect() .collect()
} }
@ -146,32 +146,39 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
transaction.execute_batch( transaction.execute_batch(
"CREATE VIEW v_tx_sent AS "CREATE VIEW v_tx_sent AS
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
transactions.tx_index AS tx_index, transactions.tx_index AS tx_index,
transactions.txid AS txid, transactions.txid AS txid,
transactions.expiry_height AS expiry_height, transactions.expiry_height AS expiry_height,
transactions.raw AS raw, transactions.raw AS raw,
SUM(sent_notes.value) AS sent_total, MAX(sent_notes.from_account) AS sent_from_account,
COUNT(sent_notes.id_note) AS sent_note_count, SUM(sent_notes.value) AS sent_total,
COUNT(sent_notes.id_note) AS sent_note_count,
SUM( SUM(
CASE CASE
WHEN sent_notes.memo IS NULL THEN 0 WHEN sent_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
END END
) AS memo_count, ) AS memo_count,
blocks.time AS block_time blocks.time AS block_time
FROM transactions FROM transactions
JOIN sent_notes JOIN sent_notes
ON transactions.id_tx = sent_notes.tx ON transactions.id_tx = sent_notes.tx
LEFT JOIN blocks LEFT JOIN blocks
ON transactions.block = blocks.height ON transactions.block = blocks.height
GROUP BY sent_notes.tx; GROUP BY sent_notes.tx, sent_notes.from_account;",
CREATE VIEW v_tx_received AS )?;
transaction.execute_batch(
"CREATE VIEW v_tx_received AS
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
transactions.tx_index AS tx_index, transactions.tx_index AS tx_index,
transactions.txid AS txid, transactions.txid AS txid,
transactions.expiry_height AS expiry_height,
transactions.raw AS raw,
MAX(received_notes.account) AS received_by_account,
SUM(received_notes.value) AS received_total, SUM(received_notes.value) AS received_total,
COUNT(received_notes.id_note) AS received_note_count, COUNT(received_notes.id_note) AS received_note_count,
SUM( SUM(
@ -186,17 +193,25 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
ON transactions.id_tx = received_notes.tx ON transactions.id_tx = received_notes.tx
LEFT JOIN blocks LEFT JOIN blocks
ON transactions.block = blocks.height ON transactions.block = blocks.height
GROUP BY received_notes.tx; GROUP BY received_notes.tx, received_notes.account;",
CREATE VIEW v_transactions AS )?;
SELECT id_tx,
mined_height, transaction.execute_batch(
tx_index, "CREATE VIEW v_transactions AS
txid, SELECT notes.id_tx,
expiry_height, notes.mined_height,
raw, notes.tx_index,
SUM(value) + MAX(fee) AS net_value, notes.txid,
SUM(is_change) > 0 AS has_change, notes.expiry_height,
SUM(memo_present) AS memo_count notes.raw,
SUM(notes.value) + MAX(notes.fee) AS net_value,
MAX(notes.fee) AS fee_paid,
SUM(notes.is_wallet_internal) > 0 AS is_wallet_internal,
SUM(notes.is_change) > 0 AS has_change,
SUM(notes.sent_count) AS sent_note_count,
SUM(notes.received_count) AS received_note_count,
SUM(notes.memo_present) AS memo_count,
blocks.time AS block_time
FROM ( FROM (
SELECT transactions.id_tx AS id_tx, SELECT transactions.id_tx AS id_tx,
transactions.block AS mined_height, transactions.block AS mined_height,
@ -209,7 +224,16 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
WHEN received_notes.is_change THEN 0 WHEN received_notes.is_change THEN 0
ELSE value ELSE value
END AS value, END AS value,
received_notes.is_change AS is_change, 0 AS is_wallet_internal,
CASE
WHEN received_notes.is_change THEN 1
ELSE 0
END AS is_change,
CASE
WHEN received_notes.is_change THEN 0
ELSE 1
END AS received_count,
0 AS sent_count,
CASE CASE
WHEN received_notes.memo IS NULL THEN 0 WHEN received_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
@ -225,15 +249,22 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
transactions.raw AS raw, transactions.raw AS raw,
transactions.fee AS fee, transactions.fee AS fee,
-sent_notes.value AS value, -sent_notes.value AS value,
false AS is_change, CASE
WHEN sent_notes.from_account = sent_notes.to_account THEN 1
ELSE 0
END AS is_wallet_internal,
0 AS is_change,
0 AS received_count,
1 AS sent_count,
CASE CASE
WHEN sent_notes.memo IS NULL THEN 0 WHEN sent_notes.memo IS NULL THEN 0
ELSE 1 ELSE 1
END AS memo_present END AS memo_present
FROM transactions FROM transactions
JOIN sent_notes ON transactions.id_tx = sent_notes.tx JOIN sent_notes ON transactions.id_tx = sent_notes.tx
) ) AS notes
GROUP BY id_tx;", LEFT JOIN blocks ON notes.mined_height = blocks.height
GROUP BY notes.id_tx;",
)?; )?;
Ok(()) Ok(())

View File

@ -7,7 +7,7 @@ use schemer;
use schemer_rusqlite::RusqliteMigration; use schemer_rusqlite::RusqliteMigration;
use uuid::Uuid; use uuid::Uuid;
use super::{addresses_table, utxos_table}; use super::{ufvk_support};
use crate::wallet::init::WalletMigrationError; use crate::wallet::init::WalletMigrationError;
/// This migration adds the `to_account` field to the `sent_notes` table. /// This migration adds the `to_account` field to the `sent_notes` table.
@ -28,7 +28,7 @@ impl schemer::Migration for Migration {
} }
fn dependencies(&self) -> HashSet<Uuid> { fn dependencies(&self) -> HashSet<Uuid> {
[utxos_table::MIGRATION_ID, addresses_table::MIGRATION_ID] [ufvk_support::MIGRATION_ID]
.into_iter() .into_iter()
.collect() .collect()
} }
@ -48,7 +48,7 @@ impl RusqliteMigration for Migration {
"CREATE TABLE sent_notes_new ( "CREATE TABLE sent_notes_new (
id_note INTEGER PRIMARY KEY, id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL, tx INTEGER NOT NULL,
output_pool INTEGER NOT NULL , output_pool INTEGER NOT NULL,
output_index INTEGER NOT NULL, output_index INTEGER NOT NULL,
from_account INTEGER NOT NULL, from_account INTEGER NOT NULL,
to_address TEXT, to_address TEXT,

View File

@ -19,7 +19,7 @@ use zcash_primitives::legacy::keys::IncomingViewingKey;
use zcash_client_backend::encoding::AddressCodec; use zcash_client_backend::encoding::AddressCodec;
use crate::wallet::{ use crate::wallet::{
init::{migrations::utxos_table, WalletMigrationError}, init::{migrations::initial_setup, WalletMigrationError},
pool_code, pool_code,
}; };
@ -41,7 +41,7 @@ impl<P> schemer::Migration for Migration<P> {
} }
fn dependencies(&self) -> HashSet<Uuid> { fn dependencies(&self) -> HashSet<Uuid> {
[utxos_table::MIGRATION_ID].into_iter().collect() [initial_setup::MIGRATION_ID].into_iter().collect()
} }
fn description(&self) -> &'static str { fn description(&self) -> &'static str {