zcash-sync/src/api/payment_v2.rs

264 lines
7.6 KiB
Rust
Raw Normal View History

2022-11-12 17:39:12 -08:00
use crate::api::account::get_unified_address;
2022-11-17 01:13:51 -08:00
use crate::api::recipient::RecipientMemo;
2022-11-12 17:39:12 -08:00
use crate::api::sync::get_latest_height;
pub use crate::broadcast_tx;
2023-02-21 00:36:29 -08:00
use crate::chain::{get_checkpoint_height, EXPIRY_HEIGHT_OFFSET};
use crate::note_selection::{FeeFlat, Order, UTXO};
2022-11-12 17:39:12 -08:00
use crate::{
2023-02-21 00:36:29 -08:00
build_tx, fetch_utxos, get_secret_keys, note_selection, AccountData, CoinConfig,
2022-11-15 19:51:52 -08:00
TransactionBuilderConfig, TransactionBuilderError, TransactionPlan, TxBuilderContext,
MAX_ATTEMPTS,
2022-11-12 17:39:12 -08:00
};
use rand::rngs::OsRng;
2022-11-06 04:50:51 -08:00
use std::cmp::min;
2022-11-15 19:51:52 -08:00
use std::slice;
use std::str::FromStr;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::Parameters;
2022-11-17 01:13:51 -08:00
use zcash_primitives::memo::{Memo, MemoBytes};
2022-11-12 17:39:12 -08:00
use zcash_primitives::transaction::builder::Progress;
2022-11-04 18:58:35 -07:00
2022-11-17 01:13:51 -08:00
#[allow(dead_code)]
2022-11-12 17:39:12 -08:00
type PaymentProgressCallback = Box<dyn Fn(Progress) + Send + Sync>;
2022-11-06 04:50:51 -08:00
2023-02-21 00:36:29 -08:00
pub async fn build_tx_plan_with_utxos(
2022-11-06 04:50:51 -08:00
coin: u8,
account: u32,
2023-02-21 00:36:29 -08:00
checkpoint_height: u32,
expiry_height: u32,
2022-11-06 04:50:51 -08:00
recipients: &[RecipientMemo],
2023-02-21 00:36:29 -08:00
utxos: &[UTXO],
2022-11-15 19:51:52 -08:00
) -> note_selection::Result<TransactionPlan> {
2022-11-06 04:50:51 -08:00
let c = CoinConfig::get(coin);
2022-11-23 00:59:22 -08:00
let network = c.chain.network();
2022-12-10 14:01:10 -08:00
let mut recipient_fee = false;
for r in recipients {
if r.fee_included {
2022-12-21 14:54:25 -08:00
if recipient_fee {
return Err(TransactionBuilderError::DuplicateRecipientFee);
}
2022-12-10 14:01:10 -08:00
recipient_fee = true;
}
}
2023-04-18 16:55:10 -07:00
let (fvk, taddr, orchard_fvk) = {
2022-11-06 04:50:51 -08:00
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
2023-04-14 06:00:42 -07:00
let taddr = db.get_taddr(account)?.unwrap_or_default();
2023-04-18 16:55:10 -07:00
let orchard_fvk = db.get_orchard(account)?.map(|o| hex::encode(&o.fvk)).unwrap_or_default();
(fvk, taddr, orchard_fvk)
2022-11-06 04:50:51 -08:00
};
2022-11-29 07:24:01 -08:00
let change_address = get_unified_address(coin, account, 7)?;
2022-11-15 02:21:47 -08:00
let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
2022-11-06 04:50:51 -08:00
let mut orders = vec![];
let mut id_order = 0;
for r in recipients {
let mut amount = r.amount;
let max_amount_per_note = if r.max_amount_per_note == 0 {
u64::MAX
} else {
r.max_amount_per_note
};
2022-11-26 19:48:22 -08:00
loop {
2022-11-06 04:50:51 -08:00
let a = min(amount, max_amount_per_note);
let memo_bytes: MemoBytes = r.memo.clone().into();
2022-12-10 14:01:10 -08:00
let order = Order::new(network, id_order, &r.address, a, false, memo_bytes);
2022-11-06 04:50:51 -08:00
orders.push(order);
amount -= a;
id_order += 1;
2022-11-26 19:48:22 -08:00
if amount == 0 {
break;
} // at least one note even when amount = 0
2022-11-06 04:50:51 -08:00
}
2022-12-10 14:01:10 -08:00
orders.last_mut().unwrap().take_fee = r.fee_included;
2022-11-06 04:50:51 -08:00
}
2022-11-12 17:39:12 -08:00
let config = TransactionBuilderConfig::new(&change_address);
2023-02-21 00:36:29 -08:00
let tx_plan = note_selection::build_tx_plan::<FeeFlat>(
2022-11-23 00:59:22 -08:00
network,
2022-11-12 17:39:12 -08:00
&fvk,
2023-04-14 06:00:42 -07:00
&taddr,
2023-04-18 16:55:10 -07:00
&orchard_fvk,
2022-11-15 02:21:47 -08:00
checkpoint_height,
2023-02-07 14:06:37 -08:00
expiry_height,
2022-11-15 02:21:47 -08:00
&context.orchard_anchor,
2022-11-12 17:39:12 -08:00
&utxos,
&orders,
&config,
)?;
2022-11-06 04:50:51 -08:00
Ok(tx_plan)
}
2022-11-12 17:39:12 -08:00
2023-02-21 00:36:29 -08:00
pub async fn build_tx_plan(
coin: u8,
account: u32,
last_height: u32,
recipients: &[RecipientMemo],
excluded_flags: u8,
confirmations: u32,
) -> note_selection::Result<TransactionPlan> {
2023-03-01 15:31:21 -08:00
let checkpoint_height = {
2023-02-21 00:36:29 -08:00
let c = CoinConfig::get(coin);
let db = c.db()?;
let checkpoint_height = get_checkpoint_height(&db, last_height, confirmations)?;
2023-03-01 15:31:21 -08:00
checkpoint_height
2023-02-21 00:36:29 -08:00
};
2023-03-01 15:31:21 -08:00
let expiry_height = get_latest_height().await? + EXPIRY_HEIGHT_OFFSET;
2023-02-21 00:36:29 -08:00
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
let tx_plan = build_tx_plan_with_utxos(
coin,
account,
checkpoint_height,
expiry_height,
recipients,
&utxos,
)
.await?;
Ok(tx_plan)
}
2022-11-12 17:39:12 -08:00
pub fn sign_plan(coin: u8, account: u32, tx_plan: &TransactionPlan) -> anyhow::Result<Vec<u8>> {
let c = CoinConfig::get(coin);
let network = c.chain.network();
2022-11-12 17:39:12 -08:00
let fvk = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
fvk
};
let fvk =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk)
.unwrap()
.to_diversifiable_full_viewing_key();
let tx_plan_fvk = decode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
&tx_plan.fvk,
)
.unwrap()
.to_diversifiable_full_viewing_key();
2022-11-12 17:39:12 -08:00
if fvk.to_bytes() != tx_plan_fvk.to_bytes() {
2022-11-12 17:39:12 -08:00
return Err(anyhow::anyhow!("Account does not match transaction"));
}
let keys = get_secret_keys(coin, account)?;
2022-11-15 02:21:47 -08:00
let tx = build_tx(c.chain.network(), &keys, &tx_plan, OsRng)?;
2022-11-12 17:39:12 -08:00
Ok(tx)
}
pub async fn sign_and_broadcast(
coin: u8,
account: u32,
tx_plan: &TransactionPlan,
) -> anyhow::Result<String> {
let tx = sign_plan(coin, account, tx_plan)?;
let txid = broadcast_tx(&tx).await?;
2022-11-13 01:00:21 -08:00
let id_notes: Vec<_> = tx_plan
.spends
.iter()
.filter_map(|n| if n.id != 0 { Some(n.id) } else { None })
.collect();
mark_spent(coin, &id_notes)?;
2022-11-12 17:39:12 -08:00
Ok(txid)
}
2022-11-15 19:51:52 -08:00
pub async fn build_max_tx(
coin: u8,
account: u32,
last_height: u32,
recipient: &RecipientMemo, // amount & max_amount per note are ignored
excluded_flags: u8,
confirmations: u32,
) -> note_selection::Result<TransactionPlan> {
let mut recipient = recipient.clone();
let checkpoint_height = {
let c = CoinConfig::get(coin);
let db = c.db()?;
get_checkpoint_height(&db, last_height, confirmations)?
};
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
let available_funds: u64 = utxos.iter().map(|n| n.amount).sum();
recipient.amount = available_funds;
for _ in 0..MAX_ATTEMPTS {
// this will fail at least once because of the fees
let result = build_tx_plan(
coin,
account,
last_height,
slice::from_ref(&recipient),
excluded_flags,
confirmations,
)
.await;
match result {
Err(TransactionBuilderError::NotEnoughFunds(missing)) => {
recipient.amount -= missing; // reduce the amount and retry
}
_ => return result,
}
}
Err(TransactionBuilderError::TxTooComplex)
}
2022-12-21 14:54:25 -08:00
pub async fn transfer_pools(
coin: u8,
account: u32,
from_pool: u8,
to_pool: u8,
amount: u64,
fee_included: bool,
memo: &str,
split_amount: u64,
confirmations: u32,
) -> anyhow::Result<TransactionPlan> {
2022-11-29 07:24:01 -08:00
let address = get_unified_address(coin, account, to_pool)?; // get our own unified address
2022-11-15 19:51:52 -08:00
let recipient = RecipientMemo {
address,
2022-12-10 14:01:10 -08:00
amount,
fee_included,
2022-12-05 09:43:50 -08:00
memo: Memo::from_str(memo)?,
2022-12-08 01:17:18 -08:00
max_amount_per_note: split_amount,
2022-11-15 19:51:52 -08:00
};
let last_height = get_latest_height().await?;
2022-12-21 14:54:25 -08:00
let tx_plan = build_tx_plan(
coin,
account,
last_height,
slice::from_ref(&recipient),
!from_pool,
confirmations,
)
.await?;
2022-11-29 07:24:01 -08:00
Ok(tx_plan)
}
/// Make a transaction that shields the transparent balance
2022-12-21 14:54:25 -08:00
pub async fn shield_taddr(
coin: u8,
account: u32,
amount: u64,
confirmations: u32,
) -> anyhow::Result<TransactionPlan> {
2022-12-21 14:54:25 -08:00
let tx_plan = transfer_pools(
coin,
account,
1,
6,
amount,
true,
"Shield Transparent Balance",
0,
confirmations,
)
.await?;
Ok(tx_plan)
2022-11-12 17:39:12 -08:00
}
2022-11-13 01:00:21 -08:00
fn mark_spent(coin: u8, ids: &[u32]) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let mut db = c.db()?;
db.tx_mark_spend(ids)?;
Ok(())
}