merged new updates

This commit is contained in:
DenioD
2019-10-20 23:12:03 +02:00
parent 457c3ea61e
commit 8c9523ad90
11 changed files with 1153 additions and 291 deletions

View File

@@ -7,10 +7,13 @@ use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicU64, AtomicI32, AtomicUsize, Ordering};
use std::path::Path;
use std::fs::File;
use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter, Error, ErrorKind};
use protobuf::parse_from_bytes;
use json::{object, array, JsonValue};
use zcash_primitives::transaction::{TxId, Transaction};
use zcash_client_backend::{
@@ -23,8 +26,8 @@ use crate::SaplingParams;
use crate::ANCHOR_OFFSET;
pub const DEFAULT_SERVER: &str = "https://";
pub const WALLET_NAME: &str = "silentdragonlite-cli.dat";
pub const LOGFILE_NAME: &str = "silentdragonlite-cli.debug.log";
pub const WALLET_NAME: &str = "silentdragonlite-cli-wallet.dat";
pub const LOGFILE_NAME: &str = "silentdragonlite-cli-wallet.debug.log";
#[derive(Clone, Debug)]
@@ -39,6 +42,18 @@ pub struct LightClientConfig {
impl LightClientConfig {
// Create an unconnected (to any server) config to test for local wallet etc...
pub fn create_unconnected(chain_name: String) -> LightClientConfig {
LightClientConfig {
server : http::Uri::default(),
chain_name : chain_name,
sapling_activation_height : 0,
consensus_branch_id : "".to_string(),
anchor_offset : ANCHOR_OFFSET,
no_cert_verification : false,
}
}
pub fn create(server: http::Uri, dangerous: bool) -> io::Result<(LightClientConfig, u64)> {
// Do a getinfo first, before opening the wallet
let info = grpcconnector::get_info(server.clone(), dangerous)
@@ -64,7 +79,7 @@ impl LightClientConfig {
zcash_data_location.push("HUSH3");
} else {
zcash_data_location = dirs::home_dir().expect("Couldn't determine home directory!");
zcash_data_location.push(".komodo/HUSH3/SilentDragonLite/");
zcash_data_location.push(".komodo/HUSH3/");
};
match &self.chain_name[..] {
@@ -84,6 +99,10 @@ impl LightClientConfig {
wallet_location.into_boxed_path()
}
pub fn wallet_exists(&self) -> bool {
return self.get_wallet_path().exists()
}
pub fn get_log_path(&self) -> Box<Path> {
let mut log_path = self.get_zcash_data_path().into_path_buf();
log_path.push(LOGFILE_NAME);
@@ -99,7 +118,7 @@ impl LightClientConfig {
)),
"main" => Some((105944,
"0000000313b0ec7c5a1e9b997ce44a7763b56c5505526c36634a004ed52d7787",
""
""
)),
_ => None
}
@@ -150,7 +169,6 @@ impl LightClientConfig {
match &self.chain_name[..] {
"main" => mainnet::B58_PUBKEY_ADDRESS_PREFIX,
c => panic!("Unknown chain {}", c)
}
}
@@ -159,8 +177,7 @@ impl LightClientConfig {
pub fn base58_script_address(&self) -> [u8; 1] {
match &self.chain_name[..] {
"main" => mainnet::B58_SCRIPT_ADDRESS_PREFIX,
c => panic!("Unknown chain {}", c)
}
}
@@ -176,7 +193,7 @@ impl LightClientConfig {
}
pub struct LightClient {
pub wallet : Arc<LightWallet>,
pub wallet : Arc<RwLock<LightWallet>>,
pub config : LightClientConfig,
@@ -193,63 +210,88 @@ impl LightClient {
let state = self.config.get_initial_state();
match state {
Some((height, hash, tree)) => self.wallet.set_initial_block(height.try_into().unwrap(), hash, tree),
Some((height, hash, tree)) => self.wallet.read().unwrap().set_initial_block(height.try_into().unwrap(), hash, tree),
_ => true,
};
}
pub fn new(seed_phrase: Option<String>, config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
let mut lc = if config.get_wallet_path().exists() {
// Make sure that if a wallet exists, there is no seed phrase being attempted
if !seed_phrase.is_none() {
return Err(Error::new(ErrorKind::AlreadyExists,
"Cannot create a new wallet from seed, because a wallet already exists"));
}
fn read_sapling_params(&mut self) {
// Read Sapling Params
self.sapling_output.extend_from_slice(SaplingParams::get("sapling-output.params").unwrap().as_ref());
self.sapling_spend.extend_from_slice(SaplingParams::get("sapling-spend.params").unwrap().as_ref());
let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?);
let wallet = LightWallet::read(&mut file_buffer, config)?;
LightClient {
wallet : Arc::new(wallet),
config : config.clone(),
sapling_output : vec![],
sapling_spend : vec![]
}
} else {
let l = LightClient {
wallet : Arc::new(LightWallet::new(seed_phrase, config, latest_block)?),
}
pub fn new_from_phrase(seed_phrase: String, config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
if config.get_wallet_path().exists() {
return Err(Error::new(ErrorKind::AlreadyExists,
"Cannot create a new wallet from seed, because a wallet already exists"));
}
let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, latest_block)?)),
config : config.clone(),
sapling_output : vec![],
sapling_spend : vec![]
};
l.set_wallet_initial_state();
l.set_wallet_initial_state();
l.read_sapling_params();
l
info!("Created new wallet!");
info!("Created LightClient to {}", &config.server);
Ok(l)
}
pub fn read_from_disk(config: &LightClientConfig) -> io::Result<Self> {
if !config.get_wallet_path().exists() {
return Err(Error::new(ErrorKind::AlreadyExists,
format!("Cannot read wallet. No file at {}", config.get_wallet_path().display())));
}
let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?);
let wallet = LightWallet::read(&mut file_buffer, config)?;
let mut lc = LightClient {
wallet : Arc::new(RwLock::new(wallet)),
config : config.clone(),
sapling_output : vec![],
sapling_spend : vec![]
};
info!("Read wallet with birthday {}", lc.wallet.get_first_tx_block());
// Read Sapling Params
lc.sapling_output.extend_from_slice(SaplingParams::get("sapling-output.params").unwrap().as_ref());
lc.sapling_spend.extend_from_slice(SaplingParams::get("sapling-spend.params").unwrap().as_ref());
lc.read_sapling_params();
info!("Read wallet with birthday {}", lc.wallet.read().unwrap().get_first_tx_block());
info!("Created LightClient to {}", &config.server);
if crate::lightwallet::bugs::BugBip39Derivation::has_bug(&lc) {
let m = format!("WARNING!!!\nYour wallet has a bip39derivation bug that's showing incorrect addresses.\nPlease run 'fixbip39bug' to automatically fix the address derivation in your wallet!\nPlease see: https://github.com/adityapk00/zecwallet-light-cli/blob/master/bip39bug.md");
info!("{}", m);
println!("{}", m);
}
Ok(lc)
}
pub fn last_scanned_height(&self) -> u64 {
self.wallet.last_scanned_height() as u64
self.wallet.read().unwrap().last_scanned_height() as u64
}
// Export private keys
pub fn do_export(&self, addr: Option<String>) -> JsonValue {
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
error!("Wallet is locked");
return object!{
"error" => "Wallet is locked"
};
}
// Clone address so it can be moved into the closure
let address = addr.clone();
let wallet = self.wallet.read().unwrap();
// Go over all z addresses
let z_keys = self.wallet.get_z_private_keys().iter()
let z_keys = wallet.get_z_private_keys().iter()
.filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr))
.map( |(addr, pk)|
object!{
@@ -262,7 +304,7 @@ impl LightClient {
let address = addr.clone();
// Go over all t addresses
let t_keys = self.wallet.get_t_secret_keys().iter()
let t_keys = wallet.get_t_secret_keys().iter()
.filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr))
.map( |(addr, sk)|
object!{
@@ -279,15 +321,15 @@ impl LightClient {
}
pub fn do_address(&self) -> JsonValue {
let wallet = self.wallet.read().unwrap();
// Collect z addresses
let z_addresses = self.wallet.address.read().unwrap().iter().map( |ad| {
let z_addresses = wallet.zaddress.read().unwrap().iter().map( |ad| {
encode_payment_address(self.config.hrp_sapling_address(), &ad)
}).collect::<Vec<String>>();
// Collect t addresses
let t_addresses = self.wallet.tkeys.read().unwrap().iter().map( |sk| {
self.wallet.address_from_sk(&sk)
}).collect::<Vec<String>>();
let t_addresses = wallet.taddresses.read().unwrap().iter().map( |a| a.clone() )
.collect::<Vec<String>>();
object!{
"z_addresses" => z_addresses,
@@ -296,47 +338,67 @@ impl LightClient {
}
pub fn do_balance(&self) -> JsonValue {
let wallet = self.wallet.read().unwrap();
// Collect z addresses
let z_addresses = self.wallet.address.read().unwrap().iter().map( |ad| {
let z_addresses = wallet.zaddress.read().unwrap().iter().map( |ad| {
let address = encode_payment_address(self.config.hrp_sapling_address(), &ad);
object!{
"address" => address.clone(),
"zbalance" => self.wallet.zbalance(Some(address.clone())),
"verified_zbalance" => self.wallet.verified_zbalance(Some(address)),
"zbalance" => wallet.zbalance(Some(address.clone())),
"verified_zbalance" => wallet.verified_zbalance(Some(address)),
}
}).collect::<Vec<JsonValue>>();
// Collect t addresses
let t_addresses = self.wallet.tkeys.read().unwrap().iter().map( |sk| {
let address = self.wallet.address_from_sk(&sk);
let t_addresses = wallet.taddresses.read().unwrap().iter().map( |address| {
// Get the balance for this address
let balance = self.wallet.tbalance(Some(address.clone()));
let balance = wallet.tbalance(Some(address.clone()));
object!{
"address" => address,
"address" => address.clone(),
"balance" => balance,
}
}).collect::<Vec<JsonValue>>();
object!{
"zbalance" => self.wallet.zbalance(None),
"verified_zbalance" => self.wallet.verified_zbalance(None),
"tbalance" => self.wallet.tbalance(None),
"zbalance" => wallet.zbalance(None),
"verified_zbalance" => wallet.verified_zbalance(None),
"tbalance" => wallet.tbalance(None),
"z_addresses" => z_addresses,
"t_addresses" => t_addresses,
}
}
pub fn do_save(&self) -> String {
pub fn do_save(&self) -> Result<(), String> {
// If the wallet is encrypted but unlocked, lock it again.
{
let mut wallet = self.wallet.write().unwrap();
if wallet.is_encrypted() && wallet.is_unlocked_for_spending() {
match wallet.lock() {
Ok(_) => {},
Err(e) => {
let err = format!("ERR: {}", e);
error!("{}", err);
return Err(e.to_string());
}
}
}
}
let mut file_buffer = BufWriter::with_capacity(
1_000_000, // 1 MB write buffer
File::create(self.config.get_wallet_path()).unwrap());
self.wallet.write(&mut file_buffer).unwrap();
info!("Saved wallet");
format!("Saved Wallet")
match self.wallet.write().unwrap().write(&mut file_buffer) {
Ok(_) => Ok(()),
Err(e) => {
let err = format!("ERR: {}", e);
error!("{}", err);
Err(e.to_string())
}
}
}
pub fn get_server_uri(&self) -> http::Uri {
@@ -362,9 +424,17 @@ impl LightClient {
}
pub fn do_seed_phrase(&self) -> JsonValue {
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
error!("Wallet is locked");
return object!{
"error" => "Wallet is locked"
};
}
let wallet = self.wallet.read().unwrap();
object!{
"seed" => self.wallet.get_seed_phrase(),
"birthday" => self.wallet.get_birthday()
"seed" => wallet.get_seed_phrase(),
"birthday" => wallet.get_birthday()
}
}
@@ -374,108 +444,95 @@ impl LightClient {
let mut spent_notes : Vec<JsonValue> = vec![];
let mut pending_notes: Vec<JsonValue> = vec![];
// Collect Sapling notes
self.wallet.txs.read().unwrap().iter()
.flat_map( |(txid, wtx)| {
wtx.notes.iter().filter_map(move |nd|
if !all_notes && nd.spent.is_some() {
None
{
// Collect Sapling notes
let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter()
.flat_map( |(txid, wtx)| {
wtx.notes.iter().filter_map(move |nd|
if !all_notes && nd.spent.is_some() {
None
} else {
Some(object!{
"created_in_block" => wtx.block,
"datetime" => wtx.datetime,
"created_in_txid" => format!("{}", txid),
"value" => nd.note.value,
"is_change" => nd.is_change,
"address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd),
"spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => nd.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
})
}
)
})
.for_each( |note| {
if note["spent"].is_null() && note["unconfirmed_spent"].is_null() {
unspent_notes.push(note);
} else if !note["spent"].is_null() {
spent_notes.push(note);
} else {
Some(object!{
"created_in_block" => wtx.block,
"created_in_txid" => format!("{}", txid),
"value" => nd.note.value,
"is_change" => nd.is_change,
"address" => self.wallet.note_address(nd),
"spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => nd.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
})
pending_notes.push(note);
}
)
})
.for_each( |note| {
if note["spent"].is_null() && note["unconfirmed_spent"].is_null() {
unspent_notes.push(note);
} else if !note["spent"].is_null() {
spent_notes.push(note);
} else {
pending_notes.push(note);
}
});
});
}
// Collect UTXOs
let utxos = self.wallet.get_utxos().iter()
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Filter out unconfirmed from the list of utxos
.map(|utxo| {
object!{
"created_in_block" => utxo.height,
"created_in_txid" => format!("{}", utxo.txid),
"value" => utxo.value,
"scriptkey" => hex::encode(utxo.script.clone()),
"is_change" => false, // TODO: Identify notes as change if we send change to taddrs
"address" => utxo.address.clone(),
"spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
}
})
.collect::<Vec<JsonValue>>();
// Collect pending UTXOs
let pending_utxos = self.wallet.get_utxos().iter()
.filter(|utxo| utxo.unconfirmed_spent.is_some()) // Filter to include only unconfirmed utxos
.map(|utxo|
object!{
"created_in_block" => utxo.height,
"created_in_txid" => format!("{}", utxo.txid),
"value" => utxo.value,
"scriptkey" => hex::encode(utxo.script.clone()),
"is_change" => false, // TODO: Identify notes as change if we send change to taddrs
"address" => utxo.address.clone(),
"spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
}
)
.collect::<Vec<JsonValue>>();;
let mut unspent_utxos: Vec<JsonValue> = vec![];
let mut spent_utxos : Vec<JsonValue> = vec![];
let mut pending_utxos: Vec<JsonValue> = vec![];
{
let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter()
.flat_map( |(txid, wtx)| {
wtx.utxos.iter().filter_map(move |utxo|
if !all_notes && utxo.spent.is_some() {
None
} else {
Some(object!{
"created_in_block" => wtx.block,
"datetime" => wtx.datetime,
"created_in_txid" => format!("{}", txid),
"value" => utxo.value,
"scriptkey" => hex::encode(utxo.script.clone()),
"is_change" => false, // TODO: Identify notes as change if we send change to taddrs
"address" => utxo.address.clone(),
"spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
})
}
)
})
.for_each( |utxo| {
if utxo["spent"].is_null() && utxo["unconfirmed_spent"].is_null() {
unspent_utxos.push(utxo);
} else if !utxo["spent"].is_null() {
spent_utxos.push(utxo);
} else {
pending_utxos.push(utxo);
}
});
}
let mut res = object!{
"unspent_notes" => unspent_notes,
"pending_notes" => pending_notes,
"utxos" => utxos,
"utxos" => unspent_utxos,
"pending_utxos" => pending_utxos,
};
if all_notes {
res["spent_notes"] = JsonValue::Array(spent_notes);
}
// If all notes, also add historical utxos
if all_notes {
res["spent_utxos"] = JsonValue::Array(self.wallet.txs.read().unwrap().values()
.flat_map(|wtx| {
wtx.utxos.iter()
.filter(|utxo| utxo.spent.is_some())
.map(|utxo| {
object!{
"created_in_block" => wtx.block,
"created_in_txid" => format!("{}", utxo.txid),
"value" => utxo.value,
"scriptkey" => hex::encode(utxo.script.clone()),
"is_change" => false, // TODO: Identify notes as change if we send change to taddrs
"address" => utxo.address.clone(),
"spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)),
"unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)),
}
}).collect::<Vec<JsonValue>>()
}).collect::<Vec<JsonValue>>()
);
res["spent_utxos"] = JsonValue::Array(spent_utxos);
}
res
}
pub fn do_list_transactions(&self) -> JsonValue {
let wallet = self.wallet.read().unwrap();
// Create a list of TransactionItems
let mut tx_list = self.wallet.txs.read().unwrap().iter()
let mut tx_list = wallet.txs.read().unwrap().iter()
.flat_map(| (_k, v) | {
let mut txns: Vec<JsonValue> = vec![];
@@ -501,6 +558,7 @@ impl LightClient {
txns.push(object! {
"block_height" => v.block,
"datetime" => v.datetime,
"txid" => format!("{}", v.txid),
"amount" => total_change as i64
- v.total_shielded_value_spent as i64
@@ -515,9 +573,10 @@ impl LightClient {
.map ( |nd|
object! {
"block_height" => v.block,
"datetime" => v.datetime,
"txid" => format!("{}", v.txid),
"amount" => nd.note.value as i64,
"address" => self.wallet.note_address(nd),
"address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd),
"memo" => LightWallet::memo_str(&nd.memo),
})
);
@@ -528,6 +587,7 @@ impl LightClient {
// Create an input transaction for the transparent value as well.
txns.push(object!{
"block_height" => v.block,
"datetime" => v.datetime,
"txid" => format!("{}", v.txid),
"amount" => total_transparent_received as i64 - v.total_transparent_value_spent as i64,
"address" => v.utxos.iter().map(|u| u.address.clone()).collect::<Vec<String>>().join(","),
@@ -551,9 +611,18 @@ impl LightClient {
/// Create a new address, deriving it from the seed.
pub fn do_new_address(&self, addr_type: &str) -> JsonValue {
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
error!("Wallet is locked");
return object!{
"error" => "Wallet is locked"
};
}
let wallet = self.wallet.write().unwrap();
let new_address = match addr_type {
"z" => self.wallet.add_zaddr(),
"t" => self.wallet.add_taddr(),
"z" => wallet.add_zaddr(),
"t" => wallet.add_taddr(),
_ => {
let e = format!("Unrecognized address type: {}", addr_type);
error!("{}", e);
@@ -569,7 +638,7 @@ impl LightClient {
pub fn do_rescan(&self) -> String {
info!("Rescan starting");
// First, clear the state from the wallet
self.wallet.clear_blocks();
self.wallet.read().unwrap().clear_blocks();
// Then set the initial block
self.set_wallet_initial_state();
@@ -587,7 +656,7 @@ impl LightClient {
// 2. Get all the blocks that we don't have
// 3. Find all new Txns that don't have the full Tx, and get them as full transactions
// and scan them, mainly to get the memos
let mut last_scanned_height = self.wallet.last_scanned_height() as u64;
let mut last_scanned_height = self.wallet.read().unwrap().last_scanned_height() as u64;
// This will hold the latest block fetched from the RPC
let latest_block_height = Arc::new(AtomicU64::new(0));
@@ -626,6 +695,10 @@ impl LightClient {
// Fetch CompactBlocks in increments
loop {
// Collect all block times, because we'll need to update transparent tx
// datetime via the block height timestamp
let block_times = Arc::new(RwLock::new(HashMap::new()));
let local_light_wallet = self.wallet.clone();
let local_bytes_downloaded = bytes_downloaded.clone();
@@ -640,7 +713,9 @@ impl LightClient {
// Fetch compact blocks
info!("Fetching blocks {}-{}", start_height, end_height);
let all_txs = all_new_txs.clone();
let block_times_inner = block_times.clone();
let last_invalid_height = Arc::new(AtomicI32::new(0));
let last_invalid_height_inner = last_invalid_height.clone();
@@ -651,8 +726,18 @@ impl LightClient {
return;
}
match local_light_wallet.scan_block(encoded_block) {
let block: Result<zcash_client_backend::proto::compact_formats::CompactBlock, _>
= parse_from_bytes(encoded_block);
match block {
Ok(b) => {
block_times_inner.write().unwrap().insert(b.height, b.time);
},
Err(_) => {}
}
match local_light_wallet.read().unwrap().scan_block(encoded_block) {
Ok(block_txns) => {
// Add to global tx list
all_txs.write().unwrap().extend_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::<Vec<_>>()[..]);
},
Err(invalid_height) => {
@@ -667,7 +752,7 @@ impl LightClient {
// Check if there was any invalid block, which means we might have to do a reorg
let invalid_height = last_invalid_height.load(Ordering::SeqCst);
if invalid_height > 0 {
total_reorg += self.wallet.invalidate_block(invalid_height);
total_reorg += self.wallet.read().unwrap().invalidate_block(invalid_height);
warn!("Invalidated block at height {}. Total reorg is now {}", invalid_height, total_reorg);
}
@@ -693,18 +778,28 @@ impl LightClient {
total_reorg = 0;
// We'll also fetch all the txids that our transparent addresses are involved with
// 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, self.config.no_cert_verification,
move |tx_bytes: &[u8], height: u64 | {
let tx = Transaction::read(tx_bytes).unwrap();
{
// Copy over addresses so as to not lock up the wallet, which we'll use inside the callback below.
let addresses = self.wallet.read().unwrap()
.taddresses.read().unwrap().iter().map(|a| a.clone())
.collect::<Vec<String>>();
for address in addresses {
let wallet = self.wallet.clone();
let block_times_inner = block_times.clone();
// Scan this Tx for transparent inputs and outputs
wallet.scan_full_tx(&tx, height as i32);
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();
// Scan this Tx for transparent inputs and outputs
let datetime = block_times_inner.read().unwrap().get(&height).map(|v| *v).unwrap_or(0);
wallet.read().unwrap().scan_full_tx(&tx, height as i32, datetime as u64);
}
);
}
);
}
// Do block height accounting
last_scanned_height = end_height;
end_height = last_scanned_height + 1000;
@@ -712,8 +807,9 @@ impl LightClient {
break;
} else if end_height > latest_block {
end_height = latest_block;
}
}
}
if print_updates{
println!(""); // New line to finish up the updates
}
@@ -727,10 +823,10 @@ 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 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)>>();
let mut txids_to_fetch: Vec<(TxId, i32)> = self.wallet.read().unwrap().txs.read().unwrap().values()
.filter(|wtx| wtx.full_tx_scanned == false)
.map(|wtx| (wtx.txid.clone(), wtx.block))
.collect::<Vec<(TxId, i32)>>();
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()[..]);
@@ -742,7 +838,6 @@ impl LightClient {
// 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);
@@ -750,27 +845,30 @@ impl LightClient {
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);
light_wallet_clone.read().unwrap().scan_full_tx(&tx, height, 0);
});
};
responses.join("\n")
}
pub fn do_send(&self, addr: &str, value: u64, memo: Option<String>) -> String {
pub fn do_send(&self, addrs: Vec<(&str, u64, Option<String>)>) -> Result<String, String> {
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
error!("Wallet is locked");
return Err("Wallet is locked".to_string());
}
info!("Creating transaction");
let rawtx = self.wallet.send_to_address(
u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(), // Blossom ID
let rawtx = self.wallet.write().unwrap().send_to_address(
u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(),
&self.sapling_spend, &self.sapling_output,
vec![(&addr, value, memo)]
addrs
);
match rawtx {
Ok(txbytes) => match broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes) {
Ok(k) => k,
Err(e) => e,
},
Err(e) => format!("No Tx to broadcast. Error was: {}", e)
Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes),
Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e))
}
}
}