Add ZIP-321 request based sends to zcash_client_backend.
This commit is contained in:
parent
8e16d93f94
commit
9b3025de4d
|
@ -217,6 +217,12 @@ pub struct DecryptedTransaction<'a> {
|
|||
pub struct SentTransaction<'a> {
|
||||
pub tx: &'a Transaction,
|
||||
pub created: time::OffsetDateTime,
|
||||
pub account: AccountId,
|
||||
pub outputs: Vec<SentTransactionOutput<'a>>,
|
||||
pub utxos_spent: Vec<OutPoint>,
|
||||
}
|
||||
|
||||
pub struct SentTransactionOutput<'a> {
|
||||
/// The index within the transaction that contains the recipient output.
|
||||
///
|
||||
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
|
||||
|
@ -224,11 +230,9 @@ pub struct SentTransaction<'a> {
|
|||
/// - If `recipient_address` is a transparent address, this is an index into the
|
||||
/// transparent outputs of the transaction.
|
||||
pub output_index: usize,
|
||||
pub account: AccountId,
|
||||
pub recipient_address: &'a RecipientAddress,
|
||||
pub value: Amount,
|
||||
pub memo: Option<MemoBytes>,
|
||||
pub utxos_spent: Vec<OutPoint>,
|
||||
}
|
||||
|
||||
/// This trait encapsulates the write capabilities required to update stored
|
||||
|
|
|
@ -13,9 +13,12 @@ use zcash_primitives::{
|
|||
|
||||
use crate::{
|
||||
address::RecipientAddress,
|
||||
data_api::{error::Error, DecryptedTransaction, SentTransaction, WalletWrite},
|
||||
data_api::{
|
||||
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite,
|
||||
},
|
||||
decrypt_transaction,
|
||||
wallet::{AccountId, OvkPolicy},
|
||||
zip321::{Payment, TransactionRequest},
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
|
@ -159,10 +162,39 @@ pub fn create_spend_to_address<E, N, P, D, R>(
|
|||
account: AccountId,
|
||||
extsk: &ExtendedSpendingKey,
|
||||
to: &RecipientAddress,
|
||||
value: Amount,
|
||||
amount: Amount,
|
||||
memo: Option<MemoBytes>,
|
||||
ovk_policy: OvkPolicy,
|
||||
) -> Result<R, E>
|
||||
where
|
||||
E: From<Error<N>>,
|
||||
P: consensus::Parameters + Clone,
|
||||
R: Copy + Debug,
|
||||
D: WalletWrite<Error = E, TxRef = R>,
|
||||
{
|
||||
let req = TransactionRequest {
|
||||
payments: vec![Payment {
|
||||
recipient_address: to.clone(),
|
||||
amount,
|
||||
memo,
|
||||
label: None,
|
||||
message: None,
|
||||
other_params: vec![],
|
||||
}],
|
||||
};
|
||||
|
||||
spend(wallet_db, params, prover, account, extsk, &req, ovk_policy)
|
||||
}
|
||||
|
||||
pub fn spend<E, N, P, D, R>(
|
||||
wallet_db: &mut D,
|
||||
params: &P,
|
||||
prover: impl TxProver,
|
||||
account: AccountId,
|
||||
extsk: &ExtendedSpendingKey,
|
||||
request: &TransactionRequest,
|
||||
ovk_policy: OvkPolicy,
|
||||
) -> Result<R, E>
|
||||
where
|
||||
E: From<Error<N>>,
|
||||
P: consensus::Parameters + Clone,
|
||||
|
@ -188,7 +220,7 @@ where
|
|||
.get_target_and_anchor_heights()
|
||||
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
|
||||
|
||||
let target_value = value + DEFAULT_FEE;
|
||||
let target_value = request.payments.iter().map(|p| p.amount).sum::<Amount>() + DEFAULT_FEE;
|
||||
let spendable_notes =
|
||||
wallet_db.select_unspent_sapling_notes(account, target_value, anchor_height)?;
|
||||
|
||||
|
@ -202,6 +234,7 @@ where
|
|||
}
|
||||
|
||||
// Create the transaction
|
||||
let consensus_branch_id = BranchId::for_height(params, height);
|
||||
let mut builder = Builder::new(params.clone(), height);
|
||||
for selected in spendable_notes {
|
||||
let from = extfvk
|
||||
|
@ -221,55 +254,61 @@ where
|
|||
.map_err(Error::Builder)?;
|
||||
}
|
||||
|
||||
match to {
|
||||
RecipientAddress::Shielded(to) => {
|
||||
memo.clone().ok_or(Error::MemoRequired).and_then(|memo| {
|
||||
builder
|
||||
.add_sapling_output(ovk, to.clone(), value, memo)
|
||||
.map_err(Error::Builder)
|
||||
})
|
||||
}
|
||||
RecipientAddress::Transparent(to) => {
|
||||
if memo.is_some() {
|
||||
Err(Error::MemoForbidden)
|
||||
} else {
|
||||
builder
|
||||
.add_transparent_output(&to, value)
|
||||
.map_err(Error::Builder)
|
||||
for payment in &request.payments {
|
||||
match &payment.recipient_address {
|
||||
RecipientAddress::Shielded(to) => builder
|
||||
.add_sapling_output(
|
||||
ovk,
|
||||
to.clone(),
|
||||
payment.amount,
|
||||
payment.memo.clone().unwrap_or_else(MemoBytes::empty),
|
||||
)
|
||||
.map_err(Error::Builder),
|
||||
RecipientAddress::Transparent(to) => {
|
||||
if payment.memo.is_some() {
|
||||
Err(Error::MemoForbidden)
|
||||
} else {
|
||||
builder
|
||||
.add_transparent_output(&to, payment.amount)
|
||||
.map_err(Error::Builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}?;
|
||||
}?
|
||||
}
|
||||
|
||||
let consensus_branch_id = BranchId::for_height(params, height);
|
||||
let (tx, tx_metadata) = builder
|
||||
.build(consensus_branch_id, &prover)
|
||||
.map_err(Error::Builder)?;
|
||||
|
||||
let output_index = match to {
|
||||
// Sapling outputs are shuffled, so we need to look up where the output ended up.
|
||||
RecipientAddress::Shielded(_) => match tx_metadata.output_index(0) {
|
||||
Some(idx) => idx,
|
||||
None => panic!("Output 0 should exist in the transaction"),
|
||||
},
|
||||
RecipientAddress::Transparent(addr) => {
|
||||
let script = addr.script();
|
||||
tx.vout
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||
.map(|(index, _)| index)
|
||||
.expect("we sent to this address")
|
||||
let sent_outputs = request.payments.iter().enumerate().map(|(i, payment)| {
|
||||
let idx = match &payment.recipient_address {
|
||||
// Sapling outputs are shuffled, so we need to look up where the output ended up.
|
||||
RecipientAddress::Shielded(_) =>
|
||||
tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment."),
|
||||
RecipientAddress::Transparent(addr) => {
|
||||
let script = addr.script();
|
||||
tx.vout
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||
.map(|(index, _)| index)
|
||||
.expect("An output should exist in the transaction for each transparent payment.")
|
||||
}
|
||||
};
|
||||
|
||||
SentTransactionOutput {
|
||||
output_index: idx,
|
||||
recipient_address: &payment.recipient_address,
|
||||
value: payment.amount,
|
||||
memo: payment.memo.clone()
|
||||
}
|
||||
};
|
||||
}).collect();
|
||||
|
||||
wallet_db.store_sent_tx(&SentTransaction {
|
||||
tx: &tx,
|
||||
created: time::OffsetDateTime::now_utc(),
|
||||
output_index,
|
||||
outputs: sent_outputs,
|
||||
account,
|
||||
recipient_address: to,
|
||||
value,
|
||||
memo,
|
||||
utxos_spent: vec![],
|
||||
})
|
||||
}
|
||||
|
@ -353,11 +392,13 @@ where
|
|||
wallet_db.store_sent_tx(&SentTransaction {
|
||||
tx: &tx,
|
||||
created: time::OffsetDateTime::now_utc(),
|
||||
output_index,
|
||||
account,
|
||||
recipient_address: &RecipientAddress::Shielded(z_address),
|
||||
value: amount_to_shield,
|
||||
memo: Some(memo.clone()),
|
||||
outputs: vec![SentTransactionOutput {
|
||||
output_index,
|
||||
recipient_address: &RecipientAddress::Shielded(z_address),
|
||||
value: amount_to_shield,
|
||||
memo: Some(memo.clone()),
|
||||
}],
|
||||
utxos_spent: utxos.iter().map(|utxo| utxo.outpoint.clone()).collect(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ impl Payment {
|
|||
/// payment value in the request.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TransactionRequest {
|
||||
payments: Vec<Payment>,
|
||||
pub payments: Vec<Payment>,
|
||||
}
|
||||
|
||||
impl TransactionRequest {
|
||||
|
|
|
@ -587,15 +587,17 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
wallet::mark_transparent_utxo_spent(up, tx_ref, &utxo_outpoint)?;
|
||||
}
|
||||
|
||||
wallet::insert_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
sent_tx.output_index,
|
||||
sent_tx.account,
|
||||
sent_tx.recipient_address,
|
||||
sent_tx.value,
|
||||
sent_tx.memo.as_ref(),
|
||||
)?;
|
||||
for output in &sent_tx.outputs {
|
||||
wallet::insert_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
output.recipient_address,
|
||||
output.value,
|
||||
output.memo.as_ref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Return the row number of the transaction, so the caller can fetch it for sending.
|
||||
Ok(tx_ref)
|
||||
|
|
|
@ -163,7 +163,7 @@ pub fn init_wallet_db<P>(wdb: &WalletDb<P>) -> Result<(), rusqlite::Error> {
|
|||
/// let tsk = derive_secret_key_from_seed(&Network::TestNetwork, &seed, account, 0).unwrap();
|
||||
/// let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
/// let taddr = derive_transparent_address_from_secret_key(&tsk);
|
||||
/// init_accounts_table(&db_data, &[&extfvk], &[&taddr]).unwrap();
|
||||
/// init_accounts_table(&db_data, &[extfvk], &[taddr]).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// [`get_address`]: crate::wallet::get_address
|
||||
|
|
Loading…
Reference in New Issue