diff --git a/darkside-tests/tests/advanced_reorg_tests.rs b/darkside-tests/tests/advanced_reorg_tests.rs index 5e215fbf74..aa2af36e83 100644 --- a/darkside-tests/tests/advanced_reorg_tests.rs +++ b/darkside-tests/tests/advanced_reorg_tests.rs @@ -1048,7 +1048,7 @@ async fn reorg_changes_outgoing_tx_index() { // // stage empty blocks from height 205 to cause a Reorg - _ = connector.stage_blocks_create(sent_tx_height, 20, 1).await; + _ = connector.stage_blocks_create(sent_tx_height, 20, 102).await; _ = connector .stage_transactions_stream( @@ -1060,7 +1060,7 @@ async fn reorg_changes_outgoing_tx_index() { ) .await; - _ = connector.apply_staged(211).await; + _ = connector.apply_staged(312).await; let reorg_sync_result = light_client.do_sync(true).await; diff --git a/darkside-tests/tests/tests.rs b/darkside-tests/tests/tests.rs index c97b16dc5b..47b1ab706b 100644 --- a/darkside-tests/tests/tests.rs +++ b/darkside-tests/tests/tests.rs @@ -181,8 +181,8 @@ async fn sent_transaction_reorged_into_mempool() { .await .unwrap(); let connector = DarksideConnector(server_id.clone()); - connector.stage_blocks_create(4, 1, 0).await.unwrap(); - connector.apply_staged(4).await.unwrap(); + connector.stage_blocks_create(4, 102, 0).await.unwrap(); + connector.apply_staged(105).await.unwrap(); sleep(std::time::Duration::from_secs(1)).await; recipient.do_sync(false).await.unwrap(); diff --git a/libtonode-tests/tests/concrete.rs b/libtonode-tests/tests/concrete.rs index 93677ead9d..d8442662ff 100644 --- a/libtonode-tests/tests/concrete.rs +++ b/libtonode-tests/tests/concrete.rs @@ -1084,10 +1084,10 @@ mod slow { scenarios::faucet_recipient_default().await; // 2. Get an incoming transaction to a t address - let taddr = get_base_address_macro!(recipient, "transparent"); + let recipient_taddr = get_base_address_macro!(recipient, "transparent"); let value = 100_000; - from_inputs::quick_send(&faucet, vec![(taddr.as_str(), value, None)]) + from_inputs::quick_send(&faucet, vec![(recipient_taddr.as_str(), value, None)]) .await .unwrap(); @@ -1099,7 +1099,10 @@ mod slow { // 3. Test the list let list = recipient.do_list_transactions().await; assert_eq!(list[0]["block_height"].as_u64().unwrap(), 4); - assert_eq!(list[0]["address"], taddr); + assert_eq!( + recipient.do_addresses().await[0]["receivers"]["transparent"].to_string(), + recipient_taddr + ); assert_eq!(list[0]["amount"].as_u64().unwrap(), value); // 4. We can't spend the funds, as they're transparent. We need to shield first @@ -3228,69 +3231,6 @@ mod slow { serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() ); } - #[tokio::test] - async fn dont_write_pending() { - let regtest_network = RegtestNetwork::all_upgrades_active(); - let (regtest_manager, _cph, faucet, recipient) = scenarios::faucet_recipient( - PoolType::Shielded(ShieldedProtocol::Orchard), - regtest_network, - ) - .await; - from_inputs::quick_send( - &faucet, - vec![( - &get_base_address_macro!(recipient, "unified"), - 100_000, - Some("funding to be received by the recipient"), - )], - ) - .await - .unwrap(); - - zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &recipient, 2) - .await - .unwrap(); - let recipient_balance = recipient.do_balance().await; - assert_eq!( - recipient_balance, - PoolBalances { - sapling_balance: Some(0), - verified_sapling_balance: Some(0), - spendable_sapling_balance: Some(0), - unverified_sapling_balance: Some(0), - orchard_balance: Some(100000), - verified_orchard_balance: Some(100000), - spendable_orchard_balance: Some(100000), - unverified_orchard_balance: Some(0), - transparent_balance: Some(0) - } - ); - from_inputs::quick_send( - &recipient, - vec![( - &get_base_address_macro!(faucet, "unified"), - 25_000, - Some("an pending transaction, that shall not be synced"), - )], - ) - .await - .unwrap(); - let recipient_balance = recipient.do_balance().await; - - dbg!(&recipient_balance.unverified_orchard_balance); - assert_eq!( - recipient_balance.unverified_orchard_balance.unwrap(), - 65_000 - ); - - let loaded_client = - zingolib::testutils::lightclient::new_client_from_save_buffer(&recipient) - .await - .unwrap(); - let loaded_balance = loaded_client.do_balance().await; - assert_eq!(loaded_balance.unverified_orchard_balance, Some(0),); - check_client_balances!(loaded_client, o: 100_000 s: 0 t: 0 ); - } #[tokio::test] async fn by_address_finsight() { diff --git a/libtonode-tests/tests/wallet.rs b/libtonode-tests/tests/wallet.rs index 0ce0669e43..c5534755d2 100644 --- a/libtonode-tests/tests/wallet.rs +++ b/libtonode-tests/tests/wallet.rs @@ -101,37 +101,40 @@ mod load_wallet { ); let expected_pre_sync_transactions = r#"[ { + "outgoing_metadata": [], + "amount": 100000, + "memo": "null, null", "block_height": 3, "pending": false, "datetime": 1692212261, "position": 0, "txid": "7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e", - "amount": 100000, "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8", - "memo": null + "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" }, { + "outgoing_metadata": [], + "amount": 50000, + "memo": "null, null", "block_height": 8, "pending": false, "datetime": 1692212266, "position": 0, "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", - "amount": 50000, "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8", - "memo": null + "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" }, { + "outgoing_metadata": [], + "amount": 30000, + "memo": "null, null", "block_height": 9, "pending": false, "datetime": 1692212299, "position": 0, "txid": "0a014017add7dc9eb57ada3e70f905c9dce610ef055e135b03f4907dd5dc99a4", - "amount": 30000, "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8", - "memo": null + "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" } ]"#; assert_eq!( @@ -141,26 +144,28 @@ mod load_wallet { recipient.do_sync(false).await.unwrap(); let expected_post_sync_transactions = r#"[ { + "outgoing_metadata": [], + "amount": 100000, + "memo": "null, null", "block_height": 3, "pending": false, "datetime": 1692212261, "position": 0, "txid": "7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e", - "amount": 100000, "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8", - "memo": null + "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" }, { + "outgoing_metadata": [], + "amount": 50000, + "memo": "null, null", "block_height": 8, "pending": false, "datetime": 1692212266, "position": 0, "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", - "amount": 50000, "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8", - "memo": null + "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" } ]"#; assert_eq!( diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 08d4d36c08..7e19136f88 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.81" +channel = "1.82" components = [ "clippy", "rustfmt" ] diff --git a/zingo-memo/src/lib.rs b/zingo-memo/src/lib.rs index b7bf6cec76..8502f35373 100644 --- a/zingo-memo/src/lib.rs +++ b/zingo-memo/src/lib.rs @@ -31,7 +31,7 @@ pub enum ParsedMemo { /// the list of unified addresses uas: Vec, /// The ephemeral address indexes - ephemeral_address_indexes: Vec, + rejection_address_indexes: Vec, }, } @@ -99,7 +99,7 @@ pub fn parse_zingo_memo(memo: [u8; 511]) -> io::Result { }), 1 => Ok(ParsedMemo::Version1 { uas: Vector::read(&mut reader, |r| read_unified_address_from_raw_encoding(r))?, - ephemeral_address_indexes: Vector::read(&mut reader, |r| CompactSize::read_t(r))?, + rejection_address_indexes: Vector::read(&mut reader, |r| CompactSize::read_t(r))?, }), _ => Err(io::Error::new( io::ErrorKind::InvalidData, diff --git a/zingo-netutils/src/lib.rs b/zingo-netutils/src/lib.rs index 557454213d..da616aeef5 100644 --- a/zingo-netutils/src/lib.rs +++ b/zingo-netutils/src/lib.rs @@ -10,7 +10,6 @@ use client::client_from_connector; use http::{uri::PathAndQuery, Uri}; use http_body_util::combinators::UnsyncBoxBody; use hyper_util::client::legacy::connect::HttpConnector; -use thiserror::Error; use tokio_rustls::rustls::pki_types::{Der, TrustAnchor}; use tokio_rustls::rustls::{ClientConfig, RootCertStore}; use tonic::Status; @@ -26,7 +25,7 @@ pub type UnderlyingService = BoxCloneService< >; #[allow(missing_docs)] // error types document themselves -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum GetClientError { #[error("bad uri: invalid scheme")] InvalidScheme, diff --git a/zingocli/src/lib.rs b/zingocli/src/lib.rs index 33c37c86b6..39d5eec852 100644 --- a/zingocli/src/lib.rs +++ b/zingocli/src/lib.rs @@ -84,28 +84,30 @@ fn parse_uri(s: &str) -> Result { /// currently this is just a whitespace delimited string of 24 words. I am /// poking around to use the actual BIP39 parser (presumably from librustzcash). fn parse_seed(s: &str) -> Result { - if let Ok(s) = s.parse::() { - let count = s.split_whitespace().count(); - if count == 24 { - Ok(s) - } else { - Err(format!("Expected 24 words, but received: {}.", count)) + match s.parse::() { + Ok(s) => { + let count = s.split_whitespace().count(); + if count == 24 { + Ok(s) + } else { + Err(format!("Expected 24 words, but received: {}.", count)) + } } - } else { - Err("Unexpected failure to parse String!!".to_string()) + Err(_) => Err("Unexpected failure to parse String!!".to_string()), } } /// Parse encoded UFVK to String and check for whitespaces fn parse_ufvk(s: &str) -> Result { - if let Ok(s) = s.parse::() { - let count = s.split_whitespace().count(); - if count == 1 { - Ok(s) - } else { - Err("Encoded UFVK should not contain whitespace!".to_string()) + match s.parse::() { + Ok(s) => { + let count = s.split_whitespace().count(); + if count == 1 { + Ok(s) + } else { + Err("Encoded UFVK should not contain whitespace!".to_string()) + } } - } else { - Err("Unexpected failure to parse String!!".to_string()) + Err(_) => Err("Unexpected failure to parse String!!".to_string()), } } #[cfg(target_os = "linux")] diff --git a/zingolib/src/blaze/fetch_taddr_transactions.rs b/zingolib/src/blaze/fetch_taddr_transactions.rs index 08e05218ac..9c0fada94e 100644 --- a/zingolib/src/blaze/fetch_taddr_transactions.rs +++ b/zingolib/src/blaze/fetch_taddr_transactions.rs @@ -50,7 +50,7 @@ impl FetchTaddrTransactions { .iter() .filter_map(|ua| ua.transparent()) .chain( - wc.transparent_child_ephemeral_addresses() + wc.get_rejection_addresses() .iter() .map(|(taddr, _metadata)| taddr), ) diff --git a/zingolib/src/lightclient/describe.rs b/zingolib/src/lightclient/describe.rs index 8c7901f348..09e7feb10e 100644 --- a/zingolib/src/lightclient/describe.rs +++ b/zingolib/src/lightclient/describe.rs @@ -508,7 +508,7 @@ impl LightClient { create_send_value_transfers(&mut value_transfers, tx); } TransactionKind::Received => { - // create 1 received value tansfer for each pool recieved to + // create 1 received value tansfer for each pool received to if !tx.orchard_notes().is_empty() { let value: u64 = tx.orchard_notes().iter().map(|output| output.value()).sum(); diff --git a/zingolib/src/lightclient/send.rs b/zingolib/src/lightclient/send.rs index 366f1b2c79..22262497b5 100644 --- a/zingolib/src/lightclient/send.rs +++ b/zingolib/src/lightclient/send.rs @@ -36,7 +36,6 @@ pub mod send_with_proposal { use zcash_primitives::transaction::{Transaction, TxId}; - use thiserror::Error; use zingo_status::confirmation_status::ConfirmationStatus; use crate::lightclient::LightClient; @@ -44,7 +43,7 @@ pub mod send_with_proposal { use crate::wallet::propose::{ProposeSendError, ProposeShieldError}; #[allow(missing_docs)] // error types document themselves - #[derive(Clone, Debug, Error)] + #[derive(Clone, Debug, thiserror::Error)] pub enum TransactionCacheError { #[error("No witness trees. This is viewkey watch, not spendkey wallet.")] NoSpendCapability, @@ -55,7 +54,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Clone, Debug, Error)] + #[derive(Clone, Debug, thiserror::Error)] pub enum BroadcastCachedTransactionsError { #[error("Cant broadcast: {0:?}")] Cache(#[from] TransactionCacheError), @@ -66,7 +65,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Debug, Error)] + #[derive(Debug, thiserror::Error)] pub enum RecordCachedTransactionsError { #[error("Cant record: {0:?}")] Cache(#[from] TransactionCacheError), @@ -77,7 +76,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Debug, Error)] + #[derive(Debug, thiserror::Error)] pub enum CompleteAndBroadcastError { #[error("The transaction could not be calculated: {0:?}")] BuildTransaction(#[from] crate::wallet::send::BuildTransactionError), @@ -90,7 +89,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Debug, Error)] + #[derive(Debug, thiserror::Error)] pub enum CompleteAndBroadcastStoredProposalError { #[error("No proposal. Call do_propose first.")] NoStoredProposal, @@ -99,7 +98,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Debug, Error)] + #[derive(Debug, thiserror::Error)] pub enum QuickSendError { #[error("propose send {0:?}")] ProposeSend(#[from] ProposeSendError), @@ -108,7 +107,7 @@ pub mod send_with_proposal { } #[allow(missing_docs)] // error types document themselves - #[derive(Debug, Error)] + #[derive(Debug, thiserror::Error)] pub enum QuickShieldError { #[error("propose shield {0:?}")] Propose(#[from] ProposeShieldError), diff --git a/zingolib/src/testutils/chain_generics/with_assertions.rs b/zingolib/src/testutils/chain_generics/with_assertions.rs index c3f0265fcf..11525e47f9 100644 --- a/zingolib/src/testutils/chain_generics/with_assertions.rs +++ b/zingolib/src/testutils/chain_generics/with_assertions.rs @@ -10,7 +10,7 @@ use crate::testutils::{ }; use zingo_status::confirmation_status::ConfirmationStatus; -/// sends to any combo of recipient clients checks that each recipient also recieved the expected balances +/// sends to any combo of recipient clients checks that each recipient also received the expected balances /// test-only generic /// NOTICE this function bumps the chain and syncs the client /// only compatible with zip317 diff --git a/zingolib/src/utils/conversion.rs b/zingolib/src/utils/conversion.rs index 42882f68d5..811e6cf0b0 100644 --- a/zingolib/src/utils/conversion.rs +++ b/zingolib/src/utils/conversion.rs @@ -1,14 +1,12 @@ //! Conversion specific utilities -use thiserror::Error; - use zcash_address::ZcashAddress; use zcash_primitives::transaction::{components::amount::NonNegativeAmount, TxId}; use super::error::ConversionError; #[allow(missing_docs)] // error types document themselves -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum TxIdFromHexEncodedStrError { #[error("{0}")] Decode(hex::FromHexError), diff --git a/zingolib/src/wallet.rs b/zingolib/src/wallet.rs index 76133036cb..983bf70832 100644 --- a/zingolib/src/wallet.rs +++ b/zingolib/src/wallet.rs @@ -383,8 +383,8 @@ impl LightWallet { let transaction_metadata_set = if wc.unified_key_store().is_spending_key() { Arc::new(RwLock::new(TxMap::new_with_witness_trees( wc.transparent_child_addresses().clone(), - wc.transparent_child_ephemeral_addresses().clone(), - wc.ephemeral_ivk().map_err(|e| { + wc.get_rejection_addresses().clone(), + wc.rejection_ivk().map_err(|e| { Error::new( ErrorKind::InvalidData, format!("Error with transparent key: {e}"), diff --git a/zingolib/src/wallet/data.rs b/zingolib/src/wallet/data.rs index 41af97d7a3..01b5c1781d 100644 --- a/zingolib/src/wallet/data.rs +++ b/zingolib/src/wallet/data.rs @@ -790,8 +790,8 @@ pub mod summaries { Shield, /// The recipient is the creator and is receiving at least 1 note with a TEXT memo MemoToSelf, - /// The recipient is an ephemeral 320 address - Ephemeral320, + /// The recipient is an "ephemeral" 320 address + Rejection, } impl std::fmt::Display for ValueTransferKind { @@ -804,7 +804,7 @@ pub mod summaries { SelfSendValueTransfer::Basic => write!(f, "basic"), SelfSendValueTransfer::Shield => write!(f, "shield"), SelfSendValueTransfer::MemoToSelf => write!(f, "memo-to-self"), - SelfSendValueTransfer::Ephemeral320 => write!(f, "ephemeral-320-tex"), + SelfSendValueTransfer::Rejection => write!(f, "rejection"), }, }, } @@ -1994,15 +1994,15 @@ impl WalletZecPriceInfo { } } -/// Generate a new ephemeral transparent address, +/// Generate a new rejection address, /// for use in a send to a TEX address. -pub fn new_persistent_ephemeral_address( - transparent_child_ephemeral_addresses: &append_only_vec::AppendOnlyVec<( +pub fn new_rejection_address( + rejection_addresses: &append_only_vec::AppendOnlyVec<( TransparentAddress, TransparentAddressMetadata, )>, - transparent_ephemeral_ivk: &zcash_primitives::legacy::keys::EphemeralIvk, + rejection_ivk: &zcash_primitives::legacy::keys::EphemeralIvk, ) -> Result< ( zcash_primitives::legacy::TransparentAddress, @@ -2010,12 +2010,13 @@ pub fn new_persistent_ephemeral_address( ), super::error::KeyError, > { - let (ephemeral_address, metadata) = super::keys::unified::WalletCapability::ephemeral_address( - transparent_ephemeral_ivk, - transparent_child_ephemeral_addresses.len() as u32, - )?; - transparent_child_ephemeral_addresses.push((ephemeral_address, metadata.clone())); - Ok((ephemeral_address, metadata)) + let (rejection_address, metadata) = + super::keys::unified::WalletCapability::get_rejection_address_by_index( + rejection_ivk, + rejection_addresses.len() as u32, + )?; + rejection_addresses.push((rejection_address, metadata.clone())); + Ok((rejection_address, metadata)) } #[test] diff --git a/zingolib/src/wallet/disk.rs b/zingolib/src/wallet/disk.rs index 717c52ed69..83ec456bcd 100644 --- a/zingolib/src/wallet/disk.rs +++ b/zingolib/src/wallet/disk.rs @@ -38,7 +38,7 @@ use super::{ impl LightWallet { /// Changes in version 30: - /// - New WalletCapability version (v4) which implements read/write for ephemeral addresses + /// - New WalletCapability version (v4) which implements read/write for rejection addresses pub const fn serialized_version() -> u64 { 30 } @@ -96,9 +96,8 @@ impl LightWallet { }; Vector::write(&mut writer, &seed_bytes, |w, byte| w.write_u8(*byte))?; - match &self.mnemonic { - Some(m) => writer.write_u32::(m.1)?, - None => (), + if let Some(m) = &self.mnemonic { + writer.write_u32::(m.1)?; } Ok(()) diff --git a/zingolib/src/wallet/error.rs b/zingolib/src/wallet/error.rs index 7e30a153f6..151d16017e 100644 --- a/zingolib/src/wallet/error.rs +++ b/zingolib/src/wallet/error.rs @@ -1,12 +1,11 @@ //! Errors for [`crate::wallet`] and sub-modules -use thiserror::Error; use zcash_keys::keys::DerivationError; use crate::wallet::data::OutgoingTxData; /// Errors associated with transaction fee calculation -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum FeeError { /// Sapling notes spent in a transaction not found in the wallet #[error("Sapling nullifier(s) {0:?} for this transaction not found in wallet. Is the wallet fully synced?")] @@ -33,7 +32,7 @@ pub enum FeeError { } /// Errors associated with balance calculation -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum BalanceError { /// failed to retrieve full viewing key #[error("failed to retrieve full viewing key.")] @@ -44,7 +43,7 @@ pub enum BalanceError { } /// Errors associated with balance key derivation -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum KeyError { /// Error asociated with standard IO #[error("{0}")] diff --git a/zingolib/src/wallet/keys/unified.rs b/zingolib/src/wallet/keys/unified.rs index 87dccafb71..75a7b96a8f 100644 --- a/zingolib/src/wallet/keys/unified.rs +++ b/zingolib/src/wallet/keys/unified.rs @@ -21,10 +21,7 @@ use zcash_client_backend::address::UnifiedAddress; use zcash_client_backend::keys::{Era, UnifiedSpendingKey}; use zcash_client_backend::wallet::TransparentAddressMetadata; use zcash_encoding::{CompactSize, Vector}; -use zcash_keys::{ - encoding::AddressCodec, - keys::{DerivationError, UnifiedFullViewingKey}, -}; +use zcash_keys::keys::{DerivationError, UnifiedFullViewingKey}; use zcash_primitives::consensus::{NetworkConstants, Parameters}; use zcash_primitives::legacy::{ keys::{AccountPubKey, IncomingViewingKey, NonHardenedChildIndex}, @@ -36,7 +33,7 @@ use crate::wallet::error::KeyError; use crate::wallet::traits::{DomainWalletExt, ReadableWriteable, Recipient}; use crate::{ config::{ChainType, ZingoConfig}, - wallet::data::new_persistent_ephemeral_address, + wallet::data::new_rejection_address, }; use super::legacy::{generate_transparent_address_from_legacy_key, legacy_sks_to_usk, Capability}; @@ -234,8 +231,7 @@ pub struct WalletCapability { transparent_child_addresses: Arc>, // TODO: read/write for ephmereral addresses // TODO: Remove this field and exclusively use the TxMap field instead - transparent_child_ephemeral_addresses: - Arc>, + rejection_addresses: Arc>, /// Cache of unified_addresses unified_addresses: append_only_vec::AppendOnlyVec, addresses_write_lock: AtomicBool, @@ -245,7 +241,7 @@ impl Default for WalletCapability { Self { unified_key_store: UnifiedKeyStore::Empty, transparent_child_addresses: Arc::new(AppendOnlyVec::new()), - transparent_child_ephemeral_addresses: Arc::new(AppendOnlyVec::new()), + rejection_addresses: Arc::new(AppendOnlyVec::new()), unified_addresses: AppendOnlyVec::new(), addresses_write_lock: AtomicBool::new(false), } @@ -584,16 +580,9 @@ impl WalletCapability { .collect() } - pub(crate) fn get_ephemeral_taddrs(&self, chain: &crate::config::ChainType) -> HashSet { - self.transparent_child_ephemeral_addresses - .iter() - .map(|(transparent_address, _metadata)| transparent_address.encode(chain)) - .collect() - } - pub(crate) fn get_taddrs(&self, chain: &crate::config::ChainType) -> HashSet { self.get_external_taddrs(chain) - .union(&self.get_ephemeral_taddrs(chain)) + .union(&self.get_rejection_address_set(chain)) .cloned() .collect() } @@ -642,13 +631,13 @@ impl ReadableWriteable for WalletCapability { fn read(mut reader: R, input: ChainType) -> io::Result { let version = Self::get_version(&mut reader)?; let legacy_key: bool; - let ephemeral_addresses_len: u32; + let length_of_rejection_addresses: u32; let wc = match version { // in version 1, only spending keys are stored 1 => { legacy_key = true; - ephemeral_addresses_len = 0; + length_of_rejection_addresses = 0; // Create a temporary USK for address generation to load old wallets // due to missing BIP0032 transparent extended private key data @@ -667,7 +656,7 @@ impl ReadableWriteable for WalletCapability { } 2 => { legacy_key = true; - ephemeral_addresses_len = 0; + length_of_rejection_addresses = 0; let orchard_capability = Capability::< orchard::keys::FullViewingKey, @@ -758,7 +747,7 @@ impl ReadableWriteable for WalletCapability { } 3 => { legacy_key = false; - ephemeral_addresses_len = 0; + length_of_rejection_addresses = 0; Self { unified_key_store: UnifiedKeyStore::read(&mut reader, input)?, @@ -767,7 +756,7 @@ impl ReadableWriteable for WalletCapability { } 4 => { legacy_key = false; - ephemeral_addresses_len = reader.read_u32::()?; + length_of_rejection_addresses = reader.read_u32::()?; Self { unified_key_store: UnifiedKeyStore::read(&mut reader, input)?, @@ -787,10 +776,10 @@ impl ReadableWriteable for WalletCapability { .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; } - for _ in 0..ephemeral_addresses_len { - new_persistent_ephemeral_address( - &wc.transparent_child_ephemeral_addresses, - &wc.ephemeral_ivk() + for _ in 0..length_of_rejection_addresses { + new_rejection_address( + &wc.rejection_addresses, + &wc.rejection_ivk() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, ) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; @@ -801,7 +790,7 @@ impl ReadableWriteable for WalletCapability { fn write(&self, mut writer: W, input: ChainType) -> io::Result<()> { writer.write_u8(Self::VERSION)?; - writer.write_u32::(self.transparent_child_ephemeral_addresses.len() as u32)?; + writer.write_u32::(self.rejection_addresses.len() as u32)?; self.unified_key_store().write(&mut writer, input)?; Vector::write( &mut writer, @@ -908,12 +897,12 @@ impl Fvk for sapling_crypto::zip32::DiversifiableFullViewingKey { } } } -mod ephemeral { - use std::sync::Arc; +mod rejection { + use std::{collections::HashSet, sync::Arc}; use append_only_vec::AppendOnlyVec; use zcash_client_backend::wallet::TransparentAddressMetadata; - use zcash_keys::keys::DerivationError; + use zcash_keys::{encoding::AddressCodec, keys::DerivationError}; use zcash_primitives::legacy::{ keys::{AccountPubKey, NonHardenedChildIndex, TransparentKeyScope}, TransparentAddress, @@ -924,7 +913,7 @@ mod ephemeral { use super::WalletCapability; impl WalletCapability { - pub(crate) fn ephemeral_ivk( + pub(crate) fn rejection_ivk( &self, ) -> Result { AccountPubKey::try_from(self.unified_key_store())? @@ -932,14 +921,14 @@ mod ephemeral { .map_err(DerivationError::Transparent) .map_err(KeyError::KeyDerivationError) } - pub(crate) fn ephemeral_address( - ephemeral_ivk: &zcash_primitives::legacy::keys::EphemeralIvk, - ephemeral_address_index: u32, + pub(crate) fn get_rejection_address_by_index( + rejection_ivk: &zcash_primitives::legacy::keys::EphemeralIvk, + rejection_address_index: u32, ) -> Result<(TransparentAddress, TransparentAddressMetadata), KeyError> { - let address_index = NonHardenedChildIndex::from_index(ephemeral_address_index) + let address_index = NonHardenedChildIndex::from_index(rejection_address_index) .ok_or(KeyError::InvalidNonHardenedChildIndex)?; Ok(( - ephemeral_ivk + rejection_ivk .derive_ephemeral_address(address_index) .map_err(DerivationError::Transparent) .map_err(KeyError::KeyDerivationError)?, @@ -947,10 +936,19 @@ mod ephemeral { )) } /// TODO: Add Doc Comment Here! - pub fn transparent_child_ephemeral_addresses( + pub fn get_rejection_addresses( &self, ) -> &Arc> { - &self.transparent_child_ephemeral_addresses + &self.rejection_addresses + } + pub(crate) fn get_rejection_address_set( + &self, + chain: &crate::config::ChainType, + ) -> HashSet { + self.rejection_addresses + .iter() + .map(|(transparent_address, _metadata)| transparent_address.encode(chain)) + .collect() } } } diff --git a/zingolib/src/wallet/notes/orchard.rs b/zingolib/src/wallet/notes/orchard.rs index 21a0d7fd7e..fa6232af7f 100644 --- a/zingolib/src/wallet/notes/orchard.rs +++ b/zingolib/src/wallet/notes/orchard.rs @@ -224,21 +224,14 @@ pub mod mocks { have_spending_key: Option, } - #[allow(dead_code)] //TODO: fix this gross hack that I tossed in to silence the language-analyzer false positive impl OrchardNoteBuilder { - /// blank builder + #[allow(dead_code)] //TODO: fix this gross hack that I tossed in to silence the language-analyzer false positive + /// A builder, for a 'blank' note. + /// Be aware that two notes generated with + /// this function will be identical if built + /// with no changes. pub fn new() -> Self { - OrchardNoteBuilder { - diversifier: None, - note: None, - witnessed_position: None, - output_index: None, - nullifier: None, - spending_tx_status: None, - memo: None, - is_change: None, - have_spending_key: None, - } + Self::default() } // Methods to set each field @@ -281,7 +274,17 @@ pub mod mocks { impl Default for OrchardNoteBuilder { fn default() -> Self { - let mut builder = OrchardNoteBuilder::new(); + let mut builder = OrchardNoteBuilder { + diversifier: None, + note: None, + witnessed_position: None, + output_index: None, + nullifier: None, + spending_tx_status: None, + memo: None, + is_change: None, + have_spending_key: None, + }; builder .diversifier(Diversifier::from_bytes([0; 11])) .note(OrchardCryptoNoteBuilder::default()) diff --git a/zingolib/src/wallet/notes/sapling.rs b/zingolib/src/wallet/notes/sapling.rs index 79b8a2fa37..831b4fb56a 100644 --- a/zingolib/src/wallet/notes/sapling.rs +++ b/zingolib/src/wallet/notes/sapling.rs @@ -240,21 +240,14 @@ pub mod mocks { have_spending_key: Option, } - #[allow(dead_code)] //TODO: fix this gross hack that I tossed in to silence the language-analyzer false positive impl SaplingNoteBuilder { - /// blank builder + /// A builder, for a 'blank' note. + /// Be aware that two notes generated with + /// this function will be identical if built + /// with no changes. + #[allow(dead_code)] pub fn new() -> Self { - SaplingNoteBuilder { - diversifier: None, - note: None, - witnessed_position: None, - output_index: None, - nullifier: None, - spending_tx_status: None, - memo: None, - is_change: None, - have_spending_key: None, - } + Self::default() } // Methods to set each field @@ -297,7 +290,17 @@ pub mod mocks { impl Default for SaplingNoteBuilder { fn default() -> Self { - let mut builder = SaplingNoteBuilder::new(); + let mut builder = SaplingNoteBuilder { + diversifier: None, + note: None, + witnessed_position: None, + output_index: None, + nullifier: None, + spending_tx_status: None, + memo: None, + is_change: None, + have_spending_key: None, + }; builder .diversifier(sapling_crypto::Diversifier([0; 11])) .note(crate::mocks::SaplingCryptoNoteBuilder::default()) diff --git a/zingolib/src/wallet/propose.rs b/zingolib/src/wallet/propose.rs index f7b342bd39..a45d2e9bb5 100644 --- a/zingolib/src/wallet/propose.rs +++ b/zingolib/src/wallet/propose.rs @@ -2,7 +2,6 @@ use std::{convert::Infallible, num::NonZeroU32, ops::DerefMut as _}; -use thiserror::Error; use zcash_client_backend::{ data_api::wallet::input_selection::GreedyInputSelector, zip321::{TransactionRequest, Zip321Error}, @@ -42,7 +41,7 @@ fn build_default_giskit(memo: Option) -> GISKit { } /// Errors that can result from do_propose -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ProposeSendError { /// error in using trait to create spend proposal #[error("{0}")] @@ -69,7 +68,7 @@ pub enum ProposeSendError { } /// Errors that can result from do_propose -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ProposeShieldError { /// error in parsed addresses #[error("{0}")] @@ -95,12 +94,9 @@ impl LightWallet { &self, request: TransactionRequest, ) -> Result { - let num_ephemeral_addresses = self - .transaction_context - .key - .transparent_child_ephemeral_addresses() - .len() as u32; - let memo = change_memo_from_transaction_request(&request, num_ephemeral_addresses); + let number_of_rejection_addresses = + self.transaction_context.key.get_rejection_addresses().len() as u32; + let memo = change_memo_from_transaction_request(&request, number_of_rejection_addresses); let input_selector = build_default_giskit(Some(memo)); let mut tmamt = self diff --git a/zingolib/src/wallet/send.rs b/zingolib/src/wallet/send.rs index 4707f622ab..0428dcce15 100644 --- a/zingolib/src/wallet/send.rs +++ b/zingolib/src/wallet/send.rs @@ -89,9 +89,8 @@ impl LightWallet { } } -use thiserror::Error; #[allow(missing_docs)] // error types document themselves -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum BuildTransactionError { #[error("No witness trees. This is viewkey watch, not spendkey wallet.")] NoSpendCapability, @@ -189,10 +188,10 @@ impl LightWallet { // TODO: move to a more suitable place pub(crate) fn change_memo_from_transaction_request( request: &TransactionRequest, - mut num_ephemeral_addresses: u32, + mut number_of_rejection_addresses: u32, ) -> MemoBytes { let mut recipient_uas = Vec::new(); - let mut ephemeral_address_indexes = Vec::new(); + let mut rejection_address_indexes = Vec::new(); for payment in request.payments().values() { match payment.recipient_address().kind() { AddressKind::Unified(ua) => { @@ -201,16 +200,16 @@ pub(crate) fn change_memo_from_transaction_request( } } AddressKind::Tex(_) => { - ephemeral_address_indexes.push(num_ephemeral_addresses); + rejection_address_indexes.push(number_of_rejection_addresses); - num_ephemeral_addresses += 1; + number_of_rejection_addresses += 1; } _ => (), } } let uas_bytes = match create_wallet_internal_memo_version_1( recipient_uas.as_slice(), - ephemeral_address_indexes.as_slice(), + rejection_address_indexes.as_slice(), ) { Ok(bytes) => bytes, Err(e) => { diff --git a/zingolib/src/wallet/traits.rs b/zingolib/src/wallet/traits.rs index 7cf872fcb5..ddc06b8330 100644 --- a/zingolib/src/wallet/traits.rs +++ b/zingolib/src/wallet/traits.rs @@ -106,14 +106,15 @@ impl ToBytes for [u8; N] { /// Exposes the out_ciphertext, domain, and value_commitment in addition to the /// required methods of ShieldedOutput pub trait ShieldedOutputExt: ShieldedOutput { - /// TODO: Add Doc Comment Here! + /// Sapling and Orchard currently, more protocols may be supported in the future fn domain(&self, height: BlockHeight, parameters: ChainType) -> D; /// A decryption key for `enc_ciphertext`. `out_ciphertext` is _itself_ decryptable /// with the `OutgoingCipherKey` "`ock`". fn out_ciphertext(&self) -> [u8; 80]; - /// TODO: Add Doc Comment Here! + /// This data is stored in an ordered structure, across which a commitment merkle tree + /// is built. fn value_commitment(&self) -> D::ValueCommitment; } @@ -1062,7 +1063,7 @@ impl where T: ShieldedNoteInterface, { - const VERSION: u8 = 4; + const VERSION: u8 = 5; fn read( mut reader: R, @@ -1079,8 +1080,11 @@ where let external_version = Self::get_version(&mut reader)?; if external_version < 2 { - let mut x = ::get_deprecated_serialized_view_key_buffer(); - reader.read_exact(&mut x).expect("To not used this data."); + let mut discarded_bytes = + ::get_deprecated_serialized_view_key_buffer(); + reader + .read_exact(&mut discarded_bytes) + .expect("To not used this data."); } let mut diversifier_bytes = [0u8; 11]; @@ -1092,44 +1096,53 @@ where (diversifier, wallet_capability), )?; - let witnessed_position = if external_version >= 4 { - Position::from(reader.read_u64::()?) - } else { - let witnesses_vec = Vector::read(&mut reader, |r| read_incremental_witness(r))?; - - let top_height = reader.read_u64::()?; - let witnesses = WitnessCache::::new(witnesses_vec, top_height); - - let pos = witnesses - .last() - .map(|w| w.witnessed_position()) - .unwrap_or_else(|| Position::from(0)); - for (i, witness) in witnesses.witnesses.into_iter().rev().enumerate().rev() { - let height = BlockHeight::from(top_height as u32 - i as u32); - if let Some(&mut ref mut wits) = inc_wit_vec { - wits.push((witness, height)); + let witnessed_position = match external_version { + 5.. => Optional::read(&mut reader, ::read_u64::)?.map(Position::from), + 4 => Some(Position::from(reader.read_u64::()?)), + ..4 => { + let witnesses_vec = Vector::read(&mut reader, |r| read_incremental_witness(r))?; + + let top_height = reader.read_u64::()?; + let witnesses = WitnessCache::::new(witnesses_vec, top_height); + + let pos = witnesses.last().map(|w| w.witnessed_position()); + for (i, witness) in witnesses.witnesses.into_iter().rev().enumerate().rev() { + let height = BlockHeight::from(top_height as u32 - i as u32); + if let Some(&mut ref mut wits) = inc_wit_vec { + wits.push((witness, height)); + } } + pos } - pos }; - let mut nullifier = [0u8; 32]; - reader.read_exact(&mut nullifier)?; - let nullifier = T::Nullifier::from_bytes(nullifier); + let read_nullifier = |r: &mut R| { + let mut nullifier = [0u8; 32]; + r.read_exact(&mut nullifier)?; + Ok(T::Nullifier::from_bytes(nullifier)) + }; + + let nullifier = match external_version { + 5.. => Optional::read(&mut reader, read_nullifier)?, + ..5 => Some(read_nullifier(&mut reader)?), + }; - // Note that this is only the spent field, we ignore the pending_spent field. - // The reason is that pending spents are only in memory, and we need to get the actual value of spent - // from the blockchain anyway. - let spent = Optional::read(&mut reader, |r| { + let spend = Optional::read(&mut reader, |r| { let mut transaction_id_bytes = [0u8; 32]; r.read_exact(&mut transaction_id_bytes)?; - let height = r.read_u32::()?; - Ok((TxId::from_bytes(transaction_id_bytes), height)) + let status = match external_version { + 5.. => ConfirmationStatus::read(r, ()), + ..5 => { + let height = r.read_u32::()?; + Ok(ConfirmationStatus::Confirmed(BlockHeight::from_u32(height))) + } + }?; + Ok((TxId::from_bytes(transaction_id_bytes), status)) })?; - let spend = - spent.map(|(txid, height)| (txid, ConfirmationStatus::Confirmed(height.into()))); - + // Note that the spent field is now an enum, that contains what used to be + // a separate 'pending_spent' field. As they're mutually exclusive states, + // they are now stored in the same field. if external_version < 3 { let _pending_spent = { Optional::read(&mut reader, |r| { @@ -1175,8 +1188,8 @@ where Ok(T::from_parts( diversifier, note, - Some(witnessed_position), - Some(nullifier), + witnessed_position, + nullifier, spend, memo, is_change, @@ -1192,34 +1205,20 @@ where writer.write_all(&self.diversifier().to_bytes())?; self.note().write(&mut writer, ())?; - writer.write_u64::(u64::from(self.witnessed_position().ok_or( - io::Error::new( - io::ErrorKind::InvalidData, - "Tried to write note without knowing its the position of its value commitment", - ), - )?))?; - - writer.write_all( - &self - .nullifier() - .ok_or(io::Error::new( - io::ErrorKind::InvalidData, - "Tried to write note with unknown nullifier", - ))? - .to_bytes(), - )?; + Optional::write(&mut writer, *self.witnessed_position(), |w, pos| { + w.write_u64::(u64::from(pos)) + })?; - let confirmed_spend = self - .spending_tx_status() - .as_ref() - .and_then(|(txid, status)| status.get_confirmed_height().map(|height| (txid, height))); + Optional::write(&mut writer, self.nullifier(), |w, null| { + w.write_all(&null.to_bytes()) + })?; Optional::write( &mut writer, - confirmed_spend, - |w, (transaction_id, height)| { + self.spending_tx_status().as_ref(), + |w, &(transaction_id, status)| { w.write_all(transaction_id.as_ref())?; - w.write_u32::(height.into()) + status.write(w, ()) }, )?; @@ -1236,3 +1235,58 @@ where Ok(()) } } + +impl ReadableWriteable for ConfirmationStatus { + const VERSION: u8 = 0; + + fn read(mut reader: R, _input: ()) -> io::Result { + let _external_version = Self::get_version(&mut reader); + let status = reader.read_u8()?; + let height = BlockHeight::from_u32(reader.read_u32::()?); + match status { + 0 => Ok(Self::Calculated(height)), + 1 => Ok(Self::Transmitted(height)), + 2 => Ok(Self::Mempool(height)), + 3 => Ok(Self::Confirmed(height)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Bad confirmation status", + )), + } + } + + fn write(&self, mut writer: W, _input: ()) -> io::Result<()> { + writer.write_u8(Self::VERSION)?; + let height = match self { + ConfirmationStatus::Calculated(h) => { + writer.write_u8(0)?; + h + } + ConfirmationStatus::Transmitted(h) => { + writer.write_u8(1)?; + h + } + ConfirmationStatus::Mempool(h) => { + writer.write_u8(2)?; + h + } + ConfirmationStatus::Confirmed(h) => { + writer.write_u8(3)?; + h + } + }; + writer.write_u32::(u32::from(*height)) + } +} + +#[cfg(test)] +mod test { + use crate::wallet::notes::orchard::mocks::OrchardNoteBuilder; + + #[ignore = "not yet complete"] + #[test] + fn check_v5_pending_note_read_write() { + let orchard_note = OrchardNoteBuilder::new(); + dbg!(orchard_note.build()); + } +} diff --git a/zingolib/src/wallet/transaction_context.rs b/zingolib/src/wallet/transaction_context.rs index 1c10ac84ae..802398f9c9 100644 --- a/zingolib/src/wallet/transaction_context.rs +++ b/zingolib/src/wallet/transaction_context.rs @@ -293,7 +293,7 @@ mod decrypt_transaction { fn identify_rejection_address(&self, spent_utxo: TransparentOutput) -> Option { if self .key - .get_ephemeral_taddrs(&self.config.chain) + .get_rejection_address_set(&self.config.chain) .contains(&spent_utxo.address) { Some(spent_utxo.address) @@ -314,11 +314,10 @@ mod decrypt_transaction { { // We are the authors of the 320 transactions (that we care about), therefore - // we have the transaction that encumbered the (misanmed) "ephemeral" taddr - // with funds. - // We backtrack from the vins of every transaction to check for "ephemeral" addresses + // we have the transaction that encumbered the rejection address with funds. + // We backtrack from the vins of every transaction to check for rejection addresses // (from zingomemo), to determine if a given vin is the source of funds for a "tex" - // address. That is, we know the ephemeral taddrs, we simply need to check if they are + // address. That is, we know the rejection addresses, we simply need to check if they are // the receiver in a given transaction. If the are then, their spend identifies a tex. if let Some(t_bundle) = transaction.transparent_bundle() { for vin in t_bundle.vin.iter() { @@ -629,7 +628,7 @@ mod decrypt_transaction { outgoing_metadatas: &mut Vec, sent_to_tex: bool, ) { - // TODO: Account for ephemeral_taddresses + // TODO: Account for rejection addresses // Collect our t-addresses for easy checking let taddrs_set = self.key.get_taddrs(&self.config.chain); let tx_map = self.transaction_metadata_set.write().await; @@ -652,7 +651,7 @@ mod decrypt_transaction { TransparentAddress::ScriptHash(_taddr_bytes) => { // tex addresses are P2PKH only. If this is a script hash, then we were wrong about // it being a tex. - todo!("This happens if someone mislabels in a zingomemo, or zingolib logic an address as \"ephemeral\".");}, + todo!("This happens if someone mislabels in a zingomemo, or zingolib logic an address as \"rejection\".");}, }, } }) { @@ -685,7 +684,7 @@ mod decrypt_transaction { #[derive(Debug)] pub(crate) enum InvalidMemoError { #[allow(dead_code)] - InvalidEphemeralIndex(KeyError), + InvalidRejectionAddress(KeyError), } impl TransactionContext { async fn handle_uas( @@ -719,28 +718,28 @@ mod decrypt_transaction { &self, zingo_memo_stored_indices: Vec, ) -> Result<(), InvalidMemoError> { - // Get list of ephemeral keys already registered to the capability. + // Get list of rejection keys already registered to the capability. // TODO: This doesn't currently handle out-of-order sync where - // the ephemeral address is discovered (from the memo) **after** the + // the rejection is discovered (from the memo) **after** the // corresponding TEX address has been "passed". - let current_keys = self.key.transparent_child_ephemeral_addresses(); + let current_keys = self.key.get_rejection_addresses(); let total_keys = current_keys.len(); - for ephemeral_address_index in zingo_memo_stored_indices { - if (ephemeral_address_index as usize) < total_keys { + for rejection in zingo_memo_stored_indices { + if (rejection as usize) < total_keys { // The emphemeral key is in the structure at its appropriate location. return Ok(()); } else { // The detected key is derived from a higher index than any previously stored key. // * generate the keys to fill in the "gap". - for _index in (total_keys as u32)..=ephemeral_address_index { - crate::wallet::data::new_persistent_ephemeral_address( + for _index in (total_keys as u32)..=rejection { + crate::wallet::data::new_rejection_address( current_keys, &self .key - .ephemeral_ivk() - .map_err(InvalidMemoError::InvalidEphemeralIndex)?, + .rejection_ivk() + .map_err(InvalidMemoError::InvalidRejectionAddress)?, ) - .map_err(InvalidMemoError::InvalidEphemeralIndex)?; + .map_err(InvalidMemoError::InvalidRejectionAddress)?; } } } @@ -763,10 +762,10 @@ mod decrypt_transaction { ParsedMemo::Version0 { uas } => self.handle_uas(uas, transaction).await, ParsedMemo::Version1 { uas, - ephemeral_address_indexes, + rejection_address_indexes, } => { self.handle_uas(uas, transaction).await; - self.handle_texes(ephemeral_address_indexes).await?; + self.handle_texes(rejection_address_indexes).await?; } other_memo_version => { log::error!( diff --git a/zingolib/src/wallet/transaction_record.rs b/zingolib/src/wallet/transaction_record.rs index 45b7f48219..ec2f7aa8e2 100644 --- a/zingolib/src/wallet/transaction_record.rs +++ b/zingolib/src/wallet/transaction_record.rs @@ -121,27 +121,24 @@ impl TransactionRecord { to: D::Recipient, output_index: usize, ) { - match D::WalletNote::get_record_outputs(self) + if !D::WalletNote::get_record_outputs(self) .iter_mut() - .find(|n| n.note() == ¬e) + .any(|n| n.note() == ¬e) { - None => { - let nd = D::WalletNote::from_parts( - to.diversifier(), - note, - None, - None, - None, - None, - // if this is change, we'll mark it later in check_notes_mark_change - false, - false, - Some(output_index as u32), - ); - - D::WalletNote::transaction_metadata_notes_mut(self).push(nd); - } - Some(_) => {} + let nd = D::WalletNote::from_parts( + to.diversifier(), + note, + None, + None, + None, + None, + // if this is change, we'll mark it later in check_notes_mark_change + false, + false, + Some(output_index as u32), + ); + + D::WalletNote::transaction_metadata_notes_mut(self).push(nd); } } @@ -255,10 +252,8 @@ impl TransactionRecord { /// TODO: Add Doc Comment Here! // TODO: This is incorrect in the edge case where where we have a send-to-self with // no text memo and 0-value fee - #[allow(deprecated)] - #[deprecated(note = "uses unstable deprecated is_change")] pub fn is_outgoing_transaction(&self) -> bool { - (!self.outgoing_tx_data.is_empty()) || self.total_value_spent() != 0 + (!self.outgoing_tx_data.is_empty()) || self.total_value_output_to_explicit_receivers() != 0 } /// This means there's at least one note that adds funds diff --git a/zingolib/src/wallet/transaction_records_by_id/trait_inputsource.rs b/zingolib/src/wallet/transaction_records_by_id/trait_inputsource.rs index 7bd99680c1..d611b887a3 100644 --- a/zingolib/src/wallet/transaction_records_by_id/trait_inputsource.rs +++ b/zingolib/src/wallet/transaction_records_by_id/trait_inputsource.rs @@ -19,13 +19,12 @@ use zcash_primitives::{ use crate::wallet::{notes::OutputInterface, transaction_records_by_id::TransactionRecordsById}; use std::fmt::Debug; -use thiserror::Error; use zcash_client_backend::wallet::NoteId; use zcash_primitives::transaction::components::amount::BalanceError; /// Error type used by InputSource trait -#[derive(Debug, PartialEq, Error)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum InputSourceError { /// No witness position found for note. Note cannot be spent. #[error("No witness position found for note. Note cannot be spent: {0:?}")] diff --git a/zingolib/src/wallet/tx_map.rs b/zingolib/src/wallet/tx_map.rs index c1709d186c..a721c2c1d5 100644 --- a/zingolib/src/wallet/tx_map.rs +++ b/zingolib/src/wallet/tx_map.rs @@ -13,7 +13,6 @@ use crate::{ use getset::{Getters, MutGetters}; use spending_data::SpendingData; use std::{fmt::Debug, sync::Arc}; -use thiserror::Error; use zcash_client_backend::wallet::TransparentAddressMetadata; use zcash_primitives::legacy::{keys::EphemeralIvk, TransparentAddress}; @@ -29,8 +28,8 @@ pub struct TxMap { // as below pub(crate) transparent_child_addresses: Arc>, - // TODO: rename (ephemeral_transparent_addresses?) - pub(crate) transparent_child_ephemeral_addresses: + /// rejection_addresses are called "ephemeral" in LRZ 320 + pub(crate) rejection_addresses: Arc>, } @@ -45,19 +44,16 @@ impl TxMap { transparent_child_addresses: Arc< append_only_vec::AppendOnlyVec<(usize, TransparentAddress)>, >, - transparent_child_ephemeral_addresses: Arc< + rejection_addresses: Arc< append_only_vec::AppendOnlyVec<(TransparentAddress, TransparentAddressMetadata)>, >, - transparent_ephemeral_ivk: EphemeralIvk, + rejection_ivk: EphemeralIvk, ) -> TxMap { Self { transaction_records_by_id: TransactionRecordsById::new(), - spending_data: Some(SpendingData::new( - WitnessTrees::default(), - transparent_ephemeral_ivk, - )), + spending_data: Some(SpendingData::new(WitnessTrees::default(), rejection_ivk)), transparent_child_addresses, - transparent_child_ephemeral_addresses, + rejection_addresses, } } pub(crate) fn new_treeless( @@ -69,7 +65,7 @@ impl TxMap { transaction_records_by_id: TransactionRecordsById::new(), spending_data: None, transparent_child_addresses, - transparent_child_ephemeral_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), + rejection_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), } } /// TODO: Doc-comment! @@ -90,7 +86,7 @@ impl TxMap { } } #[allow(missing_docs)] // error types document themselves -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum TxMapTraitError { #[error("No witness trees. This is viewkey watch, not a spendkey wallet.")] NoSpendCapability, @@ -130,7 +126,7 @@ impl TxMap { .unwrap(), )), transparent_child_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), - transparent_child_ephemeral_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), + rejection_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), } } /// For any unit tests that don't require a WalletCapability, where the addresses come from @@ -139,7 +135,7 @@ impl TxMap { transaction_records_by_id: TransactionRecordsById::new(), spending_data: None, transparent_child_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), - transparent_child_ephemeral_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), + rejection_addresses: Arc::new(append_only_vec::AppendOnlyVec::new()), } } } diff --git a/zingolib/src/wallet/tx_map/read_write.rs b/zingolib/src/wallet/tx_map/read_write.rs index 111378061e..f332db5509 100644 --- a/zingolib/src/wallet/tx_map/read_write.rs +++ b/zingolib/src/wallet/tx_map/read_write.rs @@ -69,12 +69,10 @@ impl TxMap { Ok(Self { transaction_records_by_id: map, spending_data: witness_trees - .zip(wallet_capability.ephemeral_ivk().ok()) + .zip(wallet_capability.rejection_ivk().ok()) .map(|(trees, key)| SpendingData::new(trees, key)), transparent_child_addresses: wallet_capability.transparent_child_addresses().clone(), - transparent_child_ephemeral_addresses: wallet_capability - .transparent_child_ephemeral_addresses() - .clone(), + rejection_addresses: wallet_capability.get_rejection_addresses().clone(), }) } @@ -141,12 +139,10 @@ impl TxMap { Ok(Self { transaction_records_by_id: TransactionRecordsById::from_map(map), spending_data: witness_trees - .zip(wallet_capability.ephemeral_ivk().ok()) + .zip(wallet_capability.rejection_ivk().ok()) .map(|(trees, key)| SpendingData::new(trees, key)), transparent_child_addresses: wallet_capability.transparent_child_addresses().clone(), - transparent_child_ephemeral_addresses: wallet_capability - .transparent_child_ephemeral_addresses() - .clone(), + rejection_addresses: wallet_capability.get_rejection_addresses().clone(), }) } @@ -158,16 +154,18 @@ impl TxMap { // The hashmap, write as a set of tuples. Store them sorted so that wallets are // deterministically saved { - let mut transaction_metadatas = self + let mut transaction_records = self .transaction_records_by_id .iter() .collect::>(); - // Don't write down metadata for transactions in the mempool, we'll rediscover - // it on reload - transaction_metadatas.retain(|metadata| metadata.1.status.is_confirmed()); - transaction_metadatas.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); - - Vector::write(&mut writer, &transaction_metadatas, |w, (k, v)| { + // Don't write down metadata for received transactions in the mempool, we'll rediscover + // them on reload + transaction_records.retain(|(_txid, record)| { + record.status.is_confirmed() || record.is_outgoing_transaction() + }); + transaction_records.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); + + Vector::write(&mut writer, &transaction_records, |w, (k, v)| { w.write_all(k.as_ref())?; v.write(w) })?; diff --git a/zingolib/src/wallet/tx_map/spending_data.rs b/zingolib/src/wallet/tx_map/spending_data.rs index 12e6753f70..45aae3922a 100644 --- a/zingolib/src/wallet/tx_map/spending_data.rs +++ b/zingolib/src/wallet/tx_map/spending_data.rs @@ -14,15 +14,15 @@ pub(crate) struct SpendingData { #[getset(get = "pub(crate)", get_mut = "pub(crate)")] cached_raw_transactions: Vec<(TxId, Vec)>, #[getset(get = "pub(crate)", get_mut = "pub(crate)")] - transparent_ephemeral_ivk: EphemeralIvk, + rejection_ivk: EphemeralIvk, } impl SpendingData { - pub fn new(witness_trees: WitnessTrees, transparent_ephemeral_ivk: EphemeralIvk) -> Self { + pub fn new(witness_trees: WitnessTrees, rejection_ivk: EphemeralIvk) -> Self { SpendingData { witness_trees, cached_raw_transactions: Vec::new(), - transparent_ephemeral_ivk, + rejection_ivk, } } } diff --git a/zingolib/src/wallet/tx_map/trait_walletread.rs b/zingolib/src/wallet/tx_map/trait_walletread.rs index 0fd0278099..13ed072b18 100644 --- a/zingolib/src/wallet/tx_map/trait_walletread.rs +++ b/zingolib/src/wallet/tx_map/trait_walletread.rs @@ -343,11 +343,7 @@ impl WalletRead for TxMap { )>, Self::Error, > { - Ok(self - .transparent_child_ephemeral_addresses - .iter() - .cloned() - .collect()) + Ok(self.rejection_addresses.iter().cloned().collect()) } } diff --git a/zingolib/src/wallet/tx_map/trait_walletwrite.rs b/zingolib/src/wallet/tx_map/trait_walletwrite.rs index f35c5481c2..0539a67fd8 100644 --- a/zingolib/src/wallet/tx_map/trait_walletwrite.rs +++ b/zingolib/src/wallet/tx_map/trait_walletwrite.rs @@ -123,9 +123,9 @@ impl WalletWrite for TxMap { .as_ref() .map(|spending_data| { iter::repeat_with(|| { - crate::wallet::data::new_persistent_ephemeral_address( - &self.transparent_child_ephemeral_addresses, - spending_data.transparent_ephemeral_ivk(), + crate::wallet::data::new_rejection_address( + &self.rejection_addresses, + spending_data.rejection_ivk(), ) .map_err(TxMapTraitError::TexSendError) })