WIP
This commit is contained in:
parent
c2093bbe12
commit
289f78fc9d
|
@ -1,5 +1,6 @@
|
|||
use std::cmp::max;
|
||||
use crate::note_selection::types::{Fill, UTXO};
|
||||
use zcash_primitives::memo::MemoBytes;
|
||||
use crate::note_selection::types::*;
|
||||
|
||||
const MARGINAL_FEE: u64 = 5000;
|
||||
const GRACE_ACTIONS: u64 = 2;
|
||||
|
@ -29,6 +30,8 @@ impl FeeCalculator for FeeZIP327 {
|
|||
let n_logical_actions = max(n_in[0], n_out[0]) +
|
||||
max(n_in[1], n_out[1]) +
|
||||
max(n_in[2], n_out[2]);
|
||||
|
||||
log::info!("fee: {}/{} {}/{} {}/{} = {}", n_in[0], n_out[0], n_in[1], n_out[1], n_in[2], n_out[2], n_logical_actions);
|
||||
let fee = MARGINAL_FEE * max(n_logical_actions, GRACE_ACTIONS);
|
||||
fee
|
||||
}
|
||||
|
@ -41,3 +44,4 @@ impl FeeCalculator for FeeFlat {
|
|||
1000
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::cmp::min;
|
|||
use zcash_address::{AddressKind, ZcashAddress};
|
||||
use zcash_address::unified::{Container, Receiver};
|
||||
use zcash_primitives::memo::{Memo, MemoBytes};
|
||||
use crate::note_selection::types::{PrivacyPolicy, NoteSelectConfig, Fill, Execution, Order, Pool, PoolAllocation, Destination};
|
||||
use crate::note_selection::types::{PrivacyPolicy, NoteSelectConfig, Fill, Execution, Order, Pool, PoolAllocation, Destination, PoolPrecedence};
|
||||
|
||||
/// Decode address and return it as an order
|
||||
///
|
||||
|
@ -48,54 +48,63 @@ pub fn decode(id: u32, address: &str, amount: u64, memo: MemoBytes) -> anyhow::R
|
|||
Ok(order)
|
||||
}
|
||||
|
||||
pub fn execute_orders(orders: &mut [Order], initial_pool: &PoolAllocation, config: &NoteSelectConfig) -> anyhow::Result<Execution> {
|
||||
let policy = config.privacy_policy.clone();
|
||||
pub fn execute_orders(orders: &mut [Order], initial_pool: &PoolAllocation, use_transparent: bool, use_shielded: bool,
|
||||
privacy_policy: PrivacyPolicy, precedence: &PoolPrecedence) -> anyhow::Result<Execution> {
|
||||
let mut allocation: PoolAllocation = PoolAllocation::default();
|
||||
let mut fills = vec![];
|
||||
|
||||
loop {
|
||||
// Direct Fill - t2t, s2s, o2o
|
||||
// Direct Shielded Fill - s2s, o2o
|
||||
if use_shielded {
|
||||
for order in orders.iter_mut() {
|
||||
for pool in config.precedence {
|
||||
if order.destinations[pool as usize].is_none() { continue }
|
||||
if !config.use_transparent && pool == Pool::Transparent { continue }
|
||||
fill_order(pool, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
for &pool in precedence {
|
||||
if pool == Pool::Transparent { continue }
|
||||
if order.destinations[pool as usize].is_some() {
|
||||
fill_order(pool, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy == PrivacyPolicy::SamePoolOnly { break }
|
||||
}
|
||||
|
||||
if privacy_policy != PrivacyPolicy::SamePoolOnly {
|
||||
// Indirect Shielded - z2z: s2o, o2s
|
||||
for order in orders.iter_mut() {
|
||||
for pool in config.precedence {
|
||||
for &pool in precedence {
|
||||
if order.destinations[pool as usize].is_none() { continue }
|
||||
if !use_shielded { continue }
|
||||
if let Some(from_pool) = pool.other_shielded() {
|
||||
fill_order(from_pool, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy == PrivacyPolicy::SamePoolTypeOnly { break }
|
||||
|
||||
// Other - s2t, o2t, t2s, t2o
|
||||
for order in orders.iter_mut() {
|
||||
for pool in config.precedence {
|
||||
if order.destinations[pool as usize].is_none() { continue }
|
||||
match pool {
|
||||
Pool::Transparent => {
|
||||
for from_pool in config.precedence {
|
||||
if from_pool.is_shielded() {
|
||||
fill_order(from_pool, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
if privacy_policy == PrivacyPolicy::AnyPool {
|
||||
// Other - s2t, o2t, t2s, t2o
|
||||
for order in orders.iter_mut() {
|
||||
for &pool in precedence {
|
||||
if order.destinations[pool as usize].is_none() { continue }
|
||||
match pool {
|
||||
Pool::Transparent if use_shielded => {
|
||||
for &from_pool in precedence {
|
||||
if from_pool != Pool::Transparent {
|
||||
fill_order(from_pool, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Pool::Sapling | Pool::Orchard => {
|
||||
if !config.use_transparent { continue }
|
||||
fill_order(Pool::Transparent, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
};
|
||||
Pool::Sapling | Pool::Orchard if use_transparent => {
|
||||
fill_order(Pool::Transparent, pool, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(policy, PrivacyPolicy::AnyPool);
|
||||
break;
|
||||
}
|
||||
|
||||
// t2t
|
||||
for order in orders.iter_mut() {
|
||||
if use_transparent && order.destinations[Pool::Transparent as usize].is_some() {
|
||||
fill_order(Pool::Transparent, Pool::Transparent, order, initial_pool, &mut allocation, &mut fills);
|
||||
}
|
||||
}
|
||||
|
||||
let execution = Execution {
|
||||
|
@ -136,14 +145,6 @@ impl Pool {
|
|||
Pool::Orchard => Some(Pool::Sapling),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_shielded(&self) -> bool {
|
||||
match self {
|
||||
Pool::Transparent => false,
|
||||
Pool::Sapling => true,
|
||||
Pool::Orchard => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -5,7 +5,7 @@ use zcash_primitives::memo::MemoBytes;
|
|||
use crate::note_selection::decode;
|
||||
use crate::note_selection::fee::FeeCalculator;
|
||||
use crate::note_selection::fill::execute_orders;
|
||||
use crate::note_selection::types::{NoteSelectConfig, Order, PoolAllocation, UTXO, Destination, TransactionPlan, Fill};
|
||||
use crate::note_selection::types::{NoteSelectConfig, Order, PoolAllocation, UTXO, Destination, TransactionPlan, Fill, PoolPrecedence};
|
||||
|
||||
pub fn select_notes(allocation: &PoolAllocation, utxos: &[UTXO]) -> anyhow::Result<Vec<UTXO>> {
|
||||
let mut allocation = allocation.clone();
|
||||
|
@ -43,8 +43,10 @@ impl OrderExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, orders: &mut [Order]) -> anyhow::Result<bool> {
|
||||
let order_execution = execute_orders(orders, &self.pool_available, &self.config)?; // calculate an execution plan without considering the fee
|
||||
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
|
||||
self.fills.extend(order_execution.fills);
|
||||
self.pool_available = self.pool_available - order_execution.allocation;
|
||||
self.pool_used = self.pool_used + order_execution.allocation;
|
||||
|
@ -52,52 +54,80 @@ impl OrderExecutor {
|
|||
Ok(fully_filled)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, fill: Fill) {
|
||||
self.fills.push(fill);
|
||||
}
|
||||
|
||||
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]))];
|
||||
|
||||
const MAX_ATTEMPTS: usize = 10;
|
||||
/// 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;
|
||||
let mut n_attempts = 0;
|
||||
let change_order = decode(u32::MAX, &config.change_address, 0, MemoBytes::empty())?;
|
||||
let change_destinations = change_order.destinations;
|
||||
|
||||
let plan = loop {
|
||||
let mut plan = loop {
|
||||
for o in orders.iter_mut() {
|
||||
o.filled = 0;
|
||||
}
|
||||
let mut executor = OrderExecutor::new(initial_pool, config.clone());
|
||||
if !executor.execute(orders)? {
|
||||
if !executor.execute(orders, &config.precedence)? {
|
||||
anyhow::bail!("Unsufficient Funds")
|
||||
}
|
||||
if fee > 0 {
|
||||
let mut fee_order = Order {
|
||||
id: u32::MAX,
|
||||
destinations: ANY_DESTINATION,
|
||||
amount: fee,
|
||||
memo: MemoBytes::empty(),
|
||||
no_fee: true, // do not include in fee calculation
|
||||
filled: 0,
|
||||
};
|
||||
if !executor.execute(slice::from_mut(&mut fee_order))? {
|
||||
anyhow::bail!("Unsufficient Funds")
|
||||
}
|
||||
if fee == 0 {
|
||||
let notes = executor.select_notes(utxos)?;
|
||||
fee = F::calculate_fee(¬es, &executor.fills);
|
||||
log::info!("base fee: {}", fee);
|
||||
}
|
||||
let mut fee_order = Order {
|
||||
id: u32::MAX,
|
||||
destinations: ANY_DESTINATION,
|
||||
amount: fee,
|
||||
memo: MemoBytes::empty(),
|
||||
no_fee: true, // do not include in fee calculation
|
||||
filled: 0,
|
||||
};
|
||||
|
||||
// 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::info!("Fee precedence: {:?}", fee_precedence);
|
||||
|
||||
if !executor.execute(slice::from_mut(&mut fee_order), &fee_precedence)? {
|
||||
anyhow::bail!("Unsufficient Funds [fees]")
|
||||
}
|
||||
|
||||
// Let's figure out the change
|
||||
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();
|
||||
let change = total_spent - total_needed; // must be >= 0 because the note selection covers the fills
|
||||
let change = pool_spent - pool_needed; // must be >= 0 because the note selection covers the fills
|
||||
|
||||
if change > 0 {
|
||||
let mut change_order = decode(u32::MAX, &config.change_address, change, MemoBytes::empty())?;
|
||||
if !executor.execute(slice::from_mut(&mut change_order))? {
|
||||
anyhow::bail!("Unsufficient Funds")
|
||||
log::info!("pool_needed: {:?} {}", pool_needed, total_needed);
|
||||
log::info!("pool_spent: {:?} {}", pool_spent, total_spent);
|
||||
log::info!("change: {:?}", change);
|
||||
|
||||
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],
|
||||
is_fee: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,15 +135,20 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
|
|||
let new_fee = F::calculate_fee(¬es, &executor.fills);
|
||||
log::info!("new fee: {}", new_fee);
|
||||
|
||||
if new_fee <= fee {
|
||||
if new_fee == fee || n_attempts == MAX_ATTEMPTS {
|
||||
let plan = TransactionPlan {
|
||||
spends: notes.clone(),
|
||||
outputs: executor.fills.clone(),
|
||||
fee: 0,
|
||||
};
|
||||
break plan;
|
||||
}
|
||||
fee = new_fee; // retry with the higher fee
|
||||
fee = new_fee; // retry with the new fee
|
||||
n_attempts += 1;
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use serde_json::Value;
|
||||
use zcash_primitives::memo::Memo;
|
||||
use crate::{CoinConfig, init_coin, set_coin_lwd_url};
|
||||
use crate::api::payment::RecipientMemo;
|
||||
use crate::unified::UnifiedAddressType;
|
||||
use super::{*, types::*, fill::*};
|
||||
|
||||
// has T+S receivers
|
||||
const CHANGE_ADDRESS: &str = "u1utw7uds5fr5ephnrm8u57ytwgpcktdmvv3q0kmfapnhyy4g7n7wqce6d8xfqeewgd9td9a57qera92apjtljs543j5yl2kks0rpaf3y4hvsmpep5ajvd2s4ggc9dxjtek8s4x92j6p9";
|
||||
// must have T+S+O receivers
|
||||
const CHANGE_ADDRESS: &str = "u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e";
|
||||
|
||||
fn init() {
|
||||
env_logger::init();
|
||||
|
@ -23,125 +24,6 @@ async fn test_fetch_utxo() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fill1() {
|
||||
let mut config = NoteSelectConfig {
|
||||
privacy_policy: PrivacyPolicy::AnyPool,
|
||||
use_transparent: true,
|
||||
precedence: [Pool::Transparent, Pool::Sapling, Pool::Orchard],
|
||||
change_address: CHANGE_ADDRESS.to_string(),
|
||||
};
|
||||
|
||||
// T2T
|
||||
let selection = execute_orders(
|
||||
&mut vec![
|
||||
mock_order(1, 100, 1), // taddr
|
||||
],
|
||||
&PoolAllocation([100, 0, 0]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[0], 100);
|
||||
|
||||
config.use_transparent = false; // disable transparent inputs
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 1), // taddr
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([100, 0, 0]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[0], 0);
|
||||
assert_eq!(orders[0].filled, 0); // no fill
|
||||
|
||||
// add sapling inputs: S2T
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 1), // taddr
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([100, 80, 0]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[1], 80);
|
||||
assert_eq!(orders[0].filled, 80); // partial fill
|
||||
|
||||
// add orchard inputs: S2T + O2T
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 1), // taddr
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([100, 80, 40]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[1], 80);
|
||||
assert_eq!(selection.allocation.0[2], 20);
|
||||
assert_eq!(orders[0].filled, 100); // fill
|
||||
|
||||
// Orchard pool preference: O2T + S2T
|
||||
config.precedence = [Pool::Transparent, Pool::Orchard, Pool::Sapling];
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 1), // taddr
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([100, 80, 40]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[1], 60);
|
||||
assert_eq!(selection.allocation.0[2], 40);
|
||||
assert_eq!(orders[0].filled, 100); // fill
|
||||
|
||||
// UA T+S: T2T + S2S + O2S
|
||||
config.use_transparent = true;
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 3), // ua: t+s
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([10, 80, 40]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[0], 10);
|
||||
assert_eq!(selection.allocation.0[1], 80);
|
||||
assert_eq!(selection.allocation.0[2], 10);
|
||||
assert_eq!(orders[0].filled, 100); // fill
|
||||
|
||||
// UA T+S, UA S+O: T2T + S2S + O2O - pool is empty
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 3), // ua: t+s
|
||||
mock_order(2, 100, 6), // ua: s+o
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([10, 80, 40]),
|
||||
&config,
|
||||
).unwrap();
|
||||
assert_eq!(selection.allocation.0[0], 10);
|
||||
assert_eq!(selection.allocation.0[1], 80);
|
||||
assert_eq!(selection.allocation.0[2], 40);
|
||||
assert_eq!(orders[0].filled, 90); // partial fill
|
||||
assert_eq!(orders[1].filled, 40); // partial fill
|
||||
|
||||
// Same as previously with more O inputs
|
||||
let mut orders = vec![
|
||||
mock_order(1, 100, 3), // ua: t+s
|
||||
mock_order(2, 100, 6), // ua: s+o
|
||||
];
|
||||
let selection = execute_orders(
|
||||
&mut orders,
|
||||
&PoolAllocation([10, 80, 120]),
|
||||
&config,
|
||||
).unwrap();
|
||||
println!("{:?}", selection);
|
||||
assert_eq!(selection.allocation.0[0], 10);
|
||||
assert_eq!(selection.allocation.0[1], 80);
|
||||
assert_eq!(selection.allocation.0[2], 110);
|
||||
assert_eq!(orders[0].filled, 100); // fill
|
||||
assert_eq!(orders[1].filled, 100); // fill
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ua() {
|
||||
init();
|
||||
|
@ -155,12 +37,7 @@ fn test_ua() {
|
|||
#[tokio::test]
|
||||
async fn test_payment() {
|
||||
init();
|
||||
let config = NoteSelectConfig {
|
||||
privacy_policy: PrivacyPolicy::SamePoolTypeOnly, // Minimum privacy level required for this tx, because the change has to go from orchard to sapling
|
||||
use_transparent: true,
|
||||
precedence: [Pool::Transparent, Pool::Sapling, Pool::Orchard],
|
||||
change_address: CHANGE_ADDRESS.to_string(), // does not have orchard receiver
|
||||
};
|
||||
let config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
|
||||
let recipients = vec![
|
||||
RecipientMemo {
|
||||
|
@ -211,3 +88,249 @@ fn mock_order(id: u32, amount: u64, tpe: u8) -> Order {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! order {
|
||||
($id:expr, $q:expr, $destinations:expr) => {
|
||||
Order {
|
||||
id: $id,
|
||||
amount: $q * 1000,
|
||||
destinations: $destinations,
|
||||
filled: 0,
|
||||
no_fee: false,
|
||||
memo: MemoBytes::empty(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! utxo {
|
||||
($id:expr, $q:expr) => {
|
||||
UTXO {
|
||||
amount: $q * 1000,
|
||||
source: Source::Transparent { txid: [0u8; 32], index: $id },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! sapling {
|
||||
($id:expr, $q:expr) => {
|
||||
UTXO {
|
||||
amount: $q * 1000,
|
||||
source: Source::Sapling {
|
||||
id_note: $id,
|
||||
diversifier: [0u8; 11],
|
||||
rseed: [0u8; 32],
|
||||
witness: vec![],
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! orchard {
|
||||
($id:expr, $q:expr) => {
|
||||
UTXO {
|
||||
amount: $q * 1000,
|
||||
source: Source::Orchard {
|
||||
id_note: $id,
|
||||
diversifier: [0u8; 11],
|
||||
rseed: [0u8; 32],
|
||||
rho: [0u8; 32],
|
||||
witness: vec![],
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! t {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [Some(Destination::Transparent([0u8; 20])), None, None])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! s {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [None, Some(Destination::Sapling([0u8; 43])), None])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! o {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [None, None, Some(Destination::Orchard([0u8; 43]))])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ts {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [Some(Destination::Transparent([0u8; 20])), Some(Destination::Sapling([0u8; 43])), None])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! to {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [Some(Destination::Transparent([0u8; 20])), None, Some(Destination::Orchard([0u8; 43]))])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! so {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [None, Some(Destination::Sapling([0u8; 43])), Some(Destination::Orchard([0u8; 43]))])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! tso {
|
||||
($id: expr, $q:expr) => {
|
||||
order!($id, $q, [Some(Destination::Transparent([0u8; 20])), Some(Destination::Sapling([0u8; 43])), Some(Destination::Orchard([0u8; 43]))])
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example1() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.use_transparent = true;
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
|
||||
let utxos = [utxo!(1, 5), utxo!(2, 7), sapling!(3, 12), orchard!(4, 10)];
|
||||
let mut orders = [t!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example2() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
|
||||
let utxos = [utxo!(1, 5), utxo!(2, 7), sapling!(3, 12), orchard!(4, 10), orchard!(5, 10)];
|
||||
let mut orders = [t!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example3() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.use_transparent = true;
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
config.precedence = [ Pool::Sapling, Pool::Orchard, Pool::Transparent ];
|
||||
|
||||
let utxos = [utxo!(1, 100), sapling!(2, 160), orchard!(3, 70), orchard!(4, 50)];
|
||||
let mut orders = [t!(1, 10), s!(2, 20), o!(3, 30), ts!(4, 40), to!(5, 50), so!(6, 60), tso!(7, 70)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
|
||||
let tx_plan_json = serde_json::to_value(&tx_plan).unwrap();
|
||||
let expected: Value = serde_json::from_str(r#"{"spends":[{"source":{"Transparent":{"txid":"0000000000000000000000000000000000000000000000000000000000000000","index":1}},"amount":100000},{"source":{"Sapling":{"id_note":2,"diversifier":"0000000000000000000000","rseed":"0000000000000000000000000000000000000000000000000000000000000000","witness":""}},"amount":160000},{"source":{"Orchard":{"id_note":3,"diversifier":"0000000000000000000000","rseed":"0000000000000000000000000000000000000000000000000000000000000000","rho":"0000000000000000000000000000000000000000000000000000000000000000","witness":""}},"amount":70000},{"source":{"Orchard":{"id_note":4,"diversifier":"0000000000000000000000","rseed":"0000000000000000000000000000000000000000000000000000000000000000","rho":"0000000000000000000000000000000000000000000000000000000000000000","witness":""}},"amount":50000}],"outputs":[{"id_order":1,"destination":{"Transparent":"0000000000000000000000000000000000000000"},"amount":10000},{"id_order":2,"destination":{"Sapling":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":20000},{"id_order":3,"destination":{"Orchard":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":30000},{"id_order":4,"destination":{"Sapling":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":40000},{"id_order":5,"destination":{"Orchard":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":50000},{"id_order":6,"destination":{"Sapling":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":60000},{"id_order":7,"destination":{"Sapling":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":40000},{"id_order":7,"destination":{"Orchard":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"amount":30000},{"id_order":4294967295,"destination":{"Transparent":"c7b7b3d299bd173ea278d792b1bd5fbdd11afe34"},"amount":55000}],"fee":45000}"#).unwrap();
|
||||
assert_eq!(tx_plan_json, expected);
|
||||
}
|
||||
|
||||
/// A simple t2t
|
||||
///
|
||||
#[test]
|
||||
fn test_example4() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.use_transparent = true;
|
||||
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
let mut orders = [t!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
/// A simple z2z
|
||||
///
|
||||
#[test]
|
||||
fn test_example5() {
|
||||
env_logger::init();
|
||||
let config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
|
||||
// z2z are preferred over t2z, so we can keep the t-notes
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
let mut orders = [s!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
/// A simple z2z
|
||||
///
|
||||
#[test]
|
||||
fn test_example5b() {
|
||||
env_logger::init();
|
||||
let config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
|
||||
// z2z are preferred over t2z, so we can keep the t-notes
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
let mut orders = [o!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
/// A simple z2t sapling
|
||||
///
|
||||
#[test]
|
||||
fn test_example6() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
// Change the destination to t
|
||||
let mut orders = [t!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
/// A simple o2t
|
||||
///
|
||||
#[test]
|
||||
fn test_example7() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.precedence = [ Pool::Orchard, Pool::Sapling, Pool::Transparent ];
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
// Change the destination to t
|
||||
let mut orders = [t!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
/// A simple t2z
|
||||
///
|
||||
#[test]
|
||||
fn test_example8() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
config.privacy_policy = PrivacyPolicy::AnyPool;
|
||||
config.use_transparent = true;
|
||||
config.use_shielded = false;
|
||||
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50), orchard!(3, 50)];
|
||||
let mut orders = [s!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
||||
/// A simple z2z (Sapling/Orchard)
|
||||
///
|
||||
#[test]
|
||||
fn test_example9() {
|
||||
env_logger::init();
|
||||
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
|
||||
|
||||
let utxos = [utxo!(1, 50), sapling!(2, 50)];
|
||||
let mut orders = [o!(1, 10)];
|
||||
|
||||
let tx_plan = note_select_with_fee::<FeeZIP327>(&utxos, &mut orders, &config).unwrap();
|
||||
println!("{}", serde_json::to_string(&tx_plan).unwrap());
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ pub struct Fill {
|
|||
pub id_order: u32,
|
||||
pub destination: Destination,
|
||||
pub amount: u64,
|
||||
#[serde(skip)]
|
||||
pub is_fee: bool,
|
||||
}
|
||||
|
||||
|
@ -110,6 +111,7 @@ pub struct Execution {
|
|||
pub struct TransactionPlan {
|
||||
pub spends: Vec<UTXO>,
|
||||
pub outputs: Vec<Fill>,
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
@ -168,10 +170,23 @@ impl Sub for PoolAllocation {
|
|||
pub struct NoteSelectConfig {
|
||||
pub privacy_policy: PrivacyPolicy,
|
||||
pub use_transparent: bool,
|
||||
pub use_shielded: bool,
|
||||
pub precedence: PoolPrecedence,
|
||||
pub change_address: String,
|
||||
}
|
||||
|
||||
impl NoteSelectConfig {
|
||||
pub fn new(change_address: &str) -> Self {
|
||||
NoteSelectConfig {
|
||||
privacy_policy: PrivacyPolicy::SamePoolTypeOnly,
|
||||
use_transparent: false,
|
||||
use_shielded: true,
|
||||
precedence: [ Pool::Transparent, Pool::Sapling, Pool::Orchard ], // We prefer to keep our orchard notes
|
||||
change_address: change_address.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Source {
|
||||
pub fn pool(&self) -> Pool {
|
||||
match self {
|
||||
|
|
Loading…
Reference in New Issue