mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-02-01 03:12:15 +00:00
Merge branch 'master' of github.com:adityapk00/zecwallet-lite-lib
This commit is contained in:
commit
a9efbac3bd
18
Cargo.toml
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
|
@ -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{}));
|
||||
|
@ -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,
|
||||
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) })
|
||||
|
@ -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
|
||||
|
||||
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,
|
||||
},
|
||||
|
@ -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());
|
||||
|
285
src/main.rs
285
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,68 +128,175 @@ pub fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do a getinfo first, before opening the wallet
|
||||
let info = match grpcconnector::get_info(server.clone()) {
|
||||
Ok(ld) => ld,
|
||||
Err(e) => {
|
||||
eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dangerous = matches.is_present("dangerous");
|
||||
let nosync = matches.is_present("nosync");
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
// 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);
|
||||
eprintln!("Error during startup: {}", e);
|
||||
error!("Error during startup: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
log4rs::init_config(log_config).unwrap();
|
||||
|
||||
// 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::<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 = 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Start the command loop
|
||||
let (command_tx, resp_rx) = command_loop(lightclient.clone());
|
||||
|
||||
// At startup, run a sync.
|
||||
let sync_output = if matches.is_present("nosync") {
|
||||
None
|
||||
} else {
|
||||
Some(lightclient.do_sync(true))
|
||||
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)> {
|
||||
// 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
|
||||
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,
|
||||
};
|
||||
|
||||
if command.is_none() {
|
||||
// If running in interactive mode, output of the sync command
|
||||
if sync_output.is_some() {
|
||||
println!("{}", sync_output.unwrap());
|
||||
Ok((config, info.block_height))
|
||||
}
|
||||
|
||||
fn create_lightclient(seed: Option<String>, latest_block: u64, config: &LightClientConfig) -> Result<(LightClient)> {
|
||||
let lightclient = LightClient::new(seed, config, latest_block)?;
|
||||
|
||||
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') >> ",
|
||||
chain_name, height));
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
rl.add_history_entry(line.as_str());
|
||||
// Parse command line arguments
|
||||
let mut cmd_args = match shellwords::split(&line) {
|
||||
Ok(args) => args,
|
||||
Err(_) => {
|
||||
println!("Mismatched Quotes");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if cmd_args.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cmd = cmd_args.remove(0);
|
||||
let args: Vec<String> = cmd_args;
|
||||
|
||||
println!("{}", send_command(cmd, args));
|
||||
|
||||
// Special check for Quit command.
|
||||
if line == "quit" {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
info!("CTRL-C");
|
||||
println!("{}", send_command("save".to_string(), vec![]));
|
||||
break
|
||||
},
|
||||
Err(ReadlineError::Eof) => {
|
||||
println!("CTRL-D");
|
||||
info!("CTRL-D");
|
||||
println!("{}", send_command("save".to_string(), vec![]));
|
||||
break
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
break
|
||||
}
|
||||
}
|
||||
start_interactive(lightclient, &config);
|
||||
} else {
|
||||
let cmd_response = commands::do_user_command(&command.unwrap(), ¶ms, lightclient.as_ref());
|
||||
println!("{}", cmd_response);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig) {
|
||||
println!("Lightclient connecting to {}", config.server);
|
||||
|
||||
let (command_tx, command_rx) = std::sync::mpsc::channel::<(String, Vec<String>)>();
|
||||
let (resp_tx, resp_rx) = std::sync::mpsc::channel::<String>();
|
||||
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 || {
|
||||
@ -201,63 +322,35 @@ fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig)
|
||||
}
|
||||
});
|
||||
|
||||
// `()` can be used when no completer is required
|
||||
let mut rl = Editor::<()>::new();
|
||||
|
||||
println!("Ready!");
|
||||
|
||||
loop {
|
||||
let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ",
|
||||
config.chain_name,
|
||||
lightclient.last_scanned_height()));
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
rl.add_history_entry(line.as_str());
|
||||
// Parse command line arguments
|
||||
let mut cmd_args = match shellwords::split(&line) {
|
||||
Ok(args) => args,
|
||||
Err(_) => {
|
||||
println!("Mismatched Quotes");
|
||||
continue;
|
||||
(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,
|
||||
};
|
||||
|
||||
if cmd_args.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap());
|
||||
let version = reader.read_u64::<LittleEndian>().unwrap();
|
||||
println!("Reading wallet version {}", version);
|
||||
|
||||
let cmd = cmd_args.remove(0);
|
||||
let args: Vec<String> = cmd_args;
|
||||
command_tx.send((cmd, args)).unwrap();
|
||||
// Seed
|
||||
let mut seed_bytes = [0u8; 32];
|
||||
reader.read_exact(&mut seed_bytes).unwrap();
|
||||
|
||||
// Wait for the response
|
||||
match resp_rx.recv() {
|
||||
Ok(response) => println!("{}", response),
|
||||
_ => { eprintln!("Error receiving response");}
|
||||
}
|
||||
|
||||
// Special check for Quit command.
|
||||
if line == "quit" {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
info!("CTRL-C");
|
||||
println!("{}", lightclient.do_save());
|
||||
break
|
||||
},
|
||||
Err(ReadlineError::Eof) => {
|
||||
println!("CTRL-D");
|
||||
info!("CTRL-D");
|
||||
println!("{}", lightclient.do_save());
|
||||
break
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string();
|
||||
|
||||
println!("Recovered seed phrase:\n{}", phrase);
|
||||
}
|
Loading…
Reference in New Issue
Block a user