Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework WalletTest::get_confirmed_sends trait method #1551

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ use incrementalmerkletree::{frontier::Frontier, Retention};
use nonempty::NonEmpty;
use secrecy::SecretVec;
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use zcash_keys::address::Address;
use zip32::fingerprint::SeedFingerprint;

use self::{
Expand Down Expand Up @@ -1233,11 +1234,12 @@ pub trait WalletTest: InputSource + WalletRead {
_protocol: ShieldedProtocol,
) -> Result<Vec<NoteId>, <Self as WalletRead>::Error>;

/// Returns the outputs for a transaction sent by the wallet.
#[allow(clippy::type_complexity)]
fn get_confirmed_sends(
fn get_sent_outputs(
&self,
txid: &TxId,
) -> Result<Vec<(u64, Option<String>, Option<String>, Option<u32>)>, <Self as WalletRead>::Error>;
) -> Result<Vec<OutputOfSentTx>, <Self as WalletRead>::Error>;

#[allow(clippy::type_complexity)]
fn get_checkpoint_history(
Expand Down Expand Up @@ -1270,6 +1272,37 @@ pub trait WalletTest: InputSource + WalletRead {
) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, <Self as InputSource>::Error>;
}

/// The output of a transaction sent by the wallet.
///
/// This type is opaque, and exists for use by tests defined in this crate.
#[cfg(any(test, feature = "test-dependencies"))]
#[derive(Clone, Debug)]
pub struct OutputOfSentTx {
value: NonNegativeAmount,
external_recipient: Option<Address>,
ephemeral_address: Option<(Address, u32)>,
str4d marked this conversation as resolved.
Show resolved Hide resolved
}

#[cfg(any(test, feature = "test-dependencies"))]
impl OutputOfSentTx {
/// Constructs an output from its test-relevant parts.
///
/// If the output is to an ephemeral address, `ephemeral_address` should contain the
/// address along with the `address_index` it was derived from under the BIP 32 path
/// `m/44'/<coin_type>'/<account>'/2/<address_index>`.
pub fn from_parts(
value: NonNegativeAmount,
external_recipient: Option<Address>,
ephemeral_address: Option<(Address, u32)>,
) -> Self {
Self {
value,
external_recipient,
ephemeral_address,
}
}
}

/// The relevance of a seed to a given wallet.
///
/// This is the return type for [`WalletRead::seed_relevance_to_derived_accounts`].
Expand Down
36 changes: 19 additions & 17 deletions zcash_client_backend/src/data_api/testing/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
DSF: DataStoreFactory,
<DSF as DataStoreFactory>::AccountId: std::fmt::Debug,
{
use crate::data_api::GAP_LIMIT;
use crate::data_api::{OutputOfSentTx, GAP_LIMIT};

let mut st = TestBuilder::new()
.with_data_store_factory(ds_factory)
Expand Down Expand Up @@ -409,7 +409,7 @@
// Check that there are sent outputs with the correct values.
let confirmed_sent: Vec<Vec<_>> = txids
.iter()
.map(|sent_txid| st.wallet().get_confirmed_sends(sent_txid).unwrap())
.map(|sent_txid| st.wallet().get_sent_outputs(sent_txid).unwrap())
.collect();

// Verify that a status request has been generated for the second transaction of
Expand All @@ -420,21 +420,24 @@
assert!(expected_step0_change < expected_ephemeral);
assert_eq!(confirmed_sent.len(), 2);
assert_eq!(confirmed_sent[0].len(), 2);
assert_eq!(confirmed_sent[0][0].value, expected_step0_change);
let OutputOfSentTx {
value: ephemeral_v,

Check warning on line 425 in zcash_client_backend/src/data_api/testing/pool.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/testing/pool.rs#L425

Added line #L425 was not covered by tests
external_recipient: to_addr,
ephemeral_address,
} = confirmed_sent[0][1].clone();
assert_eq!(ephemeral_v, expected_ephemeral);
assert!(to_addr.is_some());
assert_eq!(
confirmed_sent[0][0].0,
u64::try_from(expected_step0_change).unwrap()
ephemeral_address,
to_addr.map(|addr| (addr, expected_index)),
);
let (ephemeral_v, to_addr, ephemeral_addr, index) = confirmed_sent[0][1].clone();
assert_eq!(ephemeral_v, u64::try_from(expected_ephemeral).unwrap());
assert!(to_addr.is_some());
assert_eq!(ephemeral_addr, to_addr);
assert_eq!(index, Some(expected_index));

assert_eq!(confirmed_sent[1].len(), 1);
assert_matches!(
confirmed_sent[1][0].clone(),
(sent_v, sent_to_addr, None, None)
if sent_v == u64::try_from(transfer_amount).unwrap() && sent_to_addr == Some(tex_addr.encode(st.network())));
&confirmed_sent[1][0],
OutputOfSentTx { value: sent_v, external_recipient: sent_to_addr, ephemeral_address: None }
if sent_v == &transfer_amount && sent_to_addr == &Some(tex_addr));

// Check that the transaction history matches what we expect.
let tx_history = st.wallet().get_tx_history().unwrap();
Expand Down Expand Up @@ -466,7 +469,7 @@
-ZatBalance::from(expected_ephemeral),
);

