Send to multiple addresses

This commit is contained in:
Aditya Kulkarni
2019-10-04 10:10:51 -07:00
parent 5ca7319fc3
commit 94108e4936
2 changed files with 105 additions and 79 deletions

View File

@@ -709,7 +709,7 @@ impl LightClient {
let rawtx = self.wallet.send_to_address( let rawtx = self.wallet.send_to_address(
u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(), // Blossom ID u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(), // Blossom ID
&self.sapling_spend, &self.sapling_output, &self.sapling_spend, &self.sapling_output,
&addr, value, memo vec![(&addr, value, memo)]
); );
match rawtx { match rawtx {

View File

@@ -1054,28 +1054,43 @@ impl LightWallet {
consensus_branch_id: u32, consensus_branch_id: u32,
spend_params: &[u8], spend_params: &[u8],
output_params: &[u8], output_params: &[u8],
to: &str, tos: Vec<(&str, u64, Option<String>)>
value: u64,
memo: Option<String>,
) -> Option<Box<[u8]>> { ) -> Option<Box<[u8]>> {
let start_time = now(); let start_time = now();
let total_value = tos.iter().map(|to| to.1).sum::<u64>();
println!( println!(
"0: Creating transaction sending {} tazoshis to {}", "0: Creating transaction sending {} tazoshis to {} addresses",
value, total_value, tos.len()
to
); );
let to = match address::RecipientAddress::from_str(to, // Convert address (str) to RecepientAddress and value to Amount
self.config.hrp_sapling_address(), let maybe_tos: Result<Vec<(address::RecipientAddress, Amount, Option<String>)>, _> = tos.iter().map(|to| {
self.config.base58_pubkey_address(), let ra = match address::RecipientAddress::from_str(to.0,
self.config.base58_script_address()) { self.config.hrp_sapling_address(),
Some(to) => to, self.config.base58_pubkey_address(),
None => { self.config.base58_script_address()) {
eprintln!("Invalid recipient address"); Some(to) => to,
None => {
let e = format!("Invalid recipient address: {}", to.0);
error!("{}", e);
return Err(e);
}
};
let value = Amount::from_u64(to.1).unwrap();
Ok((ra, value, to.2.clone()))
}).collect();
let tos = match maybe_tos {
Ok(t) => t,
Err(e) => {
error!("{}", e);
return None; return None;
} }
}; };
let value = Amount::from_u64(value).unwrap();
// Target the next block, assuming we are up-to-date. // Target the next block, assuming we are up-to-date.
let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() { let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() {
@@ -1088,7 +1103,7 @@ impl LightWallet {
// Select notes to cover the target value // Select notes to cover the target value
println!("{}: Selecting notes", now() - start_time); println!("{}: Selecting notes", now() - start_time);
let target_value = value + DEFAULT_FEE ; let target_value = Amount::from_u64(total_value).unwrap() + DEFAULT_FEE ;
let notes: Vec<_> = self.txs.read().unwrap().iter() let notes: Vec<_> = self.txs.read().unwrap().iter()
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note))) .map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
.flatten() .flatten()
@@ -1117,48 +1132,55 @@ impl LightWallet {
// address as change. // address as change.
let mut tinputs = vec![]; let mut tinputs = vec![];
if let Err(e) = match to { // Check if all to addresses are shielded
address::RecipientAddress::Shielded(_) => { let all_shielded = !tos.iter().any(|to| match to.0 {
// The destination is a sapling address, so add all transparent inputs address::RecipientAddress::Transparent(_) => true,
tinputs.extend(self.get_utxos().iter() _ => false
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends });
.map(|utxo| utxo.clone()));
// Create a map from address -> sk for all taddrs, so we can spend from the
// right address
let address_to_sk: HashMap<_, _> = self.tkeys.read().unwrap().iter().map(|sk|
(self.address_from_sk(&sk), sk.clone())
).collect();
// Add all tinputs if all_shielded {
tinputs.iter() // The destination is a sapling address, so add all transparent inputs
.map(|utxo| { tinputs.extend(self.get_utxos().iter()
let outpoint: OutPoint = utxo.to_outpoint(); .filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
.map(|utxo| utxo.clone()));
let coin = TxOut {
value: Amount::from_u64(utxo.value).unwrap(), // Create a map from address -> sk for all taddrs, so we can spend from the
script_pubkey: Script { 0: utxo.script.clone() }, // right address
}; let address_to_sk: HashMap<_, _> = self.tkeys.read().unwrap().iter().map(|sk|
(self.address_from_sk(&sk), sk.clone())
).collect();
match address_to_sk.get(&utxo.address) { // Add all tinputs
Some(sk) => builder.add_transparent_input(*sk, outpoint.clone(), coin.clone()), let r = tinputs.iter()
None => { .map(|utxo| {
// Something is very wrong let outpoint: OutPoint = utxo.to_outpoint();
let e = format!("Couldn't find the secreykey for taddr {}", utxo.address);
error!("{}", e); let coin = TxOut {
eprintln!("{}", e); value: Amount::from_u64(utxo.value).unwrap(),
script_pubkey: Script { 0: utxo.script.clone() },
};
Err(zcash_primitives::transaction::builder::Error::InvalidAddress) match address_to_sk.get(&utxo.address) {
} Some(sk) => builder.add_transparent_input(*sk, outpoint.clone(), coin.clone()),
None => {
// Something is very wrong
let e = format!("Couldn't find the secreykey for taddr {}", utxo.address);
error!("{}", e);
Err(zcash_primitives::transaction::builder::Error::InvalidAddress)
} }
}
})
.collect::<Result<Vec<_>, _>>() })
}, .collect::<Result<Vec<_>, _>>();
_ => Ok(vec![])
} { match r {
eprintln!("Error adding transparent inputs: {:?}", e); Err(e) => {
return None; error!("Error adding transparent inputs: {:?}", e);
return None;
},
Ok(_) => {}
};
} }
// Confirm we were able to select sufficient value // Confirm we were able to select sufficient value
@@ -1197,25 +1219,29 @@ impl LightWallet {
self.extsks.read().unwrap()[0].default_address().unwrap().1); self.extsks.read().unwrap()[0].default_address().unwrap().1);
} }
// Compute memo if it exists
let encoded_memo = memo.map(|s| Memo::from_str(&s).unwrap() );
println!("{}: Adding output", now() - start_time);
// TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok? // TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok?
let ovk = self.extfvks.read().unwrap()[0].fvk.ovk; let ovk = self.extfvks.read().unwrap()[0].fvk.ovk;
if let Err(e) = match to { for (to, value, memo) in tos {
address::RecipientAddress::Shielded(to) => { // Compute memo if it exists
builder.add_sapling_output(ovk, to.clone(), value, encoded_memo) let encoded_memo = memo.map(|s| Memo::from_str(&s).unwrap());
println!("{}: Adding output", now() - start_time);
if let Err(e) = match to {
address::RecipientAddress::Shielded(to) => {
builder.add_sapling_output(ovk, to.clone(), value, encoded_memo)
}
address::RecipientAddress::Transparent(to) => {
builder.add_transparent_output(&to, value)
}
} {
eprintln!("Error adding output: {:?}", e);
return None;
} }
address::RecipientAddress::Transparent(to) => {
builder.add_transparent_output(&to, value)
}
} {
eprintln!("Error adding output: {:?}", e);
return None;
} }
println!("{}: Building transaction", now() - start_time); println!("{}: Building transaction", now() - start_time);
let (tx, _) = match builder.build( let (tx, _) = match builder.build(
consensus_branch_id, consensus_branch_id,
@@ -1969,7 +1995,7 @@ pub mod tests {
// Create a tx and send to address // Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone())).unwrap(); vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2037,7 +2063,7 @@ pub mod tests {
// Create a tx and send to address // Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&zaddr2, AMOUNT_SENT, Some(outgoing_memo.clone())).unwrap(); vec![(&zaddr2, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2091,7 +2117,7 @@ pub mod tests {
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr, amount_all, None).unwrap(); vec![(&taddr, amount_all, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_ext_txid = sent_tx.txid(); let sent_ext_txid = sent_tx.txid();
@@ -2133,7 +2159,7 @@ pub mod tests {
let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr, AMOUNT_SENT, None).unwrap(); vec![(&taddr, AMOUNT_SENT, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2225,7 +2251,7 @@ pub mod tests {
// Create a tx and send to address. This should consume both the UTXO and the note // Create a tx and send to address. This should consume both the UTXO and the note
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone())).unwrap(); vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2299,7 +2325,7 @@ pub mod tests {
// Create a tx and send to address // Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&my_address, AMOUNT1 - fee, Some(memo.clone())).unwrap(); vec![(&my_address, AMOUNT1 - fee, Some(memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2338,7 +2364,7 @@ pub mod tests {
// Create a tx and send to address // Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr, AMOUNT_SENT, None).unwrap(); vec![(&taddr, AMOUNT_SENT, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();
@@ -2398,7 +2424,7 @@ pub mod tests {
// Create a Tx and send to the second t address // Create a Tx and send to the second t address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr2, AMOUNT_SENT1, None).unwrap(); vec![(&taddr2, AMOUNT_SENT1, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid1 = sent_tx.txid(); let sent_txid1 = sent_tx.txid();
@@ -2442,7 +2468,7 @@ pub mod tests {
// Create a Tx and send to the second t address // Create a Tx and send to the second t address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr3, AMOUNT_SENT2, None).unwrap(); vec![(&taddr3, AMOUNT_SENT2, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid2 = sent_tx.txid(); let sent_txid2 = sent_tx.txid();
@@ -2479,7 +2505,7 @@ pub mod tests {
// Create a tx and send to address // Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&ext_address, AMOUNT_SENT_EXT, Some(outgoing_memo.clone())).unwrap(); vec![(&ext_address, AMOUNT_SENT_EXT, Some(outgoing_memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid3 = sent_tx.txid(); let sent_txid3 = sent_tx.txid();
@@ -2606,7 +2632,7 @@ pub mod tests {
const AMOUNT_SENT: u64 = 30000; const AMOUNT_SENT: u64 = 30000;
let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let raw_tx = wallet.send_to_address(branch_id, &ss, &so, let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
&taddr, AMOUNT_SENT, None).unwrap(); vec![(&taddr, AMOUNT_SENT, None)]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid(); let sent_txid = sent_tx.txid();