Require spending from specified address 1. Add from address, t or z 2. Use ovk of the address being spent from 3. disable lazy consolidation of t addresses 4. In a t->z transaction use the ovk of the fist z-address in the wallet.

This commit is contained in:
Cryptoforge 2020-07-23 20:28:37 -07:00
parent e71528f625
commit 6f8aa9f39e
4 changed files with 102 additions and 86 deletions

View File

@ -457,15 +457,12 @@ impl Command for SendCommand {
let mut h = vec![];
h.push("Send ZEC to a given address(es)");
h.push("Usage:");
h.push("send <address> <amount in zatoshis || \"entire-verified-zbalance\"> \"optional_memo\"");
h.push("OR");
h.push("send '[{'address': <address>, 'amount': <amount in zatoshis>, 'memo': <optional memo>}, ...]'");
h.push("send '{'input': <address>, 'output': [{'address': <address>, 'amount': <amount in zatoshis>, 'memo': <optional memo>}, ...]}");
h.push("");
h.push("NOTE: The fee required to send this transaction (currently ZEC 0.0001) is additionally detected from your balance.");
h.push("Example:");
h.push("send ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d 200000 \"Hello from the command line\"");
h.push("send '{\"input\":\"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"output\": [{ \"address\": \"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"amount\": 200000, \"memo\": \"Hello from the command line\"}]}'");
h.push("");
h.join("\n")
}
@ -474,12 +471,11 @@ impl Command for SendCommand {
}
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
// Parse the args. There are two argument types.
// 1 - A set of 2(+1 optional) arguments for a single address send representing address, value, memo?
// 2 - A single argument in the form of a JSON string that is "[{address: address, value: value, memo: memo},...]"
// Parse the args.
// A single argument in the form of a JSON string that is "{input: address, output: [{address: address, value: value, memo: memo},...], fee: fee}"
// 1 - Destination address. T or Z address
if args.len() < 1 || args.len() > 3 {
if args.len() != 1 {
return self.help();
}
@ -487,8 +483,8 @@ impl Command for SendCommand {
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
// Check for a single argument that can be parsed as JSON
let send_args = if args.len() == 1 {
let arg_list = args[0];
let json_args = match json::parse(&arg_list) {
@ -499,11 +495,27 @@ impl Command for SendCommand {
}
};
if !json_args.is_array() {
//Check for a input key and convert to str
let from = if json_args.has_key("input") {
json_args["input"].as_str().unwrap().clone()
} else {
return format!("Error: {}\n{}", "Need input address", self.help());
};
//Check for output key
let json_tos = if json_args.has_key("output") {
&json_args["output"]
} else {
return format!("Error: {}\n{}", "Need output address", self.help());
};
//Check output is in the form of an array
if !json_tos.is_array() {
return format!("Couldn't parse argument as array\n{}", self.help());
}
let maybe_send_args = json_args.members().map( |j| {
//Check array for manadantory address and amount keys
let maybe_send_args = json_tos.members().map( |j| {
if !j.has_key("address") || !j.has_key("amount") {
Err(format!("Need 'address' and 'amount'\n"))
} else {
@ -519,45 +531,17 @@ impl Command for SendCommand {
}
}).collect::<Result<Vec<(String, u64, Option<String>)>, String>>();
match maybe_send_args {
let send_args = match maybe_send_args {
Ok(a) => a.clone(),
Err(s) => { return format!("Error: {}\n{}", s, self.help()); }
}
} else if args.len() == 2 || args.len() == 3 {
let address = args[0].to_string();
// Make sure we can parse the amount
let value = match args[1].parse::<u64>() {
Ok(amt) => amt,
Err(e) => {
if args[1] == "entire-verified-zbalance" {
match lightclient.wallet.read().unwrap().verified_zbalance(None).checked_sub(fee) {
Some(amt) => amt,
None => { return format!("Not enough in wallet to pay transaction fee") }
}
} else {
return format!("Couldn't parse amount: {}", e);
}
}
};
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)]
} else {
return self.help()
};
match lightclient.do_sync(true) {
Ok(_) => {
// Convert to the right format. String -> &str.
let tos = send_args.iter().map(|(a, v, m)| (a.as_str(), *v, m.clone()) ).collect::<Vec<_>>();
match lightclient.do_send(tos) {
match lightclient.do_send(from, tos) {
Ok(txid) => { object!{ "txid" => txid } },
Err(e) => { object!{ "error" => e } }
}.pretty(2)

View File

@ -1334,7 +1334,7 @@ impl LightClient {
}
}
pub fn do_send(&self, addrs: Vec<(&str, u64, Option<String>)>) -> Result<String, String> {
pub fn do_send(&self, from: &str, 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());
@ -1345,9 +1345,12 @@ impl LightClient {
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,
addrs
from, addrs
);
info!("Transaction Complete");
match rawtx {
Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes),
Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e))

View File

@ -1679,6 +1679,7 @@ impl LightWallet {
consensus_branch_id: u32,
spend_params: &[u8],
output_params: &[u8],
from: &str,
tos: Vec<(&str, u64, Option<String>)>
) -> Result<Box<[u8]>, String> {
if !self.unlocked {
@ -1692,7 +1693,7 @@ impl LightWallet {
let total_value = tos.iter().map(|to| to.1).sum::<u64>();
println!(
"0: Creating transaction sending {} ztoshis to {} addresses",
"0: Creating transaction sending {} zatoshis to {} addresses",
total_value, tos.len()
);
@ -1731,6 +1732,7 @@ impl LightWallet {
let notes: Vec<_> = self.txs.read().unwrap().iter()
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
.flatten()
.filter(|(_txid, note)|LightWallet::note_address(self.config.hrp_sapling_address(), note).unwrap() == from)
.filter_map(|(txid, note)|
SpendableNote::from(txid, note, anchor_offset, &self.extsks.read().unwrap()[note.account])
)
@ -1755,6 +1757,7 @@ impl LightWallet {
// ZecWallet will add all your t-address funds into that transaction, and send them to your shielded
// address as change.
let tinputs: Vec<_> = self.get_utxos().iter()
.filter(|utxo| utxo.address == from)
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
.map(|utxo| utxo.clone())
.collect();
@ -1820,17 +1823,43 @@ impl LightWallet {
}
}
// Use the ovk belonging to the address being sent from, if not using any notes
// use the first address in the wallet for the ovk.
let ovk = if notes.len() == 0 {
self.extfvks.read().unwrap()[0].fvk.ovk
} else {
ExtendedFullViewingKey::from(&notes[0].extsk).fvk.ovk
};
// If no Sapling notes were added, add the change address manually. That is,
// send the change to our sapling address manually. Note that if a sapling note was spent,
// the builder will automatically send change to that address
if notes.len() == 0 {
builder.send_change_to(
ExtendedFullViewingKey::from(&self.extsks.read().unwrap()[0]).fvk.ovk,
self.extsks.read().unwrap()[0].default_address().unwrap().1);
// send the change back to the transparent address being used,
// the builder will automatically send change back to the sapling address if notes are used.
if notes.len() == 0 && selected_value - u64::from(target_value) > 0 {
println!("{}: Adding change output", now() - start_time);
let from_addr = address::RecipientAddress::from_str(from,
self.config.hrp_sapling_address(),
self.config.base58_pubkey_address(),
self.config.base58_script_address()).unwrap();
if let Err(e) = match from_addr {
address::RecipientAddress::Shielded(from_addr) => {
builder.add_sapling_output(ovk, from_addr.clone(), Amount::from_u64(selected_value - u64::from(target_value)).unwrap(), None)
}
address::RecipientAddress::Transparent(from_addr) => {
builder.add_transparent_output(&from_addr, Amount::from_u64(selected_value - u64::from(target_value)).unwrap())
}
} {
let e = format!("Error adding transparent change output: {:?}", e);
error!("{}", e);
return Err(e);
}
}
// TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok?
let ovk = self.extfvks.read().unwrap()[0].fvk.ovk;
for (to, value, memo) in recepients {
// Compute memo if it exists
@ -1846,7 +1875,7 @@ impl LightWallet {
}
};
println!("{}: Adding output", now() - start_time);
println!("{}: Adding outputs", now() - start_time);
if let Err(e) = match to {
address::RecipientAddress::Shielded(to) => {

View File

@ -82,7 +82,7 @@ impl BugBip39Derivation {
let txid = if amount > 0 {
println!("Sending funds to ourself.");
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
match client.do_send(vec![(&zaddr, amount-fee, None)]) {
match client.do_send(client.do_address()["z_addresses"][0].as_str().unwrap(), vec![(&zaddr, amount-fee, None)]) {
Ok(txid) => txid,
Err(e) => {
let r = object!{