(ephemeral_addr.unwrap(), txids)
(ephemeral_address.unwrap().0, txids)
};

// Each transfer should use a different ephemeral address.
Expand All @@ -476,9 +479,8 @@

let height = add_funds(&mut st, value);

let ephemeral_taddr = Address::decode(st.network(), &ephemeral0).expect("valid address");
assert_matches!(
ephemeral_taddr,
ephemeral0,

Check warning on line 483 in zcash_client_backend/src/data_api/testing/pool.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/testing/pool.rs#L483

Added line #L483 was not covered by tests
Address::Transparent(TransparentAddress::PublicKeyHash(_))
);

Expand All @@ -488,7 +490,7 @@
account_id,
StandardFeeRule::Zip317,
NonZeroU32::new(1).unwrap(),
&ephemeral_taddr,
&ephemeral0,
transfer_amount,
None,
None,
Expand All @@ -503,7 +505,7 @@
);
assert_matches!(
&create_proposed_result,
Err(Error::PaysEphemeralTransparentAddress(address_str)) if address_str == &ephemeral0);
Err(Error::PaysEphemeralTransparentAddress(address_str)) if address_str == &ephemeral0.encode(st.network()));

// Simulate another wallet sending to an ephemeral address with an index
// within the current gap limit. The `PaysEphemeralTransparentAddress` error
Expand Down
30 changes: 22 additions & 8 deletions zcash_client_sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@
};

#[cfg(any(test, feature = "test-dependencies"))]
use zcash_client_backend::data_api::{testing::TransactionSummary, WalletTest};
use {
zcash_client_backend::data_api::{testing::TransactionSummary, OutputOfSentTx, WalletTest},
zcash_keys::address::Address,
};

/// `maybe-rayon` doesn't provide this as a fallback, so we have to.
#[cfg(not(feature = "multicore"))]
Expand Down Expand Up @@ -654,11 +657,10 @@
Ok(note_ids)
}

fn get_confirmed_sends(
fn get_sent_outputs(
&self,
txid: &TxId,
) -> Result<Vec<(u64, Option<String>, Option<String>, Option<u32>)>, <Self as WalletRead>::Error>
{
) -> Result<Vec<OutputOfSentTx>, <Self as WalletRead>::Error> {
let mut stmt_sent = self
.conn.borrow()
.prepare(
Expand All @@ -672,12 +674,24 @@

let sends = stmt_sent
.query_map(rusqlite::params![txid.as_ref()], |row| {
let v: u32 = row.get(0)?;
let to_address: Option<String> = row.get(1)?;
let ephemeral_address: Option<String> = row.get(2)?;
let v = row.get(0)?;
let to_address = row
.get::<_, Option<String>>(1)?
.and_then(|s| Address::decode(&self.params, &s));
let ephemeral_address = row
.get::<_, Option<String>>(2)?
.and_then(|s| Address::decode(&self.params, &s));
let address_index: Option<u32> = row.get(3)?;
Ok((u64::from(v), to_address, ephemeral_address, address_index))
Ok((v, to_address, ephemeral_address.zip(address_index)))

Check warning on line 685 in zcash_client_sqlite/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/lib.rs#L685

Added line #L685 was not covered by tests
})?
.map(|res| {
let (amount, external_recipient, ephemeral_address) = res?;
Ok::<_, <Self as WalletRead>::Error>(OutputOfSentTx::from_parts(
NonNegativeAmount::from_u64(amount)?,
external_recipient,
ephemeral_address,
))
})
.collect::<Result<_, _>>()?;

Ok(sends)
Expand Down
20 changes: 20 additions & 0 deletions zcash_client_sqlite/src/wallet/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,26 @@ CREATE TABLE transparent_spend_map (
/// this table, distinguished by the `output_pool` column. The information we want to
/// record for sent outputs is the same across all pools, whereas for received outputs we
/// want to cache pool-specific data.
///
/// ### Columns
/// - `(tx, output_pool, output_index)` collectively identify a transaction output.
/// - `from_account_id`: the ID of the account that created the transaction.
/// - On recover-from-seed or when scanning by UFVK, this will be either the account
/// that decrypted the output, or one of the accounts that funded the transaction.
/// - `to_address`: the address of the external recipient of this output, or `NULL` if the
/// output was received by the wallet.
/// - `to_account_id`: the ID of the account that received this output, or `NULL` if the
/// output was for an external recipient.
/// - `value`: the value of the output in zatoshis.
/// - `memo`: the memo bytes associated with this output, if known.
/// - This is always `NULL` for transparent outputs.
/// - This will be set for all shielded outputs of transactions created by the wallet.
/// - On recover-from-seed or when scanning by UFVK, this will only be set for shielded
/// outputs after post-scanning transaction enhancement. For shielded notes sent to
/// external recipients, the transaction needs to have been created with an
/// [`OvkPolicy`] using a known OVK.
///
/// [`OvkPolicy`]: zcash_client_backend::wallet::OvkPolicy
pub(super) const TABLE_SENT_NOTES: &str = r#"
CREATE TABLE "sent_notes" (
id INTEGER PRIMARY KEY,
Expand Down
Loading