Skip to content
Open
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
21 changes: 12 additions & 9 deletions libtonode-tests/tests/unit_test_twins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
//! either side.

mod unit_test_twins {
use std::str::FromStr as _;

use pepper_sync::wallet::IronwoodNote;
use zcash_primitives::transaction::fees::zip317::{MARGINAL_FEE, MINIMUM_FEE};
use zcash_protocol::PoolType;
use zcash_protocol::consensus::{BlockHeight, COINBASE_MATURITY_BLOCKS};
use zcash_protocol::memo::Memo;
use zcash_protocol::value::Zatoshis;
use zingo_perspective::{LightClientPerspectiveExt as _, SentValueTransfer, ValueTransferKind};
use zingo_status::confirmation_status::ConfirmationStatus;
Expand Down Expand Up @@ -176,7 +179,7 @@ mod unit_test_twins {
utils::conversion::txid_from_hex_encoded_str(TEST_TXID).unwrap(),
),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -218,7 +221,7 @@ mod unit_test_twins {
utils::conversion::txid_from_hex_encoded_str(TEST_TXID).unwrap(),
),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand All @@ -228,7 +231,7 @@ mod unit_test_twins {
outgoing_sapling_notes: vec![OutgoingNoteSummary {
output_index: 0,
value: first_send_to_sapling,
memo: None,
memo: Memo::Empty,
recipient: "zregtestsapling1sa4rckrf4zs6ny3l3ljnezupacvxfnjjn90lpeaa4ddtjeyww2ypzqr3jxfsta3t8dn3jk8cm4f".to_string(),
recipient_unified_address: Some("uregtest183rtm3qhxxermx3nxwa706va0xnypt3td648tayetchlp28hue08vrcnwq02ryyk5rh3y0xhftay8a5ynjdg8kr3juq5x0d9ygd5ffht".to_string()),
account_id: AccountId::ZERO,
Expand All @@ -255,7 +258,7 @@ mod unit_test_twins {
99_925_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -344,7 +347,7 @@ mod unit_test_twins {
utils::conversion::txid_from_hex_encoded_str(TEST_TXID).unwrap(),
),
0,
Some("Second wave incoming".to_string()),
Memo::from_str("Second wave incoming").unwrap(),
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -388,7 +391,7 @@ mod unit_test_twins {
utils::conversion::txid_from_hex_encoded_str(TEST_TXID).unwrap(),
),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -427,7 +430,7 @@ TransactionSummary {
99_885_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand All @@ -437,7 +440,7 @@ TransactionSummary {
outgoing_sapling_notes: vec![OutgoingNoteSummary {
output_index: 0,
value: second_send_to_sapling,
memo: None,
memo: Memo::Empty,
recipient: "zregtestsapling1sa4rckrf4zs6ny3l3ljnezupacvxfnjjn90lpeaa4ddtjeyww2ypzqr3jxfsta3t8dn3jk8cm4f".to_string(),
recipient_unified_address: Some("uregtest183rtm3qhxxermx3nxwa706va0xnypt3td648tayetchlp28hue08vrcnwq02ryyk5rh3y0xhftay8a5ynjdg8kr3juq5x0d9ygd5ffht".to_string()),
account_id: AccountId::ZERO,
Expand Down Expand Up @@ -477,7 +480,7 @@ TransactionSummary {
930_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down
49 changes: 44 additions & 5 deletions zingo-perspective/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use std::collections::{HashMap, HashSet};

use zcash_protocol::PoolType;
use zcash_protocol::memo::Memo;

use zingolib::lightclient::LightClient;
use zingolib::wallet::LightWallet;
Expand All @@ -24,6 +25,41 @@ use crate::value_transfer::{
SelfSendValueTransfer, SentValueTransfer, ValueTransfer, ValueTransferKind, ValueTransfers,
};

/// The text-only memo policy this editorial layer applies: a text memo
/// shows as its string; empty and non-text memos are not shown. The
/// canonical summaries carry every memo losslessly typed.
fn text_memo(memo: &Memo) -> Option<String> {
if let Memo::Text(text) = memo {
Some(text.to_string())
} else {
None
}
}

/// All text memos on the transaction's wallet-received shielded notes,
/// in pool order ironwood, orchard, sapling: the received-memo view the
/// text-only policy exposes, driving the memo-to-self classification.
fn received_text_memos(transaction: &TransactionSummary) -> Vec<String> {
transaction
.shielded_notes_by_pool()
.into_iter()
.flat_map(|(notes, _)| notes.iter().filter_map(|note| text_memo(&note.memo)))
.collect()
}

/// The value of the wallet-received shielded notes whose memos the
/// text-only policy displays, so a memo-to-self row's value agrees with
/// the memos it shows; memo-less change is excluded with the rest.
fn received_memo_value(transaction: &TransactionSummary) -> u64 {
transaction
.shielded_notes_by_pool()
.into_iter()
.flat_map(|(notes, _)| notes.iter())
.filter(|note| text_memo(&note.memo).is_some())
.map(|note| note.value)
.sum()
}

/// Creates one value transfer of `kind` for each shielded pool the transaction
/// received notes into, newest pool first (ironwood, orchard, sapling).
fn shielded_pool_value_transfers(
Expand All @@ -41,7 +77,10 @@ fn shielded_pool_value_transfers(
notes.iter().map(|output| output.value).sum(),
None,
vec![pool],
notes.iter().filter_map(|note| note.memo.clone()).collect(),
notes
.iter()
.filter_map(|note| text_memo(&note.memo))
.collect(),
)
})
.collect()
Expand Down Expand Up @@ -136,7 +175,7 @@ fn create_send_value_transfers(
.sum();
let memos: Vec<String> = outgoing_notes_to_address
.iter()
.filter_map(|&(note, _)| note.memo.clone())
.filter_map(|&(note, _)| text_memo(&note.memo))
.collect();
let has_notes_in = |pool: PoolType| {
outgoing_notes_to_address
Expand Down Expand Up @@ -238,12 +277,12 @@ impl LightWalletPerspectiveExt for LightWallet {
value_transfers.append(&mut create_send_value_transfers(self, &transaction)?);

// create 1 memo-to-self if any number of memos are received in the sending transaction
let memos = transaction.received_memos();
let memos = received_text_memos(&transaction);
if !memos.is_empty() {
value_transfers.push(self_send_value_transfer(
&transaction,
SelfSendValueTransfer::MemoToSelf,
transaction.received_memo_value(),
received_memo_value(&transaction),
memos,
));
}
Expand All @@ -264,7 +303,7 @@ impl LightWalletPerspectiveExt for LightWallet {
// any number of memos, otherwise 1 basic send-to-self so every transaction
// creates at least 1 value transfer.
// (deshield and other pool-movement kinds may join this list later.)
let memos = transaction.received_memos();
let memos = received_text_memos(&transaction);
let self_send_kind = if transaction.is_orchard_to_ironwood_migration() {
SelfSendValueTransfer::Migration
} else if !memos.is_empty() {
Expand Down
23 changes: 22 additions & 1 deletion zingo-perspective/tests/derivation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,28 @@ use zingolib::config::ChainType;
use zingolib::mocks::orchard_note::OrchardCryptoNoteBuilder;
use zingolib::wallet::keys::unified::ReceiverSelection;

use common::{received_texts, regtest_wallet, sent};
use common::{received, received_texts, regtest_wallet, sent};

/// The canonical summaries carry every memo losslessly typed; the
/// text-only policy is applied by this crate, never at construction.
/// An arbitrary-bytes memo is present and typed on the transaction
/// summary, while the derived value transfer shows no memo for it.
#[tokio::test]
async fn arbitrary_memo_is_carried_typed_and_hidden_editorially() {
let arbitrary = Memo::Arbitrary(Box::new([7u8; 511]));
let mut wallet = regtest_wallet(seeds::HOSPITAL_MUSEUM_SEED);
wallet.wallet_transactions.insert(
TxId::from_bytes([1; 32]),
received(1, 10, std::slice::from_ref(&arbitrary)),
);

let summaries = wallet.transaction_summaries(false).await.unwrap();
assert_eq!(summaries.0[0].ironwood_notes[0].memo, arbitrary);

let value_transfers = wallet.value_transfers(true).await.unwrap();
assert_eq!(value_transfers.len(), 1);
assert!(value_transfers[0].memos.is_empty());
}

/// Migrated from libtonode `fast::filter_empty_messages`.
#[tokio::test]
Expand Down
21 changes: 12 additions & 9 deletions zingolib/src/lightclient/mock_chain_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
//! live versions are never removed. They eventually move to a gated
//! "pre-migration" mod once side-by-side equivalence is documented).

use std::str::FromStr as _;

use pepper_sync::wallet::IronwoodNote;
use zcash_protocol::PoolType;
use zcash_protocol::ShieldedPool;
use zcash_protocol::memo::Memo;

use crate::check_client_balances;
use crate::testutils::lightclient::{from_inputs, get_base_address};
Expand Down Expand Up @@ -274,7 +277,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
recipient_initial_funds,
SpendStatus::Spent(placeholder_txid),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -309,7 +312,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
99_960_000,
SpendStatus::TransmittedSpent(placeholder_txid),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand All @@ -319,7 +322,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
outgoing_sapling_notes: vec![OutgoingNoteSummary {
output_index: 0,
value: first_send_to_sapling,
memo: None,
memo: Memo::Empty,
recipient: "zregtestsapling1sa4rckrf4zs6ny3l3ljnezupacvxfnjjn90lpeaa4ddtjeyww2ypzqr3jxfsta3t8dn3jk8cm4f".to_string(),
recipient_unified_address: Some("uregtest183rtm3qhxxermx3nxwa706va0xnypt3td648tayetchlp28hue08vrcnwq02ryyk5rh3y0xhftay8a5ynjdg8kr3juq5x0d9ygd5ffht".to_string()),
account_id: zip32::AccountId::ZERO,
Expand All @@ -345,7 +348,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
99_925_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -432,7 +435,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
recipient_second_funding,
SpendStatus::Spent(placeholder_txid),
0,
Some("Second wave incoming".to_string()),
Memo::from_str("Second wave incoming").unwrap(),
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down Expand Up @@ -475,7 +478,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
965_000,
SpendStatus::Spent(placeholder_txid),
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand All @@ -499,7 +502,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
99_885_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand All @@ -509,7 +512,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
outgoing_sapling_notes: vec![OutgoingNoteSummary {
output_index: 0,
value: second_send_to_sapling,
memo: None,
memo: Memo::Empty,
recipient: "zregtestsapling1sa4rckrf4zs6ny3l3ljnezupacvxfnjjn90lpeaa4ddtjeyww2ypzqr3jxfsta3t8dn3jk8cm4f".to_string(),
recipient_unified_address: Some("uregtest183rtm3qhxxermx3nxwa706va0xnypt3td648tayetchlp28hue08vrcnwq02ryyk5rh3y0xhftay8a5ynjdg8kr3juq5x0d9ygd5ffht".to_string()),
account_id: zip32::AccountId::ZERO,
Expand Down Expand Up @@ -542,7 +545,7 @@ async fn send_to_transparent_and_sapling_maintain_balance() {
930_000,
SpendStatus::Unspent,
0,
None,
Memo::Empty,
)],
orchard_notes: vec![],
sapling_notes: vec![],
Expand Down
Loading
Loading