zcash_client_backend: Update to use extracted `zip321` crate

This commit is contained in:
Kris Nuttycombe 2024-01-27 13:46:14 -07:00
parent d982d7826a
commit 3ea7d84183
13 changed files with 120 additions and 41 deletions

View File

@ -8,8 +8,8 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Added
- `zcash_address::ZcashAddress::can_receive_memo`
- `zcash_address::unified::Address::can_receive_memo`
- `zcash_address::ZcashAddress::{can_receive_memo, can_receive_as}`
- `zcash_address::unified::Address::{can_receive_memo, has_receiver}`
- Module `zcash_address::testing` under the `test-dependencies` feature.
- Module `zcash_address::unified::address::testing` under the
`test-dependencies` feature.

View File

@ -1,3 +1,5 @@
use zcash_protocol::{PoolType, ShieldedProtocol};
use super::{private::SealedItem, ParseError, Typecode};
use std::convert::{TryFrom, TryInto};
@ -102,6 +104,17 @@ impl SealedItem for Receiver {
pub struct Address(pub(crate) Vec<Receiver>);
impl Address {
/// Returns whether this address has the ability to receive transfers of the given pool type.
pub fn has_receiver(&self, pool_type: PoolType) -> bool {
self.0.iter().any(|r| match r {
Receiver::Orchard(_) => pool_type == PoolType::Shielded(ShieldedProtocol::Orchard),
Receiver::Sapling(_) => pool_type == PoolType::Shielded(ShieldedProtocol::Sapling),
Receiver::P2pkh(_) => pool_type == PoolType::Transparent,
Receiver::P2sh(_) => pool_type == PoolType::Transparent,
Receiver::Unknown { .. } => false,
})
}
/// Returns whether this address can receive a memo.
pub fn can_receive_memo(&self) -> bool {
self.0

View File

@ -142,6 +142,7 @@ pub use convert::{
pub use encoding::ParseError;
pub use kind::unified;
pub use zcash_protocol::consensus::NetworkType as Network;
use zcash_protocol::{PoolType, ShieldedProtocol};
/// A Zcash address.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@ -267,6 +268,18 @@ impl ZcashAddress {
}
}
/// Returns whether this address has the ability to receive transfers of the given pool type.
pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
match &self.kind {
AddressKind::Sprout(_) => false,
AddressKind::Sapling(_) => pool_type == PoolType::Shielded(ShieldedProtocol::Sapling),
AddressKind::Unified(addr) => addr.has_receiver(pool_type),
AddressKind::P2pkh(_) => pool_type == PoolType::Transparent,
AddressKind::P2sh(_) => pool_type == PoolType::Transparent,
AddressKind::Tex(_) => pool_type == PoolType::Transparent,
}
}
/// Returns whether this address can receive a memo.
pub fn can_receive_memo(&self) -> bool {
match &self.kind {

View File

@ -19,6 +19,12 @@ and this library adheres to Rust's notion of
of the root module of the `zip321` crate. Several of the APIs of this module
have changed as a consequence of this extraction; please see the `zip321`
CHANGELOG for details.
- `zcash_client_backend::data_api`:
- `error::Error` has a new `Address` variant.
- `wallet::input_selection::InputSelectorError` has a new `Address` variant.
- `zcash_client_backend::proto::proposal::Proposal::{from_standard_proposal,
try_into_standard_proposal}` each no longer require a `consensus::Parameters`
argument.
## [0.12.1] - 2024-03-27

View File

@ -4,6 +4,7 @@ use std::error;
use std::fmt::{self, Debug, Display};
use shardtree::error::ShardTreeError;
use zcash_address::ConversionError;
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
use zcash_primitives::transaction::{
builder,
@ -81,6 +82,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
/// full viewing key for an account.
NoteMismatch(NoteId),
/// An error occurred parsing the address from a payment request.
Address(ConversionError<&'static str>),
#[cfg(feature = "transparent-inputs")]
AddressNotRecognized(TransparentAddress),
}
@ -145,6 +149,9 @@ where
Error::NoSpendingKey(addr) => write!(f, "No spending key available for address: {}", addr),
Error::NoteMismatch(n) => write!(f, "A note being spent ({:?}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
Error::Address(e) => {
write!(f, "An error occurred decoding the address from a payment request: {}.", e)
}
#[cfg(feature = "transparent-inputs")]
Error::AddressNotRecognized(_) => {
write!(f, "The specified transparent address was not recognized as belonging to the wallet.")
@ -184,6 +191,12 @@ impl<DE, CE, SE, FE> From<BalanceError> for Error<DE, CE, SE, FE> {
}
}
impl<DE, CE, SE, FE> From<ConversionError<&'static str>> for Error<DE, CE, SE, FE> {
fn from(value: ConversionError<&'static str>) -> Self {
Error::Address(value)
}
}
impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE> {
fn from(e: InputSelectorError<DE, SE>) -> Self {
match e {
@ -198,6 +211,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
required,
},
InputSelectorError::SyncRequired => Error::ScanRequired,
InputSelectorError::Address(e) => Error::Address(e),
}
}
}

View File

@ -498,7 +498,7 @@ where
DbT::NoteRef: Copy + Eq + Ord,
{
let request = zip321::TransactionRequest::new(vec![Payment {
recipient_address: to.clone(),
recipient_address: to.to_zcash_address(params),
amount,
memo,
label: None,
@ -848,13 +848,16 @@ where
// the transaction in payment index order, so we can use dead reckoning to
// figure out which output it ended up being.
let (prior_step, result) = &prior_step_results[input_ref.step_index()];
let recipient_address = match &prior_step
let recipient_address = &prior_step
.transaction_request()
.payments()
.get(&i)
.expect("Payment step references are checked at construction")
.recipient_address
{
.clone()
.convert_if_network(params.network_type())?;
let recipient_taddr = match recipient_address {
Address::Transparent(t) => Some(t),
Address::Unified(uaddr) => uaddr.transparent(),
_ => None,
@ -879,7 +882,7 @@ where
.ok_or(Error::Proposal(ProposalError::ReferenceError(*input_ref)))?
.vout[outpoint.n() as usize];
add_transparent_input(recipient_address, outpoint, utxo.clone())?;
add_transparent_input(recipient_taddr, outpoint, utxo.clone())?;
}
proposal::StepOutputIndex::Change(_) => unreachable!(),
}
@ -953,7 +956,11 @@ where
(payment, output_pool)
})
{
match &payment.recipient_address {
let recipient_address = payment
.recipient_address
.clone()
.convert_if_network::<Address>(params.network_type())?;
match recipient_address {
Address::Unified(ua) => {
let memo = payment
.memo
@ -1019,17 +1026,17 @@ where
.map_or_else(MemoBytes::empty, |m| m.clone());
builder.add_sapling_output(
sapling_external_ovk,
*addr,
addr,
payment.amount,
memo.clone(),
)?;
sapling_output_meta.push((Recipient::Sapling(*addr), payment.amount, Some(memo)));
sapling_output_meta.push((Recipient::Sapling(addr), payment.amount, Some(memo)));
}
Address::Transparent(to) => {
if payment.memo.is_some() {
return Err(Error::MemoForbidden);
} else {
builder.add_transparent_output(to, payment.amount)?;
builder.add_transparent_output(&to, payment.amount)?;
}
transparent_output_meta.push((to, payment.amount));
}
@ -1167,7 +1174,7 @@ where
.map(|(index, _)| index)
.expect("An output should exist in the transaction for each transparent payment.");
SentTransactionOutput::from_parts(output_index, Recipient::Transparent(*addr), value, None)
SentTransactionOutput::from_parts(output_index, Recipient::Transparent(addr), value, None)
});
let mut outputs = vec![];

View File

@ -8,6 +8,7 @@ use std::{
};
use nonempty::NonEmpty;
use zcash_address::ConversionError;
use zcash_primitives::{
consensus::{self, BlockHeight},
transaction::{
@ -48,6 +49,8 @@ pub enum InputSelectorError<DbErrT, SelectorErrT> {
Selection(SelectorErrT),
/// Input selection attempted to generate an invalid transaction proposal.
Proposal(ProposalError),
/// An error occurred parsing the address from a payment request.
Address(ConversionError<&'static str>),
/// Insufficient funds were available to satisfy the payment request that inputs were being
/// selected to attempt to satisfy.
InsufficientFunds {
@ -59,6 +62,12 @@ pub enum InputSelectorError<DbErrT, SelectorErrT> {
SyncRequired,
}
impl<E, S> From<ConversionError<&'static str>> for InputSelectorError<E, S> {
fn from(value: ConversionError<&'static str>) -> Self {
InputSelectorError::Address(value)
}
}
impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE, SE> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
@ -79,6 +88,13 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
e
)
}
InputSelectorError::Address(e) => {
write!(
f,
"An error occurred decoding the address from a payment request: {}.",
e
)
}
InputSelectorError::InsufficientFunds {
available,
required,
@ -344,7 +360,11 @@ where
let mut orchard_outputs = vec![];
let mut payment_pools = BTreeMap::new();
for (idx, payment) in transaction_request.payments() {
match &payment.recipient_address {
let recipient_address = payment
.recipient_address
.clone()
.convert_if_network::<Address>(params.network_type())?;
match recipient_address {
Address::Transparent(addr) => {
payment_pools.insert(*idx, PoolType::Transparent);
transparent_outputs.push(TxOut {
@ -380,7 +400,7 @@ where
}
return Err(InputSelectorError::Selection(
GreedyInputSelectorError::UnsupportedAddress(Box::new(addr.clone())),
GreedyInputSelectorError::UnsupportedAddress(Box::new(addr)),
));
}
}

View File

@ -377,7 +377,7 @@ impl<NoteRef> Step<NoteRef> {
.payments()
.get(idx)
.iter()
.any(|payment| payment.recipient_address.has_receiver(*pool))
.any(|payment| payment.recipient_address.can_receive_as(*pool))
{
return Err(ProposalError::PaymentPoolsMismatch);
}

View File

@ -13,7 +13,7 @@ use sapling::{self, note::ExtractedNoteCommitment, Node};
use zcash_note_encryption::{EphemeralKeyBytes, COMPACT_NOTE_SIZE};
use zcash_primitives::{
block::{BlockHash, BlockHeader},
consensus::{self, BlockHeight, Parameters},
consensus::BlockHeight,
memo::{self, MemoBytes},
merkle_tree::read_commitment_tree,
transaction::{components::amount::NonNegativeAmount, fees::StandardFeeRule, TxId},
@ -485,17 +485,14 @@ impl From<ShieldedProtocol> for proposal::ValuePool {
impl proposal::Proposal {
/// Serializes a [`Proposal`] based upon a supported [`StandardFeeRule`] to its protobuf
/// representation.
pub fn from_standard_proposal<P: Parameters, NoteRef>(
params: &P,
value: &Proposal<StandardFeeRule, NoteRef>,
) -> Self {
pub fn from_standard_proposal<NoteRef>(value: &Proposal<StandardFeeRule, NoteRef>) -> Self {
use proposal::proposed_input;
use proposal::{PriorStepChange, PriorStepOutput, ReceivedOutput};
let steps = value
.steps()
.iter()
.map(|step| {
let transaction_request = step.transaction_request().to_uri(params);
let transaction_request = step.transaction_request().to_uri();
let anchor_height = step
.shielded_inputs()
@ -607,9 +604,8 @@ impl proposal::Proposal {
/// Attempts to parse a [`Proposal`] based upon a supported [`StandardFeeRule`] from its
/// protobuf representation.
pub fn try_into_standard_proposal<P: consensus::Parameters, DbT, DbError>(
pub fn try_into_standard_proposal<DbT, DbError>(
&self,
params: &P,
wallet_db: &DbT,
) -> Result<Proposal<StandardFeeRule, DbT::NoteRef>, ProposalDecodingError<DbError>>
where
@ -631,7 +627,7 @@ impl proposal::Proposal {
let mut steps = Vec::with_capacity(self.steps.len());
for step in &self.steps {
let transaction_request =
TransactionRequest::from_uri(params, &step.transaction_request)?;
TransactionRequest::from_uri(&step.transaction_request)?;
let payment_pools = step
.payment_output_pools

View File

@ -1896,7 +1896,7 @@ fn check_proposal_serialization_roundtrip(
db_data: &WalletDb<rusqlite::Connection, LocalNetwork>,
proposal: &Proposal<StandardFeeRule, ReceivedNoteId>,
) {
let proposal_proto = proposal::Proposal::from_standard_proposal(&db_data.params, proposal);
let deserialized_proposal = proposal_proto.try_into_standard_proposal(&db_data.params, db_data);
let proposal_proto = proposal::Proposal::from_standard_proposal(proposal);
let deserialized_proposal = proposal_proto.try_into_standard_proposal(db_data);
assert_matches!(deserialized_proposal, Ok(r) if &r == proposal);
}

View File

@ -169,7 +169,7 @@ pub(crate) fn send_single_step_proposed_transfer<T: ShieldedPoolTester>() {
let to_extsk = T::sk(&[0xf5; 32]);
let to: Address = T::sk_default_address(&to_extsk);
let request = zip321::TransactionRequest::new(vec![Payment {
recipient_address: to,
recipient_address: to.to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(10000),
memo: None, // this should result in the creation of an empty memo
label: None,
@ -338,7 +338,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
// The first step will deshield to the wallet's default transparent address
let to0 = Address::Transparent(account.usk().default_transparent_address().0);
let request0 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: to0,
recipient_address: to0.to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(50000),
memo: None,
label: None,
@ -383,7 +383,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
.0,
);
let request1 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: to1,
recipient_address: to1.to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(40000),
memo: None,
label: None,
@ -1043,7 +1043,7 @@ pub(crate) fn external_address_change_spends_detected_in_restore_from_seed<
let req = TransactionRequest::new(vec![
// payment to an external recipient
Payment {
recipient_address: addr2,
recipient_address: addr2.to_zcash_address(&st.network()),
amount: amount_sent,
memo: None,
label: None,
@ -1052,7 +1052,7 @@ pub(crate) fn external_address_change_spends_detected_in_restore_from_seed<
},
// payment back to the originating wallet, simulating legacy change
Payment {
recipient_address: addr,
recipient_address: addr.to_zcash_address(&st.network()),
amount: amount_legacy_change,
memo: None,
label: None,
@ -1152,7 +1152,7 @@ pub(crate) fn zip317_spend<T: ShieldedPoolTester>() {
// This first request will fail due to insufficient non-dust funds
let req = TransactionRequest::new(vec![Payment {
recipient_address: T::fvk_default_address(&dfvk),
recipient_address: T::fvk_default_address(&dfvk).to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(50000),
memo: None,
label: None,
@ -1177,7 +1177,7 @@ pub(crate) fn zip317_spend<T: ShieldedPoolTester>() {
// This request will succeed, spending a single dust input to pay the 10000
// ZAT fee in addition to the 41000 ZAT output to the recipient
let req = TransactionRequest::new(vec![Payment {
recipient_address: T::fvk_default_address(&dfvk),
recipient_address: T::fvk_default_address(&dfvk).to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(41000),
memo: None,
label: None,
@ -1480,7 +1480,7 @@ pub(crate) fn pool_crossing_required<P0: ShieldedPoolTester, P1: ShieldedPoolTes
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: p1_to,
recipient_address: p1_to.to_zcash_address(&st.network()),
amount: transfer_amount,
memo: None,
label: None,
@ -1571,7 +1571,7 @@ pub(crate) fn fully_funded_fully_private<P0: ShieldedPoolTester, P1: ShieldedPoo
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: p1_to,
recipient_address: p1_to.to_zcash_address(&st.network()),
amount: transfer_amount,
memo: None,
label: None,
@ -1662,7 +1662,7 @@ pub(crate) fn fully_funded_send_to_t<P0: ShieldedPoolTester, P1: ShieldedPoolTes
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: Address::Transparent(p1_to),
recipient_address: Address::Transparent(p1_to).to_zcash_address(&st.network()),
amount: transfer_amount,
memo: None,
label: None,
@ -1777,7 +1777,7 @@ pub(crate) fn multi_pool_checkpoint<P0: ShieldedPoolTester, P1: ShieldedPoolTest
// First, send funds just to P0
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
let p0_transfer = zip321::TransactionRequest::new(vec![Payment::without_memo(
P0::random_address(&mut st.rng),
P0::random_address(&mut st.rng).to_zcash_address(&st.network()),
transfer_amount,
)])
.unwrap();
@ -1802,8 +1802,14 @@ pub(crate) fn multi_pool_checkpoint<P0: ShieldedPoolTester, P1: ShieldedPoolTest
// In the next block, send funds to both P0 and P1
let both_transfer = zip321::TransactionRequest::new(vec![
Payment::without_memo(P0::random_address(&mut st.rng), transfer_amount),
Payment::without_memo(P1::random_address(&mut st.rng), transfer_amount),
Payment::without_memo(
P0::random_address(&mut st.rng).to_zcash_address(&st.network()),
transfer_amount,
),
Payment::without_memo(
P1::random_address(&mut st.rng).to_zcash_address(&st.network()),
transfer_amount,
),
])
.unwrap();
let res = st
@ -2110,7 +2116,7 @@ pub(crate) fn scan_cached_blocks_allows_blocks_out_of_order<T: ShieldedPoolTeste
// We can spend the received notes
let req = TransactionRequest::new(vec![Payment {
recipient_address: T::fvk_default_address(&dfvk),
recipient_address: T::fvk_default_address(&dfvk).to_zcash_address(&st.network()),
amount: NonNegativeAmount::const_from_u64(110_000),
memo: None,
label: None,

View File

@ -65,6 +65,7 @@ The entries below are relative to the `zcash_client_backend` crate as of
- `UnifiedAddressRequest`
- A new `orchard` feature flag has been added to make it possible to
build client code without `orchard` dependendencies.
- `zcash_keys::address::Address::to_zcash_address`
### Changed
- The following methods and enum variants have been placed behind an `orchard`

View File

@ -295,7 +295,7 @@ impl Address {
addr.convert_if_network(params.network_type()).ok()
}
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
pub fn to_zcash_address<P: consensus::Parameters>(&self, params: &P) -> ZcashAddress {
let net = params.network_type();
match self {
@ -311,7 +311,10 @@ impl Address {
},
Address::Unified(ua) => ua.to_address(net),
}
.to_string()
}
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
self.to_zcash_address(params).to_string()
}
pub fn has_receiver(&self, pool_type: PoolType) -> bool {