2022-11-01 04:03:57 -07:00
|
|
|
use std::cmp::min;
|
|
|
|
use std::slice;
|
|
|
|
use zcash_primitives::memo::MemoBytes;
|
|
|
|
use crate::note_selection::decode;
|
|
|
|
use crate::note_selection::fee::FeeCalculator;
|
|
|
|
use crate::note_selection::fill::execute_orders;
|
2022-11-05 02:29:16 -07:00
|
|
|
use crate::note_selection::types::{NoteSelectConfig, Order, PoolAllocation, UTXO, Destination, TransactionPlan, Fill, PoolPrecedence, PoolPriority};
|
2022-11-01 04:03:57 -07:00
|
|
|
|
|
|
|
pub fn select_notes(allocation: &PoolAllocation, utxos: &[UTXO]) -> anyhow::Result<Vec<UTXO>> {
|
|
|
|
let mut allocation = allocation.clone();
|
|
|
|
|
|
|
|
let mut selected = vec![];
|
|
|
|
for utxo in utxos {
|
|
|
|
let pool = utxo.source.pool() as usize;
|
|
|
|
if allocation.0[pool] > 0 {
|
|
|
|
let amount = min(allocation.0[pool], utxo.amount);
|
|
|
|
selected.push(utxo.clone());
|
|
|
|
allocation.0[pool] -= amount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(selected)
|
|
|
|
}
|
|
|
|
|
|
|
|
struct OrderExecutor {
|
|
|
|
pub pool_available: PoolAllocation,
|
|
|
|
pub pool_used: PoolAllocation,
|
|
|
|
pub config: NoteSelectConfig,
|
|
|
|
pub fills: Vec<Fill>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OrderExecutor {
|
|
|
|
pub fn new(initial_pool: PoolAllocation, config: NoteSelectConfig) -> Self {
|
|
|
|
OrderExecutor {
|
|
|
|
pool_available: initial_pool,
|
|
|
|
pool_used: PoolAllocation::default(),
|
|
|
|
config,
|
|
|
|
fills: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
pub fn execute(&mut self, orders: &mut [Order], precedence: &PoolPrecedence) -> anyhow::Result<bool> {
|
|
|
|
let order_execution = execute_orders(orders, &self.pool_available,
|
|
|
|
self.config.use_transparent, self.config.use_shielded,
|
|
|
|
self.config.privacy_policy.clone(), precedence)?; // calculate an execution plan without considering the fee
|
2022-11-01 04:03:57 -07:00
|
|
|
self.fills.extend(order_execution.fills);
|
|
|
|
self.pool_available = self.pool_available - order_execution.allocation;
|
|
|
|
self.pool_used = self.pool_used + order_execution.allocation;
|
|
|
|
let fully_filled = orders.iter().all(|o| o.amount == o.filled);
|
|
|
|
Ok(fully_filled)
|
|
|
|
}
|
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
pub fn add(&mut self, fill: Fill) {
|
|
|
|
self.fills.push(fill);
|
|
|
|
}
|
|
|
|
|
2022-11-01 04:03:57 -07:00
|
|
|
pub fn select_notes(&self, utxos: &[UTXO]) -> anyhow::Result<Vec<UTXO>> {
|
|
|
|
select_notes(&self.pool_used, &utxos)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const ANY_DESTINATION: [Option<Destination>; 3] = [Some(Destination::Transparent([0u8; 20])), Some(Destination::Sapling([0u8; 43])), Some(Destination::Orchard([0u8; 43]))];
|
2022-11-02 06:54:24 -07:00
|
|
|
const MAX_ATTEMPTS: usize = 10;
|
2022-11-01 04:03:57 -07:00
|
|
|
/// Select notes from the `utxos` that can pay for the `orders`
|
|
|
|
///
|
|
|
|
pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Order], config: &NoteSelectConfig) -> anyhow::Result<TransactionPlan> {
|
|
|
|
let initial_pool = PoolAllocation::from(&*utxos); // amount of funds available in each pool
|
|
|
|
let mut fee = 0;
|
2022-11-02 06:54:24 -07:00
|
|
|
let mut n_attempts = 0;
|
|
|
|
let change_order = decode(u32::MAX, &config.change_address, 0, MemoBytes::empty())?;
|
|
|
|
let change_destinations = change_order.destinations;
|
2022-11-01 04:03:57 -07:00
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
let mut plan = loop {
|
2022-11-01 04:03:57 -07:00
|
|
|
for o in orders.iter_mut() {
|
|
|
|
o.filled = 0;
|
|
|
|
}
|
|
|
|
let mut executor = OrderExecutor::new(initial_pool, config.clone());
|
2022-11-02 06:54:24 -07:00
|
|
|
if !executor.execute(orders, &config.precedence)? {
|
2022-11-01 04:03:57 -07:00
|
|
|
anyhow::bail!("Unsufficient Funds")
|
|
|
|
}
|
2022-11-02 06:54:24 -07:00
|
|
|
if fee == 0 {
|
|
|
|
let notes = executor.select_notes(utxos)?;
|
|
|
|
fee = F::calculate_fee(¬es, &executor.fills);
|
2022-11-04 18:58:35 -07:00
|
|
|
log::debug!("base fee: {}", fee);
|
2022-11-02 06:54:24 -07:00
|
|
|
}
|
2022-11-05 02:29:16 -07:00
|
|
|
|
|
|
|
// Favor the pools that are already used, because it may cause lower fees
|
|
|
|
let pool_needed = executor.pool_used;
|
|
|
|
let prec_1 = config.precedence.iter().filter(|&&p| pool_needed.0[p as usize] != 0);
|
|
|
|
let prec_2 = config.precedence.iter().filter(|&&p| pool_needed.0[p as usize] == 0);
|
|
|
|
let fee_precedence: PoolPrecedence = prec_1.chain(prec_2).cloned().collect::<Vec<_>>().try_into().unwrap();
|
|
|
|
log::debug!("Fee precedence: {:?}", fee_precedence);
|
2022-11-02 06:54:24 -07:00
|
|
|
let mut fee_order = Order {
|
|
|
|
id: u32::MAX,
|
|
|
|
destinations: ANY_DESTINATION,
|
2022-11-05 05:42:55 -07:00
|
|
|
priority: PoolPriority::OS, // ignored for fees
|
2022-11-02 06:54:24 -07:00
|
|
|
amount: fee,
|
|
|
|
memo: MemoBytes::empty(),
|
2022-11-03 22:06:11 -07:00
|
|
|
is_fee: true, // do not include in fee calculation
|
2022-11-02 06:54:24 -07:00
|
|
|
filled: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !executor.execute(slice::from_mut(&mut fee_order), &fee_precedence)? {
|
|
|
|
anyhow::bail!("Unsufficient Funds [fees]")
|
2022-11-01 04:03:57 -07:00
|
|
|
}
|
2022-11-02 06:54:24 -07:00
|
|
|
|
|
|
|
// Let's figure out the change
|
2022-11-01 04:03:57 -07:00
|
|
|
let pool_needed = executor.pool_used;
|
|
|
|
let total_needed = pool_needed.total();
|
|
|
|
|
|
|
|
let notes = executor.select_notes(utxos)?;
|
|
|
|
let pool_spent = PoolAllocation::from(&*notes);
|
|
|
|
let total_spent = pool_spent.total();
|
2022-11-02 06:54:24 -07:00
|
|
|
let change = pool_spent - pool_needed; // must be >= 0 because the note selection covers the fills
|
|
|
|
|
2022-11-04 18:58:35 -07:00
|
|
|
log::debug!("pool_needed: {:?} {}", pool_needed, total_needed);
|
|
|
|
log::debug!("pool_spent: {:?} {}", pool_spent, total_spent);
|
|
|
|
log::debug!("change: {:?}", change);
|
2022-11-01 04:03:57 -07:00
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
for pool in 0..3 {
|
|
|
|
if change.0[pool] != 0 {
|
|
|
|
executor.add(Fill {
|
|
|
|
id_order: u32::MAX,
|
|
|
|
destination: change_destinations[pool].unwrap(),
|
|
|
|
amount: change.0[pool],
|
2022-11-04 18:58:35 -07:00
|
|
|
memo: MemoBytes::empty(),
|
2022-11-02 06:54:24 -07:00
|
|
|
is_fee: false
|
|
|
|
})
|
2022-11-01 04:03:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let notes = executor.select_notes(utxos)?;
|
|
|
|
let new_fee = F::calculate_fee(¬es, &executor.fills);
|
2022-11-04 18:58:35 -07:00
|
|
|
log::debug!("new fee: {}", new_fee);
|
2022-11-01 04:03:57 -07:00
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
if new_fee == fee || n_attempts == MAX_ATTEMPTS {
|
2022-11-01 04:03:57 -07:00
|
|
|
let plan = TransactionPlan {
|
|
|
|
spends: notes.clone(),
|
|
|
|
outputs: executor.fills.clone(),
|
2022-11-02 06:54:24 -07:00
|
|
|
fee: 0,
|
2022-11-01 04:03:57 -07:00
|
|
|
};
|
|
|
|
break plan;
|
|
|
|
}
|
2022-11-02 06:54:24 -07:00
|
|
|
fee = new_fee; // retry with the new fee
|
|
|
|
n_attempts += 1;
|
2022-11-01 04:03:57 -07:00
|
|
|
};
|
|
|
|
|
2022-11-02 06:54:24 -07:00
|
|
|
plan.fee = plan.outputs.iter().filter_map(|f| if f.is_fee { Some(f.amount) } else { None }).sum();
|
|
|
|
plan.outputs = plan.outputs.into_iter().filter(|f| !f.is_fee).collect();
|
|
|
|
|
2022-11-01 04:03:57 -07:00
|
|
|
Ok(plan)
|
|
|
|
}
|