diff --git a/src/commands.rs b/src/commands.rs index 61ce69f..8e4df5a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -283,7 +283,7 @@ impl Command for SeedCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_seed_phrase() + format!("{}", lightclient.do_seed_phrase().pretty(2)) } } diff --git a/src/lightclient.rs b/src/lightclient.rs index 2bf96ca..663f7b3 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -183,7 +183,7 @@ impl LightClient { }; } - pub fn new(seed_phrase: Option, config: &LightClientConfig) -> io::Result { + pub fn new(seed_phrase: Option, config: &LightClientConfig, latest_block: u64) -> io::Result { let mut lc = if config.get_wallet_path().exists() { // Make sure that if a wallet exists, there is no seed phrase being attempted if !seed_phrase.is_none() { @@ -202,7 +202,7 @@ impl LightClient { } } else { let l = LightClient { - wallet : Arc::new(LightWallet::new(seed_phrase, config)?), + wallet : Arc::new(LightWallet::new(seed_phrase, config, latest_block)?), config : config.clone(), sapling_output : vec![], sapling_spend : vec![] @@ -213,6 +213,7 @@ impl LightClient { l }; + info!("Read wallet with birthday {}", lc.wallet.get_first_tx_block()); // Read Sapling Params let mut f = match File::open(config.get_params_path("sapling-output.params")) { @@ -368,8 +369,11 @@ impl LightClient { format!("{:?}", LightClient::get_info(uri)) } - pub fn do_seed_phrase(&self) -> String { - self.wallet.get_seed_phrase() + pub fn do_seed_phrase(&self) -> JsonValue { + object!{ + "seed" => self.wallet.get_seed_phrase(), + "birthday" => self.wallet.get_birthday() + } } // Return a list of all notes, spent and unspent diff --git a/src/lightwallet/mod.rs b/src/lightwallet/mod.rs index ce22a36..dfe440d 100644 --- a/src/lightwallet/mod.rs +++ b/src/lightwallet/mod.rs @@ -121,6 +121,10 @@ pub struct LightWallet { blocks: Arc>>, pub txs: Arc>>, + // The block at which this wallet was born. Rescans + // will start from here. + birthday: u64, + // Non-serialized fields config: LightClientConfig, } @@ -146,7 +150,7 @@ impl LightWallet { (extsk, extfvk, address) } - pub fn new(seed_phrase: Option, config: &LightClientConfig) -> io::Result { + pub fn new(seed_phrase: Option, config: &LightClientConfig, latest_block: u64) -> io::Result { use rand::{FromEntropy, ChaChaRng, Rng}; // This is the source entropy that corresponds to the 24-word seed phrase @@ -181,14 +185,15 @@ impl LightWallet { let (extsk, extfvk, address) = LightWallet::get_pk_from_bip39seed(&bip39_seed.as_bytes()); Ok(LightWallet { - seed: seed_bytes, - extsks: vec![extsk], - extfvks: vec![extfvk], - address: vec![address], - tkeys: vec![tpk], - blocks: Arc::new(RwLock::new(vec![])), - txs: Arc::new(RwLock::new(HashMap::new())), - config: config.clone(), + seed: seed_bytes, + extsks: vec![extsk], + extfvks: vec![extfvk], + address: vec![address], + tkeys: vec![tpk], + blocks: Arc::new(RwLock::new(vec![])), + txs: Arc::new(RwLock::new(HashMap::new())), + config: config.clone(), + birthday: latest_block, }) } @@ -236,15 +241,22 @@ impl LightWallet { } } + let birthday = if version >= 2 { + reader.read_u64::()? + } else { + 0 + }; + Ok(LightWallet{ seed: seed_bytes, - extsks: extsks, - extfvks: extfvks, + extsks, + extfvks, address: addresses, tkeys: vec![tpk], blocks: Arc::new(RwLock::new(blocks)), txs: Arc::new(RwLock::new(txs)), config: config.clone(), + birthday, }) } @@ -274,6 +286,10 @@ impl LightWallet { })?; utils::write_string(&mut writer, &self.config.chain_name)?; + // While writing the birthday, be sure that we're right, and that we don't + // have a tx that is before the current birthday + writer.write_u64::(self.get_birthday())?; + Ok(()) } @@ -284,6 +300,24 @@ impl LightWallet { } } + pub fn get_birthday(&self) -> u64 { + cmp::min(self.get_first_tx_block(), self.birthday) + } + + // Get the first block that this wallet has a tx in. This is often used as the wallet's "birthday" + // If there are no Txns, then the actual birthday (which is recorder at wallet creation) is returned + // If no birthday was recorded, return the sapling activation height + pub fn get_first_tx_block(&self) -> u64 { + // Find the first transaction + let mut blocks = self.txs.read().unwrap().values() + .map(|wtx| wtx.block as u64) + .collect::>(); + blocks.sort(); + + *blocks.first() // Returns optional + .unwrap_or(&cmp::max(self.birthday, self.config.sapling_activation_height)) + } + // Get all z-address private keys. Returns a Vector of (address, privatekey) pub fn get_z_private_keys(&self) -> Vec<(String, String)> { self.extsks.iter().map(|sk| { diff --git a/src/main.rs b/src/main.rs index a6783c6..dcae363 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,12 +58,7 @@ pub fn main() { consensus_branch_id : info.consensus_branch_id, }; - let lightclient = match LightClient::new(seed, &config) { - Ok(lc) => Arc::new(lc), - Err(e) => { eprintln!("Failed to start wallet. Error was:\n{}", e); return; } - }; - - // Configure logging first. + // Configure logging first. let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{l} -{d(%Y-%m-%d %H:%M:%S)}- {m}\n"))) .build(config.get_log_path()).unwrap(); @@ -80,6 +75,12 @@ pub fn main() { info!("Starting ZecLite CLI"); info!("Light Client config {:?}", config); + let lightclient = match LightClient::new(seed, &config, info.block_height) { + Ok(lc) => Arc::new(lc), + Err(e) => { eprintln!("Failed to start wallet. Error was:\n{}", e); return; } + }; + + // At startup, run a sync let sync_update = lightclient.do_sync(true); println!("{}", sync_update);