Skip to content

Commit

Permalink
Merge pull request zingolabs#506 from zancas/refactor_send_to_address
Browse files Browse the repository at this point in the history
Refactor send to address
  • Loading branch information
fluidvanadium authored Sep 22, 2023
2 parents af49a34 + cef9828 commit 0907e00
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 103 deletions.
27 changes: 18 additions & 9 deletions zingolib/src/lightclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,14 +1010,18 @@ impl LightClient {

let result = {
let _lock = self.sync_lock.lock().await;
// I am not clear on how long this operation may take, but it's
// clearly unnecessary in a send that doesn't include sapling
// TODO: Remove from sends that don't include Sapling
let (sapling_output, sapling_spend) = self.read_sapling_params()?;

let prover = LocalTxProver::from_bytes(&sapling_spend, &sapling_output);
let sapling_prover = LocalTxProver::from_bytes(&sapling_spend, &sapling_output);

self.wallet
.send_to_address(
prover,
vec![crate::wallet::Pool::Orchard, crate::wallet::Pool::Sapling],
.send_to_addresses(
sapling_prover,
vec![crate::wallet::Pool::Orchard, crate::wallet::Pool::Sapling], // This policy doesn't allow
// spend from transparent.
address_amount_memo_tuples,
transaction_submission_height,
|transaction_bytes| {
Expand All @@ -1044,8 +1048,13 @@ impl LightClient {
address: Option<String>,
) -> Result<String, String> {
let transaction_submission_height = self.get_submission_height().await?;
let fee = u64::from(MINIMUM_FEE);
let tbal = self.wallet.tbalance(None).await.unwrap_or(0);
let fee = u64::from(MINIMUM_FEE); // TODO: This can no longer be hard coded, and must be calced
// as a fn of the transactions structure.
let tbal = self
.wallet
.tbalance(None)
.await
.expect("to receive a balance");
let sapling_bal = self
.wallet
.spendable_sapling_balance(None)
Expand Down Expand Up @@ -1076,11 +1085,11 @@ impl LightClient {
let _lock = self.sync_lock.lock().await;
let (sapling_output, sapling_spend) = self.read_sapling_params()?;

let prover = LocalTxProver::from_bytes(&sapling_spend, &sapling_output);
let sapling_prover = LocalTxProver::from_bytes(&sapling_spend, &sapling_output);

self.wallet
.send_to_address(
prover,
.send_to_addresses(
sapling_prover,
pools_to_shield.to_vec(),
vec![(&addr, balance_to_shield - fee, None)],
transaction_submission_height,
Expand Down
207 changes: 113 additions & 94 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,9 +919,9 @@ impl LightWallet {
)
}

pub async fn send_to_address<F, Fut, P: TxProver>(
pub async fn send_to_addresses<F, Fut, P: TxProver>(
&self,
prover: P,
sapling_prover: P,
policy: NoteSelectionPolicy,
tos: Vec<(&str, u64, Option<MemoBytes>)>,
submission_height: BlockHeight,
Expand All @@ -936,7 +936,7 @@ impl LightWallet {

// Call the internal function
match self
.send_to_address_inner(prover, policy, tos, submission_height, broadcast_fn)
.send_to_addresses_inner(sapling_prover, policy, tos, submission_height, broadcast_fn)
.await
{
Ok((transaction_id, raw_transaction)) => {
Expand All @@ -950,94 +950,18 @@ impl LightWallet {
}
}

async fn send_to_address_inner<F, Fut, P: TxProver>(
async fn load_transaction_builder_spends(
&self,
prover: P,
policy: NoteSelectionPolicy,
tos: Vec<(&str, u64, Option<MemoBytes>)>,
submission_height: BlockHeight,
broadcast_fn: F,
) -> Result<(String, Vec<u8>), String>
where
F: Fn(Box<[u8]>) -> Fut,
Fut: Future<Output = Result<String, String>>,
{
let start_time = now();
if tos.is_empty() {
return Err("Need at least one destination address".to_string());
}

if !self.wallet_capability().can_spend_from_all_pools() {
// Creating transactions in context of all possible combinations
// of wallet capabilities requires a rigorous case study
// and can have undesired effects if not implemented properly.
//
// Thus we forbid spending for wallets without complete spending capability for now
return Err("Wallet is in watch-only mode and thus it cannot spend.".to_string());
}

let total_value = tos.iter().map(|to| to.1).sum::<u64>();
info!(
"0: Creating transaction sending {} zatoshis to {} addresses",
total_value,
tos.len()
);

// Convert address (str) to RecipientAddress and value to Amount
let recipients = tos
.iter()
.map(|to| {
let ra = match address::RecipientAddress::decode(
&self.transaction_context.config.chain,
to.0,
) {
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::<Result<Vec<(address::RecipientAddress, Amount, Option<MemoBytes>)>, String>>(
)?;

let destination_uas = recipients
.iter()
.filter_map(|recipient| match recipient.0 {
address::RecipientAddress::Shielded(_) => None,
address::RecipientAddress::Transparent(_) => None,
address::RecipientAddress::Unified(ref ua) => Some(ua.clone()),
})
.collect::<Vec<_>>();

// Select notes to cover the target value
info!("{}: Selecting notes", now() - start_time);

let target_amount = (Amount::from_u64(total_value).unwrap() + MINIMUM_FEE).unwrap();

let (orchard_notes, sapling_notes, utxos, selected_value) =
self.select_notes_and_utxos(target_amount, policy).await;
if selected_value < target_amount {
let e = format!(
"Insufficient verified shielded funds. Have {} zats, need {} zats. NOTE: funds need at least {} confirmations before they can be spent. Transparent funds must be shielded before they can be spent. If you are trying to spend transparent funds, please use the shield button and try again in a few minutes.",
u64::from(selected_value), u64::from(target_amount), self.transaction_context.config
.reorg_buffer_offset + 1
);
error!("{}", e);
return Err(e);
}
info!("Selected notes worth {}", u64::from(selected_value));
orchard_notes: &[SpendableOrchardNote],
sapling_notes: &[SpendableSaplingNote],
utxos: &[ReceivedTransparentOutput],
) -> Result<Builder<'_, zingoconfig::ChainType, OsRng>, String> {
let txmds_readlock = self
.transaction_context
.transaction_metadata_set
.read()
.await;

let witness_trees = txmds_readlock
.witness_trees
.as_ref()
Expand All @@ -1050,14 +974,6 @@ impl LightWallet {
submission_height,
Some(orchard_anchor),
);
info!(
"{}: Adding {} sapling notes, {} orchard notes, and {} utxos",
now() - start_time,
sapling_notes.len(),
orchard_notes.len(),
utxos.len()
);

// Add all tinputs
// Create a map from address -> sk for all taddrs, so we can spend from the
// right address
Expand Down Expand Up @@ -1136,6 +1052,110 @@ impl LightWallet {
return Err(e);
}
}
drop(txmds_readlock);
Ok(builder)
}

async fn send_to_addresses_inner<F, Fut, P: TxProver>(
&self,
sapling_prover: P,
policy: NoteSelectionPolicy,
tos: Vec<(&str, u64, Option<MemoBytes>)>,
submission_height: BlockHeight,
broadcast_fn: F,
) -> Result<(String, Vec<u8>), String>
where
F: Fn(Box<[u8]>) -> Fut,
Fut: Future<Output = Result<String, String>>,
{
let start_time = now();
if tos.is_empty() {
return Err("Need at least one destination address".to_string());
}

if !self.wallet_capability().can_spend_from_all_pools() {
// Creating transactions in context of all possible combinations
// of wallet capabilities requires a rigorous case study
// and can have undesired effects if not implemented properly.
//
// Thus we forbid spending for wallets without complete spending capability for now
return Err("Wallet is in watch-only mode and thus it cannot spend.".to_string());
}

let total_value = tos.iter().map(|to| to.1).sum::<u64>();
info!(
"0: Creating transaction sending {} zatoshis to {} addresses",
total_value,
tos.len()
);

// Convert address (str) to RecipientAddress and value to Amount
let recipients = tos
.iter()
.map(|to| {
let ra = match address::RecipientAddress::decode(
&self.transaction_context.config.chain,
to.0,
) {
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::<Result<Vec<(address::RecipientAddress, Amount, Option<MemoBytes>)>, String>>(
)?;

let destination_uas = recipients
.iter()
.filter_map(|recipient| match recipient.0 {
address::RecipientAddress::Shielded(_) => None,
address::RecipientAddress::Transparent(_) => None,
address::RecipientAddress::Unified(ref ua) => Some(ua.clone()),
})
.collect::<Vec<_>>();

// Select notes to cover the target value
info!("{}: Selecting notes", now() - start_time);

let target_amount = (Amount::from_u64(total_value).unwrap() + MINIMUM_FEE).unwrap();

let (orchard_notes, sapling_notes, utxos, selected_value) =
self.select_notes_and_utxos(target_amount, policy).await;
if selected_value < target_amount {
let e = format!(
"Insufficient verified shielded funds. Have {} zats, need {} zats. NOTE: funds need at least {} confirmations before they can be spent. Transparent funds must be shielded before they can be spent. If you are trying to spend transparent funds, please use the shield button and try again in a few minutes.",
u64::from(selected_value), u64::from(target_amount), self.transaction_context.config
.reorg_buffer_offset + 1
);
error!("{}", e);
return Err(e);
}
info!("Selected notes worth {}", u64::from(selected_value));

info!(
"{}: Adding {} sapling notes, {} orchard notes, and {} utxos",
now() - start_time,
&sapling_notes.len(),
&orchard_notes.len(),
&utxos.len()
);

let mut builder = self
.load_transaction_builder_spends(
submission_height,
&orchard_notes,
&sapling_notes,
&utxos,
)
.await
.expect("To populate a builder with notes.");

// We'll use the first ovk to encrypt outgoing transactions
let sapling_ovk =
Expand Down Expand Up @@ -1249,7 +1269,7 @@ impl LightWallet {

builder.with_progress_notifier(transmitter);
let (transaction, _) = match builder.build(
&prover,
&sapling_prover,
&transaction::fees::fixed::FeeRule::non_standard(MINIMUM_FEE),
) {
Ok(res) => res,
Expand Down Expand Up @@ -1279,7 +1299,6 @@ impl LightWallet {

// Now that we've gotten this far, we need to write
// so we drop the readlock
drop(txmds_readlock);
// Mark notes as spent.
{
// Mark sapling notes as unconfirmed spent
Expand Down

0 comments on commit 0907e00

Please sign in to comment.