2021-06-24 05:08:20 -07:00
use rusqlite ::{ Connection , params , OptionalExtension , NO_PARAMS } ;
2021-06-21 17:33:13 -07:00
use crate ::{ Witness , CTree } ;
2021-06-24 05:08:20 -07:00
use zcash_primitives ::sapling ::{ Note , Diversifier , Rseed , Node } ;
use zcash_primitives ::zip32 ::ExtendedFullViewingKey ;
use zcash_primitives ::merkle_tree ::IncrementalWitness ;
use std ::collections ::HashMap ;
use crate ::chain ::Nf ;
use zcash_primitives ::consensus ::{ Parameters , NetworkUpgrade } ;
2021-06-21 17:33:13 -07:00
pub struct DbAdapter {
connection : Connection ,
}
pub struct ReceivedNote {
pub height : u32 ,
pub output_index : u32 ,
pub diversifier : Vec < u8 > ,
pub value : u64 ,
pub rcm : Vec < u8 > ,
pub nf : Vec < u8 > ,
2021-06-24 05:08:20 -07:00
pub spent : Option < u32 > ,
}
pub struct SpendableNote {
pub note : Note ,
pub diversifier : Diversifier ,
pub witness : IncrementalWitness < Node > ,
2021-06-21 17:33:13 -07:00
}
impl DbAdapter {
pub fn new ( db_path : & str ) -> anyhow ::Result < DbAdapter > {
let connection = Connection ::open ( db_path ) ? ;
Ok ( DbAdapter {
connection ,
} )
}
pub fn init_db ( & self ) -> anyhow ::Result < ( ) > {
2021-06-24 05:08:20 -07:00
self . connection . execute ( " CREATE TABLE IF NOT EXISTS accounts (
id_account INTEGER PRIMARY KEY ,
sk TEXT NOT NULL UNIQUE ,
ivk TEXT NOT NULL ,
address TEXT NOT NULL ) " , NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self . connection . execute ( " CREATE TABLE IF NOT EXISTS blocks (
height INTEGER PRIMARY KEY ,
hash BLOB NOT NULL ,
2021-06-24 05:08:20 -07:00
sapling_tree BLOB NOT NULL ) " , NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self . connection . execute ( " CREATE TABLE IF NOT EXISTS transactions (
id_tx INTEGER PRIMARY KEY ,
txid BLOB NOT NULL UNIQUE ,
height INTEGER ,
2021-06-24 05:08:20 -07:00
tx_index INTEGER ) " , NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self . connection . execute ( " CREATE TABLE IF NOT EXISTS received_notes (
id_note INTEGER PRIMARY KEY ,
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 ,
2021-06-24 05:08:20 -07:00
CONSTRAINT tx_output UNIQUE ( tx , output_index ) ) " , NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self . 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 ,
2021-06-24 05:08:20 -07:00
CONSTRAINT witness_height UNIQUE ( note , height ) ) " , NO_PARAMS)?;
Ok ( ( ) )
}
2021-06-21 17:33:13 -07:00
2021-06-24 05:08:20 -07:00
pub fn store_account ( & self , sk : & str , ivk : & str , address : & str ) -> anyhow ::Result < ( ) > {
self . connection . execute ( " INSERT INTO accounts(sk, ivk, address) VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING " , params![sk, ivk, address])?;
2021-06-21 17:33:13 -07:00
Ok ( ( ) )
}
pub fn trim_to_height ( & mut self , height : u32 ) -> anyhow ::Result < ( ) > {
let tx = self . connection . transaction ( ) ? ;
tx . execute ( " DELETE FROM blocks WHERE height >= ?1 " , params! [ height ] ) ? ;
tx . execute ( " DELETE FROM sapling_witnesses WHERE height >= ?1 " , params! [ height ] ) ? ;
tx . execute ( " DELETE FROM received_notes WHERE height >= ?1 " , params! [ height ] ) ? ;
tx . execute ( " DELETE FROM transactions WHERE height >= ?1 " , params! [ height ] ) ? ;
tx . commit ( ) ? ;
Ok ( ( ) )
}
pub fn store_block ( & self , height : u32 , hash : & [ u8 ] , tree : & CTree ) -> anyhow ::Result < ( ) > {
2021-06-24 05:08:20 -07:00
log ::info! ( " +block " ) ;
2021-06-21 17:33:13 -07:00
let mut bb : Vec < u8 > = vec! [ ] ;
tree . write ( & mut bb ) ? ;
self . connection . execute ( " INSERT INTO blocks(height, hash, sapling_tree)
VALUES ( ? 1 , ? 2 , ? 3 )
ON CONFLICT DO NOTHING " , params![height, hash, &bb])?;
2021-06-24 05:08:20 -07:00
log ::info! ( " -block " ) ;
2021-06-21 17:33:13 -07:00
Ok ( ( ) )
}
pub fn store_transaction ( & self , txid : & [ u8 ] , height : u32 , tx_index : u32 ) -> anyhow ::Result < u32 > {
2021-06-24 05:08:20 -07:00
log ::info! ( " +transaction " ) ;
2021-06-21 17:33:13 -07:00
self . connection . execute ( " INSERT INTO transactions(txid, height, tx_index)
VALUES ( ? 1 , ? 2 , ? 3 )
ON CONFLICT DO NOTHING " , params![txid, height, tx_index])?;
let id_tx : u32 = self . connection . query_row ( " SELECT id_tx FROM transactions WHERE txid = ?1 " , params! [ txid ] , | row | row . get ( 0 ) ) ? ;
2021-06-24 05:08:20 -07:00
log ::info! ( " -transaction {} " , id_tx ) ;
2021-06-21 17:33:13 -07:00
Ok ( id_tx )
}
pub fn store_received_note ( & self , note : & ReceivedNote , id_tx : u32 , position : usize ) -> anyhow ::Result < u32 > {
2021-06-24 05:08:20 -07:00
log ::info! ( " +received_note {} " , id_tx ) ;
self . connection . execute ( " INSERT INTO received_notes(tx, height, position, output_index, diversifier, value, rcm, nf, spent)
VALUES ( ? 1 , ? 2 , ? 3 , ? 4 , ? 5 , ? 6 , ? 7 , ? 8 , ? 9 )
ON CONFLICT DO NOTHING " , params![id_tx, note.height, position as u32, note.output_index, note.diversifier, note.value as i64, note.rcm, note.nf, note.spent])?;
2021-06-21 17:33:13 -07:00
let id_note : u32 = self . connection . query_row ( " SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2 " , params! [ id_tx , note . output_index ] , | row | row . get ( 0 ) ) ? ;
2021-06-24 05:08:20 -07:00
log ::info! ( " -received_note " ) ;
2021-06-21 17:33:13 -07:00
Ok ( id_note )
}
pub fn store_witnesses ( & self , witness : & Witness , height : u32 , id_note : u32 ) -> anyhow ::Result < ( ) > {
2021-06-24 05:08:20 -07:00
log ::info! ( " +witnesses " ) ;
2021-06-21 17:33:13 -07:00
let mut bb : Vec < u8 > = vec! [ ] ;
witness . write ( & mut bb ) ? ;
self . connection . execute ( " INSERT INTO sapling_witnesses(note, height, witness) VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING " , params![id_note, height, bb])?;
2021-06-24 05:08:20 -07:00
log ::info! ( " -witnesses " ) ;
2021-06-21 17:33:13 -07:00
Ok ( ( ) )
}
pub fn get_balance ( & self ) -> anyhow ::Result < u64 > {
2021-06-24 05:08:20 -07:00
let balance : Option < i64 > = self . connection . query_row ( " SELECT SUM(value) FROM received_notes WHERE spent IS NULL " , NO_PARAMS , | row | row . get ( 0 ) ) ? ;
Ok ( balance . unwrap_or ( 0 ) as u64 )
2021-06-21 17:33:13 -07:00
}
2021-06-24 05:08:20 -07:00
pub fn get_last_sync_height ( & self ) -> anyhow ::Result < Option < u32 > > {
let height : Option < u32 > = self . connection . query_row ( " SELECT MAX(height) FROM blocks " , NO_PARAMS , | row | row . get ( 0 ) ) ? ;
Ok ( height )
}
pub fn get_db_height ( & self ) -> anyhow ::Result < u32 > {
let height : u32 = self . get_last_sync_height ( ) ? . unwrap_or_else ( | | {
crate ::NETWORK
. activation_height ( NetworkUpgrade ::Sapling )
. unwrap ( )
. into ( )
} ) ;
2021-06-21 17:33:13 -07:00
Ok ( height )
}
pub fn get_tree ( & self ) -> anyhow ::Result < ( CTree , Vec < Witness > ) > {
let res = self . connection . query_row (
" SELECT height, sapling_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks) " ,
2021-06-24 05:08:20 -07:00
NO_PARAMS , | row | {
2021-06-21 17:33:13 -07:00
let height : u32 = row . get ( 0 ) ? ;
let tree : Vec < u8 > = row . get ( 1 ) ? ;
Ok ( ( height , tree ) )
} ) . optional ( ) ? ;
Ok ( match res {
Some ( ( height , tree ) ) = > {
let tree = CTree ::read ( & * tree ) ? ;
let mut statement = self . connection . prepare (
" SELECT id_note, position, witness FROM sapling_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note " ) ? ;
let ws = statement . query_map ( params! [ height ] , | row | {
let id_note : u32 = row . get ( 0 ) ? ;
let position : u32 = row . get ( 1 ) ? ;
let witness : Vec < u8 > = row . get ( 2 ) ? ;
Ok ( Witness ::read ( position as usize , id_note , & * witness ) . unwrap ( ) )
} ) ? ;
let mut witnesses : Vec < Witness > = vec! [ ] ;
for w in ws {
witnesses . push ( w ? ) ;
}
( tree , witnesses )
} ,
None = > ( CTree ::new ( ) , vec! [ ] )
} )
}
2021-06-24 05:08:20 -07:00
pub fn get_nullifiers ( & self ) -> anyhow ::Result < HashMap < Nf , u32 > > {
let mut statement = self . connection . prepare (
" SELECT id_note, nf FROM received_notes WHERE spent = 0 " ) ? ;
let nfs_res = statement . query_map ( NO_PARAMS , | row | {
let id_note : u32 = row . get ( 0 ) ? ;
let nf_vec : Vec < u8 > = row . get ( 1 ) ? ;
let mut nf = [ 0 u8 ; 32 ] ;
nf . clone_from_slice ( & nf_vec ) ;
Ok ( ( id_note , nf ) )
} ) ? ;
let mut nfs : HashMap < Nf , u32 > = HashMap ::new ( ) ;
for n in nfs_res {
let n = n ? ;
nfs . insert ( Nf ( n . 1 ) , n . 0 ) ;
}
Ok ( nfs )
}
pub fn get_spendable_notes ( & self , anchor_height : u32 , fvk : & ExtendedFullViewingKey ) -> anyhow ::Result < Vec < SpendableNote > > {
let mut statement = self . connection . prepare (
" SELECT diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL
AND w . height = (
SELECT MAX ( height ) FROM sapling_witnesses WHERE height < = ? 1
) AND r . id_note = w . note " )?;
let notes = statement . query_map ( params! [ anchor_height ] , | row | {
let diversifier : Vec < u8 > = row . get ( 0 ) ? ;
let value : i64 = row . get ( 1 ) ? ;
let rcm : Vec < u8 > = row . get ( 2 ) ? ;
let witness : Vec < u8 > = row . get ( 3 ) ? ;
let mut diversifer_bytes = [ 0 u8 ; 11 ] ;
diversifer_bytes . copy_from_slice ( & diversifier ) ;
let diversifier = Diversifier ( diversifer_bytes ) ;
let mut rcm_bytes = [ 0 u8 ; 32 ] ;
rcm_bytes . copy_from_slice ( & rcm ) ;
let rcm = jubjub ::Fr ::from_bytes ( & rcm_bytes ) . unwrap ( ) ;
let rseed = Rseed ::BeforeZip212 ( rcm ) ;
let witness = IncrementalWitness ::< Node > ::read ( & * witness ) . unwrap ( ) ;
let pa = fvk . fvk . vk . to_payment_address ( diversifier ) . unwrap ( ) ;
let note = pa . create_note ( value as u64 , rseed ) . unwrap ( ) ;
Ok ( SpendableNote {
note ,
diversifier ,
witness
} )
} ) ? ;
let mut spendable_notes : Vec < SpendableNote > = vec! [ ] ;
for n in notes {
spendable_notes . push ( n ? ) ;
}
Ok ( spendable_notes )
}
pub fn mark_spent ( & self , id : u32 , height : u32 ) -> anyhow ::Result < ( ) > {
log ::info! ( " +mark_spent " ) ;
self . connection . execute ( " UPDATE received_notes SET spent = ?1 WHERE id_note = ?2 " , params! [ height , id ] ) ? ;
log ::info! ( " -mark_spent " ) ;
Ok ( ( ) )
}
pub fn get_sk ( & self , account : u32 ) -> anyhow ::Result < String > {
log ::info! ( " +get_sk " ) ;
let ivk = self . connection . query_row ( " SELECT sk FROM accounts WHERE id_account = ?1 " , params! [ account ] , | row | {
let ivk : String = row . get ( 0 ) ? ;
Ok ( ivk )
} ) ? ;
log ::info! ( " -get_sk " ) ;
Ok ( ivk )
}
pub fn get_ivk ( & self , account : u32 ) -> anyhow ::Result < String > {
log ::info! ( " +get_ivk " ) ;
let ivk = self . connection . query_row ( " SELECT ivk FROM accounts WHERE id_account = ?1 " , params! [ account ] , | row | {
let ivk : String = row . get ( 0 ) ? ;
Ok ( ivk )
} ) ? ;
log ::info! ( " -get_ivk " ) ;
Ok ( ivk )
}
2021-06-21 17:33:13 -07:00
}
#[ cfg(test) ]
mod tests {
use crate ::db ::{ DbAdapter , ReceivedNote } ;
use crate ::{ Witness , CTree } ;
const DB_PATH : & str = " zec.db " ;
#[ test ]
fn test_db ( ) {
let mut db = DbAdapter ::new ( DB_PATH ) . unwrap ( ) ;
db . init_db ( ) . unwrap ( ) ;
db . trim_to_height ( 0 ) . unwrap ( ) ;
db . store_block ( 1 , & [ 0 u8 ; 32 ] , & CTree ::new ( ) ) . unwrap ( ) ;
let id_tx = db . store_transaction ( & [ 0 ; 32 ] , 1 , 20 ) . unwrap ( ) ;
db . store_received_note ( & ReceivedNote {
height : 1 ,
output_index : 0 ,
diversifier : vec ! [ ] ,
value : 0 ,
rcm : vec ! [ ] ,
nf : vec ! [ ] ,
2021-06-24 05:08:20 -07:00
spent : None
2021-06-21 17:33:13 -07:00
} , id_tx , 5 ) . unwrap ( ) ;
let witness = Witness {
position : 10 ,
id_note : 0 ,
note : None ,
tree : CTree ::new ( ) ,
filled : vec ! [ ] ,
cursor : CTree ::new ( ) ,
} ;
db . store_witnesses ( & witness , 1000 , 1 ) . unwrap ( ) ;
}
#[ test ]
fn test_balance ( ) {
let db = DbAdapter ::new ( DB_PATH ) . unwrap ( ) ;
let balance = db . get_balance ( ) . unwrap ( ) ;
println! ( " {} " , balance ) ;
}
}