From 6033c531bd317609ecfb757a40b0d907529040aa Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 10:51:14 -0700 Subject: [PATCH 1/9] Allow self signed certs --- Cargo.toml | 1 + src/commands.rs | 2 +- src/grpcconnector.rs | 60 +++++++++++++++++++++++++++++++------------- src/lightclient.rs | 16 ++++++------ src/main.rs | 10 +++++++- 5 files changed, 62 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d35a981..80151bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ ring = "0.14.0" lazy_static = "1.2.0" tower-service = "0.2" tokio-rustls = "0.10.0-alpha.3" +rustls = { version = "0.15.2", features = ["dangerous_configuration"] } webpki = "0.19.1" webpki-roots = "0.16.0" tower-h2 = { git = "https://github.com/tower-rs/tower-h2" } diff --git a/src/commands.rs b/src/commands.rs index ed087a9..4c090a7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -118,7 +118,7 @@ impl Command for InfoCommand { fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { lightclient.do_sync(true); - LightClient::do_info(lightclient.get_server_uri()) + lightclient.do_info() } } diff --git a/src/grpcconnector.rs b/src/grpcconnector.rs index 073cda4..8052ad1 100644 --- a/src/grpcconnector.rs +++ b/src/grpcconnector.rs @@ -24,11 +24,28 @@ use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo}; use crate::grpc_client::client::CompactTxStreamer; +mod danger { + use rustls; + use webpki; + + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert(&self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8]) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} /// A Secure (https) grpc destination. struct Dst { - addr: SocketAddr, - host: String, + addr: SocketAddr, + host: String, + no_cert: bool, } impl tower_service::Service<()> for Dst { @@ -43,15 +60,24 @@ impl tower_service::Service<()> for Dst { fn call(&mut self, _: ()) -> Self::Future { let mut config = ClientConfig::new(); + config.alpn_protocols.push(b"h2".to_vec()); config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + + if self.no_cert { + config.dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + } let config = Arc::new(config); let tls_connector = TlsConnector::from(config); let addr_string_local = self.host.clone(); - let domain = webpki::DNSNameRef::try_from_ascii_str(&addr_string_local).unwrap(); + let domain = match webpki::DNSNameRef::try_from_ascii_str(&addr_string_local) { + Ok(d) => d, + Err(_) => webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap() + }; let domain_local = domain.to_owned(); let stream = TcpStream::connect(&self.addr).and_then(move |sock| { @@ -92,7 +118,7 @@ impl tower_service::Service<()> for Dst { macro_rules! make_grpc_client { - ($protocol:expr, $host:expr, $port:expr) => {{ + ($protocol:expr, $host:expr, $port:expr, $nocert:expr) => {{ let uri: http::Uri = format!("{}://{}", $protocol, $host).parse().unwrap(); let addr = format!("{}:{}", $host, $port) @@ -102,7 +128,7 @@ macro_rules! make_grpc_client { .unwrap(); let h2_settings = Default::default(); - let mut make_client = tower_h2::client::Connect::new(Dst {addr, host: $host.to_string()}, h2_settings, DefaultExecutor::current()); + let mut make_client = tower_h2::client::Connect::new(Dst {addr, host: $host.to_string(), no_cert: $nocert}, h2_settings, DefaultExecutor::current()); make_client .make_service(()) @@ -126,8 +152,8 @@ macro_rules! make_grpc_client { // GRPC code // ============== -pub fn get_info(uri: http::Uri) -> Result { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) +pub fn get_info(uri: http::Uri, no_cert: bool) -> Result { + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { client.get_lightd_info(Request::new(Empty{})) .map_err(|e| { @@ -145,9 +171,9 @@ pub fn get_info(uri: http::Uri) -> Result { } -pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, c: F) +pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, c: F) where F : Fn(&[u8]) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let bs = BlockId{ height: start_height, hash: vec!()}; let be = BlockId{ height: end_height, hash: vec!()}; @@ -183,9 +209,9 @@ pub fn fetch_blocks(uri: &http::Uri, start_heig } pub fn fetch_transparent_txids(uri: &http::Uri, address: String, - start_height: u64, end_height: u64,c: F) + start_height: u64, end_height: u64, no_cert: bool, c: F) where F : Fn(&[u8], u64) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let start = Some(BlockId{ height: start_height, hash: vec!()}); let end = Some(BlockId{ height: end_height, hash: vec!()}); @@ -218,9 +244,9 @@ pub fn fetch_transparent_txids(uri: &http::Uri, }; } -pub fn fetch_full_tx(uri: &http::Uri, txid: TxId, c: F) +pub fn fetch_full_tx(uri: &http::Uri, txid: TxId, no_cert: bool, c: F) where F : Fn(&[u8]) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let txfilter = TxFilter { block: None, index: 0, hash: txid.0.to_vec() }; client.get_transaction(Request::new(txfilter)) @@ -244,8 +270,8 @@ pub fn fetch_full_tx(uri: &http::Uri, txid: TxI }; } -pub fn broadcast_raw_tx(uri: &http::Uri, tx_bytes: Box<[u8]>) -> Result { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) +pub fn broadcast_raw_tx(uri: &http::Uri, no_cert: bool, tx_bytes: Box<[u8]>) -> Result { + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { client.send_transaction(Request::new(RawTransaction {data: tx_bytes.to_vec(), height: 0})) .map_err(|e| { @@ -265,9 +291,9 @@ pub fn broadcast_raw_tx(uri: &http::Uri, tx_bytes: Box<[u8]>) -> Result(uri: &http::Uri, mut c : F) +pub fn fetch_latest_block(uri: &http::Uri, no_cert: bool, mut c : F) where F : FnMut(BlockId) { - let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap()) + let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(|mut client| { client.get_latest_block(Request::new(ChainSpec {})) .map_err(|e| { format!("ERR = {:?}", e) }) diff --git a/src/lightclient.rs b/src/lightclient.rs index 0aa82c2..c432bfe 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -33,6 +33,7 @@ pub struct LightClientConfig { pub sapling_activation_height : u64, pub consensus_branch_id : String, pub anchor_offset : u32, + pub no_cert_verification : bool, } impl LightClientConfig { @@ -323,9 +324,8 @@ impl LightClient { self.config.server.clone() } - pub fn do_info(uri: http::Uri) -> String { - let r = get_info(uri); - match r { + pub fn do_info(&self) -> String { + match get_info(self.get_server_uri(), self.config.no_cert_verification) { Ok(i) => format!("{:?}", i)[11..].to_string(), Err(e) => e } @@ -562,7 +562,7 @@ 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(), 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); @@ -602,7 +602,7 @@ impl LightClient { let last_invalid_height = Arc::new(AtomicI32::new(0)); let last_invalid_height_inner = last_invalid_height.clone(); - fetch_blocks(&self.get_server_uri(), start_height, end_height, + fetch_blocks(&self.get_server_uri(), start_height, end_height, self.config.no_cert_verification, move |encoded_block: &[u8]| { // Process the block only if there were no previous errors if last_invalid_height_inner.load(Ordering::SeqCst) > 0 { @@ -652,7 +652,7 @@ impl LightClient { // TODO: Use for all t addresses let address = self.wallet.address_from_sk(&self.wallet.tkeys.read().unwrap()[0]); let wallet = self.wallet.clone(); - fetch_transparent_txids(&self.get_server_uri(), address, start_height, end_height, + fetch_transparent_txids(&self.get_server_uri(), address, start_height, end_height, self.config.no_cert_verification, move |tx_bytes: &[u8], height: u64 | { let tx = Transaction::read(tx_bytes).unwrap(); @@ -697,7 +697,7 @@ impl LightClient { info!("Fetching full Tx: {}", txid); responses.push(format!("Fetching full Tx: {}", txid)); - fetch_full_tx(&self.get_server_uri(), txid, move |tx_bytes: &[u8] | { + fetch_full_tx(&self.get_server_uri(), txid, self.config.no_cert_verification, move |tx_bytes: &[u8] | { let tx = Transaction::read(tx_bytes).unwrap(); light_wallet_clone.scan_full_tx(&tx, height); @@ -716,7 +716,7 @@ impl LightClient { ); match rawtx { - Ok(txbytes) => match broadcast_raw_tx(&self.get_server_uri(), txbytes) { + Ok(txbytes) => match broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes) { Ok(k) => k, Err(e) => e, }, diff --git a/src/main.rs b/src/main.rs index 0e96dbb..b41de5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,10 @@ pub fn main() { .help("Lightwalletd server to connect to.") .takes_value(true) .default_value(lightclient::DEFAULT_SERVER)) + .arg(Arg::with_name("dangerous") + .long("dangerous") + .help("Disable server TLS certificate verification. Use this if you're running a local lightwalletd with a self-signed certificate. WARNING: This is dangerous, don't use it with a server that is not your own.") + .takes_value(false)) .arg(Arg::with_name("nosync") .help("By default, zecwallet-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.") .long("nosync") @@ -114,8 +118,10 @@ pub fn main() { return; } + let dangerous = matches.is_present("dangerous"); + // Do a getinfo first, before opening the wallet - let info = match grpcconnector::get_info(server.clone()) { + let info = match grpcconnector::get_info(server.clone(), dangerous) { Ok(ld) => ld, Err(e) => { eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e); @@ -123,6 +129,7 @@ pub fn main() { } }; + // Create a Light Client Config let config = lightclient::LightClientConfig { server : server.clone(), @@ -130,6 +137,7 @@ pub fn main() { sapling_activation_height : info.sapling_activation_height, consensus_branch_id : info.consensus_branch_id, anchor_offset : ANCHOR_OFFSET, + no_cert_verification : dangerous, }; // Configure logging first. From 225db642400ff8ac181bdef8c2a0c2aad5af3983 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 11:38:19 -0700 Subject: [PATCH 2/9] Fetch all txns in a block --- Cargo.toml | 5 ++--- src/grpcconnector.rs | 6 +++--- src/lightclient.rs | 28 +++++++++++++++++++++------- src/lightwallet.rs | 42 ++++++++++++++++++++++++++++-------------- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80151bd..2a749da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ hex = "0.3" protobuf = "2" rustyline = "5.0.2" byteorder = "1" -rand = "0.5.6" json = "0.12.0" shellwords = "1.0.0" tiny-bip39 = "0.6.2" @@ -37,6 +36,7 @@ webpki = "0.19.1" webpki-roots = "0.16.0" tower-h2 = { git = "https://github.com/tower-rs/tower-h2" } rust-embed = "5.1.0" +rand = "0.7.2" [dependencies.bellman] git = "https://github.com/adityapk00/librustzcash.git" @@ -72,8 +72,7 @@ features = ["ff_derive"] [build-dependencies] tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", features = ["tower-hyper"] } -[dev-dependencies] -rand_core = "0.5.1" + [profile.release] debug = false \ No newline at end of file diff --git a/src/grpcconnector.rs b/src/grpcconnector.rs index 8052ad1..66a0f2d 100644 --- a/src/grpcconnector.rs +++ b/src/grpcconnector.rs @@ -171,8 +171,8 @@ pub fn get_info(uri: http::Uri, no_cert: bool) -> Result { } -pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, c: F) - where F : Fn(&[u8]) { +pub fn fetch_blocks(uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, mut c: F) + where F : FnMut(&[u8], u64) { let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert) .and_then(move |mut client| { let bs = BlockId{ height: start_height, hash: vec!()}; @@ -191,7 +191,7 @@ pub fn fetch_blocks(uri: &http::Uri, start_heig let mut encoded_buf = vec![]; b.encode(&mut encoded_buf).unwrap(); - c(&encoded_buf); + c(&encoded_buf, b.height); Ok(()) }) diff --git a/src/lightclient.rs b/src/lightclient.rs index c432bfe..0f554e5 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -2,7 +2,9 @@ use crate::lightwallet::LightWallet; use log::{info, warn, error}; -use std::sync::{Arc}; +use rand::{rngs::OsRng, seq::SliceRandom}; + +use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicU64, AtomicI32, AtomicUsize, Ordering}; use std::path::Path; use std::fs::File; @@ -583,6 +585,11 @@ impl LightClient { let mut total_reorg = 0; + // Collect all txns in blocks that we have a tx in. We'll fetch all these + // txs along with our own, so that the server doesn't learn which ones + // belong to us. + let all_new_txs = Arc::new(RwLock::new(vec![])); + // Fetch CompactBlocks in increments loop { let local_light_wallet = self.wallet.clone(); @@ -599,23 +606,26 @@ impl LightClient { // Fetch compact blocks info!("Fetching blocks {}-{}", start_height, end_height); - + let all_txs = all_new_txs.clone(); + let last_invalid_height = Arc::new(AtomicI32::new(0)); let last_invalid_height_inner = last_invalid_height.clone(); fetch_blocks(&self.get_server_uri(), start_height, end_height, self.config.no_cert_verification, - move |encoded_block: &[u8]| { + move |encoded_block: &[u8], height: u64| { // Process the block only if there were no previous errors if last_invalid_height_inner.load(Ordering::SeqCst) > 0 { return; } match local_light_wallet.scan_block(encoded_block) { - Ok(_) => {}, + Ok(block_txns) => { + all_txs.write().unwrap().copy_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::>()[..]); + }, Err(invalid_height) => { // Block at this height seems to be invalid, so invalidate up till that point last_invalid_height_inner.store(invalid_height, Ordering::SeqCst); } - } + }; local_bytes_downloaded.fetch_add(encoded_block.len(), Ordering::SeqCst); }); @@ -683,12 +693,16 @@ impl LightClient { // We need to first copy over the Txids from the wallet struct, because // we need to free the read lock from here (Because we'll self.wallet.txs later) - let txids_to_fetch: Vec<(TxId, i32)> = self.wallet.txs.read().unwrap().values() + let mut txids_to_fetch: Vec<(TxId, i32)> = self.wallet.txs.read().unwrap().values() .filter(|wtx| wtx.full_tx_scanned == false) .map(|wtx| (wtx.txid, wtx.block)) .collect::>(); - info!("Fetching {} new txids", txids_to_fetch.len()); + info!("Fetching {} new txids, along with {} decoy", txids_to_fetch.len(), all_new_txs.read().unwrap().len()); + txids_to_fetch.extend_from_slice(&all_new_txs.read().unwrap()[..]); + + let mut rng = OsRng; + txids_to_fetch.shuffle(&mut rng); // And go and fetch the txids, getting the full transaction, so we can // read the memos diff --git a/src/lightwallet.rs b/src/lightwallet.rs index e674949..1818dd2 100644 --- a/src/lightwallet.rs +++ b/src/lightwallet.rs @@ -5,6 +5,8 @@ use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; use std::io::{Error, ErrorKind}; +use rand::{Rng, rngs::OsRng}; + use log::{info, warn, error}; use protobuf::parse_from_bytes; @@ -165,14 +167,12 @@ impl LightWallet { } 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 let mut seed_bytes = [0u8; 32]; if seed_phrase.is_none() { // Create a random seed. - let mut system_rng = ChaChaRng::from_entropy(); + let mut system_rng = OsRng; system_rng.fill(&mut seed_bytes); } else { seed_bytes.copy_from_slice(&Mnemonic::from_phrase(seed_phrase.expect("should have a seed phrase"), @@ -881,8 +881,8 @@ impl LightWallet { } // Scan a block. Will return an error with the block height that failed to scan - pub fn scan_block(&self, block: &[u8]) -> Result<(), i32> { - let block: CompactBlock = match parse_from_bytes(block) { + pub fn scan_block(&self, block_bytes: &[u8]) -> Result, i32> { + let block: CompactBlock = match parse_from_bytes(block_bytes) { Ok(block) => block, Err(e) => { error!("Could not parse CompactBlock from bytes: {}", e); @@ -900,7 +900,7 @@ impl LightWallet { return Err(height); } } - return Ok(()) + return Ok(vec![]); } else if height != (self.last_scanned_height() + 1) { error!( "Block is not height-sequential (expected {}, found {})", @@ -978,7 +978,7 @@ impl LightWallet { .collect(); scan_block( - block, + block.clone(), &self.extfvks.read().unwrap(), &nf_refs[..], &mut block_data.tree, @@ -986,6 +986,18 @@ impl LightWallet { ) }; + // If this block had any new Txs, return the list of ALL txids in this block, + // so the wallet can fetch them all as a decoy. + let all_txs = if !new_txs.is_empty() { + block.vtx.iter().map(|vtx| { + let mut t = [0u8; 32]; + t.copy_from_slice(&vtx.hash[..]); + TxId{0: t} + }).collect::>() + } else { + vec![] + }; + for tx in new_txs { // Mark notes as spent. let mut total_shielded_value_spent: u64 = 0; @@ -1023,9 +1035,7 @@ impl LightWallet { tx_entry.total_shielded_value_spent = total_shielded_value_spent; // Save notes. - for output in tx - .shielded_outputs - .into_iter() + for output in tx.shielded_outputs { info!("Received sapling output"); @@ -1059,7 +1069,8 @@ impl LightWallet { } } - Ok(()) + + Ok(all_txs) } pub fn send_to_address( @@ -1284,9 +1295,10 @@ impl LightWallet { pub mod tests { use std::convert::TryInto; use std::io::{Error}; + use rand::{RngCore, rngs::OsRng}; + use ff::{Field, PrimeField, PrimeFieldRepr}; use pairing::bls12_381::Bls12; - use rand_core::{RngCore, OsRng}; use protobuf::{Message, UnknownFields, CachedSize, RepeatedField}; use zcash_client_backend::{encoding::encode_payment_address, proto::compact_formats::{ @@ -1938,7 +1950,8 @@ pub mod tests { chain_name: "test".to_string(), sapling_activation_height: 0, consensus_branch_id: "000000".to_string(), - anchor_offset: 0 + anchor_offset: 0, + no_cert_verification: false, } } @@ -2860,7 +2873,8 @@ pub mod tests { chain_name: "main".to_string(), sapling_activation_height: 0, consensus_branch_id: "000000".to_string(), - anchor_offset: 1 + anchor_offset: 1, + no_cert_verification: false, }; let seed_phrase = Some("chimney better bulb horror rebuild whisper improve intact letter giraffe brave rib appear bulk aim burst snap salt hill sad merge tennis phrase raise".to_string()); From 1356ef809b1c25cbd4bb3d6a99929fff092346a5 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 11:39:51 -0700 Subject: [PATCH 3/9] Improve TLS error message --- src/grpcconnector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grpcconnector.rs b/src/grpcconnector.rs index 66a0f2d..03bef21 100644 --- a/src/grpcconnector.rs +++ b/src/grpcconnector.rs @@ -132,7 +132,7 @@ macro_rules! make_grpc_client { make_client .make_service(()) - .map_err(|e| { format!("HTTP/2 connection failed; err={:?}", e) }) + .map_err(|e| { format!("HTTP/2 connection failed; err={:?}.\nIf you're connecting to a local server, please pass --dangerous to trust the server without checking its TLS certificate", e) }) .and_then(move |conn| { let conn = tower_request_modifier::Builder::new() .set_origin(uri) From b489870480cbc9905703f2d82b90ff69716efe0f Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 12:27:31 -0700 Subject: [PATCH 4/9] Fix decoy tx fetching --- Cargo.toml | 12 ++++++------ src/lightclient.rs | 15 ++++++++++----- src/lightwallet.rs | 6 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a749da..5d87e4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,33 +40,33 @@ rand = "0.7.2" [dependencies.bellman] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" default-features = false features = ["groth16"] [dependencies.pairing] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" [dependencies.zcash_client_backend] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" default-features = false [dependencies.zcash_primitives] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" default-features = false features = ["transparent-inputs"] [dependencies.zcash_proofs] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" default-features = false [dependencies.ff] git = "https://github.com/adityapk00/librustzcash.git" -rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016" +rev = "188537ea025fcb7fbdfc11266f307a084a5451e4" features = ["ff_derive"] [build-dependencies] diff --git a/src/lightclient.rs b/src/lightclient.rs index 0f554e5..15adb27 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -619,7 +619,7 @@ impl LightClient { match local_light_wallet.scan_block(encoded_block) { Ok(block_txns) => { - all_txs.write().unwrap().copy_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::>()[..]); + all_txs.write().unwrap().extend_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::>()[..]); }, Err(invalid_height) => { // Block at this height seems to be invalid, so invalidate up till that point @@ -698,18 +698,23 @@ impl LightClient { .map(|wtx| (wtx.txid, wtx.block)) .collect::>(); - info!("Fetching {} new txids, along with {} decoy", txids_to_fetch.len(), all_new_txs.read().unwrap().len()); + info!("Fetching {} new txids, total {} with decoy", txids_to_fetch.len(), all_new_txs.read().unwrap().len()); txids_to_fetch.extend_from_slice(&all_new_txs.read().unwrap()[..]); - + txids_to_fetch.sort(); + txids_to_fetch.dedup(); + let mut rng = OsRng; txids_to_fetch.shuffle(&mut rng); // And go and fetch the txids, getting the full transaction, so we can - // read the memos + // read the memos + for (txid, height) in txids_to_fetch { let light_wallet_clone = self.wallet.clone(); info!("Fetching full Tx: {}", txid); - responses.push(format!("Fetching full Tx: {}", txid)); + if print_updates { + responses.push(format!("Fetching full Tx: {}", txid)); + } fetch_full_tx(&self.get_server_uri(), txid, self.config.no_cert_verification, move |tx_bytes: &[u8] | { let tx = Transaction::read(tx_bytes).unwrap(); diff --git a/src/lightwallet.rs b/src/lightwallet.rs index 1818dd2..9e6794a 100644 --- a/src/lightwallet.rs +++ b/src/lightwallet.rs @@ -816,8 +816,10 @@ impl LightWallet { // Mark this Tx as scanned { let mut txs = self.txs.write().unwrap(); - let mut wtx = txs.get_mut(&tx.txid()).unwrap(); - wtx.full_tx_scanned = true; + match txs.get_mut(&tx.txid()) { + Some(wtx) => wtx.full_tx_scanned = true, + None => {}, + }; } } From a2c57a73607d710f28213258feb9dced266990a1 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 12:47:59 -0700 Subject: [PATCH 5/9] Add recover commad --- src/main.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index b41de5f..d70f7db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,6 +89,10 @@ pub fn main() { .long("dangerous") .help("Disable server TLS certificate verification. Use this if you're running a local lightwalletd with a self-signed certificate. WARNING: This is dangerous, don't use it with a server that is not your own.") .takes_value(false)) + .arg(Arg::with_name("recover") + .long("recover") + .help("Attempt to recover the seed from the wallet") + .takes_value(false)) .arg(Arg::with_name("nosync") .help("By default, zecwallet-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.") .long("nosync") @@ -104,6 +108,11 @@ pub fn main() { .multiple(true)) .get_matches(); + if matches.is_present("recover") { + attempt_recover_seed(); + return; + } + let command = matches.value_of("COMMAND"); let params = matches.values_of("PARAMS").map(|v| v.collect()).or(Some(vec![])).unwrap(); @@ -129,7 +138,6 @@ pub fn main() { } }; - // Create a Light Client Config let config = lightclient::LightClientConfig { server : server.clone(), @@ -179,6 +187,36 @@ pub fn main() { } } +fn attempt_recover_seed() { + use std::fs::File; + use std::io::prelude::*; + use std::io::{BufReader}; + use byteorder::{LittleEndian, ReadBytesExt,}; + use bip39::{Mnemonic, Language}; + + // Create a Light Client Config in an attempt to recover the file. + let config = LightClientConfig { + server: "0.0.0.0:0".parse().unwrap(), + chain_name: "main".to_string(), + sapling_activation_height: 0, + consensus_branch_id: "000000".to_string(), + anchor_offset: 0, + no_cert_verification: false, + }; + + let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap()); + let version = reader.read_u64::().unwrap(); + println!("Reading wallet version {}", version); + + // Seed + let mut seed_bytes = [0u8; 32]; + reader.read_exact(&mut seed_bytes).unwrap(); + + let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string(); + + println!("Recovered seed phrase:\n{}", phrase); +} + fn start_interactive(lightclient: Arc, config: &LightClientConfig) { println!("Lightclient connecting to {}", config.server); @@ -267,5 +305,4 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } } } - } \ No newline at end of file From 0db5423927d22de9490ce97d638e6c8f3b49dd4e Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 12:48:55 -0700 Subject: [PATCH 6/9] Cleanup --- src/lightclient.rs | 3 --- src/lightwallet.rs | 23 +++-------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/lightclient.rs b/src/lightclient.rs index 15adb27..ea5fa71 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -712,9 +712,6 @@ impl LightClient { for (txid, height) in txids_to_fetch { let light_wallet_clone = self.wallet.clone(); info!("Fetching full Tx: {}", txid); - if print_updates { - responses.push(format!("Fetching full Tx: {}", txid)); - } fetch_full_tx(&self.get_server_uri(), txid, self.config.no_cert_verification, move |tx_bytes: &[u8] | { let tx = Transaction::read(tx_bytes).unwrap(); diff --git a/src/lightwallet.rs b/src/lightwallet.rs index 9e6794a..f9fe0bb 100644 --- a/src/lightwallet.rs +++ b/src/lightwallet.rs @@ -88,26 +88,6 @@ impl ToBase58Check for [u8] { payload.to_base58() } } -// -//pub trait FromBase58Check { -// fn from_base58check(&self, version: &[u8], suffix: &[u8]) -> Vec; -//} -// -// -//impl FromBase58Check for str { -// fn from_base58check(&self, version: &[u8], suffix: &[u8]) -> Vec { -// let mut payload: Vec = Vec::new(); -// let bytes = self.from_base58().unwrap(); -// -// let start = version.len(); -// let end = bytes.len() - (4 + suffix.len()); -// -// payload.extend(&bytes[start..end]); -// -// payload -// } -//} - pub struct LightWallet { seed: [u8; 32], // Seed phrase for this wallet. @@ -269,6 +249,9 @@ impl LightWallet { // Write the seed writer.write_all(&self.seed)?; + // Flush after writing the seed, so in case of a disaster, we can still recover the seed. + writer.flush()?; + // Write all the spending keys Vector::write(&mut writer, &self.extsks.read().unwrap(), |w, sk| sk.write(w) From 7016280d1c0c83f69893ff40946158a17b77059f Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 9 Oct 2019 15:04:53 -0700 Subject: [PATCH 7/9] Warn if server is behind --- src/lightclient.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lightclient.rs b/src/lightclient.rs index ea5fa71..d9d7295 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -569,6 +569,12 @@ impl LightClient { }); 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); + warn!("{}", w); + return w; + } + info!("Latest block is {}", latest_block); // Get the end height to scan to. From 79e4b8d0a44d304bafb20887e539ad985df82464 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 11 Oct 2019 10:47:36 -0700 Subject: [PATCH 8/9] Refactor for lib --- src/main.rs | 169 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 71 deletions(-) diff --git a/src/main.rs b/src/main.rs index d70f7db..5ff7bbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,12 @@ mod commands; use std::io::{Result, Error, ErrorKind}; use std::sync::{Arc}; +use std::sync::mpsc::{channel, Sender, Receiver}; use std::time::Duration; use lightclient::{LightClient, LightClientConfig}; -use log::{info, LevelFilter}; +use log::{info, error, LevelFilter}; use log4rs::append::rolling_file::RollingFileAppender; use log4rs::encode::pattern::PatternEncoder; use log4rs::config::{Appender, Config, Root}; @@ -129,25 +130,15 @@ pub fn main() { let dangerous = matches.is_present("dangerous"); - // Do a getinfo first, before opening the wallet - let info = match grpcconnector::get_info(server.clone(), dangerous) { - Ok(ld) => ld, + // Try to get the configuration + let (config, latest_block_height) = match create_lightclient_config(server.clone(), dangerous) { + Ok((c, h)) => (c, h), Err(e) => { - eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e); + eprintln!("Couldn't create config: {}", e); return; } }; - // Create a Light Client Config - let config = lightclient::LightClientConfig { - server : server.clone(), - chain_name : info.chain_name, - sapling_activation_height : info.sapling_activation_height, - consensus_branch_id : info.consensus_branch_id, - anchor_offset : ANCHOR_OFFSET, - no_cert_verification : dangerous, - }; - // Configure logging first. let log_config = match get_log_config(&config) { Ok(c) => c, @@ -158,16 +149,20 @@ pub fn main() { }; log4rs::init_config(log_config).unwrap(); + let lightclient = match create_lightclient(seed, latest_block_height, &config) { + Ok(lc) => Arc::new(lc), + Err(e) => { + eprintln!("Couldn't create Lightclient. {}", e); + error!("Couldn't create Lightclient. {}", e); + return; + } + }; + // Startup info!(""); // Blank line info!("Starting Zecwallet-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_output = if matches.is_present("nosync") { None @@ -187,65 +182,33 @@ pub fn main() { } } -fn attempt_recover_seed() { - use std::fs::File; - use std::io::prelude::*; - use std::io::{BufReader}; - use byteorder::{LittleEndian, ReadBytesExt,}; - use bip39::{Mnemonic, Language}; +fn create_lightclient_config(server: http::Uri, dangerous: bool) -> Result<(LightClientConfig, u64)> { + // Do a getinfo first, before opening the wallet + let info = grpcconnector::get_info(server.clone(), dangerous) + .map_err(|e| std::io::Error::new(ErrorKind::ConnectionRefused, e))?; - // Create a Light Client Config in an attempt to recover the file. - let config = LightClientConfig { - server: "0.0.0.0:0".parse().unwrap(), - chain_name: "main".to_string(), - sapling_activation_height: 0, - consensus_branch_id: "000000".to_string(), - anchor_offset: 0, - no_cert_verification: false, + // Create a Light Client Config + let config = lightclient::LightClientConfig { + server, + chain_name : info.chain_name, + sapling_activation_height : info.sapling_activation_height, + consensus_branch_id : info.consensus_branch_id, + anchor_offset : ANCHOR_OFFSET, + no_cert_verification : dangerous, }; - let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap()); - let version = reader.read_u64::().unwrap(); - println!("Reading wallet version {}", version); + Ok((config, info.block_height)) +} - // Seed - let mut seed_bytes = [0u8; 32]; - reader.read_exact(&mut seed_bytes).unwrap(); +fn create_lightclient(seed: Option, latest_block: u64, config: &LightClientConfig) -> Result<(LightClient)> { + let lightclient = LightClient::new(seed, config, latest_block)?; - let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string(); - - println!("Recovered seed phrase:\n{}", phrase); + Ok(lightclient) } fn start_interactive(lightclient: Arc, config: &LightClientConfig) { - println!("Lightclient connecting to {}", config.server); - - let (command_tx, command_rx) = std::sync::mpsc::channel::<(String, Vec)>(); - let (resp_tx, resp_rx) = std::sync::mpsc::channel::(); - - let lc = lightclient.clone(); - std::thread::spawn(move || { - loop { - match command_rx.recv_timeout(Duration::from_secs(5 * 60)) { - Ok((cmd, args)) => { - let args = args.iter().map(|s| s.as_ref()).collect(); - - let cmd_response = commands::do_user_command(&cmd, &args, lc.as_ref()); - resp_tx.send(cmd_response).unwrap(); - - if cmd == "quit" { - info!("Quit"); - break; - } - }, - 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); - } - } - } - }); + // Start the command loop + let (command_tx, resp_rx) = command_loop(lightclient.clone(), config); // `()` can be used when no completer is required let mut rl = Editor::<()>::new(); @@ -305,4 +268,68 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } } } +} + + +fn command_loop(lightclient: Arc, config: &LightClientConfig) -> (Sender<(String, Vec)>, Receiver) { + println!("Lightclient connecting to {}", config.server); + + let (command_tx, command_rx) = channel::<(String, Vec)>(); + let (resp_tx, resp_rx) = channel::(); + + let lc = lightclient.clone(); + std::thread::spawn(move || { + loop { + match command_rx.recv_timeout(Duration::from_secs(5 * 60)) { + Ok((cmd, args)) => { + let args = args.iter().map(|s| s.as_ref()).collect(); + + let cmd_response = commands::do_user_command(&cmd, &args, lc.as_ref()); + resp_tx.send(cmd_response).unwrap(); + + if cmd == "quit" { + info!("Quit"); + break; + } + }, + 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); + } + } + } + }); + + (command_tx, resp_rx) +} + +fn attempt_recover_seed() { + use std::fs::File; + use std::io::prelude::*; + use std::io::{BufReader}; + use byteorder::{LittleEndian, ReadBytesExt,}; + use bip39::{Mnemonic, Language}; + + // Create a Light Client Config in an attempt to recover the file. + let config = LightClientConfig { + server: "0.0.0.0:0".parse().unwrap(), + chain_name: "main".to_string(), + sapling_activation_height: 0, + consensus_branch_id: "000000".to_string(), + anchor_offset: 0, + no_cert_verification: false, + }; + + let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap()); + let version = reader.read_u64::().unwrap(); + println!("Reading wallet version {}", version); + + // Seed + let mut seed_bytes = [0u8; 32]; + reader.read_exact(&mut seed_bytes).unwrap(); + + let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string(); + + println!("Recovered seed phrase:\n{}", phrase); } \ No newline at end of file From 374f50e8e3b630585ea5336f5c2f1b61b352fe86 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Fri, 11 Oct 2019 12:03:34 -0700 Subject: [PATCH 9/9] Refactor for library use --- src/commands.rs | 27 +++++++++ src/lightclient.rs | 14 ++++- src/main.rs | 135 ++++++++++++++++++++++++++------------------- 3 files changed, 117 insertions(+), 59 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 4c090a7..e7e627f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use json::{object}; use crate::LightClient; @@ -311,6 +312,31 @@ impl Command for TransactionsCommand { } } +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("Usage:"); + h.push("height"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the latest block height that the wallet is at".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + format!("{}", + object! { + "height" => lightclient.last_scanned_height() + }.pretty(2)) + } +} + + struct NewAddressCommand {} impl Command for NewAddressCommand { fn help(&self) -> String { @@ -407,6 +433,7 @@ pub fn get_commands() -> Box>> { map.insert("help".to_string(), Box::new(HelpCommand{})); map.insert("balance".to_string(), Box::new(BalanceCommand{})); map.insert("addresses".to_string(), Box::new(AddressCommand{})); + map.insert("height".to_string(), Box::new(HeightCommand{})); map.insert("export".to_string(), Box::new(ExportCommand{})); map.insert("info".to_string(), Box::new(InfoCommand{})); map.insert("send".to_string(), Box::new(SendCommand{})); diff --git a/src/lightclient.rs b/src/lightclient.rs index d9d7295..7e8793b 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -1,7 +1,6 @@ use crate::lightwallet::LightWallet; use log::{info, warn, error}; - use rand::{rngs::OsRng, seq::SliceRandom}; use std::sync::{Arc, RwLock}; @@ -328,7 +327,18 @@ impl LightClient { pub fn do_info(&self) -> String { match get_info(self.get_server_uri(), self.config.no_cert_verification) { - Ok(i) => format!("{:?}", i)[11..].to_string(), + Ok(i) => { + let o = object!{ + "version" => i.version, + "vendor" => i.vendor, + "taddr_support" => i.taddr_support, + "chain_name" => i.chain_name, + "sapling_activation_height" => i.sapling_activation_height, + "consensus_branch_id" => i.consensus_branch_id, + "latest_block_height" => i.block_height + }; + o.pretty(2) + }, Err(e) => e } } diff --git a/src/main.rs b/src/main.rs index 5ff7bbf..c8483f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,57 +129,70 @@ pub fn main() { } let dangerous = matches.is_present("dangerous"); + let nosync = matches.is_present("nosync"); - // Try to get the configuration - let (config, latest_block_height) = match create_lightclient_config(server.clone(), dangerous) { - Ok((c, h)) => (c, h), - Err(e) => { - eprintln!("Couldn't create config: {}", e); - return; - } - }; - - // Configure logging first. - let log_config = match get_log_config(&config) { + let (command_tx, resp_rx) = match startup(server, dangerous, seed, !nosync, command.is_none()) { Ok(c) => c, Err(e) => { - eprintln!("Error:\n{}\nCouldn't configure logging, quitting!", e); - return; - } - }; - log4rs::init_config(log_config).unwrap(); - - let lightclient = match create_lightclient(seed, latest_block_height, &config) { - Ok(lc) => Arc::new(lc), - Err(e) => { - eprintln!("Couldn't create Lightclient. {}", e); - error!("Couldn't create Lightclient. {}", e); + eprintln!("Error during startup: {}", e); + error!("Error during startup: {}", e); return; } }; - // Startup + if command.is_none() { + start_interactive(command_tx, resp_rx); + } else { + command_tx.send( + (command.unwrap().to_string(), + params.iter().map(|s| s.to_string()).collect::>())) + .unwrap(); + + match resp_rx.recv() { + Ok(s) => println!("{}", s), + Err(e) => { + let e = format!("Error executing command {}: {}", command.unwrap(), e); + eprintln!("{}", e); + error!("{}", e); + } + } + } +} + +fn startup(server: http::Uri, dangerous: bool, seed: Option, first_sync: bool, print_updates: bool) + -> Result<(Sender<(String, Vec)>, Receiver)> { + // Try to get the configuration + let (config, latest_block_height) = create_lightclient_config(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 = Arc::new(create_lightclient(seed, latest_block_height, &config)?); + + // Print startup Messages info!(""); // Blank line info!("Starting Zecwallet-CLI"); info!("Light Client config {:?}", config); - // At startup, run a sync. - let sync_output = if matches.is_present("nosync") { - None - } else { - Some(lightclient.do_sync(true)) - }; - - if command.is_none() { - // If running in interactive mode, output of the sync command - if sync_output.is_some() { - println!("{}", sync_output.unwrap()); - } - start_interactive(lightclient, &config); - } else { - let cmd_response = commands::do_user_command(&command.unwrap(), ¶ms, lightclient.as_ref()); - println!("{}", cmd_response); + if print_updates { + println!("Lightclient connecting to {}", config.server); } + + // Start the command loop + let (command_tx, resp_rx) = command_loop(lightclient.clone()); + + // At startup, run a sync. + if first_sync { + let update = lightclient.do_sync(true); + if print_updates { + println!("{}", update); + } + } + + Ok((command_tx, resp_rx)) } fn create_lightclient_config(server: http::Uri, dangerous: bool) -> Result<(LightClientConfig, u64)> { @@ -206,19 +219,34 @@ fn create_lightclient(seed: Option, latest_block: u64, config: &LightCli Ok(lightclient) } -fn start_interactive(lightclient: Arc, config: &LightClientConfig) { - // Start the command loop - let (command_tx, resp_rx) = command_loop(lightclient.clone(), config); - +fn start_interactive(command_tx: Sender<(String, Vec)>, resp_rx: Receiver) { // `()` can be used when no completer is required let mut rl = Editor::<()>::new(); println!("Ready!"); + let send_command = |cmd: String, args: Vec| -> String { + command_tx.send((cmd.clone(), args)).unwrap(); + match resp_rx.recv() { + Ok(s) => s, + Err(e) => { + let e = format!("Error executing command {}: {}", cmd, e); + eprintln!("{}", e); + error!("{}", e); + return "".to_string() + } + } + }; + + let info = &send_command("info".to_string(), vec![]); + let chain_name = json::parse(info).unwrap()["chain_name"].as_str().unwrap().to_string(); + loop { + // Read the height first + let height = json::parse(&send_command("height".to_string(), vec![])).unwrap()["height"].as_i64().unwrap(); + let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ", - config.chain_name, - lightclient.last_scanned_height())); + chain_name, height)); match readline { Ok(line) => { rl.add_history_entry(line.as_str()); @@ -236,14 +264,9 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } let cmd = cmd_args.remove(0); - let args: Vec = cmd_args; - command_tx.send((cmd, args)).unwrap(); + let args: Vec = cmd_args; - // Wait for the response - match resp_rx.recv() { - Ok(response) => println!("{}", response), - _ => { eprintln!("Error receiving response");} - } + println!("{}", send_command(cmd, args)); // Special check for Quit command. if line == "quit" { @@ -253,13 +276,13 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) Err(ReadlineError::Interrupted) => { println!("CTRL-C"); info!("CTRL-C"); - println!("{}", lightclient.do_save()); + println!("{}", send_command("save".to_string(), vec![])); break }, Err(ReadlineError::Eof) => { println!("CTRL-D"); info!("CTRL-D"); - println!("{}", lightclient.do_save()); + println!("{}", send_command("save".to_string(), vec![])); break }, Err(err) => { @@ -271,9 +294,7 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } -fn command_loop(lightclient: Arc, config: &LightClientConfig) -> (Sender<(String, Vec)>, Receiver) { - println!("Lightclient connecting to {}", config.server); - +fn command_loop(lightclient: Arc) -> (Sender<(String, Vec)>, Receiver) { let (command_tx, command_rx) = channel::<(String, Vec)>(); let (resp_tx, resp_rx) = channel::();