Browse Source

Merge branch 'master' of github.com:adityapk00/zecwallet-lite-lib

checkpoints
Aditya Kulkarni 5 years ago
parent
commit
a9efbac3bd
  1. 18
      Cargo.toml
  2. 29
      src/commands.rs
  3. 66
      src/grpcconnector.rs
  4. 68
      src/lightclient.rs
  5. 71
      src/lightwallet.rs
  6. 245
      src/main.rs

18
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"
@ -32,47 +31,48 @@ 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" }
rust-embed = "5.1.0"
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]
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

29
src/commands.rs

@ -1,4 +1,5 @@
use std::collections::HashMap;
use json::{object};
use crate::LightClient;
@ -118,7 +119,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()
}
}
@ -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<HashMap<String, Box<dyn Command>>> {
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{}));

66
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<rustls::ServerCertVerified, rustls::TLSError> {
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,11 +128,11 @@ 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(())
.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)
@ -126,8 +152,8 @@ macro_rules! make_grpc_client {
// GRPC code
// ==============
pub fn get_info(uri: http::Uri) -> Result<LightdInfo, String> {
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<LightdInfo, String> {
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<LightdInfo, String> {
}
pub fn fetch_blocks<F : 'static + std::marker::Send>(uri: &http::Uri, start_height: u64, end_height: u64, c: F)
where F : Fn(&[u8]) {
let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap())
pub fn fetch_blocks<F : 'static + std::marker::Send>(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!()};
let be = BlockId{ height: end_height, hash: vec!()};
@ -165,7 +191,7 @@ pub fn fetch_blocks<F : 'static + std::marker::Send>(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(())
})
@ -183,9 +209,9 @@ pub fn fetch_blocks<F : 'static + std::marker::Send>(uri: &http::Uri, start_heig
}
pub fn fetch_transparent_txids<F : 'static + std::marker::Send>(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<F : 'static + std::marker::Send>(uri: &http::Uri,
};
}
pub fn fetch_full_tx<F : 'static + std::marker::Send>(uri: &http::Uri, txid: TxId, c: F)
pub fn fetch_full_tx<F : 'static + std::marker::Send>(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<F : 'static + std::marker::Send>(uri: &http::Uri, txid: TxI
};
}
pub fn broadcast_raw_tx(uri: &http::Uri, tx_bytes: Box<[u8]>) -> Result<String, String> {
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<String, String> {
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<String,
tokio::runtime::current_thread::Runtime::new().unwrap().block_on(runner)
}
pub fn fetch_latest_block<F : 'static + std::marker::Send>(uri: &http::Uri, mut c : F)
pub fn fetch_latest_block<F : 'static + std::marker::Send>(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) })

68
src/lightclient.rs

@ -1,8 +1,9 @@
use crate::lightwallet::LightWallet;
use log::{info, warn, error};
use rand::{rngs::OsRng, seq::SliceRandom};
use std::sync::{Arc};
use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicU64, AtomicI32, AtomicUsize, Ordering};
use std::path::Path;
use std::fs::File;
@ -33,6 +34,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,10 +325,20 @@ impl LightClient {
self.config.server.clone()
}
pub fn do_info(uri: http::Uri) -> String {
let r = get_info(uri);
match r {
Ok(i) => format!("{:?}", i)[11..].to_string(),
pub fn do_info(&self) -> String {
match get_info(self.get_server_uri(), self.config.no_cert_verification) {
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
}
}
@ -562,11 +574,17 @@ 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);
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.
@ -583,6 +601,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 +622,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,
move |encoded_block: &[u8]| {
fetch_blocks(&self.get_server_uri(), start_height, end_height, self.config.no_cert_verification,
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().extend_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::<Vec<_>>()[..]);
},
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);
});
@ -652,7 +678,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();
@ -683,21 +709,27 @@ 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::<Vec<(TxId, i32)>>();
info!("Fetching {} new txids", txids_to_fetch.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));
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 +748,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,
},

