diff --git a/src/builder.rs b/src/builder.rs index 0709ede..4ea39d2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -216,18 +216,19 @@ impl Builder for WitnessBuilder { } } + let right = if depth != 0 && !context.first_block { + context.right + } else { + None + }; // println!("D {}", depth); // println!("O {:?}", offset.map(|r| hex::encode(r.repr))); // println!("R {:?}", right.map(|r| hex::encode(r.repr))); // for c in commitments.iter() { // println!("{}", hex::encode(c.repr)); // } - let right = if depth != 0 && !context.first_block { - context.right - } else { - None - }; let p1 = self.p + 1; + // println!("P {} P1 {} S {} AS {}", self.p, p1, context.start, context.adjusted_start(&right)); let has_p1 = p1 >= context.adjusted_start(&right) && p1 < context.start + commitments.len(); if has_p1 { let p1 = CTreeBuilder::get(commitments, p1 - context.adjusted_start(&right), &right); @@ -342,6 +343,7 @@ impl BlockProcessor { } pub fn add_nodes(&mut self, nodes: &mut [Node], new_witnesses: &[Witness]) { + if nodes.is_empty() { return } self.prev_witnesses.extend_from_slice(new_witnesses); let (t, ws) = advance_tree( &self.prev_tree, @@ -349,13 +351,19 @@ impl BlockProcessor { nodes, self.first_block, ); + self.first_block = false; self.prev_tree = t; self.prev_witnesses = ws; } pub fn finalize(self) -> (CTree, Vec) { - let (t, ws) = advance_tree(&self.prev_tree, &self.prev_witnesses, &mut [], false); - (t, ws) + if self.first_block { + (self.prev_tree, self.prev_witnesses) + } + else { + let (t, ws) = advance_tree(&self.prev_tree, &self.prev_witnesses, &mut [], false); + (t, ws) + } } } @@ -369,6 +377,121 @@ mod tests { use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness}; use zcash_primitives::sapling::Node; + fn make_nodes(p: usize, len: usize) -> Vec { + let nodes: Vec<_> = (p..p+len).map(|v| { + let mut bb = [0u8; 32]; + bb[0..8].copy_from_slice(&v.to_be_bytes()); + Node::new(bb) + }).collect(); + nodes + } + + fn make_witnesses(p: usize, len: usize) -> Vec { + let witnesses: Vec<_> = (p..p+len).map(|v| { + Witness::new(v, 0, None) + }).collect(); + witnesses + } + + fn update_witnesses1(tree: &mut CommitmentTree, ws: &mut Vec>, nodes: &[Node]) { + for n in nodes.iter() { + tree.append(n.clone()).unwrap(); + for w in ws.iter_mut() { + w.append(n.clone()).unwrap(); + } + let w = IncrementalWitness::::from_tree(&tree); + ws.push(w); + } + } + + fn compare_witness(w1: &IncrementalWitness, w2: &Witness) { + let mut bb1: Vec = vec![]; + w1.write(&mut bb1).unwrap(); + let mut bb2: Vec = vec![]; + w2.write(&mut bb2).unwrap(); + + if bb1 != bb2 { + print_witness(&w1); + print_witness2(&w2); + + assert!(false); + } + } + + #[test] + fn test_simple() { + let v = [0u8; 32]; + let mut bp = BlockProcessor::new(&CTree::new(), &[]); + let mut nodes = [Node::new(v)]; + bp.add_nodes(&mut [], &[]); + bp.add_nodes(&mut nodes, &[Witness::new(0, 0, None)]); + bp.finalize(); + } + + #[test] + fn test_bp_1run() { + for n1 in 0..=40 { + for n2 in 0..=40 { + println!("{} {}", n1, n2); + let mut bp = BlockProcessor::new(&CTree::new(), &[]); + let mut tree1: CommitmentTree = CommitmentTree::empty(); + let mut ws1: Vec> = vec![]; + + let mut nodes = make_nodes(0, n1); + update_witnesses1(&mut tree1, &mut ws1, &nodes); + bp.add_nodes(&mut nodes, &make_witnesses(0, n1)); + + let mut nodes = make_nodes(n1, n2); + update_witnesses1(&mut tree1, &mut ws1, &nodes); + bp.add_nodes(&mut nodes, &make_witnesses(n1, n2)); + + let (_, ws2) = bp.finalize(); + + for (i, (w1, w2)) in ws1.iter().zip(ws2.iter()).enumerate() { + println!("Compare {}", i); + compare_witness(w1, w2); + } + } + } + } + + #[test] + fn test_bp_2run() { + for n1 in 0..=40 { + for n2 in 0..=40 { + println!("{} {}", n1, n2); + let mut tree1: CommitmentTree = CommitmentTree::empty(); + let mut ws1: Vec> = vec![]; + let mut tree2 = CTree::new(); + let mut ws2: Vec = vec![]; + + { + let mut bp = BlockProcessor::new(&tree2, &ws2); + let mut nodes = make_nodes(0, n1); + update_witnesses1(&mut tree1, &mut ws1, &nodes); + bp.add_nodes(&mut nodes, &make_witnesses(0, n1)); + let (t2, w2) = bp.finalize(); + tree2 = t2; + ws2 = w2; + } + + { + let mut bp = BlockProcessor::new(&tree2, &ws2); + let mut nodes = make_nodes(n1, n2); + update_witnesses1(&mut tree1, &mut ws1, &nodes); + bp.add_nodes(&mut nodes, &make_witnesses(n1, n2)); + let (_t2, w2) = bp.finalize(); + ws2 = w2; + } + + for (i, (w1, w2)) in ws1.iter().zip(ws2.iter()).enumerate() { + println!("Compare {}", i); + compare_witness(w1, w2); + } + } + } + } + #[test] fn test_advance_tree_equal_blocks() { for num_nodes in 1..=10 { @@ -380,8 +503,8 @@ mod tests { #[test] fn test_advance_tree_unequal_blocks() { - for num_nodes1 in 1..=30 { - for num_nodes2 in 1..=30 { + for num_nodes1 in 0..=30 { + for num_nodes2 in 0..=30 { println!("TESTING {} {}", num_nodes1, num_nodes2); let (t, ws) = test_advance_tree_helper(num_nodes1, 1, 100.0, None); test_advance_tree_helper(num_nodes2, 1, 100.0, Some((t, ws))); @@ -390,8 +513,16 @@ mod tests { } #[test] - fn test_advance_tree() { - test_advance_tree_helper(100, 50, 1.0, None); + fn test_small_blocks() { + for num_nodes1 in 1..=30 { + println!("TESTING {}", num_nodes1); + test_advance_tree_helper(num_nodes1, 1, 100.0, None); + } + } + + #[test] + fn test_tree() { + test_advance_tree_helper(4, 1, 100.0, None); // test_advance_tree_helper(2, 10, 100.0); // test_advance_tree_helper(1, 40, 100.0); diff --git a/src/chain.rs b/src/chain.rs index 8898978..e07630f 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -19,10 +19,11 @@ use zcash_primitives::transaction::components::sapling::CompactOutputDescription use zcash_primitives::zip32::ExtendedFullViewingKey; const MAX_CHUNK: u32 = 50000; +pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067"; // pub const LWD_URL: &str = "https://testnet.lightwalletd.com:9067"; // pub const LWD_URL: &str = "http://lwd.hanh.me:9067"; // pub const LWD_URL: &str = "https://lwdv3.zecwallet.co"; -pub const LWD_URL: &str = "http://127.0.0.1:9067"; +// pub const LWD_URL: &str = "http://127.0.0.1:9067"; pub async fn get_latest_height( client: &mut CompactTxStreamerClient, @@ -211,7 +212,13 @@ pub async fn send_transaction( .send_transaction(Request::new(raw_tx)) .await? .into_inner(); - Ok(rep.error_message) + let code = rep.error_code; + if code == 0 { + Ok(rep.error_message) + } + else { + Err(anyhow::anyhow!(rep.error_message)) + } } /* Using the IncrementalWitness */ diff --git a/src/commitment.rs b/src/commitment.rs index 7042d18..19be998 100644 --- a/src/commitment.rs +++ b/src/commitment.rs @@ -98,19 +98,20 @@ impl Witness { } } - pub fn read(position: usize, id_note: u32, mut reader: R) -> std::io::Result { + pub fn read(id_note: u32, mut reader: R) -> std::io::Result { let tree = CTree::read(&mut reader)?; let filled = Vector::read(&mut reader, |r| Node::read(r))?; let cursor = Optional::read(&mut reader, |r| CTree::read(r))?; - let witness = Witness { - position, + let mut witness = Witness { + position: 0, id_note, tree, filled, cursor: cursor.unwrap_or_else(CTree::new), note: None, }; + witness.position = witness.tree.get_position() - 1; Ok(witness) } diff --git a/src/db.rs b/src/db.rs index c08c51f..fc3a99a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -11,7 +11,7 @@ use zcash_primitives::zip32::ExtendedFullViewingKey; pub const DEFAULT_DB_PATH: &str = "zec.db"; pub struct DbAdapter { - connection: Connection, + pub connection: Connection, } pub struct ReceivedNote { @@ -25,6 +25,7 @@ pub struct ReceivedNote { } pub struct SpendableNote { + pub id: u32, pub note: Note, pub diversifier: Diversifier, pub witness: IncrementalWitness, @@ -40,6 +41,7 @@ impl DbAdapter { self.connection.execute( "CREATE TABLE IF NOT EXISTS accounts ( id_account INTEGER PRIMARY KEY, + seed TEXT NOT NULL, sk TEXT NOT NULL UNIQUE, ivk TEXT NOT NULL, address TEXT NOT NULL)", @@ -95,11 +97,11 @@ impl DbAdapter { Ok(()) } - pub fn store_account(&self, sk: &str, ivk: &str, address: &str) -> anyhow::Result<()> { + pub fn store_account(&self, seed: &str, sk: &str, ivk: &str, address: &str) -> anyhow::Result<()> { self.connection.execute( - "INSERT INTO accounts(sk, ivk, address) VALUES (?1, ?2, ?3) + "INSERT INTO accounts(seed, sk, ivk, address) VALUES (?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING", - params![sk, ivk, address], + params![seed, sk, ivk, address], )?; Ok(()) } @@ -235,7 +237,7 @@ impl DbAdapter { pub fn get_balance(&self) -> anyhow::Result { let balance: Option = self.connection.query_row( - "SELECT SUM(value) FROM received_notes WHERE spent IS NULL", + "SELECT SUM(value) FROM received_notes WHERE spent IS NULL OR spent = 0", NO_PARAMS, |row| row.get(0), )?; @@ -291,12 +293,11 @@ impl DbAdapter { 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 AND n.spent IS NULL")?; + "SELECT id_note, witness FROM sapling_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note AND (n.spent IS NULL OR n.spent = 0)")?; let ws = statement.query_map(params![height], |row| { let id_note: u32 = row.get(0)?; - let position: u32 = row.get(1)?; - let witness: Vec = row.get(2)?; - Ok(Witness::read(position as usize, id_note, &*witness).unwrap()) + let witness: Vec = row.get(1)?; + Ok(Witness::read(id_note, &*witness).unwrap()) })?; let mut witnesses: Vec = vec![]; for w in ws { @@ -311,7 +312,7 @@ impl DbAdapter { pub fn get_nullifiers(&self) -> anyhow::Result> { let mut statement = self .connection - .prepare("SELECT id_note, nf FROM received_notes WHERE spent IS NULL")?; + .prepare("SELECT id_note, nf FROM received_notes WHERE spent IS NULL OR spent = 0")?; let nfs_res = statement.query_map(NO_PARAMS, |row| { let id_note: u32 = row.get(0)?; let nf_vec: Vec = row.get(1)?; @@ -331,7 +332,7 @@ impl DbAdapter { pub fn get_nullifier_amounts(&self) -> anyhow::Result, u64>> { let mut statement = self .connection - .prepare("SELECT value, nf FROM received_notes WHERE spent IS NULL")?; + .prepare("SELECT value, nf FROM received_notes WHERE spent IS NULL OR spent = 0")?; let nfs_res = statement.query_map(NO_PARAMS, |row| { let amount: i64 = row.get(0)?; let nf: Vec = row.get(1)?; @@ -352,15 +353,17 @@ impl DbAdapter { fvk: &ExtendedFullViewingKey, ) -> anyhow::Result> { let mut statement = self.connection.prepare( - "SELECT diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL + "SELECT id_note, 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 = row.get(0)?; - let value: i64 = row.get(1)?; - let rcm: Vec = row.get(2)?; - let witness: Vec = row.get(3)?; + let id_note: u32 = row.get(0)?; + + let diversifier: Vec = row.get(1)?; + let value: i64 = row.get(2)?; + let rcm: Vec = row.get(3)?; + let witness: Vec = row.get(4)?; let mut diversifer_bytes = [0u8; 11]; diversifer_bytes.copy_from_slice(&diversifier); @@ -374,6 +377,7 @@ impl DbAdapter { let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap(); let note = pa.create_note(value as u64, rseed).unwrap(); Ok(SpendableNote { + id: id_note, note, diversifier, witness, @@ -397,6 +401,20 @@ impl DbAdapter { Ok(()) } + pub fn get_seed(&self, account: u32) -> anyhow::Result { + log::debug!("+get_seed"); + let ivk = self.connection.query_row( + "SELECT seed FROM accounts WHERE id_account = ?1", + params![account], + |row| { + let ivk: String = row.get(0)?; + Ok(ivk) + }, + )?; + log::debug!("-get_seed"); + Ok(ivk) + } + pub fn get_sk(&self, account: u32) -> anyhow::Result { log::debug!("+get_sk"); let ivk = self.connection.query_row( diff --git a/src/lib.rs b/src/lib.rs index 69d8308..293d150 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use zcash_primitives::consensus::Network; #[path = "generated/cash.z.wallet.sdk.rpc.rs"] pub mod lw_rpc; -pub const NETWORK: Network = Network::TestNetwork; +pub const NETWORK: Network = Network::MainNetwork; mod builder; mod chain; @@ -28,3 +28,4 @@ pub use crate::lw_rpc::*; pub use crate::mempool::MemPool; pub use crate::scan::{latest_height, scan_all, sync_async}; pub use crate::wallet::{Wallet, WalletBalance, DEFAULT_ACCOUNT}; +pub use crate::print::*; diff --git a/src/main/warp_cli.rs b/src/main/warp_cli.rs index b7bb2ca..5b1225c 100644 --- a/src/main/warp_cli.rs +++ b/src/main/warp_cli.rs @@ -1,7 +1,8 @@ use bip39::{Language, Mnemonic}; use rand::rngs::OsRng; use rand::RngCore; -use sync::{DbAdapter, Wallet, DEFAULT_ACCOUNT, ChainError}; +use sync::{DbAdapter, Wallet, DEFAULT_ACCOUNT, ChainError, Witness, print_witness2}; +use rusqlite::NO_PARAMS; const DB_NAME: &str = "zec.db"; @@ -29,11 +30,11 @@ async fn test() -> anyhow::Result<()> { println!("REORG"); } } - let tx_id = wallet - .send_payment(DEFAULT_ACCOUNT, &address, 50000) - .await - .unwrap(); - println!("TXID = {}", tx_id); + // let tx_id = wallet + // .send_payment(DEFAULT_ACCOUNT, &address, 50000) + // .await + // .unwrap(); + // println!("TXID = {}", tx_id); Ok(()) } @@ -50,18 +51,53 @@ fn test_make_wallet() { #[allow(dead_code)] fn test_rewind() { let mut db = DbAdapter::new(DB_NAME).unwrap(); - db.trim_to_height(1460000).unwrap(); + db.trim_to_height(1466520).unwrap(); } +#[allow(dead_code)] fn test_get_balance() { let db = DbAdapter::new(DB_NAME).unwrap(); let balance = db.get_balance().unwrap(); println!("Balance = {}", (balance as f64) / 100_000_000.0); } +#[allow(dead_code)] +fn test_invalid_witness() { + dotenv::dotenv().unwrap(); + env_logger::init(); + + println!("BAD"); + let witness = dotenv::var("WITNESS").unwrap(); + let w = Witness::read(0, &*hex::decode(&witness).unwrap()).unwrap(); + print_witness2(&w); + + println!("GOOD"); + let witness = dotenv::var("WITNESS2").unwrap(); + let w = Witness::read(0, &*hex::decode(&witness).unwrap()).unwrap(); + print_witness2(&w); +} + +fn w() { + let db = DbAdapter::new("zec.db").unwrap(); + // let w_b: Vec = db.connection.query_row("SELECT witness FROM sapling_witnesses WHERE note = 66 AND height = 1466097", NO_PARAMS, |row| row.get(0)).unwrap(); + // let w = Witness::read(0, &*w_b).unwrap(); + // print_witness2(&w); + // + let w_b: Vec = db.connection.query_row("SELECT witness FROM sapling_witnesses WHERE note = 66 AND height = 1466200", NO_PARAMS, |row| row.get(0)).unwrap(); + let w = Witness::read(0, &*w_b).unwrap(); + print_witness2(&w); + + println!("GOOD"); + let witness = dotenv::var("WITNESS2").unwrap(); + let w = Witness::read(0, &*hex::decode(&witness).unwrap()).unwrap(); + print_witness2(&w); +} + fn main() { init(); + // test_invalid_witness() // test_rewind(); test().unwrap(); - test_get_balance(); + // test_get_balance(); + // w(); } diff --git a/src/scan.rs b/src/scan.rs index 48cd33d..fd0e8cf 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -17,6 +17,7 @@ use zcash_client_backend::encoding::decode_extended_full_viewing_key; use zcash_primitives::consensus::{NetworkUpgrade, Parameters}; use zcash_primitives::sapling::Node; use zcash_primitives::zip32::ExtendedFullViewingKey; +use std::panic; pub async fn scan_all(fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> { let decrypter = DecryptNode::new(fvks.to_vec()); @@ -214,6 +215,7 @@ pub async fn sync_async( if !nodes.is_empty() { bp.add_nodes(&mut nodes, &witnesses); } + // println!("NOTES = {}", nodes.len()); if let Some(block) = blocks.0.last() { let mut hash = [0u8; 32]; @@ -276,6 +278,9 @@ pub async fn sync_async( } Err(err) => { log::info!("Sync error = {}", err); + if err.is_panic() { + panic::resume_unwind(err.into_panic()); + } anyhow::bail!("Join Error"); }, } diff --git a/src/wallet.rs b/src/wallet.rs index 4122935..fea8de1 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -76,6 +76,10 @@ impl Wallet { Ok(()) } + pub fn get_seed(&self, account: u32) -> anyhow::Result { + self.db.get_seed(account) + } + pub fn has_account(&self, account: u32) -> anyhow::Result { self.db.has_account(account) } @@ -84,7 +88,7 @@ impl Wallet { let sk = get_secret_key(&seed).unwrap(); let vk = get_viewing_key(&sk).unwrap(); let pa = get_address(&vk).unwrap(); - self.db.store_account(&sk, &vk, &pa)?; + self.db.store_account(seed, &sk, &vk, &pa)?; Ok(()) } @@ -195,7 +199,8 @@ impl Wallet { let mut amount = target_amount; amount += DEFAULT_FEE; - for n in notes { + let mut selected_note: Vec = vec![]; + for n in notes.iter() { if amount.is_positive() { let a = amount.min( Amount::from_u64(n.note.value) @@ -203,12 +208,15 @@ impl Wallet { ); amount -= a; let merkle_path = n.witness.path().context("Invalid Merkle Path")?; + let mut witness_bytes: Vec = vec![]; + n.witness.write(&mut witness_bytes)?; builder.add_sapling_spend( skey.clone(), n.diversifier, n.note.clone(), merkle_path, )?; + selected_note.push(n.id); } } if amount.is_positive() { @@ -237,6 +245,10 @@ impl Wallet { let mut client = connect_lightwalletd().await?; let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?; log::info!("Tx ID = {}", tx_id); + + for id_note in selected_note.iter() { + self.db.mark_spent(*id_note, 0)?; + } Ok(tx_id) } diff --git a/zec.db-journal b/zec.db-journal deleted file mode 100644 index 52b3747..0000000 Binary files a/zec.db-journal and /dev/null differ