2022-11-06 04:50:51 -08:00
use crate ::orchard ::derive_orchard_keys ;
2022-05-19 09:19:04 -07:00
use rusqlite ::{ params , Connection , OptionalExtension } ;
2022-10-28 23:40:05 -07:00
use zcash_primitives ::consensus ::{ Network , Parameters } ;
2021-07-09 22:44:13 -07:00
pub fn get_schema_version ( connection : & Connection ) -> anyhow ::Result < u32 > {
2021-07-16 01:42:29 -07:00
let version : Option < u32 > = connection
. query_row (
" SELECT version FROM schema_version WHERE id = 1 " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-07-16 01:42:29 -07:00
| row | row . get ( 0 ) ,
)
. optional ( ) ? ;
2021-07-09 22:44:13 -07:00
Ok ( version . unwrap_or ( 0 ) )
}
pub fn update_schema_version ( connection : & Connection , version : u32 ) -> anyhow ::Result < ( ) > {
2021-07-16 01:42:29 -07:00
connection . execute (
" INSERT INTO schema_version(id, version) VALUES (1, ?1) \
ON CONFLICT ( id ) DO UPDATE SET version = excluded . version " ,
params! [ version ] ,
) ? ;
2021-07-09 22:44:13 -07:00
Ok ( ( ) )
}
2021-08-09 07:13:10 -07:00
2021-11-26 19:34:26 -08:00
pub fn reset_db ( connection : & Connection ) -> anyhow ::Result < ( ) > {
// don't drop account data: accounts, taddrs, secret_shares
2022-05-19 09:19:04 -07:00
connection . execute ( " DROP TABLE blocks " , [ ] ) ? ;
connection . execute ( " DROP TABLE transactions " , [ ] ) ? ;
connection . execute ( " DROP TABLE received_notes " , [ ] ) ? ;
connection . execute ( " DROP TABLE sapling_witnesses " , [ ] ) ? ;
connection . execute ( " DROP TABLE diversifiers " , [ ] ) ? ;
connection . execute ( " DROP TABLE historical_prices " , [ ] ) ? ;
2022-06-08 05:48:16 -07:00
update_schema_version ( connection , 0 ) ? ;
2021-11-26 19:34:26 -08:00
Ok ( ( ) )
}
2022-12-23 23:22:38 -08:00
const LATEST_VERSION : u32 = 6 ;
2022-11-18 03:02:53 -08:00
2022-11-15 02:21:47 -08:00
pub fn init_db ( connection : & Connection , network : & Network , has_ua : bool ) -> anyhow ::Result < ( ) > {
2021-08-09 07:13:10 -07:00
connection . execute (
" CREATE TABLE IF NOT EXISTS schema_version (
id INTEGER PRIMARY KEY NOT NULL ,
version INTEGER NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
2022-06-08 05:48:16 -07:00
let version = get_schema_version ( connection ) ? ;
2021-08-09 07:13:10 -07:00
if version < 1 {
connection . execute (
" CREATE TABLE IF NOT EXISTS accounts (
id_account INTEGER PRIMARY KEY ,
name TEXT NOT NULL ,
seed TEXT ,
2022-03-07 06:47:06 -08:00
aindex INTEGER NOT NULL ,
2021-08-09 07:13:10 -07:00
sk TEXT ,
ivk TEXT NOT NULL UNIQUE ,
address TEXT NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS blocks (
height INTEGER PRIMARY KEY ,
hash BLOB NOT NULL ,
timestamp INTEGER NOT NULL ,
sapling_tree BLOB NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS transactions (
id_tx INTEGER PRIMARY KEY ,
account INTEGER NOT NULL ,
txid BLOB NOT NULL ,
height INTEGER NOT NULL ,
timestamp INTEGER NOT NULL ,
value INTEGER NOT NULL ,
address TEXT ,
memo TEXT ,
tx_index INTEGER ,
CONSTRAINT tx_account UNIQUE ( height , tx_index , account ) ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS received_notes (
id_note INTEGER PRIMARY KEY ,
account INTEGER NOT NULL ,
position INTEGER NOT NULL ,
tx INTEGER NOT NULL ,
height INTEGER NOT NULL ,
output_index INTEGER NOT NULL ,
diversifier BLOB NOT NULL ,
value INTEGER NOT NULL ,
rcm BLOB NOT NULL ,
nf BLOB NOT NULL UNIQUE ,
spent INTEGER ,
2022-03-07 06:47:06 -08:00
excluded BOOL ,
2021-08-09 07:13:10 -07:00
CONSTRAINT tx_output UNIQUE ( tx , output_index ) ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS sapling_witnesses (
id_witness INTEGER PRIMARY KEY ,
note INTEGER NOT NULL ,
height INTEGER NOT NULL ,
witness BLOB NOT NULL ,
CONSTRAINT witness_height UNIQUE ( note , height ) ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS diversifiers (
account INTEGER PRIMARY KEY NOT NULL ,
diversifier_index BLOB NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS taddrs (
account INTEGER PRIMARY KEY NOT NULL ,
sk TEXT NOT NULL ,
address TEXT NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
connection . execute (
" CREATE TABLE IF NOT EXISTS historical_prices (
currency TEXT NOT NULL ,
timestamp INTEGER NOT NULL ,
price REAL NOT NULL ,
PRIMARY KEY ( currency , timestamp ) ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-08-09 07:13:10 -07:00
) ? ;
2021-09-08 07:10:22 -07:00
connection . execute (
" CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY ,
name TEXT NOT NULL ,
address TEXT NOT NULL ,
dirty BOOL NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2021-09-08 07:10:22 -07:00
) ? ;
2022-03-16 04:51:09 -07:00
}
2022-03-16 04:14:08 -07:00
2022-03-16 04:51:09 -07:00
if version < 2 {
2022-03-16 04:14:08 -07:00
connection . execute (
2022-06-07 09:58:24 -07:00
" CREATE INDEX i_received_notes ON received_notes(account) " ,
[ ] ,
) ? ;
connection . execute ( " CREATE INDEX i_account ON accounts(address) " , [ ] ) ? ;
connection . execute ( " CREATE INDEX i_contact ON contacts(address) " , [ ] ) ? ;
connection . execute ( " CREATE INDEX i_transaction ON transactions(account) " , [ ] ) ? ;
connection . execute ( " CREATE INDEX i_witness ON sapling_witnesses(height) " , [ ] ) ? ;
2021-09-08 07:10:22 -07:00
}
2022-04-15 23:23:50 -07:00
if version < 3 {
connection . execute (
" CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY ,
account INTEGER NOT NULL ,
sender TEXT ,
recipient TEXT NOT NULL ,
subject TEXT NOT NULL ,
body TEXT NOT NULL ,
timestamp INTEGER NOT NULL ,
height INTEGER NOT NULL ,
read BOOL NOT NULL ) " ,
2022-05-19 09:19:04 -07:00
[ ] ,
2022-04-15 23:23:50 -07:00
) ? ;
2022-04-16 06:15:05 -07:00
// Don't index because it *really* slows down inserts
// connection.execute(
// "CREATE INDEX i_messages ON messages(account)",
2022-05-19 09:19:04 -07:00
// [],
2022-04-16 06:15:05 -07:00
// )?;
2022-04-15 23:23:50 -07:00
}
2022-09-05 08:05:55 -07:00
if version < 4 {
connection . execute ( " ALTER TABLE messages ADD id_tx INTEGER " , [ ] ) ? ;
}
2022-10-19 23:32:11 -07:00
if version < 5 {
2022-11-06 04:50:51 -08:00
connection . execute (
" CREATE TABLE orchard_addrs(
2022-10-28 23:40:05 -07:00
account INTEGER PRIMARY KEY ,
sk BLOB ,
2022-11-06 04:50:51 -08:00
fvk BLOB NOT NULL ) " ,
[ ] ,
) ? ;
connection . execute (
" CREATE TABLE ua_settings(
2022-10-28 23:40:05 -07:00
account INTEGER PRIMARY KEY ,
transparent BOOL NOT NULL ,
sapling BOOL NOT NULL ,
2022-11-06 04:50:51 -08:00
orchard BOOL NOT NULL ) " ,
[ ] ,
) ? ;
2022-11-15 02:21:47 -08:00
if has_ua {
upgrade_accounts ( & connection , network ) ? ;
}
2022-11-06 04:50:51 -08:00
connection . execute (
" CREATE TABLE sapling_tree(
2022-10-28 06:02:34 -07:00
height INTEGER PRIMARY KEY ,
2022-11-06 04:50:51 -08:00
tree BLOB NOT NULL ) " ,
[ ] ,
) ? ;
connection . execute (
" CREATE TABLE orchard_tree(
2022-10-28 06:02:34 -07:00
height INTEGER PRIMARY KEY ,
2022-11-06 04:50:51 -08:00
tree BLOB NOT NULL ) " ,
[ ] ,
) ? ;
connection . execute (
" INSERT INTO sapling_tree SELECT height, sapling_tree FROM blocks " ,
[ ] ,
) ? ;
2022-10-28 06:02:34 -07:00
connection . execute ( " ALTER TABLE blocks DROP sapling_tree " , [ ] ) ? ;
2022-11-04 18:58:35 -07:00
connection . execute (
" CREATE TABLE IF NOT EXISTS new_received_notes (
id_note INTEGER PRIMARY KEY ,
account INTEGER NOT NULL ,
position INTEGER NOT NULL ,
tx INTEGER NOT NULL ,
height INTEGER NOT NULL ,
output_index INTEGER NOT NULL ,
diversifier BLOB NOT NULL ,
value INTEGER NOT NULL ,
rcm BLOB NOT NULL ,
nf BLOB NOT NULL UNIQUE ,
rho BLOB ,
2022-11-22 01:53:16 -08:00
orchard BOOL NOT NULL DEFAULT false ,
2022-11-04 18:58:35 -07:00
spent INTEGER ,
excluded BOOL ,
CONSTRAINT tx_output UNIQUE ( tx , orchard , output_index ) ) " ,
[ ] ,
) ? ;
2022-11-06 04:50:51 -08:00
connection . execute (
" INSERT INTO new_received_notes(
2022-11-04 18:58:35 -07:00
id_note , account , position , tx , height , output_index , diversifier , value ,
rcm , nf , spent , excluded
2022-11-06 04:50:51 -08:00
) SELECT * FROM received_notes " ,
[ ] ,
) ? ;
2022-11-04 18:58:35 -07:00
connection . execute ( " DROP TABLE received_notes " , [ ] ) ? ;
2022-11-06 04:50:51 -08:00
connection . execute (
" ALTER TABLE new_received_notes RENAME TO received_notes " ,
[ ] ,
) ? ;
2022-10-19 23:32:11 -07:00
connection . execute (
" CREATE TABLE IF NOT EXISTS orchard_witnesses (
id_witness INTEGER PRIMARY KEY ,
note INTEGER NOT NULL ,
height INTEGER NOT NULL ,
witness BLOB NOT NULL ,
CONSTRAINT witness_height UNIQUE ( note , height ) ) " ,
[ ] ,
) ? ;
connection . execute (
" CREATE INDEX IF NOT EXISTS i_orchard_witness ON orchard_witnesses(height) " ,
[ ] ,
) ? ;
2022-11-15 02:21:47 -08:00
connection . execute (
2022-11-22 01:53:16 -08:00
" ALTER TABLE messages ADD incoming BOOL NOT NULL DEFAULT true " ,
2022-11-15 02:21:47 -08:00
[ ] ,
) ? ;
2022-10-19 23:32:11 -07:00
}
2022-12-23 23:22:38 -08:00
if version < 6 {
connection . execute (
" CREATE TABLE IF NOT EXISTS send_templates (
id_send_template INTEGER PRIMARY KEY ,
title TEXT NOT NULL ,
address TEXT NOT NULL ,
amount INTEGER NOT NULL ,
fiat_amount DECIMAL NOT NULL ,
fee_included BOOL NOT NULL ,
fiat TEXT ,
include_reply_to BOOL NOT NULL ,
subject TEXT NOT NULL ,
2022-12-30 12:34:26 -08:00
body TEXT NOT NULL ) " ,
[ ] ,
) ? ;
2022-12-23 23:22:38 -08:00
}
2022-11-18 03:02:53 -08:00
if version ! = LATEST_VERSION {
update_schema_version ( connection , LATEST_VERSION ) ? ;
2022-11-15 02:21:47 -08:00
connection . cache_flush ( ) ? ;
2022-05-13 02:50:52 -07:00
log ::info! ( " Database migrated " ) ;
}
2021-08-09 07:13:10 -07:00
2022-09-21 01:11:50 -07:00
// We may get a database that has no valid schema version from a version of single currency Z/YWallet
// because we kept the same app name in Google/Apple Stores. The upgraded app fails to recognize the db tables
// At least we monkey patch the accounts table to let the user access the backup page and recover his seed phrase
let c = connection . query_row (
" SELECT COUNT(*) FROM pragma_table_info('accounts') WHERE name = 'aindex' " ,
params! [ ] ,
| row | {
let c : u32 = row . get ( 0 ) ? ;
Ok ( c )
} ,
) ? ;
if c = = 0 {
connection . execute (
" ALTER TABLE accounts ADD aindex INTEGER NOT NULL DEFAULT (0) " ,
params! [ ] ,
) ? ;
}
2021-08-09 07:13:10 -07:00
Ok ( ( ) )
}
2022-10-28 23:40:05 -07:00
fn upgrade_accounts ( connection : & Connection , network : & Network ) -> anyhow ::Result < ( ) > {
let mut statement = connection . prepare ( " SELECT a.id_account, a.seed, a.aindex, t.address FROM accounts a LEFT JOIN taddrs t ON a.id_account = t.account " ) ? ;
let rows = statement . query_map ( [ ] , | row | {
let id_account : u32 = row . get ( 0 ) ? ;
let seed : Option < String > = row . get ( 1 ) ? ;
let aindex : u32 = row . get ( 2 ) ? ;
let transparent_address : Option < String > = row . get ( 3 ) ? ;
Ok ( ( id_account , seed , aindex , transparent_address . is_some ( ) ) )
} ) ? ;
let mut res = vec! [ ] ;
for row in rows {
res . push ( row ? ) ;
}
for ( id_account , seed , aindex , has_transparent ) in res {
let has_orchard = seed . is_some ( ) ;
if let Some ( seed ) = seed {
let orchard_keys = derive_orchard_keys ( network . coin_type ( ) , & seed , aindex ) ;
2022-11-06 04:50:51 -08:00
connection . execute (
" INSERT INTO orchard_addrs(account, sk, fvk) VALUES (?1,?2,?3) " ,
params! [ id_account , & orchard_keys . sk , & orchard_keys . fvk ] ,
) ? ;
2022-10-28 23:40:05 -07:00
}
2022-11-06 04:50:51 -08:00
connection . execute (
" INSERT INTO ua_settings(account, transparent, sapling, orchard) VALUES (?1,?2,?3,?4) " ,
params! [ id_account , has_transparent , true , has_orchard ] ,
) ? ;
2022-10-28 23:40:05 -07:00
}
Ok ( ( ) )
}