Skip to content

Commit

Permalink
Merge pull request zingolabs#1347 from fluidvanadium/wallet_propose
Browse files Browse the repository at this point in the history
LightWallet can propose
  • Loading branch information
zancas authored Aug 29, 2024
2 parents 6444f19 + 2910466 commit 5983d57
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 180 deletions.
2 changes: 1 addition & 1 deletion libtonode-tests/tests/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use zcash_address::unified::Fvk;
use zcash_client_backend::encoding::encode_payment_address;
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
use zcash_primitives::{consensus::BlockHeight, transaction::fees::zip317::MINIMUM_FEE};
use zingolib::lightclient::propose::ProposeSendError;
use zingolib::testutils::lightclient::from_inputs;
use zingolib::testutils::{build_fvk_client, increase_height_and_wait_for_client, scenarios};
use zingolib::utils::conversion::address_from_str;
use zingolib::wallet::data::summaries::TransactionSummaryInterface;
use zingolib::wallet::propose::ProposeSendError;
use zingolib::{check_client_balances, get_base_address_macro, get_otd, validate_otds};

use zingolib::config::{ChainType, RegtestNetwork, MAX_REORG};
Expand Down
2 changes: 1 addition & 1 deletion libtonode-tests/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ mod load_wallet {
use zingolib::config::RegtestNetwork;
use zingolib::config::ZingoConfig;
use zingolib::get_base_address_macro;
use zingolib::lightclient::propose::ProposeSendError::Proposal;
use zingolib::lightclient::send::send_with_proposal::QuickSendError;
use zingolib::lightclient::LightClient;
use zingolib::lightclient::PoolBalances;
Expand All @@ -18,6 +17,7 @@ mod load_wallet {
use zingolib::testutils::scenarios;
use zingolib::utils;
use zingolib::wallet::disk::testing::examples;
use zingolib::wallet::propose::ProposeSendError::Proposal;
use zingolib::wallet::LightWallet;

#[tokio::test]
Expand Down
182 changes: 9 additions & 173 deletions zingolib/src/lightclient/propose.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
//! LightClient function do_propose generates a proposal to send to specified addresses.

use std::convert::Infallible;
use std::num::NonZeroU32;
use std::ops::DerefMut;

use zcash_client_backend::data_api::wallet::input_selection::GreedyInputSelector;
use zcash_client_backend::zip321::TransactionRequest;
use zcash_client_backend::zip321::Zip321Error;
use zcash_client_backend::ShieldedProtocol;
use zcash_primitives::{memo::MemoBytes, transaction::components::amount::NonNegativeAmount};
use zcash_primitives::transaction::components::amount::NonNegativeAmount;

use crate::config::ZENNIES_FOR_ZINGO_AMOUNT;
use crate::config::ZENNIES_FOR_ZINGO_DONATION_ADDRESS;
use thiserror::Error;
use crate::wallet::propose::{ProposeSendError, ProposeShieldError};

use crate::config::ChainType;
use crate::data::proposal::ProportionalFeeProposal;
Expand All @@ -21,81 +14,6 @@ use crate::data::proposal::ZingoProposal;
use crate::data::receivers::transaction_request_from_receivers;
use crate::data::receivers::Receiver;
use crate::lightclient::LightClient;
use crate::wallet::send::change_memo_from_transaction_request;
use crate::wallet::tx_map_and_maybe_trees::TxMapAndMaybeTrees;
use crate::wallet::tx_map_and_maybe_trees::TxMapAndMaybeTreesTraitError;

type GISKit = GreedyInputSelector<
TxMapAndMaybeTrees,
zcash_client_backend::fees::zip317::SingleOutputChangeStrategy,
>;

// This private helper is a very small DRY, but it has already corrected a minor
// divergence in change strategy.
// Because shielding operations are never expected to create dust notes this change
// is not a bugfix.
fn build_default_giskit(memo: Option<MemoBytes>) -> GISKit {
let change_strategy = zcash_client_backend::fees::zip317::SingleOutputChangeStrategy::new(
zcash_primitives::transaction::fees::zip317::FeeRule::standard(),
memo,
ShieldedProtocol::Orchard,
); // review consider change strategy!

GISKit::new(
change_strategy,
zcash_client_backend::fees::DustOutputPolicy::new(
zcash_client_backend::fees::DustAction::AllowDustChange,
None,
),
)
}
/// Errors that can result from do_propose
#[derive(Debug, Error)]
pub enum ProposeSendError {
/// error in using trait to create spend proposal
#[error("{0}")]
Proposal(
zcash_client_backend::data_api::error::Error<
TxMapAndMaybeTreesTraitError,
TxMapAndMaybeTreesTraitError,
zcash_client_backend::data_api::wallet::input_selection::GreedyInputSelectorError<
zcash_primitives::transaction::fees::zip317::FeeError,
zcash_client_backend::wallet::NoteId,
>,
zcash_primitives::transaction::fees::zip317::FeeError,
>,
),
/// failed to construct a transaction request
#[error("{0}")]
TransactionRequestFailed(#[from] Zip321Error),
/// send all is transferring no value
#[error("send all is transferring no value. only enough funds to pay the fees!")]
ZeroValueSendAll,
/// failed to calculate balance.
#[error("failed to calculated balance. {0}")]
BalanceError(#[from] crate::wallet::error::BalanceError),
}

/// Errors that can result from do_propose
#[derive(Debug, Error)]
pub enum ProposeShieldError {
/// error in parsed addresses
#[error("{0}")]
Receiver(zcash_client_backend::zip321::Zip321Error),
#[error("{0}")]
/// error in using trait to create shielding proposal
Component(
zcash_client_backend::data_api::error::Error<
TxMapAndMaybeTreesTraitError,
TxMapAndMaybeTreesTraitError,
zcash_client_backend::data_api::wallet::input_selection::GreedyInputSelectorError<
zcash_primitives::transaction::fees::zip317::FeeError,
Infallible,
>,
zcash_primitives::transaction::fees::zip317::FeeError,
>,
),
}

fn append_zingo_zenny_receiver(receivers: &mut Vec<Receiver>) {
let dev_donation_receiver = Receiver::new(
Expand All @@ -117,85 +35,12 @@ impl LightClient {
let mut latest_proposal_lock = self.latest_proposal.write().await;
*latest_proposal_lock = Some(proposal);
}

/// Creates a proposal from a transaction request.
pub(crate) async fn create_send_proposal(
&self,
request: TransactionRequest,
) -> Result<ProportionalFeeProposal, ProposeSendError> {
let memo = change_memo_from_transaction_request(&request);

let input_selector = build_default_giskit(Some(memo));
let mut tmamt = self
.wallet
.transaction_context
.transaction_metadata_set
.write()
.await;

zcash_client_backend::data_api::wallet::propose_transfer::<
TxMapAndMaybeTrees,
ChainType,
GISKit,
TxMapAndMaybeTreesTraitError,
>(
tmamt.deref_mut(),
&self.wallet.transaction_context.config.chain,
zcash_primitives::zip32::AccountId::ZERO,
&input_selector,
request,
NonZeroU32::MIN, //review! use custom constant?
)
.map_err(ProposeSendError::Proposal)
}

/// The shield operation consumes a proposal that transfers value
/// into the Orchard pool.
///
/// The proposal is generated with this method, which operates on
/// the balance transparent pool, without other input.
/// In other words, shield does not take a user-specified amount
/// to shield, rather it consumes all transparent value in the wallet that
/// can be consumsed without costing more in zip317 fees than is being transferred.
pub(crate) async fn create_shield_proposal(
&self,
) -> Result<crate::data::proposal::ProportionalFeeShieldProposal, ProposeShieldError> {
let input_selector = build_default_giskit(None);

let mut tmamt = self
.wallet
.transaction_context
.transaction_metadata_set
.write()
.await;

let proposed_shield = zcash_client_backend::data_api::wallet::propose_shielding::<
TxMapAndMaybeTrees,
ChainType,
GISKit,
TxMapAndMaybeTreesTraitError,
>(
&mut tmamt,
&self.wallet.transaction_context.config.chain,
&input_selector,
// don't shield dust
NonNegativeAmount::const_from_u64(10_000),
&self.get_transparent_addresses(),
// review! do we want to require confirmations?
// make it configurable?
0,
)
.map_err(ProposeShieldError::Component)?;

Ok(proposed_shield)
}

/// Creates and stores a proposal from a transaction request.
pub async fn propose_send(
&self,
request: TransactionRequest,
) -> Result<ProportionalFeeProposal, ProposeSendError> {
let proposal = self.create_send_proposal(request).await?;
) -> Result<ProportionalFeeProposal, crate::wallet::propose::ProposeSendError> {
let proposal = self.wallet.create_send_proposal(request).await?;
self.store_proposal(ZingoProposal::Transfer(proposal.clone()))
.await;
Ok(proposal)
Expand All @@ -220,7 +65,7 @@ impl LightClient {
}
let request = transaction_request_from_receivers(receivers)
.map_err(ProposeSendError::TransactionRequestFailed)?;
let proposal = self.create_send_proposal(request).await?;
let proposal = self.wallet.create_send_proposal(request).await?;
self.store_proposal(ZingoProposal::Transfer(proposal.clone()))
.await;
Ok(proposal)
Expand Down Expand Up @@ -254,7 +99,7 @@ impl LightClient {
append_zingo_zenny_receiver(&mut receivers);
}
let request = transaction_request_from_receivers(receivers)?;
let failing_proposal = self.create_send_proposal(request).await;
let failing_proposal = self.wallet.create_send_proposal(request).await;

let shortfall = match failing_proposal {
Err(ProposeSendError::Proposal(
Expand Down Expand Up @@ -289,20 +134,11 @@ impl LightClient {
))
}

fn get_transparent_addresses(&self) -> Vec<zcash_primitives::legacy::TransparentAddress> {
self.wallet
.wallet_capability()
.transparent_child_addresses()
.iter()
.map(|(_index, sk)| *sk)
.collect::<Vec<_>>()
}

/// Creates and stores a proposal for shielding all transparent funds..
pub async fn propose_shield(
&self,
) -> Result<ProportionalFeeShieldProposal, ProposeShieldError> {
let proposal = self.create_shield_proposal().await?;
let proposal = self.wallet.create_shield_proposal().await?;
self.store_proposal(ZingoProposal::Shield(proposal.clone()))
.await;
Ok(proposal)
Expand All @@ -327,7 +163,7 @@ mod shielding {
#[tokio::test]
async fn propose_shield_missing_scan_prerequisite() {
let basic_client = create_basic_client().await;
let propose_shield_result = basic_client.create_shield_proposal().await;
let propose_shield_result = basic_client.wallet.create_shield_proposal().await;
match propose_shield_result {
Err(ProposeShieldError::Component(
zcash_client_backend::data_api::error::Error::ScanRequired,
Expand All @@ -339,7 +175,7 @@ mod shielding {
async fn get_transparent_addresses() {
let basic_client = create_basic_client().await;
assert_eq!(
basic_client.get_transparent_addresses(),
basic_client.wallet.get_transparent_addresses(),
[zcash_primitives::legacy::TransparentAddress::PublicKeyHash(
[
161, 138, 222, 242, 254, 121, 71, 105, 93, 131, 177, 31, 59, 185, 120, 148,
Expand Down
6 changes: 3 additions & 3 deletions zingolib/src/lightclient/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ pub mod send_with_proposal {

use thiserror::Error;

use crate::lightclient::propose::{ProposeSendError, ProposeShieldError};
use crate::lightclient::LightClient;
use crate::wallet::propose::{ProposeSendError, ProposeShieldError};

#[allow(missing_docs)] // error types document themselves
#[derive(Debug, Error)]
Expand Down Expand Up @@ -146,13 +146,13 @@ pub mod send_with_proposal {
&self,
request: TransactionRequest,
) -> Result<NonEmpty<TxId>, QuickSendError> {
let proposal = self.create_send_proposal(request).await?;
let proposal = self.wallet.create_send_proposal(request).await?;
Ok(self.complete_and_broadcast::<NoteId>(&proposal).await?)
}

/// Shields all transparent funds without confirmation.
pub async fn quick_shield(&self) -> Result<NonEmpty<TxId>, QuickShieldError> {
let proposal = self.create_shield_proposal().await?;
let proposal = self.wallet.create_shield_proposal().await?;
Ok(self.complete_and_broadcast::<Infallible>(&proposal).await?)
}
}
Expand Down
2 changes: 1 addition & 1 deletion zingolib/src/testutils/lightclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub mod from_inputs {
raw_receivers: Vec<(&str, u64, Option<&str>)>,
) -> Result<
crate::data::proposal::ProportionalFeeProposal,
crate::lightclient::propose::ProposeSendError,
crate::wallet::propose::ProposeSendError,
> {
let request = transaction_request_from_send_inputs(proposer, raw_receivers)
.expect("should be able to create a transaction request as receivers are valid.");
Expand Down
1 change: 1 addition & 0 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub mod utils;
//these mods contain pieces of the impl LightWallet
pub mod describe;
pub mod disk;
pub mod propose;
pub mod send;
pub mod witnesses;

Expand Down
9 changes: 9 additions & 0 deletions zingolib/src/wallet/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,15 @@ impl LightWallet {
pub fn transactions(&self) -> Arc<RwLock<TxMapAndMaybeTrees>> {
self.transaction_context.transaction_metadata_set.clone()
}

/// lists the transparent addresses known by the wallet.
pub fn get_transparent_addresses(&self) -> Vec<zcash_primitives::legacy::TransparentAddress> {
self.wallet_capability()
.transparent_child_addresses()
.iter()
.map(|(_index, sk)| *sk)
.collect::<Vec<_>>()
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 5983d57

Please sign in to comment.