diff --git a/libtonode-tests/tests/concrete.rs b/libtonode-tests/tests/concrete.rs index 1d6aab25e..4f9d6815c 100644 --- a/libtonode-tests/tests/concrete.rs +++ b/libtonode-tests/tests/concrete.rs @@ -1700,95 +1700,97 @@ mod slow { // )); // } - #[tokio::test] - async fn sends_to_self_handle_balance_properly() { - let transparent_funding = 100_000; - let (ref regtest_manager, _cph, faucet, ref recipient) = - scenarios::faucet_recipient_default().await; - from_inputs::quick_send( - &faucet, - vec![( - &get_base_address_macro!(recipient, "transparent"), - transparent_funding, - None, - )], - ) - .await - .unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) - .await - .unwrap(); - recipient.quick_shield().await.unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) - .await - .unwrap(); - println!( - "{}", - serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() - ); - println!("{}", recipient.do_list_transactions().await.pretty(2)); - println!( - "{}", - JsonValue::from(recipient.sorted_value_transfers(true).await).pretty(2) - ); - recipient.do_rescan().await.unwrap(); - println!( - "{}", - serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() - ); - println!("{}", recipient.do_list_transactions().await.pretty(2)); - println!( - "{}", - JsonValue::from(recipient.sorted_value_transfers(true).await).pretty(2) - ); - // TODO: Add asserts! - } - #[tokio::test] - async fn send_to_ua_saves_full_ua_in_wallet() { - let (regtest_manager, _cph, faucet, recipient) = - scenarios::faucet_recipient_default().await; - //utils::increase_height_and_wait_for_client(®test_manager, &faucet, 5).await; - let recipient_unified_address = get_base_address_macro!(recipient, "unified"); - let sent_value = 50_000; - from_inputs::quick_send( - &faucet, - vec![(recipient_unified_address.as_str(), sent_value, None)], - ) - .await - .unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &faucet, 1) - .await - .unwrap(); - let list = faucet.do_list_transactions().await; - assert!(list.members().any(|transaction| { - transaction.entries().any(|(key, value)| { - if key == "outgoing_metadata" { - value[0]["address"] == recipient_unified_address - } else { - false - } - }) - })); - faucet.do_rescan().await.unwrap(); - let new_list = faucet.do_list_transactions().await; - assert!(new_list.members().any(|transaction| { - transaction.entries().any(|(key, value)| { - if key == "outgoing_metadata" { - value[0]["address"] == recipient_unified_address - } else { - false - } - }) - })); - assert_eq!( - list, - new_list, - "Pre-Rescan: {}\n\n\nPost-Rescan: {}\n\n\n", - json::stringify_pretty(list.clone(), 4), - json::stringify_pretty(new_list.clone(), 4) - ); - } - // FIXME: + // FIXME: sync integration + // #[tokio::test] + // async fn sends_to_self_handle_balance_properly() { + // let transparent_funding = 100_000; + // let (ref regtest_manager, _cph, faucet, ref recipient) = + // scenarios::faucet_recipient_default().await; + // from_inputs::quick_send( + // &faucet, + // vec![( + // &get_base_address_macro!(recipient, "transparent"), + // transparent_funding, + // None, + // )], + // ) + // .await + // .unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) + // .await + // .unwrap(); + // recipient.quick_shield().await.unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) + // .await + // .unwrap(); + // println!( + // "{}", + // serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() + // ); + // println!("{}", recipient.do_list_transactions().await.pretty(2)); + // println!( + // "{}", + // JsonValue::from(recipient.sorted_value_transfers(true).await).pretty(2) + // ); + // recipient.do_rescan().await.unwrap(); + // println!( + // "{}", + // serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() + // ); + // println!("{}", recipient.do_list_transactions().await.pretty(2)); + // println!( + // "{}", + // JsonValue::from(recipient.sorted_value_transfers(true).await).pretty(2) + // ); + // // TODO: Add asserts! + // } + // FIXME: sync integration + // #[tokio::test] + // async fn send_to_ua_saves_full_ua_in_wallet() { + // let (regtest_manager, _cph, faucet, recipient) = + // scenarios::faucet_recipient_default().await; + // //utils::increase_height_and_wait_for_client(®test_manager, &faucet, 5).await; + // let recipient_unified_address = get_base_address_macro!(recipient, "unified"); + // let sent_value = 50_000; + // from_inputs::quick_send( + // &faucet, + // vec![(recipient_unified_address.as_str(), sent_value, None)], + // ) + // .await + // .unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &faucet, 1) + // .await + // .unwrap(); + // let list = faucet.do_list_transactions().await; + // assert!(list.members().any(|transaction| { + // transaction.entries().any(|(key, value)| { + // if key == "outgoing_metadata" { + // value[0]["address"] == recipient_unified_address + // } else { + // false + // } + // }) + // })); + // faucet.do_rescan().await.unwrap(); + // let new_list = faucet.do_list_transactions().await; + // assert!(new_list.members().any(|transaction| { + // transaction.entries().any(|(key, value)| { + // if key == "outgoing_metadata" { + // value[0]["address"] == recipient_unified_address + // } else { + // false + // } + // }) + // })); + // assert_eq!( + // list, + // new_list, + // "Pre-Rescan: {}\n\n\nPost-Rescan: {}\n\n\n", + // json::stringify_pretty(list.clone(), 4), + // json::stringify_pretty(new_list.clone(), 4) + // ); + // } + // FIXME: sync integration // #[tokio::test] // async fn send_to_transparent_and_sapling_maintain_balance() { // // Receipt of orchard funds @@ -2356,80 +2358,81 @@ mod slow { let mut txids = recipient.transaction_summaries().await.txids().into_iter(); assert!(itertools::Itertools::all_unique(&mut txids)); } - #[tokio::test] - async fn sapling_to_sapling_scan_together() { - // Create an incoming transaction, and then send that transaction, and scan everything together, to make sure it works. - // (For this test, the Sapling Domain is assumed in all cases.) - // Sender Setup: - // 1. create a spend key: SpendK_S - // 2. derive a Shielded Payment Address from SpendK_S: SPA_KS - // 3. construct a Block Reward Transaction where SPA_KS receives a block reward: BRT - // 4. publish BRT - // 5. optionally mine a block including BRT <-- There are two separate tests to run - // 6. optionally mine sufficient subsequent blocks to "validate" BRT - // Recipient Setup: - // 1. create a spend key: "SpendK_R" - // 2. from SpendK_R derive a Shielded Payment Address: SPA_R - // Test Procedure: - // 1. construct a transaction "spending" from a SpendK_S output to SPA_R - // 2. publish the transaction to the mempool - // 3. mine a block - // Constraints: - // 1. SpendK_S controls start - spend funds - // 2. SpendK_R controls 0 + spend funds - let (regtest_manager, _cph, faucet, recipient) = - scenarios::faucet_recipient_default().await; + // FIXME: sync integration + // #[tokio::test] + // async fn sapling_to_sapling_scan_together() { + // // Create an incoming transaction, and then send that transaction, and scan everything together, to make sure it works. + // // (For this test, the Sapling Domain is assumed in all cases.) + // // Sender Setup: + // // 1. create a spend key: SpendK_S + // // 2. derive a Shielded Payment Address from SpendK_S: SPA_KS + // // 3. construct a Block Reward Transaction where SPA_KS receives a block reward: BRT + // // 4. publish BRT + // // 5. optionally mine a block including BRT <-- There are two separate tests to run + // // 6. optionally mine sufficient subsequent blocks to "validate" BRT + // // Recipient Setup: + // // 1. create a spend key: "SpendK_R" + // // 2. from SpendK_R derive a Shielded Payment Address: SPA_R + // // Test Procedure: + // // 1. construct a transaction "spending" from a SpendK_S output to SPA_R + // // 2. publish the transaction to the mempool + // // 3. mine a block + // // Constraints: + // // 1. SpendK_S controls start - spend funds + // // 2. SpendK_R controls 0 + spend funds + // let (regtest_manager, _cph, faucet, recipient) = + // scenarios::faucet_recipient_default().await; - // Give the faucet a block reward - zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &faucet, 1) - .await - .unwrap(); - let value = 100_000; + // // Give the faucet a block reward + // zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &faucet, 1) + // .await + // .unwrap(); + // let value = 100_000; - // Send some sapling value to the recipient - let txid = zingolib::testutils::send_value_between_clients_and_sync( - ®test_manager, - &faucet, - &recipient, - value, - "sapling", - ) - .await - .unwrap(); + // // Send some sapling value to the recipient + // let txid = zingolib::testutils::send_value_between_clients_and_sync( + // ®test_manager, + // &faucet, + // &recipient, + // value, + // "sapling", + // ) + // .await + // .unwrap(); - let spent_value = 250; + // let spent_value = 250; - // Construct transaction to wallet-external recipient-address. - let exit_zaddr = get_base_address_macro!(faucet, "sapling"); - let spent_txid = - from_inputs::quick_send(&recipient, vec![(&exit_zaddr, spent_value, None)]) - .await - .unwrap() - .first() - .to_string(); + // // Construct transaction to wallet-external recipient-address. + // let exit_zaddr = get_base_address_macro!(faucet, "sapling"); + // let spent_txid = + // from_inputs::quick_send(&recipient, vec![(&exit_zaddr, spent_value, None)]) + // .await + // .unwrap() + // .first() + // .to_string(); - zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &recipient, 1) - .await - .unwrap(); - // 5. Check the transaction list to make sure we got all transactions - let list = recipient.do_list_transactions().await; + // zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &recipient, 1) + // .await + // .unwrap(); + // // 5. Check the transaction list to make sure we got all transactions + // let list = recipient.do_list_transactions().await; - assert_eq!(list[0]["block_height"].as_u64().unwrap(), 5); - assert_eq!(list[0]["txid"], txid.to_string()); - assert_eq!(list[0]["amount"].as_i64().unwrap(), (value as i64)); + // assert_eq!(list[0]["block_height"].as_u64().unwrap(), 5); + // assert_eq!(list[0]["txid"], txid.to_string()); + // assert_eq!(list[0]["amount"].as_i64().unwrap(), (value as i64)); - assert_eq!(list[1]["block_height"].as_u64().unwrap(), 6); - assert_eq!(list[1]["txid"], spent_txid); - assert_eq!( - list[1]["amount"].as_i64().unwrap(), - -((spent_value + u64::from(MINIMUM_FEE)) as i64) - ); - assert_eq!(list[1]["outgoing_metadata"][0]["address"], exit_zaddr); - assert_eq!( - list[1]["outgoing_metadata"][0]["value"].as_u64().unwrap(), - spent_value - ); - } + // assert_eq!(list[1]["block_height"].as_u64().unwrap(), 6); + // assert_eq!(list[1]["txid"], spent_txid); + // assert_eq!( + // list[1]["amount"].as_i64().unwrap(), + // -((spent_value + u64::from(MINIMUM_FEE)) as i64) + // ); + // assert_eq!(list[1]["outgoing_metadata"][0]["address"], exit_zaddr); + // assert_eq!( + // list[1]["outgoing_metadata"][0]["value"].as_u64().unwrap(), + // spent_value + // ); + // } // FIXME: // #[tokio::test] // async fn sapling_incoming_sapling_outgoing() { @@ -2770,68 +2773,70 @@ mod slow { external_send_txid_with_memo_ref ); } - #[tokio::test] - async fn check_list_value_transfers_across_rescan() { - let inital_value = 100_000; - let (ref regtest_manager, _cph, faucet, ref recipient, _txid) = - scenarios::faucet_funded_recipient_default(inital_value).await; - from_inputs::quick_send( - recipient, - vec![(&get_base_address_macro!(faucet, "unified"), 10_000, None); 2], - ) - .await - .unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) - .await - .unwrap(); - let pre_rescan_transactions = recipient.do_list_transactions().await; - let pre_rescan_summaries = recipient.sorted_value_transfers(true).await; - recipient.do_rescan().await.unwrap(); - let post_rescan_transactions = recipient.do_list_transactions().await; - let post_rescan_summaries = recipient.sorted_value_transfers(true).await; - assert_eq!(pre_rescan_transactions, post_rescan_transactions); - assert_eq!(pre_rescan_summaries, post_rescan_summaries); - let mut outgoing_metadata = pre_rescan_transactions - .members() - .find_map(|tx| tx.entries().find(|(key, _val)| key == &"outgoing_metadata")) - .unwrap() - .1 - .members(); - // The two outgoing spends were identical. They should be represented as such - assert_eq!(outgoing_metadata.next(), outgoing_metadata.next()); - } - } - #[ignore = "redundant with tests that validate with validate_otd"] - #[tokio::test] - async fn multiple_outgoing_metadatas_work_right_on_restore() { - let inital_value = 100_000; - let (ref regtest_manager, _cph, faucet, ref recipient, _txid) = - scenarios::faucet_funded_recipient_default(inital_value).await; - from_inputs::quick_send( - recipient, - vec![(&get_base_address_macro!(faucet, "unified"), 10_000, None); 2], - ) - .await - .unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) - .await - .unwrap(); - let pre_rescan_transactions = recipient.do_list_transactions().await; - let pre_rescan_summaries = recipient.transaction_summaries().await; - recipient.do_rescan().await.unwrap(); - let post_rescan_transactions = recipient.do_list_transactions().await; - let post_rescan_summaries = recipient.transaction_summaries().await; - assert_eq!(pre_rescan_transactions, post_rescan_transactions); - assert_eq!(pre_rescan_summaries, post_rescan_summaries); - let mut outgoing_metadata = pre_rescan_transactions - .members() - .find_map(|tx| tx.entries().find(|(key, _val)| key == &"outgoing_metadata")) - .unwrap() - .1 - .members(); - // The two outgoing spends were identical. They should be represented as such - assert_eq!(outgoing_metadata.next(), outgoing_metadata.next()); + // FIXME: sync integration + // #[tokio::test] + // async fn check_list_value_transfers_across_rescan() { + // let inital_value = 100_000; + // let (ref regtest_manager, _cph, faucet, ref recipient, _txid) = + // scenarios::faucet_funded_recipient_default(inital_value).await; + // from_inputs::quick_send( + // recipient, + // vec![(&get_base_address_macro!(faucet, "unified"), 10_000, None); 2], + // ) + // .await + // .unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) + // .await + // .unwrap(); + // let pre_rescan_transactions = recipient.do_list_transactions().await; + // let pre_rescan_summaries = recipient.sorted_value_transfers(true).await; + // recipient.do_rescan().await.unwrap(); + // let post_rescan_transactions = recipient.do_list_transactions().await; + // let post_rescan_summaries = recipient.sorted_value_transfers(true).await; + // assert_eq!(pre_rescan_transactions, post_rescan_transactions); + // assert_eq!(pre_rescan_summaries, post_rescan_summaries); + // let mut outgoing_metadata = pre_rescan_transactions + // .members() + // .find_map(|tx| tx.entries().find(|(key, _val)| key == &"outgoing_metadata")) + // .unwrap() + // .1 + // .members(); + // // The two outgoing spends were identical. They should be represented as such + // assert_eq!(outgoing_metadata.next(), outgoing_metadata.next()); + // } } + // FIXME: sync integration + // #[ignore = "redundant with tests that validate with validate_otd"] + // #[tokio::test] + // async fn multiple_outgoing_metadatas_work_right_on_restore() { + // let inital_value = 100_000; + // let (ref regtest_manager, _cph, faucet, ref recipient, _txid) = + // scenarios::faucet_funded_recipient_default(inital_value).await; + // from_inputs::quick_send( + // recipient, + // vec![(&get_base_address_macro!(faucet, "unified"), 10_000, None); 2], + // ) + // .await + // .unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(regtest_manager, recipient, 1) + // .await + // .unwrap(); + // let pre_rescan_transactions = recipient.do_list_transactions().await; + // let pre_rescan_summaries = recipient.transaction_summaries().await; + // recipient.do_rescan().await.unwrap(); + // let post_rescan_transactions = recipient.do_list_transactions().await; + // let post_rescan_summaries = recipient.transaction_summaries().await; + // assert_eq!(pre_rescan_transactions, post_rescan_transactions); + // assert_eq!(pre_rescan_summaries, post_rescan_summaries); + // let mut outgoing_metadata = pre_rescan_transactions + // .members() + // .find_map(|tx| tx.entries().find(|(key, _val)| key == &"outgoing_metadata")) + // .unwrap() + // .1 + // .members(); + // // The two outgoing spends were identical. They should be represented as such + // assert_eq!(outgoing_metadata.next(), outgoing_metadata.next()); + // } #[tokio::test] async fn note_selection_order() { // In order to fund a transaction multiple notes may be selected and consumed. @@ -3503,34 +3508,35 @@ mod slow { .await .unwrap(); } - #[tokio::test] - async fn dust_sends_change_correctly() { - let (regtest_manager, _cph, faucet, recipient, _txid) = - scenarios::faucet_funded_recipient_default(100_000).await; + // FIXME: sync integration + // #[tokio::test] + // async fn dust_sends_change_correctly() { + // let (regtest_manager, _cph, faucet, recipient, _txid) = + // scenarios::faucet_funded_recipient_default(100_000).await; - // Send of less that transaction fee - let sent_value = 1000; - let _sent_transaction_id = from_inputs::quick_send( - &recipient, - vec![( - &get_base_address_macro!(faucet, "unified"), - sent_value, - None, - )], - ) - .await - .unwrap(); + // // Send of less that transaction fee + // let sent_value = 1000; + // let _sent_transaction_id = from_inputs::quick_send( + // &recipient, + // vec![( + // &get_base_address_macro!(faucet, "unified"), + // sent_value, + // None, + // )], + // ) + // .await + // .unwrap(); - zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &recipient, 5) - .await - .unwrap(); + // zingolib::testutils::increase_height_and_wait_for_client(®test_manager, &recipient, 5) + // .await + // .unwrap(); - println!("{}", recipient.do_list_transactions().await.pretty(4)); - println!( - "{}", - serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() - ); - } + // println!("{}", recipient.do_list_transactions().await.pretty(4)); + // println!( + // "{}", + // serde_json::to_string_pretty(&recipient.do_balance().await).unwrap() + // ); + // } #[tokio::test] async fn by_address_finsight() { diff --git a/libtonode-tests/tests/sync.rs b/libtonode-tests/tests/sync.rs index 8b4d73336..c55164339 100644 --- a/libtonode-tests/tests/sync.rs +++ b/libtonode-tests/tests/sync.rs @@ -70,8 +70,8 @@ async fn sync_status() { let lightclient = LightClient::create_from_wallet_base_async( WalletBase::from_string(HOSPITAL_MUSEUM_SEED.to_string()), &config, - 2_750_000, - // 2_670_000, + // 2_750_000, + 2_496_152, true, ) .await diff --git a/libtonode-tests/tests/wallet.rs b/libtonode-tests/tests/wallet.rs index 3f88f3453..6468357b7 100644 --- a/libtonode-tests/tests/wallet.rs +++ b/libtonode-tests/tests/wallet.rs @@ -1,303 +1,305 @@ #![forbid(unsafe_code)] mod load_wallet { - use std::fs::File; + // use std::fs::File; - use zcash_client_backend::PoolType; - use zcash_client_backend::ShieldedProtocol; - use zingolib::check_client_balances; - use zingolib::config::RegtestNetwork; - use zingolib::config::ZingoConfig; + // use zcash_client_backend::PoolType; + // use zcash_client_backend::ShieldedProtocol; + // use zingolib::check_client_balances; + // use zingolib::config::RegtestNetwork; + // use zingolib::config::ZingoConfig; use zingolib::get_base_address_macro; - use zingolib::lightclient::send::send_with_proposal::QuickSendError; - use zingolib::lightclient::LightClient; - use zingolib::lightclient::PoolBalances; - use zingolib::testutils::chain_generics::conduct_chain::ConductChain as _; - use zingolib::testutils::chain_generics::libtonode::LibtonodeEnvironment; + // use zingolib::lightclient::send::send_with_proposal::QuickSendError; + // use zingolib::lightclient::LightClient; + // use zingolib::lightclient::PoolBalances; + // use zingolib::testutils::chain_generics::conduct_chain::ConductChain as _; + // use zingolib::testutils::chain_generics::libtonode::LibtonodeEnvironment; use zingolib::testutils::lightclient::from_inputs; - use zingolib::testutils::paths::get_cargo_manifest_dir; + // use zingolib::testutils::paths::get_cargo_manifest_dir; use zingolib::testutils::scenarios; - use zingolib::utils; - use zingolib::wallet::disk::testing::examples; - use zingolib::wallet::propose::ProposeSendError::Proposal; + // use zingolib::utils; + // use zingolib::wallet::disk::testing::examples; + // use zingolib::wallet::propose::ProposeSendError::Proposal; - #[tokio::test] - async fn load_old_wallet_at_reorged_height() { - let regtest_network = RegtestNetwork::new(1, 1, 1, 1, 1, 1, 200); - let (ref regtest_manager, cph, ref faucet) = scenarios::faucet( - PoolType::Shielded(ShieldedProtocol::Orchard), - regtest_network, - false, - ) - .await; - println!("Shutting down initial zcd/lwd unneeded processes"); - drop(cph); + // FIXME: sync integration + // #[tokio::test] + // async fn load_old_wallet_at_reorged_height() { + // let regtest_network = RegtestNetwork::new(1, 1, 1, 1, 1, 1, 200); + // let (ref regtest_manager, cph, ref faucet) = scenarios::faucet( + // PoolType::Shielded(ShieldedProtocol::Orchard), + // regtest_network, + // false, + // ) + // .await; + // println!("Shutting down initial zcd/lwd unneeded processes"); + // drop(cph); - let zcd_datadir = ®test_manager.zcashd_data_dir; - let zingo_datadir = ®test_manager.zingo_datadir; - // This test is the unique consumer of: - // zingolib/src/testvectors/old_wallet_reorg_test_wallet - let cached_data_dir = get_cargo_manifest_dir() - .parent() - .unwrap() - .join("zingolib/src/testvectors") - .join("old_wallet_reorg_test_wallet"); - let zcd_source = cached_data_dir - .join("zcashd") - .join(".") - .to_string_lossy() - .to_string(); - let zcd_dest = zcd_datadir.to_string_lossy().to_string(); - std::process::Command::new("rm") - .arg("-r") - .arg(&zcd_dest) - .output() - .expect("directory rm failed"); - std::fs::DirBuilder::new() - .create(&zcd_dest) - .expect("Dir recreate failed"); - std::process::Command::new("cp") - .arg("-r") - .arg(zcd_source) - .arg(zcd_dest) - .output() - .expect("directory copy failed"); - let zingo_source = cached_data_dir - .join("zingo-wallet.dat") - .to_string_lossy() - .to_string(); - let zingo_dest = zingo_datadir.to_string_lossy().to_string(); - std::process::Command::new("cp") - .arg("-f") - .arg(zingo_source) - .arg(&zingo_dest) - .output() - .expect("wallet copy failed"); - let _cph = regtest_manager.launch(false).unwrap(); - println!("loading wallet"); + // let zcd_datadir = ®test_manager.zcashd_data_dir; + // let zingo_datadir = ®test_manager.zingo_datadir; + // // This test is the unique consumer of: + // // zingolib/src/testvectors/old_wallet_reorg_test_wallet + // let cached_data_dir = get_cargo_manifest_dir() + // .parent() + // .unwrap() + // .join("zingolib/src/testvectors") + // .join("old_wallet_reorg_test_wallet"); + // let zcd_source = cached_data_dir + // .join("zcashd") + // .join(".") + // .to_string_lossy() + // .to_string(); + // let zcd_dest = zcd_datadir.to_string_lossy().to_string(); + // std::process::Command::new("rm") + // .arg("-r") + // .arg(&zcd_dest) + // .output() + // .expect("directory rm failed"); + // std::fs::DirBuilder::new() + // .create(&zcd_dest) + // .expect("Dir recreate failed"); + // std::process::Command::new("cp") + // .arg("-r") + // .arg(zcd_source) + // .arg(zcd_dest) + // .output() + // .expect("directory copy failed"); + // let zingo_source = cached_data_dir + // .join("zingo-wallet.dat") + // .to_string_lossy() + // .to_string(); + // let zingo_dest = zingo_datadir.to_string_lossy().to_string(); + // std::process::Command::new("cp") + // .arg("-f") + // .arg(zingo_source) + // .arg(&zingo_dest) + // .output() + // .expect("wallet copy failed"); + // let _cph = regtest_manager.launch(false).unwrap(); + // println!("loading wallet"); - let wallet = examples::NetworkSeedVersion::Regtest( - examples::RegtestSeedVersion::HospitalMuseum(examples::HospitalMuseumVersion::V27), - ) - .load_example_wallet() - .await; + // let wallet = examples::NetworkSeedVersion::Regtest( + // examples::RegtestSeedVersion::HospitalMuseum(examples::HospitalMuseumVersion::V27), + // ) + // .load_example_wallet() + // .await; - // let wallet = zingolib::testutils::load_wallet( - // zingo_dest.into(), - // ChainType::Regtest(regtest_network), - // ) - // .await; - println!("setting uri"); - *wallet - .transaction_context - .config - .lightwalletd_uri - .write() - .unwrap() = faucet.get_server_uri(); - println!("creating lightclient"); - let recipient = LightClient::create_from_wallet_async(wallet).await.unwrap(); - println!( - "pre-sync transactions: {}", - recipient.do_list_transactions().await.pretty(2) - ); - let expected_pre_sync_transactions = r#"[ - { - "outgoing_metadata": [], - "amount": 100000, - "memo": "null, null", - "block_height": 3, - "pending": false, - "datetime": 1692212261, - "position": 0, - "txid": "7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e", - "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" - }, - { - "outgoing_metadata": [], - "amount": 50000, - "memo": "null, null", - "block_height": 8, - "pending": false, - "datetime": 1692212266, - "position": 0, - "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", - "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" - }, - { - "outgoing_metadata": [], - "amount": 30000, - "memo": "null, null", - "block_height": 9, - "pending": false, - "datetime": 1692212299, - "position": 0, - "txid": "0a014017add7dc9eb57ada3e70f905c9dce610ef055e135b03f4907dd5dc99a4", - "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" - } -]"#; - assert_eq!( - expected_pre_sync_transactions, - recipient.do_list_transactions().await.pretty(2) - ); - 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", - "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" - }, - { - "outgoing_metadata": [], - "amount": 50000, - "memo": "null, null", - "block_height": 8, - "pending": false, - "datetime": 1692212266, - "position": 0, - "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", - "zec_price": null, - "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" - } -]"#; - assert_eq!( - expected_post_sync_transactions, - recipient.do_list_transactions().await.pretty(2) - ); - let expected_post_sync_balance = PoolBalances { - sapling_balance: Some(0), - verified_sapling_balance: Some(0), - spendable_sapling_balance: Some(0), - unverified_sapling_balance: Some(0), - orchard_balance: Some(150000), - verified_orchard_balance: Some(150000), - spendable_orchard_balance: Some(150000), - unverified_orchard_balance: Some(0), - transparent_balance: Some(0), - }; - assert_eq!(expected_post_sync_balance, recipient.do_balance().await); - let missing_output_index = from_inputs::quick_send( - &recipient, - vec![(&get_base_address_macro!(faucet, "unified"), 14000, None)], - ) - .await; - if let Err(QuickSendError::ProposeSend(Proposal( - zcash_client_backend::data_api::error::Error::DataSource(zingolib::wallet::tx_map::TxMapTraitError::InputSource( - zingolib::wallet::transaction_records_by_id::trait_inputsource::InputSourceError::MissingOutputIndexes(output_error) - )), - ))) = missing_output_index { - let txid1 = utils::conversion::txid_from_hex_encoded_str("122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841").unwrap(); - let txid2 = utils::conversion::txid_from_hex_encoded_str("7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e").unwrap(); - let expected_txids = vec![txid1, txid2]; - // in case the txids are in reverse order - let missing_index_txids: Vec = output_error.into_iter().map(|(txid, _)| txid).collect(); - if missing_index_txids != expected_txids { - let expected_txids = vec![txid2, txid1]; - assert!(missing_index_txids == expected_txids, "{:?}\n\n{:?}", missing_index_txids, expected_txids); - } - }; - } + // // let wallet = zingolib::testutils::load_wallet( + // // zingo_dest.into(), + // // ChainType::Regtest(regtest_network), + // // ) + // // .await; + // println!("setting uri"); + // *wallet + // .transaction_context + // .config + // .lightwalletd_uri + // .write() + // .unwrap() = faucet.get_server_uri(); + // println!("creating lightclient"); + // let recipient = LightClient::create_from_wallet_async(wallet).await.unwrap(); + // println!( + // "pre-sync transactions: {}", + // recipient.do_list_transactions().await.pretty(2) + // ); + // let expected_pre_sync_transactions = r#"[ + // { + // "outgoing_metadata": [], + // "amount": 100000, + // "memo": "null, null", + // "block_height": 3, + // "pending": false, + // "datetime": 1692212261, + // "position": 0, + // "txid": "7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e", + // "zec_price": null, + // "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" + // }, + // { + // "outgoing_metadata": [], + // "amount": 50000, + // "memo": "null, null", + // "block_height": 8, + // "pending": false, + // "datetime": 1692212266, + // "position": 0, + // "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", + // "zec_price": null, + // "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" + // }, + // { + // "outgoing_metadata": [], + // "amount": 30000, + // "memo": "null, null", + // "block_height": 9, + // "pending": false, + // "datetime": 1692212299, + // "position": 0, + // "txid": "0a014017add7dc9eb57ada3e70f905c9dce610ef055e135b03f4907dd5dc99a4", + // "zec_price": null, + // "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" + // } + // ]"#; + // assert_eq!( + // expected_pre_sync_transactions, + // recipient.do_list_transactions().await.pretty(2) + // ); + // 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", + // "zec_price": null, + // "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" + // }, + // { + // "outgoing_metadata": [], + // "amount": 50000, + // "memo": "null, null", + // "block_height": 8, + // "pending": false, + // "datetime": 1692212266, + // "position": 0, + // "txid": "122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841", + // "zec_price": null, + // "address": "uregtest1wdukkmv5p5n824e8ytnc3m6m77v9vwwl7hcpj0wangf6z23f9x0fnaen625dxgn8cgp67vzw6swuar6uwp3nqywfvvkuqrhdjffxjfg644uthqazrtxhrgwac0a6ujzgwp8y9cwthjeayq8r0q6786yugzzyt9vevxn7peujlw8kp3vf6d8p4fvvpd8qd5p7xt2uagelmtf3vl6w3u8" + // } + // ]"#; + // assert_eq!( + // expected_post_sync_transactions, + // recipient.do_list_transactions().await.pretty(2) + // ); + // let expected_post_sync_balance = PoolBalances { + // sapling_balance: Some(0), + // verified_sapling_balance: Some(0), + // spendable_sapling_balance: Some(0), + // unverified_sapling_balance: Some(0), + // orchard_balance: Some(150000), + // verified_orchard_balance: Some(150000), + // spendable_orchard_balance: Some(150000), + // unverified_orchard_balance: Some(0), + // transparent_balance: Some(0), + // }; + // assert_eq!(expected_post_sync_balance, recipient.do_balance().await); + // let missing_output_index = from_inputs::quick_send( + // &recipient, + // vec![(&get_base_address_macro!(faucet, "unified"), 14000, None)], + // ) + // .await; + // if let Err(QuickSendError::ProposeSend(Proposal( + // zcash_client_backend::data_api::error::Error::DataSource(zingolib::wallet::tx_map::TxMapTraitError::InputSource( + // zingolib::wallet::transaction_records_by_id::trait_inputsource::InputSourceError::MissingOutputIndexes(output_error) + // )), + // ))) = missing_output_index { + // let txid1 = utils::conversion::txid_from_hex_encoded_str("122f8ab8dc5483e36256a4fbd7ff8d60eb7196670716a6690f9215f1c2a4d841").unwrap(); + // let txid2 = utils::conversion::txid_from_hex_encoded_str("7a9d41caca143013ebd2f710e4dad04f0eb9f0ae98b42af0f58f25c61a9d439e").unwrap(); + // let expected_txids = vec![txid1, txid2]; + // // in case the txids are in reverse order + // let missing_index_txids: Vec = output_error.into_iter().map(|(txid, _)| txid).collect(); + // if missing_index_txids != expected_txids { + // let expected_txids = vec![txid2, txid1]; + // assert!(missing_index_txids == expected_txids, "{:?}\n\n{:?}", missing_index_txids, expected_txids); + // } + // }; + // } - #[tokio::test] - async fn pending_notes_are_not_saved() { - let mut environment = LibtonodeEnvironment::setup().await; + // FIXME: sync integration + // #[tokio::test] + // async fn pending_notes_are_not_saved() { + // let mut environment = LibtonodeEnvironment::setup().await; - let faucet = environment.create_faucet().await; - let recipient = environment.create_client().await; + // let faucet = environment.create_faucet().await; + // let recipient = environment.create_client().await; - environment.bump_chain().await; - faucet.do_sync(false).await.unwrap(); + // environment.bump_chain().await; + // faucet.do_sync(false).await.unwrap(); - check_client_balances!(faucet, o: 0 s: 2_500_000_000u64 t: 0u64); + // check_client_balances!(faucet, o: 0 s: 2_500_000_000u64 t: 0u64); - let pending_txid = *from_inputs::quick_send( - &faucet, - vec![( - get_base_address_macro!(recipient, "unified").as_str(), - 5_000, - Some("this note never makes it to the wallet! or chain"), - )], - ) - .await - .unwrap() - .first(); + // let pending_txid = *from_inputs::quick_send( + // &faucet, + // vec![( + // get_base_address_macro!(recipient, "unified").as_str(), + // 5_000, + // Some("this note never makes it to the wallet! or chain"), + // )], + // ) + // .await + // .unwrap() + // .first(); - assert!(!faucet - .transaction_summaries() - .await - .iter() - .find(|transaction_summary| transaction_summary.txid() == pending_txid) - .unwrap() - .status() - .is_confirmed()); + // assert!(!faucet + // .transaction_summaries() + // .await + // .iter() + // .find(|transaction_summary| transaction_summary.txid() == pending_txid) + // .unwrap() + // .status() + // .is_confirmed()); - assert_eq!( - faucet.do_list_notes(true).await["unspent_orchard_notes"].len(), - 1 - ); + // assert_eq!( + // faucet.do_list_notes(true).await["unspent_orchard_notes"].len(), + // 1 + // ); - // Create a new client using the faucet's wallet - let faucet_reloaded = { - let mut wallet_location = environment.scenario_builder.regtest_manager.zingo_datadir; - wallet_location.pop(); - wallet_location.push("zingo_client_1"); - // Create zingo config - let zingo_config = ZingoConfig::build(zingolib::config::ChainType::Regtest( - environment.regtest_network, - )) - .set_wallet_dir(wallet_location.clone()) - .create(); - wallet_location.push("zingo-wallet.dat"); - let read_buffer = File::open(wallet_location.clone()).unwrap(); + // // Create a new client using the faucet's wallet + // let faucet_reloaded = { + // let mut wallet_location = environment.scenario_builder.regtest_manager.zingo_datadir; + // wallet_location.pop(); + // wallet_location.push("zingo_client_1"); + // // Create zingo config + // let zingo_config = ZingoConfig::build(zingolib::config::ChainType::Regtest( + // environment.regtest_network, + // )) + // .set_wallet_dir(wallet_location.clone()) + // .create(); + // wallet_location.push("zingo-wallet.dat"); + // let read_buffer = File::open(wallet_location.clone()).unwrap(); - // Create wallet from faucet zingo-wallet.dat - let faucet_wallet = - zingolib::wallet::LightWallet::read_internal(read_buffer, &zingo_config) - .await - .unwrap(); + // // Create wallet from faucet zingo-wallet.dat + // let faucet_wallet = + // zingolib::wallet::LightWallet::read_internal(read_buffer, &zingo_config) + // .await + // .unwrap(); - // Create client based on config and wallet of faucet - LightClient::create_from_wallet_async(faucet_wallet) - .await - .unwrap() - }; + // // Create client based on config and wallet of faucet + // LightClient::create_from_wallet_async(faucet_wallet) + // .await + // .unwrap() + // }; - assert_eq!( - &faucet_reloaded.do_seed_phrase().await.unwrap(), - &faucet.do_seed_phrase().await.unwrap() - ); // Sanity check identity + // assert_eq!( + // &faucet_reloaded.do_seed_phrase().await.unwrap(), + // &faucet.do_seed_phrase().await.unwrap() + // ); // Sanity check identity - // assert that the unconfirmed unspent orchard note was dropped - assert_eq!( - faucet_reloaded.do_list_notes(true).await["unspent_orchard_notes"].len(), - 0 - ); + // // assert that the unconfirmed unspent orchard note was dropped + // assert_eq!( + // faucet_reloaded.do_list_notes(true).await["unspent_orchard_notes"].len(), + // 0 + // ); - // assert that the unconfirmed transaction was dropped - assert!(!faucet_reloaded - .transaction_summaries() - .await - .iter() - .any(|transaction_summary| transaction_summary.txid() == pending_txid)); + // // assert that the unconfirmed transaction was dropped + // assert!(!faucet_reloaded + // .transaction_summaries() + // .await + // .iter() + // .any(|transaction_summary| transaction_summary.txid() == pending_txid)); - // sanity-check that the other transactions in the wallet are identical. - let mut faucet_transactions = faucet.do_list_transactions().await; - let mut faucet_reloaded_transactions = faucet_reloaded.do_list_transactions().await; + // // sanity-check that the other transactions in the wallet are identical. + // let mut faucet_transactions = faucet.do_list_transactions().await; + // let mut faucet_reloaded_transactions = faucet_reloaded.do_list_transactions().await; - faucet_transactions.pop(); - faucet_transactions.pop(); - faucet_reloaded_transactions.pop(); - assert_eq!(faucet_transactions, faucet_reloaded_transactions); - } + // faucet_transactions.pop(); + // faucet_transactions.pop(); + // faucet_reloaded_transactions.pop(); + // assert_eq!(faucet_transactions, faucet_reloaded_transactions); + // } #[tokio::test] async fn verify_old_wallet_uses_server_height_in_send() { diff --git a/zingo-sync/Cargo.toml b/zingo-sync/Cargo.toml index 426b406ca..a790880cc 100644 --- a/zingo-sync/Cargo.toml +++ b/zingo-sync/Cargo.toml @@ -3,6 +3,9 @@ name = "zingo-sync" version = "0.1.0" edition = "2021" +[features] +wallet_pack = [] + [dependencies] # Zingo zingo-netutils = { path = "../zingo-netutils" } diff --git a/zingo-sync/src/lib.rs b/zingo-sync/src/lib.rs index d7b1e0a49..f1603458c 100644 --- a/zingo-sync/src/lib.rs +++ b/zingo-sync/src/lib.rs @@ -10,8 +10,8 @@ //! Entrypoint: [`crate::sync::sync`] //! //! Terminology: -//! Chain height - highest block height of best chain from the server -//! Wallet height - highest block height of blockchain known to the wallet. Commonly used, to determine the chain height +//! Chain height - highest block height of best chain from the server. +//! Wallet height - highest block height of blockchain known to the wallet. Commonly used to determine the chain height //! of the previous sync, before the server is contacted to update the wallet height to the new chain height. //! Fully scanned height - block height in which the wallet has completed scanning all blocks equal to and below this height. @@ -25,3 +25,5 @@ pub mod sync; pub mod traits; pub(crate) mod utils; pub mod witness; + +pub(crate) const MAX_BATCH_OUTPUTS: usize = 16_384; // 2^14 diff --git a/zingo-sync/src/primitives.rs b/zingo-sync/src/primitives.rs index 42e56d8c1..c82b1f4b2 100644 --- a/zingo-sync/src/primitives.rs +++ b/zingo-sync/src/primitives.rs @@ -248,6 +248,7 @@ pub struct WalletBlock { #[getset(skip)] txids: Vec, tree_boundaries: TreeBoundaries, + // TODO: optional price } impl WalletBlock { @@ -282,7 +283,9 @@ pub struct WalletTransaction { #[getset(get = "pub")] transaction: zcash_primitives::transaction::Transaction, #[getset(get_copy = "pub")] - confirmation_status: ConfirmationStatus, + status: ConfirmationStatus, + #[getset(skip)] + transparent_coins: Vec, #[getset(skip)] sapling_notes: Vec, #[getset(skip)] @@ -291,8 +294,6 @@ pub struct WalletTransaction { outgoing_sapling_notes: Vec, #[getset(skip)] outgoing_orchard_notes: Vec, - #[getset(skip)] - transparent_coins: Vec, } impl WalletTransaction { @@ -300,17 +301,17 @@ impl WalletTransaction { pub fn from_parts( txid: TxId, transaction: zcash_primitives::transaction::Transaction, - confirmation_status: ConfirmationStatus, + status: ConfirmationStatus, + transparent_coins: Vec, sapling_notes: Vec, orchard_notes: Vec, outgoing_sapling_notes: Vec, outgoing_orchard_notes: Vec, - transparent_coins: Vec, ) -> Self { Self { txid, transaction, - confirmation_status, + status, sapling_notes, orchard_notes, outgoing_sapling_notes, @@ -319,6 +320,13 @@ impl WalletTransaction { } } + pub fn transparent_coins(&self) -> &[TransparentCoin] { + &self.transparent_coins + } + + pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> { + self.transparent_coins.iter_mut().collect() + } pub fn sapling_notes(&self) -> &[SaplingNote] { &self.sapling_notes } @@ -342,54 +350,39 @@ impl WalletTransaction { pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] { &self.outgoing_orchard_notes } - - pub fn transparent_coins(&self) -> &[TransparentCoin] { - &self.transparent_coins - } - - pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> { - self.transparent_coins.iter_mut().collect() - } } impl std::fmt::Debug for WalletTransaction { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("WalletTransaction") - .field("confirmation_status", &self.confirmation_status) + .field("confirmation_status", &self.status) + .field("transparent_coins", &self.transparent_coins) .field("sapling_notes", &self.sapling_notes) .field("orchard_notes", &self.orchard_notes) .field("outgoing_sapling_notes", &self.outgoing_sapling_notes) .field("outgoing_orchard_notes", &self.outgoing_orchard_notes) - .field("transparent_coins", &self.transparent_coins) .finish() } } -pub type SaplingNote = WalletNote; -pub type OrchardNote = WalletNote; /// Wallet note, shielded output with metadata relevant to the wallet -#[derive(Debug, Getters, CopyGetters, Setters)] +#[derive(Debug, Clone)] pub struct WalletNote { /// Output ID - #[getset(get_copy = "pub")] - output_id: OutputId, + pub(crate) output_id: OutputId, /// Identifier for key used to decrypt output - #[getset(get_copy = "pub")] - key_id: KeyId, + pub(crate) key_id: KeyId, /// Decrypted note with recipient and value - #[getset(get = "pub")] - note: N, + pub(crate) note: N, /// Derived nullifier - #[getset(get_copy = "pub")] - nullifier: Option, //TODO: syncing without nullifier deriving key + pub(crate) nullifier: Option, //TODO: syncing without nullifier deriving key /// Commitment tree leaf position - #[getset(get_copy = "pub")] - position: Option, + pub(crate) position: Option, /// Memo - #[getset(get = "pub")] - memo: Memo, - #[getset(get = "pub", set = "pub")] - spending_transaction: Option, + pub(crate) memo: Memo, + /// Transaction this note was spent in. + /// If `None`, note is not spent. + pub(crate) spending_transaction: Option, } impl WalletNote { @@ -414,6 +407,125 @@ impl WalletNote { } } +pub trait NoteInterface: Sized { + type ZcashNote; + type Nullifier: Copy; + + /// Output ID + fn output_id(&self) -> OutputId; + + /// Identifier for key used to decrypt output + fn key_id(&self) -> KeyId; + + /// Decrypted note with recipient and value + fn note(&self) -> &Self::ZcashNote; + + /// Derived nullifier + fn nullifier(&self) -> Option; + + /// Commitment tree leaf position + fn position(&self) -> Option; + + /// Memo + fn memo(&self) -> &Memo; + + /// Txid of transaction this note was spent in. + /// If `None`, note is not spent. + fn spending_transaction(&self) -> Option; + + /// Note value. + fn value(&self) -> u64; + + /// Notes within `transaction`. + fn transaction_notes(transaction: &WalletTransaction) -> &[Self]; +} + +pub type SaplingNote = WalletNote; + +impl NoteInterface for SaplingNote { + type ZcashNote = sapling_crypto::Note; + type Nullifier = sapling_crypto::Nullifier; + + fn output_id(&self) -> OutputId { + self.output_id + } + + fn key_id(&self) -> KeyId { + self.key_id + } + + fn note(&self) -> &Self::ZcashNote { + &self.note + } + + fn nullifier(&self) -> Option { + self.nullifier + } + + fn position(&self) -> Option { + self.position + } + + fn memo(&self) -> &Memo { + &self.memo + } + + fn spending_transaction(&self) -> Option { + self.spending_transaction + } + + fn value(&self) -> u64 { + self.note.value().inner() + } + + fn transaction_notes(transaction: &WalletTransaction) -> &[SaplingNote] { + transaction.sapling_notes() + } +} + +pub type OrchardNote = WalletNote; + +impl NoteInterface for OrchardNote { + type ZcashNote = orchard::Note; + type Nullifier = orchard::note::Nullifier; + + fn output_id(&self) -> OutputId { + self.output_id + } + + fn key_id(&self) -> KeyId { + self.key_id + } + + fn note(&self) -> &Self::ZcashNote { + &self.note + } + + fn nullifier(&self) -> Option { + self.nullifier + } + + fn position(&self) -> Option { + self.position + } + + fn memo(&self) -> &Memo { + &self.memo + } + + fn spending_transaction(&self) -> Option { + self.spending_transaction + } + + fn value(&self) -> u64 { + self.note.value().inner() + } + + fn transaction_notes(transaction: &WalletTransaction) -> &[OrchardNote] { + transaction.orchard_notes() + } +} + pub type OutgoingSaplingNote = OutgoingNote; pub type OutgoingOrchardNote = OutgoingNote; diff --git a/zingo-sync/src/scan/compact_blocks.rs b/zingo-sync/src/scan/compact_blocks.rs index 6a7beb3bf..ffdcfb750 100644 --- a/zingo-sync/src/scan/compact_blocks.rs +++ b/zingo-sync/src/scan/compact_blocks.rs @@ -23,6 +23,7 @@ use crate::{ keys::{KeyId, ScanningKeyOps, ScanningKeys}, primitives::{NullifierMap, OutputId, TreeBoundaries, WalletBlock}, witness::WitnessData, + MAX_BATCH_OUTPUTS, }; use self::runners::{BatchRunners, DecryptedOutput}; @@ -34,8 +35,7 @@ use super::{ mod runners; -// TODO: move parameters to config module -const TRIAL_DECRYPT_TASK_SIZE: usize = 1_024; // 2^10 +const TRIAL_DECRYPT_TASK_SIZE: usize = MAX_BATCH_OUTPUTS / 16; pub(crate) fn scan_compact_blocks

( compact_blocks: Vec, diff --git a/zingo-sync/src/scan/task.rs b/zingo-sync/src/scan/task.rs index e0424d097..1ee965ae9 100644 --- a/zingo-sync/src/scan/task.rs +++ b/zingo-sync/src/scan/task.rs @@ -28,12 +28,12 @@ use crate::{ primitives::{Locator, WalletBlock}, sync, traits::{SyncBlocks, SyncWallet}, + MAX_BATCH_OUTPUTS, }; use super::{compact_blocks::calculate_block_tree_boundaries, error::ScanError, scan, ScanResults}; const MAX_WORKER_POOLSIZE: usize = 2; -const MAX_BATCH_OUTPUTS: usize = 8_192; // 2^13 pub(crate) enum ScannerState { Verification, diff --git a/zingo-sync/src/scan/transactions.rs b/zingo-sync/src/scan/transactions.rs index be100cfcf..0d5939c5f 100644 --- a/zingo-sync/src/scan/transactions.rs +++ b/zingo-sync/src/scan/transactions.rs @@ -297,11 +297,11 @@ pub(crate) fn scan_transaction( transaction.txid(), transaction, confirmation_status, + transparent_coins, sapling_notes, orchard_notes, outgoing_sapling_notes, outgoing_orchard_notes, - transparent_coins, )) } @@ -430,7 +430,7 @@ fn parse_encoded_memos( let encoded_memos = wallet_notes .iter() .flat_map(|note| { - if let Memo::Arbitrary(ref encoded_memo_bytes) = note.memo() { + if let Memo::Arbitrary(ref encoded_memo_bytes) = note.memo { Some(zingo_memo::parse_zingo_memo(*encoded_memo_bytes.as_ref()).unwrap()) } else { None diff --git a/zingo-sync/src/sync.rs b/zingo-sync/src/sync.rs index 87d28b652..ee6401001 100644 --- a/zingo-sync/src/sync.rs +++ b/zingo-sync/src/sync.rs @@ -38,7 +38,7 @@ pub(crate) mod state; pub(crate) mod transparent; const VERIFY_BLOCK_RANGE_SIZE: u32 = 10; -const MAX_VERIFICATION_WINDOW: u32 = 100; +pub(crate) const MAX_VERIFICATION_WINDOW: u32 = 100; /// Syncs a wallet to the latest state of the blockchain pub async fn sync( @@ -377,7 +377,7 @@ async fn process_mempool_transaction( .unwrap() .get(&transaction.txid()) { - if tx.confirmation_status().is_confirmed() { + if tx.status().is_confirmed() { return; } } @@ -537,7 +537,7 @@ where .get_wallet_transactions() .unwrap() .values() - .filter_map(|tx| tx.confirmation_status().get_confirmed_height()) + .filter_map(|tx| tx.status().get_confirmed_height()) .collect::>(); wallet.get_wallet_blocks_mut().unwrap().retain(|height, _| { diff --git a/zingo-sync/src/sync/spend.rs b/zingo-sync/src/sync/spend.rs index 1767885c6..91a0e4c71 100644 --- a/zingo-sync/src/sync/spend.rs +++ b/zingo-sync/src/sync/spend.rs @@ -95,12 +95,12 @@ pub(super) fn collect_derived_nullifiers( let sapling_nullifiers = wallet_transactions .values() .flat_map(|tx| tx.sapling_notes()) - .flat_map(|note| note.nullifier()) + .flat_map(|note| note.nullifier) .collect::>(); let orchard_nullifiers = wallet_transactions .values() .flat_map(|tx| tx.orchard_notes()) - .flat_map(|note| note.nullifier()) + .flat_map(|note| note.nullifier) .collect::>(); (sapling_nullifiers, orchard_nullifiers) @@ -139,10 +139,10 @@ pub(super) fn update_spent_notes( .flat_map(|tx| tx.sapling_notes_mut()) .for_each(|note| { if let Some((_, txid)) = note - .nullifier() + .nullifier .and_then(|nf| sapling_spend_locators.get(&nf)) { - note.set_spending_transaction(Some(*txid)); + note.spending_transaction = Some(*txid); } }); wallet_transactions @@ -150,10 +150,10 @@ pub(super) fn update_spent_notes( .flat_map(|tx| tx.orchard_notes_mut()) .for_each(|note| { if let Some((_, txid)) = note - .nullifier() + .nullifier .and_then(|nf| orchard_spend_locators.get(&nf)) { - note.set_spending_transaction(Some(*txid)); + note.spending_transaction = Some(*txid); } }); } diff --git a/zingo-sync/src/sync/state.rs b/zingo-sync/src/sync/state.rs index c5af83cc6..dfb04207d 100644 --- a/zingo-sync/src/sync/state.rs +++ b/zingo-sync/src/sync/state.rs @@ -902,7 +902,7 @@ pub(super) fn update_found_note_shard_priority( sync_state, shielded_protocol, ( - wallet_transaction.confirmation_status().get_height(), + wallet_transaction.status().get_height(), wallet_transaction.txid(), ), ) diff --git a/zingo-sync/src/traits.rs b/zingo-sync/src/traits.rs index d797c13dc..c6a637b94 100644 --- a/zingo-sync/src/traits.rs +++ b/zingo-sync/src/traits.rs @@ -119,10 +119,7 @@ pub trait SyncTransactions: SyncWallet { let invalid_txids: Vec = self .get_wallet_transactions()? .values() - .filter(|tx| { - tx.confirmation_status() - .is_confirmed_after(&truncate_height) - }) + .filter(|tx| tx.status().is_confirmed_after(&truncate_height)) .map(|tx| tx.transaction().txid()) .collect(); @@ -131,25 +128,25 @@ pub trait SyncTransactions: SyncWallet { .values_mut() .flat_map(|tx| tx.sapling_notes_mut()) .filter(|note| { - note.spending_transaction().map_or_else( + note.spending_transaction.map_or_else( || false, |spending_txid| invalid_txids.contains(&spending_txid), ) }) .for_each(|note| { - note.set_spending_transaction(None); + note.spending_transaction = None; }); wallet_transactions .values_mut() .flat_map(|tx| tx.orchard_notes_mut()) .filter(|note| { - note.spending_transaction().map_or_else( + note.spending_transaction.map_or_else( || false, |spending_txid| invalid_txids.contains(&spending_txid), ) }) .for_each(|note| { - note.set_spending_transaction(None); + note.spending_transaction = None; }); invalid_txids.iter().for_each(|invalid_txid| { diff --git a/zingo-sync/src/witness.rs b/zingo-sync/src/witness.rs index 6e0f49012..3cece47ec 100644 --- a/zingo-sync/src/witness.rs +++ b/zingo-sync/src/witness.rs @@ -12,10 +12,11 @@ use shardtree::{ use zcash_client_backend::proto::service::SubtreeRoot; use zcash_primitives::consensus::BlockHeight; +use crate::{sync::MAX_VERIFICATION_WINDOW, MAX_BATCH_OUTPUTS}; + const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32; const SHARD_HEIGHT: u8 = 16; -const MAX_CHECKPOINTS: usize = 100; -const LOCATED_TREE_SIZE: usize = 128; +const LOCATED_TREE_SIZE: usize = MAX_BATCH_OUTPUTS / 16; type SaplingShardStore = MemoryShardStore; type OrchardShardStore = MemoryShardStore; @@ -34,8 +35,8 @@ impl ShardTrees { /// Create new ShardTrees pub fn new() -> Self { Self { - sapling: ShardTree::new(MemoryShardStore::empty(), MAX_CHECKPOINTS), - orchard: ShardTree::new(MemoryShardStore::empty(), MAX_CHECKPOINTS), + sapling: ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize), + orchard: ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize), } } } diff --git a/zingolib/Cargo.toml b/zingolib/Cargo.toml index 0ebc27227..8d1628792 100644 --- a/zingolib/Cargo.toml +++ b/zingolib/Cargo.toml @@ -18,7 +18,7 @@ zaino-test = ['test-elevation'] zingo-memo = { path = "../zingo-memo" } zingo-status = { path = "../zingo-status" } zingo-netutils = { path = "../zingo-netutils" } -zingo-sync = { path = "../zingo-sync" } +zingo-sync = { path = "../zingo-sync", features = [ "wallet_pack" ] } testvectors = { path = "../testvectors", optional = true } orchard = { workspace = true } diff --git a/zingolib/src/lib.rs b/zingolib/src/lib.rs index 7edf38837..cc57a465e 100644 --- a/zingolib/src/lib.rs +++ b/zingolib/src/lib.rs @@ -4,7 +4,10 @@ //! Zingo backend code base //! Use this high level API to do things like submit transactions to the zcash blockchain +use zcash_client_backend::ShieldedProtocol; + pub use lightclient::describe::UAReceivers; + #[macro_use] extern crate rust_embed; @@ -41,3 +44,25 @@ pub fn get_latest_block_height(lightwalletd_uri: http::Uri) -> std::io::Result( - &'a self, - transaction_metadata: &'b TransactionRecord, - unified_spend_auth: &'c crate::wallet::keys::unified::WalletCapability, - ) -> impl Iterator + 'b - where - 'a: 'b, - 'c: 'b, - { - self.add_wallet_notes_in_transaction_to_list_inner::<'a, 'b, 'c, sapling_crypto::note_encryption::SaplingDomain>( - transaction_metadata, - unified_spend_auth, - ) - .chain( - self.add_wallet_notes_in_transaction_to_list_inner::<'a, 'b, 'c, orchard::note_encryption::OrchardDomain>( - transaction_metadata, - unified_spend_auth, - ), - ) - } - - fn add_wallet_notes_in_transaction_to_list_inner<'a, 'b, 'c, D>( - &'a self, - transaction_metadata: &'b TransactionRecord, - unified_spend_auth: &'c crate::wallet::keys::unified::WalletCapability, - ) -> impl Iterator + 'b - where - 'a: 'b, - 'c: 'b, - D: crate::wallet::traits::DomainWalletExt, - D::WalletNote: 'b, - ::Recipient: crate::wallet::traits::Recipient, - ::Note: PartialEq + Clone, - { - D::WalletNote::transaction_metadata_notes(transaction_metadata).iter().filter(|nd| !nd.is_change()).enumerate().map(|(i, nd)| { - let block_height: u32 = transaction_metadata.status.get_height().into(); - object! { - "block_height" => block_height, - "pending" => !transaction_metadata.status.is_confirmed(), - "datetime" => transaction_metadata.datetime, - "position" => i, - "txid" => format!("{}", transaction_metadata.txid), - "amount" => nd.value() as i64, - "zec_price" => transaction_metadata.price.map(|p| (p * 100.0).round() / 100.0), - "address" => LightWallet::note_address::(&self.config.chain, nd, unified_spend_auth), - "memo" => LightWallet::memo_str(nd.memo().clone()) - } - - }) - } - - #[allow(deprecated)] - fn append_change_notes( - wallet_transaction: &TransactionRecord, - received_utxo_value: u64, - ) -> JsonValue { - // TODO: Understand why sapling and orchard have an "is_change" filter, but transparent does not - // It seems like this already depends on an invariant where all outgoing utxos are change. - // This should never be true _AFTER SOME VERSION_ since we only send change to orchard. - // If I sent a transaction to a foreign transparent address wouldn't this "total_change" value - // be wrong? - let total_change = wallet_transaction - .sapling_notes - .iter() - .filter(|nd| nd.is_change) - .map(|nd| nd.sapling_crypto_note.value().inner()) - .sum::() - + wallet_transaction - .orchard_notes - .iter() - .filter(|nd| nd.is_change) - .map(|nd| nd.orchard_crypto_note.value().inner()) - .sum::() - + received_utxo_value; - - // Collect outgoing metadata - let outgoing_json = wallet_transaction - .outgoing_tx_data - .iter() - .map(|om| { - object! { - // Is this address ever different than the address in the containing struct - // this is the full UA. - "address" => om.recipient_ua.clone().unwrap_or(om.recipient_address.clone()), - "value" => om.value, - "memo" => LightWallet::memo_str(Some(om.memo.clone())) - } - }) - .collect::>(); - - let block_height: u32 = wallet_transaction.status.get_height().into(); - object! { - "block_height" => block_height, - "pending" => !wallet_transaction.status.is_confirmed(), - "datetime" => wallet_transaction.datetime, - "txid" => format!("{}", wallet_transaction.txid), - "zec_price" => wallet_transaction.price.map(|p| (p * 100.0).round() / 100.0), - "amount" => total_change as i64 - wallet_transaction.total_value_spent() as i64, - "outgoing_metadata" => outgoing_json, - } - } - - /// TODO: Add Doc Comment Here! - #[allow(deprecated)] - //TODO: add this flag and address warnings - //#[deprecated = "please use transaction_summaries"] - pub async fn do_list_transactions(&self) -> JsonValue { - // Create a list of TransactionItems from wallet transactions - let wallet = self.wallet.lock().await; - let mut consumer_ui_notes = - wallet - .transaction_context.transaction_metadata_set - .read() - .await - .transaction_records_by_id - .iter() - .flat_map(|(txid, wallet_transaction)| { - let mut consumer_notes_by_tx: Vec = vec![]; - - let total_transparent_received = wallet_transaction.transparent_outputs.iter().map(|u| u.value).sum::(); - if wallet_transaction.is_outgoing_transaction() { - // If money was spent, create a consumer_ui_note. For this, we'll subtract - // all the change notes + Utxos - consumer_notes_by_tx.push(Self::append_change_notes(wallet_transaction, total_transparent_received)); - } - - // For each note that is not a change, add a consumer_ui_note. - consumer_notes_by_tx.extend(self.add_nonchange_notes(wallet_transaction, &wallet.wallet_capability())); - - // TODO: determine if all notes are either Change-or-NotChange, if that's the case - // add a sanity check that asserts all notes are processed by this point - - // Get the total transparent value received in this transaction - // Again we see the assumption that utxos are incoming. - let net_transparent_value = total_transparent_received as i64 - wallet_transaction.total_transparent_value_spent as i64; - let address = wallet_transaction.transparent_outputs.iter().map(|utxo| utxo.address.clone()).collect::>().join(","); - if net_transparent_value > 0 { - if let Some(transaction) = consumer_notes_by_tx.iter_mut().find(|transaction| transaction["txid"] == txid.to_string()) { - // If this transaction is outgoing: - // Then we've already accounted for the entire balance. - - if !wallet_transaction.is_outgoing_transaction() { - // If not, we've added sapling/orchard, and need to add transparent - let old_amount = transaction.remove("amount").as_i64().unwrap(); - transaction.insert("amount", old_amount + net_transparent_value).unwrap(); - } - } else { - // Create an input transaction for the transparent value as well. - let block_height: u32 = wallet_transaction.status.get_height().into(); - consumer_notes_by_tx.push(object! { - "block_height" => block_height, - "pending" => !wallet_transaction.status.is_confirmed(), - "datetime" => wallet_transaction.datetime, - "txid" => format!("{}", txid), - "amount" => net_transparent_value, - "zec_price" => wallet_transaction.price.map(|p| (p * 100.0).round() / 100.0), - "address" => address, - "memo" => None:: - }) - } - } - - consumer_notes_by_tx - }) - .collect::>(); - - let match_by_txid = - |a: &JsonValue, b: &JsonValue| a["txid"].to_string().cmp(&b["txid"].to_string()); - consumer_ui_notes.sort_by(match_by_txid); - consumer_ui_notes.dedup_by(|a, b| { - if match_by_txid(a, b) == cmp::Ordering::Equal { - let val_b = b.remove("amount").as_i64().unwrap(); - b.insert( - "amount", - JsonValue::from(val_b + a.remove("amount").as_i64().unwrap()), - ) - .unwrap(); - let memo_b = b.remove("memo").to_string(); - b.insert("memo", [a.remove("memo").to_string(), memo_b].join(", ")) - .unwrap(); - for (key, a_val) in a.entries_mut() { - let b_val = b.remove(key); - if b_val == JsonValue::Null { - b.insert(key, a_val.clone()).unwrap(); - } else { - if a_val != &b_val { - log::error!("{key}: {a_val} does not match {key}: {b_val}"); - } - b.insert(key, b_val).unwrap() - } - } - - true - } else { - false - } - }); - consumer_ui_notes.sort_by(|a, b| { - if a["block_height"] == b["block_height"] { - a["txid"].as_str().cmp(&b["txid"].as_str()) - } else { - a["block_height"].as_i32().cmp(&b["block_height"].as_i32()) - } - }); - - JsonValue::Array(consumer_ui_notes) - } + // FIXME: sync integration + // fn add_nonchange_notes<'a, 'b, 'c>( + // &'a self, + // transaction_metadata: &'b TransactionRecord, + // unified_spend_auth: &'c crate::wallet::keys::unified::WalletCapability, + // ) -> impl Iterator + 'b + // where + // 'a: 'b, + // 'c: 'b, + // { + // self.add_wallet_notes_in_transaction_to_list_inner::<'a, 'b, 'c, sapling_crypto::note_encryption::SaplingDomain>( + // transaction_metadata, + // unified_spend_auth, + // ) + // .chain( + // self.add_wallet_notes_in_transaction_to_list_inner::<'a, 'b, 'c, orchard::note_encryption::OrchardDomain>( + // transaction_metadata, + // unified_spend_auth, + // ), + // ) + // } + + // fn add_wallet_notes_in_transaction_to_list_inner<'a, 'b, 'c, D>( + // &'a self, + // transaction_metadata: &'b TransactionRecord, + // unified_spend_auth: &'c crate::wallet::keys::unified::WalletCapability, + // ) -> impl Iterator + 'b + // where + // 'a: 'b, + // 'c: 'b, + // D: crate::wallet::traits::DomainWalletExt, + // D::WalletNote: 'b, + // ::Recipient: crate::wallet::traits::Recipient, + // ::Note: PartialEq + Clone, + // { + // D::WalletNote::transaction_metadata_notes(transaction_metadata).iter().filter(|nd| !nd.is_change()).enumerate().map(|(i, nd)| { + // let block_height: u32 = transaction_metadata.status.get_height().into(); + // object! { + // "block_height" => block_height, + // "pending" => !transaction_metadata.status.is_confirmed(), + // "datetime" => transaction_metadata.datetime, + // "position" => i, + // "txid" => format!("{}", transaction_metadata.txid), + // "amount" => nd.value() as i64, + // "zec_price" => transaction_metadata.price.map(|p| (p * 100.0).round() / 100.0), + // "address" => LightWallet::note_address::(&self.config.chain, nd, unified_spend_auth), + // "memo" => LightWallet::memo_str(nd.memo().clone()) + // } + + // }) + // } + + // #[allow(deprecated)] + // fn append_change_notes( + // wallet_transaction: &TransactionRecord, + // received_utxo_value: u64, + // ) -> JsonValue { + // // TODO: Understand why sapling and orchard have an "is_change" filter, but transparent does not + // // It seems like this already depends on an invariant where all outgoing utxos are change. + // // This should never be true _AFTER SOME VERSION_ since we only send change to orchard. + // // If I sent a transaction to a foreign transparent address wouldn't this "total_change" value + // // be wrong? + // let total_change = wallet_transaction + // .sapling_notes + // .iter() + // .filter(|nd| nd.is_change) + // .map(|nd| nd.sapling_crypto_note.value().inner()) + // .sum::() + // + wallet_transaction + // .orchard_notes + // .iter() + // .filter(|nd| nd.is_change) + // .map(|nd| nd.orchard_crypto_note.value().inner()) + // .sum::() + // + received_utxo_value; + + // // Collect outgoing metadata + // let outgoing_json = wallet_transaction + // .outgoing_tx_data + // .iter() + // .map(|om| { + // object! { + // // Is this address ever different than the address in the containing struct + // // this is the full UA. + // "address" => om.recipient_ua.clone().unwrap_or(om.recipient_address.clone()), + // "value" => om.value, + // "memo" => LightWallet::memo_str(Some(om.memo.clone())) + // } + // }) + // .collect::>(); + + // let block_height: u32 = wallet_transaction.status.get_height().into(); + // object! { + // "block_height" => block_height, + // "pending" => !wallet_transaction.status.is_confirmed(), + // "datetime" => wallet_transaction.datetime, + // "txid" => format!("{}", wallet_transaction.txid), + // "zec_price" => wallet_transaction.price.map(|p| (p * 100.0).round() / 100.0), + // "amount" => total_change as i64 - wallet_transaction.total_value_spent() as i64, + // "outgoing_metadata" => outgoing_json, + // } + // } } diff --git a/zingolib/src/lightclient/describe.rs b/zingolib/src/lightclient/describe.rs index da88d7e02..c8483c94c 100644 --- a/zingolib/src/lightclient/describe.rs +++ b/zingolib/src/lightclient/describe.rs @@ -1,7 +1,6 @@ //! These functions can be called by consumer to learn about the LightClient. use ::orchard::note_encryption::OrchardDomain; use json::{object, JsonValue}; -use sapling_crypto::note_encryption::SaplingDomain; use std::{cmp::Ordering, collections::HashMap}; use tokio::runtime::Runtime; @@ -26,6 +25,7 @@ use crate::{ transaction_record::{SendType, TransactionKind}, LightWallet, }, + Orchard, Sapling, }; #[allow(missing_docs)] @@ -105,14 +105,14 @@ impl LightClient { /// PoolType variant as an argument, and iterate over a `Vec` pub async fn do_balance(&self) -> PoolBalances { let wallet = self.wallet.lock().await; - let verified_sapling_balance = wallet.confirmed_balance::().await; - let unverified_sapling_balance = wallet.pending_balance::().await; - let spendable_sapling_balance = wallet.spendable_balance::().await; + let verified_sapling_balance = wallet.confirmed_balance::().await; + let unverified_sapling_balance = wallet.pending_balance::().await; + let spendable_sapling_balance = wallet.spendable_balance::().await; let sapling_balance = some_sum(verified_sapling_balance, unverified_sapling_balance); - let verified_orchard_balance = wallet.confirmed_balance::().await; - let unverified_orchard_balance = wallet.pending_balance::().await; - let spendable_orchard_balance = wallet.spendable_balance::().await; + let verified_orchard_balance = wallet.confirmed_balance::().await; + let unverified_orchard_balance = wallet.pending_balance::().await; + let spendable_orchard_balance = wallet.spendable_balance::().await; let orchard_balance = some_sum(verified_orchard_balance, unverified_orchard_balance); PoolBalances { sapling_balance, diff --git a/zingolib/src/wallet/describe.rs b/zingolib/src/wallet/describe.rs index e5fe72674..4d6da44ea 100644 --- a/zingolib/src/wallet/describe.rs +++ b/zingolib/src/wallet/describe.rs @@ -1,11 +1,10 @@ //! Wallet-State reporters as LightWallet methods. use zcash_client_backend::ShieldedProtocol; -use orchard::note_encryption::OrchardDomain; -use sapling_crypto::note_encryption::SaplingDomain; - use zcash_primitives::transaction::components::amount::NonNegativeAmount; use zcash_primitives::transaction::fees::zip317::MARGINAL_FEE; +use zingo_sync::primitives::NoteInterface; +use zingo_sync::primitives::WalletTransaction; use std::sync::Arc; use tokio::sync::RwLock; @@ -15,7 +14,6 @@ use bip0039::Mnemonic; use zcash_note_encryption::Domain; use crate::utils; -use crate::wallet::data::TransactionRecord; use crate::wallet::notes::OutputInterface; use crate::wallet::notes::ShieldedNoteInterface; @@ -29,6 +27,9 @@ use crate::wallet::traits::Recipient; use crate::wallet::tx_map::TxMap; use crate::wallet::LightWallet; +use crate::Orchard; +use crate::Sapling; +use crate::WalletDomain; use super::keys::unified::UnifiedKeyStore; @@ -39,23 +40,17 @@ impl LightWallet { self.mnemonic() .map(|(mnemonic, _)| mnemonic.phrase().to_string()) } + // Core shielded_balance function, other public methods dispatch specific sets of filters to this // method for processing. - /// Returns the sum of unspent notes recorded by the wallet - /// with optional filtering. - /// This method ensures that None is returned in the case of a missing view capability. - #[allow(clippy::type_complexity)] - pub async fn get_filtered_balance( - &self, - filter_function: Box bool + '_>, - ) -> Option + /// Returns the sum of unspent notes recorded by the wallet with optional filtering. + /// This method ensures that `None` is returned in the case of a missing view capability. + pub async fn get_filtered_balance(&self, filter_function: F) -> Option where - D: DomainWalletExt, - ::Note: PartialEq + Clone, - ::Recipient: Recipient, + D: WalletDomain, + F: Fn(&D::Note, &WalletTransaction) -> bool, { - // For the moment we encode lack of view capability as None - match &self.wallet_capability().unified_key_store { + match &self.unified_key_store { UnifiedKeyStore::Spend(_) => (), UnifiedKeyStore::View(ufvk) => match D::SHIELDED_PROTOCOL { ShieldedProtocol::Sapling => { @@ -67,45 +62,23 @@ impl LightWallet { }, UnifiedKeyStore::Empty => return None, } + Some( - self.transaction_context - .transaction_metadata_set - .read() - .await - .transaction_records_by_id + self.wallet_transactions .values() - .map(|transaction| { - let mut selected_notes: Box> = - Box::new(D::WalletNote::transaction_metadata_notes(transaction).iter()); - // All filters in iterator are applied, by this loop - selected_notes = - Box::new(selected_notes.filter(|nnmd| filter_function(nnmd, transaction))); - selected_notes - .map(|notedata| { - if notedata.spending_tx_status().is_none() { - ::value(notedata) - } else { - 0 - } + .fold(0, |acc, transaction| { + acc + D::Note::transaction_notes(transaction) + .iter() + .filter(|¬e| { + filter_function(note, transaction) + && note.spending_transaction().is_none() }) + .map(|note| note.value()) .sum::() - }) - .sum::(), + }), ) } - /// Spendable balance is confirmed balance, that we have the *spend* capability for - pub async fn spendable_balance(&self) -> Option - where - ::Recipient: Recipient, - ::Note: PartialEq + Clone, - { - if let UnifiedKeyStore::Spend(_) = self.wallet_capability().unified_key_store { - self.confirmed_balance::().await - } else { - None - } - } /// Sums the transparent balance (unspent) pub async fn get_transparent_balance(&self) -> Option { match &self.wallet_capability().unified_key_store { @@ -125,44 +98,52 @@ impl LightWallet { ) } - /// On chain balance - pub async fn confirmed_balance(&self) -> Option + /// Returns total wallet balance of unspent notes in confirmed blocks for a given shielded pool. + pub async fn confirmed_balance(&self) -> Option where - ::Recipient: Recipient, - ::Note: PartialEq + Clone, + D: WalletDomain, { - #[allow(clippy::type_complexity)] - let filter_function: Box bool> = - Box::new(|nnmd, transaction| { - transaction.status.is_confirmed() && !nnmd.pending_receipt() - }); - self.get_filtered_balance::(filter_function).await + self.get_filtered_balance::(|_, transaction: &WalletTransaction| { + transaction.status().is_confirmed() + }) + .await } - /// The amount in pending notes, not yet on chain - pub async fn pending_balance(&self) -> Option + + /// Returns total wallet balance of unspent notes in confirmed blocks for a given shielded pool. + /// Returns `None` if the wallet does not have spend capability. + // TODO: also calculate whether notes in the wallet have the necessary info (i.e. commitment trees) to spend + pub async fn spendable_balance(&self) -> Option where - ::Recipient: Recipient, - ::Note: PartialEq + Clone, + D: WalletDomain, { - self.get_filtered_balance::(Box::new(|note, _| note.pending_receipt())) - .await + if let UnifiedKeyStore::Spend(_) = self.wallet_capability().unified_key_store { + self.confirmed_balance::().await + } else { + None + } } - /// Returns balance for a given shielded pool excluding any notes with value less than marginal fee - /// that are confirmed on the block chain (the block has at least 1 confirmation) - pub async fn confirmed_balance_excluding_dust(&self) -> Option + /// Returns total wallet balance of unspent notes not yet confirmed on the block chain for a given shielded pool. + pub async fn pending_balance(&self) -> Option where - ::Recipient: Recipient, - ::Note: PartialEq + Clone, + D: WalletDomain, + { + self.get_filtered_balance::(|_, transaction: &WalletTransaction| { + !transaction.status().is_confirmed() + }) + .await + } + + /// Returns total wallet balance of unspent notes in confirmed blocks for a given shielded pool excluding any notes + /// with value less than marginal fee (5_000). + pub async fn confirmed_balance_excluding_dust(&self) -> Option + where + D: WalletDomain, { - #[allow(clippy::type_complexity)] - let filter_function: Box bool> = - Box::new(|note, transaction| { - transaction.status.is_confirmed() - && !note.pending_receipt() - && note.value() > MARGINAL_FEE.into_u64() - }); - self.get_filtered_balance::(filter_function).await + self.get_filtered_balance::(|note, transaction: &WalletTransaction| { + D::Note::value(note) > MARGINAL_FEE.into_u64() && transaction.status().is_confirmed() + }) + .await } /// Returns total balance of all shielded pools excluding any notes with value less than marginal fee @@ -176,11 +157,11 @@ impl LightWallet { &self, ) -> Result { Ok(utils::conversion::zatoshis_from_u64( - self.confirmed_balance_excluding_dust::() + self.confirmed_balance_excluding_dust::() .await .ok_or(BalanceError::NoFullViewingKey)? + self - .confirmed_balance_excluding_dust::() + .confirmed_balance_excluding_dust::() .await .ok_or(BalanceError::NoFullViewingKey)?, )?) @@ -312,9 +293,9 @@ mod test { } #[cfg(test)] - use orchard::note_encryption::OrchardDomain; + use crate::Orchard; #[cfg(test)] - use sapling_crypto::note_encryption::SaplingDomain; + use crate::Sapling; #[cfg(test)] use zingo_status::confirmation_status::ConfirmationStatus; @@ -399,15 +380,11 @@ mod test { } assert_eq!( - wallet - .confirmed_balance_excluding_dust::() - .await, + wallet.confirmed_balance_excluding_dust::().await, Some(400_000) ); assert_eq!( - wallet - .confirmed_balance_excluding_dust::() - .await, + wallet.confirmed_balance_excluding_dust::().await, Some(1_605_001) ); }