Skip to content

Commit

Permalink
Merge pull request #65 from Electric-Coin-Company/shield-funds
Browse files Browse the repository at this point in the history
Add commands to shield funds
  • Loading branch information
str4d authored Dec 14, 2024
2 parents 410de82 + cc9f8f4 commit 39b668a
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) mod pczt;
pub(crate) mod propose;
pub(crate) mod reset;
pub(crate) mod send;
pub(crate) mod shield;
pub(crate) mod sync;
pub(crate) mod upgrade;

Expand Down
15 changes: 10 additions & 5 deletions src/commands/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use gumdrop::Options;
use iso_currency::Currency;
use rust_decimal::{prelude::FromPrimitive, Decimal};
use tracing::{info, warn};
use uuid::Uuid;
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 crate::{
Expand All @@ -16,6 +17,13 @@ use crate::{
// Options accepted for the `balance` command
#[derive(Debug, Options)]
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")]
convert: Option<Currency>,
}
Expand All @@ -26,10 +34,7 @@ impl Command {

let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let account_id = AccountUuid::from_uuid(self.account_id);

let address = db_data
.get_current_address(account_id)?
Expand Down
21 changes: 13 additions & 8 deletions src/commands/list_unspent.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
use anyhow::anyhow;
use gumdrop::Options;

use uuid::Uuid;
use zcash_client_backend::{
data_api::{InputSource, WalletRead},
ShieldedProtocol,
};
use zcash_client_sqlite::WalletDb;
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::{Zatoshis, MAX_MONEY};

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)]
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 {
pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
let params = get_wallet_network(wallet_dir.as_ref())?;

let (_, db_data) = get_db_paths(wallet_dir);
let db_data = WalletDb::for_path(db_data, params)?;
let account = *db_data
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let account_id = AccountUuid::from_uuid(self.account_id);

// 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.
Expand All @@ -34,7 +39,7 @@ impl Command {
.block_height();

let notes = db_data.select_spendable_notes(
account,
account_id,
Zatoshis::const_from_u64(MAX_MONEY),
&[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard],
anchor_height,
Expand Down
3 changes: 3 additions & 0 deletions src/commands/pczt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub(crate) mod create;
pub(crate) mod inspect;
pub(crate) mod prove;
pub(crate) mod send;
pub(crate) mod shield;
pub(crate) mod sign;

#[cfg(feature = "pczt-qr")]
Expand All @@ -14,6 +15,8 @@ pub(crate) mod qr;
pub(crate) enum Command {
#[options(help = "create a PCZT")]
Create(create::Command),
#[options(help = "create a shielding PCZT")]
Shield(shield::Command),
#[options(help = "inspect a PCZT")]
Inspect(inspect::Command),
#[options(help = "create proofs for a PCZT")]
Expand Down
18 changes: 8 additions & 10 deletions src/commands/pczt/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ use anyhow::anyhow;
use gumdrop::Options;

use tokio::io::{stdout, AsyncWriteExt};
use uuid::Uuid;
use zcash_address::ZcashAddress;
use zcash_client_backend::{
data_api::{
wallet::{
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
},
WalletRead,
data_api::wallet::{
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
},
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
wallet::OvkPolicy,
ShieldedProtocol,
};
use zcash_client_sqlite::WalletDb;
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::{
memo::{Memo, MemoBytes},
value::Zatoshis,
Expand All @@ -29,6 +27,9 @@ use crate::{config::WalletConfig, data::get_db_paths, error, MIN_CONFIRMATIONS};
// Options accepted for the `pczt create` command
#[derive(Debug, Options)]
pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,

#[options(
required,
help = "the recipient's Unified, Sapling or transparent address"
Expand Down Expand Up @@ -61,10 +62,7 @@ impl Command {

let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let account_id = AccountUuid::from_uuid(self.account_id);

// Create the PCZT.
let change_strategy = MultiOutputChangeStrategy::new(
Expand Down
100 changes: 100 additions & 0 deletions src/commands/pczt/shield.rs
Original file line number Diff line number Diff line change
@@ -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,
&params,
&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,
&params,
account_id,
OvkPolicy::Sender,
&proposal,
)
.map_err(error::Error::Shield)?;

stdout().write_all(&pczt.serialize()).await?;

Ok(())
}
}
18 changes: 8 additions & 10 deletions src/commands/propose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ use std::{num::NonZeroUsize, str::FromStr};
use anyhow::anyhow;
use gumdrop::Options;

use uuid::Uuid;
use zcash_address::ZcashAddress;
use zcash_client_backend::{
data_api::{
wallet::{input_selection::GreedyInputSelector, propose_transfer},
WalletRead,
},
data_api::wallet::{input_selection::GreedyInputSelector, propose_transfer},
fees::{zip317::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
ShieldedProtocol,
};
use zcash_client_sqlite::WalletDb;
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::Zatoshis;
use zip321::{Payment, TransactionRequest};

use crate::{config::get_wallet_network, data::get_db_paths, error, MIN_CONFIRMATIONS};
// Options accepted for the `propose` command
#[derive(Debug, Options)]
pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,

#[options(
required,
help = "the recipient's Unified, Sapling or transparent address"
Expand Down Expand Up @@ -48,10 +49,7 @@ impl Command {

let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?;
let account = *db_data
.get_account_ids()?
.first()
.ok_or_else(|| anyhow!("Wallet has no accounts."))?;
let account_id = AccountUuid::from_uuid(self.account_id);

let change_strategy = MultiOutputChangeStrategy::new(
StandardFeeRule::Zip317,
Expand All @@ -75,7 +73,7 @@ impl Command {
let proposal = propose_transfer(
&mut db_data,
&params,
account,
account_id,
&input_selector,
&change_strategy,
request,
Expand Down
11 changes: 6 additions & 5 deletions src/commands/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::anyhow;
use gumdrop::Options;
use secrecy::ExposeSecret;

use uuid::Uuid;
use zcash_address::ZcashAddress;
use zcash_client_backend::{
data_api::{
Expand All @@ -19,7 +20,7 @@ use zcash_client_backend::{
wallet::OvkPolicy,
ShieldedProtocol,
};
use zcash_client_sqlite::WalletDb;
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_proofs::prover::LocalTxProver;
use zcash_protocol::value::Zatoshis;
use zip321::{Payment, TransactionRequest};
Expand All @@ -35,6 +36,9 @@ use crate::{
// Options accepted for the `send` command
#[derive(Debug, Options)]
pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,

#[options(
required,
help = "age identity file to decrypt the mnemonic phrase with"
Expand Down Expand Up @@ -80,10 +84,7 @@ impl Command {

let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let account_id = AccountUuid::from_uuid(self.account_id);
let account = db_data
.get_account(account_id)?
.ok_or(anyhow!("Account missing: {:?}", account_id))?;
Expand Down
Loading

0 comments on commit 39b668a

Please sign in to comment.