2022-11-12 17:39:12 -08:00
|
|
|
use super::{types::*, Result};
|
2022-11-06 04:50:51 -08:00
|
|
|
use crate::note_selection::fee::FeeCalculator;
|
|
|
|
use crate::note_selection::ua::decode;
|
2022-11-12 17:39:12 -08:00
|
|
|
use crate::note_selection::TransactionBuilderError::TxTooComplex;
|
|
|
|
use crate::note_selection::{TransactionBuilderError, MAX_ATTEMPTS};
|
2022-11-15 02:21:47 -08:00
|
|
|
use crate::Hash;
|
2022-11-23 20:47:34 -08:00
|
|
|
use zcash_primitives::consensus::Network;
|
2022-11-17 01:13:51 -08:00
|
|
|
use zcash_primitives::memo::MemoBytes;
|
2022-11-06 04:50:51 -08:00
|
|
|
|
|
|
|
pub fn sum_utxos(utxos: &[UTXO]) -> Result<PoolAllocation> {
|
|
|
|
let mut pool = PoolAllocation::default();
|
|
|
|
for utxo in utxos {
|
|
|
|
match utxo.source {
|
|
|
|
Source::Transparent { .. } => {
|
|
|
|
pool.0[0] += utxo.amount;
|
|
|
|
}
|
|
|
|
Source::Sapling { .. } => {
|
|
|
|
pool.0[1] += utxo.amount;
|
|
|
|
}
|
|
|
|
Source::Orchard { .. } => {
|
|
|
|
pool.0[2] += utxo.amount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(pool)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn group_orders(orders: &[Order], fee: u64) -> Result<(Vec<OrderInfo>, OrderGroupAmounts)> {
|
|
|
|
let mut order_info = vec![];
|
|
|
|
for order in orders {
|
|
|
|
let mut group_type = 0;
|
|
|
|
for i in 0..3 {
|
|
|
|
if order.destinations[i].is_some() {
|
|
|
|
group_type |= 1 << i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
order_info.push(OrderInfo {
|
|
|
|
group_type,
|
|
|
|
amount: order.amount,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut t0 = 0u64;
|
|
|
|
let mut s0 = 0u64;
|
|
|
|
let mut o0 = 0u64;
|
|
|
|
let mut x = 0u64;
|
|
|
|
for info in order_info.iter_mut() {
|
|
|
|
if info.group_type != 1 {
|
|
|
|
info.group_type &= 6; // unselect transparent outputs except for t-addr
|
|
|
|
}
|
|
|
|
match info.group_type {
|
|
|
|
1 => {
|
|
|
|
t0 += info.amount;
|
|
|
|
}
|
|
|
|
2 => {
|
|
|
|
s0 += info.amount;
|
|
|
|
}
|
|
|
|
4 => {
|
|
|
|
o0 += info.amount;
|
|
|
|
}
|
|
|
|
6 => {
|
|
|
|
x += info.amount;
|
|
|
|
}
|
2022-11-12 17:39:12 -08:00
|
|
|
_ => unreachable!(),
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
log::debug!("{} {} {} {}", t0, s0, o0, x);
|
2022-11-12 17:39:12 -08:00
|
|
|
let amounts = OrderGroupAmounts { t0, s0, o0, x, fee };
|
2022-11-06 04:50:51 -08:00
|
|
|
Ok((order_info, amounts))
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
fn get_net_chg(t0: i64, s0: i64, o0: i64, x: i64, t2: i64, fee: i64) -> (i64, i64) {
|
|
|
|
let (d_s, d_o) = match (x, s0, o0) {
|
|
|
|
(0, 0, _) => (0, t0 + fee - t2), // only orchard
|
|
|
|
(0, _, 0) => (t0 + fee - t2, 0), // only sapling
|
|
|
|
_ => ((t0 - t2 + fee) / 2, t0 + fee - t2 - (t0 - t2 + fee) / 2), // do not simplify because of rounding
|
|
|
|
};
|
|
|
|
log::info!("{} {}", d_s, d_o);
|
|
|
|
(d_s, d_o)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn allocate_funds(
|
|
|
|
amounts: &OrderGroupAmounts,
|
|
|
|
initial: &PoolAllocation,
|
|
|
|
) -> Result<FundAllocation> {
|
2022-11-06 04:50:51 -08:00
|
|
|
log::debug!("{:?}", initial);
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
let OrderGroupAmounts { t0, s0, o0, x, fee } = *amounts;
|
2022-11-06 04:50:51 -08:00
|
|
|
let (t0, s0, o0, x, fee) = (t0 as i64, s0 as i64, o0 as i64, x as i64, fee as i64);
|
|
|
|
|
|
|
|
let sum = t0 + s0 + o0 + x + fee;
|
|
|
|
let tmax = initial.0[0] as i64;
|
|
|
|
let smax = initial.0[1] as i64;
|
|
|
|
let omax = initial.0[2] as i64;
|
|
|
|
|
|
|
|
let mut s1;
|
|
|
|
let mut o1;
|
|
|
|
let mut s2;
|
|
|
|
let mut o2;
|
|
|
|
let mut t2 = sum - smax - omax;
|
|
|
|
if t2 > 0 {
|
|
|
|
if t2 > tmax {
|
2022-11-15 19:51:52 -08:00
|
|
|
return Err(TransactionBuilderError::NotEnoughFunds((t2 - tmax) as u64));
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
// Not enough shielded notes. Use them all before using transparent notes
|
|
|
|
s2 = smax;
|
|
|
|
o2 = omax;
|
2022-11-12 17:39:12 -08:00
|
|
|
let (d_s, d_o) = get_net_chg(t0, s0, o0, x, t2, fee);
|
2022-11-06 04:50:51 -08:00
|
|
|
s1 = s2 - d_s - s0;
|
|
|
|
o1 = o2 - d_o - o0;
|
2022-11-12 17:39:12 -08:00
|
|
|
} else {
|
2022-11-06 04:50:51 -08:00
|
|
|
t2 = 0;
|
2022-11-12 17:39:12 -08:00
|
|
|
let (d_s, d_o) = get_net_chg(t0, s0, o0, x, t2, fee);
|
2022-11-06 04:50:51 -08:00
|
|
|
|
|
|
|
// Solve relaxed problem
|
|
|
|
let inp = sum / 2;
|
|
|
|
s2 = inp;
|
|
|
|
o2 = sum - inp;
|
|
|
|
s1 = s2 - d_s - s0;
|
|
|
|
o1 = o2 - d_o - o0;
|
|
|
|
|
|
|
|
// Check solution validity
|
|
|
|
if s1 < 0 {
|
|
|
|
s1 = 0;
|
|
|
|
o1 = x;
|
|
|
|
s2 = s0 + d_s;
|
|
|
|
o2 = o0 + d_o + x;
|
|
|
|
} else if o1 < 0 {
|
|
|
|
o1 = 0;
|
|
|
|
s1 = x;
|
|
|
|
o2 = o0 + d_o;
|
|
|
|
s2 = s0 + d_s + x;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(s2 >= 0);
|
|
|
|
assert!(o2 >= 0);
|
|
|
|
|
|
|
|
// Check account balances
|
|
|
|
|
|
|
|
if s2 > smax {
|
|
|
|
s2 = smax;
|
|
|
|
o2 = sum - s2;
|
2022-11-17 22:31:48 -08:00
|
|
|
s1 = s2 - d_s - s0;
|
|
|
|
o1 = x - s1;
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
if o2 > omax {
|
|
|
|
o2 = omax;
|
|
|
|
s2 = sum - o2;
|
2022-11-17 22:31:48 -08:00
|
|
|
o1 = o2 - d_o - o0;
|
|
|
|
s1 = x - o1;
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 22:31:48 -08:00
|
|
|
if s1 < 0 {
|
|
|
|
s1 = 0;
|
|
|
|
o1 = x;
|
|
|
|
} else if o1 < 0 {
|
|
|
|
o1 = 0;
|
|
|
|
s1 = x;
|
|
|
|
}
|
|
|
|
|
2022-11-06 04:50:51 -08:00
|
|
|
assert!(s1 >= 0);
|
|
|
|
assert!(o1 >= 0);
|
|
|
|
assert!(t2 >= 0);
|
|
|
|
assert!(s2 >= 0);
|
|
|
|
assert!(o2 >= 0);
|
|
|
|
assert!(t2 <= tmax);
|
|
|
|
assert!(s2 <= smax);
|
|
|
|
assert!(o2 <= omax);
|
|
|
|
|
|
|
|
assert_eq!(sum, t2 + s2 + o2);
|
|
|
|
assert_eq!(x, s1 + o1);
|
|
|
|
|
|
|
|
log::debug!("{} {}", s1, o1);
|
|
|
|
log::debug!("{} {} {}", t2, s2, o2);
|
|
|
|
|
|
|
|
let fund_allocation = FundAllocation {
|
|
|
|
s1: s1 as u64,
|
|
|
|
o1: o1 as u64,
|
|
|
|
t2: t2 as u64,
|
|
|
|
s2: s2 as u64,
|
|
|
|
o2: o2 as u64,
|
|
|
|
};
|
|
|
|
Ok(fund_allocation)
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
pub fn fill(
|
|
|
|
orders: &[Order],
|
|
|
|
order_infos: &[OrderInfo],
|
|
|
|
amounts: &OrderGroupAmounts,
|
|
|
|
allocation: &FundAllocation,
|
|
|
|
) -> Result<Vec<Fill>> {
|
2022-11-06 04:50:51 -08:00
|
|
|
assert_eq!(orders.len(), order_infos.len());
|
|
|
|
let mut fills = vec![];
|
|
|
|
let mut f = 0f64;
|
|
|
|
if amounts.x != 0 {
|
|
|
|
f = allocation.s1 as f64 / amounts.x as f64;
|
|
|
|
}
|
|
|
|
for (order, info) in orders.iter().zip(order_infos) {
|
|
|
|
match info.group_type {
|
|
|
|
1 | 2 | 4 => {
|
|
|
|
let fill = Fill {
|
|
|
|
id_order: Some(order.id),
|
|
|
|
destination: order.destinations[ilog2(info.group_type)].unwrap(),
|
|
|
|
amount: order.amount,
|
|
|
|
memo: order.memo.clone(),
|
|
|
|
};
|
|
|
|
fills.push(fill);
|
|
|
|
}
|
|
|
|
6 => {
|
|
|
|
let fill1 = Fill {
|
|
|
|
id_order: Some(order.id),
|
|
|
|
destination: order.destinations[1].unwrap(),
|
|
|
|
amount: (order.amount as f64 * f).round() as u64,
|
|
|
|
memo: order.memo.clone(),
|
|
|
|
};
|
|
|
|
let fill2 = Fill {
|
|
|
|
id_order: Some(order.id),
|
|
|
|
destination: order.destinations[2].unwrap(),
|
|
|
|
amount: order.amount - fill1.amount,
|
|
|
|
memo: order.memo.clone(),
|
|
|
|
};
|
2022-11-12 17:39:12 -08:00
|
|
|
if fill1.amount != 0 {
|
|
|
|
fills.push(fill1);
|
|
|
|
}
|
|
|
|
if fill2.amount != 0 {
|
|
|
|
fills.push(fill2);
|
|
|
|
}
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
2022-11-12 17:39:12 -08:00
|
|
|
_ => unreachable!(),
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(fills)
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
pub fn select_inputs(
|
|
|
|
utxos: &[UTXO],
|
|
|
|
allocation: &FundAllocation,
|
|
|
|
) -> Result<(Vec<UTXO>, PoolAllocation)> {
|
2022-11-06 04:50:51 -08:00
|
|
|
let mut needed = [allocation.t2, allocation.s2, allocation.o2];
|
|
|
|
let mut change = [0u64; 3];
|
|
|
|
let mut inputs = vec![];
|
|
|
|
for utxo in utxos {
|
|
|
|
let idx = match utxo.source {
|
|
|
|
Source::Transparent { .. } => 0,
|
|
|
|
Source::Sapling { .. } => 1,
|
|
|
|
Source::Orchard { .. } => 2,
|
|
|
|
};
|
|
|
|
if needed[idx] > 0 {
|
|
|
|
let available = utxo.amount;
|
|
|
|
let a = available.min(needed[idx]);
|
|
|
|
inputs.push(utxo.clone());
|
|
|
|
needed[idx] -= a;
|
|
|
|
change[idx] += available - a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((inputs, PoolAllocation(change)))
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
pub fn outputs_for_change(
|
|
|
|
change_destinations: &[Option<Destination>; 3],
|
|
|
|
change: &PoolAllocation,
|
|
|
|
) -> Result<Vec<Fill>> {
|
2022-11-06 04:50:51 -08:00
|
|
|
let mut change_fills = vec![];
|
|
|
|
for i in 0..3 {
|
|
|
|
let destination = change_destinations[i];
|
2022-11-22 01:53:16 -08:00
|
|
|
match destination {
|
|
|
|
Some(destination) => {
|
|
|
|
let change_fill = Fill {
|
|
|
|
id_order: None,
|
|
|
|
destination,
|
|
|
|
amount: change.0[i],
|
|
|
|
memo: MemoBytes::empty(),
|
|
|
|
};
|
|
|
|
if change_fill.amount != 0 {
|
|
|
|
change_fills.push(change_fill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None if change.0[i] == 0 => {}
|
|
|
|
None => {
|
|
|
|
return Err(anyhow::anyhow!("No change address").into());
|
2022-11-12 17:39:12 -08:00
|
|
|
}
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(change_fills)
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
pub fn build_tx_plan<F: FeeCalculator>(
|
2022-11-23 00:59:22 -08:00
|
|
|
network: &Network,
|
2022-11-12 17:39:12 -08:00
|
|
|
fvk: &str,
|
|
|
|
height: u32,
|
2022-11-23 00:59:22 -08:00
|
|
|
orchard_anchor: &Option<Hash>,
|
2022-11-12 17:39:12 -08:00
|
|
|
utxos: &[UTXO],
|
|
|
|
orders: &[Order],
|
|
|
|
config: &TransactionBuilderConfig,
|
|
|
|
) -> Result<TransactionPlan> {
|
2022-11-06 04:50:51 -08:00
|
|
|
let mut fee = 0;
|
|
|
|
|
|
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
|
|
let balances = sum_utxos(utxos)?;
|
|
|
|
let (groups, amounts) = group_orders(&orders, fee)?;
|
|
|
|
let allocation = allocate_funds(&amounts, &balances)?;
|
|
|
|
|
|
|
|
let OrderGroupAmounts { s0, o0, .. } = amounts;
|
|
|
|
let FundAllocation { s1, o1, s2, o2, .. } = allocation;
|
2022-11-12 17:39:12 -08:00
|
|
|
let (s0, o0, s1, o1, s2, o2) = (
|
|
|
|
s0 as i64, o0 as i64, s1 as i64, o1 as i64, s2 as i64, o2 as i64,
|
|
|
|
);
|
2022-11-06 04:50:51 -08:00
|
|
|
let net_chg = [s0 + s1 - s2, o0 + o1 - o2];
|
|
|
|
|
|
|
|
let mut fills = fill(&orders, &groups, &amounts, &allocation)?;
|
|
|
|
|
|
|
|
let (notes, change) = select_inputs(&utxos, &allocation)?;
|
2022-11-23 00:59:22 -08:00
|
|
|
let change_destinations = decode(network, &config.change_address)?;
|
2022-11-22 01:53:16 -08:00
|
|
|
let change_outputs = outputs_for_change(&change_destinations, &change)?;
|
2022-11-06 04:50:51 -08:00
|
|
|
fills.extend(change_outputs);
|
|
|
|
|
|
|
|
let updated_fee = F::calculate_fee(¬es, &fills);
|
|
|
|
if updated_fee == fee {
|
|
|
|
let tx_plan = TransactionPlan {
|
|
|
|
fvk: fvk.to_string(),
|
|
|
|
height,
|
2022-11-23 00:59:22 -08:00
|
|
|
orchard_anchor: orchard_anchor.unwrap_or(Hash::default()),
|
2022-11-06 04:50:51 -08:00
|
|
|
spends: notes,
|
|
|
|
outputs: fills,
|
|
|
|
net_chg,
|
2022-11-12 17:39:12 -08:00
|
|
|
fee,
|
2022-11-06 04:50:51 -08:00
|
|
|
};
|
2022-11-12 17:39:12 -08:00
|
|
|
return Ok(tx_plan);
|
2022-11-06 04:50:51 -08:00
|
|
|
}
|
|
|
|
fee = updated_fee;
|
|
|
|
}
|
|
|
|
Err(TxTooComplex)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ilog2(u: usize) -> usize {
|
|
|
|
match u {
|
|
|
|
1 => 0,
|
|
|
|
2 => 1,
|
|
|
|
4 => 2,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|