mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-02-14 10:55:47 +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"
|
protobuf = "2"
|
||||||
rustyline = "5.0.2"
|
rustyline = "5.0.2"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
rand = "0.5.6"
|
|
||||||
json = "0.12.0"
|
json = "0.12.0"
|
||||||
shellwords = "1.0.0"
|
shellwords = "1.0.0"
|
||||||
tiny-bip39 = "0.6.2"
|
tiny-bip39 = "0.6.2"
|
||||||
@ -32,47 +31,48 @@ ring = "0.14.0"
|
|||||||
lazy_static = "1.2.0"
|
lazy_static = "1.2.0"
|
||||||
tower-service = "0.2"
|
tower-service = "0.2"
|
||||||
tokio-rustls = "0.10.0-alpha.3"
|
tokio-rustls = "0.10.0-alpha.3"
|
||||||
|
rustls = { version = "0.15.2", features = ["dangerous_configuration"] }
|
||||||
webpki = "0.19.1"
|
webpki = "0.19.1"
|
||||||
webpki-roots = "0.16.0"
|
webpki-roots = "0.16.0"
|
||||||
tower-h2 = { git = "https://github.com/tower-rs/tower-h2" }
|
tower-h2 = { git = "https://github.com/tower-rs/tower-h2" }
|
||||||
rust-embed = "5.1.0"
|
rust-embed = "5.1.0"
|
||||||
|
rand = "0.7.2"
|
||||||
|
|
||||||
[dependencies.bellman]
|
[dependencies.bellman]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["groth16"]
|
features = ["groth16"]
|
||||||
|
|
||||||
[dependencies.pairing]
|
[dependencies.pairing]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
|
|
||||||
[dependencies.zcash_client_backend]
|
[dependencies.zcash_client_backend]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.zcash_primitives]
|
[dependencies.zcash_primitives]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["transparent-inputs"]
|
features = ["transparent-inputs"]
|
||||||
|
|
||||||
[dependencies.zcash_proofs]
|
[dependencies.zcash_proofs]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.ff]
|
[dependencies.ff]
|
||||||
git = "https://github.com/adityapk00/librustzcash.git"
|
git = "https://github.com/adityapk00/librustzcash.git"
|
||||||
rev = "0743dadcd017b60a0ac7123d04f0d6e7ce1e8016"
|
rev = "188537ea025fcb7fbdfc11266f307a084a5451e4"
|
||||||
features = ["ff_derive"]
|
features = ["ff_derive"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", features = ["tower-hyper"] }
|
tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", features = ["tower-hyper"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rand_core = "0.5.1"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = false
|
debug = false
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use json::{object};
|
||||||
|
|
||||||
use crate::LightClient;
|
use crate::LightClient;
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ impl Command for InfoCommand {
|
|||||||
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
|
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
|
||||||
lightclient.do_sync(true);
|
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 {}
|
struct NewAddressCommand {}
|
||||||
impl Command for NewAddressCommand {
|
impl Command for NewAddressCommand {
|
||||||
fn help(&self) -> String {
|
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("help".to_string(), Box::new(HelpCommand{}));
|
||||||
map.insert("balance".to_string(), Box::new(BalanceCommand{}));
|
map.insert("balance".to_string(), Box::new(BalanceCommand{}));
|
||||||
map.insert("addresses".to_string(), Box::new(AddressCommand{}));
|
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("export".to_string(), Box::new(ExportCommand{}));
|
||||||
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
||||||
map.insert("send".to_string(), Box::new(SendCommand{}));
|
map.insert("send".to_string(), Box::new(SendCommand{}));
|
||||||
|
@ -24,11 +24,28 @@ use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction,
|
|||||||
TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo};
|
TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo};
|
||||||
use crate::grpc_client::client::CompactTxStreamer;
|
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.
|
/// A Secure (https) grpc destination.
|
||||||
struct Dst {
|
struct Dst {
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
host: String,
|
host: String,
|
||||||
|
no_cert: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl tower_service::Service<()> for Dst {
|
impl tower_service::Service<()> for Dst {
|
||||||
@ -43,15 +60,24 @@ impl tower_service::Service<()> for Dst {
|
|||||||
fn call(&mut self, _: ()) -> Self::Future {
|
fn call(&mut self, _: ()) -> Self::Future {
|
||||||
let mut config = ClientConfig::new();
|
let mut config = ClientConfig::new();
|
||||||
|
|
||||||
|
|
||||||
config.alpn_protocols.push(b"h2".to_vec());
|
config.alpn_protocols.push(b"h2".to_vec());
|
||||||
config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
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 config = Arc::new(config);
|
||||||
let tls_connector = TlsConnector::from(config);
|
let tls_connector = TlsConnector::from(config);
|
||||||
|
|
||||||
let addr_string_local = self.host.clone();
|
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 domain_local = domain.to_owned();
|
||||||
|
|
||||||
let stream = TcpStream::connect(&self.addr).and_then(move |sock| {
|
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 {
|
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 uri: http::Uri = format!("{}://{}", $protocol, $host).parse().unwrap();
|
||||||
|
|
||||||
let addr = format!("{}:{}", $host, $port)
|
let addr = format!("{}:{}", $host, $port)
|
||||||
@ -102,11 +128,11 @@ macro_rules! make_grpc_client {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let h2_settings = Default::default();
|
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_client
|
||||||
.make_service(())
|
.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| {
|
.and_then(move |conn| {
|
||||||
let conn = tower_request_modifier::Builder::new()
|
let conn = tower_request_modifier::Builder::new()
|
||||||
.set_origin(uri)
|
.set_origin(uri)
|
||||||
@ -126,8 +152,8 @@ macro_rules! make_grpc_client {
|
|||||||
// GRPC code
|
// GRPC code
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
pub fn get_info(uri: http::Uri) -> Result<LightdInfo, String> {
|
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())
|
let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert)
|
||||||
.and_then(move |mut client| {
|
.and_then(move |mut client| {
|
||||||
client.get_lightd_info(Request::new(Empty{}))
|
client.get_lightd_info(Request::new(Empty{}))
|
||||||
.map_err(|e| {
|
.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)
|
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 : Fn(&[u8]) {
|
where F : FnMut(&[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| {
|
.and_then(move |mut client| {
|
||||||
let bs = BlockId{ height: start_height, hash: vec!()};
|
let bs = BlockId{ height: start_height, hash: vec!()};
|
||||||
let be = BlockId{ height: end_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![];
|
let mut encoded_buf = vec![];
|
||||||
|
|
||||||
b.encode(&mut encoded_buf).unwrap();
|
b.encode(&mut encoded_buf).unwrap();
|
||||||
c(&encoded_buf);
|
c(&encoded_buf, b.height);
|
||||||
|
|
||||||
Ok(())
|
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,
|
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) {
|
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| {
|
.and_then(move |mut client| {
|
||||||
let start = Some(BlockId{ height: start_height, hash: vec!()});
|
let start = Some(BlockId{ height: start_height, hash: vec!()});
|
||||||
let end = Some(BlockId{ height: end_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]) {
|
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| {
|
.and_then(move |mut client| {
|
||||||
let txfilter = TxFilter { block: None, index: 0, hash: txid.0.to_vec() };
|
let txfilter = TxFilter { block: None, index: 0, hash: txid.0.to_vec() };
|
||||||
client.get_transaction(Request::new(txfilter))
|
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> {
|
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())
|
let runner = make_grpc_client!(uri.scheme_str().unwrap(), uri.host().unwrap(), uri.port_part().unwrap(), no_cert)
|
||||||
.and_then(move |mut client| {
|
.and_then(move |mut client| {
|
||||||
client.send_transaction(Request::new(RawTransaction {data: tx_bytes.to_vec(), height: 0}))
|
client.send_transaction(Request::new(RawTransaction {data: tx_bytes.to_vec(), height: 0}))
|
||||||
.map_err(|e| {
|
.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)
|
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) {
|
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| {
|
.and_then(|mut client| {
|
||||||
client.get_latest_block(Request::new(ChainSpec {}))
|
client.get_latest_block(Request::new(ChainSpec {}))
|
||||||
.map_err(|e| { format!("ERR = {:?}", e) })
|
.map_err(|e| { format!("ERR = {:?}", e) })
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use crate::lightwallet::LightWallet;
|
use crate::lightwallet::LightWallet;
|
||||||
|
|
||||||
use log::{info, warn, error};
|
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::sync::atomic::{AtomicU64, AtomicI32, AtomicUsize, Ordering};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -33,6 +34,7 @@ pub struct LightClientConfig {
|
|||||||
pub sapling_activation_height : u64,
|
pub sapling_activation_height : u64,
|
||||||
pub consensus_branch_id : String,
|
pub consensus_branch_id : String,
|
||||||
pub anchor_offset : u32,
|
pub anchor_offset : u32,
|
||||||
|
pub no_cert_verification : bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightClientConfig {
|
impl LightClientConfig {
|
||||||
@ -323,10 +325,20 @@ impl LightClient {
|
|||||||
self.config.server.clone()
|
self.config.server.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_info(uri: http::Uri) -> String {
|
pub fn do_info(&self) -> String {
|
||||||
let r = get_info(uri);
|
match get_info(self.get_server_uri(), self.config.no_cert_verification) {
|
||||||
match r {
|
Ok(i) => {
|
||||||
Ok(i) => format!("{:?}", i)[11..].to_string(),
|
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
|
Err(e) => e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -562,11 +574,17 @@ impl LightClient {
|
|||||||
// This will hold the latest block fetched from the RPC
|
// This will hold the latest block fetched from the RPC
|
||||||
let latest_block_height = Arc::new(AtomicU64::new(0));
|
let latest_block_height = Arc::new(AtomicU64::new(0));
|
||||||
let lbh = latest_block_height.clone();
|
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);
|
lbh.store(block.height, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
let latest_block = latest_block_height.load(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);
|
info!("Latest block is {}", latest_block);
|
||||||
|
|
||||||
// Get the end height to scan to.
|
// Get the end height to scan to.
|
||||||
@ -583,6 +601,11 @@ impl LightClient {
|
|||||||
|
|
||||||
let mut total_reorg = 0;
|
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
|
// Fetch CompactBlocks in increments
|
||||||
loop {
|
loop {
|
||||||
let local_light_wallet = self.wallet.clone();
|
let local_light_wallet = self.wallet.clone();
|
||||||
@ -599,23 +622,26 @@ impl LightClient {
|
|||||||
|
|
||||||
// Fetch compact blocks
|
// Fetch compact blocks
|
||||||
info!("Fetching blocks {}-{}", start_height, end_height);
|
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 = Arc::new(AtomicI32::new(0));
|
||||||
let last_invalid_height_inner = last_invalid_height.clone();
|
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]| {
|
move |encoded_block: &[u8], height: u64| {
|
||||||
// Process the block only if there were no previous errors
|
// Process the block only if there were no previous errors
|
||||||
if last_invalid_height_inner.load(Ordering::SeqCst) > 0 {
|
if last_invalid_height_inner.load(Ordering::SeqCst) > 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match local_light_wallet.scan_block(encoded_block) {
|
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) => {
|
Err(invalid_height) => {
|
||||||
// Block at this height seems to be invalid, so invalidate up till that point
|
// Block at this height seems to be invalid, so invalidate up till that point
|
||||||
last_invalid_height_inner.store(invalid_height, Ordering::SeqCst);
|
last_invalid_height_inner.store(invalid_height, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
local_bytes_downloaded.fetch_add(encoded_block.len(), Ordering::SeqCst);
|
local_bytes_downloaded.fetch_add(encoded_block.len(), Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
@ -652,7 +678,7 @@ impl LightClient {
|
|||||||
// TODO: Use for all t addresses
|
// TODO: Use for all t addresses
|
||||||
let address = self.wallet.address_from_sk(&self.wallet.tkeys.read().unwrap()[0]);
|
let address = self.wallet.address_from_sk(&self.wallet.tkeys.read().unwrap()[0]);
|
||||||
let wallet = self.wallet.clone();
|
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 | {
|
move |tx_bytes: &[u8], height: u64 | {
|
||||||
let tx = Transaction::read(tx_bytes).unwrap();
|
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 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)
|
// 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)
|
.filter(|wtx| wtx.full_tx_scanned == false)
|
||||||
.map(|wtx| (wtx.txid, wtx.block))
|
.map(|wtx| (wtx.txid, wtx.block))
|
||||||
.collect::<Vec<(TxId, i32)>>();
|
.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
|
// 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 {
|
for (txid, height) in txids_to_fetch {
|
||||||
let light_wallet_clone = self.wallet.clone();
|
let light_wallet_clone = self.wallet.clone();
|
||||||
info!("Fetching full Tx: {}", txid);
|
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();
|
let tx = Transaction::read(tx_bytes).unwrap();
|
||||||
|
|
||||||
light_wallet_clone.scan_full_tx(&tx, height);
|
light_wallet_clone.scan_full_tx(&tx, height);
|
||||||
@ -716,7 +748,7 @@ impl LightClient {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match rawtx {
|
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,
|
Ok(k) => k,
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,8 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
|
use rand::{Rng, rngs::OsRng};
|
||||||
|
|
||||||
use log::{info, warn, error};
|
use log::{info, warn, error};
|
||||||
|
|
||||||
use protobuf::parse_from_bytes;
|
use protobuf::parse_from_bytes;
|
||||||
@ -86,26 +88,6 @@ impl ToBase58Check for [u8] {
|
|||||||
payload.to_base58()
|
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 {
|
pub struct LightWallet {
|
||||||
seed: [u8; 32], // Seed phrase for this wallet.
|
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> {
|
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
|
// This is the source entropy that corresponds to the 24-word seed phrase
|
||||||
let mut seed_bytes = [0u8; 32];
|
let mut seed_bytes = [0u8; 32];
|
||||||
|
|
||||||
if seed_phrase.is_none() {
|
if seed_phrase.is_none() {
|
||||||
// Create a random seed.
|
// Create a random seed.
|
||||||
let mut system_rng = ChaChaRng::from_entropy();
|
let mut system_rng = OsRng;
|
||||||
system_rng.fill(&mut seed_bytes);
|
system_rng.fill(&mut seed_bytes);
|
||||||
} else {
|
} else {
|
||||||
seed_bytes.copy_from_slice(&Mnemonic::from_phrase(seed_phrase.expect("should have a seed phrase"),
|
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
|
// Write the seed
|
||||||
writer.write_all(&self.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
|
// Write all the spending keys
|
||||||
Vector::write(&mut writer, &self.extsks.read().unwrap(),
|
Vector::write(&mut writer, &self.extsks.read().unwrap(),
|
||||||
|w, sk| sk.write(w)
|
|w, sk| sk.write(w)
|
||||||
@ -816,8 +799,10 @@ impl LightWallet {
|
|||||||
// Mark this Tx as scanned
|
// Mark this Tx as scanned
|
||||||
{
|
{
|
||||||
let mut txs = self.txs.write().unwrap();
|
let mut txs = self.txs.write().unwrap();
|
||||||
let mut wtx = txs.get_mut(&tx.txid()).unwrap();
|
match txs.get_mut(&tx.txid()) {
|
||||||
wtx.full_tx_scanned = true;
|
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
|
// Scan a block. Will return an error with the block height that failed to scan
|
||||||
pub fn scan_block(&self, block: &[u8]) -> Result<(), i32> {
|
pub fn scan_block(&self, block_bytes: &[u8]) -> Result<Vec<TxId>, i32> {
|
||||||
let block: CompactBlock = match parse_from_bytes(block) {
|
let block: CompactBlock = match parse_from_bytes(block_bytes) {
|
||||||
Ok(block) => block,
|
Ok(block) => block,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not parse CompactBlock from bytes: {}", e);
|
error!("Could not parse CompactBlock from bytes: {}", e);
|
||||||
@ -900,7 +885,7 @@ impl LightWallet {
|
|||||||
return Err(height);
|
return Err(height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(())
|
return Ok(vec![]);
|
||||||
} else if height != (self.last_scanned_height() + 1) {
|
} else if height != (self.last_scanned_height() + 1) {
|
||||||
error!(
|
error!(
|
||||||
"Block is not height-sequential (expected {}, found {})",
|
"Block is not height-sequential (expected {}, found {})",
|
||||||
@ -978,7 +963,7 @@ impl LightWallet {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
scan_block(
|
scan_block(
|
||||||
block,
|
block.clone(),
|
||||||
&self.extfvks.read().unwrap(),
|
&self.extfvks.read().unwrap(),
|
||||||
&nf_refs[..],
|
&nf_refs[..],
|
||||||
&mut block_data.tree,
|
&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 {
|
for tx in new_txs {
|
||||||
// Mark notes as spent.
|
// Mark notes as spent.
|
||||||
let mut total_shielded_value_spent: u64 = 0;
|
let mut total_shielded_value_spent: u64 = 0;
|
||||||
@ -1023,9 +1020,7 @@ impl LightWallet {
|
|||||||
tx_entry.total_shielded_value_spent = total_shielded_value_spent;
|
tx_entry.total_shielded_value_spent = total_shielded_value_spent;
|
||||||
|
|
||||||
// Save notes.
|
// Save notes.
|
||||||
for output in tx
|
for output in tx.shielded_outputs
|
||||||
.shielded_outputs
|
|
||||||
.into_iter()
|
|
||||||
{
|
{
|
||||||
info!("Received sapling output");
|
info!("Received sapling output");
|
||||||
|
|
||||||
@ -1059,7 +1054,8 @@ impl LightWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
|
Ok(all_txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_to_address(
|
pub fn send_to_address(
|
||||||
@ -1284,9 +1280,10 @@ impl LightWallet {
|
|||||||
pub mod tests {
|
pub mod tests {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::io::{Error};
|
use std::io::{Error};
|
||||||
|
use rand::{RngCore, rngs::OsRng};
|
||||||
|
|
||||||
use ff::{Field, PrimeField, PrimeFieldRepr};
|
use ff::{Field, PrimeField, PrimeFieldRepr};
|
||||||
use pairing::bls12_381::Bls12;
|
use pairing::bls12_381::Bls12;
|
||||||
use rand_core::{RngCore, OsRng};
|
|
||||||
use protobuf::{Message, UnknownFields, CachedSize, RepeatedField};
|
use protobuf::{Message, UnknownFields, CachedSize, RepeatedField};
|
||||||
use zcash_client_backend::{encoding::encode_payment_address,
|
use zcash_client_backend::{encoding::encode_payment_address,
|
||||||
proto::compact_formats::{
|
proto::compact_formats::{
|
||||||
@ -1938,7 +1935,8 @@ pub mod tests {
|
|||||||
chain_name: "test".to_string(),
|
chain_name: "test".to_string(),
|
||||||
sapling_activation_height: 0,
|
sapling_activation_height: 0,
|
||||||
consensus_branch_id: "000000".to_string(),
|
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(),
|
chain_name: "main".to_string(),
|
||||||
sapling_activation_height: 0,
|
sapling_activation_height: 0,
|
||||||
consensus_branch_id: "000000".to_string(),
|
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());
|
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());
|
||||||
|
283
src/main.rs
283
src/main.rs
@ -8,11 +8,12 @@ mod commands;
|
|||||||
|
|
||||||
use std::io::{Result, Error, ErrorKind};
|
use std::io::{Result, Error, ErrorKind};
|
||||||
use std::sync::{Arc};
|
use std::sync::{Arc};
|
||||||
|
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use lightclient::{LightClient, LightClientConfig};
|
use lightclient::{LightClient, LightClientConfig};
|
||||||
|
|
||||||
use log::{info, LevelFilter};
|
use log::{info, error, LevelFilter};
|
||||||
use log4rs::append::rolling_file::RollingFileAppender;
|
use log4rs::append::rolling_file::RollingFileAppender;
|
||||||
use log4rs::encode::pattern::PatternEncoder;
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
use log4rs::config::{Appender, Config, Root};
|
use log4rs::config::{Appender, Config, Root};
|
||||||
@ -85,6 +86,14 @@ pub fn main() {
|
|||||||
.help("Lightwalletd server to connect to.")
|
.help("Lightwalletd server to connect to.")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value(lightclient::DEFAULT_SERVER))
|
.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")
|
.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.")
|
.help("By default, zecwallet-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.")
|
||||||
.long("nosync")
|
.long("nosync")
|
||||||
@ -100,6 +109,11 @@ pub fn main() {
|
|||||||
.multiple(true))
|
.multiple(true))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
if matches.is_present("recover") {
|
||||||
|
attempt_recover_seed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let command = matches.value_of("COMMAND");
|
let command = matches.value_of("COMMAND");
|
||||||
let params = matches.values_of("PARAMS").map(|v| v.collect()).or(Some(vec![])).unwrap();
|
let params = matches.values_of("PARAMS").map(|v| v.collect()).or(Some(vec![])).unwrap();
|
||||||
|
|
||||||
@ -114,68 +128,175 @@ pub fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a getinfo first, before opening the wallet
|
let dangerous = matches.is_present("dangerous");
|
||||||
let info = match grpcconnector::get_info(server.clone()) {
|
let nosync = matches.is_present("nosync");
|
||||||
Ok(ld) => ld,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error:\n{}\nCouldn't get server info, quitting!", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a Light Client Config
|
let (command_tx, resp_rx) = match startup(server, dangerous, seed, !nosync, command.is_none()) {
|
||||||
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) {
|
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error:\n{}\nCouldn't configure logging, quitting!", e);
|
eprintln!("Error during startup: {}", e);
|
||||||
|
error!("Error during startup: {}", e);
|
||||||
return;
|
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!(""); // Blank line
|
||||||
info!("Starting Zecwallet-CLI");
|
info!("Starting Zecwallet-CLI");
|
||||||
info!("Light Client config {:?}", config);
|
info!("Light Client config {:?}", config);
|
||||||
|
|
||||||
let lightclient = match LightClient::new(seed, &config, info.block_height) {
|
if print_updates {
|
||||||
Ok(lc) => Arc::new(lc),
|
println!("Lightclient connecting to {}", config.server);
|
||||||
Err(e) => { eprintln!("Failed to start wallet. Error was:\n{}", e); return; }
|
}
|
||||||
};
|
|
||||||
|
// Start the command loop
|
||||||
|
let (command_tx, resp_rx) = command_loop(lightclient.clone());
|
||||||
|
|
||||||
// At startup, run a sync.
|
// At startup, run a sync.
|
||||||
let sync_output = if matches.is_present("nosync") {
|
if first_sync {
|
||||||
None
|
let update = lightclient.do_sync(true);
|
||||||
} else {
|
if print_updates {
|
||||||
Some(lightclient.do_sync(true))
|
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() {
|
Ok((config, info.block_height))
|
||||||
// If running in interactive mode, output of the sync command
|
}
|
||||||
if sync_output.is_some() {
|
|
||||||
println!("{}", sync_output.unwrap());
|
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>)>();
|
fn command_loop(lightclient: Arc<LightClient>) -> (Sender<(String, Vec<String>)>, Receiver<String>) {
|
||||||
let (resp_tx, resp_rx) = std::sync::mpsc::channel::<String>();
|
let (command_tx, command_rx) = channel::<(String, Vec<String>)>();
|
||||||
|
let (resp_tx, resp_rx) = channel::<String>();
|
||||||
|
|
||||||
let lc = lightclient.clone();
|
let lc = lightclient.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
@ -201,63 +322,35 @@ fn start_interactive(lightclient: Arc<LightClient>, config: &LightClientConfig)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// `()` can be used when no completer is required
|
(command_tx, resp_rx)
|
||||||
let mut rl = Editor::<()>::new();
|
}
|
||||||
|
|
||||||
println!("Ready!");
|
fn attempt_recover_seed() {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::{BufReader};
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt,};
|
||||||
|
use bip39::{Mnemonic, Language};
|
||||||
|
|
||||||
loop {
|
// Create a Light Client Config in an attempt to recover the file.
|
||||||
let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ",
|
let config = LightClientConfig {
|
||||||
config.chain_name,
|
server: "0.0.0.0:0".parse().unwrap(),
|
||||||
lightclient.last_scanned_height()));
|
chain_name: "main".to_string(),
|
||||||
match readline {
|
sapling_activation_height: 0,
|
||||||
Ok(line) => {
|
consensus_branch_id: "000000".to_string(),
|
||||||
rl.add_history_entry(line.as_str());
|
anchor_offset: 0,
|
||||||
// Parse command line arguments
|
no_cert_verification: false,
|
||||||
let mut cmd_args = match shellwords::split(&line) {
|
|
||||||
Ok(args) => args,
|
|
||||||
Err(_) => {
|
|
||||||
println!("Mismatched Quotes");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if cmd_args.is_empty() {
|
let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap());
|
||||||
continue;
|
let version = reader.read_u64::<LittleEndian>().unwrap();
|
||||||
}
|
println!("Reading wallet version {}", version);
|
||||||
|
|
||||||
let cmd = cmd_args.remove(0);
|
// Seed
|
||||||
let args: Vec<String> = cmd_args;
|
let mut seed_bytes = [0u8; 32];
|
||||||
command_tx.send((cmd, args)).unwrap();
|
reader.read_exact(&mut seed_bytes).unwrap();
|
||||||
|
|
||||||
// Wait for the response
|
let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string();
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
println!("Recovered seed phrase:\n{}", phrase);
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user