zcash-sync/src/api/payment_v2.rs

200 lines
6.4 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;
2022-11-17 01:13:51 -08:00
use crate::note_selection::{FeeFlat, Order};
2022-11-12 17:39:12 -08:00
use crate::{
2022-11-15 19:51:52 -08:00
build_tx, fetch_utxos, get_secret_keys, note_selection, AccountData, CoinConfig, DbAdapter,
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;
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
pub async fn build_tx_plan(
coin: u8,
account: u32,
last_height: u32,
recipients: &[RecipientMemo],
2022-11-15 19:51:52 -08:00
excluded_flags: u8,
2022-11-06 04:50:51 -08:00
confirmations: u32,
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-11-15 02:21:47 -08:00
let (fvk, checkpoint_height) = {
2022-11-06 04:50:51 -08:00
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
2022-11-15 19:51:52 -08:00
let checkpoint_height = get_checkpoint_height(&db, last_height, confirmations)?;
2022-11-15 02:21:47 -08:00
(fvk, checkpoint_height)
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-11-23 00:59:22 -08:00
let order = Order::new(network, id_order, &r.address, a, 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-11-15 19:51:52 -08:00
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
2022-11-06 04:50:51 -08:00
2022-11-12 17:39:12 -08:00
let config = TransactionBuilderConfig::new(&change_address);
2022-11-15 02:21:47 -08:00
let tx_plan = crate::note_selection::build_tx_plan::<FeeFlat>(
2022-11-23 00:59:22 -08:00
network,
2022-11-12 17:39:12 -08:00
&fvk,
2022-11-15 02:21:47 -08:00
checkpoint_height,
&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
pub fn sign_plan(coin: u8, account: u32, tx_plan: &TransactionPlan) -> anyhow::Result<Vec<u8>> {
let c = CoinConfig::get(coin);
let fvk = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
fvk
};
if fvk != tx_plan.fvk {
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-11-29 07:24:01 -08:00
pub enum AmountOrMax {
Amount(u64),
Max
}
pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: AmountOrMax,
2022-12-05 09:43:50 -08:00
memo: &str, 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
let a = match amount {
AmountOrMax::Amount(a) => a,
AmountOrMax::Max => 0,
};
2022-11-15 19:51:52 -08:00
let recipient = RecipientMemo {
address,
2022-11-29 07:24:01 -08:00
amount: a,
2022-12-05 09:43:50 -08:00
memo: Memo::from_str(memo)?,
2022-11-15 19:51:52 -08:00
max_amount_per_note: 0,
};
let last_height = get_latest_height().await?;
2022-11-29 07:24:01 -08:00
let tx_plan = match amount {
AmountOrMax::Amount(_) => build_tx_plan(coin, account, last_height, slice::from_ref(&recipient),
!from_pool, confirmations).await?,
AmountOrMax::Max => build_max_tx(coin, account, last_height, &recipient, !from_pool, confirmations).await?,
};
Ok(tx_plan)
}
/// Make a transaction that shields the transparent balance
pub async fn shield_taddr(coin: u8, account: u32, confirmations: u32) -> anyhow::Result<String> {
2022-12-05 09:43:50 -08:00
let tx_plan = transfer_pools(coin, account, 1, 6, AmountOrMax::Max, "Shield Transparent Balance", confirmations).await?;
2022-11-15 19:51:52 -08:00
let tx_id = sign_and_broadcast(coin, account, &tx_plan).await?;
log::info!("TXID: {}", tx_id);
Ok(tx_id)
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(())
}
2022-11-15 19:51:52 -08:00
fn get_checkpoint_height(
db: &DbAdapter,
last_height: u32,
confirmations: u32,
) -> anyhow::Result<u32> {
let anchor_height = last_height.saturating_sub(confirmations);
let checkpoint_height = db
.get_checkpoint_height(anchor_height)?
.unwrap_or_else(|| db.sapling_activation_height()); // get the latest checkpoint before the requested anchor height
Ok(checkpoint_height)
}