2022-11-06 04:50:51 -08:00
use crate ::chain ::Nf ;
2021-11-11 17:39:50 -08:00
use crate ::contact ::Contact ;
2022-11-12 17:39:12 -08:00
use crate ::note_selection ::{ Source , UTXO } ;
2022-11-06 04:50:51 -08:00
use crate ::orchard ::{ derive_orchard_keys , OrchardKeyBytes , OrchardViewKey } ;
2021-08-16 06:07:04 -07:00
use crate ::prices ::Quote ;
2022-11-06 04:50:51 -08:00
use crate ::sapling ::SaplingViewKey ;
use crate ::sync ::tree ::{ CTree , TreeCheckpoint } ;
2022-06-07 09:58:24 -07:00
use crate ::taddr ::{ derive_tkeys , TBalance } ;
2022-11-05 18:49:17 -07:00
use crate ::transaction ::{ GetTransactionDetailRequest , TransactionDetails } ;
2022-11-06 04:50:51 -08:00
use crate ::unified ::UnifiedAddressType ;
2022-11-18 03:02:53 -08:00
use crate ::{ sync , BlockId , CompactTxStreamerClient , Hash } ;
2022-11-06 04:50:51 -08:00
use orchard ::keys ::FullViewingKey ;
2022-09-02 01:44:31 -07:00
use rusqlite ::Error ::QueryReturnedNoRows ;
2022-11-22 01:53:16 -08:00
use rusqlite ::{ params , Connection , OpenFlags , OptionalExtension , Transaction } ;
2022-11-22 19:18:54 -08:00
use serde ::Serialize ;
2021-06-26 02:52:03 -07:00
use std ::collections ::HashMap ;
2022-10-19 23:32:11 -07:00
use std ::convert ::TryInto ;
2022-12-20 03:57:11 -08:00
use std ::path ::Path ;
use anyhow ::anyhow ;
2022-11-18 03:02:53 -08:00
use tonic ::transport ::Channel ;
use tonic ::Request ;
2021-07-30 01:11:44 -07:00
use zcash_client_backend ::encoding ::decode_extended_full_viewing_key ;
2022-11-22 19:18:54 -08:00
use zcash_params ::coin ::{ get_coin_chain , CoinType } ;
2022-03-07 06:47:06 -08:00
use zcash_primitives ::consensus ::{ Network , NetworkUpgrade , Parameters } ;
2021-06-26 02:52:03 -07:00
use zcash_primitives ::merkle_tree ::IncrementalWitness ;
2022-11-06 04:50:51 -08:00
use zcash_primitives ::sapling ::{ Diversifier , Node , Note , SaplingIvk } ;
2021-07-16 01:42:29 -07:00
use zcash_primitives ::zip32 ::{ DiversifierIndex , ExtendedFullViewingKey } ;
2021-07-09 22:44:13 -07:00
2022-11-22 01:53:16 -08:00
mod backup ;
2021-07-09 22:44:13 -07:00
mod migration ;
2021-06-26 02:52:03 -07:00
2022-11-22 01:53:16 -08:00
pub use backup ::FullEncryptedBackup ;
2021-06-26 02:52:03 -07:00
#[ allow(dead_code) ]
pub const DEFAULT_DB_PATH : & str = " zec.db " ;
2021-06-21 17:33:13 -07:00
2022-10-19 23:32:11 -07:00
#[ derive(Clone) ]
pub struct DbAdapterBuilder {
pub coin_type : CoinType ,
pub db_path : String ,
}
2021-06-21 17:33:13 -07:00
pub struct DbAdapter {
2022-03-07 06:47:06 -08:00
pub coin_type : CoinType ,
2021-06-28 21:49:00 -07:00
pub connection : Connection ,
2022-08-21 09:40:14 -07:00
pub db_path : String ,
2021-06-21 17:33:13 -07:00
}
2022-10-30 03:03:38 -07:00
#[ derive(Debug) ]
2021-06-21 17:33:13 -07:00
pub struct ReceivedNote {
2021-06-29 00:04:12 -07:00
pub account : u32 ,
2021-06-21 17:33:13 -07:00
pub height : u32 ,
pub output_index : u32 ,
pub diversifier : Vec < u8 > ,
pub value : u64 ,
pub rcm : Vec < u8 > ,
pub nf : Vec < u8 > ,
2022-10-19 23:32:11 -07:00
pub rho : Option < Vec < u8 > > ,
2021-06-24 05:08:20 -07:00
pub spent : Option < u32 > ,
}
2022-10-27 03:10:51 -07:00
#[ derive(Clone) ]
2022-10-19 23:32:11 -07:00
pub struct ReceivedNoteShort {
pub id : u32 ,
pub account : u32 ,
pub nf : Nf ,
pub value : u64 ,
}
2021-11-11 17:39:50 -08:00
#[ derive(Clone) ]
2021-06-24 05:08:20 -07:00
pub struct SpendableNote {
2021-06-28 21:49:00 -07:00
pub id : u32 ,
2021-06-24 05:08:20 -07:00
pub note : Note ,
pub diversifier : Diversifier ,
pub witness : IncrementalWitness < Node > ,
2021-06-21 17:33:13 -07:00
}
2021-07-27 22:07:20 -07:00
pub struct AccountViewKey {
pub fvk : ExtendedFullViewingKey ,
pub ivk : SaplingIvk ,
pub viewonly : bool ,
}
2022-09-02 01:44:31 -07:00
pub fn wrap_query_no_rows ( name : & 'static str ) -> impl Fn ( rusqlite ::Error ) -> anyhow ::Error {
move | err : rusqlite ::Error | match err {
QueryReturnedNoRows = > anyhow ::anyhow! ( " Query {} returned no rows " , name ) ,
other = > anyhow ::anyhow! ( other . to_string ( ) ) ,
}
}
2022-10-19 23:32:11 -07:00
impl DbAdapterBuilder {
pub fn build ( & self ) -> anyhow ::Result < DbAdapter > {
2022-11-06 04:50:51 -08:00
DbAdapter ::new ( self . coin_type , & self . db_path )
2022-10-19 23:32:11 -07:00
}
}
2021-06-21 17:33:13 -07:00
impl DbAdapter {
2022-04-19 09:47:08 -07:00
pub fn new ( coin_type : CoinType , db_path : & str ) -> anyhow ::Result < DbAdapter > {
2021-06-21 17:33:13 -07:00
let connection = Connection ::open ( db_path ) ? ;
2022-06-07 09:58:24 -07:00
Ok ( DbAdapter {
coin_type ,
connection ,
2022-08-21 09:40:14 -07:00
db_path : db_path . to_owned ( ) ,
2022-06-07 09:58:24 -07:00
} )
2021-06-21 17:33:13 -07:00
}
2022-11-22 01:53:16 -08:00
pub fn create_db ( db_path : & str ) -> anyhow ::Result < ( ) > {
2022-12-20 03:57:11 -08:00
let path = Path ::new ( db_path ) ;
let directory = path . parent ( ) . ok_or ( anyhow! ( " Invalid path " ) ) ? ;
std ::fs ::create_dir_all ( directory ) ? ;
2022-12-19 13:20:12 -08:00
let connection = Connection ::open_with_flags ( db_path , OpenFlags ::SQLITE_OPEN_READ_WRITE | OpenFlags ::SQLITE_OPEN_CREATE ) ? ;
2022-11-22 01:53:16 -08:00
connection . query_row ( " PRAGMA journal_mode = WAL " , [ ] , | _ | Ok ( ( ) ) ) ? ;
connection . execute ( " PRAGMA synchronous = NORMAL " , [ ] ) ? ;
Ok ( ( ) )
}
2022-11-15 02:21:47 -08:00
pub fn migrate_db ( network : & Network , db_path : & str , has_ua : bool ) -> anyhow ::Result < ( ) > {
let connection = Connection ::open ( db_path ) ? ;
migration ::init_db ( & connection , network , has_ua ) ? ;
Ok ( ( ) )
}
2022-11-18 03:02:53 -08:00
pub async fn migrate_data (
& self ,
client : & mut CompactTxStreamerClient < Channel > ,
) -> anyhow ::Result < ( ) > {
let mut stmt = self . connection . prepare ( " select s.height from sapling_tree s LEFT JOIN orchard_tree o ON s.height = o.height WHERE o.height IS NULL " ) ? ;
let rows = stmt . query_map ( [ ] , | row | {
let height : u32 = row . get ( 0 ) ? ;
Ok ( height )
} ) ? ;
let mut trees = HashMap ::new ( ) ;
for r in rows {
trees . insert ( r ? , vec! [ ] ) ;
}
for ( height , tree ) in trees . iter_mut ( ) {
let tree_state = client
. get_tree_state ( Request ::new ( BlockId {
height : * height as u64 ,
hash : vec ! [ ] ,
} ) )
. await ?
. into_inner ( ) ;
let orchard_tree = hex ::decode ( & tree_state . orchard_tree ) . unwrap ( ) ;
tree . extend ( orchard_tree ) ;
}
for ( height , tree ) in trees . iter ( ) {
self . connection . execute (
" INSERT INTO orchard_tree(height, tree) VALUES (?1, ?2) ON CONFLICT DO NOTHING " ,
params! [ height , tree ] ,
) ? ;
}
Ok ( ( ) )
}
2022-08-21 09:40:14 -07:00
pub fn disable_wal ( db_path : & str ) -> anyhow ::Result < ( ) > {
let connection = Connection ::open ( db_path ) ? ;
connection . query_row ( " PRAGMA journal_mode = OFF " , [ ] , | _ | Ok ( ( ) ) ) ? ;
Ok ( ( ) )
}
2021-09-02 19:56:10 -07:00
pub fn begin_transaction ( & mut self ) -> anyhow ::Result < Transaction > {
let tx = self . connection . transaction ( ) ? ;
Ok ( tx )
}
2022-11-05 18:49:17 -07:00
2022-09-02 01:44:31 -07:00
pub fn init_db ( & mut self ) -> anyhow ::Result < ( ) > {
self . delete_incomplete_scan ( ) ? ;
2021-06-24 05:08:20 -07:00
Ok ( ( ) )
}
2021-06-21 17:33:13 -07:00
2021-11-26 19:34:26 -08:00
pub fn reset_db ( & self ) -> anyhow ::Result < ( ) > {
migration ::reset_db ( & self . connection ) ? ;
Ok ( ( ) )
}
2022-11-15 02:21:47 -08:00
pub fn get_account_id ( & self , ivk : & str ) -> anyhow ::Result < Option < u32 > > {
let r = self
. connection
. query_row (
" SELECT id_account FROM accounts WHERE ivk = ?1 " ,
params! [ ivk ] ,
| r | {
let id : u32 = r . get ( 0 ) ? ;
Ok ( id )
} ,
)
. optional ( ) ? ;
Ok ( r )
}
2021-07-16 01:42:29 -07:00
pub fn store_account (
& self ,
name : & str ,
seed : Option < & str > ,
2022-03-07 06:47:06 -08:00
index : u32 ,
2021-07-16 01:42:29 -07:00
sk : Option < & str > ,
ivk : & str ,
address : & str ,
2022-11-15 02:21:47 -08:00
) -> anyhow ::Result < u32 > {
2021-06-26 02:52:03 -07:00
self . connection . execute (
2022-11-05 19:55:53 -07:00
" INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6) " ,
2022-03-07 06:47:06 -08:00
params! [ name , seed , index , sk , ivk , address ] ,
2021-06-26 02:52:03 -07:00
) ? ;
2022-09-02 01:44:31 -07:00
let id_account : u32 = self
. connection
. query_row (
" SELECT id_account FROM accounts WHERE ivk = ?1 " ,
params! [ ivk ] ,
| row | row . get ( 0 ) ,
)
2022-09-02 08:11:50 -07:00
. map_err ( wrap_query_no_rows ( " store_account/id_account " ) ) ? ;
2022-11-15 02:21:47 -08:00
Ok ( id_account )
2021-06-21 17:33:13 -07:00
}
2022-06-08 05:48:16 -07:00
pub fn next_account_id ( & self , seed : & str ) -> anyhow ::Result < u32 > {
2022-06-07 09:58:24 -07:00
let index = self . connection . query_row (
" SELECT MAX(aindex) FROM accounts WHERE seed = ?1 " ,
[ seed ] ,
| row | {
2022-03-07 06:47:06 -08:00
let aindex : Option < i32 > = row . get ( 0 ) ? ;
Ok ( aindex . unwrap_or ( - 1 ) )
2022-06-07 09:58:24 -07:00
} ,
) ? + 1 ;
2022-06-08 05:48:16 -07:00
Ok ( index as u32 )
2022-03-07 06:47:06 -08:00
}
2022-07-16 20:23:56 -07:00
pub fn store_transparent_key (
& self ,
id_account : u32 ,
sk : & str ,
addr : & str ,
) -> anyhow ::Result < ( ) > {
self . connection . execute (
" UPDATE taddrs SET sk = ?1, address = ?2 WHERE account = ?3 " ,
params! [ sk , addr , id_account ] ,
) ? ;
Ok ( ( ) )
}
2022-11-17 01:13:51 -08:00
pub fn convert_to_watchonly ( & self , id_account : u32 ) -> anyhow ::Result < ( ) > {
self . connection . execute (
" UPDATE accounts SET seed = NULL, sk = NULL WHERE id_account = ?1 " ,
params! [ id_account ] ,
) ? ;
self . connection . execute (
" UPDATE orchard_addrs SET sk = NULL WHERE account = ?1 " ,
params! [ id_account ] ,
) ? ;
Ok ( ( ) )
}
2022-10-30 03:03:38 -07:00
pub fn get_sapling_fvks ( & self ) -> anyhow ::Result < Vec < SaplingViewKey > > {
2021-07-16 01:42:29 -07:00
let mut statement = self
. connection
2022-10-30 03:03:38 -07:00
. prepare ( " SELECT id_account, ivk FROM accounts " ) ? ;
2022-05-19 09:19:04 -07:00
let rows = statement . query_map ( [ ] , | row | {
2021-06-29 00:04:12 -07:00
let account : u32 = row . get ( 0 ) ? ;
let ivk : String = row . get ( 1 ) ? ;
2021-07-30 01:11:44 -07:00
let fvk = decode_extended_full_viewing_key (
2022-03-07 06:47:06 -08:00
self . network ( ) . hrp_sapling_extended_full_viewing_key ( ) ,
2021-07-30 01:11:44 -07:00
& ivk ,
)
. unwrap ( ) ;
2021-07-27 22:07:20 -07:00
let ivk = fvk . fvk . vk . ivk ( ) ;
2022-11-06 04:50:51 -08:00
Ok ( SaplingViewKey { account , fvk , ivk } )
2022-10-30 03:03:38 -07:00
} ) ? ;
let mut fvks = vec! [ ] ;
for r in rows {
let row = r ? ;
fvks . push ( row ) ;
}
Ok ( fvks )
}
pub fn get_orchard_fvks ( & self ) -> anyhow ::Result < Vec < OrchardViewKey > > {
2022-11-06 04:50:51 -08:00
let mut statement = self
. connection
. prepare ( " SELECT account, fvk FROM orchard_addrs " ) ? ;
2022-10-30 03:03:38 -07:00
let rows = statement . query_map ( [ ] , | row | {
let account : u32 = row . get ( 0 ) ? ;
let fvk : Vec < u8 > = row . get ( 1 ) ? ;
let fvk : [ u8 ; 96 ] = fvk . try_into ( ) . unwrap ( ) ;
let fvk = FullViewingKey ::from_bytes ( & fvk ) . unwrap ( ) ;
2022-11-06 04:50:51 -08:00
let vk = OrchardViewKey { account , fvk } ;
2022-10-30 03:03:38 -07:00
Ok ( vk )
2021-06-29 00:04:12 -07:00
} ) ? ;
2022-10-30 03:03:38 -07:00
let mut fvks = vec! [ ] ;
2021-06-29 00:04:12 -07:00
for r in rows {
let row = r ? ;
2022-10-30 03:03:38 -07:00
fvks . push ( row ) ;
2021-06-29 00:04:12 -07:00
}
Ok ( fvks )
2021-06-26 02:52:03 -07:00
}
2022-11-15 02:21:47 -08:00
pub fn drop_last_checkpoint ( & mut self ) -> anyhow ::Result < u32 > {
let height = self . get_last_sync_height ( ) ? ;
if let Some ( height ) = height {
let height = self . trim_to_height ( height - 1 ) ? ;
return Ok ( height ) ;
}
Ok ( self . sapling_activation_height ( ) )
}
2022-09-06 19:38:16 -07:00
pub fn trim_to_height ( & mut self , height : u32 ) -> anyhow ::Result < u32 > {
2022-09-02 01:44:31 -07:00
// snap height to an existing checkpoint
2022-09-06 19:38:16 -07:00
let height = self . connection . query_row (
" SELECT MAX(height) from blocks WHERE height <= ?1 " ,
params! [ height ] ,
| row | {
let height : Option < u32 > = row . get ( 0 ) ? ;
Ok ( height )
} ,
) ? ;
let height = height . unwrap_or ( 0 ) ;
2022-09-05 08:05:55 -07:00
log ::info! ( " Rewind to height: {} " , height ) ;
2022-09-02 01:44:31 -07:00
2021-06-21 17:33:13 -07:00
let tx = self . connection . transaction ( ) ? ;
2022-09-02 01:44:31 -07:00
tx . execute ( " DELETE FROM blocks WHERE height > ?1 " , params! [ height ] ) ? ;
2022-11-06 04:50:51 -08:00
tx . execute (
" DELETE FROM sapling_tree WHERE height > ?1 " ,
params! [ height ] ,
) ? ;
tx . execute (
" DELETE FROM orchard_tree WHERE height > ?1 " ,
params! [ height ] ,
) ? ;
2021-06-26 02:52:03 -07:00
tx . execute (
2022-09-02 01:44:31 -07:00
" DELETE FROM sapling_witnesses WHERE height > ?1 " ,
2021-06-26 02:52:03 -07:00
params! [ height ] ,
) ? ;
2022-10-19 23:32:11 -07:00
tx . execute (
" DELETE FROM orchard_witnesses WHERE height > ?1 " ,
params! [ height ] ,
) ? ;
2021-06-26 02:52:03 -07:00
tx . execute (
2022-09-02 01:44:31 -07:00
" DELETE FROM received_notes WHERE height > ?1 " ,
2021-06-26 02:52:03 -07:00
params! [ height ] ,
) ? ;
2021-12-11 22:19:58 -08:00
tx . execute (
2022-09-02 01:44:31 -07:00
" UPDATE received_notes SET spent = NULL WHERE spent > ?1 " ,
2021-12-11 22:19:58 -08:00
params! [ height ] ,
) ? ;
2021-06-26 02:52:03 -07:00
tx . execute (
2022-09-02 01:44:31 -07:00
" DELETE FROM transactions WHERE height > ?1 " ,
2021-06-26 02:52:03 -07:00
params! [ height ] ,
) ? ;
2022-09-02 01:44:31 -07:00
tx . execute ( " DELETE FROM messages WHERE height > ?1 " , params! [ height ] ) ? ;
2021-06-21 17:33:13 -07:00
tx . commit ( ) ? ;
2022-09-02 08:11:50 -07:00
Ok ( height )
2021-06-21 17:33:13 -07:00
}
2021-06-26 02:52:03 -07:00
pub fn store_block (
2022-07-12 00:28:58 -07:00
connection : & Connection ,
2021-06-26 02:52:03 -07:00
height : u32 ,
hash : & [ u8 ] ,
timestamp : u32 ,
2022-10-19 23:32:11 -07:00
sapling_tree : & CTree ,
2022-11-15 02:21:47 -08:00
orchard_tree : & CTree ,
2021-06-26 02:52:03 -07:00
) -> anyhow ::Result < ( ) > {
2022-11-15 02:21:47 -08:00
log ::info! ( " +store_block " ) ;
2022-10-19 23:32:11 -07:00
let mut sapling_bb : Vec < u8 > = vec! [ ] ;
sapling_tree . write ( & mut sapling_bb ) ? ;
2022-07-12 00:28:58 -07:00
connection . execute (
2022-11-15 02:21:47 -08:00
" INSERT INTO blocks(height, hash, timestamp)
2022-11-17 01:13:51 -08:00
VALUES ( ? 1 , ? 2 , ? 3 ) ON CONFLICT DO NOTHING " ,
2022-11-15 02:21:47 -08:00
params! [ height , hash , timestamp ] ,
) ? ;
connection . execute (
2022-11-17 01:13:51 -08:00
" INSERT INTO sapling_tree(height, tree) VALUES (?1, ?2) ON CONFLICT DO NOTHING " ,
2022-11-15 02:21:47 -08:00
params! [ height , & sapling_bb ] ,
) ? ;
let mut orchard_bb : Vec < u8 > = vec! [ ] ;
orchard_tree . write ( & mut orchard_bb ) ? ;
connection . execute (
2022-11-17 01:13:51 -08:00
" INSERT INTO orchard_tree(height, tree) VALUES (?1, ?2) ON CONFLICT DO NOTHING " ,
2022-11-15 02:21:47 -08:00
params! [ height , & orchard_bb ] ,
2021-06-26 02:52:03 -07:00
) ? ;
log ::debug! ( " -block " ) ;
2021-06-21 17:33:13 -07:00
Ok ( ( ) )
}
2021-06-26 02:52:03 -07:00
pub fn store_transaction (
txid : & [ u8 ] ,
2021-06-29 00:04:12 -07:00
account : u32 ,
2021-06-26 02:52:03 -07:00
height : u32 ,
timestamp : u32 ,
tx_index : u32 ,
2021-11-11 17:39:50 -08:00
db_tx : & Transaction ,
2021-06-26 02:52:03 -07:00
) -> anyhow ::Result < u32 > {
log ::debug! ( " +transaction " ) ;
2021-09-02 19:56:10 -07:00
db_tx . execute (
2021-06-29 00:04:12 -07:00
" INSERT INTO transactions(account, txid, height, timestamp, tx_index, value)
2022-11-06 04:50:51 -08:00
VALUES ( ? 1 , ? 2 , ? 3 , ? 4 , ? 5 , 0 ) ON CONFLICT DO NOTHING " , // ignore conflict when same tx has sapling + orchard outputs
2021-06-29 00:04:12 -07:00
params! [ account , txid , height , timestamp , tx_index ] ,
2021-06-26 02:52:03 -07:00
) ? ;
2022-09-02 01:44:31 -07:00
let id_tx : u32 = db_tx
. query_row (
" SELECT id_tx FROM transactions WHERE account = ?1 AND txid = ?2 " ,
params! [ account , txid ] ,
| row | row . get ( 0 ) ,
)
. map_err ( wrap_query_no_rows ( " store_transaction/id_tx " ) ) ? ;
2021-06-26 02:52:03 -07:00
log ::debug! ( " -transaction {} " , id_tx ) ;
2021-06-21 17:33:13 -07:00
Ok ( id_tx )
}
2021-06-26 02:52:03 -07:00
pub fn store_received_note (
note : & ReceivedNote ,
id_tx : u32 ,
position : usize ,
2021-11-11 17:39:50 -08:00
db_tx : & Transaction ,
2021-06-26 02:52:03 -07:00
) -> anyhow ::Result < u32 > {
2022-10-30 03:03:38 -07:00
log ::info! ( " +received_note {} {:?} " , id_tx , note ) ;
2022-11-04 18:58:35 -07:00
let orchard = note . rho . is_some ( ) ;
db_tx . execute ( " INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, rho, nf, orchard, spent)
VALUES ( ? 1 , ? 2 , ? 3 , ? 4 , ? 5 , ? 6 , ? 7 , ? 8 , ? 9 , ? 10 , ? 11 , ? 12 ) " , params![note.account, id_tx, note.height, position as u32, note.output_index,
note . diversifier , note . value as i64 , note . rcm , note . rho , note . nf , orchard , note . spent ] ) ? ;
2022-09-02 01:44:31 -07:00
let id_note : u32 = db_tx
. query_row (
2022-11-06 04:50:51 -08:00
" SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2 AND orchard = ?3 " ,
params! [ id_tx , note . output_index , orchard ] ,
2022-09-02 01:44:31 -07:00
| row | row . get ( 0 ) ,
)
. map_err ( wrap_query_no_rows ( " store_received_note/id_note " ) ) ? ;
2021-06-26 02:52:03 -07:00
log ::debug! ( " -received_note " ) ;
2021-06-21 17:33:13 -07:00
Ok ( id_note )
}
2022-10-19 23:32:11 -07:00
pub fn store_witness (
witness : & sync ::Witness ,
height : u32 ,
id_note : u32 ,
connection : & Connection ,
2022-11-06 04:50:51 -08:00
shielded_pool : & str ,
2022-10-19 23:32:11 -07:00
) -> anyhow ::Result < ( ) > {
log ::debug! ( " +store_witness " ) ;
let mut bb : Vec < u8 > = vec! [ ] ;
witness . write ( & mut bb ) ? ;
connection . execute (
2022-11-06 04:50:51 -08:00
& format! (
" INSERT INTO {}_witnesses(note, height, witness) VALUES (?1, ?2, ?3) " ,
shielded_pool
) ,
2022-10-19 23:32:11 -07:00
params! [ id_note , height , bb ] ,
) ? ;
log ::debug! ( " -store_witness " ) ;
Ok ( ( ) )
}
2022-11-06 04:50:51 -08:00
pub fn store_block_timestamp (
& self ,
height : u32 ,
hash : & [ u8 ] ,
timestamp : u32 ,
) -> anyhow ::Result < ( ) > {
self . connection . execute (
" INSERT INTO blocks(height, hash, timestamp) VALUES (?1,?2,?3) " ,
params! [ height , hash , timestamp ] ,
) ? ;
2022-10-27 03:10:51 -07:00
Ok ( ( ) )
}
2022-11-06 04:50:51 -08:00
pub fn store_tree (
height : u32 ,
tree : & CTree ,
db_tx : & Connection ,
shielded_pool : & str ,
) -> anyhow ::Result < ( ) > {
2022-10-28 06:02:34 -07:00
let mut bb : Vec < u8 > = vec! [ ] ;
tree . write ( & mut bb ) ? ;
2022-11-06 04:50:51 -08:00
db_tx . execute (
& format! (
" INSERT INTO {}_tree(height, tree) VALUES (?1,?2) " ,
shielded_pool
) ,
params! [ height , & bb ] ,
) ? ;
2022-10-27 03:10:51 -07:00
Ok ( ( ) )
}
2022-11-05 19:55:53 -07:00
pub fn update_transaction_with_memo ( & self , details : & TransactionDetails ) -> anyhow ::Result < ( ) > {
2022-11-06 04:50:51 -08:00
self . connection . execute (
" UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3 " ,
params! [ details . address , details . memo , details . id_tx ] ,
) ? ;
2021-07-09 22:44:13 -07:00
Ok ( ( ) )
}
2021-09-02 19:56:10 -07:00
pub fn add_value ( id_tx : u32 , value : i64 , db_tx : & Transaction ) -> anyhow ::Result < ( ) > {
db_tx . execute (
2021-06-26 02:52:03 -07:00
" UPDATE transactions SET value = value + ?2 WHERE id_tx = ?1 " ,
params! [ id_tx , value ] ,
) ? ;
2021-06-21 17:33:13 -07:00
Ok ( ( ) )
}
2022-11-05 19:55:53 -07:00
#[ allow(dead_code) ]
2021-06-29 00:04:12 -07:00
pub fn get_balance ( & self , account : u32 ) -> anyhow ::Result < u64 > {
2021-06-26 02:52:03 -07:00
let balance : Option < i64 > = self . connection . query_row (
2021-06-29 00:04:12 -07:00
" SELECT SUM(value) FROM received_notes WHERE (spent IS NULL OR spent = 0) AND account = ?1 " ,
params! [ account ] ,
2021-06-26 02:52:03 -07:00
| row | row . get ( 0 ) ,
) ? ;
Ok ( balance . unwrap_or ( 0 ) as u64 )
}
2021-06-24 05:08:20 -07:00
pub fn get_last_sync_height ( & self ) -> anyhow ::Result < Option < u32 > > {
2022-11-15 02:21:47 -08:00
let height : Option < u32 > =
self . connection
. query_row ( " SELECT MAX(height) FROM blocks " , [ ] , | row | row . get ( 0 ) ) ? ;
Ok ( height )
}
pub fn get_checkpoint_height ( & self , max_height : u32 ) -> anyhow ::Result < Option < u32 > > {
let height : Option < u32 > = self . connection . query_row (
" SELECT MAX(height) FROM blocks WHERE height <= ?1 " ,
[ max_height ] ,
| row | row . get ( 0 ) ,
) ? ;
2021-06-24 05:08:20 -07:00
Ok ( height )
}
pub fn get_db_height ( & self ) -> anyhow ::Result < u32 > {
2022-11-15 02:21:47 -08:00
let height : u32 = self
. get_last_sync_height ( ) ?
. unwrap_or_else ( | | self . sapling_activation_height ( ) ) ;
2021-06-21 17:33:13 -07:00
Ok ( height )
}
2021-06-26 02:52:03 -07:00
pub fn get_db_hash ( & self , height : u32 ) -> anyhow ::Result < Option < [ u8 ; 32 ] > > {
2021-07-16 01:42:29 -07:00
let hash : Option < Vec < u8 > > = self
. connection
. query_row (
" SELECT hash FROM blocks WHERE height = ?1 " ,
params! [ height ] ,
| row | row . get ( 0 ) ,
)
. optional ( ) ? ;
2021-06-26 02:52:03 -07:00
Ok ( hash . map ( | h | {
let mut hash = [ 0 u8 ; 32 ] ;
hash . copy_from_slice ( & h ) ;
hash
} ) )
}
2022-11-06 04:50:51 -08:00
pub fn get_tree_by_name (
& self ,
height : u32 ,
shielded_pool : & str ,
) -> anyhow ::Result < TreeCheckpoint > {
let tree = self
. connection
. query_row (
& format! ( " SELECT tree FROM {} _tree WHERE height = ?1 " , shielded_pool ) ,
[ height ] ,
| row | {
let tree : Vec < u8 > = row . get ( 0 ) ? ;
Ok ( tree )
} ,
)
. optional ( ) ? ;
2022-10-28 06:02:34 -07:00
2022-11-04 18:58:35 -07:00
match tree {
Some ( tree ) = > {
2022-10-19 23:32:11 -07:00
let tree = sync ::CTree ::read ( & * tree ) ? ;
2021-06-21 17:33:13 -07:00
let mut statement = self . connection . prepare (
2022-10-19 23:32:11 -07:00
& format! ( " SELECT id_note, witness FROM {} _witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note AND (n.spent IS NULL OR n.spent = 0) " , shielded_pool ) ) ? ;
2021-06-21 17:33:13 -07:00
let ws = statement . query_map ( params! [ height ] , | row | {
let id_note : u32 = row . get ( 0 ) ? ;
2021-06-28 21:49:00 -07:00
let witness : Vec < u8 > = row . get ( 1 ) ? ;
2022-10-19 23:32:11 -07:00
Ok ( sync ::Witness ::read ( id_note , & * witness ) . unwrap ( ) )
2021-06-21 17:33:13 -07:00
} ) ? ;
2022-10-19 23:32:11 -07:00
let mut witnesses = vec! [ ] ;
2021-06-21 17:33:13 -07:00
for w in ws {
witnesses . push ( w ? ) ;
}
2022-11-04 18:58:35 -07:00
Ok ( TreeCheckpoint { tree , witnesses } )
2021-06-26 02:52:03 -07:00
}
2022-11-04 18:58:35 -07:00
None = > Ok ( TreeCheckpoint {
2022-10-22 19:34:50 -07:00
tree : CTree ::new ( ) ,
witnesses : vec ! [ ] ,
2022-11-06 04:50:51 -08:00
} ) ,
2022-11-04 18:58:35 -07:00
}
2021-06-21 17:33:13 -07:00
}
2021-06-24 05:08:20 -07:00
2021-07-16 01:42:29 -07:00
pub fn get_nullifier_amounts (
& self ,
account : u32 ,
unspent_only : bool ,
2022-11-17 17:02:03 -08:00
) -> anyhow ::Result < HashMap < Hash , u64 > > {
2021-07-09 22:44:13 -07:00
let mut sql = " SELECT value, nf FROM received_notes WHERE account = ?1 " . to_string ( ) ;
if unspent_only {
2022-09-09 19:15:43 -07:00
sql + = " AND (spent IS NULL OR spent = 0) " ;
2021-07-09 22:44:13 -07:00
}
2021-07-16 01:42:29 -07:00
let mut statement = self . connection . prepare ( & sql ) ? ;
2021-06-29 00:04:12 -07:00
let nfs_res = statement . query_map ( params! [ account ] , | row | {
2021-06-26 02:52:03 -07:00
let amount : i64 = row . get ( 0 ) ? ;
let nf : Vec < u8 > = row . get ( 1 ) ? ;
2022-11-17 17:02:03 -08:00
Ok ( ( amount , nf . try_into ( ) . unwrap ( ) ) )
2021-06-26 02:52:03 -07:00
} ) ? ;
2022-11-17 17:02:03 -08:00
let mut nfs : HashMap < Hash , u64 > = HashMap ::new ( ) ;
2021-06-26 02:52:03 -07:00
for n in nfs_res {
let n = n ? ;
nfs . insert ( n . 1 , n . 0 as u64 ) ;
}
Ok ( nfs )
}
2022-11-06 04:50:51 -08:00
pub fn get_unspent_nullifiers ( & self ) -> anyhow ::Result < Vec < ReceivedNoteShort > > {
2022-10-27 03:10:51 -07:00
let sql = " SELECT id_note, account, nf, value FROM received_notes WHERE spent IS NULL OR spent = 0 " ;
2022-10-19 23:32:11 -07:00
let mut statement = self . connection . prepare ( sql ) ? ;
2022-10-27 03:10:51 -07:00
let nfs_res = statement . query_map ( params! [ ] , | row | {
2022-10-19 23:32:11 -07:00
let id : u32 = row . get ( 0 ) ? ;
2022-10-27 03:10:51 -07:00
let account : u32 = row . get ( 1 ) ? ;
let nf : Vec < u8 > = row . get ( 2 ) ? ;
let value : i64 = row . get ( 3 ) ? ;
2022-10-19 23:32:11 -07:00
let nf : [ u8 ; 32 ] = nf . try_into ( ) . unwrap ( ) ;
let nf = Nf ( nf ) ;
Ok ( ReceivedNoteShort {
id ,
account ,
nf ,
value : value as u64 ,
} )
} ) ? ;
let mut nfs = vec! [ ] ;
for n in nfs_res {
let n = n ? ;
nfs . push ( n ) ;
}
Ok ( nfs )
}
2022-11-06 04:50:51 -08:00
pub fn get_unspent_received_notes (
& self ,
account : u32 ,
2022-11-15 02:21:47 -08:00
checkpoint_height : u32 ,
2022-11-29 07:24:01 -08:00
orchard : bool ,
2022-11-06 04:50:51 -08:00
) -> anyhow ::Result < Vec < UTXO > > {
2022-11-01 04:03:57 -07:00
let mut notes = vec! [ ] ;
2022-11-15 02:21:47 -08:00
2022-12-05 23:55:24 -08:00
if ! orchard {
let mut statement = self . connection . prepare (
" SELECT id_note, diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NULL
AND ( r . excluded IS NULL OR NOT r . excluded ) AND w . height = ? 1
AND r . id_note = w . note " )?;
let rows = statement . query_map ( params! [ checkpoint_height , account ] , | row | {
let id_note : u32 = row . get ( 0 ) ? ;
let diversifier : Vec < u8 > = row . get ( 1 ) ? ;
let amount : i64 = row . get ( 2 ) ? ;
let rcm : Vec < u8 > = row . get ( 3 ) ? ;
let witness : Vec < u8 > = row . get ( 4 ) ? ;
let source = Source ::Sapling {
id_note ,
diversifier : diversifier . try_into ( ) . unwrap ( ) ,
rseed : rcm . try_into ( ) . unwrap ( ) ,
witness ,
} ;
Ok ( UTXO {
id : id_note ,
source ,
amount : amount as u64 ,
} )
} ) ? ;
for r in rows {
let note = r ? ;
notes . push ( note ) ;
}
2022-11-01 04:03:57 -07:00
}
2022-12-05 23:55:24 -08:00
else {
let mut statement = self . connection . prepare (
" SELECT id_note, diversifier, value, rcm, rho, witness FROM received_notes r, orchard_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NOT NULL
AND ( r . excluded IS NULL OR NOT r . excluded ) AND w . height = ? 1
AND r . id_note = w . note " )?;
let rows = statement . query_map ( params! [ checkpoint_height , account ] , | row | {
let id_note : u32 = row . get ( 0 ) ? ;
let diversifier : Vec < u8 > = row . get ( 1 ) ? ;
let amount : i64 = row . get ( 2 ) ? ;
let rcm : Vec < u8 > = row . get ( 3 ) ? ;
let rho : Vec < u8 > = row . get ( 4 ) . unwrap ( ) ;
let witness : Vec < u8 > = row . get ( 5 ) ? ;
let source = Source ::Orchard {
id_note ,
diversifier : diversifier . try_into ( ) . unwrap ( ) ,
rseed : rcm . try_into ( ) . unwrap ( ) ,
rho : rho . try_into ( ) . unwrap ( ) ,
witness ,
} ;
Ok ( UTXO {
id : id_note ,
source ,
amount : amount as u64 ,
} )
} ) ? ;
for r in rows {
let note = r ? ;
notes . push ( note ) ;
}
} ;
2022-11-01 04:03:57 -07:00
Ok ( notes )
}
2022-06-08 05:48:16 -07:00
pub fn tx_mark_spend ( & mut self , selected_notes : & [ u32 ] ) -> anyhow ::Result < ( ) > {
let db_tx = self . begin_transaction ( ) ? ;
for id_note in selected_notes . iter ( ) {
DbAdapter ::mark_spent ( * id_note , 0 , & db_tx ) ? ;
}
db_tx . commit ( ) ? ;
Ok ( ( ) )
}
2021-09-02 19:56:10 -07:00
pub fn mark_spent ( id : u32 , height : u32 , tx : & Transaction ) -> anyhow ::Result < ( ) > {
2021-06-26 02:52:03 -07:00
log ::debug! ( " +mark_spent " ) ;
2021-09-02 19:56:10 -07:00
tx . execute (
2021-06-26 02:52:03 -07:00
" UPDATE received_notes SET spent = ?1 WHERE id_note = ?2 " ,
params! [ height , id ] ,
) ? ;
log ::debug! ( " -mark_spent " ) ;
2021-06-24 05:08:20 -07:00
Ok ( ( ) )
}
2022-08-26 05:54:52 -07:00
pub fn purge_old_witnesses ( & mut self , height : u32 ) -> anyhow ::Result < ( ) > {
2021-07-16 01:42:29 -07:00
log ::debug! ( " +purge_old_witnesses " ) ;
2021-10-20 18:42:10 -07:00
let min_height : Option < u32 > = self . connection . query_row (
2022-09-02 01:44:31 -07:00
" SELECT MAX(height) FROM blocks WHERE height <= ?1 " ,
2021-11-11 17:39:50 -08:00
params! [ height ] ,
| row | row . get ( 0 ) ,
) ? ;
2021-10-20 18:42:10 -07:00
// Leave at least one sapling witness
if let Some ( min_height ) = min_height {
log ::debug! ( " Purging witnesses older than {} " , min_height ) ;
2022-08-26 05:54:52 -07:00
let transaction = self . connection . transaction ( ) ? ;
transaction . execute (
2021-10-20 18:42:10 -07:00
" DELETE FROM sapling_witnesses WHERE height < ?1 " ,
params! [ min_height ] ,
) ? ;
2022-10-19 23:32:11 -07:00
transaction . execute (
" DELETE FROM orchard_witnesses WHERE height < ?1 " ,
params! [ min_height ] ,
) ? ;
2022-08-26 05:54:52 -07:00
transaction . execute ( " DELETE FROM blocks WHERE height < ?1 " , params! [ min_height ] ) ? ;
2022-11-15 23:44:51 -08:00
transaction . execute (
" DELETE FROM sapling_tree WHERE height < ?1 " ,
params! [ min_height ] ,
) ? ;
transaction . execute (
" DELETE FROM orchard_tree WHERE height < ?1 " ,
params! [ min_height ] ,
) ? ;
2022-08-26 05:54:52 -07:00
transaction . commit ( ) ? ;
2021-10-20 18:42:10 -07:00
}
2021-07-16 01:42:29 -07:00
log ::debug! ( " -purge_old_witnesses " ) ;
Ok ( ( ) )
}
2021-09-08 07:10:22 -07:00
pub fn store_contact ( & self , contact : & Contact , dirty : bool ) -> anyhow ::Result < ( ) > {
if contact . id = = 0 {
2021-07-30 01:11:44 -07:00
self . connection . execute (
2021-09-08 07:10:22 -07:00
" INSERT INTO contacts(name, address, dirty)
VALUES ( ? 1 , ? 2 , ? 3 ) " ,
params! [ & contact . name , & contact . address , dirty ] ,
2021-07-30 01:11:44 -07:00
) ? ;
2021-11-11 17:39:50 -08:00
} else {
2021-07-30 01:11:44 -07:00
self . connection . execute (
2021-09-08 07:10:22 -07:00
" INSERT INTO contacts(id, name, address, dirty)
VALUES ( ? 1 , ? 2 , ? 3 , ? 4 ) ON CONFLICT ( id ) DO UPDATE SET
name = excluded . name , address = excluded . address , dirty = excluded . dirty " ,
params! [ contact . id , & contact . name , & contact . address , dirty ] ,
2021-07-30 01:11:44 -07:00
) ? ;
2021-07-19 01:17:05 -07:00
}
2021-07-18 08:57:43 -07:00
Ok ( ( ) )
}
2021-09-08 07:10:22 -07:00
pub fn get_unsaved_contacts ( & self ) -> anyhow ::Result < Vec < Contact > > {
2021-11-11 17:39:50 -08:00
let mut statement = self
. connection
. prepare ( " SELECT id, name, address FROM contacts WHERE dirty = TRUE " ) ? ;
2022-05-19 09:19:04 -07:00
let rows = statement . query_map ( [ ] , | row | {
2021-09-08 07:10:22 -07:00
let id : u32 = row . get ( 0 ) ? ;
let name : String = row . get ( 1 ) ? ;
let address : String = row . get ( 2 ) ? ;
2021-11-11 17:39:50 -08:00
let contact = Contact { id , name , address } ;
2021-09-08 07:10:22 -07:00
Ok ( contact )
} ) ? ;
let mut contacts : Vec < Contact > = vec! [ ] ;
for r in rows {
contacts . push ( r ? ) ;
}
Ok ( contacts )
}
2021-07-07 21:22:34 -07:00
pub fn get_diversifier ( & self , account : u32 ) -> anyhow ::Result < DiversifierIndex > {
2021-07-16 01:42:29 -07:00
let diversifier_index = self
. connection
. query_row (
" SELECT diversifier_index FROM diversifiers WHERE account = ?1 " ,
params! [ account ] ,
| row | {
let d : Vec < u8 > = row . get ( 0 ) ? ;
let mut div = [ 0 u8 ; 11 ] ;
div . copy_from_slice ( & d ) ;
Ok ( div )
} ,
)
. optional ( ) ?
2022-06-08 05:48:16 -07:00
. unwrap_or ( [ 0 u8 ; 11 ] ) ;
2021-07-07 21:22:34 -07:00
Ok ( DiversifierIndex ( diversifier_index ) )
}
2022-10-19 23:32:11 -07:00
pub fn store_diversifier (
& self ,
account : u32 ,
diversifier_index : & DiversifierIndex ,
) -> anyhow ::Result < ( ) > {
let diversifier_bytes = diversifier_index . 0. to_vec ( ) ;
self . connection . execute (
" INSERT INTO diversifiers(account, diversifier_index) VALUES (?1, ?2) ON CONFLICT \
( account ) DO UPDATE SET diversifier_index = excluded . diversifier_index " ,
params! [ account , diversifier_bytes ] ,
) ? ;
Ok ( ( ) )
}
2022-09-04 04:19:49 -07:00
pub fn get_account_info ( & self , account : u32 ) -> anyhow ::Result < AccountData > {
2022-11-22 01:53:16 -08:00
assert_ne! ( account , 0 ) ;
log ::info! ( " get_account_info {} {} " , self . db_path , account ) ;
2022-09-04 04:19:49 -07:00
let account_data = self
. connection
. query_row (
" SELECT name, seed, sk, ivk, address, aindex FROM accounts WHERE id_account = ?1 " ,
params! [ account ] ,
| row | {
let name : String = row . get ( 0 ) ? ;
let seed : Option < String > = row . get ( 1 ) ? ;
let sk : Option < String > = row . get ( 2 ) ? ;
let fvk : String = row . get ( 3 ) ? ;
let address : String = row . get ( 4 ) ? ;
let aindex : u32 = row . get ( 5 ) ? ;
Ok ( AccountData {
name ,
seed ,
sk ,
fvk ,
address ,
aindex ,
} )
} ,
)
. map_err ( wrap_query_no_rows ( " get_account_info " ) ) ? ;
Ok ( account_data )
}
2021-07-09 06:33:05 -07:00
pub fn get_taddr ( & self , account : u32 ) -> anyhow ::Result < Option < String > > {
2021-07-16 01:42:29 -07:00
let address = self
. connection
. query_row (
" SELECT address FROM taddrs WHERE account = ?1 " ,
params! [ account ] ,
| row | {
let address : String = row . get ( 0 ) ? ;
Ok ( address )
} ,
)
. optional ( ) ? ;
2021-07-09 06:33:05 -07:00
Ok ( address )
}
2021-11-11 17:39:50 -08:00
pub fn get_tsk ( & self , account : u32 ) -> anyhow ::Result < Option < String > > {
let sk = self
. connection
. query_row (
" SELECT sk FROM taddrs WHERE account = ?1 " ,
params! [ account ] ,
| row | {
2022-11-04 18:58:35 -07:00
let sk : String = row . get ( 0 ) ? ;
Ok ( sk )
2021-11-11 17:39:50 -08:00
} ,
)
. optional ( ) ? ;
2021-07-09 06:33:05 -07:00
Ok ( sk )
}
pub fn create_taddr ( & self , account : u32 ) -> anyhow ::Result < ( ) > {
2022-09-04 04:19:49 -07:00
let AccountData { seed , aindex , .. } = self . get_account_info ( account ) ? ;
2021-07-09 06:33:05 -07:00
if let Some ( seed ) = seed {
2022-09-04 04:19:49 -07:00
let bip44_path = format! ( " m/44'/ {} '/0'/0/ {} " , self . network ( ) . coin_type ( ) , aindex ) ;
2022-03-07 06:47:06 -08:00
let ( sk , address ) = derive_tkeys ( self . network ( ) , & seed , & bip44_path ) ? ;
2021-07-16 01:42:29 -07:00
self . connection . execute (
2022-11-05 19:55:53 -07:00
" INSERT INTO taddrs(account, sk, address) VALUES (?1, ?2, ?3) " ,
2021-07-16 01:42:29 -07:00
params! [ account , & sk , & address ] ,
) ? ;
2021-07-09 06:33:05 -07:00
}
Ok ( ( ) )
}
2021-08-09 07:13:10 -07:00
2022-10-28 23:40:05 -07:00
pub fn create_orchard ( & self , account : u32 ) -> anyhow ::Result < ( ) > {
let AccountData { seed , aindex , .. } = self . get_account_info ( account ) ? ;
if let Some ( seed ) = seed {
let keys = derive_orchard_keys ( self . network ( ) . coin_type ( ) , & seed , aindex ) ;
self . connection . execute (
2022-11-05 19:55:53 -07:00
" INSERT INTO orchard_addrs(account, sk, fvk) VALUES (?1, ?2, ?3) " ,
2022-10-28 23:40:05 -07:00
params! [ account , & keys . sk , & keys . fvk ] ,
) ? ;
}
Ok ( ( ) )
}
2022-11-17 01:13:51 -08:00
pub fn store_orchard_fvk ( & self , account : u32 , fvk : & [ u8 ; 96 ] ) -> anyhow ::Result < ( ) > {
self . connection . execute (
" INSERT INTO orchard_addrs(account, sk, fvk) VALUES (?1, NULL, ?2) ON CONFLICT DO NOTHING " ,
params! [ account , fvk ] ,
) ? ;
Ok ( ( ) )
}
2022-11-06 04:50:51 -08:00
pub fn find_account_by_fvk ( & self , fvk : & str ) -> anyhow ::Result < Option < u32 > > {
let account = self
. connection
. query_row (
" SELECT id_account FROM accounts WHERE fvk = ?1 " ,
params! [ fvk ] ,
| row | {
let account : u32 = row . get ( 0 ) ? ;
Ok ( account )
} ,
)
. optional ( ) ? ;
Ok ( account )
}
2022-10-30 03:03:38 -07:00
pub fn get_orchard ( & self , account : u32 ) -> anyhow ::Result < Option < OrchardKeyBytes > > {
2022-11-06 04:50:51 -08:00
let key = self
. connection
. query_row (
" SELECT sk, fvk FROM orchard_addrs WHERE account = ?1 " ,
params! [ account ] ,
| row | {
2022-11-17 01:13:51 -08:00
let sk : Option < Vec < u8 > > = row . get ( 0 ) ? ;
2022-11-06 04:50:51 -08:00
let fvk : Vec < u8 > = row . get ( 1 ) ? ;
Ok ( OrchardKeyBytes {
2022-11-17 01:13:51 -08:00
sk : sk . map ( | sk | sk . try_into ( ) . unwrap ( ) ) ,
2022-11-06 04:50:51 -08:00
fvk : fvk . try_into ( ) . unwrap ( ) ,
} )
} ,
)
. optional ( ) ? ;
2022-10-30 03:03:38 -07:00
Ok ( key )
}
2022-11-06 04:50:51 -08:00
pub fn store_ua_settings (
& self ,
account : u32 ,
transparent : bool ,
sapling : bool ,
orchard : bool ,
) -> anyhow ::Result < ( ) > {
2022-10-28 23:40:05 -07:00
self . connection . execute (
2022-11-05 19:55:53 -07:00
" INSERT INTO ua_settings(account, transparent, sapling, orchard) VALUES (?1, ?2, ?3, ?4) " ,
2022-10-28 23:40:05 -07:00
params! [ account , transparent , sapling , orchard ] ,
) ? ;
Ok ( ( ) )
}
2022-10-30 03:03:38 -07:00
pub fn get_ua_settings ( & self , account : u32 ) -> anyhow ::Result < UnifiedAddressType > {
2022-11-06 04:50:51 -08:00
let tpe = self . connection . query_row (
" SELECT transparent, sapling, orchard FROM ua_settings WHERE account = ?1 " ,
params! [ account ] ,
| row | {
let transparent : bool = row . get ( 0 ) ? ;
let sapling : bool = row . get ( 1 ) ? ;
let orchard : bool = row . get ( 2 ) ? ;
Ok ( UnifiedAddressType {
transparent ,
sapling ,
orchard ,
} )
} ,
) ? ;
2022-10-30 03:03:38 -07:00
Ok ( tpe )
}
2021-08-16 06:07:04 -07:00
pub fn store_historical_prices (
& mut self ,
prices : & [ Quote ] ,
currency : & str ,
) -> anyhow ::Result < ( ) > {
2021-08-09 07:13:10 -07:00
let db_transaction = self . connection . transaction ( ) ? ;
{
2021-08-16 06:07:04 -07:00
let mut statement = db_transaction . prepare (
" INSERT INTO historical_prices(timestamp, price, currency) VALUES (?1, ?2, ?3) " ,
) ? ;
for q in prices {
statement . execute ( params! [ q . timestamp , q . price , currency ] ) ? ;
2021-08-09 07:13:10 -07:00
}
}
db_transaction . commit ( ) ? ;
Ok ( ( ) )
}
2021-08-16 06:07:04 -07:00
pub fn get_latest_quote ( & self , currency : & str ) -> anyhow ::Result < Option < Quote > > {
let quote = self . connection . query_row (
" SELECT timestamp, price FROM historical_prices WHERE currency = ?1 ORDER BY timestamp DESC " ,
params! [ currency ] ,
| row | {
let timestamp : i64 = row . get ( 0 ) ? ;
let price : f64 = row . get ( 1 ) ? ;
Ok ( Quote { timestamp , price } )
} ) . optional ( ) ? ;
Ok ( quote )
}
2021-09-08 07:10:22 -07:00
pub fn truncate_data ( & self ) -> anyhow ::Result < ( ) > {
2022-09-02 01:44:31 -07:00
self . truncate_sync_data ( ) ? ;
self . connection . execute ( " DELETE FROM diversifiers " , [ ] ) ? ;
Ok ( ( ) )
}
pub fn truncate_sync_data ( & self ) -> anyhow ::Result < ( ) > {
2022-05-19 09:19:04 -07:00
self . connection . execute ( " DELETE FROM blocks " , [ ] ) ? ;
2022-11-12 17:39:12 -08:00
self . connection . execute ( " DELETE FROM sapling_tree " , [ ] ) ? ;
self . connection . execute ( " DELETE FROM orchard_tree " , [ ] ) ? ;
2022-05-19 09:19:04 -07:00
self . connection . execute ( " DELETE FROM contacts " , [ ] ) ? ;
2022-06-07 09:58:24 -07:00
self . connection . execute ( " DELETE FROM diversifiers " , [ ] ) ? ;
2021-11-11 17:39:50 -08:00
self . connection
2022-05-19 09:19:04 -07:00
. execute ( " DELETE FROM historical_prices " , [ ] ) ? ;
2022-06-07 09:58:24 -07:00
self . connection . execute ( " DELETE FROM received_notes " , [ ] ) ? ;
2021-11-11 17:39:50 -08:00
self . connection
2022-05-19 09:19:04 -07:00
. execute ( " DELETE FROM sapling_witnesses " , [ ] ) ? ;
2022-10-19 23:32:11 -07:00
self . connection
. execute ( " DELETE FROM orchard_witnesses " , [ ] ) ? ;
2022-06-07 09:58:24 -07:00
self . connection . execute ( " DELETE FROM transactions " , [ ] ) ? ;
self . connection . execute ( " DELETE FROM messages " , [ ] ) ? ;
2021-09-08 07:10:22 -07:00
Ok ( ( ) )
}
2021-09-10 02:54:55 -07:00
2022-09-02 01:44:31 -07:00
pub fn delete_incomplete_scan ( & mut self ) -> anyhow ::Result < ( ) > {
let synced_height = self . get_last_sync_height ( ) ? ;
if let Some ( synced_height ) = synced_height {
2022-09-06 19:38:16 -07:00
self . trim_to_height ( synced_height ) ? ;
2022-09-02 01:44:31 -07:00
}
Ok ( ( ) )
}
2021-09-10 02:54:55 -07:00
pub fn delete_account ( & self , account : u32 ) -> anyhow ::Result < ( ) > {
2021-11-11 17:39:50 -08:00
self . connection . execute (
" DELETE FROM received_notes WHERE account = ?1 " ,
params! [ account ] ,
) ? ;
2021-12-15 03:09:53 -08:00
self . connection . execute (
" DELETE FROM transactions WHERE account = ?1 " ,
params! [ account ] ,
) ? ;
self . connection . execute (
" DELETE FROM diversifiers WHERE account = ?1 " ,
params! [ account ] ,
) ? ;
2021-11-11 17:39:50 -08:00
self . connection . execute (
" DELETE FROM accounts WHERE id_account = ?1 " ,
params! [ account ] ,
) ? ;
self . connection
. execute ( " DELETE FROM taddrs WHERE account = ?1 " , params! [ account ] ) ? ;
2022-11-12 17:39:12 -08:00
self . connection . execute (
" DELETE FROM orchard_addrs WHERE account = ?1 " ,
params! [ account ] ,
) ? ;
2022-11-15 02:21:47 -08:00
self . connection . execute (
" DELETE FROM ua_settings WHERE account = ?1 " ,
params! [ account ] ,
) ? ;
2022-04-17 01:09:25 -07:00
self . connection
. execute ( " DELETE FROM messages WHERE account = ?1 " , params! [ account ] ) ? ;
2021-09-10 02:54:55 -07:00
Ok ( ( ) )
}
2022-02-28 07:35:29 -08:00
2022-04-15 23:23:50 -07:00
pub fn store_message ( & self , account : u32 , message : & ZMessage ) -> anyhow ::Result < ( ) > {
2022-11-15 02:21:47 -08:00
self . connection . execute ( " INSERT INTO messages(account, id_tx, sender, recipient, subject, body, timestamp, height, incoming, read) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10) " ,
params! [ account , message . id_tx , message . sender , message . recipient , message . subject , message . body , message . timestamp , message . height , message . incoming , false ] ) ? ;
2022-04-15 23:23:50 -07:00
Ok ( ( ) )
}
pub fn mark_message_read ( & self , message_id : u32 , read : bool ) -> anyhow ::Result < ( ) > {
2022-06-07 09:58:24 -07:00
self . connection . execute (
" UPDATE messages SET read = ?1 WHERE id = ?2 " ,
params! [ read , message_id ] ,
) ? ;
2022-04-15 23:23:50 -07:00
Ok ( ( ) )
}
2022-04-16 20:15:59 -07:00
pub fn mark_all_messages_read ( & self , account : u32 , read : bool ) -> anyhow ::Result < ( ) > {
2022-06-07 09:58:24 -07:00
self . connection . execute (
" UPDATE messages SET read = ?1 WHERE account = ?2 " ,
params! [ read , account ] ,
) ? ;
Ok ( ( ) )
}
pub fn store_t_scan ( & self , addresses : & [ TBalance ] ) -> anyhow ::Result < ( ) > {
self . connection . execute (
" CREATE TABLE IF NOT EXISTS taddr_scan( \
id INTEGER NOT NULL PRIMARY KEY ,
address TEXT NOT NULL ,
value INTEGER NOT NULL ,
aindex INTEGER NOT NULL ) " ,
[ ] ,
) ? ;
for addr in addresses . iter ( ) {
self . connection . execute (
" INSERT INTO taddr_scan(address, value, aindex) VALUES (?1, ?2, ?3) " ,
params! [ addr . address , addr . balance , addr . index ] ,
) ? ;
}
2022-04-15 23:23:50 -07:00
Ok ( ( ) )
}
2022-06-10 03:41:03 -07:00
pub fn get_accounts ( & self ) -> anyhow ::Result < Vec < AccountRec > > {
2022-06-10 11:41:05 -07:00
let mut s = self
. connection
. prepare ( " SELECT id_account, name, address FROM accounts " ) ? ;
2022-06-10 03:41:03 -07:00
let accounts = s . query_map ( [ ] , | row | {
let id_account : u32 = row . get ( 0 ) ? ;
let name : String = row . get ( 1 ) ? ;
let address : String = row . get ( 2 ) ? ;
Ok ( AccountRec {
2022-06-10 11:41:05 -07:00
id_account ,
name ,
address ,
2022-06-10 03:41:03 -07:00
} )
} ) ? ;
let mut account_recs = vec! [ ] ;
for row in accounts {
account_recs . push ( row ? ) ;
}
Ok ( account_recs )
}
2022-06-10 02:16:00 -07:00
pub fn get_txs ( & self , account : u32 ) -> anyhow ::Result < Vec < TxRec > > {
let mut s = self . connection . prepare ( " SELECT txid, height, timestamp, value, address, memo FROM transactions WHERE account = ?1 " ) ? ;
let tx_rec = s . query_map ( params! [ account ] , | row | {
let mut txid : Vec < u8 > = row . get ( 0 ) ? ;
txid . reverse ( ) ;
let txid = hex ::encode ( txid ) ;
let height : u32 = row . get ( 1 ) ? ;
let timestamp : u32 = row . get ( 2 ) ? ;
let value : i64 = row . get ( 3 ) ? ;
let address : String = row . get ( 4 ) ? ;
let memo : String = row . get ( 5 ) ? ;
Ok ( TxRec {
2022-06-10 03:09:37 -07:00
txid ,
height ,
timestamp ,
value ,
address ,
memo ,
2022-06-10 02:16:00 -07:00
} )
} ) ? ;
let mut txs = vec! [ ] ;
for row in tx_rec {
txs . push ( row ? ) ;
}
Ok ( txs )
}
2022-11-05 18:49:17 -07:00
pub fn get_txid_without_memo ( & self ) -> anyhow ::Result < Vec < GetTransactionDetailRequest > > {
2022-11-15 02:21:47 -08:00
let mut stmt = self . connection . prepare (
" SELECT account, id_tx, height, timestamp, txid FROM transactions WHERE memo IS NULL " ,
) ? ;
2022-11-05 18:49:17 -07:00
let rows = stmt . query_map ( [ ] , | row | {
let account : u32 = row . get ( 0 ) ? ;
let id_tx : u32 = row . get ( 1 ) ? ;
let height : u32 = row . get ( 2 ) ? ;
2022-11-15 02:21:47 -08:00
let timestamp : u32 = row . get ( 3 ) ? ;
let txid : Vec < u8 > = row . get ( 4 ) ? ;
2022-11-05 18:49:17 -07:00
Ok ( GetTransactionDetailRequest {
account ,
id_tx ,
height ,
2022-11-15 02:21:47 -08:00
timestamp ,
2022-11-05 18:49:17 -07:00
txid : txid . try_into ( ) . unwrap ( ) ,
} )
} ) ? ;
let mut reqs = vec! [ ] ;
for r in rows {
reqs . push ( r ? ) ;
}
Ok ( reqs )
}
2022-03-07 06:47:06 -08:00
fn network ( & self ) -> & 'static Network {
let chain = get_coin_chain ( self . coin_type ) ;
chain . network ( )
}
2022-11-15 02:21:47 -08:00
pub fn sapling_activation_height ( & self ) -> u32 {
self . network ( )
. activation_height ( NetworkUpgrade ::Sapling )
. unwrap ( )
. into ( )
}
2021-06-21 17:33:13 -07:00
}
2022-04-15 23:23:50 -07:00
pub struct ZMessage {
2022-09-05 08:05:55 -07:00
pub id_tx : u32 ,
2022-04-15 23:23:50 -07:00
pub sender : Option < String > ,
pub recipient : String ,
pub subject : String ,
pub body : String ,
pub timestamp : u32 ,
pub height : u32 ,
2022-11-15 02:21:47 -08:00
pub incoming : bool ,
2022-04-15 23:23:50 -07:00
}
2022-04-17 01:09:25 -07:00
impl ZMessage {
pub fn is_empty ( & self ) -> bool {
self . sender . is_none ( ) & & self . subject . is_empty ( ) & & self . body . is_empty ( )
}
}
2022-06-10 02:16:00 -07:00
#[ derive(Serialize) ]
pub struct TxRec {
txid : String ,
height : u32 ,
timestamp : u32 ,
value : i64 ,
address : String ,
memo : String ,
}
2022-06-10 03:41:03 -07:00
#[ derive(Serialize) ]
pub struct AccountRec {
id_account : u32 ,
name : String ,
address : String ,
}
2022-09-21 01:11:50 -07:00
pub struct AccountData {
pub name : String ,
pub seed : Option < String > ,
pub sk : Option < String > ,
pub fvk : String ,
pub address : String ,
pub aindex : u32 ,
}
2021-06-21 17:33:13 -07:00
#[ cfg(test) ]
mod tests {
2022-11-17 17:02:03 -08:00
use crate ::db ::{ DbAdapter , DEFAULT_DB_PATH } ;
2022-11-06 04:50:51 -08:00
use zcash_params ::coin ::CoinType ;
2021-06-21 17:33:13 -07:00
#[ test ]
fn test_balance ( ) {
2022-04-19 09:47:08 -07:00
let db = DbAdapter ::new ( CoinType ::Zcash , DEFAULT_DB_PATH ) . unwrap ( ) ;
2021-06-29 00:04:12 -07:00
let balance = db . get_balance ( 1 ) . unwrap ( ) ;
2021-06-21 17:33:13 -07:00
println! ( " {} " , balance ) ;
}
}