71
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;
@ -86,26 +88,6 @@ impl ToBase58Check for [u8] {
payload.to_base58()
}
}
//
//pub trait FromBase58Check {
// fn from_base58check(&self, version: &[u8], suffix: &[u8]) -> Vec<u8>;
//}
//
//
//impl FromBase58Check for str {
// fn from_base58check(&self, version: &[u8], suffix: &[u8]) -> Vec<u8> {
// let mut payload: Vec<u8> = 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.
@ -165,14 +147,12 @@ impl LightWallet {
}
pub fn new(seed_phrase: Option<String>, config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
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"),
@ -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)
@ -816,8 +799,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 => {},
};
}
}
@ -881,8 +866,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<Vec<TxId>, 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 +885,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 +963,7 @@ impl LightWallet {
.collect();
scan_block(
block,
block.clone(),
&self.extfvks.read().unwrap(),
&nf_refs[..],
&mut block_data.tree,
@ -986,6 +971,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::<Vec<TxId>>()
} else {
vec![]
};
for tx in new_txs {
// Mark notes as spent.
let mut total_shielded_value_spent: u64 = 0;
@ -1023,9 +1020,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 +1054,8 @@ impl LightWallet {
}
}
Ok(())
Ok(all_txs)
}
pub fn send_to_address(
@ -1284,9 +1280,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 +1935,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 +2858,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());

245
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};
@ -85,6 +86,14 @@ 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("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")
@ -100,6 +109,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();
@ -114,102 +128,125 @@ pub fn main() {
return;
}
// Do a getinfo first, before opening the wallet
let info = match grpcconnector::get_info(server.clone()) {
Ok(ld) => ld,
let dangerous = matches.is_present("dangerous");
let nosync = matches.is_present("nosync");
let (command_tx, resp_rx) = match startup(server, dangerous, seed, !nosync, command.is_none()) {
Ok(c) => c,
Err(e) => {
eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e);
eprintln!("Error during startup: {}", e);
error!("Error during startup: {}", 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,
};
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::<Vec<String>>()))
.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<String>, first_sync: bool, print_updates: bool)
-> Result<(Sender<(String, Vec<String>)>, Receiver<String>)> {
// Try to get the configuration
let (config, latest_block_height) = create_lightclient_config(server.clone(), dangerous)?;
// Configure logging first.
let log_config = match get_log_config(&config) {
Ok(c) => c,
Err(e) => {
eprintln!("Error:\n{}\nCouldn't configure logging, quitting!", e);
return;
}
};
log4rs::init_config(log_config).unwrap();
let log_config = get_log_config(&config)?;
log4rs::init_config(log_config).map_err(|e| {
std::io::Error::new(ErrorKind::Other, e)
})?;
// Startup
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);
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; }
};
if print_updates {
println!("Lightclient connecting to {}", config.server);
}
// At startup, run a sync.
let sync_output = if matches.is_present("nosync") {
None
} else {
Some(lightclient.do_sync(true))
};
// Start the command loop
let (command_tx, resp_rx) = command_loop(lightclient.clone());
if command.is_none() {
// If running in interactive mode, output of the sync command
if sync_output.is_some() {
println!("{}", sync_output.unwrap());
// At startup, run a sync.
if first_sync {
let update = lightclient.do_sync(true);
if print_updates {
println!("{}", update);
}
start_interactive(lightclient, &config);
} else {
let cmd_response = commands::do_user_command(&command.unwrap(), &params, lightclient.as_ref());
println!("{}", cmd_response);
}
Ok((command_tx, resp_rx))
}
fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig) {
println!("Lightclient connecting to {}", config.server);
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))?;
let (command_tx, command_rx) = std::sync::mpsc::channel::<(String, Vec<String>)>();
let (resp_tx, resp_rx) = std::sync::mpsc::channel::<String>();
// 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 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();
Ok((config, info.block_height))
}
let cmd_response = commands::do_user_command(&cmd, &args, lc.as_ref());
resp_tx.send(cmd_response).unwrap();
fn create_lightclient(seed: Option<String>, latest_block: u64, config: &LightClientConfig) -> Result<(LightClient)> {
let lightclient = LightClient::new(seed, config, latest_block)?;
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);
}
}
}
});
Ok(lightclient)
}
fn start_interactive(command_tx: Sender<(String, Vec<String>)>, resp_rx: Receiver<String>) {
// `()` can be used when no completer is required
let mut rl = Editor::<()>::new();
println!("Ready!");
let send_command = |cmd: String, args: Vec<String>| -> 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());
@ -227,14 +264,9 @@ fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig)
}
let cmd = cmd_args.remove(0);
let args: Vec<String> = cmd_args;
command_tx.send((cmd, args)).unwrap();
let args: Vec<String> = 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" {
@ -244,13 +276,13 @@ fn start_interactive(lightclient: Arc<LightClient>, 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) => {
@ -259,5 +291,66 @@ fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig)
}
}
}
}
fn command_loop(lightclient: Arc<LightClient>) -> (Sender<(String, Vec<String>)>, Receiver<String>) {
let (command_tx, command_rx) = channel::<(String, Vec<String>)>();
let (resp_tx, resp_rx) = channel::<String>();
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::<LittleEndian>().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);
}
Loading…
Cancel
Save