From 21e1e2d47de04d5e264a7579e87374ea5b050d68 Mon Sep 17 00:00:00 2001 From: oisupov Date: Fri, 8 Nov 2024 20:06:20 +0400 Subject: [PATCH 1/2] Implement Zcash sync process and Orchard inputs spending https://github.com/brave/brave-browser/issues/42898 --- components/brave_wallet/browser/BUILD.gn | 18 +- .../browser/internal/hd_key_zip32.cc | 4 + .../browser/internal/hd_key_zip32.h | 2 + .../internal/orchard_bundle_manager.cc | 3 +- .../browser/internal/orchard_bundle_manager.h | 1 + .../orchard_bundle_manager_unittest.cc | 14 +- .../browser/internal/orchard_sync_state.cc | 25 ++ .../browser/internal/orchard_sync_state.h | 13 + .../brave_wallet/browser/keyring_service.cc | 11 + .../brave_wallet/browser/keyring_service.h | 2 + .../zcash/rust/extended_spending_key_impl.cc | 60 ++++ .../rust/orchard_extended_spending_key.h | 2 + .../orchard_extended_spending_key_impl.cc | 4 + .../rust/orchard_extended_spending_key_impl.h | 2 + .../zcash/rust/orchard_unauthorized_bundle.h | 1 + .../rust/orchard_unauthorized_bundle_impl.cc | 25 +- .../browser/zcash/zcash_action_context.cc | 31 ++ .../browser/zcash/zcash_action_context.h | 42 +++ .../zcash/zcash_blocks_batch_scan_task.cc | 256 ++++++++++++++ .../zcash/zcash_blocks_batch_scan_task.h | 85 +++++ .../zcash/zcash_complete_transaction_task.cc | 287 +++++++++++++++ .../zcash/zcash_complete_transaction_task.h | 90 +++++ .../zcash_create_shield_transaction_task.cc | 23 +- .../zcash_create_shield_transaction_task.h | 27 +- .../zcash_create_shielded_transaction_task.cc | 142 ++++++++ .../zcash_create_shielded_transaction_task.h | 74 ++++ ...ash_create_transparent_transaction_task.cc | 19 +- ...cash_create_transparent_transaction_task.h | 27 +- ...discover_next_unused_zcash_address_task.cc | 15 +- ..._discover_next_unused_zcash_address_task.h | 20 +- .../zcash_get_zcash_chain_tip_status_task.cc | 101 ++++++ .../zcash_get_zcash_chain_tip_status_task.h | 59 ++++ .../browser/zcash/zcash_keyring.cc | 14 + .../browser/zcash/zcash_keyring.h | 2 + .../zcash/zcash_resolve_balance_task.cc | 9 +- .../zcash/zcash_resolve_balance_task.h | 8 +- .../brave_wallet/browser/zcash/zcash_rpc.cc | 87 +++++ .../brave_wallet/browser/zcash/zcash_rpc.h | 17 + .../browser/zcash/zcash_scan_blocks_task.cc | 170 +++++++++ .../browser/zcash/zcash_scan_blocks_task.h | 86 +++++ .../browser/zcash/zcash_serializer.cc | 27 +- .../zcash/zcash_serializer_unittest.cc | 3 +- .../zcash/zcash_shield_sync_service.cc | 331 ++++++------------ .../browser/zcash/zcash_shield_sync_service.h | 106 +++--- .../zcash_shield_sync_service_unittest.cc | 44 ++- .../browser/zcash/zcash_transaction.cc | 45 ++- .../browser/zcash/zcash_transaction.h | 5 +- .../zcash_transaction_complete_manager.cc | 172 --------- .../zcash_transaction_complete_manager.h | 71 ---- .../browser/zcash/zcash_transaction_utils.cc | 55 +++ .../browser/zcash/zcash_transaction_utils.h | 22 ++ .../zcash/zcash_transaction_utils_unittest.cc | 63 ++++ .../browser/zcash/zcash_tx_manager.cc | 24 +- .../browser/zcash/zcash_tx_meta.cc | 1 + .../zcash/zcash_update_subtree_roots_task.cc | 93 +++++ .../zcash/zcash_update_subtree_roots_task.h | 45 +++ .../zcash/zcash_verify_chain_state_task.cc | 214 +++++++++++ .../zcash/zcash_verify_chain_state_task.h | 67 ++++ .../browser/zcash/zcash_wallet_service.cc | 280 +++++++++++---- .../browser/zcash/zcash_wallet_service.h | 64 +++- .../zcash/zcash_wallet_service_unittest.cc | 30 +- .../brave_wallet/common/brave_wallet.mojom | 37 +- components/brave_wallet/common/features.cc | 2 +- components/brave_wallet/common/zcash_utils.h | 4 +- .../slices/endpoints/zcash.endpoints.ts | 2 +- .../page/screens/dev-zcash/dev-zcash.tsx | 65 +++- .../public/mojom/zcash_decoder.mojom | 1 + .../public/proto/zcash_grpc_data.proto | 16 + .../brave_wallet/zcash/zcash_decoder.cc | 19 + .../brave_wallet/zcash/zcash_decoder.h | 2 + 70 files changed, 3039 insertions(+), 749 deletions(-) create mode 100644 components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_action_context.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_action_context.h create mode 100644 components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h create mode 100644 components/brave_wallet/browser/zcash/zcash_complete_transaction_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h create mode 100644 components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h create mode 100644 components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h create mode 100644 components/brave_wallet/browser/zcash/zcash_scan_blocks_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h delete mode 100644 components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc delete mode 100644 components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h create mode 100644 components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h create mode 100644 components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index acb8ac254373..bb70c2a10656 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -224,14 +224,20 @@ static_library("browser") { "wallet_data_files_installer.cc", "wallet_data_files_installer.h", "wallet_data_files_installer_delegate.h", + "zcash/zcash_action_context.cc", + "zcash/zcash_action_context.h", "zcash/zcash_block_tracker.cc", "zcash/zcash_block_tracker.h", + "zcash/zcash_complete_transaction_task.cc", + "zcash/zcash_complete_transaction_task.h", "zcash/zcash_create_transparent_transaction_task.cc", "zcash/zcash_create_transparent_transaction_task.h", "zcash/zcash_discover_next_unused_zcash_address_task.cc", "zcash/zcash_discover_next_unused_zcash_address_task.h", "zcash/zcash_get_transparent_utxos_context.cc", "zcash/zcash_get_transparent_utxos_context.h", + "zcash/zcash_get_zcash_chain_tip_status_task.cc", + "zcash/zcash_get_zcash_chain_tip_status_task.h", "zcash/zcash_grpc_utils.cc", "zcash/zcash_grpc_utils.h", "zcash/zcash_resolve_balance_task.cc", @@ -242,8 +248,6 @@ static_library("browser") { "zcash/zcash_serializer.h", "zcash/zcash_transaction.cc", "zcash/zcash_transaction.h", - "zcash/zcash_transaction_complete_manager.cc", - "zcash/zcash_transaction_complete_manager.h", "zcash/zcash_transaction_utils.cc", "zcash/zcash_transaction_utils.h", "zcash/zcash_tx_manager.cc", @@ -312,10 +316,20 @@ static_library("browser") { if (enable_orchard) { sources += [ + "zcash/zcash_blocks_batch_scan_task.cc", + "zcash/zcash_blocks_batch_scan_task.h", "zcash/zcash_create_shield_transaction_task.cc", "zcash/zcash_create_shield_transaction_task.h", + "zcash/zcash_create_shielded_transaction_task.cc", + "zcash/zcash_create_shielded_transaction_task.h", + "zcash/zcash_scan_blocks_task.cc", + "zcash/zcash_scan_blocks_task.h", "zcash/zcash_shield_sync_service.cc", "zcash/zcash_shield_sync_service.h", + "zcash/zcash_update_subtree_roots_task.cc", + "zcash/zcash_update_subtree_roots_task.h", + "zcash/zcash_verify_chain_state_task.cc", + "zcash/zcash_verify_chain_state_task.h", ] deps += [ diff --git a/components/brave_wallet/browser/internal/hd_key_zip32.cc b/components/brave_wallet/browser/internal/hd_key_zip32.cc index a6ca7ac06681..eca68fc7e398 100644 --- a/components/brave_wallet/browser/internal/hd_key_zip32.cc +++ b/components/brave_wallet/browser/internal/hd_key_zip32.cc @@ -39,4 +39,8 @@ OrchardFullViewKey HDKeyZip32::GetFullViewKey() { return orchard_extended_spending_key_->GetFullViewKey(); } +OrchardSpendingKey HDKeyZip32::GetSpendingKey() { + return orchard_extended_spending_key_->GetSpendingKey(); +} + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_zip32.h b/components/brave_wallet/browser/internal/hd_key_zip32.h index 50c738e14fe8..069de92d606d 100644 --- a/components/brave_wallet/browser/internal/hd_key_zip32.h +++ b/components/brave_wallet/browser/internal/hd_key_zip32.h @@ -42,6 +42,8 @@ class HDKeyZip32 { // Full view key(fvk) is used to decode incoming transactions OrchardFullViewKey GetFullViewKey(); + OrchardSpendingKey GetSpendingKey(); + private: explicit HDKeyZip32(std::unique_ptr key); // Extended spending key is a root key of an account, all other keys can be diff --git a/components/brave_wallet/browser/internal/orchard_bundle_manager.cc b/components/brave_wallet/browser/internal/orchard_bundle_manager.cc index 101b31ecbc16..d8fb0f4bf20b 100644 --- a/components/brave_wallet/browser/internal/orchard_bundle_manager.cc +++ b/components/brave_wallet/browser/internal/orchard_bundle_manager.cc @@ -22,12 +22,13 @@ std::optional OrchardBundleManager::random_seed_for_testing_ = // static std::unique_ptr OrchardBundleManager::Create( base::span tree_state, + const OrchardSpendsBundle& spends_bundle, const std::vector& orchard_outputs) { if (orchard_outputs.empty()) { return nullptr; } auto bundle = orchard::OrchardUnauthorizedBundle::Create( - tree_state, orchard_outputs, random_seed_for_testing_); + tree_state, spends_bundle, orchard_outputs, random_seed_for_testing_); if (!bundle) { return nullptr; } diff --git a/components/brave_wallet/browser/internal/orchard_bundle_manager.h b/components/brave_wallet/browser/internal/orchard_bundle_manager.h index e505c045d1e5..2fc9fe45cd1f 100644 --- a/components/brave_wallet/browser/internal/orchard_bundle_manager.h +++ b/components/brave_wallet/browser/internal/orchard_bundle_manager.h @@ -45,6 +45,7 @@ class OrchardBundleManager { // Returns in unauthorized state static std::unique_ptr Create( base::span tree_state, + const OrchardSpendsBundle& spends_bundle, const std::vector& orchard_outputs); static void OverrideRandomSeedForTesting(size_t seed) { diff --git a/components/brave_wallet/browser/internal/orchard_bundle_manager_unittest.cc b/components/brave_wallet/browser/internal/orchard_bundle_manager_unittest.cc index 9f0000c9f632..d40633a0f1a1 100644 --- a/components/brave_wallet/browser/internal/orchard_bundle_manager_unittest.cc +++ b/components/brave_wallet/browser/internal/orchard_bundle_manager_unittest.cc @@ -27,8 +27,9 @@ TEST(OrchardBundleManagerTest, SingleOutput) { 127, 239, 163, 246, 227, 18, 158, 164, 223, 176, 169, 233, 135, 3, 166, 61, 171, 146, 149, 137, 214, 220, 81, 201, 112, 249, 53, 179}}); - auto unauthorized_state = - OrchardBundleManager::Create(std::vector(), std::move(outputs)); + OrchardSpendsBundle orchard_spends_bundle; + auto unauthorized_state = OrchardBundleManager::Create( + std::vector(), orchard_spends_bundle, std::move(outputs)); EXPECT_TRUE(unauthorized_state); // Unauthorized state doesn't have raw tx bytes EXPECT_FALSE(unauthorized_state->GetRawTxBytes()); @@ -324,8 +325,9 @@ TEST(OrchardBundleManagerTest, MultiplyOutputs) { 0x91, 0xd7, 0x34, 0xdf, 0x12, 0xd0, 0x46, 0xc9, 0x69, 0x75, 0x13, 0x30, 0xbb, 0xf4, 0x93, 0xa2, 0x41, 0xec, 0x4b, 0x88, 0xbc}}); - auto unauthorized_state = - OrchardBundleManager::Create(std::vector(), std::move(outputs)); + OrchardSpendsBundle orchard_spends_bundle; + auto unauthorized_state = OrchardBundleManager::Create( + std::vector(), orchard_spends_bundle, std::move(outputs)); EXPECT_TRUE(unauthorized_state); // Unauthorized state doesn't have raw tx bytes EXPECT_FALSE(unauthorized_state->GetRawTxBytes()); @@ -602,9 +604,11 @@ TEST(OrchardBundleManagerTest, MultiplyOutputs) { TEST(OrchardBundleManagerTest, NoOutputs) { OrchardBundleManager::OverrideRandomSeedForTesting(0); + OrchardSpendsBundle orchard_spends_bundle; std::vector outputs; auto unauthorized_state = OrchardBundleManager::Create( - std::vector(), std::vector()); + std::vector(), orchard_spends_bundle, + std::vector()); EXPECT_FALSE(unauthorized_state); } diff --git a/components/brave_wallet/browser/internal/orchard_sync_state.cc b/components/brave_wallet/browser/internal/orchard_sync_state.cc index 7174b829adaa..34a9b6f0441d 100644 --- a/components/brave_wallet/browser/internal/orchard_sync_state.cc +++ b/components/brave_wallet/browser/internal/orchard_sync_state.cc @@ -134,6 +134,31 @@ OrchardSyncState::CalculateWitnessForCheckpoint( return base::ok(std::move(result)); } +base::expected, OrchardStorage::Error> +OrchardSyncState::GetLatestShardIndex(const mojom::AccountIdPtr& account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.GetLatestShardIndex(account_id); +} + +base::expected, OrchardStorage::Error> +OrchardSyncState::GetMaxCheckpointedHeight( + const mojom::AccountIdPtr& account_id, + uint32_t chain_tip_height, + uint32_t min_confirmations) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.GetMaxCheckpointedHeight(account_id, chain_tip_height, + min_confirmations); +} + +base::expected +OrchardSyncState::UpdateSubtreeRoots( + const mojom::AccountIdPtr& account_id, + uint32_t start_index, + const std::vector& roots) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.UpdateSubtreeRoots(account_id, start_index, roots); +} + void OrchardSyncState::ResetDatabase() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); storage_.ResetDatabase(); diff --git a/components/brave_wallet/browser/internal/orchard_sync_state.h b/components/brave_wallet/browser/internal/orchard_sync_state.h index a205af075c46..ae7760e4bff3 100644 --- a/components/brave_wallet/browser/internal/orchard_sync_state.h +++ b/components/brave_wallet/browser/internal/orchard_sync_state.h @@ -56,6 +56,19 @@ class OrchardSyncState { const uint32_t latest_scanned_block, const std::string& latest_scanned_block_hash); + base::expected, OrchardStorage::Error> + GetLatestShardIndex(const mojom::AccountIdPtr& account_id); + + base::expected, OrchardStorage::Error> + GetMaxCheckpointedHeight(const mojom::AccountIdPtr& account_id, + uint32_t chain_tip_height, + uint32_t min_confirmations); + + base::expected + UpdateSubtreeRoots(const mojom::AccountIdPtr& account_id, + uint32_t start_index, + const std::vector& roots); + // Clears sync data related to the account except it's birthday. base::expected ResetAccountSyncState(const mojom::AccountIdPtr& account_id); diff --git a/components/brave_wallet/browser/keyring_service.cc b/components/brave_wallet/browser/keyring_service.cc index 40aaf5e22761..298675c142ff 100644 --- a/components/brave_wallet/browser/keyring_service.cc +++ b/components/brave_wallet/browser/keyring_service.cc @@ -2212,6 +2212,17 @@ std::optional KeyringService::GetOrchardFullViewKey( return zcash_keyring->GetOrchardFullViewKey(account_id->account_index); } + +std::optional KeyringService::GetOrchardSpendingKey( + const mojom::AccountIdPtr& account_id) { + auto* zcash_keyring = GetZCashKeyringById(account_id->keyring_id); + if (!zcash_keyring) { + return std::nullopt; + } + + return zcash_keyring->GetOrchardSpendingKey(account_id->account_index); +} + #endif void KeyringService::UpdateNextUnusedAddressForBitcoinAccount( diff --git a/components/brave_wallet/browser/keyring_service.h b/components/brave_wallet/browser/keyring_service.h index e2bccb83ee8e..63895955a257 100644 --- a/components/brave_wallet/browser/keyring_service.h +++ b/components/brave_wallet/browser/keyring_service.h @@ -251,6 +251,8 @@ class KeyringService : public mojom::KeyringService { const mojom::ZCashKeyIdPtr& key_id); std::optional GetOrchardFullViewKey( const mojom::AccountIdPtr& account_id); + std::optional GetOrchardSpendingKey( + const mojom::AccountIdPtr& account_id); #endif const std::vector& GetAllAccountInfos(); diff --git a/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.cc b/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.cc new file mode 100644 index 000000000000..22c6616ca376 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.h" + +#include + +#include "base/memory/ptr_util.h" + +namespace brave_wallet::orchard { + +ExtendedSpendingKeyImpl::ExtendedSpendingKeyImpl( + absl::variant, + base::PassKey>, + rust::Box esk) + : extended_spending_key_(std::move(esk)) {} + +ExtendedSpendingKeyImpl::~ExtendedSpendingKeyImpl() = default; + +std::unique_ptr +ExtendedSpendingKeyImpl::DeriveHardenedChild(uint32_t index) { + auto esk = extended_spending_key_->derive(index); + if (esk->is_ok()) { + return std::make_unique( + base::PassKey(), esk->unwrap()); + } + return nullptr; +} + +std::optional +ExtendedSpendingKeyImpl::GetDiversifiedAddress(uint32_t div_index, + OrchardAddressKind kind) { + return kind == OrchardAddressKind::External + ? extended_spending_key_->external_address(div_index) + : extended_spending_key_->internal_address(div_index); +} + +// static +std::unique_ptr ExtendedSpendingKey::GenerateFromSeed( + base::span seed) { + auto mk = generate_orchard_extended_spending_key_from_seed( + rust::Slice{seed.data(), seed.size()}); + if (mk->is_ok()) { + return std::make_unique( + base::PassKey(), mk->unwrap()); + } + return nullptr; +} + +OrchardFullViewKey ExtendedSpendingKeyImpl::GetFullViewKey() { + return extended_spending_key_->full_view_key(); +} + +OrchardSpendingKey ExtendedSpendingKeyImpl::GetSpendingKey() { + return extended_spending_key_->spending_key(); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h index da17811a3ab2..a4b88c60685f 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h @@ -34,6 +34,8 @@ class OrchardExtendedSpendingKey { uint32_t div_index, OrchardAddressKind kind) = 0; + virtual OrchardSpendingKey GetSpendingKey() = 0; + virtual OrchardFullViewKey GetFullViewKey() = 0; }; diff --git a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.cc index 1c63cd459599..98313f634ec0 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.cc +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.cc @@ -53,4 +53,8 @@ OrchardFullViewKey OrchardExtendedSpendingKeyImpl::GetFullViewKey() { return cxx_extended_spending_key_->full_view_key(); } +OrchardSpendingKey OrchardExtendedSpendingKeyImpl::GetSpendingKey() { + return cxx_extended_spending_key_->spending_key(); +} + } // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.h index c1600dd7ac11..876ca71f04c7 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.h @@ -42,6 +42,8 @@ class OrchardExtendedSpendingKeyImpl : public OrchardExtendedSpendingKey { OrchardFullViewKey GetFullViewKey() override; + OrchardSpendingKey GetSpendingKey() override; + private: // Extended spending key is a root key of an account, all other keys can be // derived from esk diff --git a/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h index 58f48b4c0326..4679f7182d16 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h @@ -30,6 +30,7 @@ class OrchardUnauthorizedBundle { // Creates OrchardUnauthorizedBundle without shielded inputs static std::unique_ptr Create( base::span tree_state, + const ::brave_wallet::OrchardSpendsBundle& orchard_spends, const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs, std::optional random_seed_for_testing); diff --git a/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc index 435a07a4a246..c3edebb20bbc 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc +++ b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc @@ -27,8 +27,27 @@ OrchardUnauthorizedBundleImpl::~OrchardUnauthorizedBundleImpl() = default; // static std::unique_ptr OrchardUnauthorizedBundle::Create( base::span tree_state, + const ::brave_wallet::OrchardSpendsBundle& orchard_spends, const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs, std::optional random_seed_for_testing) { + ::rust::Vec spends; + for (const auto& input : orchard_spends.inputs) { + if (!input.witness) { + return nullptr; + } + + auto& note = input.note; + + orchard::CxxMerklePath merkle_path; + merkle_path.position = input.witness->position; + for (const auto& merkle_hash : input.witness->merkle_path) { + merkle_path.auth_path.push_back(orchard::CxxMerkleHash{merkle_hash}); + } + spends.push_back(orchard::CxxOrchardSpend{ + orchard_spends.fvk, orchard_spends.sk, note.amount, note.addr, note.rho, + note.seed, std::move(merkle_path)}); + } + ::rust::Vec outputs; for (const auto& output : orchard_outputs) { outputs.push_back(orchard::CxxOrchardOutput{ @@ -39,8 +58,7 @@ std::unique_ptr OrchardUnauthorizedBundle::Create( CHECK_IS_TEST(); auto bundle_result = create_testing_orchard_bundle( ::rust::Slice{tree_state.data(), tree_state.size()}, - ::rust::Vec<::brave_wallet::orchard::CxxOrchardSpend>(), - std::move(outputs), random_seed_for_testing.value()); + std::move(spends), std::move(outputs), random_seed_for_testing.value()); if (!bundle_result->is_ok()) { return nullptr; } @@ -50,8 +68,7 @@ std::unique_ptr OrchardUnauthorizedBundle::Create( } else { auto bundle_result = create_orchard_bundle( ::rust::Slice{tree_state.data(), tree_state.size()}, - ::rust::Vec<::brave_wallet::orchard::CxxOrchardSpend>(), - std::move(outputs)); + std::move(spends), std::move(outputs)); if (!bundle_result->is_ok()) { return nullptr; } diff --git a/components/brave_wallet/browser/zcash/zcash_action_context.cc b/components/brave_wallet/browser/zcash/zcash_action_context.cc new file mode 100644 index 000000000000..1788d2749e70 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_action_context.cc @@ -0,0 +1,31 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" + +namespace brave_wallet { + +ZCashActionContext::ZCashActionContext( + ZCashRpc& zcash_rpc, +#if BUILDFLAG(ENABLE_ORCHARD) + base::SequenceBound& sync_state, +#endif // BUILDFLAG(ENABLE_ORCHARD) + const mojom::AccountIdPtr& account_id, + const std::string& chain_id) + : zcash_rpc(zcash_rpc), +#if BUILDFLAG(ENABLE_ORCHARD) + sync_state(sync_state), +#endif // BUILDFLAG(ENABLE_ORCHARD) + account_id(account_id.Clone()), + chain_id(chain_id) { +} + +ZCashActionContext& ZCashActionContext::operator=(ZCashActionContext&&) = + default; +ZCashActionContext::ZCashActionContext(ZCashActionContext&&) = default; + +ZCashActionContext::~ZCashActionContext() = default; + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_action_context.h b/components/brave_wallet/browser/zcash/zcash_action_context.h new file mode 100644 index 000000000000..741bc30a9340 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_action_context.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_ACTION_CONTEXT_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_ACTION_CONTEXT_H_ + +#include "base/memory/raw_ref.h" +#include "base/threading/sequence_bound.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "brave/components/brave_wallet/common/buildflags.h" + +namespace brave_wallet { + +class ZCashRpc; +class OrchardSyncState; + +// Basic context required by most orchard-related operations. +struct ZCashActionContext { + ZCashActionContext(ZCashRpc& zcash_rpc, +#if BUILDFLAG(ENABLE_ORCHARD) + base::SequenceBound& sync_state, +#endif // BUILDFLAG(ENABLE_ORCHARD) + const mojom::AccountIdPtr& account_id, + const std::string& chain_id); + ~ZCashActionContext(); + raw_ref zcash_rpc; +#if BUILDFLAG(ENABLE_ORCHARD) + raw_ref> sync_state; +#endif // BUILDFLAG(ENABLE_ORCHARD) + ZCashActionContext(ZCashActionContext&) = delete; + ZCashActionContext& operator=(ZCashActionContext&) = delete; + ZCashActionContext& operator=(ZCashActionContext&&); + ZCashActionContext(ZCashActionContext&&); + mojom::AccountIdPtr account_id; + std::string chain_id; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_SHIELDED_ACTION_CONTEXT_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.cc b/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.cc new file mode 100644 index 000000000000..74c96ebb486a --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h" + +#include +#include +#include +#include + +#include "base/containers/extend.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" +#include "brave/components/brave_wallet/common/hex_utils.h" + +namespace brave_wallet { + +namespace { +constexpr uint32_t kBlockDownloadBatchSize = 10u; +} + +ZCashBlocksBatchScanTask::ZCashBlocksBatchScanTask( + ZCashActionContext& context, + ZCashShieldSyncService::OrchardBlockScannerProxy& scanner, + uint32_t from, + uint32_t to, + ZCashBlocksBatchScanTaskCallback callback) + : context_(context), + scanner_(scanner), + from_(from), + to_(to), + callback_(std::move(callback)) { + CHECK_GT(from, kNu5BlockUpdate); + frontier_block_height_ = from - 1; +} + +ZCashBlocksBatchScanTask::~ZCashBlocksBatchScanTask() = default; + +void ZCashBlocksBatchScanTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + +void ZCashBlocksBatchScanTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashBlocksBatchScanTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashBlocksBatchScanTask::WorkOnTask() { + if (error_) { + std::move(callback_).Run(base::unexpected(*error_)); + return; + } + + if (!frontier_tree_state_) { + GetFrontierTreeState(); + return; + } + + if (!frontier_block_) { + GetFrontierBlock(); + return; + } + + if (!scan_result_ && (!downloaded_blocks_ || + downloaded_blocks_->size() != (to_ - from_ + 1))) { + DownloadBlocks(); + return; + } + + if (!scan_result_) { + ScanBlocks(); + return; + } + + if (!database_updated_) { + UpdateDatabase(); + return; + } + + std::move(callback_).Run(true); +} + +void ZCashBlocksBatchScanTask::GetFrontierTreeState() { + auto block_id = zcash::mojom::BlockID::New(frontier_block_height_, + std::vector({})); + context_->zcash_rpc->GetTreeState( + context_->chain_id, std::move(block_id), + base::BindOnce(&ZCashBlocksBatchScanTask::OnGetFrontierTreeState, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashBlocksBatchScanTask::OnGetFrontierTreeState( + base::expected result) { + if (!result.has_value() || !result.value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToReceiveTreeState, + base::StrCat({"Frontier tree state failed, ", result.error()})}; + ScheduleWorkOnTask(); + return; + } + frontier_tree_state_ = result.value().Clone(); + ScheduleWorkOnTask(); +} + +void ZCashBlocksBatchScanTask::GetFrontierBlock() { + context_->zcash_rpc->GetCompactBlocks( + context_->chain_id, frontier_block_height_, frontier_block_height_, + base::BindOnce(&ZCashBlocksBatchScanTask::OnGetFrontierBlock, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashBlocksBatchScanTask::OnGetFrontierBlock( + base::expected, std::string> + result) { + if (!result.has_value() || result.value().size() != 1) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToDownloadBlocks, + result.error()}; + ScheduleWorkOnTask(); + return; + } + + frontier_block_ = std::move(result.value()[0]); + ScheduleWorkOnTask(); +} + +void ZCashBlocksBatchScanTask::DownloadBlocks() { + uint32_t start_index = + downloaded_blocks_ ? from_ + downloaded_blocks_->size() : from_; + uint32_t end_index = std::min(start_index + kBlockDownloadBatchSize - 1, to_); + auto expected_size = end_index - start_index + 1; + + context_->zcash_rpc->GetCompactBlocks( + context_->chain_id, start_index, end_index, + base::BindOnce(&ZCashBlocksBatchScanTask::OnBlocksDownloaded, + weak_ptr_factory_.GetWeakPtr(), expected_size)); +} + +void ZCashBlocksBatchScanTask::OnBlocksDownloaded( + size_t expected_size, + base::expected, std::string> + result) { + CHECK(frontier_block_); + CHECK(frontier_tree_state_); + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToDownloadBlocks, + result.error()}; + ScheduleWorkOnTask(); + return; + } + + if (expected_size != result.value().size()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToDownloadBlocks, + "Expected block count doesn't match actual"}; + ScheduleWorkOnTask(); + return; + } + + if (!downloaded_blocks_) { + downloaded_blocks_ = std::vector(); + } + base::Extend(downloaded_blocks_.value(), std::move(result.value())); + ScheduleWorkOnTask(); +} + +void ZCashBlocksBatchScanTask::ScanBlocks() { + if (!downloaded_blocks_ || downloaded_blocks_->empty()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kScannerError, "No blocks to scan"}; + ScheduleWorkOnTask(); + return; + } + + if (!frontier_block_.value() || !frontier_tree_state_.value() || + !frontier_block_.value()->chain_metadata) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kScannerError, "Frontier error"}; + ScheduleWorkOnTask(); + return; + } + + OrchardTreeState tree_state; + { + auto frontier_bytes = PrefixedHexStringToBytes( + base::StrCat({"0x", frontier_tree_state_.value()->orchardTree})); + + if (!frontier_bytes) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kScannerError, + "Failed to parse tree state"}; + ScheduleWorkOnTask(); + return; + } + + tree_state.block_height = frontier_block_.value()->height; + tree_state.tree_size = + frontier_block_.value()->chain_metadata->orchard_commitment_tree_size; + tree_state.frontier = std::move(*frontier_bytes); + } + + latest_scanned_block_ = downloaded_blocks_->back().Clone(); + + scanner_->ScanBlocks( + std::move(tree_state), std::move(downloaded_blocks_.value()), + base::BindOnce(&ZCashBlocksBatchScanTask::OnBlocksScanned, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashBlocksBatchScanTask::OnBlocksScanned( + base::expected + result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kScannerError, + "Failed to scan blocks"}; + ScheduleWorkOnTask(); + return; + } + + scan_result_ = std::move(result.value()); + ScheduleWorkOnTask(); +} + +void ZCashBlocksBatchScanTask::UpdateDatabase() { + CHECK(scan_result_.has_value()); + CHECK(latest_scanned_block_.has_value()); + auto latest_scanned_block_hash = ToHex((*latest_scanned_block_)->hash); + auto latest_scanned_block_height = (*latest_scanned_block_)->height; + + context_->sync_state->AsyncCall(&OrchardSyncState::ApplyScanResults) + .WithArgs(context_->account_id.Clone(), std::move(scan_result_.value()), + latest_scanned_block_height, latest_scanned_block_hash) + .Then(base::BindOnce(&ZCashBlocksBatchScanTask::OnDatabaseUpdated, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashBlocksBatchScanTask::OnDatabaseUpdated( + base::expected result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToUpdateDatabase, + result.error().message}; + ScheduleWorkOnTask(); + return; + } + database_updated_ = true; + ScheduleWorkOnTask(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h b/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h new file mode 100644 index 000000000000..c4ce9d89aacf --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h @@ -0,0 +1,85 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_BLOCKS_BATCH_SCAN_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_BLOCKS_BATCH_SCAN_TASK_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +// Scans single scan range. +class ZCashBlocksBatchScanTask { + public: + using ZCashBlocksBatchScanTaskCallback = base::OnceCallback)>; + ZCashBlocksBatchScanTask( + ZCashActionContext& context, + ZCashShieldSyncService::OrchardBlockScannerProxy& scanner, + uint32_t from, + uint32_t to, + ZCashBlocksBatchScanTaskCallback callback); + ~ZCashBlocksBatchScanTask(); + + uint32_t from() const { return from_; } + + uint32_t to() const { return to_; } + + void Start(); + + private: + void WorkOnTask(); + void ScheduleWorkOnTask(); + + void GetFrontierTreeState(); + void OnGetFrontierTreeState( + base::expected); + void GetFrontierBlock(); + void OnGetFrontierBlock( + base::expected, std::string> + result); + + void DownloadBlocks(); + void OnBlocksDownloaded( + size_t expected_size, + base::expected, std::string> + result); + + void ScanBlocks(); + void OnBlocksScanned(base::expected result); + + void UpdateDatabase(); + void OnDatabaseUpdated( + base::expected result); + + raw_ref context_; + raw_ref scanner_; + uint32_t from_ = 0; + uint32_t to_ = 0; + ZCashBlocksBatchScanTaskCallback callback_; + + uint32_t frontier_block_height_ = 0; + + std::optional error_; + std::optional frontier_tree_state_; + std::optional frontier_block_; + std::optional> downloaded_blocks_; + std::optional scan_result_; + std::optional latest_scanned_block_; + + bool started_ = false; + bool database_updated_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_BLOCKS_BATCH_SCAN_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.cc b/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.cc new file mode 100644 index 000000000000..81808f92f8ac --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h" + +#include +#include + +#include "base/task/thread_pool.h" +#include "brave/components/brave_wallet/browser/keyring_service.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_serializer.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" +#include "brave/components/brave_wallet/common/hex_utils.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" +#include "components/grit/brave_components_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace brave_wallet { + +namespace { + +constexpr size_t kMinConfirmations = 10; + +#if BUILDFLAG(ENABLE_ORCHARD) +std::unique_ptr ApplyOrchardSignatures( + std::unique_ptr orchard_bundle_manager, + std::array sighash) { + DVLOG(1) << "Apply signatures for ZCash transaction"; + // Heavy CPU operation, should be executed on background thread + auto result = orchard_bundle_manager->ApplySignature(sighash); + DVLOG(1) << "Signatures applied"; + return result; +} +#endif // BUILDFLAG(ENABLE_ORCHARD) + +} // namespace + +ZCashCompleteTransactionTask::ZCashCompleteTransactionTask( + base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + KeyringService& keyring_service, + const ZCashTransaction& transaction, + ZCashCompleteTransactionTaskCallback callback) + : zcash_wallet_service_(zcash_wallet_service), + context_(std::move(context)), + keyring_service_(keyring_service), + transaction_(transaction), + callback_(std::move(callback)) {} + +void ZCashCompleteTransactionTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashCompleteTransactionTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::WorkOnTask() { + if (error_) { + std::move(callback_).Run(base::unexpected(*error_)); + zcash_wallet_service_->CompleteTransactionTaskDone(this); + return; + } + + if (!chain_tip_height_) { + GetLatestBlock(); + return; + } + +#if BUILDFLAG(ENABLE_ORCHARD) + if (!transaction_.orchard_part().outputs.empty()) { + if (!anchor_block_height_) { + GetMaxCheckpointedHeight(); + return; + } + + if (!witness_inputs_) { + CalculateWitness(); + return; + } + + if (!anchor_tree_state_) { + GetTreeState(); + return; + } + + if (!transaction_.orchard_part().raw_tx) { + SignOrchardPart(); + return; + } + } +#endif // BUILDFLAG(ENABLE_ORCHARD) + + if (!transaction_.transparent_part().inputs.empty() && + !transaction_.IsTransparentPartSigned()) { + SignTransparentPart(); + return; + } + + std::move(callback_).Run(std::move(transaction_)); + zcash_wallet_service_->CompleteTransactionTaskDone(this); +} + +void ZCashCompleteTransactionTask::Start() { + ScheduleWorkOnTask(); +} + +ZCashCompleteTransactionTask::~ZCashCompleteTransactionTask() = default; + +void ZCashCompleteTransactionTask::GetLatestBlock() { + context_.zcash_rpc->GetLatestBlock( + context_.chain_id, + base::BindOnce(&ZCashCompleteTransactionTask::OnGetLatestBlockHeight, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::OnGetLatestBlockHeight( + base::expected result) { + if (!result.has_value()) { + std::move(callback_).Run(base::unexpected("block height error")); + ScheduleWorkOnTask(); + return; + } + + chain_tip_height_ = result.value()->height; + + transaction_.set_locktime(result.value()->height); + transaction_.set_expiry_height(result.value()->height + + kDefaultZCashBlockHeightDelta); + ScheduleWorkOnTask(); +} + +#if BUILDFLAG(ENABLE_ORCHARD) + +void ZCashCompleteTransactionTask::GetMaxCheckpointedHeight() { + CHECK(chain_tip_height_); + if (transaction_.orchard_part().inputs.empty()) { + anchor_block_height_ = chain_tip_height_; + ScheduleWorkOnTask(); + return; + } + context_.sync_state->AsyncCall(&OrchardSyncState::GetMaxCheckpointedHeight) + .WithArgs(context_.account_id.Clone(), chain_tip_height_.value(), + kMinConfirmations) + .Then(base::BindOnce( + &ZCashCompleteTransactionTask::OnGetMaxCheckpointedHeight, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::OnGetMaxCheckpointedHeight( + base::expected, OrchardStorage::Error> result) { + if (!result.has_value() || !result.value()) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + anchor_block_height_ = *result; + ScheduleWorkOnTask(); +} + +void ZCashCompleteTransactionTask::CalculateWitness() { + if (transaction_.orchard_part().inputs.empty()) { + witness_inputs_ = std::vector(); + ScheduleWorkOnTask(); + return; + } + + context_.sync_state + ->AsyncCall(&OrchardSyncState::CalculateWitnessForCheckpoint) + .WithArgs(context_.account_id.Clone(), transaction_.orchard_part().inputs, + anchor_block_height_.value()) + .Then(base::BindOnce( + &ZCashCompleteTransactionTask::OnWitnessCalulcateResult, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::OnWitnessCalulcateResult( + base::expected, OrchardStorage::Error> result) { + if (!result.has_value()) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + // TODO(cypt4): rewrite + witness_inputs_ = result.value(); + transaction_.orchard_part().inputs = result.value(); + ScheduleWorkOnTask(); +} + +void ZCashCompleteTransactionTask::GetTreeState() { + context_.zcash_rpc->GetTreeState( + context_.chain_id, + zcash::mojom::BlockID::New(anchor_block_height_.value(), + std::vector({})), + base::BindOnce(&ZCashCompleteTransactionTask::OnGetTreeState, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::OnGetTreeState( + base::expected result) { + if (!result.has_value()) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + anchor_tree_state_ = std::move(result.value()); + ScheduleWorkOnTask(); +} + +void ZCashCompleteTransactionTask::SignOrchardPart() { + auto state_tree_bytes = PrefixedHexStringToBytes( + base::StrCat({"0x", anchor_tree_state_.value()->orchardTree})); + if (!state_tree_bytes) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + auto fvk = zcash_wallet_service_->keyring_service_->GetOrchardFullViewKey( + context_.account_id); + auto sk = zcash_wallet_service_->keyring_service_->GetOrchardSpendingKey( + context_.account_id); + if (!fvk || !sk) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + OrchardSpendsBundle spends_bundle; + spends_bundle.sk = *sk; + spends_bundle.fvk = *fvk; + spends_bundle.inputs = transaction_.orchard_part().inputs; + auto orchard_bundle_manager = OrchardBundleManager::Create( + *state_tree_bytes, spends_bundle, transaction_.orchard_part().outputs); + + if (!orchard_bundle_manager) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + transaction_.orchard_part().digest = + orchard_bundle_manager->GetOrchardDigest(); + + // Calculate Orchard sighash + auto sighash = + ZCashSerializer::CalculateSignatureDigest(transaction_, std::nullopt); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&ApplyOrchardSignatures, std::move(orchard_bundle_manager), + sighash), + base::BindOnce(&ZCashCompleteTransactionTask::OnSignOrchardPartComplete, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCompleteTransactionTask::OnSignOrchardPartComplete( + std::unique_ptr orchard_bundle_manager) { + if (!orchard_bundle_manager) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + transaction_.orchard_part().raw_tx = orchard_bundle_manager->GetRawTxBytes(); + ScheduleWorkOnTask(); +} + +#endif // BUILDFLAG(ENABLE_ORCHARD) + +void ZCashCompleteTransactionTask::SignTransparentPart() { + // Sign transparent part + if (!ZCashSerializer::SignTransparentPart( + keyring_service_.get(), context_.account_id, transaction_)) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + + ScheduleWorkOnTask(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h b/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h new file mode 100644 index 000000000000..83455da8a9b0 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h @@ -0,0 +1,90 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_COMPLETE_TRANSACTION_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_COMPLETE_TRANSACTION_TASK_H_ + +#include +#include +#include + +#include "brave/components/brave_wallet/browser/internal/orchard_bundle_manager.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_transaction.h" +#include "brave/components/brave_wallet/common/buildflags.h" + +namespace brave_wallet { + +class KeyringService; +class OrchardSyncState; +class ZCashRpc; +class ZCashWalletService; + +// Completes transaction by signing transparent inputs and generating orchard +// part(if needed). +class ZCashCompleteTransactionTask { + public: + using ZCashCompleteTransactionTaskCallback = + base::OnceCallback)>; + ZCashCompleteTransactionTask(base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + KeyringService& keyring_service, + const ZCashTransaction& transaction, + ZCashCompleteTransactionTaskCallback callback); + ~ZCashCompleteTransactionTask(); + + void Start(); + + private: + void ScheduleWorkOnTask(); + void WorkOnTask(); + + void GetLatestBlock(); + void OnGetLatestBlockHeight( + base::expected result); + +#if BUILDFLAG(ENABLE_ORCHARD) + void GetMaxCheckpointedHeight(); + void OnGetMaxCheckpointedHeight( + base::expected, OrchardStorage::Error> result); + + void CalculateWitness(); + void OnWitnessCalulcateResult( + base::expected, OrchardStorage::Error> result); + + void GetTreeState(); + void OnGetTreeState( + base::expected result); + + void SignOrchardPart(); + void OnSignOrchardPartComplete( + std::unique_ptr orchard_bundle_manager); +#endif // BUILDFLAG(ENABLE_ORCHARD) + + void SignTransparentPart(); + + raw_ref zcash_wallet_service_; + ZCashActionContext context_; + raw_ref keyring_service_; + ZCashTransaction transaction_; + ZCashCompleteTransactionTaskCallback callback_; + + std::optional error_; + std::optional chain_tip_height_; + +#if BUILDFLAG(ENABLE_ORCHARD) + std::optional> witness_inputs_; + std::optional anchor_block_height_; + std::optional anchor_tree_state_; +#endif // BUILDFLAG(ENABLE_ORCHARD) + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_COMPLETE_TRANSACTION_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.cc b/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.cc index 4eb134d4ada2..73d2b26a0cb1 100644 --- a/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.cc +++ b/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.cc @@ -16,16 +16,15 @@ namespace brave_wallet { ZCashCreateShieldTransactionTask::ZCashCreateShieldTransactionTask( + base::PassKey pass_key, ZCashWalletService& zcash_wallet_service, - const std::string& chain_id, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const OrchardAddrRawPart& receiver, std::optional memo, uint64_t amount, ZCashWalletService::CreateTransactionCallback callback) : zcash_wallet_service_(zcash_wallet_service), - chain_id_(chain_id), - account_id_(account_id.Clone()), + context_(std::move(context)), receiver_(receiver), memo_(memo), amount_(amount), @@ -33,6 +32,12 @@ ZCashCreateShieldTransactionTask::ZCashCreateShieldTransactionTask( ZCashCreateShieldTransactionTask::~ZCashCreateShieldTransactionTask() = default; +void ZCashCreateShieldTransactionTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + void ZCashCreateShieldTransactionTask::ScheduleWorkOnTask() { base::SequencedTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&ZCashCreateShieldTransactionTask::WorkOnTask, @@ -89,7 +94,7 @@ bool ZCashCreateShieldTransactionTask::CreateTransaction() { change_output.address = change_address_->address_string; change_output.amount = pick_transparent_inputs_result->change; change_output.script_pubkey = ZCashAddressToScriptPubkey( - change_output.address, chain_id_ == mojom::kZCashTestnet); + change_output.address, context_.chain_id == mojom::kZCashTestnet); } // Create shielded output @@ -101,8 +106,8 @@ bool ZCashCreateShieldTransactionTask::CreateTransaction() { orchard_output.addr = receiver_; orchard_output.memo = memo_; - auto orchard_unified_addr = - GetOrchardUnifiedAddress(receiver_, chain_id_ == mojom::kZCashTestnet); + auto orchard_unified_addr = GetOrchardUnifiedAddress( + receiver_, context_.chain_id == mojom::kZCashTestnet); if (!orchard_unified_addr) { error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); return false; @@ -118,14 +123,14 @@ bool ZCashCreateShieldTransactionTask::CreateTransaction() { void ZCashCreateShieldTransactionTask::GetAllUtxos() { zcash_wallet_service_->GetUtxos( - chain_id_, account_id_.Clone(), + context_.chain_id, context_.account_id.Clone(), base::BindOnce(&ZCashCreateShieldTransactionTask::OnGetUtxos, weak_ptr_factory_.GetWeakPtr())); } void ZCashCreateShieldTransactionTask::GetChangeAddress() { zcash_wallet_service_->DiscoverNextUnusedAddress( - account_id_, true, + context_.account_id.Clone(), true, base::BindOnce(&ZCashCreateShieldTransactionTask::OnGetChangeAddress, weak_ptr_factory_.GetWeakPtr())); } diff --git a/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.h b/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.h index 18e1c7579318..1e9a82bec4d3 100644 --- a/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.h +++ b/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.h @@ -14,33 +14,31 @@ #include "base/memory/raw_ref.h" #include "brave/components/brave_wallet/browser/internal/orchard_bundle_manager.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" #include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" -#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_wallet/common/zcash_utils.h" namespace brave_wallet { -// This tasks takes all transparent UTXOs for an account and -// creates transaction which transfers this funds to the internal shielded +// This tasks takes all transparent UTXOs for the provided account and +// creates transaction which transfers this funds to the provided shielded // address. class ZCashCreateShieldTransactionTask { public: - ~ZCashCreateShieldTransactionTask(); - - void ScheduleWorkOnTask(); - - private: - friend class ZCashWalletService; - ZCashCreateShieldTransactionTask( + base::PassKey pass_key, ZCashWalletService& zcash_wallet_service, - const std::string& chain_id, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const OrchardAddrRawPart& receiver, std::optional memo, uint64_t amount, ZCashWalletService::CreateTransactionCallback callback); + ~ZCashCreateShieldTransactionTask(); + + void Start(); + private: + void ScheduleWorkOnTask(); void WorkOnTask(); void GetAllUtxos(); @@ -58,12 +56,13 @@ class ZCashCreateShieldTransactionTask { bool CreateTransaction(); const raw_ref zcash_wallet_service_; // Owns `this`. - std::string chain_id_; - mojom::AccountIdPtr account_id_; + ZCashActionContext context_; OrchardAddrRawPart receiver_; std::optional memo_; uint64_t amount_; + bool started_ = false; + std::optional error_; std::optional utxo_map_; diff --git a/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.cc b/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.cc new file mode 100644 index 000000000000..bfc3c37f81ce --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.cc @@ -0,0 +1,142 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h" + +#include + +#include "components/grit/brave_components_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace brave_wallet { + +ZCashCreateShieldedTransactionTask::ZCashCreateShieldedTransactionTask( + base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + const OrchardAddrRawPart& receiver, + std::optional memo, + uint64_t amount, + ZCashWalletService::CreateTransactionCallback callback) + : zcash_wallet_service_(zcash_wallet_service), + context_(std::move(context)), + receiver_(receiver), + memo_(memo), + amount_(amount), + callback_(std::move(callback)) {} + +ZCashCreateShieldedTransactionTask::~ZCashCreateShieldedTransactionTask() = + default; + +void ZCashCreateShieldedTransactionTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + +void ZCashCreateShieldedTransactionTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashCreateShieldedTransactionTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCreateShieldedTransactionTask::WorkOnTask() { + if (error_) { + std::move(callback_).Run(base::unexpected(*error_)); + return; + } + + if (!spendable_notes_) { + GetSpendableNotes(); + return; + } + + if (!transaction_) { + CreateTransaction(); + return; + } + + std::move(callback_).Run(base::ok(std::move(*transaction_))); +} + +void ZCashCreateShieldedTransactionTask::GetSpendableNotes() { + context_.sync_state->AsyncCall(&OrchardSyncState::GetSpendableNotes) + .WithArgs(context_.account_id.Clone()) + .Then(base::BindOnce( + &ZCashCreateShieldedTransactionTask::OnGetSpendableNotes, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashCreateShieldedTransactionTask::OnGetSpendableNotes( + base::expected, OrchardStorage::Error> result) { + if (!result.has_value()) { + error_ = result.error().message; + ScheduleWorkOnTask(); + return; + } + + spendable_notes_ = result.value(); + ScheduleWorkOnTask(); +} + +void ZCashCreateShieldedTransactionTask::CreateTransaction() { + CHECK(spendable_notes_); + auto pick_result = PickZCashOrchardInputs(spendable_notes_.value(), amount_); + if (!pick_result) { + error_ = "Can't pick inputs"; + ScheduleWorkOnTask(); + return; + } + + ZCashTransaction zcash_transaction; + for (const auto& note : pick_result.value().inputs) { + OrchardInput orchard_input; + orchard_input.note = note; + zcash_transaction.orchard_part().inputs.push_back(std::move(orchard_input)); + } + + zcash_transaction.set_fee(pick_result->fee); + + // Create shielded change + if (pick_result->change != 0) { + OrchardOutput& orchard_output = + zcash_transaction.orchard_part().outputs.emplace_back(); + orchard_output.value = pick_result->change; + auto change_addr = + zcash_wallet_service_->keyring_service_->GetOrchardRawBytes( + context_.account_id.Clone(), + mojom::ZCashKeyId::New(context_.account_id->account_index, + 1 /* internal */, 0)); + if (!change_addr) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + orchard_output.addr = *change_addr; + } + + // Create shielded output + OrchardOutput& orchard_output = + zcash_transaction.orchard_part().outputs.emplace_back(); + orchard_output.value = zcash_transaction.TotalInputsAmount() - + zcash_transaction.fee() - pick_result->change; + orchard_output.addr = receiver_; + orchard_output.memo = memo_; + + auto orchard_unified_addr = GetOrchardUnifiedAddress( + receiver_, context_.chain_id == mojom::kZCashTestnet); + if (!orchard_unified_addr) { + error_ = l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR); + ScheduleWorkOnTask(); + return; + } + zcash_transaction.set_amount(orchard_output.value); + zcash_transaction.set_to(*orchard_unified_addr); + + transaction_ = std::move(zcash_transaction); + ScheduleWorkOnTask(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h b/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h new file mode 100644 index 000000000000..ed7254e9b44b --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h @@ -0,0 +1,74 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_CREATE_SHIELDED_TRANSACTION_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_CREATE_SHIELDED_TRANSACTION_TASK_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_transaction_utils.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" + +namespace brave_wallet { + +// Creates transaction within Orchard pool. +// Uses shielded inputs and shielded output. +class ZCashCreateShieldedTransactionTask { + public: + using CreateTransactionCallback = + ZCashWalletService::CreateTransactionCallback; + + ZCashCreateShieldedTransactionTask( + base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + const OrchardAddrRawPart& receiver, + std::optional memo, + uint64_t amount, + ZCashWalletService::CreateTransactionCallback callback); + + virtual ~ZCashCreateShieldedTransactionTask(); + + void Start(); + + private: + void ScheduleWorkOnTask(); + void WorkOnTask(); + + void GetSpendableNotes(); + void OnGetSpendableNotes( + base::expected, OrchardStorage::Error> result); + void CreateTransaction(); + + void GetAnchor(); + void OnGetAnchor(); + + void CalculateWitness(); + void OnWittnessCalculated(); + + raw_ref zcash_wallet_service_; + ZCashActionContext context_; + OrchardAddrRawPart receiver_; + std::optional memo_; + uint64_t amount_; + ZCashWalletService::CreateTransactionCallback callback_; + + bool started_ = false; + + std::optional error_; + std::optional> spendable_notes_; + std::optional picked_notes_; + std::optional spends_bundle_; + std::optional transaction_; + + base::WeakPtrFactory weak_ptr_factory_{ + this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_CREATE_SHIELDED_TRANSACTION_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.cc b/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.cc index d37ae7a36f81..96034a78530e 100644 --- a/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.cc +++ b/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.cc @@ -17,15 +17,14 @@ namespace brave_wallet { // CreateTransparentTransactionTask ZCashCreateTransparentTransactionTask::ZCashCreateTransparentTransactionTask( + base::PassKey pass_key, ZCashWalletService& zcash_wallet_service, - const std::string& chain_id, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const std::string& address_to, uint64_t amount, CreateTransactionCallback callback) : zcash_wallet_service_(zcash_wallet_service), - chain_id_(chain_id), - account_id_(account_id.Clone()), + context_(std::move(context)), amount_(amount), callback_(std::move(callback)) { transaction_.set_to(address_to); @@ -35,6 +34,12 @@ ZCashCreateTransparentTransactionTask::ZCashCreateTransparentTransactionTask( ZCashCreateTransparentTransactionTask:: ~ZCashCreateTransparentTransactionTask() = default; +void ZCashCreateTransparentTransactionTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + void ZCashCreateTransparentTransactionTask::ScheduleWorkOnTask() { base::SequencedTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, @@ -55,7 +60,7 @@ void ZCashCreateTransparentTransactionTask::WorkOnTask() { if (!chain_height_) { zcash_wallet_service_->zcash_rpc().GetLatestBlock( - chain_id_, + context_.chain_id, base::BindOnce(&ZCashCreateTransparentTransactionTask::OnGetChainHeight, weak_ptr_factory_.GetWeakPtr())); return; @@ -63,7 +68,7 @@ void ZCashCreateTransparentTransactionTask::WorkOnTask() { if (!change_address_) { zcash_wallet_service_->DiscoverNextUnusedAddress( - account_id_, true, + context_.account_id.Clone(), true, base::BindOnce( &ZCashCreateTransparentTransactionTask::OnGetChangeAddress, weak_ptr_factory_.GetWeakPtr())); @@ -72,7 +77,7 @@ void ZCashCreateTransparentTransactionTask::WorkOnTask() { if (utxo_map_.empty()) { zcash_wallet_service_->GetUtxos( - chain_id_, account_id_.Clone(), + context_.chain_id, context_.account_id.Clone(), base::BindOnce(&ZCashCreateTransparentTransactionTask::OnGetUtxos, weak_ptr_factory_.GetWeakPtr())); return; diff --git a/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.h b/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.h index 90fbe225b8f9..d1d2966b3c3e 100644 --- a/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.h +++ b/components/brave_wallet/browser/zcash/zcash_create_transparent_transaction_task.h @@ -9,35 +9,33 @@ #include #include "base/memory/raw_ref.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" #include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" -#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" namespace brave_wallet { +// Creates transaction with transparent inputs and transparent outputs. class ZCashCreateTransparentTransactionTask { public: using UtxoMap = ZCashWalletService::UtxoMap; using CreateTransactionCallback = ZCashWalletService::CreateTransactionCallback; - - virtual ~ZCashCreateTransparentTransactionTask(); - - void ScheduleWorkOnTask(); - - private: - friend class ZCashWalletService; - ZCashCreateTransparentTransactionTask( + base::PassKey pass_key, ZCashWalletService& zcash_wallet_service, - const std::string& chain_id, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const std::string& address_to, uint64_t amount, CreateTransactionCallback callback); + virtual ~ZCashCreateTransparentTransactionTask(); + + void Start(); + private: + void ScheduleWorkOnTask(); void WorkOnTask(); - bool IsTestnet() { return chain_id_ == mojom::kZCashTestnet; } + bool IsTestnet() { return context_.chain_id == mojom::kZCashTestnet; } void SetError(const std::string& error_string) { error_ = error_string; } @@ -51,11 +49,12 @@ class ZCashCreateTransparentTransactionTask { base::expected result); const raw_ref zcash_wallet_service_; // Owns `this`. - std::string chain_id_; - mojom::AccountIdPtr account_id_; + ZCashActionContext context_; uint64_t amount_; CreateTransactionCallback callback_; + bool started_ = false; + std::optional chain_height_; ZCashWalletService::UtxoMap utxo_map_; diff --git a/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.cc b/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.cc index 3bcf5806a113..2527dd096841 100644 --- a/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.cc +++ b/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.cc @@ -14,18 +14,25 @@ namespace brave_wallet { ZCashDiscoverNextUnusedZCashAddressTask:: ZCashDiscoverNextUnusedZCashAddressTask( + base::PassKey pass_key, base::WeakPtr zcash_wallet_service, - mojom::AccountIdPtr account_id, - mojom::ZCashAddressPtr start_address, + const mojom::AccountIdPtr& account_id, + const mojom::ZCashAddressPtr& start_address, ZCashWalletService::DiscoverNextUnusedAddressCallback callback) : zcash_wallet_service_(std::move(zcash_wallet_service)), - account_id_(std::move(account_id)), - start_address_(std::move(start_address)), + account_id_(account_id.Clone()), + start_address_(start_address.Clone()), callback_(std::move(callback)) {} ZCashDiscoverNextUnusedZCashAddressTask:: ~ZCashDiscoverNextUnusedZCashAddressTask() = default; +void ZCashDiscoverNextUnusedZCashAddressTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + void ZCashDiscoverNextUnusedZCashAddressTask::ScheduleWorkOnTask() { base::SequencedTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, diff --git a/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.h b/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.h index 8340e8d49a6d..e7247bf78f28 100644 --- a/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.h +++ b/components/brave_wallet/browser/zcash/zcash_discover_next_unused_zcash_address_task.h @@ -14,19 +14,22 @@ namespace brave_wallet { class ZCashDiscoverNextUnusedZCashAddressTask : public base::RefCounted { public: - void ScheduleWorkOnTask(); + ZCashDiscoverNextUnusedZCashAddressTask( + base::PassKey pass_key, + base::WeakPtr zcash_wallet_service, + const mojom::AccountIdPtr& account_id, + const mojom::ZCashAddressPtr& start_address, + ZCashWalletService::DiscoverNextUnusedAddressCallback callback); + + void Start(); private: - friend class ZCashWalletService; friend class base::RefCounted; - ZCashDiscoverNextUnusedZCashAddressTask( - base::WeakPtr zcash_wallet_service, - mojom::AccountIdPtr account_id, - mojom::ZCashAddressPtr start_address, - ZCashWalletService::DiscoverNextUnusedAddressCallback callback); virtual ~ZCashDiscoverNextUnusedZCashAddressTask(); + void ScheduleWorkOnTask(); + mojom::ZCashAddressPtr GetNextAddress(const mojom::ZCashAddressPtr& address); void WorkOnTask(); @@ -38,6 +41,9 @@ class ZCashDiscoverNextUnusedZCashAddressTask mojom::AccountIdPtr account_id_; mojom::ZCashAddressPtr start_address_; mojom::ZCashAddressPtr current_address_; + + bool started_ = false; + mojom::ZCashAddressPtr result_; std::optional block_end_; std::optional error_; diff --git a/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.cc b/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.cc new file mode 100644 index 000000000000..d1545751a443 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h" + +namespace brave_wallet { + +ZCashGetZCashChainTipStatusTask::ZCashGetZCashChainTipStatusTask( + base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + ZCashGetZCashChainTipStatusTaskCallback callback) + : zcash_wallet_service_(zcash_wallet_service), + context_(std::move(context)), + callback_(std::move(callback)) {} + +ZCashGetZCashChainTipStatusTask::~ZCashGetZCashChainTipStatusTask() = default; + +void ZCashGetZCashChainTipStatusTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + +void ZCashGetZCashChainTipStatusTask::WorkOnTask() { + if (error_) { + std::move(callback_).Run(base::unexpected(error_.value())); + zcash_wallet_service_->GetZCashChainTipStatusTaskDone(this); + return; + } + + if (!account_meta_) { + GetAccountMeta(); + return; + } + + if (!chain_tip_height_) { + GetChainTipHeight(); + return; + } + + uint32_t latest_scanned_block = + account_meta_->latest_scanned_block_id + ? account_meta_->latest_scanned_block_id.value() + : account_meta_->account_birthday; + + std::move(callback_).Run(base::ok(mojom::ZCashChainTipStatus::New( + latest_scanned_block, chain_tip_height_.value()))); + + zcash_wallet_service_->GetZCashChainTipStatusTaskDone(this); +} + +void ZCashGetZCashChainTipStatusTask::GetAccountMeta() { + context_.sync_state->AsyncCall(&OrchardSyncState::GetAccountMeta) + .WithArgs(context_.account_id.Clone()) + .Then(base::BindOnce(&ZCashGetZCashChainTipStatusTask::OnGetAccountMeta, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashGetZCashChainTipStatusTask::GetChainTipHeight() { + context_.zcash_rpc->GetLatestBlock( + context_.chain_id, + base::BindOnce( + &ZCashGetZCashChainTipStatusTask::OnGetChainTipHeightResult, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashGetZCashChainTipStatusTask::OnGetChainTipHeightResult( + base::expected result) { + if (!result.has_value()) { + error_ = "Failed to resolve chain tip"; + ScheduleWorkOnTask(); + return; + } + + chain_tip_height_ = (*result)->height; + ScheduleWorkOnTask(); +} + +void ZCashGetZCashChainTipStatusTask::OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result) { + if (!result.has_value() || !result.value()) { + error_ = "Failed to resolve account's meta"; + ScheduleWorkOnTask(); + return; + } + + account_meta_ = **result; + ScheduleWorkOnTask(); +} + +void ZCashGetZCashChainTipStatusTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashGetZCashChainTipStatusTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h b/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h new file mode 100644 index 000000000000..91a5a5a84ece --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h @@ -0,0 +1,59 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_GET_ZCASH_CHAIN_TIP_STATUS_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_GET_ZCASH_CHAIN_TIP_STATUS_TASK_H_ + +#include "base/functional/callback.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" + +namespace brave_wallet { + +// Resolves information regarding current chain tip and the latest scanned block. +class ZCashGetZCashChainTipStatusTask { + public: + using ZCashGetZCashChainTipStatusTaskCallback = base::OnceCallback)>; + + ZCashGetZCashChainTipStatusTask( + base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, + ZCashActionContext context, + ZCashGetZCashChainTipStatusTaskCallback callback); + ~ZCashGetZCashChainTipStatusTask(); + + void Start(); + + private: + void WorkOnTask(); + void ScheduleWorkOnTask(); + + void GetAccountMeta(); + void OnGetAccountMeta( + base::expected, + OrchardStorage::Error>); + + void GetChainTipHeight(); + void OnGetChainTipHeightResult( + base::expected result); + + raw_ref zcash_wallet_service_; + ZCashActionContext context_; + ZCashGetZCashChainTipStatusTaskCallback callback_; + + std::optional account_meta_; + std::optional chain_tip_height_; + std::optional error_; + + bool started_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_GET_ZCASH_CHAIN_TIP_STATUS_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_keyring.cc b/components/brave_wallet/browser/zcash/zcash_keyring.cc index 81fc7508a983..6824e2facd12 100644 --- a/components/brave_wallet/browser/zcash/zcash_keyring.cc +++ b/components/brave_wallet/browser/zcash/zcash_keyring.cc @@ -173,6 +173,20 @@ std::optional ZCashKeyring::GetOrchardFullViewKey( return esk->GetFullViewKey(); } +std::optional ZCashKeyring::GetOrchardSpendingKey( + const uint32_t& account_id) { + if (!orchard_key_) { + return std::nullopt; + } + + auto esk = orchard_key_->DeriveHardenedChild(account_id); + if (!esk) { + return std::nullopt; + } + + return esk->GetSpendingKey(); +} + #endif std::unique_ptr ZCashKeyring::DeriveAccount(uint32_t index) const { diff --git a/components/brave_wallet/browser/zcash/zcash_keyring.h b/components/brave_wallet/browser/zcash/zcash_keyring.h index d38dce0b035e..34e41bd4f9bf 100644 --- a/components/brave_wallet/browser/zcash/zcash_keyring.h +++ b/components/brave_wallet/browser/zcash/zcash_keyring.h @@ -45,6 +45,8 @@ class ZCashKeyring : public Secp256k1HDKeyring { const mojom::ZCashKeyId& key_id); std::optional GetOrchardFullViewKey( const uint32_t& account_id); + std::optional GetOrchardSpendingKey( + const uint32_t& account_id); #endif std::optional> SignMessage( diff --git a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc index cdb6f62e199d..69c25936a0e4 100644 --- a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc +++ b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc @@ -14,6 +14,7 @@ namespace brave_wallet { ZCashResolveBalanceTask::ZCashResolveBalanceTask( + base::PassKey pass_key, ZCashWalletService& zcash_wallet_service, const std::string& chain_id, mojom::AccountIdPtr account_id, @@ -23,7 +24,13 @@ ZCashResolveBalanceTask::ZCashResolveBalanceTask( account_id_(std::move(account_id)), callback_(std::move(callback)) {} -ZCashResolveBalanceTask::~ZCashResolveBalanceTask() {} +ZCashResolveBalanceTask::~ZCashResolveBalanceTask() = default; + +void ZCashResolveBalanceTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} void ZCashResolveBalanceTask::ScheduleWorkOnTask() { base::SequencedTaskRunner::GetCurrentDefault()->PostTask( diff --git a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h index b4c170bde4e9..6ff1b040db77 100644 --- a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h +++ b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h @@ -23,15 +23,17 @@ class ZCashResolveBalanceTask { public: using ZCashResolveBalanceTaskCallback = base::OnceCallback)>; - ZCashResolveBalanceTask(ZCashWalletService& zcash_wallet_service, + ZCashResolveBalanceTask(base::PassKey pass_key, + ZCashWalletService& zcash_wallet_service, const std::string& chain_id, mojom::AccountIdPtr account_id, ZCashResolveBalanceTaskCallback callback); ~ZCashResolveBalanceTask(); - void ScheduleWorkOnTask(); + void Start(); private: + void ScheduleWorkOnTask(); void WorkOnTask(); void OnDiscoveryDoneForBalance( @@ -53,6 +55,8 @@ class ZCashResolveBalanceTask { mojom::AccountIdPtr account_id_; ZCashResolveBalanceTaskCallback callback_; + bool started_ = false; + std::optional error_; std::optional discovery_result_; std::optional utxo_map_; diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.cc b/components/brave_wallet/browser/zcash/zcash_rpc.cc index ee5f4e7d868e..860b4b7f1044 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.cc +++ b/components/brave_wallet/browser/zcash/zcash_rpc.cc @@ -259,6 +259,23 @@ const GURL MakeGetCompactBlocksURL(const GURL& base_url) { return base_url.ReplaceComponents(replacements); } +const GURL MakeGetSubtreeRootsURL(const GURL& base_url) { + if (!base_url.is_valid()) { + return GURL(); + } + if (!UrlPathEndsWithSlash(base_url)) { + return GURL(); + } + + GURL::Replacements replacements; + std::string path = + base::StrCat({base_url.path(), + "cash.z.wallet.sdk.rpc.CompactTxStreamer/GetSubtreeRoots"}); + replacements.SetPathStr(path); + + return base_url.ReplaceComponents(replacements); +} + std::string MakeGetTreeStateURLParams( const zcash::mojom::BlockIDPtr& block_id) { ::zcash::BlockID request; @@ -336,6 +353,16 @@ std::string MakeGetCompactBlocksParams(uint32_t block_start, return GetPrefixedProtobuf(range.SerializeAsString()); } +std::string MakeGetSubtreeRootsParams(uint32_t start, uint32_t entries) { + ::zcash::GetSubtreeRootsArg arg; + + arg.set_startindex(start); + arg.set_maxentries(entries); + arg.set_shieldedprotocol(::zcash::ShieldedProtocol::orchard); + + return GetPrefixedProtobuf(arg.SerializeAsString()); +} + std::unique_ptr MakeGRPCLoader( const GURL& url, const std::string& body) { @@ -517,6 +544,36 @@ void ZCashRpc::GetCompactBlocks(const std::string& chain_id, (*it)->DownloadAsStream(url_loader_factory_.get(), handler_it->get()); } +void ZCashRpc::GetSubtreeRoots(const std::string& chain_id, + uint32_t start, + uint32_t entries, + GetSubtreeRootsCallback callback) { + GURL request_url = MakeGetSubtreeRootsURL(GetNetworkURL(chain_id)); + + if (!request_url.is_valid()) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + auto url_loader = + MakeGRPCLoader(request_url, MakeGetSubtreeRootsParams(start, entries)); + + UrlLoadersList::iterator it = url_loaders_list_.insert( + url_loaders_list_.begin(), std::move(url_loader)); + + StreamHandlersList::iterator handler_it = stream_handlers_list_.insert( + stream_handlers_list_.begin(), + std::make_unique()); + + static_cast(handler_it->get()) + ->set_callback(base::BindOnce(&ZCashRpc::OnGetSubtreeRootsResponse, + weak_ptr_factory_.GetWeakPtr(), + std::move(callback), it, handler_it)); + + (*it)->DownloadAsStream(url_loader_factory_.get(), handler_it->get()); +} + void ZCashRpc::OnGetCompactBlocksResponse( ZCashRpc::GetCompactBlocksCallback callback, UrlLoadersList::iterator it, @@ -537,6 +594,26 @@ void ZCashRpc::OnGetCompactBlocksResponse( weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } +void ZCashRpc::OnGetSubtreeRootsResponse( + ZCashRpc::GetSubtreeRootsCallback callback, + UrlLoadersList::iterator it, + StreamHandlersList::iterator handler_it, + base::expected, std::string> result) { + url_loaders_list_.erase(it); + stream_handlers_list_.erase(handler_it); + + if (!result.has_value()) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + GetDecoder()->ParseSubtreeRoots( + *result, + base::BindOnce(&ZCashRpc::OnParseSubtreeRoots, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + void ZCashRpc::OnGetUtxosResponse(ZCashRpc::GetUtxoListCallback callback, UrlLoadersList::iterator it, std::unique_ptr response_body) { @@ -571,6 +648,16 @@ void ZCashRpc::OnParseCompactBlocks( } } +void ZCashRpc::OnParseSubtreeRoots( + GetSubtreeRootsCallback callback, + std::optional> subtree_roots) { + if (subtree_roots) { + std::move(callback).Run(std::move(subtree_roots.value())); + } else { + std::move(callback).Run(base::unexpected("Cannot parse subtree roots")); + } +} + template void ZCashRpc::OnParseResult( base::OnceCallback)> callback, diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.h b/components/brave_wallet/browser/zcash/zcash_rpc.h index 3234c255ad00..f657932d5d53 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.h +++ b/components/brave_wallet/browser/zcash/zcash_rpc.h @@ -44,6 +44,8 @@ class ZCashRpc { base::expected)>; using GetCompactBlocksCallback = base::OnceCallback, std::string>)>; + using GetSubtreeRootsCallback = base::OnceCallback, std::string>)>; ZCashRpc(NetworkManager* network_manager, scoped_refptr url_loader_factory); @@ -81,6 +83,11 @@ class ZCashRpc { uint32_t to, GetCompactBlocksCallback callback); + virtual void GetSubtreeRoots(const std::string& chain_id, + uint32_t start, + uint32_t entries, + GetSubtreeRootsCallback); + private: friend class base::RefCountedThreadSafe; @@ -119,6 +126,12 @@ class ZCashRpc { StreamHandlersList::iterator handler_it, base::expected, std::string> result); + void OnGetSubtreeRootsResponse( + ZCashRpc::GetSubtreeRootsCallback callback, + UrlLoadersList::iterator it, + StreamHandlersList::iterator handler_it, + base::expected, std::string> result); + template void OnParseResult(base::OnceCallback)>, T value); @@ -127,6 +140,10 @@ class ZCashRpc { GetCompactBlocksCallback callback, std::optional> compact_blocks); + void OnParseSubtreeRoots( + GetSubtreeRootsCallback callback, + std::optional> subtree_roots); + mojo::AssociatedRemote& GetDecoder(); GURL GetNetworkURL(const std::string& chain_id); diff --git a/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.cc b/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.cc new file mode 100644 index 000000000000..5ccc92c46bbc --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.cc @@ -0,0 +1,170 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h" + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" + +namespace brave_wallet { + +namespace { +constexpr uint32_t kBatchSize = 1024; +} // namespace + +ZCashScanBlocksTask::ZCashScanBlocksTask( + ZCashActionContext& context, + ZCashShieldSyncService::OrchardBlockScannerProxy& scanner, + ZCashScanBlocksTaskObserver observer, + std::optional to) + : context_(context), + scanner_(scanner), + observer_(std::move(observer)), + to_(to) {} + +ZCashScanBlocksTask::~ZCashScanBlocksTask() = default; + +void ZCashScanBlocksTask::Start() { + CHECK(!started_); + started_ = true; + ScheduleWorkOnTask(); +} + +void ZCashScanBlocksTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashScanBlocksTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashScanBlocksTask::WorkOnTask() { + if (error_) { + observer_.Run(base::unexpected(*error_)); + return; + } + + if (!account_meta_) { + GetAccountMeta(); + return; + } + + if (!chain_tip_block_) { + GetChainTip(); + return; + } + + if (!scan_ranges_) { + PrepareScanRanges(); + return; + } + + if (!scan_ranges_->empty()) { + ScanRanges(); + return; + } +} + +void ZCashScanBlocksTask::PrepareScanRanges() { + CHECK(account_meta_); + uint32_t from = account_meta_->latest_scanned_block_id + ? account_meta_->latest_scanned_block_id.value() + 1 + : account_meta_->account_birthday; + if (to_ && (chain_tip_block_.value() < to_.value() || to_.value() < from)) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToUpdateChainTip, + "Scan range error"}; + ScheduleWorkOnTask(); + return; + } + uint32_t to = to_.value_or(chain_tip_block_.value()); + + start_block_ = from; + end_block_ = to; + + initial_ranges_count_ = + std::ceil(static_cast((to - from + 1)) / kBatchSize); + scan_ranges_ = std::deque(); + for (size_t i = 0; i < initial_ranges_count_.value(); i++) { + scan_ranges_->push_back(ScanRange{ + static_cast(from + i * kBatchSize), + std::min(to, static_cast(from + (i + 1) * kBatchSize))}); + } + ScheduleWorkOnTask(); +} + +void ZCashScanBlocksTask::GetAccountMeta() { + context_->sync_state->AsyncCall(&OrchardSyncState::GetAccountMeta) + .WithArgs(context_->account_id.Clone()) + .Then(base::BindOnce(&ZCashScanBlocksTask::OnGetAccountMeta, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashScanBlocksTask::OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result) { + if (!result.has_value() || !result.value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount, + "Failed to retrieve account"}; + ScheduleWorkOnTask(); + return; + } + + account_meta_ = **result; + ScheduleWorkOnTask(); +} + +void ZCashScanBlocksTask::GetChainTip() { + context_->zcash_rpc->GetLatestBlock( + context_->chain_id, base::BindOnce(&ZCashScanBlocksTask::OnGetChainTip, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashScanBlocksTask::OnGetChainTip( + base::expected result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToUpdateChainTip, + result.error()}; + ScheduleWorkOnTask(); + return; + } + + chain_tip_block_ = (*result)->height; + ScheduleWorkOnTask(); +} + +void ZCashScanBlocksTask::ScanRanges() { + CHECK(scan_ranges_); + CHECK(!scan_ranges_->empty()); + auto scan_range = scan_ranges_->front(); + scan_ranges_->pop_front(); + current_block_range_ = std::make_unique( + context_.get(), scanner_.get(), scan_range.from, scan_range.to, + base::BindOnce(&ZCashScanBlocksTask::OnScanningRangeComplete, + weak_ptr_factory_.GetWeakPtr())); + current_block_range_->Start(); +} + +void ZCashScanBlocksTask::OnScanningRangeComplete( + base::expected result) { + if (!result.has_value()) { + error_ = result.error(); + ScheduleWorkOnTask(); + return; + } + ZCashShieldSyncService::ScanRangeResult scan_ranges_result; + scan_ranges_result.end_block = end_block_.value(); + scan_ranges_result.start_block = start_block_.value(); + scan_ranges_result.total_ranges = initial_ranges_count_.value(); + scan_ranges_result.ready_ranges = + initial_ranges_count_.value() - scan_ranges_.value().size(); + observer_.Run(scan_ranges_result); + + ScheduleWorkOnTask(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h b/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h new file mode 100644 index 000000000000..9e32599dbd07 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_SCAN_BLOCKS_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_SCAN_BLOCKS_TASK_H_ + +#include +#include +#include + +#include "base/threading/sequence_bound.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_blocks_batch_scan_task.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h" + +namespace brave_wallet { + +// ZCashScanBlocksTask scans blocks from the last scanned block to the provided +// right border. Splits this range to subranges and uses a bunch of smaller +// tasks to process. Current implementation uses sequentally scanning. Parallel +// implementation TBD. Notifies client with the progress. See also +// ZCashBlocksBatchScanTask. +class ZCashScanBlocksTask { + public: + using ZCashScanBlocksTaskObserver = base::RepeatingCallback)>; + + ZCashScanBlocksTask(ZCashActionContext& context, + ZCashShieldSyncService::OrchardBlockScannerProxy& scanner, + ZCashScanBlocksTaskObserver observer, + // Right border for scanning, chain tip is used + // if std::nullopt provided + std::optional to); + ~ZCashScanBlocksTask(); + + void Start(); + + private: + struct ScanRange { + uint32_t from; + uint32_t to; + }; + void ScheduleWorkOnTask(); + void WorkOnTask(); + + void GetAccountMeta(); + void OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result); + + void GetChainTip(); + void OnGetChainTip( + base::expected result); + + void PrepareScanRanges(); + + void ScanRanges(); + void OnScanningRangeComplete( + base::expected result); + + raw_ref context_; + raw_ref scanner_; + + ZCashScanBlocksTaskObserver observer_; + + std::optional to_; + std::optional start_block_; + std::optional end_block_; + + bool started_ = false; + + std::optional error_; + std::optional account_meta_; + std::optional chain_tip_block_; + std::optional> scan_ranges_; + std::optional initial_ranges_count_; + std::unique_ptr current_block_range_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_SCAN_BLOCKS_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_serializer.cc b/components/brave_wallet/browser/zcash/zcash_serializer.cc index 75e03c030788..1c49f309ca21 100644 --- a/components/brave_wallet/browser/zcash/zcash_serializer.cc +++ b/components/brave_wallet/browser/zcash/zcash_serializer.cc @@ -231,9 +231,11 @@ std::array ZCashSerializer::CalculateTxIdDigest( { std::vector data; BtcLikeSerializerStream stream(&data); - stream.PushBytes(ZCashSerializer::HashPrevouts(zcash_transaction)); - stream.PushBytes(ZCashSerializer::HashSequences(zcash_transaction)); - stream.PushBytes(ZCashSerializer::HashOutputs(zcash_transaction)); + if (!zcash_transaction.transparent_part().IsEmpty()) { + stream.PushBytes(ZCashSerializer::HashPrevouts(zcash_transaction)); + stream.PushBytes(ZCashSerializer::HashSequences(zcash_transaction)); + stream.PushBytes(ZCashSerializer::HashOutputs(zcash_transaction)); + } transparent_hash = blake2b256( data, base::byte_span_from_cstring(kTransparentHashPersonalizer)); } @@ -281,14 +283,17 @@ std::array ZCashSerializer::CalculateSignatureDigest( { std::vector data; BtcLikeSerializerStream stream(&data); - stream.Push8(zcash_transaction.sighash_type()); - stream.PushBytes(HashPrevouts(zcash_transaction)); - - stream.PushBytes(HashAmounts(zcash_transaction)); - stream.PushBytes(HashScriptPubKeys(zcash_transaction)); - stream.PushBytes(HashSequences(zcash_transaction)); - stream.PushBytes(HashOutputs(zcash_transaction)); - stream.PushBytes(HashTxIn(input)); + + if (!zcash_transaction.transparent_part().IsEmpty()) { + stream.Push8(zcash_transaction.sighash_type()); + stream.PushBytes(HashPrevouts(zcash_transaction)); + + stream.PushBytes(HashAmounts(zcash_transaction)); + stream.PushBytes(HashScriptPubKeys(zcash_transaction)); + stream.PushBytes(HashSequences(zcash_transaction)); + stream.PushBytes(HashOutputs(zcash_transaction)); + stream.PushBytes(HashTxIn(input)); + } transparent_hash = blake2b256( data, base::byte_span_from_cstring(kTransparentHashPersonalizer)); diff --git a/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc b/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc index 273242c5d207..9e2bf8b72862 100644 --- a/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc @@ -237,7 +237,8 @@ TEST(ZCashSerializerTest, OrchardBundle) { OrchardBundleManager::OverrideRandomSeedForTesting(0); auto orchard_bundle_manager = OrchardBundleManager::Create( - std::vector() /* Use empty orchard tree */, std::move(outputs)); + std::vector() /* Use empty orchard tree */, + OrchardSpendsBundle(), std::move(outputs)); tx.orchard_part().digest = orchard_bundle_manager->GetOrchardDigest(); diff --git a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc index fffc45cca813..68bffc600f62 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc @@ -9,6 +9,9 @@ #include #include "base/task/thread_pool.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_scan_blocks_task.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h" #include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" #include "brave/components/brave_wallet/common/common_utils.h" #include "brave/components/brave_wallet/common/hex_utils.h" @@ -34,8 +37,14 @@ size_t GetCode(ZCashShieldSyncService::ErrorCode error) { return 5; case ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount: return 6; - case ZCashShieldSyncService::ErrorCode::kScannerError: + case ZCashShieldSyncService::ErrorCode::kFailedToVerifyChainState: return 7; + case ZCashShieldSyncService::ErrorCode::kFailedToUpdateSubtreeRoots: + return 8; + case ZCashShieldSyncService::ErrorCode::kDatabaseError: + return 9; + case ZCashShieldSyncService::ErrorCode::kScannerError: + return 10; } } @@ -63,18 +72,13 @@ void ZCashShieldSyncService::OrchardBlockScannerProxy::ScanBlocks( } ZCashShieldSyncService::ZCashShieldSyncService( - ZCashRpc& zcash_rpc, - base::SequenceBound& zcash_orchard_sync_state, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const mojom::ZCashAccountShieldBirthdayPtr& account_birthday, const OrchardFullViewKey& fvk, base::WeakPtr observer) - : zcash_rpc_(zcash_rpc), - zcash_orchard_sync_state_(zcash_orchard_sync_state), - account_id_(account_id.Clone()), + : context_(std::move(context)), account_birthday_(account_birthday.Clone()), observer_(std::move(observer)) { - chain_id_ = GetNetworkForZCashKeyring(account_id->keyring_id); block_scanner_ = std::make_unique(fvk); } @@ -85,10 +89,11 @@ void ZCashShieldSyncService::SetOrchardBlockScannerProxyForTesting( block_scanner_ = std::move(block_scanner); } -void ZCashShieldSyncService::StartSyncing() { +void ZCashShieldSyncService::StartSyncing(std::optional to) { + to_ = to; ScheduleWorkOnTask(); if (observer_) { - observer_->OnSyncStart(account_id_); + observer_->OnSyncStart(context_.account_id); } } @@ -103,56 +108,49 @@ void ZCashShieldSyncService::ScheduleWorkOnTask() { } void ZCashShieldSyncService::WorkOnTask() { - if (stopped_) { - return; - } - if (error_) { + verify_chain_state_task_.reset(); + update_subtree_roots_task_.reset(); + scan_blocks_task_.reset(); + if (observer_) { observer_->OnSyncError( - account_id_, + context_.account_id, base::NumberToString(GetCode(error_->code)) + ": " + error_->message); } return; } - if (!chain_tip_block_) { - UpdateChainTip(); - return; - } - if (!account_meta_) { GetOrCreateAccount(); return; } - if (!latest_scanned_block_) { - VerifyChainState(*account_meta_); - return; - } - - if (!spendable_notes_) { - UpdateSpendableNotes(); + if (!chain_state_verified_) { + VerifyChainState(); return; + } else { + verify_chain_state_task_.reset(); } - if (observer_) { - observer_->OnSyncStatusUpdate(account_id_, current_sync_status_.Clone()); - } - - if (!downloaded_blocks_ && *latest_scanned_block_ < *chain_tip_block_) { - DownloadBlocks(); + if (!subtree_roots_updated_) { + UpdateSubtreeRoots(); return; + } else { + update_subtree_roots_task_.reset(); } - if (downloaded_blocks_) { - ScanBlocks(); + if (!scan_blocks_task_) { + StartBlockScanning(); return; } - if (observer_) { - observer_->OnSyncStop(account_id_); - stopped_ = true; + if (latest_scanned_block_result_ && + latest_scanned_block_result_->IsFinished()) { + scan_blocks_task_.reset(); + if (observer_) { + observer_->OnSyncStop(context_.account_id); + } } } @@ -165,7 +163,7 @@ void ZCashShieldSyncService::GetOrCreateAccount() { } sync_state() .AsyncCall(&OrchardSyncState::GetAccountMeta) - .WithArgs(account_id_.Clone()) + .WithArgs(context_.account_id.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetAccountMeta, weak_ptr_factory_.GetWeakPtr())); } @@ -174,14 +172,15 @@ void ZCashShieldSyncService::OnGetAccountMeta( base::expected, OrchardStorage::Error> result) { if (!result.has_value()) { - error_ = Error{ErrorCode::kFailedToRetrieveAccount, result.error().message}; + error_ = Error{ErrorCode::kFailedToInitAccount, "Database error"}; ScheduleWorkOnTask(); + return; } if (!result.value()) { InitAccount(); return; } - account_meta_ = *result; + account_meta_ = **result; if (account_meta_->latest_scanned_block_id.value() && (account_meta_->latest_scanned_block_id.value() < account_meta_->account_birthday)) { @@ -193,7 +192,7 @@ void ZCashShieldSyncService::OnGetAccountMeta( void ZCashShieldSyncService::InitAccount() { sync_state() .AsyncCall(&OrchardSyncState::RegisterAccount) - .WithArgs(account_id_.Clone(), account_birthday_->value) + .WithArgs(context_.account_id.Clone(), account_birthday_->value) .Then(base::BindOnce(&ZCashShieldSyncService::OnAccountInit, weak_ptr_factory_.GetWeakPtr())); } @@ -208,113 +207,87 @@ void ZCashShieldSyncService::OnAccountInit( ScheduleWorkOnTask(); } -void ZCashShieldSyncService::VerifyChainState( - OrchardStorage::AccountMeta account_meta) { - if (account_meta.account_birthday < kNu5BlockUpdate) { - error_ = Error{ErrorCode::kFailedToRetrieveAccount, - "Wrong birthday block height"}; - ScheduleWorkOnTask(); - return; - } - if (!account_meta.latest_scanned_block_id) { - latest_scanned_block_ = account_meta.account_birthday - 1; - ScheduleWorkOnTask(); - return; - } - // If block chain has removed blocks we already scanned then we need to handle - // chain reorg. - if (*chain_tip_block_ < account_meta.latest_scanned_block_id.value()) { - // Assume that chain reorg can't affect more than kChainReorgBlockDelta - // blocks So we can just fallback on this number from the chain tip block. - GetTreeStateForChainReorg(*chain_tip_block_ - kChainReorgBlockDelta); - return; - } - // Retrieve block info for last scanned block id to check whether block hash - // is the same - auto block_id = zcash::mojom::BlockID::New( - account_meta.latest_scanned_block_id.value(), std::vector()); - zcash_rpc().GetTreeState( - chain_id_, std::move(block_id), - base::BindOnce( - &ZCashShieldSyncService::OnGetTreeStateForChainVerification, - weak_ptr_factory_.GetWeakPtr(), std::move(account_meta))); +void ZCashShieldSyncService::VerifyChainState() { + CHECK(!verify_chain_state_task_.get()); + verify_chain_state_task_ = std::make_unique( + context_, base::BindOnce(&ZCashShieldSyncService::OnChainStateVerified, + weak_ptr_factory_.GetWeakPtr())); + verify_chain_state_task_->Start(); } -void ZCashShieldSyncService::OnGetTreeStateForChainVerification( - OrchardStorage::AccountMeta account_meta, - base::expected tree_state) { - if (!tree_state.has_value() || !tree_state.value()) { - error_ = Error{ErrorCode::kFailedToReceiveTreeState, tree_state.error()}; +void ZCashShieldSyncService::OnChainStateVerified( + base::expected result) { + if (!result.has_value()) { + error_ = result.error(); ScheduleWorkOnTask(); return; } - auto backend_block_hash = RevertHex(tree_state.value()->hash); - if (backend_block_hash != account_meta.latest_scanned_block_hash.value()) { - // Assume that chain reorg can't affect more than kChainReorgBlockDelta - // blocks So we can just fallback on this number. - uint32_t new_block_id = - account_meta.latest_scanned_block_id.value() > kChainReorgBlockDelta - ? account_meta.latest_scanned_block_id.value() - - kChainReorgBlockDelta - : 0; - GetTreeStateForChainReorg(new_block_id); + + if (!result.value()) { + error_ = Error{ErrorCode::kFailedToVerifyChainState, ""}; + ScheduleWorkOnTask(); return; } - // Restore latest scanned block from the database so we can continue - // scanning from previous point. - latest_scanned_block_ = account_meta.latest_scanned_block_id; + chain_state_verified_ = true; ScheduleWorkOnTask(); } -void ZCashShieldSyncService::GetTreeStateForChainReorg( - uint32_t new_block_height) { - // Query block info by block height - auto block_id = - zcash::mojom::BlockID::New(new_block_height, std::vector()); - zcash_rpc().GetTreeState( - chain_id_, std::move(block_id), - base::BindOnce(&ZCashShieldSyncService::OnGetTreeStateForChainReorg, - weak_ptr_factory_.GetWeakPtr(), new_block_height)); +void ZCashShieldSyncService::UpdateSubtreeRoots() { + CHECK(!update_subtree_roots_task_); + update_subtree_roots_task_ = std::make_unique( + context_, base::BindOnce(&ZCashShieldSyncService::OnSubtreeRootsUpdated, + weak_ptr_factory_.GetWeakPtr())); + update_subtree_roots_task_->Start(); } -void ZCashShieldSyncService::OnGetTreeStateForChainReorg( - uint32_t new_block_height, - base::expected tree_state) { - if (!tree_state.has_value() || !tree_state.value() || - new_block_height != (*tree_state)->height) { - error_ = Error{ErrorCode::kFailedToReceiveTreeState, tree_state.error()}; +void ZCashShieldSyncService::OnSubtreeRootsUpdated(bool result) { + if (!result) { + error_ = Error{ErrorCode::kFailedToUpdateSubtreeRoots, ""}; ScheduleWorkOnTask(); return; - } else { - // Reorg database so records related to removed blocks are wiped out - sync_state() - .AsyncCall(&OrchardSyncState::HandleChainReorg) - .WithArgs(account_id_.Clone(), (*tree_state)->height, - (*tree_state)->hash) - .Then(base::BindOnce( - &ZCashShieldSyncService::OnDatabaseUpdatedForChainReorg, - weak_ptr_factory_.GetWeakPtr(), (*tree_state)->height)); } + + subtree_roots_updated_ = true; + ScheduleWorkOnTask(); } -void ZCashShieldSyncService::OnDatabaseUpdatedForChainReorg( - uint32_t new_block_height, - base::expected result) { +void ZCashShieldSyncService::StartBlockScanning() { + CHECK(!scan_blocks_task_); + scan_blocks_task_ = std::make_unique( + context_, *block_scanner_, + base::BindRepeating(&ZCashShieldSyncService::OnScanRangeResult, + weak_ptr_factory_.GetWeakPtr()), + to_); + scan_blocks_task_->Start(); +} + +void ZCashShieldSyncService::OnScanRangeResult( + base::expected result) { if (!result.has_value()) { - error_ = Error{ErrorCode::kFailedToUpdateDatabase, result.error().message}; + error_ = result.error(); ScheduleWorkOnTask(); return; } - latest_scanned_block_ = new_block_height; - ScheduleWorkOnTask(); + latest_scanned_block_result_ = result.value(); + UpdateSpendableNotes(); +} + +uint32_t ZCashShieldSyncService::GetSpendableBalance() { + CHECK(spendable_notes_.has_value()); + uint32_t balance = 0; + for (const auto& note : spendable_notes_.value()) { + balance += note.amount; + } + return balance; } void ZCashShieldSyncService::UpdateSpendableNotes() { + spendable_notes_ = std::nullopt; sync_state() .AsyncCall(&OrchardSyncState::GetSpendableNotes) - .WithArgs(account_id_.Clone()) + .WithArgs(context_.account_id.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetSpendableNotes, weak_ptr_factory_.GetWeakPtr())); } @@ -324,121 +297,39 @@ void ZCashShieldSyncService::OnGetSpendableNotes( if (!result.has_value()) { error_ = Error{ErrorCode::kFailedToRetrieveSpendableNotes, result.error().message}; - } else { - spendable_notes_ = result.value(); - current_sync_status_ = mojom::ZCashShieldSyncStatus::New( - latest_scanned_block_.value(), chain_tip_block_.value(), - spendable_notes_->size(), GetSpendableBalance()); - } - ScheduleWorkOnTask(); -} - -void ZCashShieldSyncService::UpdateChainTip() { - zcash_rpc().GetLatestBlock( - chain_id_, base::BindOnce(&ZCashShieldSyncService::OnGetLatestBlock, - weak_ptr_factory_.GetWeakPtr())); -} - -void ZCashShieldSyncService::OnGetLatestBlock( - base::expected result) { - if (!result.has_value()) { - error_ = Error{ErrorCode::kFailedToUpdateChainTip, result.error()}; - } else { - chain_tip_block_ = (*result)->height; - } - ScheduleWorkOnTask(); -} - -void ZCashShieldSyncService::DownloadBlocks() { - zcash_rpc().GetCompactBlocks( - chain_id_, *latest_scanned_block_ + 1, - std::min(*chain_tip_block_, *latest_scanned_block_ + kScanBatchSize), - base::BindOnce(&ZCashShieldSyncService::OnBlocksDownloaded, - weak_ptr_factory_.GetWeakPtr())); -} - -void ZCashShieldSyncService::OnBlocksDownloaded( - base::expected, std::string> - result) { - if (!result.has_value()) { - error_ = Error{ErrorCode::kFailedToDownloadBlocks, result.error()}; - } else { - downloaded_blocks_ = std::move(result.value()); - } - ScheduleWorkOnTask(); -} - -void ZCashShieldSyncService::ScanBlocks() { - if (!downloaded_blocks_ || downloaded_blocks_->empty()) { - error_ = Error{ErrorCode::kScannerError, ""}; ScheduleWorkOnTask(); return; } - auto last_block_hash = ToHex(downloaded_blocks_->back()->hash); - auto last_block_height = downloaded_blocks_->back()->height; + spendable_notes_ = result.value(); - block_scanner_->ScanBlocks( - OrchardTreeState(), std::move(downloaded_blocks_.value()), - base::BindOnce(&ZCashShieldSyncService::OnBlocksScanned, - weak_ptr_factory_.GetWeakPtr(), last_block_height, - last_block_hash)); -} - -void ZCashShieldSyncService::OnBlocksScanned( - uint32_t last_block_height, - std::string last_block_hash, - base::expected - result) { - downloaded_blocks_ = std::nullopt; - if (!result.has_value()) { - error_ = Error{ErrorCode::kScannerError, ""}; - ScheduleWorkOnTask(); + if (latest_scanned_block_result_) { + current_sync_status_ = mojom::ZCashShieldSyncStatus::New( + latest_scanned_block_result_->start_block, + latest_scanned_block_result_->end_block, + latest_scanned_block_result_->total_ranges, + latest_scanned_block_result_->ready_ranges, spendable_notes_->size(), + GetSpendableBalance()); } else { - UpdateNotes(std::move(result.value()), last_block_height, last_block_hash); + current_sync_status_ = mojom::ZCashShieldSyncStatus::New( + latest_scanned_block_.value_or(0), latest_scanned_block_.value_or(0), 0, + 0, spendable_notes_->size(), GetSpendableBalance()); } -} - -void ZCashShieldSyncService::UpdateNotes( - OrchardBlockScanner::Result result, - uint32_t latest_scanned_block, - std::string latest_scanned_block_hash) { - sync_state() - .AsyncCall(&OrchardSyncState::ApplyScanResults) - .WithArgs(account_id_.Clone(), std::move(result), latest_scanned_block, - latest_scanned_block_hash) - .Then(base::BindOnce(&ZCashShieldSyncService::UpdateNotesComplete, - weak_ptr_factory_.GetWeakPtr(), - latest_scanned_block)); -} -void ZCashShieldSyncService::UpdateNotesComplete( - uint32_t new_latest_scanned_block, - base::expected result) { - if (!result.has_value()) { - error_ = Error{ErrorCode::kFailedToUpdateDatabase, result.error().message}; - } else { - latest_scanned_block_ = new_latest_scanned_block; - spendable_notes_ = std::nullopt; + if (observer_) { + observer_->OnSyncStatusUpdate(context_.account_id, + current_sync_status_.Clone()); } - ScheduleWorkOnTask(); -} -uint32_t ZCashShieldSyncService::GetSpendableBalance() { - CHECK(spendable_notes_.has_value()); - uint32_t balance = 0; - for (const auto& note : spendable_notes_.value()) { - balance += note.amount; - } - return balance; + ScheduleWorkOnTask(); } ZCashRpc& ZCashShieldSyncService::zcash_rpc() { - return zcash_rpc_.get(); + return context_.zcash_rpc.get(); } base::SequenceBound& ZCashShieldSyncService::sync_state() { - return zcash_orchard_sync_state_.get(); + return context_.sync_state.get(); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h index af0284d67d0a..508c3183ac7d 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h @@ -11,18 +11,20 @@ #include #include -#include "base/memory/scoped_refptr.h" -#include "base/threading/sequence_bound.h" #include "base/types/expected.h" #include "brave/components/brave_wallet/browser/internal/orchard_block_scanner.h" #include "brave/components/brave_wallet/browser/internal/orchard_sync_state.h" -#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" -#include "mojo/public/cpp/bindings/remote.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_action_context.h" namespace brave_wallet { class OrchardStorage; +class ZCashBlocksBatchScanTask; class ZCashRpc; +class ZCashScanBlocksTask; +class ZCashUpdateSubtreeRootsTask; +class ZCashVerifyChainStateTask; +class ZCashWalletService; // ZCashShieldSyncService downloads and scans blockchain blocks to find // spendable notes related to the account. @@ -38,6 +40,9 @@ class ZCashShieldSyncService { kFailedToReceiveTreeState, kFailedToInitAccount, kFailedToRetrieveAccount, + kFailedToVerifyChainState, + kFailedToUpdateSubtreeRoots, + kDatabaseError, kScannerError, }; @@ -46,6 +51,15 @@ class ZCashShieldSyncService { std::string message; }; + struct ScanRangeResult { + uint32_t start_block = 0; + uint32_t end_block = 0; + size_t total_ranges = 0; + size_t ready_ranges = 0; + + bool IsFinished() { return total_ranges == ready_ranges; } + }; + class Observer { public: virtual ~Observer() {} @@ -74,22 +88,23 @@ class ZCashShieldSyncService { }; ZCashShieldSyncService( - ZCashRpc& zcash_rpc, - base::SequenceBound& zcash_orchard_sync_state, - const mojom::AccountIdPtr& account_id, + ZCashActionContext context, const mojom::ZCashAccountShieldBirthdayPtr& account_birthday, - const std::array& fvk, + const OrchardFullViewKey& fvk, base::WeakPtr observer); virtual ~ZCashShieldSyncService(); bool IsStarted(); - void StartSyncing(); + void StartSyncing(std::optional to); mojom::ZCashShieldSyncStatusPtr GetSyncStatus(); private: FRIEND_TEST_ALL_PREFIXES(ZCashShieldSyncServiceTest, ScanBlocks); + friend class ZCashScanBlocksTask; + friend class ZCashUpdateSubtreeRootsTask; + friend class ZCashVerifyChainStateTask; void SetOrchardBlockScannerProxyForTesting( std::unique_ptr block_scanner); @@ -106,79 +121,56 @@ class ZCashShieldSyncService { void OnAccountInit( base::expected error); - // Get last known block in the blockchain - void UpdateChainTip(); - void OnGetLatestBlock( - base::expected result); - // Chain reorg flow // Chain reorg happens when latest blocks are removed from the blockchain // We assume that there is a limit of reorg depth - kChainReorgBlockDelta + void VerifyChainState(); + void OnChainStateVerified( + base::expected result); + + void UpdateSubtreeRoots(); + void OnSubtreeRootsUpdated(bool result); - // Verifies that last known scanned block hash is unchanged - void VerifyChainState(OrchardStorage::AccountMeta account_meta); - void OnGetTreeStateForChainVerification( - OrchardStorage::AccountMeta account_meta, - base::expected tree_state); - - // Resolves block hash for the block we are going to fallback - void GetTreeStateForChainReorg(uint32_t new_block_id); - void OnGetTreeStateForChainReorg( - uint32_t new_block_height, - base::expected tree_state); - void OnDatabaseUpdatedForChainReorg( - uint32_t new_block_height, - base::expected result); + uint32_t GetSpendableBalance(); // Update spendable notes state void UpdateSpendableNotes(); void OnGetSpendableNotes( base::expected, OrchardStorage::Error> result); - // Download, scan, update flow - // Download next bunch of blocks - void DownloadBlocks(); - void OnBlocksDownloaded( - base::expected, std::string> - result); - // Process a bunch of downloaded blocks to resolve related notes and - // nullifiers - void ScanBlocks(); - void OnBlocksScanned(uint32_t last_block_height, - std::string last_block_hash, - base::expected result); - void UpdateNotes(OrchardBlockScanner::Result result, - uint32_t latest_scanned_block, - std::string latest_scanned_block_hash); - void UpdateNotesComplete( - uint32_t new_latest_scanned_block, - base::expected result); + void StartBlockScanning(); + void OnScanRangeResult( + base::expected result); ZCashRpc& zcash_rpc(); base::SequenceBound& sync_state(); - uint32_t GetSpendableBalance(); std::optional error() { return error_; } // Params - raw_ref zcash_rpc_; - raw_ref> zcash_orchard_sync_state_; - mojom::AccountIdPtr account_id_; + ZCashActionContext context_; // Birthday of the account will be used to resolve initial scan range. mojom::ZCashAccountShieldBirthdayPtr account_birthday_; - base::FilePath db_dir_path_; base::WeakPtr observer_; - std::string chain_id_; + std::optional to_; std::unique_ptr block_scanner_; std::optional account_meta_; // Latest scanned block - std::optional latest_scanned_block_; - // Latest block in the blockchain - std::optional chain_tip_block_; - std::optional> downloaded_blocks_; + std::optional latest_scanned_block_; + + std::unique_ptr verify_chain_state_task_; + bool chain_state_verified_ = false; + + std::unique_ptr update_subtree_roots_task_; + bool subtree_roots_updated_ = false; + + std::unique_ptr scan_blocks_task_; + bool scanning_finished_ = false; + + std::optional latest_scanned_block_result_; + // Local cache of spendable notes to fast check on discovered nullifiers std::optional> spendable_notes_; std::optional error_; diff --git a/components/brave_wallet/browser/zcash/zcash_shield_sync_service_unittest.cc b/components/brave_wallet/browser/zcash/zcash_shield_sync_service_unittest.cc index 050fffdd6cb7..ad2bddf990c6 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service_unittest.cc @@ -60,6 +60,12 @@ class MockZCashRPC : public ZCashRpc { MOCK_METHOD2(GetLatestTreeState, void(const std::string& chain_id, GetTreeStateCallback callback)); + + MOCK_METHOD4(GetSubtreeRoots, + void(const std::string& chain_id, + uint32_t start, + uint32_t entries, + GetSubtreeRootsCallback)); }; class MockZCashShieldSyncServiceObserver @@ -99,8 +105,8 @@ class MockOrchardBlockScannerProxy : public ZCashShieldSyncService::OrchardBlockScannerProxy { public: using Callback = base::RepeatingCallback, + OrchardTreeState tree_state, + std::vector blocks, base::OnceCallback)> callback)>; @@ -155,8 +161,9 @@ class ZCashShieldSyncServiceTest : public testing::Test { OrchardFullViewKey fvk; sync_service_ = std::make_unique( - zcash_rpc_, sync_state_, account_id, account_birthday, fvk, - observer_->GetWeakPtr()); + ZCashActionContext(zcash_rpc_, sync_state_, account_id, + mojom::kZCashMainnet), + account_birthday, fvk, observer_->GetWeakPtr()); // Ensure previous OrchardStorage is destroyed on background thread task_environment_.RunUntilIdle(); @@ -198,11 +205,11 @@ class ZCashShieldSyncServiceTest : public testing::Test { // First 2 notes are spent if (block->height == kNu5BlockUpdate + 255) { - result.found_spends.push_back(OrchardNoteSpend{ - block->height, GenerateMockNullifier(account_id, 1)}); + result.found_spends.push_back(OrchardNoteSpend( + block->height, {GenerateMockNullifier(account_id, 1)})); } else if (block->height == kNu5BlockUpdate + 265) { - result.found_spends.push_back(OrchardNoteSpend{ - block->height, GenerateMockNullifier(account_id, 2)}); + result.found_spends.push_back(OrchardNoteSpend( + block->height, {GenerateMockNullifier(account_id, 2)})); } } std::move(callback).Run(std::move(result)); @@ -228,6 +235,14 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { sync_service()->SetOrchardBlockScannerProxyForTesting( std::move(mock_block_scanner)); + ON_CALL(zcash_rpc(), GetSubtreeRoots(_, _, _, _)) + .WillByDefault(::testing::Invoke( + [](const std::string& chain_id, uint32_t start, uint32_t entries, + MockZCashRPC::GetSubtreeRootsCallback callback) { + std::move(callback).Run( + std::vector()); + })); + ON_CALL(zcash_rpc(), GetLatestBlock(_, _)) .WillByDefault( ::testing::Invoke([](const std::string& chain_id, @@ -252,17 +267,20 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { ZCashRpc::GetCompactBlocksCallback callback) { std::vector blocks; for (uint32_t i = from; i <= to; i++) { + auto chain_metadata = zcash::mojom::ChainMetadata::New(); + chain_metadata->orchard_commitment_tree_size = 0; // Create empty block for testing blocks.push_back(zcash::mojom::CompactBlock::New( 0u, i, std::vector({0xbb, 0xaa}), std::vector(), 0u, std::vector(), - std::vector(), nullptr)); + std::vector(), + std::move(chain_metadata))); } std::move(callback).Run(std::move(blocks)); })); { - sync_service()->StartSyncing(); + sync_service()->StartSyncing(std::nullopt); task_environment_.RunUntilIdle(); auto sync_status = sync_service()->GetSyncStatus(); @@ -326,7 +344,7 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { })); { - sync_service()->StartSyncing(); + sync_service()->StartSyncing(std::nullopt); task_environment_.RunUntilIdle(); auto sync_status = sync_service()->GetSyncStatus(); @@ -368,7 +386,7 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { }))); { - sync_service()->StartSyncing(); + sync_service()->StartSyncing(std::nullopt); task_environment_.RunUntilIdle(); auto sync_status = sync_service()->GetSyncStatus(); @@ -431,7 +449,7 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { }))); { - sync_service()->StartSyncing(); + sync_service()->StartSyncing(std::nullopt); task_environment_.RunUntilIdle(); auto sync_status = sync_service()->GetSyncStatus(); diff --git a/components/brave_wallet/browser/zcash/zcash_transaction.cc b/components/brave_wallet/browser/zcash/zcash_transaction.cc index c083d50600b3..0caab4fc56b3 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction.cc +++ b/components/brave_wallet/browser/zcash/zcash_transaction.cc @@ -95,6 +95,9 @@ bool ZCashTransaction::TransparentPart::operator!=( const TransparentPart& other) const { return !(*this == other); } +bool ZCashTransaction::TransparentPart::IsEmpty() const { + return inputs.empty() && outputs.empty(); +} base::Value::Dict ZCashTransaction::Outpoint::ToValue() const { base::Value::Dict dict; @@ -269,6 +272,12 @@ base::Value::Dict ZCashTransaction::ToValue() const { inputs_value.Append(input.ToValue()); } + auto& orchard_inputs_value = + dict.Set("orchard_inputs", base::Value::List())->GetList(); + for (auto& input : orchard_part_.inputs) { + orchard_inputs_value.Append(input.ToValue()); + } + auto& outputs_value = dict.Set("outputs", base::Value::List())->GetList(); for (auto& output : transparent_part_.outputs) { outputs_value.Append(output.ToValue()); @@ -297,18 +306,35 @@ std::optional ZCashTransaction::FromValue( ZCashTransaction result; auto* inputs_list = value.FindList("inputs"); - if (!inputs_list) { + auto* orchard_inputs_list = value.FindList("orchard_inputs"); + if (!inputs_list && !orchard_inputs_list) { return std::nullopt; } - for (auto& item : *inputs_list) { - if (!item.is_dict()) { - return std::nullopt; + if (inputs_list) { + for (auto& item : *inputs_list) { + if (!item.is_dict()) { + return std::nullopt; + } + auto input_opt = ZCashTransaction::TxInput::FromValue(item.GetDict()); + if (!input_opt) { + return std::nullopt; + } + result.transparent_part_.inputs.push_back(std::move(*input_opt)); } - auto input_opt = ZCashTransaction::TxInput::FromValue(item.GetDict()); - if (!input_opt) { - return std::nullopt; + } + + if (orchard_inputs_list) { + for (auto& item : *orchard_inputs_list) { + if (!item.is_dict()) { + return std::nullopt; + } + auto input_opt = + ZCashTransaction::OrchardInput::FromValue(item.GetDict()); + if (!input_opt) { + return std::nullopt; + } + result.orchard_part().inputs.push_back(std::move(*input_opt)); } - result.transparent_part_.inputs.push_back(std::move(*input_opt)); } auto* outputs_list = value.FindList("outputs"); @@ -385,6 +411,9 @@ uint64_t ZCashTransaction::TotalInputsAmount() const { for (auto& input : transparent_part_.inputs) { result += input.utxo_value; } + for (auto& input : orchard_part_.inputs) { + result += input.note.amount; + } return result; } diff --git a/components/brave_wallet/browser/zcash/zcash_transaction.h b/components/brave_wallet/browser/zcash/zcash_transaction.h index 7749a558073a..eb1668a6b05d 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction.h +++ b/components/brave_wallet/browser/zcash/zcash_transaction.h @@ -93,11 +93,14 @@ class ZCashTransaction { bool operator==(const TransparentPart& other) const; bool operator!=(const TransparentPart& other) const; + bool IsEmpty() const; + std::vector inputs; std::vector outputs; }; using OrchardOutput = ::brave_wallet::OrchardOutput; + using OrchardInput = ::brave_wallet::OrchardInput; struct OrchardPart { OrchardPart(); @@ -109,7 +112,7 @@ class ZCashTransaction { bool operator==(const OrchardPart& other) const; bool operator!=(const OrchardPart& other) const; - // Only outputs are supported + std::vector inputs; std::vector outputs; std::optional> digest; std::optional> raw_tx; diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc deleted file mode 100644 index 6fd78916bf17..000000000000 --- a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -#include "brave/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h" - -#include - -#include "base/task/thread_pool.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_serializer.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" -#include "brave/components/brave_wallet/common/hex_utils.h" -#include "brave/components/brave_wallet/common/zcash_utils.h" -#include "components/grit/brave_components_strings.h" -#include "ui/base/l10n/l10n_util.h" - -namespace brave_wallet { - -namespace { - -#if BUILDFLAG(ENABLE_ORCHARD) -std::unique_ptr ApplyOrchardSignatures( - std::unique_ptr orchard_bundle_manager, - std::array sighash) { - // Heavy CPU operation, should be executed on background thread - return orchard_bundle_manager->ApplySignature(sighash); -} -#endif // BUILDFLAG(ENABLE_ORCHARD) - -} // namespace - -ZCashTransactionCompleteManager::ParamsBundle::ParamsBundle( - std::string chain_id, - ZCashTransaction transaction, - mojom::AccountIdPtr account_id, - CompleteTransactionCallback callback) - : chain_id(std::move(chain_id)), - transaction(std::move(transaction)), - account_id(std::move(account_id)), - callback(std::move(callback)) {} -ZCashTransactionCompleteManager::ParamsBundle::~ParamsBundle() {} -ZCashTransactionCompleteManager::ParamsBundle::ParamsBundle( - ParamsBundle&& other) = default; - -ZCashTransactionCompleteManager::ZCashTransactionCompleteManager( - ZCashWalletService& zcash_wallet_service) - : zcash_wallet_service_(zcash_wallet_service) {} - -ZCashTransactionCompleteManager::~ZCashTransactionCompleteManager() {} - -void ZCashTransactionCompleteManager::CompleteTransaction( - const std::string& chain_id, - const ZCashTransaction& transaction, - const mojom::AccountIdPtr& account_id, - CompleteTransactionCallback callback) { - zcash_wallet_service_->zcash_rpc().GetLatestBlock( - chain_id, - base::BindOnce(&ZCashTransactionCompleteManager::OnGetLatestBlockHeight, - weak_ptr_factory_.GetWeakPtr(), - ParamsBundle{chain_id, transaction, account_id.Clone(), - std::move(callback)})); -} - -void ZCashTransactionCompleteManager::OnGetLatestBlockHeight( - ParamsBundle params, - base::expected result) { - if (!result.has_value()) { - std::move(params.callback).Run(base::unexpected("block height error")); - return; - } - - params.transaction.set_locktime(result.value()->height); - params.transaction.set_expiry_height(result.value()->height + - kDefaultZCashBlockHeightDelta); - -#if BUILDFLAG(ENABLE_ORCHARD) - if (params.transaction.orchard_part().outputs.empty()) { - SignTransparentPart(std::move(params)); - return; - } - std::string chain_id = params.chain_id; - zcash_wallet_service_->zcash_rpc().GetLatestTreeState( - chain_id, - base::BindOnce(&ZCashTransactionCompleteManager::OnGetTreeState, - weak_ptr_factory_.GetWeakPtr(), std::move(params))); -#else - SignTransparentPart(std::move(params)); -#endif // BUILDFLAG(ENABLE_ORCHARD) -} - -#if BUILDFLAG(ENABLE_ORCHARD) - -void ZCashTransactionCompleteManager::OnGetTreeState( - ParamsBundle params, - base::expected result) { - if (!result.has_value()) { - std::move(params.callback) - .Run(base::unexpected( - l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); - return; - } - - // Sign shielded part - auto state_tree_bytes = PrefixedHexStringToBytes( - base::StrCat({"0x", result.value()->orchardTree})); - if (!state_tree_bytes) { - std::move(params.callback) - .Run(base::unexpected( - l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); - return; - } - - CHECK_EQ(params.transaction.orchard_part().outputs.size(), 1u); - auto orchard_bundle_manager = OrchardBundleManager::Create( - *state_tree_bytes, params.transaction.orchard_part().outputs); - - if (!orchard_bundle_manager) { - std::move(params.callback) - .Run(base::unexpected( - l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); - return; - } - - params.transaction.orchard_part().digest = - orchard_bundle_manager->GetOrchardDigest(); - - // Calculate Orchard sighash - auto sighash = ZCashSerializer::CalculateSignatureDigest(params.transaction, - std::nullopt); - - base::ThreadPool::PostTaskAndReplyWithResult( - FROM_HERE, {base::MayBlock()}, - base::BindOnce(&ApplyOrchardSignatures, std::move(orchard_bundle_manager), - sighash), - base::BindOnce( - &ZCashTransactionCompleteManager::OnSignOrchardPartComplete, - weak_ptr_factory_.GetWeakPtr(), std::move(params))); -} - -void ZCashTransactionCompleteManager::OnSignOrchardPartComplete( - ParamsBundle params, - std::unique_ptr orchard_bundle_manager) { - if (!orchard_bundle_manager) { - std::move(params.callback) - .Run(base::unexpected( - l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); - return; - } - params.transaction.orchard_part().raw_tx = - orchard_bundle_manager->GetRawTxBytes(); - - SignTransparentPart(std::move(params)); -} - -#endif // BUILDFLAG(ENABLE_ORCHARD) - -void ZCashTransactionCompleteManager::SignTransparentPart(ParamsBundle params) { - // Sign transparent part - if (!ZCashSerializer::SignTransparentPart( - zcash_wallet_service_->keyring_service(), params.account_id, - params.transaction)) { - std::move(params.callback) - .Run(base::unexpected( - l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); - return; - } - - std::move(params.callback).Run(std::move(params.transaction)); -} - -} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h deleted file mode 100644 index 27c036debc18..000000000000 --- a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_TRANSACTION_COMPLETE_MANAGER_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_TRANSACTION_COMPLETE_MANAGER_H_ - -#include -#include - -#include "brave/components/brave_wallet/browser/internal/orchard_bundle_manager.h" -#include "brave/components/brave_wallet/browser/keyring_service.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_transaction.h" -#include "brave/components/brave_wallet/common/buildflags.h" - -namespace brave_wallet { - -class ZCashWalletService; - -// Completes transaction by signing transparent inputs and generating orchard -// part(if needed) -class ZCashTransactionCompleteManager { - public: - using CompleteTransactionCallback = - base::OnceCallback)>; - explicit ZCashTransactionCompleteManager( - ZCashWalletService& zcash_wallet_service); - ~ZCashTransactionCompleteManager(); - void CompleteTransaction(const std::string& chain_id, - const ZCashTransaction& transaction, - const mojom::AccountIdPtr& account_id, - CompleteTransactionCallback callback); - - private: - struct ParamsBundle { - std::string chain_id; - ZCashTransaction transaction; - mojom::AccountIdPtr account_id; - CompleteTransactionCallback callback; - ParamsBundle(std::string chain_id, - ZCashTransaction transaction, - mojom::AccountIdPtr account_id, - CompleteTransactionCallback callback); - ~ParamsBundle(); - ParamsBundle(ParamsBundle& other) = delete; - ParamsBundle(ParamsBundle&& other); - }; - - void SignTransparentPart(ParamsBundle params); - - void OnGetLatestBlockHeight( - ParamsBundle params, - base::expected result); -#if BUILDFLAG(ENABLE_ORCHARD) - void OnGetTreeState( - ParamsBundle params, - base::expected result); - void OnSignOrchardPartComplete( - ParamsBundle params, - std::unique_ptr orchard_bundle_manager); -#endif // BUILDFLAG(ENABLE_ORCHARD) - - raw_ref zcash_wallet_service_; // Owns `this`. - base::WeakPtrFactory weak_ptr_factory_{this}; -}; - -} // namespace brave_wallet - -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_TRANSACTION_COMPLETE_MANAGER_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_utils.cc b/components/brave_wallet/browser/zcash/zcash_transaction_utils.cc index f2c2c032caf7..558a2533a560 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction_utils.cc +++ b/components/brave_wallet/browser/zcash/zcash_transaction_utils.cc @@ -20,6 +20,14 @@ uint64_t CalculateInputsAmount( return total_value; } +uint64_t CalculateInputsAmount(const std::vector& notes) { + uint64_t total_value = 0; + for (const auto& note : notes) { + total_value += note.amount; + } + return total_value; +} + } // namespace PickInputsResult::PickInputsResult( @@ -79,4 +87,51 @@ std::optional PickZCashTransparentInputs( return std::nullopt; } +PickOrchardInputsResult::PickOrchardInputsResult( + std::vector inputs, + uint64_t fee, + uint64_t change) + : inputs(inputs), fee(fee), change(change) {} +PickOrchardInputsResult::~PickOrchardInputsResult() {} +PickOrchardInputsResult::PickOrchardInputsResult( + const PickOrchardInputsResult& other) = default; +PickOrchardInputsResult::PickOrchardInputsResult( + PickOrchardInputsResult&& other) = default; + +#if BUILDFLAG(ENABLE_ORCHARD) +std::optional PickZCashOrchardInputs( + const std::vector& notes, + uint64_t amount) { + if (amount == kZCashFullAmount) { + auto fee = + CalculateZCashTxFee(0, notes.size() + 1 /* orchard actions count */); + if (CalculateInputsAmount(notes) < fee) { + return std::nullopt; + } + return PickOrchardInputsResult{notes, fee, 0}; + } + + std::vector mutable_notes = notes; + + base::ranges::sort(mutable_notes, [](auto& input1, auto& input2) { + return input1.amount < input2.amount; + }); + + std::vector selected_inputs; + uint64_t fee = 0; + for (auto& input : mutable_notes) { + selected_inputs.push_back(input); + fee = CalculateZCashTxFee(0, selected_inputs.size() + 1); + + auto total_inputs_amount = CalculateInputsAmount(selected_inputs); + if (total_inputs_amount >= amount + fee) { + return PickOrchardInputsResult{std::move(selected_inputs), fee, + total_inputs_amount - amount - fee}; + } + } + + return std::nullopt; +} +#endif // BUILDFLAG(ENABLE_ORCHARD) + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_utils.h b/components/brave_wallet/browser/zcash/zcash_transaction_utils.h index cfc955a4509d..f7ad2f1f24cc 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction_utils.h +++ b/components/brave_wallet/browser/zcash/zcash_transaction_utils.h @@ -39,6 +39,28 @@ std::optional PickZCashTransparentInputs( uint64_t amount, size_t orchard_actions_count); +struct PickOrchardInputsResult { + std::vector inputs; + uint64_t fee; + uint64_t change; + + PickOrchardInputsResult(std::vector inputs, + uint64_t fee, + uint64_t change); + ~PickOrchardInputsResult(); + PickOrchardInputsResult(const PickOrchardInputsResult& other); + PickOrchardInputsResult& operator=(const PickOrchardInputsResult& other) = + delete; + PickOrchardInputsResult(PickOrchardInputsResult&& other); + PickOrchardInputsResult& operator=(PickOrchardInputsResult&& other) = delete; +}; + +#if BUILDFLAG(ENABLE_ORCHARD) +std::optional PickZCashOrchardInputs( + const std::vector& notes, + uint64_t amount); +#endif // BUILDFLAG(ENABLE_ORCHARD) + } // namespace brave_wallet #endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_TRANSACTION_UTILS_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_utils_unittest.cc b/components/brave_wallet/browser/zcash/zcash_transaction_utils_unittest.cc index 1d9afb578ba7..13adcc26d511 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction_utils_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_transaction_utils_unittest.cc @@ -88,4 +88,67 @@ TEST(ZCashTransactionUtilsUnitTest, PickZCashTransparentInputs) { } } +#if BUILDFLAG(ENABLE_ORCHARD) +TEST(ZCashTransactionUtilsUnitTest, PickZCashOrchardInputs) { + // Able to select inputs + { + std::vector notes; + notes.push_back(OrchardNote{{}, 1u, {}, 100000u, 0, {}, {}}); + notes.push_back(OrchardNote{{}, 2u, {}, 200000u, 0, {}, {}}); + notes.push_back(OrchardNote{{}, 3u, {}, 70000u, 0, {}, {}}); + auto result = PickZCashOrchardInputs(notes, 150000u); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(result->change, 170000u - 150000u - result->fee); // 17 - 15 + EXPECT_EQ(result->inputs.size(), 2u); + EXPECT_EQ(result->fee, 15000u); + EXPECT_EQ(result->inputs.size(), 2u); + EXPECT_EQ(result->inputs[0].amount, 70000u); + EXPECT_EQ(result->inputs[0].block_id, 3u); + EXPECT_EQ(result->inputs[1].amount, 100000u); + EXPECT_EQ(result->inputs[1].block_id, 1u); + } + + // Full amount + { + std::vector notes; + notes.push_back(OrchardNote{{}, 1u, {}, 100000u, 0, {}, {}}); + notes.push_back(OrchardNote{{}, 2u, {}, 200000u, 0, {}, {}}); + notes.push_back(OrchardNote{{}, 3u, {}, 70000u, 0, {}, {}}); + auto result = PickZCashOrchardInputs(notes, kZCashFullAmount); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(result->change, 0u); // 17 - 15 + EXPECT_EQ(result->inputs.size(), 3u); + EXPECT_EQ(result->fee, 20000u); + EXPECT_EQ(result->inputs[0].amount, 100000u); + EXPECT_EQ(result->inputs[0].block_id, 1u); + EXPECT_EQ(result->inputs[1].amount, 200000u); + EXPECT_EQ(result->inputs[1].block_id, 2u); + EXPECT_EQ(result->inputs[2].amount, 70000u); + EXPECT_EQ(result->inputs[2].block_id, 3u); + } + + // Unable to pick inputs + { + std::vector notes; + notes.push_back(OrchardNote{{}, 1u, {}, 100000u, 0, {}, {}}); + notes.push_back(OrchardNote{{}, 2u, {}, 200000u, 0, {}, {}}); + auto result = PickZCashOrchardInputs(notes, 300000u); + EXPECT_FALSE(result.has_value()); + } + + // Empty inputs + { + auto result = + PickZCashOrchardInputs(std::vector(), kZCashFullAmount); + EXPECT_FALSE(result.has_value()); + } + + // Empty inputs + { + auto result = PickZCashOrchardInputs(std::vector(), 10000u); + EXPECT_FALSE(result.has_value()); + } +} +#endif // BUILDFLAG(ENABLE_ORCHARD) + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_tx_manager.cc b/components/brave_wallet/browser/zcash/zcash_tx_manager.cc index 6c3932ea4c65..5c24920f1b15 100644 --- a/components/brave_wallet/browser/zcash/zcash_tx_manager.cc +++ b/components/brave_wallet/browser/zcash/zcash_tx_manager.cc @@ -56,10 +56,6 @@ void ZCashTxManager::AddUnapprovedTransaction( AddUnapprovedTransactionCallback callback) { const auto& zec_tx_data = tx_data_union->get_zec_tx_data(); - if (zec_tx_data->use_shielded_pool) { - std::move(callback).Run(false, "", ""); - return; - } #if BUILDFLAG(ENABLE_ORCHARD) if (IsZCashShieldedTransactionsEnabled()) { bool has_orchard_part = @@ -71,11 +67,21 @@ void ZCashTxManager::AddUnapprovedTransaction( std::move(callback).Run(false, "", ""); return; } - zcash_wallet_service_->CreateShieldTransaction( - chain_id, from->Clone(), zec_tx_data->to, zec_tx_data->amount, memo, - base::BindOnce(&ZCashTxManager::ContinueAddUnapprovedTransaction, - weak_factory_.GetWeakPtr(), chain_id, from.Clone(), - origin, std::move(callback))); + if (zec_tx_data->use_shielded_pool) { + // Create shielded transaction + zcash_wallet_service_->CreateShieldedTransaction( + chain_id, from->Clone(), zec_tx_data->to, zec_tx_data->amount, memo, + base::BindOnce(&ZCashTxManager::ContinueAddUnapprovedTransaction, + weak_factory_.GetWeakPtr(), chain_id, from.Clone(), + origin, std::move(callback))); + } else { + // Create transparent to shielded transaction + zcash_wallet_service_->CreateShieldTransaction( + chain_id, from->Clone(), zec_tx_data->to, zec_tx_data->amount, memo, + base::BindOnce(&ZCashTxManager::ContinueAddUnapprovedTransaction, + weak_factory_.GetWeakPtr(), chain_id, from.Clone(), + origin, std::move(callback))); + } return; } } diff --git a/components/brave_wallet/browser/zcash/zcash_tx_meta.cc b/components/brave_wallet/browser/zcash/zcash_tx_meta.cc index 33f43564b921..7d3dbf2a17b1 100644 --- a/components/brave_wallet/browser/zcash/zcash_tx_meta.cc +++ b/components/brave_wallet/browser/zcash/zcash_tx_meta.cc @@ -35,6 +35,7 @@ mojom::ZecTxDataPtr ToZecTxData(const std::string& chain_id, mojom::ZecTxOutput::New(*orchard_unified_addr, output.value)); } } + // TODO(cypt4): Add proper flag here // https://github.com/brave/brave-browser/issues/39314 return mojom::ZecTxData::New(false, tx.to(), OrchardMemoToVec(tx.memo()), diff --git a/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.cc b/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.cc new file mode 100644 index 000000000000..2606ddeddde7 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.cc @@ -0,0 +1,93 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h" + +#include +#include +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" + +namespace brave_wallet { + +namespace { +constexpr size_t kSubTreeRootsResolveBatchSize = 1024; +} // namespace + +ZCashUpdateSubtreeRootsTask::ZCashUpdateSubtreeRootsTask( + ZCashActionContext& context, + ZCashUpdateSubtreeRootsTaskCallback callback) + : context_(context), callback_(std::move(callback)) {} + +ZCashUpdateSubtreeRootsTask::~ZCashUpdateSubtreeRootsTask() = default; + +void ZCashUpdateSubtreeRootsTask::Start() { + context_->sync_state->AsyncCall(&OrchardSyncState::GetLatestShardIndex) + .WithArgs(context_->account_id.Clone()) + .Then(base::BindOnce(&ZCashUpdateSubtreeRootsTask::OnGetLatestShardIndex, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashUpdateSubtreeRootsTask::OnGetLatestShardIndex( + base::expected, OrchardStorage::Error> result) { + if (!result.has_value()) { + std::move(callback_).Run(false); + return; + } + + auto latest_shard_index = result.value(); + auto start = latest_shard_index ? latest_shard_index.value() + 1 : 0; + context_->zcash_rpc->GetSubtreeRoots( + context_->chain_id, start, kSubTreeRootsResolveBatchSize, + base::BindOnce(&ZCashUpdateSubtreeRootsTask::OnGetSubtreeRoots, + weak_ptr_factory_.GetWeakPtr(), start)); +} + +void ZCashUpdateSubtreeRootsTask::GetSubtreeRoots(uint32_t start_index) { + context_->zcash_rpc->GetSubtreeRoots( + context_->chain_id, start_index, kSubTreeRootsResolveBatchSize, + base::BindOnce(&ZCashUpdateSubtreeRootsTask::OnGetSubtreeRoots, + weak_ptr_factory_.GetWeakPtr(), start_index)); +} + +void ZCashUpdateSubtreeRootsTask::OnGetSubtreeRoots( + uint32_t start_index, + base::expected, std::string> + result) { + if (!result.has_value()) { + std::move(callback_).Run(false); + return; + } + + std::optional next_start_index; + if (result->size() == kSubTreeRootsResolveBatchSize) { + next_start_index = start_index + kSubTreeRootsResolveBatchSize; + } + + context_->sync_state->AsyncCall(&OrchardSyncState::UpdateSubtreeRoots) + .WithArgs(context_->account_id.Clone(), start_index, + std::move(result.value())) + .Then(base::BindOnce(&ZCashUpdateSubtreeRootsTask::OnSubtreeRootsUpdated, + weak_ptr_factory_.GetWeakPtr(), next_start_index)); +} + +void ZCashUpdateSubtreeRootsTask::OnSubtreeRootsUpdated( + std::optional next_start_index, + base::expected result) { + if (!result.has_value()) { + std::move(callback_).Run(false); + return; + } + + if (next_start_index) { + GetSubtreeRoots(*next_start_index); + } else { + std::move(callback_).Run(true); + } +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h b/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h new file mode 100644 index 000000000000..5aca3d4f6a70 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_update_subtree_roots_task.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_UPDATE_SUBTREE_ROOTS_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_UPDATE_SUBTREE_ROOTS_TASK_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h" + +namespace brave_wallet { + +class ZCashUpdateSubtreeRootsTask { + public: + using ZCashUpdateSubtreeRootsTaskCallback = base::OnceCallback; + ZCashUpdateSubtreeRootsTask(ZCashActionContext& context, + ZCashUpdateSubtreeRootsTaskCallback callback); + ~ZCashUpdateSubtreeRootsTask(); + + void Start(); + + private: + void OnGetLatestShardIndex( + base::expected, OrchardStorage::Error> result); + void GetSubtreeRoots(uint32_t start_index); + void OnGetSubtreeRoots( + uint32_t start_index, + base::expected, std::string> + result); + void OnSubtreeRootsUpdated( + std::optional next_start_index, + base::expected result); + + raw_ref context_; + ZCashUpdateSubtreeRootsTaskCallback callback_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_UPDATE_SUBTREE_ROOTS_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.cc b/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.cc new file mode 100644 index 000000000000..9c7730be7711 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.cc @@ -0,0 +1,214 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h" + +#include +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" + +namespace brave_wallet { + +ZCashVerifyChainStateTask::ZCashVerifyChainStateTask( + ZCashActionContext& context, + ZCashVerifyChainStateTaskCallback callback) + : context_(context), callback_(std::move(callback)) {} +ZCashVerifyChainStateTask::~ZCashVerifyChainStateTask() = default; + +void ZCashVerifyChainStateTask::Start() { + ScheduleWorkOnTask(); +} + +void ZCashVerifyChainStateTask::WorkOnTask() { + if (error_) { + std::move(callback_).Run(base::unexpected(*error_)); + return; + } + + if (!account_meta_) { + GetAccountMeta(); + return; + } + + if (!chain_tip_block_) { + GetChainTipBlock(); + return; + } + + if (!chain_state_verified_) { + VerifyChainState(); + return; + } + + std::move(callback_).Run(chain_state_verified_); +} + +void ZCashVerifyChainStateTask::ScheduleWorkOnTask() { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ZCashVerifyChainStateTask::WorkOnTask, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashVerifyChainStateTask::GetAccountMeta() { + context_->sync_state->AsyncCall(&OrchardSyncState::GetAccountMeta) + .WithArgs(context_->account_id.Clone()) + .Then(base::BindOnce(&ZCashVerifyChainStateTask::OnGetAccountMeta, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashVerifyChainStateTask::OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount, + result.error().message}; + ScheduleWorkOnTask(); + return; + } + + if (!result.value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount, + "Account doesn't exist"}; + ScheduleWorkOnTask(); + return; + } + + if (result.value()->account_birthday < kNu5BlockUpdate) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount, + "Wrong birthday block height"}; + ScheduleWorkOnTask(); + return; + } + + account_meta_ = **result; + ScheduleWorkOnTask(); +} + +void ZCashVerifyChainStateTask::GetChainTipBlock() { + context_->zcash_rpc->GetLatestBlock( + context_->chain_id, + base::BindOnce(&ZCashVerifyChainStateTask::OnGetChainTipBlock, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashVerifyChainStateTask::OnGetChainTipBlock( + base::expected result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToUpdateChainTip, + result.error()}; + ScheduleWorkOnTask(); + return; + } + + chain_tip_block_ = (*result)->height; + ScheduleWorkOnTask(); +} + +void ZCashVerifyChainStateTask::VerifyChainState() { + // Skip chain state verification if no blocks were scanned yet + if (!account_meta_->latest_scanned_block_id) { + chain_state_verified_ = true; + ScheduleWorkOnTask(); + return; + } + + // If block chain has removed blocks we already scanned then we need to handle + // chain reorg. + if (*chain_tip_block_ < account_meta_->latest_scanned_block_id.value()) { + // Assume that chain reorg can't affect more than kChainReorgBlockDelta + // blocks So we can just fallback on this number from the chain tip block. + GetTreeStateForChainReorg(*chain_tip_block_ - kChainReorgBlockDelta); + return; + } + // Retrieve block info for last scanned block id to check whether block hash + // is the same + auto block_id = zcash::mojom::BlockID::New( + account_meta_->latest_scanned_block_id.value(), std::vector()); + context_->zcash_rpc->GetTreeState( + context_->chain_id, std::move(block_id), + base::BindOnce( + &ZCashVerifyChainStateTask::OnGetTreeStateForChainVerification, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashVerifyChainStateTask::OnGetTreeStateForChainVerification( + base::expected tree_state) { + if (!tree_state.has_value() || !tree_state.value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToReceiveTreeState, + base::StrCat({"Verification tree state failed, ", tree_state.error()})}; + ScheduleWorkOnTask(); + return; + } + auto backend_block_hash = RevertHex(tree_state.value()->hash); + if (backend_block_hash != account_meta_->latest_scanned_block_hash.value()) { + // Assume that chain reorg can't affect more than kChainReorgBlockDelta + // blocks So we can just fallback on this number. + uint32_t new_block_id = + account_meta_->latest_scanned_block_id.value() > kChainReorgBlockDelta + ? account_meta_->latest_scanned_block_id.value() - + kChainReorgBlockDelta + : 0; + GetTreeStateForChainReorg(new_block_id); + return; + } + + chain_state_verified_ = true; + ScheduleWorkOnTask(); +} + +void ZCashVerifyChainStateTask::GetTreeStateForChainReorg( + uint32_t new_block_height) { + // Query block info by block height + auto block_id = + zcash::mojom::BlockID::New(new_block_height, std::vector()); + context_->zcash_rpc->GetTreeState( + context_->chain_id, std::move(block_id), + base::BindOnce(&ZCashVerifyChainStateTask::OnGetTreeStateForChainReorg, + weak_ptr_factory_.GetWeakPtr(), new_block_height)); +} + +void ZCashVerifyChainStateTask::OnGetTreeStateForChainReorg( + uint32_t new_block_height, + base::expected tree_state) { + if (!tree_state.has_value() || !tree_state.value() || + new_block_height != (*tree_state)->height) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToReceiveTreeState, + base::StrCat({"Reorg tree state failed, ", tree_state.error()})}; + ScheduleWorkOnTask(); + return; + } else { + // Reorg database so records related to removed blocks are wiped out + context_->sync_state->AsyncCall(&OrchardSyncState::HandleChainReorg) + .WithArgs(context_->account_id.Clone(), (*tree_state)->height, + (*tree_state)->hash) + .Then(base::BindOnce( + &ZCashVerifyChainStateTask::OnDatabaseUpdatedForChainReorg, + weak_ptr_factory_.GetWeakPtr())); + } +} + +void ZCashVerifyChainStateTask::OnDatabaseUpdatedForChainReorg( + base::expected result) { + if (!result.has_value()) { + error_ = ZCashShieldSyncService::Error{ + ZCashShieldSyncService::ErrorCode::kFailedToUpdateDatabase, + result.error().message}; + ScheduleWorkOnTask(); + return; + } + + chain_state_verified_ = true; + ScheduleWorkOnTask(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h b/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h new file mode 100644 index 000000000000..985d1251e402 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_verify_chain_state_task.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_VERIFY_CHAIN_STATE_TASK_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_VERIFY_CHAIN_STATE_TASK_H_ + +#include + +#include "brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h" + +namespace brave_wallet { + +class ZCashVerifyChainStateTask { + public: + using ZCashVerifyChainStateTaskCallback = base::OnceCallback)>; + ZCashVerifyChainStateTask(ZCashActionContext& context, + ZCashVerifyChainStateTaskCallback callback); + ~ZCashVerifyChainStateTask(); + + void Start(); + + private: + void WorkOnTask(); + void ScheduleWorkOnTask(); + + void GetAccountMeta(); + void OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result); + + void GetChainTipBlock(); + void OnGetChainTipBlock( + base::expected result); + + void VerifyChainState(); + + // Verifies that last known scanned block hash is unchanged + void GetTreeStateForLatestScannedBlock(); + void OnGetTreeStateForChainVerification( + base::expected tree_state); + + // Resolves block hash for the block we are going to fallback + void GetTreeStateForChainReorg(uint32_t new_block_id); + void OnGetTreeStateForChainReorg( + uint32_t new_block_height, + base::expected tree_state); + void OnDatabaseUpdatedForChainReorg( + base::expected result); + + raw_ref context_; + ZCashVerifyChainStateTaskCallback callback_; + + std::optional error_; + std::optional account_meta_; + // Latest block in the blockchain + std::optional chain_tip_block_; + bool chain_state_verified_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_VERIFY_CHAIN_STATE_TASK_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.cc b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc index 36a837db43be..c05fd02c41a3 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc @@ -27,6 +27,8 @@ #if BUILDFLAG(ENABLE_ORCHARD) #include "brave/components/brave_wallet/browser/zcash/zcash_create_shield_transaction_task.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_create_shielded_transaction_task.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_get_zcash_chain_tip_status_task.h" #endif // BUILDFLAG(ENABLE_ORCHARD) namespace brave_wallet { @@ -59,8 +61,7 @@ ZCashWalletService::ZCashWalletService( : zcash_data_path_(std::move(zcash_data_path)), keyring_service_(keyring_service), zcash_rpc_( - std::make_unique(network_manager, url_loader_factory)), - complete_manager_(*this) { + std::make_unique(network_manager, url_loader_factory)) { keyring_service_->AddObserver( keyring_observer_receiver_.BindNewPipeAndPassRemote()); #if BUILDFLAG(ENABLE_ORCHARD) @@ -75,8 +76,7 @@ ZCashWalletService::ZCashWalletService(base::FilePath zcash_data_path, std::unique_ptr zcash_rpc) : zcash_data_path_(std::move(zcash_data_path)), keyring_service_(keyring_service), - zcash_rpc_(std::move(zcash_rpc)), - complete_manager_(*this) { + zcash_rpc_(std::move(zcash_rpc)) { CHECK_IS_TEST(); keyring_service_->AddObserver( keyring_observer_receiver_.BindNewPipeAndPassRemote()); @@ -93,10 +93,11 @@ void ZCashWalletService::GetBalance(const std::string& chain_id, mojom::AccountIdPtr account_id, GetBalanceCallback callback) { auto balance_task = std::make_unique( - *this, chain_id, std::move(account_id), + base::PassKey(), *this, chain_id, + std::move(account_id), base::BindOnce(&ZCashWalletService::OnResolveBalanceResult, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); - balance_task->ScheduleWorkOnTask(); + balance_task->Start(); resolve_balance_tasks_.push_back(std::move(balance_task)); } @@ -110,6 +111,16 @@ void ZCashWalletService::OnResolveBalanceResult( } } +void ZCashWalletService::OnGetChainTipStatusResult( + GetChainTipStatusCallback callback, + base::expected result) { + if (result.has_value()) { + std::move(callback).Run(std::move(result.value()), std::nullopt); + } else { + std::move(callback).Run(nullptr, result.error()); + } +} + void ZCashWalletService::GetReceiverAddress( mojom::AccountIdPtr account_id, GetReceiverAddressCallback callback) { @@ -133,6 +144,7 @@ void ZCashWalletService::GetZCashAccountInfo( void ZCashWalletService::MakeAccountShielded( mojom::AccountIdPtr account_id, + uint32_t account_birthday_block, MakeAccountShieldedCallback callback) { #if BUILDFLAG(ENABLE_ORCHARD) if (IsZCashShieldedTransactionsEnabled()) { @@ -145,7 +157,12 @@ void ZCashWalletService::MakeAccountShielded( return; } } - GetLatestBlockForAccountBirthday(account_id.Clone(), std::move(callback)); + if (account_birthday_block == 0) { + GetLatestBlockForAccountBirthday(account_id.Clone(), std::move(callback)); + } else { + GetTreeStateForAccountBirthday( + std::move(account_id), account_birthday_block, std::move(callback)); + } return; } #endif @@ -153,6 +170,7 @@ void ZCashWalletService::MakeAccountShielded( } void ZCashWalletService::StartShieldSync(mojom::AccountIdPtr account_id, + uint32_t to, StartShieldSyncCallback callback) { #if BUILDFLAG(ENABLE_ORCHARD) if (IsZCashShieldedTransactionsEnabled()) { @@ -175,10 +193,12 @@ void ZCashWalletService::StartShieldSync(mojom::AccountIdPtr account_id, shield_sync_services_[account_id.Clone()] = std::make_unique( - *zcash_rpc_, sync_state_, account_id, account_birthday, fvk.value(), - weak_ptr_factory_.GetWeakPtr()); + CreateActionContext( + account_id, GetNetworkForZCashKeyring(account_id->keyring_id)), + account_birthday, fvk.value(), weak_ptr_factory_.GetWeakPtr()); - shield_sync_services_[account_id.Clone()]->StartSyncing(); + shield_sync_services_[account_id.Clone()]->StartSyncing( + to == 0 ? std::nullopt : std::optional(to)); std::move(callback).Run(std::nullopt); return; @@ -278,11 +298,10 @@ void ZCashWalletService::DiscoverNextUnusedAddress( auto start_address = change ? account_info->next_transparent_change_address.Clone() : account_info->next_transparent_receive_address.Clone(); - auto task = base::WrapRefCounted( - new ZCashDiscoverNextUnusedZCashAddressTask( - weak_ptr_factory_.GetWeakPtr(), account_id.Clone(), - std::move(start_address), std::move(callback))); - task->ScheduleWorkOnTask(); + auto task = base::MakeRefCounted( + base::PassKey(), weak_ptr_factory_.GetWeakPtr(), + account_id, start_address, std::move(callback)); + task->Start(); } void ZCashWalletService::GetUtxos(const std::string& chain_id, @@ -343,16 +362,34 @@ void ZCashWalletService::CompleteTransactionDone( std::move(result.value()))); } +void ZCashWalletService::OnResetSyncState( + ResetSyncStateCallback callback, + base::expected result) { + if (result.has_value()) { + std::move(callback).Run( + result.value() == OrchardStorage::Result::kSuccess + ? std::nullopt + : std::optional("Account data wasn't deleted")); + return; + } + + std::move(callback).Run(result.error().message); +} + void ZCashWalletService::SignAndPostTransaction( const std::string& chain_id, const mojom::AccountIdPtr& account_id, const ZCashTransaction& zcash_transaction, SignAndPostTransactionCallback callback) { - complete_manager_.CompleteTransaction( - chain_id, zcash_transaction, account_id.Clone(), - base::BindOnce(&ZCashWalletService::CompleteTransactionDone, - weak_ptr_factory_.GetWeakPtr(), chain_id, - zcash_transaction, std::move(callback))); + auto& task = complete_transaction_tasks_.emplace_back( + std::make_unique( + base::PassKey(), *this, + CreateActionContext(account_id, chain_id), keyring_service_.get(), + zcash_transaction, + base::BindOnce(&ZCashWalletService::CompleteTransactionDone, + weak_ptr_factory_.GetWeakPtr(), chain_id, + zcash_transaction, std::move(callback)))); + task->Start(); } void ZCashWalletService::SetZCashRpcForTesting( @@ -421,41 +458,6 @@ void ZCashWalletService::OnSendTransactionResult( } } -void ZCashWalletService::OnGetUtxos( - scoped_refptr context, - const std::string& address, - base::expected - result) { - DCHECK(context->addresses.contains(address)); - DCHECK(!context->utxos.contains(address)); - - if (!result.has_value() || !result.value()) { - context->SetError(result.error()); - WorkOnGetUtxos(std::move(context)); - return; - } - - context->addresses.erase(address); - context->utxos[address] = std::move(result.value()->address_utxos); - - WorkOnGetUtxos(std::move(context)); -} - -void ZCashWalletService::WorkOnGetUtxos( - scoped_refptr context) { - if (!context->ShouldRespond()) { - return; - } - - if (context->error) { - std::move(context->callback) - .Run(base::unexpected(std::move(*context->error))); - return; - } - - std::move(context->callback).Run(std::move(context->utxos)); -} - void ZCashWalletService::OnDiscoveryDoneForBalance( mojom::AccountIdPtr account_id, std::string chain_id, @@ -492,6 +494,41 @@ void ZCashWalletService::OnUtxosResolvedForBalance( std::move(initial_callback).Run(std::move(result), std::nullopt); } +void ZCashWalletService::OnGetUtxos( + scoped_refptr context, + const std::string& address, + base::expected + result) { + DCHECK(context->addresses.contains(address)); + DCHECK(!context->utxos.contains(address)); + + if (!result.has_value() || !result.value()) { + context->SetError(result.error()); + WorkOnGetUtxos(std::move(context)); + return; + } + + context->addresses.erase(address); + context->utxos[address] = std::move(result.value()->address_utxos); + + WorkOnGetUtxos(std::move(context)); +} + +void ZCashWalletService::WorkOnGetUtxos( + scoped_refptr context) { + if (!context->ShouldRespond()) { + return; + } + + if (context->error) { + std::move(context->callback) + .Run(base::unexpected(std::move(*context->error))); + return; + } + + std::move(context->callback).Run(std::move(context->utxos)); +} + void ZCashWalletService::CreateFullyTransparentTransaction( const std::string& chain_id, mojom::AccountIdPtr account_id, @@ -511,14 +548,39 @@ void ZCashWalletService::CreateFullyTransparentTransaction( } auto& task = create_transaction_tasks_.emplace_back( - base::WrapUnique( - new ZCashCreateTransparentTransactionTask(*this, chain_id, account_id, - final_address, amount, - std::move(callback)))); - task->ScheduleWorkOnTask(); + std::make_unique( + base::PassKey(), *this, + CreateActionContext(account_id, chain_id), final_address, amount, + std::move(callback))); + task->Start(); } #if BUILDFLAG(ENABLE_ORCHARD) +void ZCashWalletService::CreateShieldedTransaction( + const std::string& chain_id, + mojom::AccountIdPtr account_id, + const std::string& address_to, + uint64_t amount, + std::optional memo, + CreateTransactionCallback callback) { + auto receiver_addr = + GetOrchardRawBytes(address_to, chain_id == mojom::kZCashTestnet); + if (!receiver_addr) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + auto transfer_shield_funds_task = + std::make_unique( + base::PassKey(), *this, + CreateActionContext(account_id, chain_id), *receiver_addr, + std::move(memo), amount, std::move(callback)); + transfer_shield_funds_task->Start(); + create_shielded_transaction_tasks_.push_back( + std::move(transfer_shield_funds_task)); +} + void ZCashWalletService::CreateShieldTransaction( const std::string& chain_id, mojom::AccountIdPtr account_id, @@ -536,11 +598,11 @@ void ZCashWalletService::CreateShieldTransaction( return; } - auto shield_funds_task = base::WrapUnique( - new ZCashCreateShieldTransactionTask(*this, chain_id, account_id, - *receiver_addr, std::move(memo), - amount, std::move(callback))); - shield_funds_task->ScheduleWorkOnTask(); + auto shield_funds_task = std::make_unique( + base::PassKey(), *this, + CreateActionContext(account_id, chain_id), *receiver_addr, + std::move(memo), amount, std::move(callback)); + shield_funds_task->Start(); create_shield_transaction_tasks_.push_back(std::move(shield_funds_task)); } #endif @@ -567,6 +629,29 @@ void ZCashWalletService::ShieldAllFunds(const std::string& chain_id, #endif } +void ZCashWalletService::ResetSyncState(mojom::AccountIdPtr account_id, + ResetSyncStateCallback callback) { +#if BUILDFLAG(ENABLE_ORCHARD) + if (IsZCashShieldedTransactionsEnabled()) { + if (shield_sync_services_.find(account_id) != shield_sync_services_.end()) { + std::move(callback).Run("Sync in progress"); + return; + } + sync_state_.AsyncCall(&OrchardSyncState::ResetAccountSyncState) + .WithArgs(account_id.Clone()) + .Then(base::BindOnce(&ZCashWalletService::OnResetSyncState, + weak_ptr_factory_.GetWeakPtr(), + std::move(callback))); + } else { + std::move(callback).Run( + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); + } +#else + std::move(callback).Run( + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); +#endif +} + #if BUILDFLAG(ENABLE_ORCHARD) void ZCashWalletService::CreateShieldAllTransaction( @@ -578,11 +663,11 @@ void ZCashWalletService::CreateShieldAllTransaction( auto internal_addr = keyring_service_->GetOrchardRawBytes( account_id, CreateOrchardInternalKeyId(account_id)); - auto shield_funds_task = base::WrapUnique( - new ZCashCreateShieldTransactionTask( - *this, chain_id, account_id, *internal_addr, std::nullopt, - kZCashFullAmount, std::move(callback))); - shield_funds_task->ScheduleWorkOnTask(); + auto shield_funds_task = std::make_unique( + base::PassKey(), *this, + CreateActionContext(account_id, chain_id), *internal_addr, std::nullopt, + kZCashFullAmount, std::move(callback)); + shield_funds_task->Start(); create_shield_transaction_tasks_.push_back(std::move(shield_funds_task)); } @@ -635,19 +720,28 @@ void ZCashWalletService::OnGetLatestBlockForAccountBirthday( return; } + GetTreeStateForAccountBirthday(std::move(account_id), (*result)->height, + std::move(callback)); +} + +void ZCashWalletService::GetTreeStateForAccountBirthday( + mojom::AccountIdPtr account_id, + uint32_t block_id, + MakeAccountShieldedCallback callback) { // Get block info for the block that is back from latest block for // kChainReorgBlockDelta to ensure account birthday won't be affected by chain // reorg. - if ((*result)->height < kChainReorgBlockDelta) { + if (block_id < kChainReorgBlockDelta) { std::move(callback).Run("Failed to retrieve latest block"); return; } - auto block_id = zcash::mojom::BlockID::New( - (*result)->height - kChainReorgBlockDelta, std::vector()); + auto block_id_param = zcash::mojom::BlockID::New( + block_id - kChainReorgBlockDelta, std::vector()); zcash_rpc_->GetTreeState( - GetNetworkForZCashKeyring(account_id->keyring_id), std::move(block_id), + GetNetworkForZCashKeyring(account_id->keyring_id), + std::move(block_id_param), base::BindOnce(&ZCashWalletService::OnGetTreeStateForAccountBirthday, weak_ptr_factory_.GetWeakPtr(), account_id.Clone(), std::move(callback))); @@ -727,6 +821,12 @@ void ZCashWalletService::OnTransactionResolvedForStatus( std::move(callback).Run((*result)->height > 0); } +void ZCashWalletService::CompleteTransactionTaskDone( + ZCashCompleteTransactionTask* task) { + CHECK(complete_transaction_tasks_.remove_if( + [task](auto& item) { return item.get() == task; })); +} + void ZCashWalletService::CreateTransactionTaskDone( ZCashCreateTransparentTransactionTask* task) { CHECK(create_transaction_tasks_.remove_if( @@ -746,6 +846,12 @@ void ZCashWalletService::ResolveBalanceTaskDone(ZCashResolveBalanceTask* task) { [task](auto& item) { return item.get() == task; })); } +void ZCashWalletService::GetZCashChainTipStatusTaskDone( + ZCashGetZCashChainTipStatusTask* task) { + CHECK(get_zcash_chain_tip_status_tasks_.remove_if( + [task](auto& item) { return item.get() == task; })); +} + ZCashRpc& ZCashWalletService::zcash_rpc() { return *zcash_rpc_; } @@ -783,4 +889,30 @@ void ZCashWalletService::Reset() { #endif // BUILDFLAG(ENABLE_ORCHARD) } +ZCashActionContext ZCashWalletService::CreateActionContext( + const mojom::AccountIdPtr& account_id, + const std::string chain_id) { + return ZCashActionContext(*zcash_rpc_, +#if BUILDFLAG(ENABLE_ORCHARD) + sync_state_, +#endif + account_id.Clone(), chain_id); +} + +void ZCashWalletService::GetChainTipStatus(mojom::AccountIdPtr account_id, + const std::string& chain_id, + GetChainTipStatusCallback callback) { +#if BUILDFLAG(ENABLE_ORCHARD) + auto task = std::make_unique( + base::PassKey(), + *this, CreateActionContext(account_id, chain_id), + base::BindOnce(&ZCashWalletService::OnGetChainTipStatusResult, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); + task->Start(); + get_zcash_chain_tip_status_tasks_.push_back(std::move(task)); +#else + std::move(callback).Run(nullptr, "Not supported"); +#endif +} + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.h b/components/brave_wallet/browser/zcash/zcash_wallet_service.h index 46c40b45867c..99da921b0979 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service.h +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.h @@ -17,13 +17,14 @@ #include "base/types/expected.h" #include "brave/components/brave_wallet/browser/keyring_service.h" #include "brave/components/brave_wallet/browser/keyring_service_observer_base.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_complete_transaction_task.h" #include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" #include "brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h" #include "brave/components/brave_wallet/browser/zcash/zcash_transaction.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_wallet/common/buildflags.h" #include "brave/components/brave_wallet/common/zcash_utils.h" +#include "brave/components/services/brave_wallet/public/mojom/zcash_decoder.mojom.h" #if BUILDFLAG(ENABLE_ORCHARD) #include "brave/components/brave_wallet/browser/internal/orchard_sync_state.h" @@ -31,10 +32,12 @@ namespace brave_wallet { +class OrchardSyncState; class ZCashCreateShieldTransactionTask; +class ZCashCreateShieldedTransactionTask; class ZCashCreateTransparentTransactionTask; class ZCashGetTransparentUtxosContext; -class OrchardSyncState; +class ZCashGetZCashChainTipStatusTask; class ZCashResolveBalanceTask; class ZCashWalletService : public mojom::ZCashWalletService, @@ -83,13 +86,20 @@ class ZCashWalletService : public mojom::ZCashWalletService, GetZCashAccountInfoCallback callback) override; void MakeAccountShielded(mojom::AccountIdPtr account_id, + uint32_t account_birthday_block, MakeAccountShieldedCallback callback) override; + // Starts Orchard pool syncing for the provided account. void StartShieldSync(mojom::AccountIdPtr account_id, + uint32_t to, StartShieldSyncCallback callback) override; void StopShieldSync(mojom::AccountIdPtr account_id, StopShieldSyncCallback callback) override; + void GetChainTipStatus(mojom::AccountIdPtr account_id, + const std::string& chain_id, + GetChainTipStatusCallback callback) override; + /** * Used for internal transfers between own accounts */ @@ -103,6 +113,9 @@ class ZCashWalletService : public mojom::ZCashWalletService, mojom::AccountIdPtr account_id, ShieldAllFundsCallback callback) override; + void ResetSyncState(mojom::AccountIdPtr account_id, + ResetSyncStateCallback callback) override; + void RunDiscovery(mojom::AccountIdPtr account_id, RunDiscoveryCallback callback); @@ -128,6 +141,14 @@ class ZCashWalletService : public mojom::ZCashWalletService, CreateTransactionCallback callback); #if BUILDFLAG(ENABLE_ORCHARD) + // Creates Orchard to Orchard transaction. + void CreateShieldedTransaction(const std::string& chain_id, + mojom::AccountIdPtr account_id, + const std::string& address_to, + uint64_t amount, + std::optional memo, + CreateTransactionCallback callback); + // Creates TransparentToOrchardTransaction void CreateShieldTransaction(const std::string& chain_id, mojom::AccountIdPtr account_id, const std::string& address_to, @@ -154,13 +175,21 @@ class ZCashWalletService : public mojom::ZCashWalletService, void Reset(); private: + friend class ZCashBlocksBatchScanTask; + friend class ZCashCompleteTransactionTask; friend class ZCashCreateShieldTransactionTask; + friend class ZCashCreateShieldedTransactionTask; friend class ZCashCreateTransparentTransactionTask; friend class ZCashDiscoverNextUnusedZCashAddressTask; + friend class ZCashGetZCashChainTipStatusTask; friend class ZCashResolveBalanceTask; + friend class ZCashScanBlocksTask; friend class ZCashShieldSyncService; friend class ZCashTransactionCompleteManager; friend class ZCashTxManager; + friend class ZCashTxManager; + friend class ZCashUpdateSubtreeRootsTask; + friend class ZCashVerifyChainStateTask; friend class ZCashWalletServiceUnitTest; friend class ZCashShieldSyncServiceTest; @@ -183,15 +212,16 @@ class ZCashWalletService : public mojom::ZCashWalletService, std::string> result); void WorkOnGetUtxos(scoped_refptr context); + void OnTransactionResolvedForStatus( + GetTransactionStatusCallback callback, + base::expected result); + void OnDiscoveryDoneForBalance(mojom::AccountIdPtr account_id, std::string chain_id, GetBalanceCallback callback, RunDiscoveryResult discovery_result); void OnUtxosResolvedForBalance(GetBalanceCallback initial_callback, base::expected result); - void OnTransactionResolvedForStatus( - GetTransactionStatusCallback callback, - base::expected result); void OnSendTransactionResult( SignAndPostTransactionCallback callback, @@ -202,20 +232,28 @@ class ZCashWalletService : public mojom::ZCashWalletService, GetBalanceCallback callback, base::expected result); + void OnGetChainTipStatusResult( + GetChainTipStatusCallback callback, + base::expected result); + void CreateTransactionTaskDone(ZCashCreateTransparentTransactionTask* task); void CreateTransactionTaskDone(ZCashCreateShieldTransactionTask* task); + void CompleteTransactionTaskDone(ZCashCompleteTransactionTask* task); void ResolveBalanceTaskDone(ZCashResolveBalanceTask* task); + void GetZCashChainTipStatusTaskDone(ZCashGetZCashChainTipStatusTask* task); void CompleteTransactionDone(std::string chain_id, ZCashTransaction original_zcash_transaction, SignAndPostTransactionCallback callback, base::expected); + void OnResetSyncState( + ResetSyncStateCallback callback, + base::expected result); #if BUILDFLAG(ENABLE_ORCHARD) void CreateShieldAllTransaction(const std::string& chain_id, mojom::AccountIdPtr account_id, CreateTransactionCallback callback); - void CreateShieldAllTransactionTaskDone( const std::string& chain_id, mojom::AccountIdPtr account_id, @@ -234,6 +272,9 @@ class ZCashWalletService : public mojom::ZCashWalletService, mojom::AccountIdPtr account_id, MakeAccountShieldedCallback callback, base::expected result); + void GetTreeStateForAccountBirthday(mojom::AccountIdPtr account_id, + uint32_t block_id, + MakeAccountShieldedCallback callback); void OnGetTreeStateForAccountBirthday( mojom::AccountIdPtr account_id, MakeAccountShieldedCallback callback, @@ -257,6 +298,9 @@ class ZCashWalletService : public mojom::ZCashWalletService, base::SequenceBound& sync_state(); #endif + ZCashActionContext CreateActionContext(const mojom::AccountIdPtr& account_id, + const std::string chain_id); + void UpdateNextUnusedAddressForAccount(const mojom::AccountIdPtr& account_id, const mojom::ZCashAddressPtr& address); @@ -266,8 +310,9 @@ class ZCashWalletService : public mojom::ZCashWalletService, base::FilePath zcash_data_path_; raw_ref keyring_service_; std::unique_ptr zcash_rpc_; - ZCashTransactionCompleteManager complete_manager_; + std::list> + complete_transaction_tasks_; std::list> create_transaction_tasks_; std::list> resolve_balance_tasks_; @@ -276,12 +321,15 @@ class ZCashWalletService : public mojom::ZCashWalletService, base::SequenceBound sync_state_; std::list> create_shield_transaction_tasks_; + std::list> + create_shielded_transaction_tasks_; std::map> shield_sync_services_; + std::list> + get_zcash_chain_tip_status_tasks_; #endif mojo::RemoteSet observers_; - mojo::ReceiverSet receivers_; mojo::Receiver keyring_observer_receiver_{this}; diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc b/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc index ef7bc9c58e79..850bd801e032 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc @@ -143,7 +143,7 @@ class ZCashWalletServiceUnitTest : public testing::Test { #if BUILDFLAG(ENABLE_ORCHARD) base::SequenceBound& sync_state() { - return zcash_wallet_service_->sync_state(); + return zcash_wallet_service_->sync_state_; } #endif // BUILDFLAG(ENABLE_ORCHARD) @@ -259,6 +259,7 @@ TEST_F(ZCashWalletServiceUnitTest, GetBalance) { EXPECT_EQ(balance->transparent_balance, 50u); EXPECT_EQ(balance->shielded_balance, 0u); })); + zcash_wallet_service_->GetBalance(mojom::kZCashMainnet, account_id.Clone(), balance_callback.Get()); task_environment_.RunUntilIdle(); @@ -329,9 +330,10 @@ TEST_F(ZCashWalletServiceUnitTest, GetBalanceWithShielded) { auto update_notes_callback = base::BindLambdaForTesting( [](base::expected) {}); - OrchardBlockScanner::Result result = CreateResultForTesting( - OrchardTreeState(), std::vector()); + auto result = CreateResultForTesting(OrchardTreeState(), + std::vector()); result.discovered_notes = std::vector({note}); + result.found_spends = std::vector(); sync_state() .AsyncCall(&OrchardSyncState::ApplyScanResults) @@ -420,6 +422,7 @@ TEST_F(ZCashWalletServiceUnitTest, GetBalanceWithShielded_FeatureDisabled) { OrchardBlockScanner::Result result = CreateResultForTesting( OrchardTreeState(), std::vector()); result.discovered_notes = std::vector({note}); + result.found_spends = std::vector(); sync_state() .AsyncCall(&OrchardSyncState::ApplyScanResults) @@ -922,7 +925,7 @@ TEST_F(ZCashWalletServiceUnitTest, MakeAccountShielded) { EXPECT_CALL(make_account_shielded_callback, Run(Eq(std::nullopt))); zcash_wallet_service_->MakeAccountShielded( - account_id_1.Clone(), make_account_shielded_callback.Get()); + account_id_1.Clone(), 0, make_account_shielded_callback.Get()); task_environment_.RunUntilIdle(); } @@ -1003,10 +1006,10 @@ TEST_F(ZCashWalletServiceUnitTest, ShieldFunds_FailsOnNetworkError) { zcash::mojom::BlockID::New(100000u, std::vector()); std::move(callback).Run(std::move(response)); })); - ON_CALL(zcash_rpc(), GetLatestTreeState(_, _)) - .WillByDefault( - ::testing::Invoke([&](const std::string& chain_id, - ZCashRpc::GetTreeStateCallback callback) { + ON_CALL(zcash_rpc(), GetTreeState(_, _, _)) + .WillByDefault(::testing::Invoke( + [&](const std::string& chain_id, zcash::mojom::BlockIDPtr block_id, + ZCashRpc::GetTreeStateCallback callback) { std::move(callback).Run(base::unexpected("error")); })); @@ -1071,8 +1074,9 @@ TEST_F(ZCashWalletServiceUnitTest, MAYBE_ShieldFunds) { std::move(callback).Run(std::move(response)); })); - ON_CALL(zcash_rpc(), GetLatestTreeState(_, _)) + ON_CALL(zcash_rpc(), GetTreeState(_, _, _)) .WillByDefault(::testing::Invoke([&](const std::string& chain_id, + zcash::mojom::BlockIDPtr block_id, ZCashRpc::GetTreeStateCallback callback) { auto tree_state = zcash::mojom::TreeState::New( @@ -1466,10 +1470,10 @@ TEST_F(ZCashWalletServiceUnitTest, MAYBE_ShieldAllFunds) { std::move(callback).Run(std::move(response)); })); - ON_CALL(zcash_rpc(), GetLatestTreeState(_, _)) - .WillByDefault( - ::testing::Invoke([&](const std::string& chain_id, - ZCashRpc::GetTreeStateCallback callback) { + ON_CALL(zcash_rpc(), GetTreeState(_, _, _)) + .WillByDefault(::testing::Invoke( + [&](const std::string& chain_id, zcash::mojom::BlockIDPtr block_id, + ZCashRpc::GetTreeStateCallback callback) { auto tree_state = zcash::mojom::TreeState::New( "main" /* network */, 2468414 /* height */, "0000000000b9f12d757cf10d5164c8eb2dceb79efbebd15939ac0c2ef69857" diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index 09ef4e69c4d9..081e8acd5c2d 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -1820,6 +1820,7 @@ struct ZCashBalance { uint64 total_balance; uint64 transparent_balance; uint64 shielded_balance; + // Transparent balances map balances; // address -> balance of that address(sum of all related utxos) }; @@ -1856,13 +1857,39 @@ enum ZCashAddressValidationResult { NetworkMismatch = 5 }; +struct ZCashOrchardNoteInfo { + uint32 block_index; + uint32 tree_position; + uint32 value; +}; + +struct ZCashOrchardCheckpoint { + uint32 checkpoint_id; + uint32 tree_position; +}; + +struct ZCashTreeState { + uint32 tree_size; + array notes; + array checkpoints; +}; + struct ZCashShieldSyncStatus { - uint64 current_block; - uint64 chain_tip; + uint32 start_block; + uint32 end_block; + + uint32 total_ranges; + uint32 scanned_ranges; + uint32 notes_found; uint64 spendable_balance; }; +struct ZCashChainTipStatus { + uint32 latest_scanned_block; + uint32 chain_tip; +}; + interface ZCashWalletServiceObserver { OnSyncStart(AccountId account_id); OnSyncStatusUpdate(AccountId account_id, ZCashShieldSyncStatus status); @@ -1884,9 +1911,11 @@ interface ZCashWalletService { AddObserver(pending_remote observer); - MakeAccountShielded(AccountId account_id) => (string? error_message); - StartShieldSync(AccountId account_id) => (string? error_message); + MakeAccountShielded(AccountId account_id, uint32 account_birthday_block /* 0 if use last available block */) => (string? error_message); + StartShieldSync(AccountId account_id, uint32 to /* 0 if latest available block is used */) => (string? error_message); StopShieldSync(AccountId account_id) => (string? error_message); + ResetSyncState(AccountId account_id) => (string? error_message); + GetChainTipStatus(AccountId account_id, string chain_id) => (ZCashChainTipStatus? status, string? error_message); }; enum TransactionStatus { diff --git a/components/brave_wallet/common/features.cc b/components/brave_wallet/common/features.cc index 6d107c577862..99883b2e7a07 100644 --- a/components/brave_wallet/common/features.cc +++ b/components/brave_wallet/common/features.cc @@ -41,7 +41,7 @@ BASE_FEATURE(kBraveWalletZCashFeature, ); const base::FeatureParam kZCashShieldedTransactionsEnabled{ - &kBraveWalletZCashFeature, "zcash_shielded_transactions_enabled", false}; + &kBraveWalletZCashFeature, "zcash_shielded_transactions_enabled", true}; BASE_FEATURE(kBraveWalletAnkrBalancesFeature, "BraveWalletAnkrBalances", diff --git a/components/brave_wallet/common/zcash_utils.h b/components/brave_wallet/common/zcash_utils.h index 4a0b043c6b84..647b25e61281 100644 --- a/components/brave_wallet/common/zcash_utils.h +++ b/components/brave_wallet/common/zcash_utils.h @@ -105,9 +105,9 @@ struct OrchardOutput { static std::optional FromValue(const base::Value::Dict& value); }; -// Structure describes note nullifier that marks some note as spent +// Describes note nullifier that marks some note as spent. struct OrchardNoteSpend { - // Block id where spent nullifier was met + // Block id where spent nullifier was met. uint32_t block_id = 0; std::array nullifier; diff --git a/components/brave_wallet_ui/common/slices/endpoints/zcash.endpoints.ts b/components/brave_wallet_ui/common/slices/endpoints/zcash.endpoints.ts index 8862d648f694..341a1a6ac039 100644 --- a/components/brave_wallet_ui/common/slices/endpoints/zcash.endpoints.ts +++ b/components/brave_wallet_ui/common/slices/endpoints/zcash.endpoints.ts @@ -22,7 +22,7 @@ export const zcashEndpoints = ({ const { zcashWalletService } = baseQuery(undefined).data const { errorMessage } = await zcashWalletService.makeAccountShielded( - args + args, 0 ) if (errorMessage) { diff --git a/components/brave_wallet_ui/page/screens/dev-zcash/dev-zcash.tsx b/components/brave_wallet_ui/page/screens/dev-zcash/dev-zcash.tsx index f303bf46bf5e..29920bd91738 100644 --- a/components/brave_wallet_ui/page/screens/dev-zcash/dev-zcash.tsx +++ b/components/brave_wallet_ui/page/screens/dev-zcash/dev-zcash.tsx @@ -7,7 +7,6 @@ import getAPIProxy from '../../../common/async/bridge' import * as React from 'react' import styled from 'styled-components' -import { useState } from 'react' import { BraveWallet } from '../../../constants/types' import { LoadingSkeleton // @@ -55,20 +54,22 @@ interface GetBalanceSectionProps { } const GetBalanceSection = (props: GetBalanceSectionProps) => { - const [loading, setLoading] = useState(true) - const [balance, setBalance] = useState< + const [loading, setLoading] = React.useState(true) + const [balance, setBalance] = React.useState< BraveWallet.ZCashBalance | undefined >() - const [shieldResult, setShieldResult] = useState() + const [shieldResult, setShieldResult] = React.useState() const [ makeAccountShieldableResult, - setMakeAccountShieldableResult] = useState() - const [syncStatusResult, setSyncStatusResult] = useState(); - const [shieldedBalanceValue, setShieldedBalanceValue] = useState(); + setMakeAccountShieldableResult] = React.useState() + const [syncStatusResult, setSyncStatusResult] = React.useState(); + const [shieldedBalanceValue, setShieldedBalanceValue] = React.useState(); + const [accountBirthdayValue, setAccountBirthdayValue] = React.useState(); + const [syncBlockLimit, setSyncBlockLimit] = React.useState(); const makeAccountShielded = async() => { const result = await getAPIProxy().zcashWalletService.makeAccountShielded( - props.accountId); + props.accountId, Number(accountBirthdayValue || '0')); setMakeAccountShieldableResult(result.errorMessage || 'Done'); } @@ -76,7 +77,7 @@ const GetBalanceSection = (props: GetBalanceSectionProps) => { const startOrchardSync = async() => { setSyncStatusResult('') const result = - await getAPIProxy().zcashWalletService.startShieldSync(props.accountId); + await getAPIProxy().zcashWalletService.startShieldSync(props.accountId, Number(syncBlockLimit || '0')); if (result.errorMessage) { setSyncStatusResult("Sync error " + result.errorMessage); @@ -91,6 +92,14 @@ const GetBalanceSection = (props: GetBalanceSectionProps) => { } } + const resetAccountSyncState = async() => { + const result = + await getAPIProxy().zcashWalletService.resetSyncState(props.accountId); + if (result.errorMessage) { + setSyncStatusResult("Stop error " + result.errorMessage); + } + } + const shieldAllFunds = async () => { let { txId, @@ -128,10 +137,11 @@ const GetBalanceSection = (props: GetBalanceSectionProps) => { }, onSyncStatusUpdate: (accountId: BraveWallet.AccountId, status: ZCashShieldSyncStatus) => { + console.error('xxxzzz status update'); if (props.accountId.uniqueKey === accountId.uniqueKey) { setSyncStatusResult("Current block " + - status.currentBlock + "/" + - status.chainTip); + status.endBlock + "-" + + status.startBlock + " " + status.scannedRanges + "/" + status.totalRanges); setShieldedBalanceValue("Found balance: " + status.spendableBalance); } }, @@ -163,9 +173,26 @@ const GetBalanceSection = (props: GetBalanceSectionProps) => { /> ) : ( <> + setAccountBirthdayValue(ev.target.value)} + spellCheck={false} + /> + setSyncBlockLimit(ev.target.value)} + spellCheck={false} + /> + @@ -203,8 +230,8 @@ interface GetZCashAccountInfoSectionProps { const GetZCashAccountInfoSection: React.FC< GetZCashAccountInfoSectionProps > = ({ accountId }) => { - const [loading, setLoading] = useState(true) - const [zcashAccountInfo, setZCashAccountInfo] = useState< + const [loading, setLoading] = React.useState(true) + const [zcashAccountInfo, setZCashAccountInfo] = React.useState< BraveWallet.ZCashAccountInfo | undefined >() @@ -251,6 +278,18 @@ const GetZCashAccountInfoSection: React.FC< {zcashAccountInfo?.unifiedAddress || '-'} +
+ Orchard address: + + {zcashAccountInfo?.orchardAddress || '-'} + +
+
+ Account shielded birthday: + + {String(zcashAccountInfo?.accountShieldBirthday?.value) || '-'} + +
Next Receive Address: {keyId(zcashAccountInfo?.nextTransparentReceiveAddress.keyId)} diff --git a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom index 2dfa39ef247b..0b0f996bc981 100644 --- a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom +++ b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom @@ -84,5 +84,6 @@ interface ZCashDecoder { ParseRawTransaction(string data) => (RawTransaction? tx); ParseTreeState(string data) => (TreeState? tree_state); ParseCompactBlocks(array data) => (array? compact_blocks); + ParseSubtreeRoots(array data) => (array? subtree_roots); }; diff --git a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto index 89ae1120db39..73657e8b68bb 100644 --- a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto +++ b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto @@ -110,3 +110,19 @@ message CompactBlock { repeated CompactTx vtx = 7; ChainMetadata chainMetadata = 8; } + +enum ShieldedProtocol { + sapling = 0; + orchard = 1; +} + +message GetSubtreeRootsArg { + uint32 startIndex = 1; + ShieldedProtocol shieldedProtocol = 2; + uint32 maxEntries = 3; +} +message SubtreeRoot { + bytes rootHash = 2; + bytes completingBlockHash = 3; + uint64 completingBlockHeight = 4; +} diff --git a/components/services/brave_wallet/zcash/zcash_decoder.cc b/components/services/brave_wallet/zcash/zcash_decoder.cc index e51cfa4857c2..668a243a7ff8 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.cc +++ b/components/services/brave_wallet/zcash/zcash_decoder.cc @@ -138,4 +138,23 @@ void ZCashDecoder::ParseCompactBlocks(const std::vector& data, std::move(callback).Run(std::move(parsed_blocks)); } +void ZCashDecoder::ParseSubtreeRoots(const std::vector& data, + ParseSubtreeRootsCallback callback) { + std::vector roots; + for (const auto& data_block : data) { + ::zcash::SubtreeRoot result; + auto serialized_message = ResolveSerializedMessage(data_block); + if (!serialized_message || + !result.ParseFromString(serialized_message.value()) || + serialized_message->empty()) { + std::move(callback).Run(std::nullopt); + return; + } + roots.push_back(zcash::mojom::SubtreeRoot::New( + ToVector(result.roothash()), ToVector(result.completingblockhash()), + result.completingblockheight())); + } + std::move(callback).Run(std::move(roots)); +} + } // namespace brave_wallet diff --git a/components/services/brave_wallet/zcash/zcash_decoder.h b/components/services/brave_wallet/zcash/zcash_decoder.h index b7136ccb0e50..cfba78838283 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.h +++ b/components/services/brave_wallet/zcash/zcash_decoder.h @@ -36,6 +36,8 @@ class ZCashDecoder : public zcash::mojom::ZCashDecoder { ParseTreeStateCallback callback) override; void ParseCompactBlocks(const std::vector& data, ParseCompactBlocksCallback callback) override; + void ParseSubtreeRoots(const std::vector& data, + ParseSubtreeRootsCallback callback) override; }; } // namespace brave_wallet From fcabd5af950f2653663ec56940a841dcfd803f1c Mon Sep 17 00:00:00 2001 From: oisupov Date: Tue, 17 Dec 2024 23:04:36 +0400 Subject: [PATCH 2/2] Fix sync --- .../brave_wallet/browser/internal/orchard_sync_state.cc | 5 +++-- .../brave_wallet/browser/zcash/zcash_shield_sync_service.cc | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/brave_wallet/browser/internal/orchard_sync_state.cc b/components/brave_wallet/browser/internal/orchard_sync_state.cc index 34a9b6f0441d..a1ab54b7a897 100644 --- a/components/brave_wallet/browser/internal/orchard_sync_state.cc +++ b/components/brave_wallet/browser/internal/orchard_sync_state.cc @@ -155,8 +155,9 @@ OrchardSyncState::UpdateSubtreeRoots( const mojom::AccountIdPtr& account_id, uint32_t start_index, const std::vector& roots) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return storage_.UpdateSubtreeRoots(account_id, start_index, roots); + // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // return storage_.UpdateSubtreeRoots(account_id, start_index, roots); + return OrchardStorage::Result::kSuccess; } void OrchardSyncState::ResetDatabase() { diff --git a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc index 68bffc600f62..981af233ff36 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc @@ -181,7 +181,7 @@ void ZCashShieldSyncService::OnGetAccountMeta( return; } account_meta_ = **result; - if (account_meta_->latest_scanned_block_id.value() && + if (account_meta_->latest_scanned_block_id && (account_meta_->latest_scanned_block_id.value() < account_meta_->account_birthday)) { error_ = Error{ErrorCode::kFailedToRetrieveAccount, ""};