diff --git a/.gitignore b/.gitignore index ea8c4bf..d9ce906 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/zec_sqlite_wallet* diff --git a/Cargo.lock b/Cargo.lock index 345575d..66b3e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,8 +529,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equihash" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab579d7cf78477773b03e80bc2f89702ef02d7112c711d54ca93dcdce68533d5" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "blake2b_simd", "byteorder", @@ -555,8 +554,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "blake2b_simd", ] @@ -1842,9 +1840,9 @@ dependencies = [ [[package]] name = "shardtree" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19f96dde3a8693874f7e7c53d95616569b4009379a903789efbd448f4ea9cc7" +checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735" dependencies = [ "bitflags 2.4.1", "either", @@ -2611,8 +2609,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zcash_address" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8944af5c206cf2e37020ad54618e1825501b98548d35a638b73e0ec5762df8d5" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "bech32", "bs58", @@ -2623,8 +2620,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a382af39be9ee5a3788157145c404b7cd19acc440903f6c34b09fb44f0e991" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "base64", "bech32", @@ -2637,6 +2633,7 @@ dependencies = [ "incrementalmerkletree", "memuse", "nom", + "nonempty", "orchard", "percent-encoding", "prost", @@ -2658,8 +2655,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e9603969437fa41e469f0c0e646e9604f04403304380c00433d8df91c046d2" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "bs58", "byteorder", @@ -2684,8 +2680,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03391b81727875efa6ac0661a20883022b6fba92365dc121c48fa9b00c5aac0" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "byteorder", "nonempty", @@ -2707,10 +2702,10 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17e4c94ca8d69d2fcf2be97522da5732a580eb2125cda3b150761952f8df8e6" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "aes", + "bellman", "bip0039", "bitvec", "blake2b_simd", @@ -2730,8 +2725,10 @@ dependencies = [ "orchard", "rand", "rand_core", + "redjubjub", "sha2", "subtle", + "tracing", "zcash_address", "zcash_encoding", "zcash_note_encryption", @@ -2740,8 +2737,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0c99f65a840ff256c106b28d67d702d9759d206112473d4982c92003262406" +source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" dependencies = [ "bellman", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index 5ca1181..5716b37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,9 @@ zcash_client_backend = { version = "0.10", features = ["lightwalletd-tonic"] } zcash_client_sqlite = { version = "0.8", features = ["unstable"] } zcash_primitives = "0.13" zcash_proofs = "0.13" + +[patch.crates-io] +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } +zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } +zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } diff --git a/src/commands.rs b/src/commands.rs index a39af4a..d203798 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,6 +2,7 @@ pub(crate) mod balance; pub(crate) mod init; pub(crate) mod list_tx; pub(crate) mod list_unspent; +pub(crate) mod propose; pub(crate) mod reset; pub(crate) mod send; pub(crate) mod sync; diff --git a/src/commands/list_unspent.rs b/src/commands/list_unspent.rs index 44c0fc3..bf57053 100644 --- a/src/commands/list_unspent.rs +++ b/src/commands/list_unspent.rs @@ -1,11 +1,14 @@ use anyhow::anyhow; use gumdrop::Options; -use zcash_client_backend::data_api::WalletRead; +use zcash_client_backend::data_api::{SaplingInputSource, WalletRead}; use zcash_client_sqlite::WalletDb; use zcash_primitives::{ consensus::Parameters, - transaction::components::amount::{Amount, MAX_MONEY}, + transaction::components::{ + amount::{Amount, MAX_MONEY}, + sapling::fees::InputView, + }, zip32::AccountId, }; @@ -41,7 +44,7 @@ impl Command { )?; for note in notes { - println!("{}: {}", note.note_id, format_zec(note.note_value)); + println!("{}: {}", note.note_id(), format_zec(note.value())); } Ok(()) diff --git a/src/commands/propose.rs b/src/commands/propose.rs new file mode 100644 index 0000000..7c99dec --- /dev/null +++ b/src/commands/propose.rs @@ -0,0 +1,110 @@ +use gumdrop::Options; + +use zcash_client_backend::{ + address::RecipientAddress, + data_api::wallet::{input_selection::GreedyInputSelector, propose_transfer}, + fees::standard::SingleOutputChangeStrategy, + zip321::{Payment, TransactionRequest}, +}; +use zcash_client_sqlite::WalletDb; +use zcash_primitives::{ + consensus::Parameters, + transaction::{components::amount::NonNegativeAmount, fees::StandardFeeRule}, + zip32::AccountId, +}; + +use crate::{data::get_db_paths, error, MIN_CONFIRMATIONS}; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum FeeRule { + Fixed, + Zip317, +} + +impl Default for FeeRule { + fn default() -> Self { + FeeRule::Zip317 + } +} + +#[allow(deprecated)] +impl From for StandardFeeRule { + fn from(rule: FeeRule) -> Self { + match rule { + FeeRule::Fixed => StandardFeeRule::PreZip313, + FeeRule::Zip317 => StandardFeeRule::Zip317, + } + } +} + +pub(crate) fn parse_fee_rule(name: &str) -> Result { + match name { + "fixed" => Ok(FeeRule::Fixed), + "zip317" => Ok(FeeRule::Zip317), + other => Err(format!("Fee rule {} not recognized.", other)), + } +} + +// Options accepted for the `propose` command +#[derive(Debug, Options)] +pub(crate) struct Command { + #[options( + required, + help = "the recipient's Unified, Sapling or transparent address" + )] + address: String, + + #[options(required, help = "the amount in zatoshis")] + value: u64, + + #[options( + required, + help = "fee strategy: \"fixed\" or \"zip317\"", + parse(try_from_str = "parse_fee_rule") + )] + fee_rule: FeeRule, +} + +impl Command { + pub(crate) async fn run( + self, + params: impl Parameters + Copy + 'static, + wallet_dir: Option, + ) -> Result<(), anyhow::Error> { + let account = AccountId::from(0); + let (_, db_data) = get_db_paths(wallet_dir.as_ref()); + let mut db_data = WalletDb::for_path(db_data, params)?; + + let input_selector = GreedyInputSelector::new( + SingleOutputChangeStrategy::new(self.fee_rule.into(), None), + Default::default(), + ); + + let request = TransactionRequest::new(vec![Payment { + recipient_address: RecipientAddress::decode(¶ms, &self.address) + .ok_or(error::Error::InvalidRecipient)?, + amount: NonNegativeAmount::from_u64(self.value) + .map_err(|_| error::Error::InvalidAmount)?, + memo: None, + label: None, + message: None, + other_params: vec![], + }]) + .map_err(error::Error::from)?; + + let proposal = propose_transfer( + &mut db_data, + ¶ms, + account, + &input_selector, + request, + MIN_CONFIRMATIONS, + ) + .map_err(error::Error::from)?; + + // Display the proposal + println!("Proposal: {:#?}", proposal); + + Ok(()) + } +} diff --git a/src/commands/send.rs b/src/commands/send.rs index 73ae0e7..4d2190e 100644 --- a/src/commands/send.rs +++ b/src/commands/send.rs @@ -7,7 +7,7 @@ use zcash_client_backend::{ wallet::{input_selection::GreedyInputSelector, spend}, WalletRead, }, - fees::zip317::SingleOutputChangeStrategy, + fees::standard::SingleOutputChangeStrategy, keys::UnifiedSpendingKey, proto::service, wallet::OvkPolicy, @@ -15,13 +15,12 @@ use zcash_client_backend::{ }; use zcash_client_sqlite::WalletDb; use zcash_primitives::{ - consensus::Parameters, - transaction::{components::Amount, fees::zip317::FeeRule}, - zip32::AccountId, + consensus::Parameters, transaction::components::amount::NonNegativeAmount, zip32::AccountId, }; use zcash_proofs::prover::LocalTxProver; use crate::{ + commands::propose::{parse_fee_rule, FeeRule}, data::{get_db_paths, get_wallet_seed}, error, remote::connect_to_lightwalletd, @@ -36,6 +35,13 @@ pub(crate) struct Command { #[options(required, help = "the amount in zatoshis")] value: u64, + + #[options( + required, + help = "fee strategy: \"fixed\" or \"zip317\"", + parse(try_from_str = "parse_fee_rule") + )] + fee_rule: FeeRule, } impl Command { @@ -59,14 +65,15 @@ impl Command { let prover = LocalTxProver::with_default_location().ok_or(error::Error::MissingParameters)?; let input_selector = GreedyInputSelector::new( - SingleOutputChangeStrategy::new(FeeRule::standard()), + SingleOutputChangeStrategy::new(self.fee_rule.into(), None), Default::default(), ); let request = TransactionRequest::new(vec![Payment { recipient_address: RecipientAddress::decode(¶ms, &self.address) .ok_or(error::Error::InvalidRecipient)?, - amount: Amount::from_u64(self.value).map_err(|_| error::Error::InvalidAmount)?, + amount: NonNegativeAmount::from_u64(self.value) + .map_err(|_| error::Error::InvalidAmount)?, memo: None, label: None, message: None, @@ -77,7 +84,8 @@ impl Command { let id_tx = spend( &mut db_data, ¶ms, - prover, + &prover, + &prover, &input_selector, &usk, request, diff --git a/src/error.rs b/src/error.rs index 1619b42..49812d4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,7 +18,6 @@ pub(crate) type WalletErrorT = WalletError< commitment_tree::Error, GreedyInputSelectorError, FeeError, - ReceivedNoteId, >; #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 216b31a..1ada5ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,9 @@ enum Command { #[options(help = "list the unspent notes in the wallet")] ListUnspent(commands::list_unspent::Command), + #[options(help = "propose a transfer of funds to the given address and display the proposal")] + Propose(commands::propose::Command), + #[options(help = "send funds to the given address")] Send(commands::send::Command), } @@ -89,6 +92,7 @@ fn main() -> Result<(), anyhow::Error> { Some(Command::Balance(command)) => command.run(params, opts.wallet_dir), Some(Command::ListTx(command)) => command.run(opts.wallet_dir), Some(Command::ListUnspent(command)) => command.run(params, opts.wallet_dir), + Some(Command::Propose(command)) => command.run(params, opts.wallet_dir).await, Some(Command::Send(command)) => command.run(params, opts.wallet_dir).await, _ => Ok(()), }