This commit is contained in:
Hanh 2022-11-02 21:54:24 +08:00
parent c2093bbe12
commit 289f78fc9d
5 changed files with 368 additions and 190 deletions

View File

@ -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
}
}

View File

@ -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)]

View File

@ -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(&notes, &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(&notes, &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)
}

View File

@ -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());
}

View File

@ -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 {