From a4d7213dc816c535b157b8310f7c5ce1c09ead80 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 1 Nov 2019 07:28:06 -0700 Subject: [PATCH 1/7] Add 630000 main checkpoint --- lib/src/lightclient/checkpoints.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/lightclient/checkpoints.rs b/lib/src/lightclient/checkpoints.rs index 61e7e69..3145e0a 100644 --- a/lib/src/lightclient/checkpoints.rs +++ b/lib/src/lightclient/checkpoints.rs @@ -24,7 +24,10 @@ fn get_main_checkpoint(height: u64) -> Option<(u64, &'static str, &'static str) let checkpoints: Vec<(u64, &str, &str)> = vec![ (610000, "000000000218882f481e3b49ca3df819734b8d74aac91f69e848d7499b34b472", "0192943f1eca6525cea7ea8e26b37c792593ed50cfe2be7a1ff551a08dc64b812f001000000001deef7ae5162a9942b4b9aa797137c5bdf60750e9548664127df99d1981dda66901747ad24d5daf294ce2a27aba923e16e52e7348eea3048c5b5654b99ab0a371200149d8aff830305beb3887529f6deb150ab012916c3ce88a6b47b78228f8bfeb3f01ff84a89890cfae65e0852bc44d9aa82be2c5d204f5aebf681c9e966aa46f540e000001d58f1dfaa9db0996996129f8c474acb813bfed452d347fb17ebac2e775e209120000000001319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13" - ) + ), + (630000, "00000000015493abba3e3bb384562f09141548f60581e06d4056993388d2ea2f", + "019b01066bae720ce88b4252c3852b0160ec4c4dcd6110df92e76de5cb23ab2f540109c3001b823fc745328a89a47fc5ace701bbd4dc1e9692e918a125ca48960545100001b2ba91c0f96777e735ded1ba9671003a399d435db3a0746bef3b2c83ba4d953f01d4c31130d2013fb57440d21fba0a8af65e61cd1405a8e2d9b987c02df8fc6514011c44ba36710e293ddf95e6715594daa927883d48cda6a3a5ee4aa3ef141ec55b0001cd9540592d39094703664771e61ce69d5b08539812886e0b9df509c80f938f6601178b3d8f9e7f7af7a1f4a049289195001abd96bb41e15b4010cecc1468af4e4b01ffe988e63aba31819640175d3fbb8c91b3c42d2f5074b4c075411d3a5c28e62801cb2e8d7f7387a9d31ba38697a9564808c9aff7d018a4cbdcd1c635edc3ab3014000001060f0c26ee205d7344bda85024a9f9a3c3022d52ea30dfb6770f4acbe168406d0103a7a58b1d7caef1531d521cc85de6fcb18d3590f31ad4486ca1252dac2c96020001319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13" + ), ]; find_checkpoint(height, checkpoints) From af0e0b9b2bcc131804d5e940f94d45c6fef74481 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 1 Nov 2019 07:31:01 -0700 Subject: [PATCH 2/7] Create .zcash directory if it doesn't exist --- lib/src/lightclient.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 1fd62a1..45de309 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -121,7 +121,14 @@ impl LightClientConfig { }; } - zcash_data_location.into_boxed_path() + // Create directory if it doesn't exist + match std::fs::create_dir_all(zcash_data_location.clone()) { + Ok(_) => zcash_data_location.into_boxed_path(), + Err(e) => { + eprintln!("Couldn't create zcash directory!\n{}", e); + panic!("Couldn't create zcash directory!"); + } + } } pub fn get_wallet_path(&self) -> Box { From 3e3f445ca33d9fc2a59e92cf25f5a64d1bf2ad28 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 8 Nov 2019 10:34:26 -0800 Subject: [PATCH 3/7] Disallow memos to t-addresses --- lib/src/commands.rs | 10 +++++++++- lib/src/lightwallet.rs | 19 ++++++++++++++++++- lib/src/lightwallet/tests.rs | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/lib/src/commands.rs b/lib/src/commands.rs index b6dc870..c052ff4 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use json::{object}; use crate::lightclient::LightClient; +use crate::lightwallet::LightWallet; pub trait Command { fn help(&self) -> String; @@ -487,6 +488,8 @@ impl Command for SendCommand { Err(s) => { return format!("Error: {}\n{}", s, self.help()); } } } else if args.len() == 2 || args.len() == 3 { + let address = args[0].to_string(); + // Make sure we can parse the amount let value = match args[1].parse::() { Ok(amt) => amt, @@ -495,7 +498,12 @@ impl Command for SendCommand { } }; - let memo = if args.len() == 3 { Some(args[2].to_string()) } else {None}; + let memo = if args.len() == 3 { Some(args[2].to_string()) } else { None }; + + // Memo has to be None if not sending to a shileded address + if memo.is_some() && !LightWallet::is_shielded_address(&address, &lightclient.config) { + return format!("Can't send a memo to the non-shielded address {}", address); + } vec![(args[0].to_string(), value, memo)] } else { diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index ec1f91c..809ac21 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -166,6 +166,16 @@ impl LightWallet { (extsk, extfvk, address) } + pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool { + match address::RecipientAddress::from_str(addr, + config.hrp_sapling_address(), + config.base58_pubkey_address(), + config.base58_script_address()) { + Some(address::RecipientAddress::Shielded(_)) => true, + _ => false, + } + } + pub fn new(seed_phrase: Option, config: &LightClientConfig, latest_block: u64) -> io::Result { // This is the source entropy that corresponds to the 24-word seed phrase let mut seed_bytes = [0u8; 32]; @@ -1572,7 +1582,14 @@ impl LightWallet { value: *amt, memo: match maybe_memo { None => Memo::default(), - Some(s) => Memo::from_str(&s).unwrap(), + Some(s) => { + // If the address is not a z-address, then drop the memo + if LightWallet::is_shielded_address(&addr.to_string(), &self.config) { + Memo::from_str(s).unwrap() + } else { + Memo::default() + } + } }, } }).collect::>(); diff --git a/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index ac8b5ff..8a1e9ba 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -922,7 +922,6 @@ fn test_z_spend_to_taddr() { cb3.add_tx(&sent_tx); wallet.scan_block(&cb3.as_bytes()).unwrap(); - // Now this new Spent tx should be in, so the note should be marked confirmed spent { let txs = wallet.txs.read().unwrap(); @@ -950,6 +949,38 @@ fn test_z_spend_to_taddr() { assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); } + + // Create a new Tx, but this time with a memo. + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, Some("T address memo".to_string()))]).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid2 = sent_tx.txid(); + + // There should be a mempool Tx, but the memo should be dropped, because it was sent to a + // t address + { + let txs = wallet.mempool_txs.read().unwrap(); + + assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None); + } + + // Now add the block + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + // Check Outgoing Metadata for t address, but once again there should be no memo + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None); + } } #[test] From 61dc063fe155d1510e9a074e5a3577a0621f8c3a Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 13 Nov 2019 15:05:07 -0800 Subject: [PATCH 4/7] Sync before height command --- lib/src/commands.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/commands.rs b/lib/src/commands.rs index c052ff4..8f4f4f3 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -627,10 +627,13 @@ impl Command for HeightCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - format!("{}", - object! { - "height" => lightclient.last_scanned_height() - }.pretty(2)) + match lightclient.do_sync(true) { + Ok(_) => format!("{}", + object! { + "height" => lightclient.last_scanned_height() + }.pretty(2)), + Err(e) => e + } } } From 96997c5a467b710286bc9c6fea818b9d7d76f254 Mon Sep 17 00:00:00 2001 From: Arjun Date: Fri, 15 Nov 2019 10:30:23 -0800 Subject: [PATCH 5/7] Move logging to lightclient --- cli/src/lib.rs | 54 ++++-------------------------------------- lib/src/commands.rs | 5 +--- lib/src/lightclient.rs | 53 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index a2dec3d..0d44911 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,18 +1,8 @@ -use std::io::{self, Error, ErrorKind}; +use std::io::{self}; use std::sync::Arc; use std::sync::mpsc::{channel, Sender, Receiver}; -use log::{info, error, LevelFilter}; -use log4rs::append::rolling_file::RollingFileAppender; -use log4rs::encode::pattern::PatternEncoder; -use log4rs::config::{Appender, Config, Root}; -use log4rs::filter::threshold::ThresholdFilter; -use log4rs::append::rolling_file::policy::compound::{ - CompoundPolicy, - trigger::size::SizeTrigger, - roll::fixed_window::FixedWindowRoller, -}; - +use log::{info, error}; use zecwalletlitelib::{commands, lightclient::{LightClient, LightClientConfig}, @@ -85,48 +75,11 @@ pub fn report_permission_error() { } } -/// Build the Logging config -pub fn get_log_config(config: &LightClientConfig) -> io::Result { - let window_size = 3; // log0, log1, log2 - let fixed_window_roller = - FixedWindowRoller::builder().build("zecwallet-light-wallet-log{}",window_size).unwrap(); - let size_limit = 5 * 1024 * 1024; // 5MB as max log file size to roll - let size_trigger = SizeTrigger::new(size_limit); - let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller)); - - Config::builder() - .appender( - Appender::builder() - .filter(Box::new(ThresholdFilter::new(LevelFilter::Info))) - .build( - "logfile", - Box::new( - RollingFileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{d} {l}::{m}{n}"))) - .build(config.get_log_path(), Box::new(compound_policy))?, - ), - ), - ) - .build( - Root::builder() - .appender("logfile") - .build(LevelFilter::Debug), - ) - .map_err(|e|Error::new(ErrorKind::Other, format!("{}", e))) -} - - pub fn startup(server: http::Uri, dangerous: bool, seed: Option, birthday: u64, first_sync: bool, print_updates: bool) -> io::Result<(Sender<(String, Vec)>, Receiver)> { // Try to get the configuration let (config, latest_block_height) = LightClientConfig::create(server.clone(), dangerous)?; - // Configure logging first. - let log_config = get_log_config(&config)?; - log4rs::init_config(log_config).map_err(|e| { - std::io::Error::new(ErrorKind::Other, e) - })?; - let lightclient = match seed { Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday)?), None => { @@ -139,6 +92,9 @@ pub fn startup(server: http::Uri, dangerous: bool, seed: Option, birthda } }; + // Initialize logging + lightclient.init_logging()?; + // Print startup Messages info!(""); // Blank line info!("Starting Zecwallet-CLI"); diff --git a/lib/src/commands.rs b/lib/src/commands.rs index 8f4f4f3..2d7bff8 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -628,10 +628,7 @@ impl Command for HeightCommand { fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { match lightclient.do_sync(true) { - Ok(_) => format!("{}", - object! { - "height" => lightclient.last_scanned_height() - }.pretty(2)), + Ok(_) => format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)), Err(e) => e } } diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 45de309..ab5e41a 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -1,6 +1,5 @@ use crate::lightwallet::LightWallet; -use log::{info, warn, error}; use rand::{rngs::OsRng, seq::SliceRandom}; use std::sync::{Arc, RwLock, Mutex}; @@ -20,6 +19,17 @@ use zcash_client_backend::{ constants::testnet, constants::mainnet, constants::regtest, encoding::encode_payment_address, }; +use log::{info, warn, error, LevelFilter}; +use log4rs::append::rolling_file::RollingFileAppender; +use log4rs::encode::pattern::PatternEncoder; +use log4rs::config::{Appender, Config, Root}; +use log4rs::filter::threshold::ThresholdFilter; +use log4rs::append::rolling_file::policy::compound::{ + CompoundPolicy, + trigger::size::SizeTrigger, + roll::fixed_window::FixedWindowRoller, +}; + use crate::grpc_client::{BlockId}; use crate::grpcconnector::{self, *}; use crate::SaplingParams; @@ -100,6 +110,37 @@ impl LightClientConfig { Ok((config, info.block_height)) } + + /// Build the Logging config + pub fn get_log_config(&self) -> io::Result { + let window_size = 3; // log0, log1, log2 + let fixed_window_roller = + FixedWindowRoller::builder().build("zecwallet-light-wallet-log{}",window_size).unwrap(); + let size_limit = 5 * 1024 * 1024; // 5MB as max log file size to roll + let size_trigger = SizeTrigger::new(size_limit); + let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller)); + + Config::builder() + .appender( + Appender::builder() + .filter(Box::new(ThresholdFilter::new(LevelFilter::Info))) + .build( + "logfile", + Box::new( + RollingFileAppender::builder() + .encoder(Box::new(PatternEncoder::new("{d} {l}::{m}{n}"))) + .build(self.get_log_path(), Box::new(compound_policy))?, + ), + ), + ) + .build( + Root::builder() + .appender("logfile") + .build(LevelFilter::Debug), + ) + .map_err(|e|Error::new(ErrorKind::Other, format!("{}", e))) + } + pub fn get_zcash_data_path(&self) -> Box { let mut zcash_data_location; if self.data_dir.is_some() { @@ -361,6 +402,16 @@ impl LightClient { Ok(lc) } + pub fn init_logging(&self) -> io::Result<()> { + // Configure logging first. + let log_config = self.config.get_log_config()?; + log4rs::init_config(log_config).map_err(|e| { + std::io::Error::new(ErrorKind::Other, e) + })?; + + Ok(()) + } + pub fn attempt_recover_seed(config: &LightClientConfig) -> Result { use std::io::prelude::*; use byteorder::{LittleEndian, ReadBytesExt,}; From 165c22e39e5b43d417f8719edcf91b07c15906bf Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Sat, 16 Nov 2019 21:11:41 -0800 Subject: [PATCH 6/7] Add clear command --- cli/src/lib.rs | 2 +- lib/src/commands.rs | 48 ++++++++++++++++++++++++++++++++++++------ lib/src/lightclient.rs | 10 +++++++-- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 0d44911..64b8b69 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -147,7 +147,7 @@ pub fn start_interactive(command_tx: Sender<(String, Vec)>, resp_rx: Rec loop { // Read the height first - let height = json::parse(&send_command("height".to_string(), vec![])).unwrap()["height"].as_i64().unwrap(); + let height = json::parse(&send_command("height".to_string(), vec!["false".to_string()])).unwrap()["height"].as_i64().unwrap(); let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ", chain_name, height)); diff --git a/lib/src/commands.rs b/lib/src/commands.rs index 2d7bff8..31a1392 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -111,6 +111,32 @@ impl Command for RescanCommand { } +struct ClearCommand {} +impl Command for ClearCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Clear the wallet state, rolling back the wallet to an empty state."); + h.push("Usage:"); + h.push("clear"); + h.push(""); + h.push("This command will clear all notes, utxos and transactions from the wallet, setting up the wallet to be synced from scratch."); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Clear the wallet state, rolling back the wallet to an empty state.".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.clear_state(); + + let result = object!{ "result" => "success" }; + + result.pretty(2) + } +} + struct HelpCommand {} impl Command for HelpCommand { fn help(&self) -> String { @@ -614,10 +640,11 @@ struct HeightCommand {} impl Command for HeightCommand { fn help(&self) -> String { let mut h = vec![]; - h.push("Get the latest block height that the wallet is at"); + h.push("Get the latest block height that the wallet is at."); h.push("Usage:"); - h.push("height"); + h.push("height [do_sync = true | false]"); h.push(""); + h.push("Pass 'true' (default) to sync to the server to get the latest block height. Pass 'false' to get the latest height in the wallet without checking with the server."); h.join("\n") } @@ -626,10 +653,18 @@ impl Command for HeightCommand { "Get the latest block height that the wallet is at".to_string() } - fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - match lightclient.do_sync(true) { - Ok(_) => format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)), - Err(e) => e + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() > 1 { + return format!("Didn't understand arguments\n{}", self.help()); + } + + if args.len() == 1 && args[0].trim() == "true" { + match lightclient.do_sync(true) { + Ok(_) => format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)), + Err(e) => e + } + } else { + format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)) } } } @@ -761,6 +796,7 @@ pub fn get_commands() -> Box>> { map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{})); map.insert("encryptionstatus".to_string(), Box::new(EncryptionStatusCommand{})); map.insert("rescan".to_string(), Box::new(RescanCommand{})); + map.insert("clear".to_string(), Box::new(ClearCommand{})); map.insert("help".to_string(), Box::new(HelpCommand{})); map.insert("balance".to_string(), Box::new(BalanceCommand{})); map.insert("addresses".to_string(), Box::new(AddressCommand{})); diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index ab5e41a..f0b36d1 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -842,14 +842,20 @@ impl LightClient { Ok(array![new_address]) } - pub fn do_rescan(&self) -> Result { - info!("Rescan starting"); + pub fn clear_state(&self) { // First, clear the state from the wallet self.wallet.read().unwrap().clear_blocks(); // Then set the initial block self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday()); + info!("Cleared wallet state"); + } + + pub fn do_rescan(&self) -> Result { + info!("Rescan starting"); + self.clear_state(); + // Then, do a sync, which will force a full rescan from the initial state let response = self.do_sync(true); info!("Rescan finished"); From 89fbe097da8cc8684791fd55faa1816cb5a21bd1 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Sun, 17 Nov 2019 20:57:12 -0800 Subject: [PATCH 7/7] Add empty addresses when existing ones get used --- lib/src/lightclient.rs | 4 ++ lib/src/lightwallet.rs | 39 ++++++++++++++++++- lib/src/lightwallet/tests.rs | 74 ++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index f0b36d1..3089437 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -852,6 +852,10 @@ impl LightClient { } pub fn do_rescan(&self) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + warn!("Wallet is locked, new HD addresses won't be added!"); + } + info!("Rescan starting"); self.clear_state(); diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 809ac21..9cb1dbd 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -879,6 +879,32 @@ impl LightWallet { } } + // If the last taddress was used, ensure we add the next HD taddress to the wallet. + pub fn ensure_hd_taddresses(&self, address: &String) { + let last_address = { + self.taddresses.read().unwrap().last().unwrap().clone() + }; + + if *last_address == *address { + // If the wallet is locked, this is a no-op. That is fine, since we really + // need to only add new addresses when restoring a new wallet, when it will not be locked. + // Also, if it is locked, the user can't create new addresses anyway. + self.add_taddr(); + } + } + + // If the last zaddress was used, ensure we add the next HD zaddress to the wallet + pub fn ensure_hd_zaddresses(&self, address: &String) { + let last_address = encode_payment_address(self.config.hrp_sapling_address(), self.zaddress.read().unwrap().last().unwrap()); + + if last_address == *address { + // If the wallet is locked, this is a no-op. That is fine, since we really + // need to only add new addresses when restoring a new wallet, when it will not be locked. + // Also, if it is locked, the user can't create new addresses anyway. + self.add_zaddr(); + } + } + // Scan the full Tx and update memos for incoming shielded transactions. pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) { let mut total_transparent_spend: u64 = 0; @@ -934,6 +960,9 @@ impl LightWallet { if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) { // This is our address. Add this as an output to the txid self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64); + + // Ensure that we add any new HD addresses + self.ensure_hd_taddresses(&address); } }, _ => {} @@ -1303,9 +1332,15 @@ impl LightWallet { // Save notes. for output in tx.shielded_outputs { - info!("Received sapling output"); - let new_note = SaplingNoteData::new(&self.extfvks.read().unwrap()[output.account], output); + match LightWallet::note_address(self.config.hrp_sapling_address(), &new_note) { + Some(a) => { + info!("Received sapling output to {}", a); + self.ensure_hd_zaddresses(&a); + }, + None => {} + } + match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) { None => tx_entry.notes.push(new_note), Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid) diff --git a/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index 8a1e9ba..65a22c6 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -845,7 +845,7 @@ fn test_multi_z() { assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[change_note_number].memo), None); assert_eq!(txs[&sent_txid].notes[ext_note_number].note.value, AMOUNT_SENT); - assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 1); + assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 2); assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false); assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, None); assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None); @@ -1085,7 +1085,7 @@ fn test_t_spend_to_z() { } } - #[test] +#[test] fn test_z_incoming_memo() { const AMOUNT1: u64 = 50000; let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1); @@ -1125,7 +1125,51 @@ fn test_z_incoming_memo() { } } - #[test] +#[test] +fn test_add_new_zt_hd_after_incoming() { + // When an address recieves funds, a new, unused address should automatically get added + const AMOUNT1: u64 = 50000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1); + + // Get the last address + let my_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &wallet.extfvks.read().unwrap().last().unwrap().default_address().unwrap().1); + + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + assert_eq!(wallet.zaddress.read().unwrap().len(), 2); // Starts with 2 addresses + + // Create a tx and send to address + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&my_address, AMOUNT1 - fee, None)]).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + + // Add it to a block + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + assert_eq!(wallet.zaddress.read().unwrap().len(), 3); // Now has a new address + + + let mut rng = OsRng; + let secp = Secp256k1::new(); + // Send a fake transaction to the last taddr + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap().last().unwrap()); + + assert_eq!(wallet.taddresses.read().unwrap().len(), 1); // Start with 1 taddr + + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, AMOUNT1); + + wallet.scan_full_tx(&tx.get_tx(), 3, 0); + assert_eq!(wallet.taddresses.read().unwrap().len(), 2); // Now there should be 2 addrs +} + +#[test] fn test_z_to_t_withinwallet() { const AMOUNT: u64 = 500000; const AMOUNT_SENT: u64 = 20000; @@ -1182,7 +1226,7 @@ fn test_z_to_t_withinwallet() { } } - #[test] +#[test] fn test_multi_t() { const AMOUNT: u64 = 5000000; const AMOUNT_SENT1: u64 = 20000; @@ -1368,7 +1412,7 @@ fn test_multi_spends() { // Find zaddr2 let zaddr2_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap(); - assert_eq!(zaddr2_note.account, 2-1); + assert_eq!(zaddr2_note.account, 2); assert_eq!(zaddr2_note.is_change, false); assert_eq!(zaddr2_note.spent, None); assert_eq!(zaddr2_note.unconfirmed_spent, None); @@ -1376,7 +1420,7 @@ fn test_multi_spends() { // Find zaddr3 let zaddr3_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT3).unwrap(); - assert_eq!(zaddr3_note.account, 3-1); + assert_eq!(zaddr3_note.account, 3); assert_eq!(zaddr3_note.is_change, false); assert_eq!(zaddr3_note.spent, None); assert_eq!(zaddr3_note.unconfirmed_spent, None); @@ -1737,8 +1781,8 @@ fn test_lock_unlock() { // Add some addresses let zaddr0 = encode_payment_address(config.hrp_sapling_address(), &wallet.extfvks.read().unwrap()[0].default_address().unwrap().1); - let zaddr1 = wallet.add_zaddr(); - let zaddr2 = wallet.add_zaddr(); + let zaddr1 = wallet.add_zaddr(); // This is actually address at index 2 + let zaddr2 = wallet.add_zaddr(); // This is actually address at index 3 let taddr0 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); let taddr1 = wallet.add_taddr(); @@ -1769,15 +1813,15 @@ fn test_lock_unlock() { { let extsks = wallet.extsks.read().unwrap(); let tkeys = wallet.tkeys.read().unwrap(); - assert_eq!(extsks.len(), 3); + assert_eq!(extsks.len(), 4); // 3 zaddrs + 1 added originally in get_test_wallet() assert_eq!(tkeys.len(), 3); assert_eq!(zaddr0, encode_payment_address(config.hrp_sapling_address(), &ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1)); assert_eq!(zaddr1, encode_payment_address(config.hrp_sapling_address(), - &ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1)); - assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(), &ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1)); + assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[3]).default_address().unwrap().1)); assert_eq!(taddr0, wallet.address_from_sk(&tkeys[0])); assert_eq!(taddr1, wallet.address_from_sk(&tkeys[1])); @@ -1805,15 +1849,15 @@ fn test_lock_unlock() { { let extsks = wallet2.extsks.read().unwrap(); let tkeys = wallet2.tkeys.read().unwrap(); - assert_eq!(extsks.len(), 3); + assert_eq!(extsks.len(), 4); assert_eq!(tkeys.len(), 3); assert_eq!(zaddr0, encode_payment_address(wallet2.config.hrp_sapling_address(), &ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1)); assert_eq!(zaddr1, encode_payment_address(wallet2.config.hrp_sapling_address(), - &ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1)); - assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(), &ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1)); + assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[3]).default_address().unwrap().1)); assert_eq!(taddr0, wallet2.address_from_sk(&tkeys[0])); assert_eq!(taddr1, wallet2.address_from_sk(&tkeys[1])); @@ -1996,7 +2040,7 @@ fn test_encrypted_zreceive() { // Find zaddr2 let zaddr2_note = txs[&txid2].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap(); - assert_eq!(zaddr2_note.account, 1); + assert_eq!(zaddr2_note.account, 2); assert_eq!(zaddr2_note.is_change, false); assert_eq!(zaddr2_note.spent, None); assert_eq!(zaddr2_note.unconfirmed_spent, None);