From 3634a6bcdd398fbf48c0783639133a035435f0de Mon Sep 17 00:00:00 2001 From: lucretius Date: Tue, 23 Jan 2024 16:59:54 +0100 Subject: [PATCH] add custom fee --- Cargo.lock | 18 ++++--- lib/Cargo.toml | 12 ++--- lib/src/commands.rs | 107 +++++++++++++++++++++++------------------ lib/src/lightclient.rs | 5 +- lib/src/lightwallet.rs | 52 ++++++++++++-------- 5 files changed, 113 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1317c15..fab19ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler32" version = "1.0.4" @@ -186,7 +188,7 @@ checksum = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" [[package]] name = "bellman" version = "0.1.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "bit-vec", "blake2s_simd", @@ -510,7 +512,7 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "ff" version = "0.4.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "byteorder", "ff_derive", @@ -520,7 +522,7 @@ dependencies = [ [[package]] name = "ff_derive" version = "0.3.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "num-bigint", "num-integer", @@ -678,7 +680,7 @@ dependencies = [ [[package]] name = "group" version = "0.1.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "ff", "rand 0.7.2", @@ -1158,7 +1160,7 @@ dependencies = [ [[package]] name = "pairing" version = "0.14.2" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "byteorder", "ff", @@ -2830,7 +2832,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.0.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "bech32", "bs58", @@ -2846,7 +2848,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.0.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "aes", "blake2b_simd", @@ -2869,7 +2871,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.0.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=1a0204113d487cdaaf183c2967010e5214ff9e37#1a0204113d487cdaaf183c2967010e5214ff9e37" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" dependencies = [ "bellman", "blake2b_simd", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 95388cc..924d436 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -43,34 +43,34 @@ webpki-roots = "0.18.0" [dependencies.bellman] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" default-features = false features = ["groth16"] [dependencies.pairing] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" [dependencies.zcash_client_backend] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" default-features = false [dependencies.zcash_primitives] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" default-features = false features = ["transparent-inputs"] [dependencies.zcash_proofs] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" default-features = false [dependencies.ff] git = "https://git.hush.is/hush/librustzcash.git" -rev= "1a0204113d487cdaaf183c2967010e5214ff9e37" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" features = ["ff_derive"] [build-dependencies] diff --git a/lib/src/commands.rs b/lib/src/commands.rs index b4835ad..4f2b12a 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -5,6 +5,8 @@ use json::{object}; use crate::lightclient::LightClient; use crate::lightwallet::LightWallet; +use zcash_primitives::transaction::components::amount::DEFAULT_FEE; +use std::convert::TryInto; pub trait Command { fn help(&self) -> String; @@ -489,69 +491,82 @@ impl Command for SendCommand { // Parse the args. There are two argument types. // 1 - A set of 2(+1 optional) arguments for a single address send representing address, value, memo? // 2 - A single argument in the form of a JSON string that is "[{address: address, value: value, memo: memo},...]" - // 1 - Destination address. T or Z address if args.len() < 1 || args.len() > 3 { return self.help(); } - // Check for a single argument that can be parsed as JSON let send_args = if args.len() == 1 { let arg_list = args[0]; - - let json_args = match json::parse(&arg_list) { - Ok(j) => j, - Err(e) => { - let es = format!("Couldn't understand JSON: {}", e); - return format!("{}\n{}", es, self.help()); - } - }; - - if !json_args.is_array() { - return format!("Couldn't parse argument as array\n{}", self.help()); + match json::parse(&arg_list) { + Ok(json_args) => { + // Check if the parsed JSON is an array. + if !json_args.is_array() { + return format!("Couldn't parse argument as array\n{}", self.help()); + } + + // Map each JSON object to a tuple (address, amount, memo, fee). + let maybe_send_args = json_args.members().map(|j| { + if !j.has_key("address") || !j.has_key("amount") { + Err(format!("Need 'address' and 'amount'\n")) + } else { + let fee = j["fee"].as_u64().unwrap_or(DEFAULT_FEE.try_into().unwrap()); + Ok(( + j["address"].as_str().unwrap().to_string(), + j["amount"].as_u64().unwrap(), + j["memo"].as_str().map(|s| s.to_string()), + fee + )) + } + }).collect::, u64)>, String>>(); + + // Handle any errors that occurred during mapping. + match maybe_send_args { + Ok(a) => a, + Err(s) => return format!("Error: {}\n{}", s, self.help()), + } + }, + Err(e) => return format!("Couldn't understand JSON: {}\n{}", e, self.help()), } - - let maybe_send_args = json_args.members().map( |j| { - if !j.has_key("address") || !j.has_key("amount") { - Err(format!("Need 'address' and 'amount'\n")) - } else { - Ok((j["address"].as_str().unwrap().to_string().clone(), j["amount"].as_u64().unwrap(), j["memo"].as_str().map(|s| s.to_string().clone()))) - } - }).collect::)>, String>>(); - - match maybe_send_args { - Ok(a) => a.clone(), - Err(s) => { return format!("Error: {}\n{}", s, self.help()); } - } - } else if args.len() == 2 || args.len() == 3 { + } else { + // Handle the case where individual arguments are provided. let address = args[0].to_string(); - - // Make sure we can parse the amount let value = match args[1].parse::() { Ok(amt) => amt, - Err(e) => { - return format!("Couldn't parse amount: {}", e); - } + Err(e) => return format!("Couldn't parse amount: {}", e), }; - - let memo = if args.len() == 3 { Some(args[2].to_string()) } else { None }; - - // Memo has to be None if not sending to a shileded address + let memo = args.get(2).map(|m| m.to_string()); + + // Memo should be None if the address is not shielded. if memo.is_some() && !LightWallet::is_shielded_address(&address, &lightclient.config) { return format!("Can't send a memo to the non-shielded address {}", address); } - - vec![(args[0].to_string(), value, memo)] - } else { - return self.help() + + // Create a vector with a single transaction (address, amount, memo). + vec![(address, value, memo, DEFAULT_FEE.try_into().unwrap())] }; + + // Transform transaction data into the required format (String -> &str). + let tos = send_args.iter().map(|(a, v, m, _)| (a.as_str(), *v, m.clone())).collect::>(); + + // Calculate the total fee for all transactions. + // This assumes that all transactions have the same fee. + // If they can have different fees, you need to modify this logic. + + let default_fee: u64 = DEFAULT_FEE.try_into().unwrap(); + let mut total_fee = default_fee; - // Convert to the right format. String -> &str. - let tos = send_args.iter().map(|(a, v, m)| (a.as_str(), *v, m.clone()) ).collect::>(); - match lightclient.do_send(tos) { - Ok(txid) => { object!{ "txid" => txid } }, - Err(e) => { object!{ "error" => e } } - }.pretty(2) + for (_, _, _, fee) in send_args.iter() { + if *fee != default_fee{ + total_fee = *fee; + break; + } +} + // Execute the transaction and handle the result. + match lightclient.do_send(tos, &total_fee) { + Ok(txid) => object!{ "txid" => txid }.pretty(2), + Err(e) => object!{ "error" => e }.pretty(2), + } } } diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index e698c95..b7c9652 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -1252,6 +1252,7 @@ pub fn start_mempool_monitor(lc: Arc) -> Result<(), String> { } let addr = address.or(self.wallet.read().unwrap().get_all_zaddresses().get(0).map(|s| s.clone())).unwrap(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let result = { let _lock = self.sync_lock.lock().unwrap(); @@ -1260,6 +1261,7 @@ pub fn start_mempool_monitor(lc: Arc) -> Result<(), String> { &self.sapling_spend, &self.sapling_output, true, vec![(&addr, tbal - fee, None)], + &fee, |txbytes| broadcast_raw_tx(&self.get_server_uri(),self.config.no_cert_verification, txbytes) ) }; @@ -1600,7 +1602,7 @@ pub fn start_mempool_monitor(lc: Arc) -> Result<(), String> { crx.iter().take(num_fetches).collect::, String>>() } - pub fn do_send(&self, addrs: Vec<(&str, u64, Option)>) -> Result { + pub fn do_send(&self, addrs: Vec<(&str, u64, Option)>, fee: &u64) -> Result { if !self.wallet.read().unwrap().is_unlocked_for_spending() { error!("Wallet is locked"); return Err("Wallet is locked".to_string()); @@ -1616,6 +1618,7 @@ pub fn start_mempool_monitor(lc: Arc) -> Result<(), String> { &self.sapling_spend, &self.sapling_output, false, addrs, + fee, |txbytes| broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes) ) }; diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 76d7f17..1a2b888 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -39,7 +39,7 @@ use zcash_primitives::{ serialize::{Vector}, transaction::{ builder::{Builder}, - components::{Amount, OutPoint, TxOut}, components::amount::DEFAULT_FEE, + components::{Amount, OutPoint, TxOut}, TxId, Transaction, }, sapling::Node, @@ -2194,6 +2194,7 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64 output_params: &[u8], _transparent_only: bool, tos: Vec<(&str, u64, Option)>, + fee: &u64, broadcast_fn: F ) -> Result<(String, Vec), String> where F: Fn(Box<[u8]>) -> Result @@ -2224,24 +2225,31 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64 total_value, tos.len() ); - // Convert address (str) to RecepientAddress and value to Amount - let recepients = tos.iter().map(|to| { - let ra = match address::RecipientAddress::from_str(to.0, - self.config.hrp_sapling_address(), - self.config.base58_pubkey_address(), - self.config.base58_script_address()) { - Some(to) => to, - None => { - let e = format!("Invalid recipient address: '{}'", to.0); - error!("{}", e); - return Err(e); - } - }; + // Convert address (str) to RecipientAddress and value to Amount - let value = Amount::from_u64(to.1).unwrap(); + let recepients: Result)>, String> = tos.iter().map(|to| { + // Convert string to RecipientAddress + let ra = match address::RecipientAddress::from_str( + to.0, + self.config.hrp_sapling_address(), + self.config.base58_pubkey_address(), + self.config.base58_script_address() + ) { + Some(addr) => addr, + None => { + let e = format!("Invalid recipient address: '{}'", to.0); + error!("{}", e); + return Err(e); + } + }; - Ok((ra, value, to.2.clone())) - }).collect::)>, String>>()?; + // Convert the second tuple element to Amount + let value = Amount::from_u64(to.1).expect("Invalid amount value"); + + Ok((ra, value, to.2.clone())) + }).collect(); + + let recepients = recepients?; // Target the next block, assuming we are up-to-date. let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() { @@ -2254,8 +2262,9 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64 }; // Select notes to cover the target value - println!("{}: Selecting notes", now() - start_time); - let target_value = Amount::from_u64(total_value).unwrap() + DEFAULT_FEE ; + // Select notes to cover the target value + println!("{}: Selecting notes", now() - start_time); + let target_value = Amount::from_u64(total_value).unwrap() + Amount::from_u64(*fee).unwrap(); // Select the candidate notes that are eligible to be spent let notes: Vec<_> = self.txs.read().unwrap().iter() .map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note))) @@ -2340,8 +2349,11 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64 return Err(e); } + let fee_amount = Amount::from_u64(*fee).expect("Invalid fee amount"); + builder.set_fee(fee_amount); + // Create the transaction - println!("{}: Adding {} notes and {} utxos", now() - start_time, notes.len(), tinputs.len()); + println!("{}: Adding {} notes and {} utxos and fee {:?}", now() - start_time, notes.len(), tinputs.len(), fee_amount); for selected in notes.iter() { if let Err(e) = builder.add_sapling_spend(