From 17bbb71a698ad9bdf927a7211618ef7810f816a6 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 24 Oct 2019 17:11:56 -0700 Subject: [PATCH 1/2] Add sync status command --- lib/src/commands.rs | 29 +++++++++++++++++++++++ lib/src/lightclient.rs | 54 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/lib/src/commands.rs b/lib/src/commands.rs index eb0f270..b25f369 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -32,6 +32,34 @@ impl Command for SyncCommand { } } + +struct SyncStatusCommand {} +impl Command for SyncStatusCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get the sync status of the wallet"); + h.push("Usage:"); + h.push("syncstatus"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the sync status of the wallet".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + let status = lightclient.do_scan_status(); + match status.is_syncing { + false => object!{ "syncing" => "false" }, + true => object!{ "syncing" => "true", + "synced_blocks" => status.synced_blocks, + "total_blocks" => status.total_blocks } + }.pretty(2) + } +} + struct RescanCommand {} impl Command for RescanCommand { fn help(&self) -> String { @@ -691,6 +719,7 @@ pub fn get_commands() -> Box>> { let mut map: HashMap> = HashMap::new(); map.insert("sync".to_string(), Box::new(SyncCommand{})); + map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{})); map.insert("rescan".to_string(), Box::new(RescanCommand{})); map.insert("help".to_string(), Box::new(HelpCommand{})); map.insert("balance".to_string(), Box::new(BalanceCommand{})); diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 0be799d..04d3364 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -31,6 +31,22 @@ pub const DEFAULT_SERVER: &str = "https://lightd-main.zecwallet.co:443"; pub const WALLET_NAME: &str = "zecwallet-light-wallet.dat"; pub const LOGFILE_NAME: &str = "zecwallet-light-wallet.debug.log"; +#[derive(Clone, Debug)] +pub struct WalletStatus { + pub is_syncing: bool, + pub total_blocks: u64, + pub synced_blocks: u64, +} + +impl WalletStatus { + pub fn new() -> Self { + WalletStatus { + is_syncing: false, + total_blocks: 0, + synced_blocks: 0 + } + } +} #[derive(Clone, Debug)] pub struct LightClientConfig { @@ -203,6 +219,7 @@ pub struct LightClient { pub sapling_spend : Vec, sync_lock : Mutex<()>, + sync_status : Arc>, // The current syncing status of the Wallet. } impl LightClient { @@ -235,6 +252,7 @@ impl LightClient { sapling_output : vec![], sapling_spend : vec![], sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), }; l.set_wallet_initial_state(0); @@ -260,6 +278,7 @@ impl LightClient { sapling_output : vec![], sapling_spend : vec![], sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), }; l.set_wallet_initial_state(latest_block); @@ -283,6 +302,7 @@ impl LightClient { sapling_output : vec![], sapling_spend : vec![], sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), }; println!("Setting birthday to {}", birthday); @@ -310,6 +330,7 @@ impl LightClient { sapling_output : vec![], sapling_spend : vec![], sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), }; lc.read_sapling_params(); @@ -735,6 +756,11 @@ impl LightClient { response } + /// Return the syncing status of the wallet + pub fn do_scan_status(&self) -> WalletStatus { + self.sync_status.read().unwrap().clone() + } + pub fn do_sync(&self, print_updates: bool) -> String { // We can only do one sync at a time because we sync blocks in serial order // If we allow multiple syncs, they'll all get jumbled up. @@ -750,10 +776,12 @@ impl LightClient { // This will hold the latest block fetched from the RPC let latest_block_height = Arc::new(AtomicU64::new(0)); let lbh = latest_block_height.clone(); - fetch_latest_block(&self.get_server_uri(), self.config.no_cert_verification, move |block: BlockId| { + fetch_latest_block(&self.get_server_uri(), self.config.no_cert_verification, + move |block: BlockId| { lbh.store(block.height, Ordering::SeqCst); }); let latest_block = latest_block_height.load(Ordering::SeqCst); + if latest_block < last_scanned_height { let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height); @@ -772,6 +800,13 @@ impl LightClient { return "".to_string(); } + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = true; + status.synced_blocks = last_scanned_height; + status.total_blocks = latest_block; + } + // Count how many bytes we've downloaded let bytes_downloaded = Arc::new(AtomicUsize::new(0)); @@ -795,11 +830,18 @@ impl LightClient { info!("Start height is {}", start_height); // Show updates only if we're syncing a lot of blocks - if print_updates && end_height - start_height > 100 { + if print_updates && (latest_block - start_height) > 100 { print!("Syncing {}/{}\r", start_height, latest_block); io::stdout().flush().ok().expect("Could not flush stdout"); } + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = true; + status.synced_blocks = start_height; + status.total_blocks = latest_block; + } + // Fetch compact blocks info!("Fetching blocks {}-{}", start_height, end_height); @@ -909,7 +951,13 @@ impl LightClient { info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024); responses.push(format!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024)); - + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = false; + status.synced_blocks = latest_block; + status.total_blocks = latest_block; + } + // Get the Raw transaction for all the wallet transactions // We need to first copy over the Txids from the wallet struct, because From 865a72442db4ee1862fa776514da4a4a1c935b94 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 24 Oct 2019 18:26:25 -0700 Subject: [PATCH 2/2] Add syncstatus command --- cli/src/main.rs | 13 +++++-- lib/src/commands.rs | 77 ++++++++++++++++++++++++------------------ lib/src/lightclient.rs | 19 ++++++----- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 48574ef..78fa96e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -221,7 +221,12 @@ fn startup(server: http::Uri, dangerous: bool, seed: Option, birthday: u if first_sync { let update = lightclient.do_sync(true); if print_updates { - println!("{}", update); + match update { + Ok(j) => { + println!("{}", j.pretty(2)); + }, + Err(e) => println!("{}", e) + } } } @@ -329,7 +334,11 @@ fn command_loop(lightclient: Arc) -> (Sender<(String, Vec)> Err(_) => { // Timeout. Do a sync to keep the wallet up-to-date. False to whether to print updates on the console info!("Timeout, doing a sync"); - lc.do_sync(false); + match lc.do_sync(false) { + Ok(_) => {}, + Err(e) => {error!("{}", e)} + } + } } } diff --git a/lib/src/commands.rs b/lib/src/commands.rs index b25f369..6f24e40 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -28,7 +28,10 @@ impl Command for SyncCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_sync(true) + match lightclient.do_sync(true) { + Ok(j) => j.pretty(2), + Err(e) => e + } } } @@ -79,7 +82,10 @@ impl Command for RescanCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_rescan() + match lightclient.do_rescan() { + Ok(j) => j.pretty(2), + Err(e) => e + } } } @@ -144,9 +150,7 @@ impl Command for InfoCommand { "Get the lightwalletd server's info".to_string() } - fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_sync(true); - + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { lightclient.do_info() } } @@ -169,9 +173,10 @@ impl Command for BalanceCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_sync(true); - - format!("{}", lightclient.do_balance().pretty(2)) + match lightclient.do_sync(true) { + Ok(_) => format!("{}", lightclient.do_balance().pretty(2)), + Err(e) => e + } } } @@ -434,7 +439,7 @@ impl Command for SendCommand { } // Check for a single argument that can be parsed as JSON - if args.len() == 1 { + let send_args = if args.len() == 1 { // Sometimes on the command line, people use "'" for the quotes, which json::parse doesn't // understand. So replace it with double-quotes let arg_list = args[0].replace("'", "\""); @@ -455,20 +460,14 @@ impl Command for SendCommand { if !j.has_key("address") || !j.has_key("amount") { Err(format!("Need 'address' and 'amount'\n")) } else { - Ok((j["address"].as_str().unwrap(), j["amount"].as_u64().unwrap(), j["memo"].as_str().map(|s| s.to_string()))) + Ok((j["address"].as_str().unwrap().to_string().clone(), j["amount"].as_u64().unwrap(), j["memo"].as_str().map(|s| s.to_string().clone()))) } - }).collect::)>, String>>(); + }).collect::)>, String>>(); - let send_args = match maybe_send_args { - Ok(a) => a, + match maybe_send_args { + Ok(a) => a.clone(), Err(s) => { return format!("Error: {}\n{}", s, self.help()); } - }; - - lightclient.do_sync(true); - match lightclient.do_send(send_args) { - Ok(txid) => { object!{ "txid" => txid } }, - Err(e) => { object!{ "error" => e } } - }.pretty(2) + } } else if args.len() == 2 || args.len() == 3 { // Make sure we can parse the amount let value = match args[1].parse::() { @@ -480,13 +479,21 @@ impl Command for SendCommand { let memo = if args.len() == 3 { Some(args[2].to_string()) } else {None}; - lightclient.do_sync(true); - match lightclient.do_send(vec!((args[0], value, memo))) { - Ok(txid) => { object!{ "txid" => txid } }, - Err(e) => { object!{ "error" => e } } - }.pretty(2) + vec![(args[0].to_string(), value, memo)] } else { - self.help() + return self.help() + }; + + match lightclient.do_sync(true) { + Ok(_) => { + // Convert to the right format. String -> &str. + let tos = send_args.iter().map(|(a, v, m)| (a.as_str(), *v, m.clone()) ).collect::>(); + match lightclient.do_send(tos) { + Ok(txid) => { object!{ "txid" => txid } }, + Err(e) => { object!{ "error" => e } } + }.pretty(2) + }, + Err(e) => e } } } @@ -568,9 +575,12 @@ impl Command for TransactionsCommand { } fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { - lightclient.do_sync(true); - - format!("{}", lightclient.do_list_transactions().pretty(2)) + match lightclient.do_sync(true) { + Ok(_) => { + format!("{}", lightclient.do_list_transactions().pretty(2)) + }, + Err(e) => e + } } } @@ -662,9 +672,12 @@ impl Command for NotesCommand { false }; - lightclient.do_sync(true); - - format!("{}", lightclient.do_list_notes(all_notes).pretty(2)) + match lightclient.do_sync(true) { + Ok(_) => { + format!("{}", lightclient.do_list_notes(all_notes).pretty(2)) + }, + Err(e) => e + } } } diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 04d3364..f2f659c 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -741,7 +741,7 @@ impl LightClient { Ok(array![new_address]) } - pub fn do_rescan(&self) -> String { + pub fn do_rescan(&self) -> Result { info!("Rescan starting"); // First, clear the state from the wallet self.wallet.read().unwrap().clear_blocks(); @@ -761,7 +761,7 @@ impl LightClient { self.sync_status.read().unwrap().clone() } - pub fn do_sync(&self, print_updates: bool) -> String { + pub fn do_sync(&self, print_updates: bool) -> Result { // We can only do one sync at a time because we sync blocks in serial order // If we allow multiple syncs, they'll all get jumbled up. let _lock = self.sync_lock.lock().unwrap(); @@ -786,7 +786,7 @@ impl LightClient { if latest_block < last_scanned_height { let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height); warn!("{}", w); - return w; + return Err(w); } info!("Latest block is {}", latest_block); @@ -797,7 +797,7 @@ impl LightClient { // If there's nothing to scan, just return if last_scanned_height == latest_block { info!("Nothing to sync, returning"); - return "".to_string(); + return Ok(object!{ "result" => "success" }) } { @@ -893,7 +893,7 @@ impl LightClient { // Make sure we're not re-orging too much! if total_reorg > (crate::lightwallet::MAX_REORG - 1) as u64 { error!("Reorg has now exceeded {} blocks!", crate::lightwallet::MAX_REORG); - return format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG); + return Err(format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG)); } if invalid_height > 0 { @@ -947,10 +947,7 @@ impl LightClient { println!(""); // New line to finish up the updates } - let mut responses = vec![]; - info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024); - responses.push(format!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024)); { let mut status = self.sync_status.write().unwrap(); status.is_syncing = false; @@ -988,7 +985,11 @@ impl LightClient { }); }; - responses.join("\n") + Ok(object!{ + "result" => "success", + "latest_block" => latest_block, + "downloaded_bytes" => bytes_downloaded.load(Ordering::SeqCst) + }) } pub fn do_send(&self, addrs: Vec<(&str, u64, Option)>) -> Result {