Add `propose` command.

This command takes the same arguments as `send`, but instead of
executing a transaction, it simply creates and prints out the proposal
for the transaction without executing it.
This commit is contained in:
Kris Nuttycombe 2023-11-16 13:00:29 -07:00
parent f3449b6a45
commit 0973bfd868
9 changed files with 157 additions and 29 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
/zec_sqlite_wallet*

32
Cargo.lock generated
View File

@ -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",

View File

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

View File

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

View File

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

110
src/commands/propose.rs Normal file
View File

@ -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<FeeRule> 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<FeeRule, String> {
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<String>,
) -> 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(&params, &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,
&params,
account,
&input_selector,
request,
MIN_CONFIRMATIONS,
)
.map_err(error::Error::from)?;
// Display the proposal
println!("Proposal: {:#?}", proposal);
Ok(())
}
}

View File

@ -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(&params, &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,
&params,
prover,
&prover,
&prover,
&input_selector,
&usk,
request,

View File

@ -18,7 +18,6 @@ pub(crate) type WalletErrorT = WalletError<
commitment_tree::Error,
GreedyInputSelectorError<FeeError, ReceivedNoteId>,
FeeError,
ReceivedNoteId,
>;
#[derive(Debug)]

View File

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