Merge pull request #65 from Electric-Coin-Company/shield-funds
Add commands to shield funds
This commit is contained in:
commit
39b668a314
|
@ -11,6 +11,7 @@ pub(crate) mod pczt;
|
||||||
pub(crate) mod propose;
|
pub(crate) mod propose;
|
||||||
pub(crate) mod reset;
|
pub(crate) mod reset;
|
||||||
pub(crate) mod send;
|
pub(crate) mod send;
|
||||||
|
pub(crate) mod shield;
|
||||||
pub(crate) mod sync;
|
pub(crate) mod sync;
|
||||||
pub(crate) mod upgrade;
|
pub(crate) mod upgrade;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ use gumdrop::Options;
|
||||||
use iso_currency::Currency;
|
use iso_currency::Currency;
|
||||||
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
use zcash_client_backend::{data_api::WalletRead, tor};
|
use zcash_client_backend::{data_api::WalletRead, tor};
|
||||||
use zcash_client_sqlite::WalletDb;
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
use zcash_protocol::value::{Zatoshis, COIN};
|
use zcash_protocol::value::{Zatoshis, COIN};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -16,6 +17,13 @@ use crate::{
|
||||||
// Options accepted for the `balance` command
|
// Options accepted for the `balance` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
|
#[options(
|
||||||
|
free,
|
||||||
|
required,
|
||||||
|
help = "the UUID of the account for which to get a balance"
|
||||||
|
)]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
#[options(help = "Convert ZEC values into the given currency")]
|
#[options(help = "Convert ZEC values into the given currency")]
|
||||||
convert: Option<Currency>,
|
convert: Option<Currency>,
|
||||||
}
|
}
|
||||||
|
@ -26,10 +34,7 @@ impl Command {
|
||||||
|
|
||||||
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
let db_data = WalletDb::for_path(db_data, params)?;
|
let db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account_id = *db_data
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
.get_account_ids()?
|
|
||||||
.first()
|
|
||||||
.ok_or(anyhow!("Wallet has no accounts"))?;
|
|
||||||
|
|
||||||
let address = db_data
|
let address = db_data
|
||||||
.get_current_address(account_id)?
|
.get_current_address(account_id)?
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{InputSource, WalletRead},
|
data_api::{InputSource, WalletRead},
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
use zcash_client_sqlite::WalletDb;
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
use zcash_protocol::value::{Zatoshis, MAX_MONEY};
|
use zcash_protocol::value::{Zatoshis, MAX_MONEY};
|
||||||
|
|
||||||
use crate::{config::get_wallet_network, data::get_db_paths, error, ui::format_zec};
|
use crate::{config::get_wallet_network, data::get_db_paths, error, ui::format_zec};
|
||||||
|
|
||||||
// Options accepted for the `balance` command
|
// Options accepted for the `list-unspent` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub(crate) struct Command {}
|
pub(crate) struct Command {
|
||||||
|
#[options(
|
||||||
|
free,
|
||||||
|
required,
|
||||||
|
help = "the UUID of the account for which to list unspent funds"
|
||||||
|
)]
|
||||||
|
account_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
||||||
|
@ -20,10 +28,7 @@ impl Command {
|
||||||
|
|
||||||
let (_, db_data) = get_db_paths(wallet_dir);
|
let (_, db_data) = get_db_paths(wallet_dir);
|
||||||
let db_data = WalletDb::for_path(db_data, params)?;
|
let db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account = *db_data
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
.get_account_ids()?
|
|
||||||
.first()
|
|
||||||
.ok_or(anyhow!("Wallet has no accounts"))?;
|
|
||||||
|
|
||||||
// Use the height of the maximum scanned block as the anchor height, to emulate a
|
// Use the height of the maximum scanned block as the anchor height, to emulate a
|
||||||
// zero-conf transaction in order to select every note in the wallet.
|
// zero-conf transaction in order to select every note in the wallet.
|
||||||
|
@ -34,7 +39,7 @@ impl Command {
|
||||||
.block_height();
|
.block_height();
|
||||||
|
|
||||||
let notes = db_data.select_spendable_notes(
|
let notes = db_data.select_spendable_notes(
|
||||||
account,
|
account_id,
|
||||||
Zatoshis::const_from_u64(MAX_MONEY),
|
Zatoshis::const_from_u64(MAX_MONEY),
|
||||||
&[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard],
|
&[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard],
|
||||||
anchor_height,
|
anchor_height,
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub(crate) mod create;
|
||||||
pub(crate) mod inspect;
|
pub(crate) mod inspect;
|
||||||
pub(crate) mod prove;
|
pub(crate) mod prove;
|
||||||
pub(crate) mod send;
|
pub(crate) mod send;
|
||||||
|
pub(crate) mod shield;
|
||||||
pub(crate) mod sign;
|
pub(crate) mod sign;
|
||||||
|
|
||||||
#[cfg(feature = "pczt-qr")]
|
#[cfg(feature = "pczt-qr")]
|
||||||
|
@ -14,6 +15,8 @@ pub(crate) mod qr;
|
||||||
pub(crate) enum Command {
|
pub(crate) enum Command {
|
||||||
#[options(help = "create a PCZT")]
|
#[options(help = "create a PCZT")]
|
||||||
Create(create::Command),
|
Create(create::Command),
|
||||||
|
#[options(help = "create a shielding PCZT")]
|
||||||
|
Shield(shield::Command),
|
||||||
#[options(help = "inspect a PCZT")]
|
#[options(help = "inspect a PCZT")]
|
||||||
Inspect(inspect::Command),
|
Inspect(inspect::Command),
|
||||||
#[options(help = "create proofs for a PCZT")]
|
#[options(help = "create proofs for a PCZT")]
|
||||||
|
|
|
@ -5,19 +5,17 @@ use anyhow::anyhow;
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
use tokio::io::{stdout, AsyncWriteExt};
|
use tokio::io::{stdout, AsyncWriteExt};
|
||||||
|
use uuid::Uuid;
|
||||||
use zcash_address::ZcashAddress;
|
use zcash_address::ZcashAddress;
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{
|
data_api::wallet::{
|
||||||
wallet::{
|
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
|
||||||
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
|
|
||||||
},
|
|
||||||
WalletRead,
|
|
||||||
},
|
},
|
||||||
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
use zcash_client_sqlite::WalletDb;
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
use zcash_protocol::{
|
use zcash_protocol::{
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
value::Zatoshis,
|
value::Zatoshis,
|
||||||
|
@ -29,6 +27,9 @@ use crate::{config::WalletConfig, data::get_db_paths, error, MIN_CONFIRMATIONS};
|
||||||
// Options accepted for the `pczt create` command
|
// Options accepted for the `pczt create` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
|
#[options(free, required, help = "the UUID of the account to send funds from")]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
#[options(
|
#[options(
|
||||||
required,
|
required,
|
||||||
help = "the recipient's Unified, Sapling or transparent address"
|
help = "the recipient's Unified, Sapling or transparent address"
|
||||||
|
@ -61,10 +62,7 @@ impl Command {
|
||||||
|
|
||||||
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
let mut db_data = WalletDb::for_path(db_data, params)?;
|
let mut db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account_id = *db_data
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
.get_account_ids()?
|
|
||||||
.first()
|
|
||||||
.ok_or(anyhow!("Wallet has no accounts"))?;
|
|
||||||
|
|
||||||
// Create the PCZT.
|
// Create the PCZT.
|
||||||
let change_strategy = MultiOutputChangeStrategy::new(
|
let change_strategy = MultiOutputChangeStrategy::new(
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use tokio::io::{stdout, AsyncWriteExt};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use zcash_client_backend::{
|
||||||
|
data_api::{
|
||||||
|
wallet::{
|
||||||
|
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_shielding,
|
||||||
|
},
|
||||||
|
WalletRead,
|
||||||
|
},
|
||||||
|
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
||||||
|
wallet::OvkPolicy,
|
||||||
|
ShieldedProtocol,
|
||||||
|
};
|
||||||
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
|
use zcash_protocol::value::Zatoshis;
|
||||||
|
|
||||||
|
use crate::{config::WalletConfig, data::get_db_paths, error};
|
||||||
|
|
||||||
|
// Options accepted for the `pczt shield` command
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub(crate) struct Command {
|
||||||
|
#[options(free, required, help = "the UUID of the account to shield funds in")]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
help = "note management: the number of notes to maintain in the wallet",
|
||||||
|
default = "4"
|
||||||
|
)]
|
||||||
|
target_note_count: usize,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
help = "note management: the minimum allowed value for split change amounts",
|
||||||
|
default = "10000000"
|
||||||
|
)]
|
||||||
|
min_split_output_value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub(crate) async fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
||||||
|
let config = WalletConfig::read(wallet_dir.as_ref())?;
|
||||||
|
let params = config.network();
|
||||||
|
|
||||||
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
|
let mut db_data = WalletDb::for_path(db_data, params)?;
|
||||||
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
|
|
||||||
|
// Create the PCZT.
|
||||||
|
let change_strategy = MultiOutputChangeStrategy::new(
|
||||||
|
StandardFeeRule::Zip317,
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Orchard,
|
||||||
|
DustOutputPolicy::default(),
|
||||||
|
SplitPolicy::with_min_output_value(
|
||||||
|
NonZeroUsize::new(self.target_note_count)
|
||||||
|
.ok_or(anyhow!("target note count must be nonzero"))?,
|
||||||
|
Zatoshis::from_u64(self.min_split_output_value)?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let input_selector = GreedyInputSelector::new();
|
||||||
|
|
||||||
|
// For this dev tool, shield all funds immediately.
|
||||||
|
let max_height = match db_data.chain_height()? {
|
||||||
|
Some(max_height) => max_height,
|
||||||
|
// If we haven't scanned anything, there's nothing to do.
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
let transparent_balances = db_data.get_transparent_balances(account_id, max_height)?;
|
||||||
|
let from_addrs = transparent_balances.into_keys().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let proposal = propose_shielding(
|
||||||
|
&mut db_data,
|
||||||
|
¶ms,
|
||||||
|
&input_selector,
|
||||||
|
&change_strategy,
|
||||||
|
Zatoshis::ZERO,
|
||||||
|
&from_addrs,
|
||||||
|
account_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.map_err(error::Error::Shield)?;
|
||||||
|
|
||||||
|
let pczt = create_pczt_from_proposal(
|
||||||
|
&mut db_data,
|
||||||
|
¶ms,
|
||||||
|
account_id,
|
||||||
|
OvkPolicy::Sender,
|
||||||
|
&proposal,
|
||||||
|
)
|
||||||
|
.map_err(error::Error::Shield)?;
|
||||||
|
|
||||||
|
stdout().write_all(&pczt.serialize()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,16 +3,14 @@ use std::{num::NonZeroUsize, str::FromStr};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
use zcash_address::ZcashAddress;
|
use zcash_address::ZcashAddress;
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{
|
data_api::wallet::{input_selection::GreedyInputSelector, propose_transfer},
|
||||||
wallet::{input_selection::GreedyInputSelector, propose_transfer},
|
|
||||||
WalletRead,
|
|
||||||
},
|
|
||||||
fees::{zip317::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
fees::{zip317::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
use zcash_client_sqlite::WalletDb;
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
use zcash_protocol::value::Zatoshis;
|
use zcash_protocol::value::Zatoshis;
|
||||||
use zip321::{Payment, TransactionRequest};
|
use zip321::{Payment, TransactionRequest};
|
||||||
|
|
||||||
|
@ -20,6 +18,9 @@ use crate::{config::get_wallet_network, data::get_db_paths, error, MIN_CONFIRMAT
|
||||||
// Options accepted for the `propose` command
|
// Options accepted for the `propose` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
|
#[options(free, required, help = "the UUID of the account to send funds from")]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
#[options(
|
#[options(
|
||||||
required,
|
required,
|
||||||
help = "the recipient's Unified, Sapling or transparent address"
|
help = "the recipient's Unified, Sapling or transparent address"
|
||||||
|
@ -48,10 +49,7 @@ impl Command {
|
||||||
|
|
||||||
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
let mut db_data = WalletDb::for_path(db_data, params)?;
|
let mut db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account = *db_data
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
.get_account_ids()?
|
|
||||||
.first()
|
|
||||||
.ok_or_else(|| anyhow!("Wallet has no accounts."))?;
|
|
||||||
|
|
||||||
let change_strategy = MultiOutputChangeStrategy::new(
|
let change_strategy = MultiOutputChangeStrategy::new(
|
||||||
StandardFeeRule::Zip317,
|
StandardFeeRule::Zip317,
|
||||||
|
@ -75,7 +73,7 @@ impl Command {
|
||||||
let proposal = propose_transfer(
|
let proposal = propose_transfer(
|
||||||
&mut db_data,
|
&mut db_data,
|
||||||
¶ms,
|
¶ms,
|
||||||
account,
|
account_id,
|
||||||
&input_selector,
|
&input_selector,
|
||||||
&change_strategy,
|
&change_strategy,
|
||||||
request,
|
request,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use anyhow::anyhow;
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
use zcash_address::ZcashAddress;
|
use zcash_address::ZcashAddress;
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{
|
data_api::{
|
||||||
|
@ -19,7 +20,7 @@ use zcash_client_backend::{
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
use zcash_client_sqlite::WalletDb;
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
use zcash_protocol::value::Zatoshis;
|
use zcash_protocol::value::Zatoshis;
|
||||||
use zip321::{Payment, TransactionRequest};
|
use zip321::{Payment, TransactionRequest};
|
||||||
|
@ -35,6 +36,9 @@ use crate::{
|
||||||
// Options accepted for the `send` command
|
// Options accepted for the `send` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
|
#[options(free, required, help = "the UUID of the account to send funds from")]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
#[options(
|
#[options(
|
||||||
required,
|
required,
|
||||||
help = "age identity file to decrypt the mnemonic phrase with"
|
help = "age identity file to decrypt the mnemonic phrase with"
|
||||||
|
@ -80,10 +84,7 @@ impl Command {
|
||||||
|
|
||||||
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
let mut db_data = WalletDb::for_path(db_data, params)?;
|
let mut db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account_id = *db_data
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
.get_account_ids()?
|
|
||||||
.first()
|
|
||||||
.ok_or(anyhow!("Wallet has no accounts"))?;
|
|
||||||
let account = db_data
|
let account = db_data
|
||||||
.get_account(account_id)?
|
.get_account(account_id)?
|
||||||
.ok_or(anyhow!("Account missing: {:?}", account_id))?;
|
.ok_or(anyhow!("Account missing: {:?}", account_id))?;
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use gumdrop::Options;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
use zcash_client_backend::{
|
||||||
|
data_api::{
|
||||||
|
wallet::{
|
||||||
|
create_proposed_transactions, input_selection::GreedyInputSelector, propose_shielding,
|
||||||
|
},
|
||||||
|
Account, WalletRead,
|
||||||
|
},
|
||||||
|
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
|
||||||
|
keys::UnifiedSpendingKey,
|
||||||
|
proto::service,
|
||||||
|
wallet::OvkPolicy,
|
||||||
|
ShieldedProtocol,
|
||||||
|
};
|
||||||
|
use zcash_client_sqlite::{AccountUuid, WalletDb};
|
||||||
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
use zcash_protocol::value::Zatoshis;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::WalletConfig,
|
||||||
|
data::get_db_paths,
|
||||||
|
error,
|
||||||
|
remote::{tor_client, Servers},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Options accepted for the `shield` command
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub(crate) struct Command {
|
||||||
|
#[options(free, required, help = "the UUID of the account to shield funds in")]
|
||||||
|
account_id: Uuid,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
required,
|
||||||
|
help = "age identity file to decrypt the mnemonic phrase with"
|
||||||
|
)]
|
||||||
|
identity: String,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
help = "the server to shield via (default is \"ecc\")",
|
||||||
|
default = "ecc",
|
||||||
|
parse(try_from_str = "Servers::parse")
|
||||||
|
)]
|
||||||
|
server: Servers,
|
||||||
|
|
||||||
|
#[options(help = "disable connections via TOR")]
|
||||||
|
disable_tor: bool,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
help = "note management: the number of notes to maintain in the wallet",
|
||||||
|
default = "4"
|
||||||
|
)]
|
||||||
|
target_note_count: usize,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
help = "note management: the minimum allowed value for split change amounts",
|
||||||
|
default = "10000000"
|
||||||
|
)]
|
||||||
|
min_split_output_value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub(crate) async fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
||||||
|
let mut config = WalletConfig::read(wallet_dir.as_ref())?;
|
||||||
|
let params = config.network();
|
||||||
|
|
||||||
|
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||||
|
let mut db_data = WalletDb::for_path(db_data, params)?;
|
||||||
|
let account_id = AccountUuid::from_uuid(self.account_id);
|
||||||
|
let account = db_data
|
||||||
|
.get_account(account_id)?
|
||||||
|
.ok_or(anyhow!("Account missing: {:?}", account_id))?;
|
||||||
|
let derivation = account.source().key_derivation().ok_or(anyhow!(
|
||||||
|
"Cannot spend from view-only accounts; did you mean to use `pczt shield` instead?"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Decrypt the mnemonic to access the seed.
|
||||||
|
let identities = age::IdentityFile::from_file(self.identity)?.into_identities()?;
|
||||||
|
config.decrypt(identities.iter().map(|i| i.as_ref() as _))?;
|
||||||
|
|
||||||
|
let usk = UnifiedSpendingKey::from_seed(
|
||||||
|
¶ms,
|
||||||
|
config
|
||||||
|
.seed()
|
||||||
|
.ok_or(anyhow!("Seed must be present to enable sending"))?
|
||||||
|
.expose_secret(),
|
||||||
|
derivation.account_index(),
|
||||||
|
)
|
||||||
|
.map_err(error::Error::from)?;
|
||||||
|
|
||||||
|
let server = self.server.pick(params)?;
|
||||||
|
let mut client = if self.disable_tor {
|
||||||
|
server.connect_direct().await?
|
||||||
|
} else {
|
||||||
|
server.connect(|| tor_client(wallet_dir.as_ref())).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the transaction.
|
||||||
|
println!("Creating transaction...");
|
||||||
|
let prover =
|
||||||
|
LocalTxProver::with_default_location().ok_or(error::Error::MissingParameters)?;
|
||||||
|
let change_strategy = MultiOutputChangeStrategy::new(
|
||||||
|
StandardFeeRule::Zip317,
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Orchard,
|
||||||
|
DustOutputPolicy::default(),
|
||||||
|
SplitPolicy::with_min_output_value(
|
||||||
|
NonZeroUsize::new(self.target_note_count)
|
||||||
|
.ok_or(anyhow!("target note count must be nonzero"))?,
|
||||||
|
Zatoshis::from_u64(self.min_split_output_value)?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let input_selector = GreedyInputSelector::new();
|
||||||
|
|
||||||
|
// For this dev tool, shield all funds immediately.
|
||||||
|
let max_height = match db_data.chain_height()? {
|
||||||
|
Some(max_height) => max_height,
|
||||||
|
// If we haven't scanned anything, there's nothing to do.
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
let transparent_balances = db_data.get_transparent_balances(account_id, max_height)?;
|
||||||
|
let from_addrs = transparent_balances.into_keys().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let proposal = propose_shielding(
|
||||||
|
&mut db_data,
|
||||||
|
¶ms,
|
||||||
|
&input_selector,
|
||||||
|
&change_strategy,
|
||||||
|
Zatoshis::ZERO,
|
||||||
|
&from_addrs,
|
||||||
|
account_id,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.map_err(error::Error::Shield)?;
|
||||||
|
|
||||||
|
let txids = create_proposed_transactions(
|
||||||
|
&mut db_data,
|
||||||
|
¶ms,
|
||||||
|
&prover,
|
||||||
|
&prover,
|
||||||
|
&usk,
|
||||||
|
OvkPolicy::Sender,
|
||||||
|
&proposal,
|
||||||
|
)
|
||||||
|
.map_err(error::Error::Shield)?;
|
||||||
|
|
||||||
|
if txids.len() > 1 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Multi-transaction proposals are not yet supported."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let txid = *txids.first();
|
||||||
|
|
||||||
|
// Send the transaction.
|
||||||
|
println!("Sending transaction...");
|
||||||
|
let (txid, raw_tx) = db_data
|
||||||
|
.get_transaction(txid)?
|
||||||
|
.map(|tx| {
|
||||||
|
let mut raw_tx = service::RawTransaction::default();
|
||||||
|
tx.write(&mut raw_tx.data).unwrap();
|
||||||
|
(tx.txid(), raw_tx)
|
||||||
|
})
|
||||||
|
.ok_or(anyhow!("Transaction not found for id {:?}", txid))?;
|
||||||
|
let response = client.send_transaction(raw_tx).await?.into_inner();
|
||||||
|
|
||||||
|
if response.error_code != 0 {
|
||||||
|
Err(error::Error::SendFailed {
|
||||||
|
code: response.error_code,
|
||||||
|
reason: response.error_message,
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
println!("{}", txid);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/error.rs
12
src/error.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
|
@ -22,6 +23,15 @@ pub(crate) type WalletErrorT = WalletError<
|
||||||
ReceivedNoteId,
|
ReceivedNoteId,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
pub(crate) type ShieldErrorT = WalletError<
|
||||||
|
SqliteClientError,
|
||||||
|
commitment_tree::Error,
|
||||||
|
GreedyInputSelectorError,
|
||||||
|
zip317::FeeError,
|
||||||
|
zip317::FeeError,
|
||||||
|
Infallible,
|
||||||
|
>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Cache(FsBlockDbError),
|
Cache(FsBlockDbError),
|
||||||
|
@ -32,6 +42,7 @@ pub enum Error {
|
||||||
InvalidTreeState,
|
InvalidTreeState,
|
||||||
MissingParameters,
|
MissingParameters,
|
||||||
SendFailed { code: i32, reason: String },
|
SendFailed { code: i32, reason: String },
|
||||||
|
Shield(ShieldErrorT),
|
||||||
Wallet(WalletErrorT),
|
Wallet(WalletErrorT),
|
||||||
Zip321(Zip321Error),
|
Zip321(Zip321Error),
|
||||||
}
|
}
|
||||||
|
@ -47,6 +58,7 @@ impl fmt::Display for Error {
|
||||||
Error::InvalidTreeState => write!(f, "Invalid TreeState received from server"),
|
Error::InvalidTreeState => write!(f, "Invalid TreeState received from server"),
|
||||||
Error::MissingParameters => write!(f, "Missing proving parameters"),
|
Error::MissingParameters => write!(f, "Missing proving parameters"),
|
||||||
Error::SendFailed { code, reason } => write!(f, "Send failed: ({}) {}", code, reason),
|
Error::SendFailed { code, reason } => write!(f, "Send failed: ({}) {}", code, reason),
|
||||||
|
Error::Shield(e) => e.fmt(f),
|
||||||
Error::Wallet(e) => e.fmt(f),
|
Error::Wallet(e) => e.fmt(f),
|
||||||
Error::Zip321(e) => write!(f, "{:?}", e),
|
Error::Zip321(e) => write!(f, "{:?}", e),
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ enum Command {
|
||||||
#[options(help = "list the unspent notes in the wallet")]
|
#[options(help = "list the unspent notes in the wallet")]
|
||||||
ListUnspent(commands::list_unspent::Command),
|
ListUnspent(commands::list_unspent::Command),
|
||||||
|
|
||||||
|
#[options(help = "shield transparent funds received by the wallet")]
|
||||||
|
Shield(commands::shield::Command),
|
||||||
|
|
||||||
#[options(help = "propose a transfer of funds to the given address and display the proposal")]
|
#[options(help = "propose a transfer of funds to the given address and display the proposal")]
|
||||||
Propose(commands::propose::Command),
|
Propose(commands::propose::Command),
|
||||||
|
|
||||||
|
@ -165,10 +168,12 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
Some(Command::ListAddresses(command)) => command.run(opts.wallet_dir),
|
Some(Command::ListAddresses(command)) => command.run(opts.wallet_dir),
|
||||||
Some(Command::ListTx(command)) => command.run(opts.wallet_dir),
|
Some(Command::ListTx(command)) => command.run(opts.wallet_dir),
|
||||||
Some(Command::ListUnspent(command)) => command.run(opts.wallet_dir),
|
Some(Command::ListUnspent(command)) => command.run(opts.wallet_dir),
|
||||||
|
Some(Command::Shield(command)) => command.run(opts.wallet_dir).await,
|
||||||
Some(Command::Propose(command)) => command.run(opts.wallet_dir).await,
|
Some(Command::Propose(command)) => command.run(opts.wallet_dir).await,
|
||||||
Some(Command::Send(command)) => command.run(opts.wallet_dir).await,
|
Some(Command::Send(command)) => command.run(opts.wallet_dir).await,
|
||||||
Some(Command::Pczt(command)) => match command {
|
Some(Command::Pczt(command)) => match command {
|
||||||
commands::pczt::Command::Create(command) => command.run(opts.wallet_dir).await,
|
commands::pczt::Command::Create(command) => command.run(opts.wallet_dir).await,
|
||||||
|
commands::pczt::Command::Shield(command) => command.run(opts.wallet_dir).await,
|
||||||
commands::pczt::Command::Inspect(command) => command.run().await,
|
commands::pczt::Command::Inspect(command) => command.run().await,
|
||||||
commands::pczt::Command::Prove(command) => command.run(opts.wallet_dir).await,
|
commands::pczt::Command::Prove(command) => command.run(opts.wallet_dir).await,
|
||||||
commands::pczt::Command::Sign(command) => command.run(opts.wallet_dir).await,
|
commands::pczt::Command::Sign(command) => command.run(opts.wallet_dir).await,
|
||||||
|
|
Loading…
Reference in New Issue