zcash_client_sqlite: Add a test that demonstrates the expected behavior of `get_memo` for empty-memo situations.
This commit is contained in:
parent
eade2acab9
commit
e718e76989
|
@ -371,7 +371,7 @@ pub(crate) fn put_received_note<T: ReceivedSaplingOutput>(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use std::num::NonZeroU32;
|
use std::{convert::Infallible, num::NonZeroU32};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
|
@ -383,8 +383,13 @@ pub(crate) mod tests {
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{BlockHeight, BranchId},
|
consensus::{BlockHeight, BranchId},
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
|
memo::Memo,
|
||||||
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
||||||
transaction::{components::Amount, fees::zip317::FeeRule as Zip317FeeRule, Transaction},
|
transaction::{
|
||||||
|
components::Amount,
|
||||||
|
fees::{fixed::FeeRule as FixedFeeRule, zip317::FeeRule as Zip317FeeRule},
|
||||||
|
Transaction,
|
||||||
|
},
|
||||||
zip32::{sapling::ExtendedSpendingKey, Scope},
|
zip32::{sapling::ExtendedSpendingKey, Scope},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -394,13 +399,17 @@ pub(crate) mod tests {
|
||||||
self,
|
self,
|
||||||
chain::scan_cached_blocks,
|
chain::scan_cached_blocks,
|
||||||
error::Error,
|
error::Error,
|
||||||
wallet::{create_spend_to_address, input_selection::GreedyInputSelector, spend},
|
wallet::{
|
||||||
|
create_proposed_transaction, create_spend_to_address,
|
||||||
|
input_selection::GreedyInputSelector, propose_transfer, spend,
|
||||||
|
},
|
||||||
WalletRead, WalletWrite,
|
WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
fees::{zip317, DustOutputPolicy},
|
decrypt_transaction,
|
||||||
|
fees::{fixed, zip317, DustOutputPolicy},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
zip321::{Payment, TransactionRequest},
|
zip321::{self, Payment, TransactionRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -413,21 +422,17 @@ pub(crate) mod tests {
|
||||||
get_balance, get_balance_at,
|
get_balance, get_balance_at,
|
||||||
init::{init_blocks_table, init_wallet_db},
|
init::{init_blocks_table, init_wallet_db},
|
||||||
},
|
},
|
||||||
AccountId, BlockDb, WalletDb,
|
AccountId, BlockDb, NoteId, WalletDb,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
zcash_client_backend::{
|
zcash_client_backend::{
|
||||||
data_api::wallet::shield_transparent_funds, fees::fixed,
|
data_api::wallet::shield_transparent_funds, wallet::WalletTransparentOutput,
|
||||||
wallet::WalletTransparentOutput,
|
|
||||||
},
|
},
|
||||||
zcash_primitives::{
|
zcash_primitives::{
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::components::{amount::NonNegativeAmount, OutPoint, TxOut},
|
||||||
components::{amount::NonNegativeAmount, OutPoint, TxOut},
|
|
||||||
fees::fixed::FeeRule as FixedFeeRule,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -440,6 +445,157 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_proposed_transfer() {
|
||||||
|
let cache_file = NamedTempFile::new().unwrap();
|
||||||
|
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
||||||
|
init_cache_database(&db_cache).unwrap();
|
||||||
|
|
||||||
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
|
// Add an account to the wallet
|
||||||
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
|
let (account, usk) = db_data.create_account(&seed).unwrap();
|
||||||
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
|
// Add funds to the wallet in a single note
|
||||||
|
let value = Amount::from_u64(60000).unwrap();
|
||||||
|
let (cb, _) = fake_compact_block(
|
||||||
|
sapling_activation_height(),
|
||||||
|
BlockHash([0; 32]),
|
||||||
|
&dfvk,
|
||||||
|
AddressType::DefaultExternal,
|
||||||
|
value,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
insert_into_cache(&db_cache, &cb);
|
||||||
|
scan_cached_blocks(
|
||||||
|
&tests::network(),
|
||||||
|
&db_cache,
|
||||||
|
&mut db_data,
|
||||||
|
sapling_activation_height(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verified balance matches total balance
|
||||||
|
let (_, anchor_height) = db_data
|
||||||
|
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
||||||
|
value
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
let to_extsk = ExtendedSpendingKey::master(&[]);
|
||||||
|
let to: RecipientAddress = to_extsk.default_address().1.into();
|
||||||
|
let request = zip321::TransactionRequest::new(vec![Payment {
|
||||||
|
recipient_address: to,
|
||||||
|
amount: Amount::from_u64(10000).unwrap(),
|
||||||
|
memo: None, // this should result in the creation of an empty memo
|
||||||
|
label: None,
|
||||||
|
message: None,
|
||||||
|
other_params: vec![],
|
||||||
|
}])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fee_rule = FixedFeeRule::standard();
|
||||||
|
let change_strategy = fixed::SingleOutputChangeStrategy::new(fee_rule);
|
||||||
|
let input_selector =
|
||||||
|
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
||||||
|
let proposal_result = propose_transfer::<_, _, _, Infallible>(
|
||||||
|
&mut db_data,
|
||||||
|
&tests::network(),
|
||||||
|
account,
|
||||||
|
input_selector,
|
||||||
|
request,
|
||||||
|
NonZeroU32::new(1).unwrap(),
|
||||||
|
);
|
||||||
|
assert_matches!(proposal_result, Ok(_));
|
||||||
|
|
||||||
|
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
||||||
|
let create_proposed_result = create_proposed_transaction::<_, _, Infallible, _>(
|
||||||
|
&mut db_data,
|
||||||
|
&tests::network(),
|
||||||
|
test_prover(),
|
||||||
|
&usk,
|
||||||
|
OvkPolicy::Sender,
|
||||||
|
proposal_result.unwrap(),
|
||||||
|
NonZeroU32::new(1).unwrap(),
|
||||||
|
Some(change_memo.clone().into()),
|
||||||
|
);
|
||||||
|
assert_matches!(create_proposed_result, Ok(_));
|
||||||
|
|
||||||
|
let sent_tx_id = create_proposed_result.unwrap();
|
||||||
|
|
||||||
|
// Verify that the sent transaction was stored and that we can decrypt the memos
|
||||||
|
let tx = db_data
|
||||||
|
.get_transaction(sent_tx_id)
|
||||||
|
.expect("Created transaction was stored.");
|
||||||
|
let ufvks = [(account, usk.to_unified_full_viewing_key())]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
let decrypted_outputs = decrypt_transaction(
|
||||||
|
&tests::network(),
|
||||||
|
sapling_activation_height() + 1,
|
||||||
|
&tx,
|
||||||
|
&ufvks,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut found_tx_change_memo = false;
|
||||||
|
let mut found_tx_empty_memo = false;
|
||||||
|
for output in decrypted_outputs {
|
||||||
|
if output.memo == change_memo.clone().into() {
|
||||||
|
found_tx_change_memo = true
|
||||||
|
}
|
||||||
|
if output.memo == Memo::Empty.into() {
|
||||||
|
found_tx_empty_memo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(found_tx_change_memo);
|
||||||
|
assert!(found_tx_empty_memo);
|
||||||
|
|
||||||
|
// Verify that the stored sent notes match what we're expecting
|
||||||
|
let mut stmt_sent_notes = db_data
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT id_note FROM sent_notes WHERE tx = ?")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sent_note_ids = stmt_sent_notes
|
||||||
|
.query(rusqlite::params![sent_tx_id])
|
||||||
|
.unwrap()
|
||||||
|
.mapped(|row| row.get::<_, i64>(0).map(NoteId::SentNoteId))
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_note_ids.len(), 2);
|
||||||
|
|
||||||
|
// The sent memo should be the empty memo for both the sent output and change
|
||||||
|
let mut found_sent_change_memo = false;
|
||||||
|
let mut found_sent_empty_memo = false;
|
||||||
|
for sent_note_id in sent_note_ids {
|
||||||
|
let memo = db_data.get_memo(sent_note_id).expect("Note id is valid");
|
||||||
|
if memo.as_ref() == Some(&change_memo) {
|
||||||
|
found_sent_change_memo = true
|
||||||
|
}
|
||||||
|
if memo == Some(Memo::Empty) {
|
||||||
|
found_sent_empty_memo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(found_sent_change_memo);
|
||||||
|
assert!(found_sent_empty_memo);
|
||||||
|
|
||||||
|
// Check that querying for a nonexistent sent note returns an error
|
||||||
|
assert_matches!(db_data.get_memo(NoteId::SentNoteId(12345)), Err(_));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_incorrect_usk() {
|
fn create_to_address_fails_on_incorrect_usk() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue