@@ -1,18 +1,8 @@
|
|||||||
use std::io::{self, Error, ErrorKind};
|
use std::io::{self};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
|
|
||||||
use log::{info, error, LevelFilter};
|
use log::{info, error};
|
||||||
use log4rs::append::rolling_file::RollingFileAppender;
|
|
||||||
use log4rs::encode::pattern::PatternEncoder;
|
|
||||||
use log4rs::config::{Appender, Config, Root};
|
|
||||||
use log4rs::filter::threshold::ThresholdFilter;
|
|
||||||
use log4rs::append::rolling_file::policy::compound::{
|
|
||||||
CompoundPolicy,
|
|
||||||
trigger::size::SizeTrigger,
|
|
||||||
roll::fixed_window::FixedWindowRoller,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
use silentdragonlitelib::{commands,
|
use silentdragonlitelib::{commands,
|
||||||
lightclient::{LightClient, LightClientConfig},
|
lightclient::{LightClient, LightClientConfig},
|
||||||
@@ -85,48 +75,11 @@ pub fn report_permission_error() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the Logging config
|
|
||||||
pub fn get_log_config(config: &LightClientConfig) -> io::Result<Config> {
|
|
||||||
let window_size = 3; // log0, log1, log2
|
|
||||||
let fixed_window_roller =
|
|
||||||
FixedWindowRoller::builder().build("Silentdragonlite-wallet-log{}",window_size).unwrap();
|
|
||||||
let size_limit = 5 * 1024 * 1024; // 5MB as max log file size to roll
|
|
||||||
let size_trigger = SizeTrigger::new(size_limit);
|
|
||||||
let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller));
|
|
||||||
|
|
||||||
Config::builder()
|
|
||||||
.appender(
|
|
||||||
Appender::builder()
|
|
||||||
.filter(Box::new(ThresholdFilter::new(LevelFilter::Info)))
|
|
||||||
.build(
|
|
||||||
"logfile",
|
|
||||||
Box::new(
|
|
||||||
RollingFileAppender::builder()
|
|
||||||
.encoder(Box::new(PatternEncoder::new("{d} {l}::{m}{n}")))
|
|
||||||
.build(config.get_log_path(), Box::new(compound_policy))?,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.build(
|
|
||||||
Root::builder()
|
|
||||||
.appender("logfile")
|
|
||||||
.build(LevelFilter::Debug),
|
|
||||||
)
|
|
||||||
.map_err(|e|Error::new(ErrorKind::Other, format!("{}", e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn startup(server: http::Uri, dangerous: bool, seed: Option<String>, birthday: u64, first_sync: bool, print_updates: bool)
|
pub fn startup(server: http::Uri, dangerous: bool, seed: Option<String>, birthday: u64, first_sync: bool, print_updates: bool)
|
||||||
-> io::Result<(Sender<(String, Vec<String>)>, Receiver<String>)> {
|
-> io::Result<(Sender<(String, Vec<String>)>, Receiver<String>)> {
|
||||||
// Try to get the configuration
|
// Try to get the configuration
|
||||||
let (config, latest_block_height) = LightClientConfig::create(server.clone(), dangerous)?;
|
let (config, latest_block_height) = LightClientConfig::create(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 = match seed {
|
let lightclient = match seed {
|
||||||
Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday)?),
|
Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday)?),
|
||||||
None => {
|
None => {
|
||||||
@@ -139,6 +92,9 @@ pub fn startup(server: http::Uri, dangerous: bool, seed: Option<String>, birthda
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
lightclient.init_logging()?;
|
||||||
|
|
||||||
// Print startup Messages
|
// Print startup Messages
|
||||||
info!(""); // Blank line
|
info!(""); // Blank line
|
||||||
info!("Starting Silentdragonlite-CLI");
|
info!("Starting Silentdragonlite-CLI");
|
||||||
@@ -191,7 +147,7 @@ pub fn start_interactive(command_tx: Sender<(String, Vec<String>)>, resp_rx: Rec
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read the height first
|
// Read the height first
|
||||||
let height = json::parse(&send_command("height".to_string(), vec![])).unwrap()["height"].as_i64().unwrap();
|
let height = json::parse(&send_command("height".to_string(), vec!["false".to_string()])).unwrap()["height"].as_i64().unwrap();
|
||||||
|
|
||||||
let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ",
|
let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ",
|
||||||
chain_name, height));
|
chain_name, height));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
use json::{object};
|
use json::{object};
|
||||||
|
|
||||||
use crate::lightclient::LightClient;
|
use crate::lightclient::LightClient;
|
||||||
|
use crate::lightwallet::LightWallet;
|
||||||
|
|
||||||
pub trait Command {
|
pub trait Command {
|
||||||
fn help(&self) -> String;
|
fn help(&self) -> String;
|
||||||
@@ -110,6 +111,32 @@ impl Command for RescanCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ClearCommand {}
|
||||||
|
impl Command for ClearCommand {
|
||||||
|
fn help(&self) -> String {
|
||||||
|
let mut h = vec![];
|
||||||
|
h.push("Clear the wallet state, rolling back the wallet to an empty state.");
|
||||||
|
h.push("Usage:");
|
||||||
|
h.push("clear");
|
||||||
|
h.push("");
|
||||||
|
h.push("This command will clear all notes, utxos and transactions from the wallet, setting up the wallet to be synced from scratch.");
|
||||||
|
|
||||||
|
h.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_help(&self) -> String {
|
||||||
|
"Clear the wallet state, rolling back the wallet to an empty state.".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
|
||||||
|
lightclient.clear_state();
|
||||||
|
|
||||||
|
let result = object!{ "result" => "success" };
|
||||||
|
|
||||||
|
result.pretty(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct HelpCommand {}
|
struct HelpCommand {}
|
||||||
impl Command for HelpCommand {
|
impl Command for HelpCommand {
|
||||||
fn help(&self) -> String {
|
fn help(&self) -> String {
|
||||||
@@ -487,6 +514,8 @@ impl Command for SendCommand {
|
|||||||
Err(s) => { return format!("Error: {}\n{}", s, self.help()); }
|
Err(s) => { return format!("Error: {}\n{}", s, self.help()); }
|
||||||
}
|
}
|
||||||
} else if args.len() == 2 || args.len() == 3 {
|
} else if args.len() == 2 || args.len() == 3 {
|
||||||
|
let address = args[0].to_string();
|
||||||
|
|
||||||
// Make sure we can parse the amount
|
// Make sure we can parse the amount
|
||||||
let value = match args[1].parse::<u64>() {
|
let value = match args[1].parse::<u64>() {
|
||||||
Ok(amt) => amt,
|
Ok(amt) => amt,
|
||||||
@@ -495,7 +524,12 @@ impl Command for SendCommand {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let memo = if args.len() == 3 { Some(args[2].to_string()) } else {None};
|
let memo = if args.len() == 3 { Some(args[2].to_string()) } else { None };
|
||||||
|
|
||||||
|
// Memo has to be None if not sending to a shileded address
|
||||||
|
if memo.is_some() && !LightWallet::is_shielded_address(&address, &lightclient.config) {
|
||||||
|
return format!("Can't send a memo to the non-shielded address {}", address);
|
||||||
|
}
|
||||||
|
|
||||||
vec![(args[0].to_string(), value, memo)]
|
vec![(args[0].to_string(), value, memo)]
|
||||||
} else {
|
} else {
|
||||||
@@ -606,10 +640,11 @@ struct HeightCommand {}
|
|||||||
impl Command for HeightCommand {
|
impl Command for HeightCommand {
|
||||||
fn help(&self) -> String {
|
fn help(&self) -> String {
|
||||||
let mut h = vec![];
|
let mut h = vec![];
|
||||||
h.push("Get the latest block height that the wallet is at");
|
h.push("Get the latest block height that the wallet is at.");
|
||||||
h.push("Usage:");
|
h.push("Usage:");
|
||||||
h.push("height");
|
h.push("height [do_sync = true | false]");
|
||||||
h.push("");
|
h.push("");
|
||||||
|
h.push("Pass 'true' (default) to sync to the server to get the latest block height. Pass 'false' to get the latest height in the wallet without checking with the server.");
|
||||||
|
|
||||||
h.join("\n")
|
h.join("\n")
|
||||||
}
|
}
|
||||||
@@ -618,11 +653,19 @@ impl Command for HeightCommand {
|
|||||||
"Get the latest block height that the wallet is at".to_string()
|
"Get the latest block height that the wallet is at".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
|
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
|
||||||
format!("{}",
|
if args.len() > 1 {
|
||||||
object! {
|
return format!("Didn't understand arguments\n{}", self.help());
|
||||||
"height" => lightclient.last_scanned_height()
|
}
|
||||||
}.pretty(2))
|
|
||||||
|
if args.len() == 1 && args[0].trim() == "true" {
|
||||||
|
match lightclient.do_sync(true) {
|
||||||
|
Ok(_) => format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)),
|
||||||
|
Err(e) => e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,6 +796,7 @@ pub fn get_commands() -> Box<HashMap<String, Box<dyn Command>>> {
|
|||||||
map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{}));
|
map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{}));
|
||||||
map.insert("encryptionstatus".to_string(), Box::new(EncryptionStatusCommand{}));
|
map.insert("encryptionstatus".to_string(), Box::new(EncryptionStatusCommand{}));
|
||||||
map.insert("rescan".to_string(), Box::new(RescanCommand{}));
|
map.insert("rescan".to_string(), Box::new(RescanCommand{}));
|
||||||
|
map.insert("clear".to_string(), Box::new(ClearCommand{}));
|
||||||
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{}));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::lightwallet::LightWallet;
|
use crate::lightwallet::LightWallet;
|
||||||
|
|
||||||
use log::{info, warn, error};
|
|
||||||
use rand::{rngs::OsRng, seq::SliceRandom};
|
use rand::{rngs::OsRng, seq::SliceRandom};
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock, Mutex};
|
use std::sync::{Arc, RwLock, Mutex};
|
||||||
@@ -20,6 +19,17 @@ use zcash_client_backend::{
|
|||||||
constants::testnet, constants::mainnet, constants::regtest, encoding::encode_payment_address,
|
constants::testnet, constants::mainnet, constants::regtest, encoding::encode_payment_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use log::{info, warn, error, LevelFilter};
|
||||||
|
use log4rs::append::rolling_file::RollingFileAppender;
|
||||||
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
|
use log4rs::config::{Appender, Config, Root};
|
||||||
|
use log4rs::filter::threshold::ThresholdFilter;
|
||||||
|
use log4rs::append::rolling_file::policy::compound::{
|
||||||
|
CompoundPolicy,
|
||||||
|
trigger::size::SizeTrigger,
|
||||||
|
roll::fixed_window::FixedWindowRoller,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::grpc_client::{BlockId};
|
use crate::grpc_client::{BlockId};
|
||||||
use crate::grpcconnector::{self, *};
|
use crate::grpcconnector::{self, *};
|
||||||
use crate::SaplingParams;
|
use crate::SaplingParams;
|
||||||
@@ -100,6 +110,37 @@ impl LightClientConfig {
|
|||||||
Ok((config, info.block_height))
|
Ok((config, info.block_height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Build the Logging config
|
||||||
|
pub fn get_log_config(&self) -> io::Result<Config> {
|
||||||
|
let window_size = 3; // log0, log1, log2
|
||||||
|
let fixed_window_roller =
|
||||||
|
FixedWindowRoller::builder().build("zecwallet-light-wallet-log{}",window_size).unwrap();
|
||||||
|
let size_limit = 5 * 1024 * 1024; // 5MB as max log file size to roll
|
||||||
|
let size_trigger = SizeTrigger::new(size_limit);
|
||||||
|
let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller));
|
||||||
|
|
||||||
|
Config::builder()
|
||||||
|
.appender(
|
||||||
|
Appender::builder()
|
||||||
|
.filter(Box::new(ThresholdFilter::new(LevelFilter::Info)))
|
||||||
|
.build(
|
||||||
|
"logfile",
|
||||||
|
Box::new(
|
||||||
|
RollingFileAppender::builder()
|
||||||
|
.encoder(Box::new(PatternEncoder::new("{d} {l}::{m}{n}")))
|
||||||
|
.build(self.get_log_path(), Box::new(compound_policy))?,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.build(
|
||||||
|
Root::builder()
|
||||||
|
.appender("logfile")
|
||||||
|
.build(LevelFilter::Debug),
|
||||||
|
)
|
||||||
|
.map_err(|e|Error::new(ErrorKind::Other, format!("{}", e)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_zcash_data_path(&self) -> Box<Path> {
|
pub fn get_zcash_data_path(&self) -> Box<Path> {
|
||||||
let mut zcash_data_location;
|
let mut zcash_data_location;
|
||||||
if self.data_dir.is_some() {
|
if self.data_dir.is_some() {
|
||||||
@@ -361,6 +402,16 @@ impl LightClient {
|
|||||||
Ok(lc)
|
Ok(lc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_logging(&self) -> io::Result<()> {
|
||||||
|
// Configure logging first.
|
||||||
|
let log_config = self.config.get_log_config()?;
|
||||||
|
log4rs::init_config(log_config).map_err(|e| {
|
||||||
|
std::io::Error::new(ErrorKind::Other, e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn attempt_recover_seed(config: &LightClientConfig) -> Result<String, String> {
|
pub fn attempt_recover_seed(config: &LightClientConfig) -> Result<String, String> {
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use byteorder::{LittleEndian, ReadBytesExt,};
|
use byteorder::{LittleEndian, ReadBytesExt,};
|
||||||
@@ -791,14 +842,24 @@ impl LightClient {
|
|||||||
Ok(array![new_address])
|
Ok(array![new_address])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_rescan(&self) -> Result<JsonValue, String> {
|
pub fn clear_state(&self) {
|
||||||
info!("Rescan starting");
|
|
||||||
// First, clear the state from the wallet
|
// First, clear the state from the wallet
|
||||||
self.wallet.read().unwrap().clear_blocks();
|
self.wallet.read().unwrap().clear_blocks();
|
||||||
|
|
||||||
// Then set the initial block
|
// Then set the initial block
|
||||||
self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday());
|
self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday());
|
||||||
|
info!("Cleared wallet state");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_rescan(&self) -> Result<JsonValue, String> {
|
||||||
|
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
|
||||||
|
warn!("Wallet is locked, new HD addresses won't be added!");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Rescan starting");
|
||||||
|
|
||||||
|
self.clear_state();
|
||||||
|
|
||||||
// Then, do a sync, which will force a full rescan from the initial state
|
// Then, do a sync, which will force a full rescan from the initial state
|
||||||
let response = self.do_sync(true);
|
let response = self.do_sync(true);
|
||||||
info!("Rescan finished");
|
info!("Rescan finished");
|
||||||
|
|||||||
@@ -168,6 +168,16 @@ impl LightWallet {
|
|||||||
(extsk, extfvk, address)
|
(extsk, extfvk, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool {
|
||||||
|
match address::RecipientAddress::from_str(addr,
|
||||||
|
config.hrp_sapling_address(),
|
||||||
|
config.base58_pubkey_address(),
|
||||||
|
config.base58_script_address()) {
|
||||||
|
Some(address::RecipientAddress::Shielded(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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> {
|
||||||
// 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];
|
||||||
@@ -872,6 +882,32 @@ impl LightWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the last taddress was used, ensure we add the next HD taddress to the wallet.
|
||||||
|
pub fn ensure_hd_taddresses(&self, address: &String) {
|
||||||
|
let last_address = {
|
||||||
|
self.taddresses.read().unwrap().last().unwrap().clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if *last_address == *address {
|
||||||
|
// If the wallet is locked, this is a no-op. That is fine, since we really
|
||||||
|
// need to only add new addresses when restoring a new wallet, when it will not be locked.
|
||||||
|
// Also, if it is locked, the user can't create new addresses anyway.
|
||||||
|
self.add_taddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last zaddress was used, ensure we add the next HD zaddress to the wallet
|
||||||
|
pub fn ensure_hd_zaddresses(&self, address: &String) {
|
||||||
|
let last_address = encode_payment_address(self.config.hrp_sapling_address(), self.zaddress.read().unwrap().last().unwrap());
|
||||||
|
|
||||||
|
if last_address == *address {
|
||||||
|
// If the wallet is locked, this is a no-op. That is fine, since we really
|
||||||
|
// need to only add new addresses when restoring a new wallet, when it will not be locked.
|
||||||
|
// Also, if it is locked, the user can't create new addresses anyway.
|
||||||
|
self.add_zaddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scan the full Tx and update memos for incoming shielded transactions.
|
// Scan the full Tx and update memos for incoming shielded transactions.
|
||||||
pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) {
|
pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) {
|
||||||
let mut total_transparent_spend: u64 = 0;
|
let mut total_transparent_spend: u64 = 0;
|
||||||
@@ -927,6 +963,9 @@ impl LightWallet {
|
|||||||
if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) {
|
if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) {
|
||||||
// This is our address. Add this as an output to the txid
|
// This is our address. Add this as an output to the txid
|
||||||
self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64);
|
self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64);
|
||||||
|
|
||||||
|
// Ensure that we add any new HD addresses
|
||||||
|
self.ensure_hd_taddresses(&address);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -1296,9 +1335,15 @@ impl LightWallet {
|
|||||||
// Save notes.
|
// Save notes.
|
||||||
for output in tx.shielded_outputs
|
for output in tx.shielded_outputs
|
||||||
{
|
{
|
||||||
info!("Received sapling output");
|
|
||||||
|
|
||||||
let new_note = SaplingNoteData::new(&self.extfvks.read().unwrap()[output.account], output);
|
let new_note = SaplingNoteData::new(&self.extfvks.read().unwrap()[output.account], output);
|
||||||
|
match LightWallet::note_address(self.config.hrp_sapling_address(), &new_note) {
|
||||||
|
Some(a) => {
|
||||||
|
info!("Received sapling output to {}", a);
|
||||||
|
self.ensure_hd_zaddresses(&a);
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) {
|
match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) {
|
||||||
None => tx_entry.notes.push(new_note),
|
None => tx_entry.notes.push(new_note),
|
||||||
Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid)
|
Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid)
|
||||||
@@ -1574,7 +1619,14 @@ impl LightWallet {
|
|||||||
value: *amt,
|
value: *amt,
|
||||||
memo: match maybe_memo {
|
memo: match maybe_memo {
|
||||||
None => Memo::default(),
|
None => Memo::default(),
|
||||||
Some(s) => Memo::from_str(&s).unwrap(),
|
Some(s) => {
|
||||||
|
// If the address is not a z-address, then drop the memo
|
||||||
|
if LightWallet::is_shielded_address(&addr.to_string(), &self.config) {
|
||||||
|
Memo::from_str(s).unwrap()
|
||||||
|
} else {
|
||||||
|
Memo::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -845,7 +845,7 @@ fn test_multi_z() {
|
|||||||
assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[change_note_number].memo), None);
|
assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[change_note_number].memo), None);
|
||||||
|
|
||||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].note.value, AMOUNT_SENT);
|
assert_eq!(txs[&sent_txid].notes[ext_note_number].note.value, AMOUNT_SENT);
|
||||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 1);
|
assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 2);
|
||||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false);
|
assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false);
|
||||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, None);
|
assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, None);
|
||||||
assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None);
|
assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None);
|
||||||
@@ -922,7 +922,6 @@ fn test_z_spend_to_taddr() {
|
|||||||
cb3.add_tx(&sent_tx);
|
cb3.add_tx(&sent_tx);
|
||||||
wallet.scan_block(&cb3.as_bytes()).unwrap();
|
wallet.scan_block(&cb3.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
|
||||||
// Now this new Spent tx should be in, so the note should be marked confirmed spent
|
// Now this new Spent tx should be in, so the note should be marked confirmed spent
|
||||||
{
|
{
|
||||||
let txs = wallet.txs.read().unwrap();
|
let txs = wallet.txs.read().unwrap();
|
||||||
@@ -950,6 +949,38 @@ fn test_z_spend_to_taddr() {
|
|||||||
assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT);
|
assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT);
|
||||||
assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1);
|
assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new Tx, but this time with a memo.
|
||||||
|
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||||
|
vec![(&taddr, AMOUNT_SENT, Some("T address memo".to_string()))]).unwrap();
|
||||||
|
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||||
|
let sent_txid2 = sent_tx.txid();
|
||||||
|
|
||||||
|
// There should be a mempool Tx, but the memo should be dropped, because it was sent to a
|
||||||
|
// t address
|
||||||
|
{
|
||||||
|
let txs = wallet.mempool_txs.read().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1);
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr);
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT);
|
||||||
|
assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now add the block
|
||||||
|
let mut cb4 = FakeCompactBlock::new(3, cb3.hash());
|
||||||
|
cb4.add_tx(&sent_tx);
|
||||||
|
wallet.scan_block(&cb4.as_bytes()).unwrap();
|
||||||
|
wallet.scan_full_tx(&sent_tx, 3, 0);
|
||||||
|
|
||||||
|
// Check Outgoing Metadata for t address, but once again there should be no memo
|
||||||
|
{
|
||||||
|
let txs = wallet.txs.read().unwrap();
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1);
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr);
|
||||||
|
assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT);
|
||||||
|
assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1054,7 +1085,7 @@ fn test_t_spend_to_z() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_z_incoming_memo() {
|
fn test_z_incoming_memo() {
|
||||||
const AMOUNT1: u64 = 50000;
|
const AMOUNT1: u64 = 50000;
|
||||||
let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1);
|
let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1);
|
||||||
@@ -1094,7 +1125,51 @@ fn test_z_incoming_memo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
fn test_add_new_zt_hd_after_incoming() {
|
||||||
|
// When an address recieves funds, a new, unused address should automatically get added
|
||||||
|
const AMOUNT1: u64 = 50000;
|
||||||
|
let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1);
|
||||||
|
|
||||||
|
// Get the last address
|
||||||
|
let my_address = encode_payment_address(wallet.config.hrp_sapling_address(),
|
||||||
|
&wallet.extfvks.read().unwrap().last().unwrap().default_address().unwrap().1);
|
||||||
|
|
||||||
|
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||||
|
|
||||||
|
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||||
|
let (ss, so) = get_sapling_params().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(wallet.zaddress.read().unwrap().len(), 2); // Starts with 2 addresses
|
||||||
|
|
||||||
|
// Create a tx and send to address
|
||||||
|
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||||
|
vec![(&my_address, AMOUNT1 - fee, None)]).unwrap();
|
||||||
|
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
|
||||||
|
|
||||||
|
// Add it to a block
|
||||||
|
let mut cb3 = FakeCompactBlock::new(2, block_hash);
|
||||||
|
cb3.add_tx(&sent_tx);
|
||||||
|
wallet.scan_block(&cb3.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(wallet.zaddress.read().unwrap().len(), 3); // Now has a new address
|
||||||
|
|
||||||
|
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
// Send a fake transaction to the last taddr
|
||||||
|
let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap().last().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(wallet.taddresses.read().unwrap().len(), 1); // Start with 1 taddr
|
||||||
|
|
||||||
|
let mut tx = FakeTransaction::new(&mut rng);
|
||||||
|
tx.add_t_output(&pk, AMOUNT1);
|
||||||
|
|
||||||
|
wallet.scan_full_tx(&tx.get_tx(), 3, 0);
|
||||||
|
assert_eq!(wallet.taddresses.read().unwrap().len(), 2); // Now there should be 2 addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
fn test_z_to_t_withinwallet() {
|
fn test_z_to_t_withinwallet() {
|
||||||
const AMOUNT: u64 = 500000;
|
const AMOUNT: u64 = 500000;
|
||||||
const AMOUNT_SENT: u64 = 20000;
|
const AMOUNT_SENT: u64 = 20000;
|
||||||
@@ -1151,7 +1226,7 @@ fn test_z_to_t_withinwallet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multi_t() {
|
fn test_multi_t() {
|
||||||
const AMOUNT: u64 = 5000000;
|
const AMOUNT: u64 = 5000000;
|
||||||
const AMOUNT_SENT1: u64 = 20000;
|
const AMOUNT_SENT1: u64 = 20000;
|
||||||
@@ -1337,7 +1412,7 @@ fn test_multi_spends() {
|
|||||||
|
|
||||||
// Find zaddr2
|
// Find zaddr2
|
||||||
let zaddr2_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap();
|
let zaddr2_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap();
|
||||||
assert_eq!(zaddr2_note.account, 2-1);
|
assert_eq!(zaddr2_note.account, 2);
|
||||||
assert_eq!(zaddr2_note.is_change, false);
|
assert_eq!(zaddr2_note.is_change, false);
|
||||||
assert_eq!(zaddr2_note.spent, None);
|
assert_eq!(zaddr2_note.spent, None);
|
||||||
assert_eq!(zaddr2_note.unconfirmed_spent, None);
|
assert_eq!(zaddr2_note.unconfirmed_spent, None);
|
||||||
@@ -1345,7 +1420,7 @@ fn test_multi_spends() {
|
|||||||
|
|
||||||
// Find zaddr3
|
// Find zaddr3
|
||||||
let zaddr3_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT3).unwrap();
|
let zaddr3_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT3).unwrap();
|
||||||
assert_eq!(zaddr3_note.account, 3-1);
|
assert_eq!(zaddr3_note.account, 3);
|
||||||
assert_eq!(zaddr3_note.is_change, false);
|
assert_eq!(zaddr3_note.is_change, false);
|
||||||
assert_eq!(zaddr3_note.spent, None);
|
assert_eq!(zaddr3_note.spent, None);
|
||||||
assert_eq!(zaddr3_note.unconfirmed_spent, None);
|
assert_eq!(zaddr3_note.unconfirmed_spent, None);
|
||||||
@@ -1706,8 +1781,8 @@ fn test_lock_unlock() {
|
|||||||
// Add some addresses
|
// Add some addresses
|
||||||
let zaddr0 = encode_payment_address(config.hrp_sapling_address(),
|
let zaddr0 = encode_payment_address(config.hrp_sapling_address(),
|
||||||
&wallet.extfvks.read().unwrap()[0].default_address().unwrap().1);
|
&wallet.extfvks.read().unwrap()[0].default_address().unwrap().1);
|
||||||
let zaddr1 = wallet.add_zaddr();
|
let zaddr1 = wallet.add_zaddr(); // This is actually address at index 2
|
||||||
let zaddr2 = wallet.add_zaddr();
|
let zaddr2 = wallet.add_zaddr(); // This is actually address at index 3
|
||||||
|
|
||||||
let taddr0 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]);
|
let taddr0 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]);
|
||||||
let taddr1 = wallet.add_taddr();
|
let taddr1 = wallet.add_taddr();
|
||||||
@@ -1738,15 +1813,15 @@ fn test_lock_unlock() {
|
|||||||
{
|
{
|
||||||
let extsks = wallet.extsks.read().unwrap();
|
let extsks = wallet.extsks.read().unwrap();
|
||||||
let tkeys = wallet.tkeys.read().unwrap();
|
let tkeys = wallet.tkeys.read().unwrap();
|
||||||
assert_eq!(extsks.len(), 3);
|
assert_eq!(extsks.len(), 4); // 3 zaddrs + 1 added originally in get_test_wallet()
|
||||||
assert_eq!(tkeys.len(), 3);
|
assert_eq!(tkeys.len(), 3);
|
||||||
|
|
||||||
assert_eq!(zaddr0, encode_payment_address(config.hrp_sapling_address(),
|
assert_eq!(zaddr0, encode_payment_address(config.hrp_sapling_address(),
|
||||||
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
|
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
|
||||||
assert_eq!(zaddr1, encode_payment_address(config.hrp_sapling_address(),
|
assert_eq!(zaddr1, encode_payment_address(config.hrp_sapling_address(),
|
||||||
&ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1));
|
|
||||||
assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(),
|
|
||||||
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
|
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
|
||||||
|
assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(),
|
||||||
|
&ExtendedFullViewingKey::from(&extsks[3]).default_address().unwrap().1));
|
||||||
|
|
||||||
assert_eq!(taddr0, wallet.address_from_sk(&tkeys[0]));
|
assert_eq!(taddr0, wallet.address_from_sk(&tkeys[0]));
|
||||||
assert_eq!(taddr1, wallet.address_from_sk(&tkeys[1]));
|
assert_eq!(taddr1, wallet.address_from_sk(&tkeys[1]));
|
||||||
@@ -1774,15 +1849,15 @@ fn test_lock_unlock() {
|
|||||||
{
|
{
|
||||||
let extsks = wallet2.extsks.read().unwrap();
|
let extsks = wallet2.extsks.read().unwrap();
|
||||||
let tkeys = wallet2.tkeys.read().unwrap();
|
let tkeys = wallet2.tkeys.read().unwrap();
|
||||||
assert_eq!(extsks.len(), 3);
|
assert_eq!(extsks.len(), 4);
|
||||||
assert_eq!(tkeys.len(), 3);
|
assert_eq!(tkeys.len(), 3);
|
||||||
|
|
||||||
assert_eq!(zaddr0, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
assert_eq!(zaddr0, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
||||||
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
|
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
|
||||||
assert_eq!(zaddr1, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
assert_eq!(zaddr1, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
||||||
&ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1));
|
|
||||||
assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
|
||||||
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
|
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
|
||||||
|
assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(),
|
||||||
|
&ExtendedFullViewingKey::from(&extsks[3]).default_address().unwrap().1));
|
||||||
|
|
||||||
assert_eq!(taddr0, wallet2.address_from_sk(&tkeys[0]));
|
assert_eq!(taddr0, wallet2.address_from_sk(&tkeys[0]));
|
||||||
assert_eq!(taddr1, wallet2.address_from_sk(&tkeys[1]));
|
assert_eq!(taddr1, wallet2.address_from_sk(&tkeys[1]));
|
||||||
@@ -1965,7 +2040,7 @@ fn test_encrypted_zreceive() {
|
|||||||
|
|
||||||
// Find zaddr2
|
// Find zaddr2
|
||||||
let zaddr2_note = txs[&txid2].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap();
|
let zaddr2_note = txs[&txid2].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap();
|
||||||
assert_eq!(zaddr2_note.account, 1);
|
assert_eq!(zaddr2_note.account, 2);
|
||||||
assert_eq!(zaddr2_note.is_change, false);
|
assert_eq!(zaddr2_note.is_change, false);
|
||||||
assert_eq!(zaddr2_note.spent, None);
|
assert_eq!(zaddr2_note.spent, None);
|
||||||
assert_eq!(zaddr2_note.unconfirmed_spent, None);
|
assert_eq!(zaddr2_note.unconfirmed_spent, None);
|
||||||
|
|||||||
Reference in New Issue
Block a user