diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index c322e93d8661..acb8ac254373 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -314,14 +314,13 @@ static_library("browser") { sources += [ "zcash/zcash_create_shield_transaction_task.cc", "zcash/zcash_create_shield_transaction_task.h", - "zcash/zcash_orchard_storage.cc", - "zcash/zcash_orchard_storage.h", "zcash/zcash_shield_sync_service.cc", "zcash/zcash_shield_sync_service.h", ] deps += [ "internal:orchard_bundle", + "internal/orchard_storage:orchard_storage", "//sql", ] } diff --git a/components/brave_wallet/browser/internal/BUILD.gn b/components/brave_wallet/browser/internal/BUILD.gn index c957e9fbd3b8..e84322df0c9c 100644 --- a/components/brave_wallet/browser/internal/BUILD.gn +++ b/components/brave_wallet/browser/internal/BUILD.gn @@ -39,13 +39,32 @@ source_set("hd_key") { } if (enable_orchard) { + source_set("test_support") { + sources = [ + "orchard_test_utils.cc", + "orchard_test_utils.h", + ] + public_deps = [ + "//brave/components/brave_wallet/browser/zcash/rust:test_support_headers", + ] + deps = [ + ":orchard_bundle", + "//brave/components/brave_wallet/browser/zcash/rust:test_support", + ] + } + source_set("orchard_bundle") { sources = [ "orchard_block_scanner.cc", "orchard_block_scanner.h", "orchard_bundle_manager.cc", "orchard_bundle_manager.h", + "orchard_sync_state.cc", + "orchard_sync_state.h", + ] + deps = [ + "orchard_storage", + "//brave/components/brave_wallet/browser/zcash/rust", ] - deps = [ "//brave/components/brave_wallet/browser/zcash/rust" ] } } diff --git a/components/brave_wallet/browser/internal/hd_key_zip32.cc b/components/brave_wallet/browser/internal/hd_key_zip32.cc index d9411228ebb4..a6ca7ac06681 100644 --- a/components/brave_wallet/browser/internal/hd_key_zip32.cc +++ b/components/brave_wallet/browser/internal/hd_key_zip32.cc @@ -8,35 +8,35 @@ #include #include "base/memory/ptr_util.h" -#include "brave/components/brave_wallet/browser/zcash/rust/extended_spending_key.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h" namespace brave_wallet { -HDKeyZip32::HDKeyZip32(std::unique_ptr esk) - : extended_spending_key_(std::move(esk)) {} +HDKeyZip32::HDKeyZip32(std::unique_ptr esk) + : orchard_extended_spending_key_(std::move(esk)) {} HDKeyZip32::~HDKeyZip32() = default; // static std::unique_ptr HDKeyZip32::GenerateFromSeed( base::span seed) { - return base::WrapUnique( - new HDKeyZip32(orchard::ExtendedSpendingKey::GenerateFromSeed(seed))); + return base::WrapUnique(new HDKeyZip32( + orchard::OrchardExtendedSpendingKey::GenerateFromSeed(seed))); } std::unique_ptr HDKeyZip32::DeriveHardenedChild(uint32_t index) { - return base::WrapUnique( - new HDKeyZip32(extended_spending_key_->DeriveHardenedChild(index))); + return base::WrapUnique(new HDKeyZip32( + orchard_extended_spending_key_->DeriveHardenedChild(index))); } std::optional HDKeyZip32::GetDiversifiedAddress( uint32_t div_index, OrchardAddressKind kind) { - return extended_spending_key_->GetDiversifiedAddress(div_index, kind); + return orchard_extended_spending_key_->GetDiversifiedAddress(div_index, kind); } OrchardFullViewKey HDKeyZip32::GetFullViewKey() { - return extended_spending_key_->GetFullViewKey(); + return orchard_extended_spending_key_->GetFullViewKey(); } } // 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 1b0d76485280..50c738e14fe8 100644 --- a/components/brave_wallet/browser/internal/hd_key_zip32.h +++ b/components/brave_wallet/browser/internal/hd_key_zip32.h @@ -14,7 +14,7 @@ namespace brave_wallet { namespace orchard { -class ExtendedSpendingKey; +class OrchardExtendedSpendingKey; } // namespace orchard // Implements Orchard key generation from @@ -43,10 +43,11 @@ class HDKeyZip32 { OrchardFullViewKey GetFullViewKey(); private: - explicit HDKeyZip32(std::unique_ptr key); + explicit HDKeyZip32(std::unique_ptr key); // Extended spending key is a root key of an account, all other keys can be // derived from esk - std::unique_ptr extended_spending_key_; + std::unique_ptr + orchard_extended_spending_key_; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_block_scanner.cc b/components/brave_wallet/browser/internal/orchard_block_scanner.cc index ad4baaf1b730..e21b1a156020 100644 --- a/components/brave_wallet/browser/internal/orchard_block_scanner.cc +++ b/components/brave_wallet/browser/internal/orchard_block_scanner.cc @@ -5,48 +5,55 @@ #include "brave/components/brave_wallet/browser/internal/orchard_block_scanner.h" +#include "base/threading/thread_restrictions.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h" + namespace brave_wallet { OrchardBlockScanner::Result::Result() = default; -OrchardBlockScanner::Result::Result(std::vector discovered_notes, - std::vector spent_notes) +OrchardBlockScanner::Result::Result( + std::vector discovered_notes, + std::vector spent_notes, + std::unique_ptr scanned_blocks) : discovered_notes(std::move(discovered_notes)), - spent_notes(std::move(spent_notes)) {} - -OrchardBlockScanner::Result::Result(const Result&) = default; + found_spends(std::move(spent_notes)), + scanned_blocks(std::move(scanned_blocks)) {} +OrchardBlockScanner::Result::Result(OrchardBlockScanner::Result&&) = default; OrchardBlockScanner::Result& OrchardBlockScanner::Result::operator=( - const Result&) = default; + OrchardBlockScanner::Result&&) = default; OrchardBlockScanner::Result::~Result() = default; -OrchardBlockScanner::OrchardBlockScanner( - const OrchardFullViewKey& full_view_key) - : decoder_(orchard::OrchardBlockDecoder::FromFullViewKey(full_view_key)) {} +OrchardBlockScanner::OrchardBlockScanner(const OrchardFullViewKey& fvk) + : fvk_(fvk) {} OrchardBlockScanner::~OrchardBlockScanner() = default; base::expected OrchardBlockScanner::ScanBlocks( - std::vector known_notes, - std::vector blocks) { + const OrchardTreeState& tree_state, + const std::vector& blocks) { + base::AssertLongCPUWorkAllowed(); + + std::unique_ptr result = + orchard::OrchardBlockDecoder::DecodeBlocks(fvk_, tree_state, blocks); + if (!result) { + return base::unexpected(ErrorCode::kInputError); + } + + std::optional> found_notes = + result->GetDiscoveredNotes(); + + if (!found_notes) { + return base::unexpected(ErrorCode::kDiscoveredNotesError); + } + std::vector found_spends; - std::vector found_notes; for (const auto& block : blocks) { - // Scan block using the decoder initialized with the provided fvk - // to find new spendable notes. - auto scan_result = decoder_->ScanBlock(block); - if (!scan_result) { - return base::unexpected(ErrorCode::kDecoderError); - } - found_notes.insert(found_notes.end(), scan_result->begin(), - scan_result->end()); - // Place found notes to the known notes list so we can also check for - // nullifiers - known_notes.insert(known_notes.end(), scan_result->begin(), - scan_result->end()); for (const auto& tx : block->vtx) { // We only scan orchard actions here for (const auto& orchard_action : tx->orchard_actions) { @@ -54,25 +61,19 @@ OrchardBlockScanner::ScanBlocks( return base::unexpected(ErrorCode::kInputError); } - std::array action_nullifier; - base::ranges::copy(orchard_action->nullifier, action_nullifier.begin()); - // Nullifier is a public information about some note being spent. - // Here we are trying to find a known spendable notes which nullifier - // matches nullifier from the processed transaction. - if (std::find_if(known_notes.begin(), known_notes.end(), - [&action_nullifier](const auto& v) { - return v.nullifier == action_nullifier; - }) != known_notes.end()) { - OrchardNoteSpend spend; - spend.block_id = block->height; - spend.nullifier = action_nullifier; - found_spends.push_back(std::move(spend)); - } + // Here we are collecting nullifiers from the blocks to check them + // later. + OrchardNoteSpend spend; + base::span(spend.nullifier).copy_from(orchard_action->nullifier); + spend.block_id = block->height; + found_spends.push_back(std::move(spend)); } } } - return Result({std::move(found_notes), std::move(found_spends)}); + + return Result({std::move(found_notes.value()), std::move(found_spends), + std::move(result)}); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_block_scanner.h b/components/brave_wallet/browser/internal/orchard_block_scanner.h index a2fa0d018653..f216885ae4b6 100644 --- a/components/brave_wallet/browser/internal/orchard_block_scanner.h +++ b/components/brave_wallet/browser/internal/orchard_block_scanner.h @@ -12,8 +12,10 @@ #include #include "base/types/expected.h" -#include "brave/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h" +#include "brave/components/brave_wallet/browser/internal/orchard_block_scanner.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h" #include "brave/components/brave_wallet/common/zcash_utils.h" +#include "brave/components/services/brave_wallet/public/mojom/zcash_decoder.mojom.h" namespace brave_wallet { @@ -21,20 +23,25 @@ namespace brave_wallet { // spendable notes related to the account. class OrchardBlockScanner { public: - enum class ErrorCode { kInputError, kDecoderError }; + enum class ErrorCode { kInputError, kDiscoveredNotesError, kDecoderError }; struct Result { Result(); Result(std::vector discovered_notes, - std::vector spent_notes); - Result(const Result&); - Result& operator=(const Result&); + std::vector spent_notes, + std::unique_ptr scanned_blocks); + Result(const Result&) = delete; + Result& operator=(const Result&) = delete; + Result(Result&&); + Result& operator=(Result&&); ~Result(); - // New notes have been discovered + // New notes have been discovered. std::vector discovered_notes; - // Nullifiers for the previously discovered notes - std::vector spent_notes; + // Nullifiers for the previously discovered notes. + std::vector found_spends; + // Decoded blocks bundle to be insterted in the shard tree. + std::unique_ptr scanned_blocks; }; explicit OrchardBlockScanner(const OrchardFullViewKey& full_view_key); @@ -43,11 +50,11 @@ class OrchardBlockScanner { // Scans blocks to find incoming notes related to fvk // Also checks whether existing notes were spent. virtual base::expected ScanBlocks( - std::vector known_notes, - std::vector blocks); + const OrchardTreeState& tree_state, + const std::vector& blocks); private: - std::unique_ptr decoder_; + OrchardFullViewKey fvk_; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_block_scanner_unittest.cc b/components/brave_wallet/browser/internal/orchard_block_scanner_unittest.cc index 7ad780095e3a..c3df069ef84f 100644 --- a/components/brave_wallet/browser/internal/orchard_block_scanner_unittest.cc +++ b/components/brave_wallet/browser/internal/orchard_block_scanner_unittest.cc @@ -189,7 +189,7 @@ TEST(OrchardBlockScannerTest, DiscoverNewNotes) { EXPECT_EQ(result.value().discovered_notes[3].block_id, 11u); EXPECT_EQ(result.value().discovered_notes[3].amount, 2549979667u); - EXPECT_EQ(result.value().spent_notes.size(), 0u); + EXPECT_EQ(result.value().found_spends.size(), 5u); } TEST(OrchardBlockScannerTest, WrongInput) { @@ -469,11 +469,13 @@ TEST(OrchardBlockScanner, FoundKnownNullifiers_SameBatch) { EXPECT_EQ(result.value().discovered_notes[0].block_id, 10u); EXPECT_EQ(result.value().discovered_notes[0].amount, 3625561528u); - EXPECT_EQ(result.value().spent_notes.size(), 1u); - EXPECT_EQ(result.value().spent_notes[0].block_id, 11u); + EXPECT_EQ(result.value().found_spends.size(), 2u); + EXPECT_EQ(result.value().found_spends[0].block_id, 10u); + EXPECT_EQ(result.value().found_spends[1].block_id, 11u); + EXPECT_EQ( - std::vector(result.value().spent_notes[0].nullifier.begin(), - result.value().spent_notes[0].nullifier.end()), + std::vector(result.value().found_spends[1].nullifier.begin(), + result.value().found_spends[1].nullifier.end()), PrefixedHexStringToBytes( "0x6588cc7fabfab2b2a4baa89d4dfafaa50cc89d22f96d10fb7689461b921ad40d") .value()); @@ -525,11 +527,13 @@ TEST(OrchardBlockScanner, FoundKnownNullifiers) { notes.push_back(note); blocks.push_back(std::move(block)); - auto result = scanner.ScanBlocks(std::move(notes), std::move(blocks)); + OrchardTreeState tree_state; + + auto result = scanner.ScanBlocks(tree_state, std::move(blocks)); EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value().spent_notes.size(), 1u); - EXPECT_EQ(result.value().spent_notes[0], spend); + EXPECT_EQ(result.value().found_spends.size(), 1u); + EXPECT_EQ(result.value().found_spends[0].nullifier, spend.nullifier); EXPECT_EQ(result.value().discovered_notes.size(), 0u); } diff --git a/components/brave_wallet/browser/internal/orchard_bundle_manager.cc b/components/brave_wallet/browser/internal/orchard_bundle_manager.cc index 3e7d4317d634..101b31ecbc16 100644 --- a/components/brave_wallet/browser/internal/orchard_bundle_manager.cc +++ b/components/brave_wallet/browser/internal/orchard_bundle_manager.cc @@ -22,11 +22,11 @@ std::optional OrchardBundleManager::random_seed_for_testing_ = // static std::unique_ptr OrchardBundleManager::Create( base::span tree_state, - const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs) { + const std::vector& orchard_outputs) { if (orchard_outputs.empty()) { return nullptr; } - auto bundle = orchard::UnauthorizedOrchardBundle::Create( + auto bundle = orchard::OrchardUnauthorizedBundle::Create( tree_state, orchard_outputs, random_seed_for_testing_); if (!bundle) { return nullptr; @@ -36,11 +36,11 @@ std::unique_ptr OrchardBundleManager::Create( } OrchardBundleManager::OrchardBundleManager( - std::unique_ptr unauthorized_bundle) + std::unique_ptr unauthorized_bundle) : unauthorized_orchard_bundle_(std::move(unauthorized_bundle)) {} OrchardBundleManager::OrchardBundleManager( - std::unique_ptr authorized_bundle) + std::unique_ptr authorized_bundle) : authorized_orchard_bundle_(std::move(authorized_bundle)) {} std::optional> diff --git a/components/brave_wallet/browser/internal/orchard_bundle_manager.h b/components/brave_wallet/browser/internal/orchard_bundle_manager.h index 06ff30c44115..e505c045d1e5 100644 --- a/components/brave_wallet/browser/internal/orchard_bundle_manager.h +++ b/components/brave_wallet/browser/internal/orchard_bundle_manager.h @@ -11,8 +11,8 @@ #include #include -#include "brave/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h" -#include "brave/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h" #include "brave/components/brave_wallet/common/zcash_utils.h" namespace brave_wallet { @@ -45,7 +45,7 @@ class OrchardBundleManager { // Returns in unauthorized state static std::unique_ptr Create( base::span tree_state, - const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs); + const std::vector& orchard_outputs); static void OverrideRandomSeedForTesting(size_t seed) { random_seed_for_testing_ = seed; @@ -53,15 +53,15 @@ class OrchardBundleManager { private: explicit OrchardBundleManager( - std::unique_ptr unauthorized_bundle); + std::unique_ptr unauthorized_bundle); explicit OrchardBundleManager( - std::unique_ptr authorized_bundle); + std::unique_ptr authorized_bundle); static std::optional random_seed_for_testing_; - std::unique_ptr + std::unique_ptr unauthorized_orchard_bundle_; - std::unique_ptr authorized_orchard_bundle_; + std::unique_ptr authorized_orchard_bundle_; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_storage/BUILD.gn b/components/brave_wallet/browser/internal/orchard_storage/BUILD.gn new file mode 100644 index 000000000000..62d308fb4b14 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_storage/BUILD.gn @@ -0,0 +1,48 @@ +# 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/. + +import("//brave/components/brave_wallet/common/config.gni") + +source_set("orchard_storage") { + sources = [ + "orchard_shard_tree_types.cc", + "orchard_storage.cc", + ] + + visibility = [ + "//brave/components/brave_wallet/browser:*", + "//brave/components/brave_wallet/browser/internal:orchard_bundle", + "//brave/components/brave_wallet/browser/test:*", + ] + + deps = [ + "//base", + "//brave/components/brave_wallet/common", + "//sql", + ] + + public_deps = [ ":headers" ] +} + +source_set("headers") { + sources = [ + "orchard_shard_tree_types.h", + "orchard_storage.h", + ] + + visibility = [ + ":orchard_storage", + "//brave/components/brave_wallet/browser/internal:orchard_bundle", + "//brave/components/brave_wallet/browser/test:*", + "//brave/components/brave_wallet/browser/zcash/rust:*", + ] + + deps = [ + "//base", + "//brave/components/brave_wallet/common", + "//brave/components/services/brave_wallet/public/mojom", + "//sql", + ] +} diff --git a/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.cc b/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.cc new file mode 100644 index 000000000000..4fc14c970188 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.cc @@ -0,0 +1,61 @@ +/* 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/internal/orchard_storage/orchard_shard_tree_types.h" + +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" + +namespace brave_wallet { + +OrchardTreeState::OrchardTreeState() = default; +OrchardTreeState::~OrchardTreeState() = default; +OrchardTreeState::OrchardTreeState(OrchardTreeState&& other) = default; +OrchardTreeState& OrchardTreeState::operator=(OrchardTreeState&& other) = + default; + +OrchardCheckpoint::OrchardCheckpoint() = default; +OrchardCheckpoint::OrchardCheckpoint(CheckpointTreeState tree_state_position, + std::vector marks_removed) + : tree_state_position(tree_state_position), + marks_removed(std::move(marks_removed)) {} +OrchardCheckpoint::~OrchardCheckpoint() = default; +OrchardCheckpoint::OrchardCheckpoint(OrchardCheckpoint&& other) = default; +OrchardCheckpoint& OrchardCheckpoint::operator=(OrchardCheckpoint&& other) = + default; +OrchardCheckpoint OrchardCheckpoint::Clone() { + return OrchardCheckpoint(tree_state_position, marks_removed); +} + +OrchardCheckpointBundle::OrchardCheckpointBundle(uint32_t checkpoint_id, + OrchardCheckpoint checkpoint) + : checkpoint_id(checkpoint_id), checkpoint(std::move(checkpoint)) {} +OrchardCheckpointBundle::~OrchardCheckpointBundle() = default; +OrchardCheckpointBundle::OrchardCheckpointBundle( + OrchardCheckpointBundle&& other) = default; +OrchardCheckpointBundle& OrchardCheckpointBundle::operator=( + OrchardCheckpointBundle&& other) = default; + +OrchardShard::OrchardShard() = default; +OrchardShard::OrchardShard(OrchardShardAddress address, + std::optional root_hash, + std::vector shard_data) + : address(std::move(address)), + root_hash(std::move(root_hash)), + shard_data(std::move(shard_data)) {} +OrchardShard::~OrchardShard() = default; +OrchardShard::OrchardShard(OrchardShard&& other) = default; +OrchardShard& OrchardShard::operator=(OrchardShard&& other) = default; + +OrchardCommitment::OrchardCommitment(OrchardCommitmentValue cmu, + bool is_marked, + std::optional checkpoint_id) + : cmu(cmu), is_marked(is_marked), checkpoint_id(checkpoint_id) {} +OrchardCommitment::OrchardCommitment() = default; +OrchardCommitment::~OrchardCommitment() = default; +OrchardCommitment::OrchardCommitment(OrchardCommitment&& other) = default; +OrchardCommitment& OrchardCommitment::operator=(OrchardCommitment&& other) = + default; + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h b/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h new file mode 100644 index 000000000000..d041cbf1f1b0 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h @@ -0,0 +1,149 @@ +/* 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_INTERNAL_ORCHARD_STORAGE_ORCHARD_SHARD_TREE_TYPES_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_STORAGE_ORCHARD_SHARD_TREE_TYPES_H_ + +#include +#include + +#include "base/memory/raw_ref.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +// Leaf position of checkpoint. +// References to the TreeState struct: +// https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/shardtree/src/store.rs#L259 +using CheckpointTreeState = std::optional; + +// Checkpointed leafs are not pruned so they could be used +// as anchors for building shielded transactions. +// Last Orchard commitment in a block is used as a checkpoint. +// References to the Checkpoint: +// https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/shardtree/src/store.rs#L271 +struct OrchardCheckpoint { + OrchardCheckpoint(); + OrchardCheckpoint(CheckpointTreeState, std::vector); + ~OrchardCheckpoint(); + OrchardCheckpoint(const OrchardCheckpoint& other) = delete; + OrchardCheckpoint& operator=(const OrchardCheckpoint& other) = delete; + OrchardCheckpoint(OrchardCheckpoint&& other); + OrchardCheckpoint& operator=(OrchardCheckpoint&& other); + + OrchardCheckpoint Clone(); + bool operator==(const OrchardCheckpoint& other) const = default; + + // Leaf position of the checkpoint. + CheckpointTreeState tree_state_position; + // List of note positions that were spent at this checkpoint. + std::vector marks_removed; +}; + +// CheckpointId + Checkpoint value. +struct OrchardCheckpointBundle { + OrchardCheckpointBundle(uint32_t checkpoint_id, OrchardCheckpoint); + ~OrchardCheckpointBundle(); + OrchardCheckpointBundle(const OrchardCheckpointBundle& other) = delete; + OrchardCheckpointBundle& operator=(const OrchardCheckpointBundle& other) = + delete; + OrchardCheckpointBundle(OrchardCheckpointBundle&& other); + OrchardCheckpointBundle& operator=(OrchardCheckpointBundle&& other); + + bool operator==(const OrchardCheckpointBundle& other) const = default; + + // The block height serves as the checkpoint identifier. + uint32_t checkpoint_id = 0; + OrchardCheckpoint checkpoint; +}; + +// Address of a subtree in the shard tree. +// https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/incrementalmerkletree/src/lib.rs#L356 +struct OrchardShardAddress { + uint8_t level = 0; + uint32_t index = 0; + + bool operator==(const OrchardShardAddress& other) const = default; +}; + +// Top part of the shard tree from the root to the shard roots level +// Used for optimization purposes in the shard tree crate. +// Represents serialized binary tree to be inserted to the OrchardStorage +// similar to +// https://github.com/zcash/librustzcash/blob/205d4c930319b7b6d24aeb4efde69e9b4d1b6f7b/zcash_client_sqlite/src/wallet/commitment_tree.rs#L558 +using OrchardShardTreeCap = std::vector; + +// Subtree with root selected from the shard roots level. +// Represents serialized binary tree with address and other related data to be +// inserted to the OrchardStorage similar to +// https://github.com/zcash/librustzcash/blob/205d4c930319b7b6d24aeb4efde69e9b4d1b6f7b/zcash_client_sqlite/src/wallet/commitment_tree.rs#L478 +struct OrchardShard { + OrchardShard(); + OrchardShard(OrchardShardAddress shard_addr, + std::optional shard_hash, + std::vector shard_data); + ~OrchardShard(); + + OrchardShard(const OrchardShard& other) = delete; + OrchardShard& operator=(const OrchardShard& other) = delete; + OrchardShard(OrchardShard&& other); + OrchardShard& operator=(OrchardShard&& other); + + bool operator==(const OrchardShard& other) const = default; + + // Subtree root address. + OrchardShardAddress address; + // Root hash exists only on completed shards. + std::optional root_hash; + std::vector shard_data; + // Right-most position of the subtree leaf. + uint32_t subtree_end_height = 0; +}; + +// References a pair of (MerkleHashOrchard, Retention) which +// represents a leaf in the shard tree. +struct OrchardCommitment { + OrchardCommitment(OrchardCommitmentValue cmu, + bool is_marked, + std::optional checkpoint_id); + OrchardCommitment(); + ~OrchardCommitment(); + OrchardCommitment(const OrchardCommitment& other) = delete; + OrchardCommitment& operator=(const OrchardCommitment& other) = delete; + OrchardCommitment(OrchardCommitment&& other); + OrchardCommitment& operator=(OrchardCommitment&& other); + + // Leaf value + OrchardCommitmentValue cmu; + // Retention + bool is_marked = false; + std::optional checkpoint_id; +}; + +// Compact representation of the Merkle tree on some point. +// Since batch inserting may contain gaps between scan ranges we insert +// frontier which allows to calculate node hashes and witnesses(merkle path from +// leaf to the tree root) even when previous scan ranges are not completed. +// References to the NonEmptyFrontier: +// https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/incrementalmerkletree/src/frontier.rs#L41 +struct OrchardTreeState { + OrchardTreeState(); + ~OrchardTreeState(); + OrchardTreeState(OrchardTreeState&& other); + OrchardTreeState& operator=(OrchardTreeState&& other); + OrchardTreeState(const OrchardTreeState&) = delete; + OrchardTreeState& operator=(const OrchardTreeState&) = delete; + + // Tree state is linked to the end of some block. + uint32_t block_height = 0u; + // Number of leafs at the position. + uint32_t tree_size = 0u; + // https://docs.aztec.network/protocol-specs/l1-smart-contracts/frontier + std::vector frontier; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_STORAGE_ORCHARD_SHARD_TREE_TYPES_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.cc similarity index 68% rename from components/brave_wallet/browser/zcash/zcash_orchard_storage.cc rename to components/brave_wallet/browser/internal/orchard_storage/orchard_storage.cc index 8bb386f53dc2..d8e6f3258757 100644 --- a/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc +++ b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.cc @@ -3,7 +3,7 @@ * 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_orchard_storage.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" #include #include @@ -13,6 +13,8 @@ #include "base/containers/to_vector.h" #include "base/files/file_util.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/types/expected_macros.h" #include "brave/components/brave_wallet/common/hex_utils.h" #include "sql/meta_table.h" #include "sql/statement.h" @@ -70,22 +72,20 @@ base::expected, std::string> ReadRootHash( } // namespace -ZCashOrchardStorage::AccountMeta::AccountMeta() = default; -ZCashOrchardStorage::AccountMeta::~AccountMeta() = default; -ZCashOrchardStorage::AccountMeta::AccountMeta(const AccountMeta&) = default; -ZCashOrchardStorage::AccountMeta& ZCashOrchardStorage::AccountMeta::operator=( +OrchardStorage::AccountMeta::AccountMeta() = default; +OrchardStorage::AccountMeta::~AccountMeta() = default; +OrchardStorage::AccountMeta::AccountMeta(const AccountMeta&) = default; +OrchardStorage::AccountMeta& OrchardStorage::AccountMeta::operator=( const AccountMeta&) = default; -ZCashOrchardStorage::ZCashOrchardStorage(base::FilePath path_to_database) - : db_file_path_(std::move(path_to_database)) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -} +OrchardStorage::OrchardStorage(const base::FilePath& path_to_database) + : db_file_path_(path_to_database) {} -ZCashOrchardStorage::~ZCashOrchardStorage() { +OrchardStorage::~OrchardStorage() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } -bool ZCashOrchardStorage::EnsureDbInit() { +bool OrchardStorage::EnsureDbInit() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (database_.is_open()) { return true; @@ -93,14 +93,18 @@ bool ZCashOrchardStorage::EnsureDbInit() { return CreateOrUpdateDatabase(); } -void ZCashOrchardStorage::ResetDatabase() { +void OrchardStorage::ResetDatabase() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); database_.Close(); sql::Database::Delete(db_file_path_); } -bool ZCashOrchardStorage::CreateOrUpdateDatabase() { +bool OrchardStorage::CreateOrUpdateDatabase() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); const base::FilePath dir = db_file_path_.DirName(); @@ -136,7 +140,10 @@ bool ZCashOrchardStorage::CreateOrUpdateDatabase() { return true; } -bool ZCashOrchardStorage::CreateSchema() { +bool OrchardStorage::CreateSchema() { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); sql::Transaction transaction(&database_); @@ -198,15 +205,18 @@ bool ZCashOrchardStorage::CreateSchema() { // TODO(cypt4): Add indexes } -bool ZCashOrchardStorage::UpdateSchema() { +bool OrchardStorage::UpdateSchema() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return true; } -base::expected -ZCashOrchardStorage::RegisterAccount(const mojom::AccountIdPtr& account_id, - uint32_t account_birthday_block) { +base::expected +OrchardStorage::RegisterAccount(const mojom::AccountIdPtr& account_id, + uint32_t account_birthday_block) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -215,7 +225,8 @@ ZCashOrchardStorage::RegisterAccount(const mojom::AccountIdPtr& account_id, sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } sql::Statement register_account_statement(database_.GetCachedStatement( @@ -241,9 +252,13 @@ ZCashOrchardStorage::RegisterAccount(const mojom::AccountIdPtr& account_id, return meta; } -base::expected -ZCashOrchardStorage::GetAccountMeta(const mojom::AccountIdPtr& account_id) { +base::expected, + OrchardStorage::Error> +OrchardStorage::GetAccountMeta(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -258,8 +273,12 @@ ZCashOrchardStorage::GetAccountMeta(const mojom::AccountIdPtr& account_id) { resolve_account_statement.BindString(0, account_id->unique_key); if (!resolve_account_statement.Step()) { - return base::unexpected( - Error{ErrorCode::kAccountNotFound, "Account not found."}); + if (!resolve_account_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } else { + return base::ok(std::nullopt); + } } AccountMeta account_meta; @@ -274,7 +293,7 @@ ZCashOrchardStorage::GetAccountMeta(const mojom::AccountIdPtr& account_id) { auto latest_scanned_block = ReadUint32(resolve_account_statement, 1); if (!latest_scanned_block) { return base::unexpected(Error{ErrorCode::kConsistencyError, - "Wrong latest scanned block format."}); + "Wrong latest scanned block format"}); } account_meta.latest_scanned_block_id = latest_scanned_block.value(); } @@ -284,24 +303,28 @@ ZCashOrchardStorage::GetAccountMeta(const mojom::AccountIdPtr& account_id) { resolve_account_statement.ColumnString(2); } - return account_meta; + return base::ok(account_meta); } -std::optional ZCashOrchardStorage::HandleChainReorg( - const mojom::AccountIdPtr& account_id, - uint32_t reorg_block_id, - const std::string& reorg_block_hash) { +base::expected +OrchardStorage::HandleChainReorg(const mojom::AccountIdPtr& account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); CHECK(account_id); if (!EnsureDbInit()) { - return Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}; + return base::unexpected( + Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}); } sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return Error{ErrorCode::kFailedToCreateTransaction, ""}; + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } sql::Statement remove_from_spent_notes(database_.GetCachedStatement( @@ -331,22 +354,25 @@ std::optional ZCashOrchardStorage::HandleChainReorg( if (!remove_from_notes.Run() || !remove_from_spent_notes.Run() || !update_account_meta.Run()) { - return Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (!transaction.Commit()) { - return Error{ErrorCode::kFailedToCommitTransaction, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToCommitTransaction, + database_.GetErrorMessage()}); } - return std::nullopt; + return base::ok(Result::kSuccess); } -base::expected -ZCashOrchardStorage::ResetAccountSyncState( - const mojom::AccountIdPtr& account_id) { +base::expected +OrchardStorage::ResetAccountSyncState(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); + if (!EnsureDbInit()) { return base::unexpected( Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}); @@ -387,7 +413,8 @@ ZCashOrchardStorage::ResetAccountSyncState( sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } if (!clear_cap_statement.Run() || !clear_shards_statement.Run() || @@ -402,12 +429,15 @@ ZCashOrchardStorage::ResetAccountSyncState( database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetNullifiers(const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetNullifiers(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -427,13 +457,13 @@ ZCashOrchardStorage::GetNullifiers(const mojom::AccountIdPtr& account_id) { auto block_id = ReadUint32(resolve_note_spents, 0); if (!block_id) { return base::unexpected( - Error{ErrorCode::kDbInitError, "Wrong database format"}); + Error{ErrorCode::kConsistencyError, "Wrong block id format"}); } spend.block_id = block_id.value(); auto nullifier_blob = resolve_note_spents.ColumnBlob(1); if (nullifier_blob.size() != kOrchardNullifierSize) { return base::unexpected( - Error{ErrorCode::kConsistencyError, "Consistency error"}); + Error{ErrorCode::kConsistencyError, "Wrong nullifier size"}); } base::span(spend.nullifier).copy_from(nullifier_blob); result.push_back(std::move(spend)); @@ -445,9 +475,12 @@ ZCashOrchardStorage::GetNullifiers(const mojom::AccountIdPtr& account_id) { return base::ok(std::move(result)); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetSpendableNotes(const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetSpendableNotes(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -499,25 +532,35 @@ ZCashOrchardStorage::GetSpendableNotes(const mojom::AccountIdPtr& account_id) { note.addr = **addr; result.push_back(std::move(note)); } + + if (!resolve_unspent_notes.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } + return base::ok(std::move(result)); } -std::optional ZCashOrchardStorage::UpdateNotes( - const mojom::AccountIdPtr& account_id, - const std::vector& found_notes, - const std::vector& found_nullifiers, - const uint32_t latest_scanned_block, - const std::string& latest_scanned_block_hash) { +base::expected +OrchardStorage::UpdateNotes(const mojom::AccountIdPtr& account_id, + base::span found_notes, + base::span found_nullifiers, + const uint32_t latest_scanned_block, + const std::string& latest_scanned_block_hash) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); CHECK(account_id); if (!EnsureDbInit()) { - return Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}; + return base::unexpected( + Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}); } sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } // Insert found notes to the notes table. @@ -540,8 +583,8 @@ std::optional ZCashOrchardStorage::UpdateNotes( statement_populate_notes.BindBlob(7, note.addr); if (!statement_populate_notes.Run()) { - return Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } } @@ -557,8 +600,8 @@ std::optional ZCashOrchardStorage::UpdateNotes( statement_populate_spent_notes.BindInt64(1, spent.block_id); statement_populate_spent_notes.BindBlob(2, spent.nullifier); if (!statement_populate_spent_notes.Run()) { - return Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } } @@ -574,22 +617,24 @@ std::optional ZCashOrchardStorage::UpdateNotes( statement_update_account_meta.BindString(1, latest_scanned_block_hash); statement_update_account_meta.BindString(2, account_id->unique_key); if (!statement_update_account_meta.Run()) { - return Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (!transaction.Commit()) { - return Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}; + return base::unexpected(Error{ErrorCode::kFailedToCommitTransaction, + database_.GetErrorMessage()}); } - return std::nullopt; + return base::ok(Result::kSuccess); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetLatestShardIndex( - const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetLatestShardIndex(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -603,24 +648,29 @@ ZCashOrchardStorage::GetLatestShardIndex( "WHERE account_id = ?;")); resolve_max_shard_id.BindString(0, account_id->unique_key); - if (resolve_max_shard_id.Step()) { - if (resolve_max_shard_id.GetColumnType(0) == sql::ColumnType::kNull) { - return base::ok(std::nullopt); - } - auto shard_index = ReadUint32(resolve_max_shard_id, 0); - if (!shard_index) { - return base::unexpected( - Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}); - } - return base::ok(shard_index.value()); + if (!resolve_max_shard_id.Step()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } - return base::ok(std::nullopt); + if (resolve_max_shard_id.GetColumnType(0) == sql::ColumnType::kNull) { + return base::ok(std::nullopt); + } + + auto shard_index = ReadUint32(resolve_max_shard_id, 0); + if (!shard_index) { + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong shard index format"}); + } + return base::ok(shard_index.value()); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetCap(const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetCap(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -642,15 +692,16 @@ ZCashOrchardStorage::GetCap(const mojom::AccountIdPtr& account_id) { return base::ok(std::nullopt); } - OrchardShardTreeCap result = base::ToVector(resolve_cap.ColumnBlob(0)); - - return base::ok(std::move(result)); + return base::ok(base::ToVector(resolve_cap.ColumnBlob(0))); } -base::expected ZCashOrchardStorage::PutCap( - const mojom::AccountIdPtr& account_id, - OrchardShardTreeCap cap) { +base::expected +OrchardStorage::PutCap(const mojom::AccountIdPtr& account_id, + const OrchardShardTreeCap& cap) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -659,13 +710,12 @@ base::expected ZCashOrchardStorage::PutCap( sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } auto existing_cap = GetCap(account_id); - if (!existing_cap.has_value()) { - return base::unexpected(existing_cap.error()); - } + RETURN_IF_ERROR(existing_cap); sql::Statement stmnt; if (!existing_cap.value()) { @@ -694,15 +744,18 @@ base::expected ZCashOrchardStorage::PutCap( database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected -ZCashOrchardStorage::UpdateSubtreeRoots( +base::expected +OrchardStorage::UpdateSubtreeRoots( const mojom::AccountIdPtr& account_id, uint32_t start_index, - std::vector roots) { + const std::vector& roots) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -711,7 +764,8 @@ ZCashOrchardStorage::UpdateSubtreeRoots( sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } sql::Statement statement_populate_roots(database_.GetCachedStatement( @@ -734,7 +788,8 @@ ZCashOrchardStorage::UpdateSubtreeRoots( for (size_t i = 0; i < roots.size(); i++) { if (!roots[i] || roots[i]->complete_block_hash.size() != kOrchardCompleteBlockHashSize) { - return base::unexpected(Error{ErrorCode::kInternalError, "Wrong data"}); + return base::unexpected( + Error{ErrorCode::kInternalError, "Complete block hash differs"}); } statement_populate_roots.Reset(true); @@ -767,13 +822,16 @@ ZCashOrchardStorage::UpdateSubtreeRoots( database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected -ZCashOrchardStorage::TruncateShards(const mojom::AccountIdPtr& account_id, - uint32_t shard_index) { +base::expected +OrchardStorage::TruncateShards(const mojom::AccountIdPtr& account_id, + uint32_t shard_index) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -786,30 +844,33 @@ ZCashOrchardStorage::TruncateShards(const mojom::AccountIdPtr& account_id, Error{ErrorCode::kDbInitError, database_.GetErrorMessage()}); } - sql::Statement remove_checkpoint_by_id(database_.GetCachedStatement( + sql::Statement remove_shard_by_id(database_.GetCachedStatement( SQL_FROM_HERE, "DELETE FROM " kShardTree " " "WHERE shard_index >= ? AND account_id = ?;")); - remove_checkpoint_by_id.BindInt64(0, shard_index); - remove_checkpoint_by_id.BindString(1, account_id->unique_key); + remove_shard_by_id.BindInt64(0, shard_index); + remove_shard_by_id.BindString(1, account_id->unique_key); - if (!remove_checkpoint_by_id.Run()) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + if (!remove_shard_by_id.Run()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (!transaction.Commit()) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + return base::unexpected(Error{ErrorCode::kFailedToCommitTransaction, database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected ZCashOrchardStorage::PutShard( - const mojom::AccountIdPtr& account_id, - OrchardShard shard) { +base::expected +OrchardStorage::PutShard(const mojom::AccountIdPtr& account_id, + const OrchardShard& shard) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -817,9 +878,7 @@ base::expected ZCashOrchardStorage::PutShard( } auto existing_shard = GetShard(account_id, shard.address); - if (!existing_shard.has_value()) { - return base::unexpected(existing_shard.error()); - } + RETURN_IF_ERROR(existing_shard); sql::Transaction transaction(&database_); if (!transaction.Begin()) { @@ -876,13 +935,16 @@ base::expected ZCashOrchardStorage::PutShard( database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetShard(const mojom::AccountIdPtr& account_id, - OrchardShardAddress address) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetShard(const mojom::AccountIdPtr& account_id, + const OrchardShardAddress& address) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -896,33 +958,35 @@ ZCashOrchardStorage::GetShard(const mojom::AccountIdPtr& account_id, resolve_shard_statement.BindString(0, account_id->unique_key); resolve_shard_statement.BindInt64(1, address.index); - if (!resolve_shard_statement.Step()) { - if (!resolve_shard_statement.Succeeded()) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}); + if (resolve_shard_statement.Step()) { + auto hash = ReadRootHash(resolve_shard_statement, 0); + if (!hash.has_value()) { + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong root hash format"}); } - return base::ok(std::nullopt); + return base::ok( + OrchardShard(address, hash.value(), + base::ToVector(resolve_shard_statement.ColumnBlob(1)))); } - auto hash = ReadRootHash(resolve_shard_statement, 0); - if (!hash.has_value()) { - return base::unexpected(Error{ErrorCode::kDbInitError, hash.error()}); + if (!resolve_shard_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } - auto shard = OrchardShard(address, hash.value(), std::vector()); - - shard.shard_data = base::ToVector(resolve_shard_statement.ColumnBlob(1)); - - return base::ok(std::move(shard)); + return base::ok(std::nullopt); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::LastShard(const mojom::AccountIdPtr& account_id, - uint8_t shard_height) { +base::expected, OrchardStorage::Error> +OrchardStorage::LastShard(const mojom::AccountIdPtr& account_id, + uint8_t shard_height) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); + auto shard_index = GetLatestShardIndex(account_id); - if (!shard_index.has_value()) { - return base::unexpected(shard_index.error()); - } + RETURN_IF_ERROR(shard_index); if (!shard_index.value()) { return base::ok(std::nullopt); @@ -932,10 +996,13 @@ ZCashOrchardStorage::LastShard(const mojom::AccountIdPtr& account_id, shard_index.value().value()}); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetShardRoots(const mojom::AccountIdPtr& account_id, - uint8_t shard_level) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetShardRoots(const mojom::AccountIdPtr& account_id, + uint8_t shard_level) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -953,23 +1020,28 @@ ZCashOrchardStorage::GetShardRoots(const mojom::AccountIdPtr& account_id, while (resolve_shards_statement.Step()) { auto shard_index = ReadUint32(resolve_shards_statement, 0); if (!shard_index) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, ""}); + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong shard index format"}); } result.push_back(OrchardShardAddress{shard_level, shard_index.value()}); } - if (!resolve_shards_statement.is_valid()) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, ""}); + if (!resolve_shards_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } return base::ok(std::move(result)); } -base::expected -ZCashOrchardStorage::AddCheckpoint(const mojom::AccountIdPtr& account_id, - uint32_t checkpoint_id, - OrchardCheckpoint checkpoint) { +base::expected +OrchardStorage::AddCheckpoint(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id, + const OrchardCheckpoint& checkpoint) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -994,7 +1066,8 @@ ZCashOrchardStorage::AddCheckpoint(const mojom::AccountIdPtr& account_id, sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } // Checkpoint with same id didn't exist. @@ -1035,21 +1108,14 @@ ZCashOrchardStorage::AddCheckpoint(const mojom::AccountIdPtr& account_id, // Existing checkpoint should be the same. if (extant_tree_state_position.value() != checkpoint.tree_state_position) { return base::unexpected( - Error{ErrorCode::kConsistencyError, "Consistency error"}); + Error{ErrorCode::kConsistencyError, "Tree state position differs"}); } auto marks_removed_result = GetMarksRemoved(account_id, checkpoint_id); - if (!marks_removed_result.has_value()) { - return base::unexpected(marks_removed_result.error()); - } - - if (!marks_removed_result.value()) { - return base::unexpected( - Error{ErrorCode::kConsistencyError, "Consistency error"}); - } + RETURN_IF_ERROR(marks_removed_result); - if (marks_removed_result.value().value() != checkpoint.marks_removed) { + if (marks_removed_result.value() != checkpoint.marks_removed) { return base::unexpected( - Error{ErrorCode::kConsistencyError, "Consistency error"}); + Error{ErrorCode::kConsistencyError, "Marks removed differs"}); } } @@ -1058,12 +1124,39 @@ ZCashOrchardStorage::AddCheckpoint(const mojom::AccountIdPtr& account_id, database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); +} + +base::expected +OrchardStorage::UpdateCheckpoint(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id, + const OrchardCheckpoint& checkpoint) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); + auto get_checkpoint_result = GetCheckpoint(account_id, checkpoint_id); + RETURN_IF_ERROR(get_checkpoint_result); + if (!get_checkpoint_result.value()) { + return base::ok(Result::kNone); + } + + auto remove_result = RemoveCheckpoint(account_id, checkpoint_id); + RETURN_IF_ERROR(remove_result); + if (remove_result.value() != Result::kSuccess) { + return base::ok(Result::kNone); + } + + auto add_result = AddCheckpoint(account_id, checkpoint_id, checkpoint); + RETURN_IF_ERROR(add_result); + + return *add_result; } -base::expected -ZCashOrchardStorage::CheckpointCount(const mojom::AccountIdPtr& account_id) { +base::expected OrchardStorage::CheckpointCount( + const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1075,23 +1168,25 @@ ZCashOrchardStorage::CheckpointCount(const mojom::AccountIdPtr& account_id) { "SELECT COUNT(*) FROM " kShardTreeCheckpoints " WHERE account_id = ?;")); resolve_checkpoints_count.BindString(0, account_id->unique_key); if (!resolve_checkpoints_count.Step()) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } auto value = ReadUint32(resolve_checkpoints_count, 0); - if (!value) { return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + Error{ErrorCode::kConsistencyError, "Wrong checkpoint count"}); } return *value; } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::MinCheckpointId(const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::MinCheckpointId(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1105,24 +1200,28 @@ ZCashOrchardStorage::MinCheckpointId(const mojom::AccountIdPtr& account_id) { resolve_min_checkpoint_id.BindString(0, account_id->unique_key); if (!resolve_min_checkpoint_id.Step()) { - if (!resolve_min_checkpoint_id.Succeeded()) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); - } else { - return base::ok(std::nullopt); - } + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (resolve_min_checkpoint_id.GetColumnType(0) == sql::ColumnType::kNull) { return base::ok(std::nullopt); - } else { - return ReadUint32(resolve_min_checkpoint_id, 0); } + + auto checkpoint_id = ReadUint32(resolve_min_checkpoint_id, 0); + if (!checkpoint_id) { + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong checkpoint id format"}); + } + return *checkpoint_id; } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::MaxCheckpointId(const mojom::AccountIdPtr& account_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::MaxCheckpointId(const mojom::AccountIdPtr& account_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1135,25 +1234,29 @@ ZCashOrchardStorage::MaxCheckpointId(const mojom::AccountIdPtr& account_id) { resolve_max_checkpoint_id.BindString(0, account_id->unique_key); if (!resolve_max_checkpoint_id.Step()) { - if (!resolve_max_checkpoint_id.Succeeded()) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); - } else { - return base::ok(std::nullopt); - } + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (resolve_max_checkpoint_id.GetColumnType(0) == sql::ColumnType::kNull) { return base::ok(std::nullopt); - } else { - return ReadUint32(resolve_max_checkpoint_id, 0); } + + auto checkpoint_id = ReadUint32(resolve_max_checkpoint_id, 0); + if (!checkpoint_id) { + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong checkpoint id format"}); + } + return *checkpoint_id; } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetCheckpointAtDepth(const mojom::AccountIdPtr& account_id, - uint32_t depth) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetCheckpointAtDepth(const mojom::AccountIdPtr& account_id, + uint32_t depth) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1162,7 +1265,7 @@ ZCashOrchardStorage::GetCheckpointAtDepth(const mojom::AccountIdPtr& account_id, sql::Statement get_checkpoint_at_depth_statement( database_.GetCachedStatement(SQL_FROM_HERE, - "SELECT checkpoint_id, position " + "SELECT checkpoint_id " "FROM " kShardTreeCheckpoints " " "WHERE account_id = ? " "ORDER BY checkpoint_id DESC " @@ -1172,27 +1275,30 @@ ZCashOrchardStorage::GetCheckpointAtDepth(const mojom::AccountIdPtr& account_id, get_checkpoint_at_depth_statement.BindString(0, account_id->unique_key); get_checkpoint_at_depth_statement.BindInt64(1, depth); - if (!get_checkpoint_at_depth_statement.Step()) { - if (!get_checkpoint_at_depth_statement.Succeeded()) { + if (get_checkpoint_at_depth_statement.Step()) { + auto value = ReadUint32(get_checkpoint_at_depth_statement, 0); + if (!value) { return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + Error{ErrorCode::kConsistencyError, "Wrong checkpoint id format"}); } - return base::ok(std::nullopt); + return *value; } - auto value = ReadUint32(get_checkpoint_at_depth_statement, 0); - - if (!value) { - return base::ok(std::nullopt); + if (!get_checkpoint_at_depth_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } - return *value; + return base::ok(std::nullopt); } -base::expected>, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetMarksRemoved(const mojom::AccountIdPtr& account_id, - uint32_t checkpoint_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetMarksRemoved(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1212,19 +1318,27 @@ ZCashOrchardStorage::GetMarksRemoved(const mojom::AccountIdPtr& account_id, while (get_marks_removed_statement.Step()) { auto position = ReadUint32(get_marks_removed_statement, 0); if (!position) { - return base::unexpected(Error{ErrorCode::kDbInitError, "Format error"}); + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong position format"}); } result.push_back(*position); } + if (!get_marks_removed_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } + return base::ok(std::move(result)); } -base::expected, - ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetCheckpoint(const mojom::AccountIdPtr& account_id, - uint32_t checkpoint_id) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetCheckpoint(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1241,13 +1355,17 @@ ZCashOrchardStorage::GetCheckpoint(const mojom::AccountIdPtr& account_id, get_checkpoint_statement.BindInt64(0, checkpoint_id); get_checkpoint_statement.BindString(1, account_id->unique_key); if (!get_checkpoint_statement.Step()) { + if (!get_checkpoint_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } return base::ok(std::nullopt); } auto checkpoint_position = ReadCheckpointTreeState(get_checkpoint_statement, 0); if (!checkpoint_position.has_value()) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}); + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong position format"}); } sql::Statement marks_removed_statement(database_.GetCachedStatement( @@ -1266,20 +1384,28 @@ ZCashOrchardStorage::GetCheckpoint(const mojom::AccountIdPtr& account_id, if (position) { positions.push_back(*position); } else { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, - database_.GetErrorMessage()}); + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong position format"}); } } + if (!marks_removed_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } + return OrchardCheckpointBundle{ checkpoint_id, OrchardCheckpoint{*checkpoint_position, std::move(positions)}}; } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetCheckpoints(const mojom::AccountIdPtr& account_id, - size_t limit) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetCheckpoints(const mojom::AccountIdPtr& account_id, + size_t limit) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1300,36 +1426,41 @@ ZCashOrchardStorage::GetCheckpoints(const mojom::AccountIdPtr& account_id, std::vector checkpoints; while (get_checkpoints_statement.Step()) { auto checkpoint_id = ReadUint32(get_checkpoints_statement, 0); - auto checkpoint_position = - ReadCheckpointTreeState(get_checkpoints_statement, 1); if (!checkpoint_id) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, ""}); + return base::unexpected( + Error{ErrorCode::kConsistencyError, "Wrong checkpoint id format"}); } + + auto checkpoint_position = + ReadCheckpointTreeState(get_checkpoints_statement, 1); if (!checkpoint_position.has_value()) { - return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, ""}); + return base::unexpected(Error{ErrorCode::kConsistencyError, + "Wrong checkpoint position format"}); } auto found_marks_removed = GetMarksRemoved(account_id, *checkpoint_id); - if (!found_marks_removed.has_value()) { - return base::unexpected(found_marks_removed.error()); - } - std::vector marks_removed; - if (found_marks_removed.value()) { - marks_removed = **found_marks_removed; - } - + RETURN_IF_ERROR(found_marks_removed); checkpoints.push_back(OrchardCheckpointBundle{ - *checkpoint_id, OrchardCheckpoint(checkpoint_position.value(), - std::move(marks_removed))}); + *checkpoint_id, + OrchardCheckpoint(checkpoint_position.value(), + std::move(found_marks_removed.value()))}); } + + if (!get_checkpoints_statement.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); + } + return base::ok(std::move(checkpoints)); } -base::expected, ZCashOrchardStorage::Error> -ZCashOrchardStorage::GetMaxCheckpointedHeight( - const mojom::AccountIdPtr& account_id, - uint32_t chain_tip_height, - uint32_t min_confirmations) { +base::expected, OrchardStorage::Error> +OrchardStorage::GetMaxCheckpointedHeight(const mojom::AccountIdPtr& account_id, + uint32_t chain_tip_height, + uint32_t min_confirmations) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1348,29 +1479,30 @@ ZCashOrchardStorage::GetMaxCheckpointedHeight( get_max_checkpointed_height.BindInt64(0, max_checkpointed_height); get_max_checkpointed_height.BindString(1, account_id->unique_key); - if (!get_max_checkpointed_height.Step()) { - if (!get_max_checkpointed_height.Succeeded()) { + if (get_max_checkpointed_height.Step()) { + auto value = ReadUint32(get_max_checkpointed_height, 0); + if (!value) { return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); - } else { - return base::ok(std::nullopt); + Error{ErrorCode::kConsistencyError, "Wrong checkpoint height"}); } + return base::ok(*value); } - auto value = ReadUint32(get_max_checkpointed_height, 0); - - if (!value) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + if (!get_max_checkpointed_height.Succeeded()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } - return base::ok(*value); + return base::ok(std::nullopt); } -base::expected -ZCashOrchardStorage::RemoveCheckpoint(const mojom::AccountIdPtr& account_id, - uint32_t checkpoint_id) { +base::expected +OrchardStorage::RemoveCheckpoint(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1378,26 +1510,24 @@ ZCashOrchardStorage::RemoveCheckpoint(const mojom::AccountIdPtr& account_id, } auto existing_checkpoint = GetCheckpoint(account_id, checkpoint_id); - if (!existing_checkpoint.has_value()) { - return base::unexpected(existing_checkpoint.error()); - } + RETURN_IF_ERROR(existing_checkpoint); if (!existing_checkpoint.value()) { - return base::ok(false); + return base::ok(Result::kNone); + } + + sql::Transaction transaction(&database_); + if (!transaction.Begin()) { + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } sql::Statement remove_checkpoint_by_id(database_.GetCachedStatement( SQL_FROM_HERE, "DELETE FROM " kShardTreeCheckpoints " " "WHERE checkpoint_id = ? AND account_id= ?;")); - remove_checkpoint_by_id.BindInt64(0, checkpoint_id); remove_checkpoint_by_id.BindString(1, account_id->unique_key); - sql::Transaction transaction(&database_); - if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); - } - if (!remove_checkpoint_by_id.Run()) { return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, database_.GetErrorMessage()}); @@ -1408,13 +1538,16 @@ ZCashOrchardStorage::RemoveCheckpoint(const mojom::AccountIdPtr& account_id, database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } -base::expected -ZCashOrchardStorage::TruncateCheckpoints(const mojom::AccountIdPtr& account_id, - uint32_t checkpoint_id) { +base::expected +OrchardStorage::TruncateCheckpoints(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + CHECK(account_id); if (!EnsureDbInit()) { return base::unexpected( @@ -1423,7 +1556,8 @@ ZCashOrchardStorage::TruncateCheckpoints(const mojom::AccountIdPtr& account_id, sql::Transaction transaction(&database_); if (!transaction.Begin()) { - return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, ""}); + return base::unexpected(Error{ErrorCode::kFailedToCreateTransaction, + database_.GetErrorMessage()}); } sql::Statement truncate_checkpoints(database_.GetCachedStatement( @@ -1434,8 +1568,8 @@ ZCashOrchardStorage::TruncateCheckpoints(const mojom::AccountIdPtr& account_id, truncate_checkpoints.BindString(1, account_id->unique_key); if (!truncate_checkpoints.Run()) { - return base::unexpected( - Error{ErrorCode::kNoCheckpoints, database_.GetErrorMessage()}); + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_.GetErrorMessage()}); } if (!transaction.Commit()) { @@ -1443,7 +1577,7 @@ ZCashOrchardStorage::TruncateCheckpoints(const mojom::AccountIdPtr& account_id, database_.GetErrorMessage()}); } - return base::ok(true); + return base::ok(Result::kSuccess); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_storage.h b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h similarity index 73% rename from components/brave_wallet/browser/zcash/zcash_orchard_storage.h rename to components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h index fc0786660926..d2aa0e865110 100644 --- a/components/brave_wallet/browser/zcash/zcash_orchard_storage.h +++ b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h @@ -3,17 +3,19 @@ * 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_ORCHARD_STORAGE_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_ORCHARD_STORAGE_H_ +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_STORAGE_ORCHARD_STORAGE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_STORAGE_ORCHARD_STORAGE_H_ #include #include #include +#include "base/containers/span.h" #include "base/files/file_path.h" #include "base/sequence_checker.h" #include "base/thread_annotations.h" #include "base/types/expected.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_wallet/common/zcash_utils.h" #include "brave/components/services/brave_wallet/public/mojom/zcash_decoder.mojom.h" @@ -51,7 +53,7 @@ ReadSizedBlob(sql::Statement& statement, size_t position) { // Implements SQLite database to store found incoming notes, // nullifiers, wallet zcash accounts and commitment trees. -class ZCashOrchardStorage { +class OrchardStorage { public: struct AccountMeta { AccountMeta(); @@ -65,50 +67,51 @@ class ZCashOrchardStorage { enum class ErrorCode { kDbInitError, - kAccountNotFound, kFailedToExecuteStatement, kFailedToCreateTransaction, kFailedToCommitTransaction, kInternalError, - kNoCheckpoints, kConsistencyError }; + enum class Result { kSuccess, kNone }; + struct Error { ErrorCode error_code; std::string message; }; - explicit ZCashOrchardStorage(base::FilePath path_to_database); - ~ZCashOrchardStorage(); + explicit OrchardStorage(const base::FilePath& path_to_database); + ~OrchardStorage(); base::expected RegisterAccount( const mojom::AccountIdPtr& account_id, uint32_t account_birthday_block); - base::expected GetAccountMeta( + base::expected, Error> GetAccountMeta( const mojom::AccountIdPtr& account_id); - base::expected ResetAccountSyncState( + base::expected ResetAccountSyncState( const mojom::AccountIdPtr& account_id); // Removes database records which are under effect of chain reorg // Removes spendable notes and nullifiers with block_height > reorg_block // Updates account's last scanned block to chain reorg block - std::optional HandleChainReorg(const mojom::AccountIdPtr& account_id, - uint32_t reorg_block_id, - const std::string& reorg_block_hash); + base::expected HandleChainReorg( + const mojom::AccountIdPtr& account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash); // Calculates a list of discovered spendable notes that don't have nullifiers // in the blockchain - base::expected, ZCashOrchardStorage::Error> + base::expected, OrchardStorage::Error> GetSpendableNotes(const mojom::AccountIdPtr& account_id); // Returns a list of discovered nullifiers base::expected, Error> GetNullifiers( const mojom::AccountIdPtr& account_id); // Updates database with discovered spendable notes and nullifiers // Also updates account info with latest scanned block info - std::optional UpdateNotes( + base::expected UpdateNotes( const mojom::AccountIdPtr& account_id, - const std::vector& notes_to_add, - const std::vector& found_nullifiers, + base::span notes_to_add, + base::span found_nullifiers, const uint32_t latest_scanned_block, const std::string& latest_scanned_block_hash); void ResetDatabase(); @@ -116,19 +119,19 @@ class ZCashOrchardStorage { // Shard tree base::expected, Error> GetCap( const mojom::AccountIdPtr& account_id); - base::expected PutCap(const mojom::AccountIdPtr& account_id, - OrchardShardTreeCap cap); + base::expected PutCap(const mojom::AccountIdPtr& account_id, + const OrchardShardTreeCap& cap); - base::expected TruncateShards( + base::expected TruncateShards( const mojom::AccountIdPtr& account_id, uint32_t shard_index); base::expected, Error> GetLatestShardIndex( const mojom::AccountIdPtr& account_id); - base::expected PutShard(const mojom::AccountIdPtr& account_id, - OrchardShard shard); + base::expected PutShard(const mojom::AccountIdPtr& account_id, + const OrchardShard& shard); base::expected, Error> GetShard( const mojom::AccountIdPtr& account_id, - OrchardShardAddress address); + const OrchardShardAddress& address); base::expected, Error> LastShard( const mojom::AccountIdPtr& account_id, uint8_t shard_height); @@ -146,30 +149,34 @@ class ZCashOrchardStorage { const mojom::AccountIdPtr& account_id, uint32_t chain_tip_height, uint32_t min_confirmations); - base::expected RemoveCheckpoint( + base::expected RemoveCheckpoint( const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id); - base::expected TruncateCheckpoints( + base::expected TruncateCheckpoints( const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id); - base::expected AddCheckpoint( + base::expected AddCheckpoint( + const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id, + const OrchardCheckpoint& checkpoint); + base::expected UpdateCheckpoint( const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id, - OrchardCheckpoint checkpoint); + const OrchardCheckpoint& checkpoint); base::expected, Error> GetCheckpoints( const mojom::AccountIdPtr& account_id, size_t limit); base::expected, Error> GetCheckpoint( const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id); - base::expected>, Error> GetMarksRemoved( + base::expected, Error> GetMarksRemoved( const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id); - base::expected UpdateSubtreeRoots( + base::expected UpdateSubtreeRoots( const mojom::AccountIdPtr& account_id, uint32_t start_index, - std::vector roots); + const std::vector& roots); base::expected, Error> GetShardRoots( const mojom::AccountIdPtr& account_id, uint8_t shard_level); @@ -188,4 +195,4 @@ class ZCashOrchardStorage { } // namespace brave_wallet -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_ORCHARD_STORAGE_H_ +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_STORAGE_ORCHARD_STORAGE_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_storage_unittest.cc b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage_unittest.cc similarity index 59% rename from components/brave_wallet/browser/zcash/zcash_orchard_storage_unittest.cc rename to components/brave_wallet/browser/internal/orchard_storage/orchard_storage_unittest.cc index a18b6ff535b6..44d04281c202 100644 --- a/components/brave_wallet/browser/zcash/zcash_orchard_storage_unittest.cc +++ b/components/brave_wallet/browser/internal/orchard_storage/orchard_storage_unittest.cc @@ -3,7 +3,7 @@ * 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_orchard_storage.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" #include #include @@ -25,14 +25,14 @@ class OrchardStorageTest : public testing::Test { base::test::TaskEnvironment task_environment_; base::ScopedTempDir temp_dir_; - std::unique_ptr orchard_storage_; + std::unique_ptr orchard_storage_; }; void OrchardStorageTest::SetUp() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); base::FilePath db_path( temp_dir_.GetPath().Append(FILE_PATH_LITERAL("orchard.db"))); - orchard_storage_ = std::make_unique(db_path); + orchard_storage_ = std::make_unique(db_path); } TEST_F(OrchardStorageTest, AccountMeta) { @@ -45,7 +45,7 @@ TEST_F(OrchardStorageTest, AccountMeta) { { auto result = orchard_storage_->GetAccountMeta(account_id_1); - EXPECT_FALSE(result.has_value()); + EXPECT_FALSE(result.value()); } EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id_1, 100).has_value()); @@ -53,16 +53,16 @@ TEST_F(OrchardStorageTest, AccountMeta) { { auto result = orchard_storage_->GetAccountMeta(account_id_1); EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result->account_birthday, 100u); - EXPECT_FALSE(result->latest_scanned_block_id); - EXPECT_FALSE(result->latest_scanned_block_hash); + EXPECT_EQ(result.value()->account_birthday, 100u); + EXPECT_FALSE(result.value()->latest_scanned_block_id); + EXPECT_FALSE(result.value()->latest_scanned_block_hash); } { // Failed to insert same account EXPECT_EQ( orchard_storage_->RegisterAccount(account_id_1, 200).error().error_code, - ZCashOrchardStorage::ErrorCode::kFailedToExecuteStatement); + OrchardStorage::ErrorCode::kFailedToExecuteStatement); } // Insert second account @@ -70,9 +70,9 @@ TEST_F(OrchardStorageTest, AccountMeta) { { auto result = orchard_storage_->GetAccountMeta(account_id_2); EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result->account_birthday, 200u); - EXPECT_FALSE(result->latest_scanned_block_id); - EXPECT_FALSE(result->latest_scanned_block_hash); + EXPECT_EQ(result.value()->account_birthday, 200u); + EXPECT_FALSE(result.value()->latest_scanned_block_id); + EXPECT_FALSE(result.value()->latest_scanned_block_hash); } } @@ -93,7 +93,9 @@ TEST_F(OrchardStorageTest, PutDiscoveredNotes) { notes.push_back(GenerateMockOrchardNote(account_id_1, 101, 1)); notes.push_back(GenerateMockOrchardNote(account_id_1, 105, 2)); - orchard_storage_->UpdateNotes(account_id_1, notes, {}, 200, "hash200"); + EXPECT_TRUE( + orchard_storage_->UpdateNotes(account_id_1, notes, {}, 200, "hash200") + .has_value()); } // Update notes for account 2 @@ -103,7 +105,9 @@ TEST_F(OrchardStorageTest, PutDiscoveredNotes) { notes.push_back(GenerateMockOrchardNote(account_id_2, 115, 2)); notes.push_back(GenerateMockOrchardNote(account_id_2, 117, 3)); - orchard_storage_->UpdateNotes(account_id_2, notes, {}, 200, "hash200"); + EXPECT_TRUE( + orchard_storage_->UpdateNotes(account_id_2, notes, {}, 200, "hash200") + .has_value()); } // Check account_1 spendable notes @@ -142,7 +146,9 @@ TEST_F(OrchardStorageTest, PutDiscoveredNotes) { spends.push_back( OrchardNoteSpend{203, GenerateMockNullifier(account_id_1, 1)}); - orchard_storage_->UpdateNotes(account_id_1, notes, spends, 300, "hash300"); + EXPECT_TRUE(orchard_storage_ + ->UpdateNotes(account_id_1, notes, spends, 300, "hash300") + .has_value()); } // Update notes for account 2 @@ -157,7 +163,9 @@ TEST_F(OrchardStorageTest, PutDiscoveredNotes) { spends.push_back( OrchardNoteSpend{233, GenerateMockNullifier(account_id_2, 3)}); - orchard_storage_->UpdateNotes(account_id_2, notes, spends, 300, "hash300"); + EXPECT_TRUE(orchard_storage_ + ->UpdateNotes(account_id_2, notes, spends, 300, "hash300") + .has_value()); } // Check account_1 spendable notes @@ -187,14 +195,14 @@ TEST_F(OrchardStorageTest, PutDiscoveredNotes) { // Accounts meta updated { auto account_meta = orchard_storage_->GetAccountMeta(account_id_1); - EXPECT_EQ(account_meta->latest_scanned_block_id, 300u); - EXPECT_EQ(account_meta->latest_scanned_block_hash, "hash300"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id, 300u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash, "hash300"); } { auto account_meta = orchard_storage_->GetAccountMeta(account_id_2); - EXPECT_EQ(account_meta->latest_scanned_block_id, 300u); - EXPECT_EQ(account_meta->latest_scanned_block_hash, "hash300"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id, 300u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash, "hash300"); } } @@ -226,7 +234,9 @@ TEST_F(OrchardStorageTest, HandleChainReorg) { spends.push_back( OrchardNoteSpend{103, GenerateMockNullifier(account_id_1, 3)}); - orchard_storage_->UpdateNotes(account_id_1, notes, spends, 450, "hash450"); + EXPECT_TRUE(orchard_storage_ + ->UpdateNotes(account_id_1, notes, spends, 450, "hash450") + .has_value()); } // Update notes for account 2 @@ -245,13 +255,15 @@ TEST_F(OrchardStorageTest, HandleChainReorg) { spends.push_back( OrchardNoteSpend{333, GenerateMockNullifier(account_id_2, 3)}); - orchard_storage_->UpdateNotes(account_id_2, notes, spends, 500, "hash500"); + EXPECT_TRUE(orchard_storage_ + ->UpdateNotes(account_id_2, notes, spends, 500, "hash500") + .has_value()); } { auto account_meta = orchard_storage_->GetAccountMeta(account_id_2); - EXPECT_EQ(account_meta->latest_scanned_block_id, 500u); - EXPECT_EQ(account_meta->latest_scanned_block_hash, "hash500"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id, 500u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash, "hash500"); auto account_2_spendable_notes = orchard_storage_->GetSpendableNotes(account_id_2); EXPECT_EQ(2u, account_2_spendable_notes->size()); @@ -261,19 +273,20 @@ TEST_F(OrchardStorageTest, HandleChainReorg) { // Call handle chain reorg so notes and spends with index > 300 will be // deleted - orchard_storage_->HandleChainReorg(account_id_2, 300u, "hash300"); + EXPECT_TRUE(orchard_storage_->HandleChainReorg(account_id_2, 300u, "hash300") + .has_value()); { auto account_meta = orchard_storage_->GetAccountMeta(account_id_2); - EXPECT_EQ(account_meta->latest_scanned_block_id, 300u); - EXPECT_EQ(account_meta->latest_scanned_block_hash, "hash300"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id, 300u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash, "hash300"); } // Unused account is untouched { auto account_meta = orchard_storage_->GetAccountMeta(account_id_1); - EXPECT_EQ(account_meta->latest_scanned_block_id, 450u); - EXPECT_EQ(account_meta->latest_scanned_block_hash, "hash450"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id, 450u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash, "hash450"); } // Check account_1 spendable notes @@ -314,12 +327,13 @@ TEST_F(OrchardStorageTest, HandleChainReorg) { GenerateMockOrchardNote(account_id_2, 213, 3)); } - orchard_storage_->HandleChainReorg(account_id_1, 0u, "hash0"); + EXPECT_TRUE(orchard_storage_->HandleChainReorg(account_id_1, 0u, "hash0") + .has_value()); // Account 1 was reorged on 0 { auto account_meta = orchard_storage_->GetAccountMeta(account_id_1); - EXPECT_EQ(account_meta->latest_scanned_block_id.value(), 0u); - EXPECT_EQ(account_meta->latest_scanned_block_hash.value(), "hash0"); + EXPECT_EQ(account_meta.value()->latest_scanned_block_id.value(), 0u); + EXPECT_EQ(account_meta.value()->latest_scanned_block_hash.value(), "hash0"); } // Check account_1 spendable notes @@ -362,34 +376,31 @@ TEST_F(OrchardStorageTest, InsertSubtreeRoots_BlockHashConflict) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); std::vector level_1_roots; level_1_roots.push_back(CreateSubtreeRoot(9, 0)); level_1_roots.push_back(CreateSubtreeRoot(9, 0)); - EXPECT_FALSE( - orchard_storage_ - ->UpdateSubtreeRoots(account_id.Clone(), 0, std::move(level_1_roots)) - .has_value()); + EXPECT_FALSE(orchard_storage_ + ->UpdateSubtreeRoots(account_id, 0, std::move(level_1_roots)) + .has_value()); } TEST_F(OrchardStorageTest, InsertSubtreeRoots) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); { std::vector level_1_roots; for (uint32_t i = 0; i < 10; i++) { level_1_roots.push_back(CreateSubtreeRoot(9, i)); } - EXPECT_TRUE(orchard_storage_ - ->UpdateSubtreeRoots(account_id.Clone(), 0, - std::move(level_1_roots)) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_ + ->UpdateSubtreeRoots(account_id, 0, std::move(level_1_roots)) + .value()); } { @@ -397,7 +408,7 @@ TEST_F(OrchardStorageTest, InsertSubtreeRoots) { for (uint32_t i = 0; i < 10; i++) { level_1_addrs.push_back(OrchardShardAddress{9, i}); } - auto result = orchard_storage_->GetShardRoots(account_id.Clone(), 9); + auto result = orchard_storage_->GetShardRoots(account_id, 9); EXPECT_EQ(result.value(), level_1_addrs); } @@ -407,27 +418,27 @@ TEST_F(OrchardStorageTest, TruncateSubtreeRoots) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); { std::vector level_1_roots; for (int i = 0; i < 10; i++) { level_1_roots.push_back(CreateSubtreeRoot(1, i)); } - EXPECT_TRUE(orchard_storage_ - ->UpdateSubtreeRoots(account_id.Clone(), 0, - std::move(level_1_roots)) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_ + ->UpdateSubtreeRoots(account_id, 0, std::move(level_1_roots)) + .value()); } - EXPECT_TRUE(orchard_storage_->TruncateShards(account_id.Clone(), 5).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->TruncateShards(account_id, 5).value()); { std::vector addresses_after_truncate; for (uint32_t i = 0; i < 5; i++) { addresses_after_truncate.push_back(OrchardShardAddress{1, i}); } - auto result = orchard_storage_->GetShardRoots(account_id.Clone(), 1); + auto result = orchard_storage_->GetShardRoots(account_id, 1); EXPECT_EQ(result.value(), addresses_after_truncate); } } @@ -436,44 +447,43 @@ TEST_F(OrchardStorageTest, TruncateShards) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); { for (uint32_t i = 0; i < 10; i++) { - EXPECT_TRUE( - orchard_storage_->PutShard(account_id.Clone(), CreateShard(i, 1)) - .value()); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + orchard_storage_->PutShard(account_id, CreateShard(i, 1)).value()); } } - EXPECT_TRUE(orchard_storage_->TruncateShards(account_id.Clone(), 5).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->TruncateShards(account_id, 5).value()); + for (uint32_t i = 0; i < 5; i++) { - EXPECT_EQ(CreateShard(i, 1), - **(orchard_storage_->GetShard(account_id.Clone(), - OrchardShardAddress(1, i)))); + EXPECT_EQ(CreateShard(i, 1), **(orchard_storage_->GetShard( + account_id, OrchardShardAddress(1, i)))); } EXPECT_EQ(std::nullopt, *(orchard_storage_->GetShard( - account_id.Clone(), OrchardShardAddress(1, 6)))); + account_id, OrchardShardAddress(1, 6)))); } TEST_F(OrchardStorageTest, ShardOverridesSubtreeRoot) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); { std::vector level_1_roots; for (uint32_t i = 0; i < 10; i++) { level_1_roots.push_back(CreateSubtreeRoot(1, i)); } - EXPECT_TRUE(orchard_storage_ - ->UpdateSubtreeRoots(account_id.Clone(), 0, - std::move(level_1_roots)) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_ + ->UpdateSubtreeRoots(account_id, 0, std::move(level_1_roots)) + .value()); } // Update existing shard @@ -483,11 +493,11 @@ TEST_F(OrchardStorageTest, ShardOverridesSubtreeRoot) { new_shard.address.level = 1; new_shard.root_hash->fill(5); new_shard.shard_data = std::vector({5, 5, 5, 5}); - EXPECT_TRUE( - orchard_storage_->PutShard(account_id.Clone(), new_shard).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->PutShard(account_id, new_shard).value()); auto result = - orchard_storage_->GetShard(account_id.Clone(), OrchardShardAddress{1, 5}); + orchard_storage_->GetShard(account_id, OrchardShardAddress{1, 5}); EXPECT_EQ(*result.value(), new_shard); } @@ -495,27 +505,24 @@ TEST_F(OrchardStorageTest, InsertShards) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); EXPECT_EQ(std::nullopt, - orchard_storage_->GetLatestShardIndex(account_id.Clone()).value()); - EXPECT_EQ( - std::nullopt, - orchard_storage_->GetShard(account_id.Clone(), OrchardShardAddress{1, 0}) - .value()); + orchard_storage_->GetLatestShardIndex(account_id).value()); EXPECT_EQ(std::nullopt, - orchard_storage_->LastShard(account_id.Clone(), 1).value()); + orchard_storage_->GetShard(account_id, OrchardShardAddress{1, 0}) + .value()); + EXPECT_EQ(std::nullopt, orchard_storage_->LastShard(account_id, 1).value()); { std::vector level_1_roots; for (uint32_t i = 0; i < 10; i++) { level_1_roots.push_back(CreateSubtreeRoot(1, i)); } - EXPECT_TRUE(orchard_storage_ - ->UpdateSubtreeRoots(account_id.Clone(), 0, - std::move(level_1_roots)) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_ + ->UpdateSubtreeRoots(account_id, 0, std::move(level_1_roots)) + .value()); } OrchardShard new_shard; @@ -525,19 +532,19 @@ TEST_F(OrchardStorageTest, InsertShards) { new_shard.root_hash->fill(11); new_shard.shard_data = std::vector({1, 1, 1, 1}); - EXPECT_TRUE( - orchard_storage_->PutShard(account_id.Clone(), new_shard).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->PutShard(account_id, new_shard).value()); { - auto result = orchard_storage_->GetShard(account_id.Clone(), - OrchardShardAddress{1, 11}); + auto result = + orchard_storage_->GetShard(account_id, OrchardShardAddress{1, 11}); EXPECT_EQ(*result.value(), new_shard); } { for (uint32_t i = 0; i < 10; i++) { - auto result = orchard_storage_->GetShard(account_id.Clone(), - OrchardShardAddress{1, i}); + auto result = + orchard_storage_->GetShard(account_id, OrchardShardAddress{1, i}); auto root = CreateSubtreeRoot(1, i); EXPECT_EQ(std::vector(std::begin(*result.value()->root_hash), std::end(*result.value()->root_hash)), @@ -545,285 +552,277 @@ TEST_F(OrchardStorageTest, InsertShards) { } } - EXPECT_EQ(11u, orchard_storage_->GetLatestShardIndex(account_id.Clone()) - .value() - .value()); - EXPECT_EQ(new_shard, - orchard_storage_->LastShard(account_id.Clone(), 1).value()); + EXPECT_EQ(11u, + orchard_storage_->GetLatestShardIndex(account_id).value().value()); + EXPECT_EQ(new_shard, orchard_storage_->LastShard(account_id, 1).value()); } TEST_F(OrchardStorageTest, RemoveChekpoint) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); OrchardCheckpoint checkpoint2; checkpoint2.marks_removed = std::vector({4, 5, 6}); checkpoint2.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint2) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint2.Clone()) + .value()); - EXPECT_TRUE( - orchard_storage_->RemoveCheckpoint(account_id.Clone(), 1).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->RemoveCheckpoint(account_id, 1).value()); EXPECT_EQ(std::nullopt, - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value()); - EXPECT_EQ( - OrchardCheckpointBundle(2, checkpoint2), - orchard_storage_->GetCheckpoint(account_id.Clone(), 2).value().value()); + orchard_storage_->GetCheckpoint(account_id, 1).value()); + EXPECT_EQ(OrchardCheckpointBundle(2, checkpoint2.Clone()), + orchard_storage_->GetCheckpoint(account_id, 2).value().value()); // Unexisting checkpoint. - EXPECT_FALSE( - orchard_storage_->RemoveCheckpoint(account_id.Clone(), 3).value()); + EXPECT_EQ(OrchardStorage::Result::kNone, + orchard_storage_->RemoveCheckpoint(account_id, 3).value()); } TEST_F(OrchardStorageTest, CheckpointId) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); EXPECT_EQ(std::nullopt, - orchard_storage_->MinCheckpointId(account_id.Clone()).value()); + orchard_storage_->MinCheckpointId(account_id).value()); + EXPECT_EQ(std::nullopt, + orchard_storage_->MaxCheckpointId(account_id).value()); EXPECT_EQ(std::nullopt, - orchard_storage_->MaxCheckpointId(account_id.Clone()).value()); + orchard_storage_->GetMaxCheckpointedHeight(account_id, 100000, 0) + .value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); OrchardCheckpoint checkpoint2; checkpoint2.marks_removed = std::vector({1, 2, 3}); checkpoint2.tree_state_position = 2; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint2) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint2.Clone()) + .value()); OrchardCheckpoint checkpoint3; checkpoint3.marks_removed = std::vector({5}); checkpoint3.tree_state_position = 3; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint3) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint3.Clone()) + .value()); OrchardCheckpoint checkpoint4; checkpoint4.marks_removed = std::vector(); checkpoint4.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 4, checkpoint4) - .value()); - - EXPECT_EQ(1, orchard_storage_->MinCheckpointId(account_id.Clone()).value()); - EXPECT_EQ(4, orchard_storage_->MaxCheckpointId(account_id.Clone()).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 4, checkpoint4.Clone()) + .value()); + + EXPECT_EQ(1, orchard_storage_->MinCheckpointId(account_id).value()); + EXPECT_EQ(4, orchard_storage_->MaxCheckpointId(account_id).value()); + EXPECT_EQ(4, orchard_storage_->GetMaxCheckpointedHeight(account_id, 100000, 0) + .value()); + EXPECT_EQ( + 2, orchard_storage_->GetMaxCheckpointedHeight(account_id, 3, 0).value()); } TEST_F(OrchardStorageTest, CheckpointAtPosition) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); + + EXPECT_EQ(std::nullopt, + orchard_storage_->GetCheckpointAtDepth(account_id, 2).value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); OrchardCheckpoint checkpoint2; checkpoint2.marks_removed = std::vector({4, 5, 6}); checkpoint2.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint2) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint2.Clone()) + .value()); OrchardCheckpoint checkpoint3; checkpoint3.marks_removed = std::vector({7, 8, 9}); checkpoint3.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint3) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint3.Clone()) + .value()); - EXPECT_EQ(1u, orchard_storage_->GetCheckpointAtDepth(account_id.Clone(), 2) - .value() - .value()); EXPECT_EQ( - std::nullopt, - orchard_storage_->GetCheckpointAtDepth(account_id.Clone(), 5).value()); + 1u, + orchard_storage_->GetCheckpointAtDepth(account_id, 2).value().value()); + EXPECT_EQ(std::nullopt, + orchard_storage_->GetCheckpointAtDepth(account_id, 5).value()); } TEST_F(OrchardStorageTest, TruncateCheckpoints_OutOfBoundary) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); - EXPECT_TRUE( - orchard_storage_->TruncateCheckpoints(account_id.Clone(), 3).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->TruncateCheckpoints(account_id, 3).value()); - EXPECT_EQ( - OrchardCheckpointBundle(1, checkpoint1), - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(1, checkpoint1.Clone()), + orchard_storage_->GetCheckpoint(account_id, 1).value().value()); } TEST_F(OrchardStorageTest, TruncateCheckpoints) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); OrchardCheckpoint checkpoint2; checkpoint2.marks_removed = std::vector({1, 2, 3}); checkpoint2.tree_state_position = 2; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint2) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint2.Clone()) + .value()); OrchardCheckpoint checkpoint3; checkpoint3.marks_removed = std::vector({5}); checkpoint3.tree_state_position = 3; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint3) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint3.Clone()) + .value()); OrchardCheckpoint checkpoint4; checkpoint4.marks_removed = std::vector(); checkpoint4.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 4, checkpoint4) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 4, checkpoint4.Clone()) + .value()); - EXPECT_TRUE( - orchard_storage_->TruncateCheckpoints(account_id.Clone(), 3).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->TruncateCheckpoints(account_id, 3).value()); - EXPECT_EQ( - OrchardCheckpointBundle(1, checkpoint1), - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value().value()); - EXPECT_EQ( - OrchardCheckpointBundle(2, checkpoint2), - orchard_storage_->GetCheckpoint(account_id.Clone(), 2).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(1, checkpoint1.Clone()), + orchard_storage_->GetCheckpoint(account_id, 1).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(2, checkpoint2.Clone()), + orchard_storage_->GetCheckpoint(account_id, 2).value().value()); EXPECT_EQ(std::nullopt, - orchard_storage_->GetCheckpoint(account_id.Clone(), 3).value()); + orchard_storage_->GetCheckpoint(account_id, 3).value()); EXPECT_EQ(std::nullopt, - orchard_storage_->GetCheckpoint(account_id.Clone(), 4).value()); + orchard_storage_->GetCheckpoint(account_id, 4).value()); } TEST_F(OrchardStorageTest, AddCheckpoint) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); + EXPECT_EQ(std::nullopt, + orchard_storage_->GetCheckpoint(account_id, 1).value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); OrchardCheckpoint checkpoint2; checkpoint2.marks_removed = std::vector({4, 5, 6}); checkpoint2.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint2) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint2.Clone()) + .value()); OrchardCheckpoint checkpoint3; checkpoint3.marks_removed = std::vector(); checkpoint3.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint3) - .value()); - - EXPECT_EQ( - OrchardCheckpointBundle(1, checkpoint1), - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value().value()); - EXPECT_EQ( - OrchardCheckpointBundle(2, checkpoint2), - orchard_storage_->GetCheckpoint(account_id.Clone(), 2).value().value()); - EXPECT_EQ( - OrchardCheckpointBundle(3, checkpoint3), - orchard_storage_->GetCheckpoint(account_id.Clone(), 3).value().value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint3.Clone()) + .value()); + + EXPECT_EQ(OrchardCheckpointBundle(1, checkpoint1.Clone()), + orchard_storage_->GetCheckpoint(account_id, 1).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(2, checkpoint2.Clone()), + orchard_storage_->GetCheckpoint(account_id, 2).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(3, checkpoint3.Clone()), + orchard_storage_->GetCheckpoint(account_id, 3).value().value()); } TEST_F(OrchardStorageTest, AddSameCheckpoint) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); { OrchardCheckpoint checkpoint; checkpoint.marks_removed = std::vector({1, 2, 3}); checkpoint.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint) - .value()); - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint.Clone()) + .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint.Clone()) + .value()); - EXPECT_EQ( - OrchardCheckpointBundle(1, checkpoint), - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(1, checkpoint.Clone()), + orchard_storage_->GetCheckpoint(account_id, 1).value().value()); } { OrchardCheckpoint checkpoint; checkpoint.marks_removed = std::vector({1, 2, 3}); checkpoint.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint) - .value()); - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 2, checkpoint) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint.Clone()) + .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 2, checkpoint.Clone()) + .value()); - EXPECT_EQ( - OrchardCheckpointBundle(2, checkpoint), - orchard_storage_->GetCheckpoint(account_id.Clone(), 2).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(2, checkpoint.Clone()), + orchard_storage_->GetCheckpoint(account_id, 2).value().value()); } { OrchardCheckpoint checkpoint; checkpoint.marks_removed = std::vector(); checkpoint.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint) - .value()); - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint.Clone()) + .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint.Clone()) + .value()); - EXPECT_EQ( - OrchardCheckpointBundle(3, checkpoint), - orchard_storage_->GetCheckpoint(account_id.Clone(), 3).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(3, checkpoint.Clone()), + orchard_storage_->GetCheckpoint(account_id, 3).value().value()); } } @@ -831,49 +830,50 @@ TEST_F(OrchardStorageTest, AddChekpoint_ErrorOnConflict) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); OrchardCheckpoint checkpoint1; checkpoint1.marks_removed = std::vector({1, 2, 3}); checkpoint1.tree_state_position = 4; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 1, checkpoint1) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 1, checkpoint1.Clone()) + .value()); - OrchardCheckpoint checkpoint_different_marks_removed = checkpoint1; + OrchardCheckpoint checkpoint_different_marks_removed; + checkpoint_different_marks_removed.tree_state_position = + checkpoint1.tree_state_position; checkpoint_different_marks_removed.marks_removed = std::vector({1, 2}); EXPECT_FALSE(orchard_storage_ - ->AddCheckpoint(account_id.Clone(), 1, - checkpoint_different_marks_removed) + ->AddCheckpoint(account_id, 1, + checkpoint_different_marks_removed.Clone()) .has_value()); - OrchardCheckpoint checkpoint_different_position1 = checkpoint1; + OrchardCheckpoint checkpoint_different_position1; + checkpoint_different_position1.marks_removed = checkpoint1.marks_removed; checkpoint_different_position1.tree_state_position = 7; EXPECT_FALSE( orchard_storage_ - ->AddCheckpoint(account_id.Clone(), 1, checkpoint_different_position1) + ->AddCheckpoint(account_id, 1, checkpoint_different_position1.Clone()) .has_value()); - OrchardCheckpoint checkpoint_different_position2 = checkpoint1; + OrchardCheckpoint checkpoint_different_position2; checkpoint_different_position2.tree_state_position = std::nullopt; + checkpoint_different_position2.marks_removed = checkpoint1.marks_removed; EXPECT_FALSE( orchard_storage_ - ->AddCheckpoint(account_id.Clone(), 1, checkpoint_different_position2) + ->AddCheckpoint(account_id, 1, checkpoint_different_position2.Clone()) .has_value()); - EXPECT_EQ( - OrchardCheckpointBundle(1, checkpoint1), - orchard_storage_->GetCheckpoint(account_id.Clone(), 1).value().value()); + EXPECT_EQ(OrchardCheckpointBundle(1, checkpoint1.Clone()), + orchard_storage_->GetCheckpoint(account_id, 1).value().value()); } TEST_F(OrchardStorageTest, ResetAccountSyncState) { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, mojom::KeyringId::kZCashMainnet, mojom::AccountKind::kDerived, 0); - EXPECT_TRUE( - orchard_storage_->RegisterAccount(account_id.Clone(), 100).has_value()); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); // Update notes for account 1 { @@ -888,36 +888,78 @@ TEST_F(OrchardStorageTest, ResetAccountSyncState) { spends.push_back( OrchardNoteSpend{333, GenerateMockNullifier(account_id, 3)}); - orchard_storage_->UpdateNotes(account_id.Clone(), notes, spends, 200, - "hash200"); + EXPECT_TRUE( + orchard_storage_->UpdateNotes(account_id, notes, spends, 200, "hash200") + .has_value()); } { OrchardCheckpoint checkpoint; checkpoint.marks_removed = std::vector(); checkpoint.tree_state_position = std::nullopt; - EXPECT_TRUE( - orchard_storage_->AddCheckpoint(account_id.Clone(), 3, checkpoint) - .value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint.Clone()) + .value()); } - EXPECT_TRUE( - orchard_storage_->ResetAccountSyncState(account_id.Clone()).value()); + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->ResetAccountSyncState(account_id).value()); { - auto account_meta = orchard_storage_->GetAccountMeta(account_id.Clone()); - EXPECT_FALSE(account_meta->latest_scanned_block_id); - EXPECT_FALSE(account_meta->latest_scanned_block_hash); + auto account_meta = orchard_storage_->GetAccountMeta(account_id); + EXPECT_FALSE(account_meta.value()->latest_scanned_block_id); + EXPECT_FALSE(account_meta.value()->latest_scanned_block_hash); } - EXPECT_EQ( - 0u, - orchard_storage_->GetCheckpoints(account_id.Clone(), 100).value().size()); - EXPECT_EQ( - 0u, - orchard_storage_->GetSpendableNotes(account_id.Clone()).value().size()); EXPECT_EQ(0u, - orchard_storage_->GetNullifiers(account_id.Clone()).value().size()); + orchard_storage_->GetCheckpoints(account_id, 100).value().size()); + EXPECT_EQ(0u, orchard_storage_->GetSpendableNotes(account_id).value().size()); + EXPECT_EQ(0u, orchard_storage_->GetNullifiers(account_id).value().size()); +} + +TEST_F(OrchardStorageTest, UpdateCheckpoint) { + auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, + mojom::KeyringId::kZCashMainnet, + mojom::AccountKind::kDerived, 0); + EXPECT_TRUE(orchard_storage_->RegisterAccount(account_id, 100).has_value()); + + // Create a single checkpoint. + { + OrchardCheckpoint checkpoint; + checkpoint.marks_removed = std::vector(); + checkpoint.tree_state_position = std::nullopt; + EXPECT_EQ(OrchardStorage::Result::kSuccess, + orchard_storage_->AddCheckpoint(account_id, 3, checkpoint.Clone()) + .value()); + } + + // Update existing checkpoint. + { + OrchardCheckpoint checkpoint; + checkpoint.marks_removed = std::vector({1}); + checkpoint.tree_state_position = 15; + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + orchard_storage_->UpdateCheckpoint(account_id, 3, checkpoint).value()); + auto get_checkpoint_result = orchard_storage_->GetCheckpoint(account_id, 3); + EXPECT_TRUE(get_checkpoint_result.has_value()); + OrchardCheckpointBundle checkpoint_bundle = + std::move(**get_checkpoint_result); + EXPECT_EQ(3u, checkpoint_bundle.checkpoint_id); + EXPECT_EQ(15, checkpoint_bundle.checkpoint.tree_state_position); + EXPECT_EQ(std::vector({1}), + checkpoint_bundle.checkpoint.marks_removed); + } + + // Update non-existing checkpoint. + { + OrchardCheckpoint checkpoint; + checkpoint.marks_removed = std::vector({1}); + checkpoint.tree_state_position = 15; + EXPECT_EQ( + OrchardStorage::Result::kNone, + orchard_storage_->UpdateCheckpoint(account_id, 5, checkpoint).value()); + } } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_sync_state.cc b/components/brave_wallet/browser/internal/orchard_sync_state.cc new file mode 100644 index 000000000000..7174b829adaa --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_sync_state.cc @@ -0,0 +1,160 @@ +/* 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/internal/orchard_sync_state.h" + +#include + +#include "base/check_is_test.h" +#include "base/containers/extend.h" +#include "base/types/expected_macros.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +OrchardSyncState::OrchardSyncState(const base::FilePath& path_to_database) + : storage_(OrchardStorage(path_to_database)) {} + +OrchardSyncState::~OrchardSyncState() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +orchard::OrchardShardTree& OrchardSyncState::GetOrCreateShardTree( + const mojom::AccountIdPtr& account_id) LIFETIME_BOUND { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (shard_trees_.find(account_id->unique_key) == shard_trees_.end()) { + shard_trees_[account_id->unique_key] = + orchard::OrchardShardTree::Create(storage_, account_id); + } + auto* manager = shard_trees_[account_id->unique_key].get(); + CHECK(manager); + return *manager; +} + +base::expected +OrchardSyncState::RegisterAccount(const mojom::AccountIdPtr& account_id, + uint64_t account_birthday_block) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.RegisterAccount(account_id, account_birthday_block); +} + +base::expected, + OrchardStorage::Error> +OrchardSyncState::GetAccountMeta(const mojom::AccountIdPtr& account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.GetAccountMeta(account_id); +} + +base::expected +OrchardSyncState::HandleChainReorg(const mojom::AccountIdPtr& account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.HandleChainReorg(account_id, reorg_block_id, + reorg_block_hash); +} + +base::expected, OrchardStorage::Error> +OrchardSyncState::GetSpendableNotes(const mojom::AccountIdPtr& account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.GetSpendableNotes(account_id); +} + +base::expected, OrchardStorage::Error> +OrchardSyncState::GetNullifiers(const mojom::AccountIdPtr& account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.GetNullifiers(account_id); +} + +base::expected +OrchardSyncState::ApplyScanResults( + const mojom::AccountIdPtr& account_id, + OrchardBlockScanner::Result block_scanner_results, + const uint32_t latest_scanned_block, + const std::string& latest_scanned_block_hash) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto existing_notes = storage_.GetSpendableNotes(account_id); + RETURN_IF_ERROR(existing_notes); + + std::vector notes_to_add = + block_scanner_results.discovered_notes; + base::Extend(existing_notes.value(), notes_to_add); + + std::vector nf_to_add; + + for (const auto& nf : block_scanner_results.found_spends) { + if (base::ranges::find_if(existing_notes.value(), [&nf](const auto& v) { + return v.nullifier == nf.nullifier; + }) != existing_notes.value().end()) { + nf_to_add.push_back(nf); + } + } + + if (!GetOrCreateShardTree(account_id) + .ApplyScanResults(std::move(block_scanner_results.scanned_blocks))) { + return base::unexpected( + OrchardStorage::Error{OrchardStorage::ErrorCode::kInternalError, + "Failed to insert commitments"}); + } + + return storage_.UpdateNotes(account_id, notes_to_add, std::move(nf_to_add), + latest_scanned_block, latest_scanned_block_hash); +} + +base::expected +OrchardSyncState::ResetAccountSyncState(const mojom::AccountIdPtr& account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_.ResetAccountSyncState(account_id); +} + +base::expected, OrchardStorage::Error> +OrchardSyncState::CalculateWitnessForCheckpoint( + const mojom::AccountIdPtr& account_id, + const std::vector& notes, + uint32_t checkpoint_position) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto& shard_tree = GetOrCreateShardTree(account_id); + + std::vector result; + result.reserve(notes.size()); + for (auto& input : notes) { + auto witness = shard_tree.CalculateWitness( + input.note.orchard_commitment_tree_position, checkpoint_position); + if (!witness.has_value()) { + return base::unexpected( + OrchardStorage::Error{OrchardStorage::ErrorCode::kInternalError, + "Failed to calculate wittness"}); + } + result.push_back(input); + result.back().witness = std::move(witness.value()); + } + return base::ok(std::move(result)); +} + +void OrchardSyncState::ResetDatabase() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + storage_.ResetDatabase(); +} + +bool OrchardSyncState::Truncate(const mojom::AccountIdPtr& account_id, + uint32_t checkpoint_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return GetOrCreateShardTree(account_id).TruncateToCheckpoint(checkpoint_id); +} + +void OrchardSyncState::OverrideShardTreeForTesting( // IN_TEST + const mojom::AccountIdPtr& account_id, + std::unique_ptr shard_tree) { + CHECK_IS_TEST(); + shard_trees_[account_id->unique_key] = std::move(shard_tree); +} + +OrchardStorage& OrchardSyncState::orchard_storage() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return storage_; +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_sync_state.h b/components/brave_wallet/browser/internal/orchard_sync_state.h new file mode 100644 index 000000000000..a205af075c46 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_sync_state.h @@ -0,0 +1,94 @@ +/* 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_INTERNAL_ORCHARD_SYNC_STATE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_SYNC_STATE_H_ + +#include +#include +#include +#include + +#include "base/sequence_checker.h" +#include "brave/components/brave_wallet/browser/internal/orchard_block_scanner.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +// Represents the persisted synchronization state for the Zcash blockchain. +// The synchronization state includes account-specific information regarding +// spendable and spent notes, sync progress, and the state of the Orchard +// commitment tree, which is used to sign notes for spending. +class OrchardSyncState { + public: + explicit OrchardSyncState(const base::FilePath& path_to_database); + ~OrchardSyncState(); + + base::expected + RegisterAccount(const mojom::AccountIdPtr& account_id, + uint64_t account_birthday_block); + + base::expected, + OrchardStorage::Error> + GetAccountMeta(const mojom::AccountIdPtr& account_id); + + base::expected + HandleChainReorg(const mojom::AccountIdPtr& account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash); + + base::expected, OrchardStorage::Error> + GetSpendableNotes(const mojom::AccountIdPtr& account_id); + + base::expected, OrchardStorage::Error> + GetNullifiers(const mojom::AccountIdPtr& account_id); + + base::expected + ApplyScanResults(const mojom::AccountIdPtr& account_id, + // Value is used here to allow moving scanned_blocks which + // wraps rust object. + OrchardBlockScanner::Result block_scanner_results, + const uint32_t latest_scanned_block, + const std::string& latest_scanned_block_hash); + + // Clears sync data related to the account except it's birthday. + base::expected + ResetAccountSyncState(const mojom::AccountIdPtr& account_id); + + // Drops underlying database. + void ResetDatabase(); + + base::expected, OrchardStorage::Error> + CalculateWitnessForCheckpoint(const mojom::AccountIdPtr& account_id, + const std::vector& notes, + uint32_t checkpoint_position); + + bool Truncate(const mojom::AccountIdPtr& account_id, uint32_t checkpoint_id); + + private: + friend class OrchardSyncStateTest; + + // Testing + void OverrideShardTreeForTesting( + const mojom::AccountIdPtr& account_id, + std::unique_ptr shard_tree); + OrchardStorage& orchard_storage(); + + orchard::OrchardShardTree& GetOrCreateShardTree( + const mojom::AccountIdPtr& account_id) LIFETIME_BOUND; + + OrchardStorage storage_; + std::map> + shard_trees_; + + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_SYNC_STATE_H_ diff --git a/components/brave_wallet/browser/internal/orchard_sync_state_unittest.cc b/components/brave_wallet/browser/internal/orchard_sync_state_unittest.cc new file mode 100644 index 000000000000..0f9a033505c3 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_sync_state_unittest.cc @@ -0,0 +1,552 @@ +/* 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/internal/orchard_sync_state.h" + +#include + +#include "base/files/scoped_temp_dir.h" +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" +#include "brave/components/brave_wallet/browser/internal/orchard_test_utils.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.h" +#include "brave/components/brave_wallet/common/common_utils.h" +#include "brave/components/brave_wallet/common/hex_utils.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace brave_wallet { + +namespace { + +constexpr uint32_t kDefaultCommitmentSeed = 1; + +OrchardNoteWitness CreateWitness(const std::vector& path, + uint32_t position) { + OrchardNoteWitness result; + for (const auto& path_elem : path) { + OrchardMerkleHash as_bytes; + EXPECT_TRUE(base::HexStringToSpan(path_elem, as_bytes)); + result.merkle_path.push_back(as_bytes); + } + result.position = position; + return result; +} + +OrchardCommitment CreateCommitment(OrchardCommitmentValue value, + bool marked, + std::optional checkpoint_id) { + return OrchardCommitment{std::move(value), marked, checkpoint_id}; +} + +} // namespace + +class OrchardSyncStateTest : public testing::Test { + public: + OrchardSyncStateTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} + void SetUp() override; + + OrchardSyncState* sync_state() { return sync_state_.get(); } + + OrchardStorage& storage() { return sync_state_->orchard_storage(); } + + mojom::AccountIdPtr account_id() { return account_id_.Clone(); } + + private: + base::test::TaskEnvironment task_environment_; + base::ScopedTempDir temp_dir_; + mojom::AccountIdPtr account_id_; + + std::unique_ptr sync_state_; +}; + +void OrchardSyncStateTest::SetUp() { + account_id_ = MakeIndexBasedAccountId(mojom::CoinType::ZEC, + mojom::KeyringId::kZCashMainnet, + mojom::AccountKind::kDerived, 0); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::FilePath db_path( + temp_dir_.GetPath().Append(FILE_PATH_LITERAL("orchard.db"))); + sync_state_ = std::make_unique(db_path); + sync_state_->OverrideShardTreeForTesting( + account_id_, orchard::CreateShardTreeForTesting( + sync_state_->orchard_storage(), account_id_)); +} + +TEST_F(OrchardSyncStateTest, CheckpointsPruned) { + std::vector commitments; + + for (int i = 0; i < 40; i++) { + std::optional checkpoint; + if (i % 2 == 0) { + checkpoint = i * 2; + } + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(i, kDefaultCommitmentSeed), + false, checkpoint)); + } + OrchardTreeState orchard_tree_state; + auto result = CreateResultForTesting(std::move(orchard_tree_state), + std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + EXPECT_EQ(10u, storage().CheckpointCount(account_id()).value()); + EXPECT_EQ(40u, storage().MinCheckpointId(account_id()).value().value()); + EXPECT_EQ(76u, storage().MaxCheckpointId(account_id()).value().value()); +} + +TEST_F(OrchardSyncStateTest, InsertWithFrontier) { + OrchardTreeState prior_tree_state; + prior_tree_state.block_height = 0; + prior_tree_state.tree_size = 48; + prior_tree_state.frontier = std::vector( + {1, 72, 173, 200, 225, 47, 142, 44, 148, 137, 119, 18, 99, 211, + 92, 65, 67, 173, 197, 93, 7, 85, 70, 105, 140, 223, 184, 193, + 172, 9, 194, 88, 62, 1, 130, 31, 76, 59, 69, 55, 151, 124, + 101, 120, 230, 247, 201, 82, 48, 160, 150, 48, 23, 84, 250, 117, + 120, 175, 108, 220, 96, 214, 42, 255, 209, 44, 7, 1, 13, 59, + 69, 136, 45, 180, 148, 18, 146, 125, 241, 196, 224, 205, 11, 196, + 195, 90, 164, 186, 175, 22, 90, 105, 82, 149, 34, 131, 232, 132, + 223, 15, 1, 211, 200, 193, 46, 24, 11, 42, 42, 182, 124, 29, + 48, 234, 215, 28, 103, 218, 239, 234, 109, 10, 231, 74, 70, 197, + 113, 131, 89, 199, 71, 102, 33, 1, 153, 86, 62, 213, 2, 98, + 191, 65, 218, 123, 73, 155, 243, 225, 45, 10, 241, 132, 49, 33, + 101, 183, 59, 35, 56, 78, 228, 47, 166, 10, 237, 50, 0, 1, + 94, 228, 186, 123, 0, 136, 39, 192, 226, 129, 40, 253, 0, 83, + 248, 138, 7, 26, 120, 212, 191, 135, 44, 0, 171, 42, 69, 6, + 133, 205, 115, 4, 0, 0}); + + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(48, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(49, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(50, kDefaultCommitmentSeed), + true, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(51, kDefaultCommitmentSeed), false, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(52, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = CreateResultForTesting(std::move(prior_tree_state), + std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + OrchardInput input; + input.note.orchard_commitment_tree_position = 50; + + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 1); + EXPECT_TRUE(witness_result.has_value()); + EXPECT_EQ( + witness_result.value()[0].witness.value(), + CreateWitness( + {"9695d64b1ccd38aa5dfdc5c70aecf0e763549034318c59943a3e3e921b415c3a", + "48ddf8a84afc5949e074c162630e3f6aab3d4350bf929ba82677cee4c634e029", + "c7413f4614cd64043abbab7cc1095c9bb104231cea89e2c3e0df83769556d030", + "2111fc397753e5fd50ec74816df27d6ada7ed2a9ac3816aab2573c8fac794204", + "2d99471d096691e4a5f43efe469734aff37f4f21c707b060c952a84169f9302f", + "5ee4ba7b008827c0e28128fd0053f88a071a78d4bf872c00ab2a450685cd7304", + "27ab1320953ae1ad70c8c15a1253a0a86fbc8a0aa36a84207293f8a495ffc402", + "4e14563df191a2a65b4b37113b5230680555051b22d74a8e1f1d706f90f3133" + "b"}, + 50)); +} + +TEST_F(OrchardSyncStateTest, Checkpoint_WithMarked) { + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(0, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(1, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(2, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(3, kDefaultCommitmentSeed), true, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(4, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + OrchardInput input; + input.note.orchard_commitment_tree_position = 3; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 1); + EXPECT_TRUE(witness_result.has_value()); + + EXPECT_EQ( + witness_result.value()[0].witness.value(), + CreateWitness( + {"3bb11bd05d2ed5e590369f274a1a247d390380aa0590160bfbf72cb186d7023f", + "d4059d13ddcbe9ec7e6fc99bdf9bfd08b0a678d26e3bf6a734e7688eca669f37", + "c7413f4614cd64043abbab7cc1095c9bb104231cea89e2c3e0df83769556d030", + "2111fc397753e5fd50ec74816df27d6ada7ed2a9ac3816aab2573c8fac794204", + "806afbfeb45c64d4f2384c51eff30764b84599ae56a7ab3d4a46d9ce3aeab431", + "873e4157f2c0f0c645e899360069fcc9d2ed9bc11bf59827af0230ed52edab18", + "27ab1320953ae1ad70c8c15a1253a0a86fbc8a0aa36a84207293f8a495ffc402", + "4e14563df191a2a65b4b37113b5230680555051b22d74a8e1f1d706f90f3133" + "b"}, + 3)); +} + +TEST_F(OrchardSyncStateTest, MinCheckpoint) { + std::vector commitments; + + for (int i = 0; i < 40; i++) { + std::optional checkpoint; + if (i % 2 == 0) { + checkpoint = i * 2; + } + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(i, kDefaultCommitmentSeed), + false, checkpoint)); + } + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + EXPECT_EQ(10u, storage().CheckpointCount(account_id()).value()); + EXPECT_EQ(40u, storage().MinCheckpointId(account_id()).value().value()); + EXPECT_EQ(76u, storage().MaxCheckpointId(account_id()).value().value()); +} + +TEST_F(OrchardSyncStateTest, MaxCheckpoint) { + { + std::vector commitments; + + for (int i = 0; i < 5; i++) { + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(i, kDefaultCommitmentSeed), + false, std::nullopt)); + } + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(5, kDefaultCommitmentSeed), false, 1u)); + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + } + + { + std::vector commitments; + + for (int i = 6; i < 10; i++) { + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(i, kDefaultCommitmentSeed), + false, std::nullopt)); + } + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(10, kDefaultCommitmentSeed), false, 2u)); + OrchardTreeState tree_state; + tree_state.block_height = 1; + tree_state.tree_size = 6; + auto result = + CreateResultForTesting(std::move(tree_state), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + } + + { + std::vector commitments; + + for (int i = 11; i < 15; i++) { + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(i, kDefaultCommitmentSeed), + false, std::nullopt)); + } + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(15, kDefaultCommitmentSeed), false, 3u)); + OrchardTreeState tree_state; + tree_state.block_height = 2; + tree_state.tree_size = 11; + auto result = + CreateResultForTesting(std::move(tree_state), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + } + + EXPECT_EQ(3u, storage().CheckpointCount(account_id()).value()); + EXPECT_EQ(1u, storage().MinCheckpointId(account_id()).value().value()); + EXPECT_EQ(3u, storage().MaxCheckpointId(account_id()).value().value()); +} + +TEST_F(OrchardSyncStateTest, NoWitnessOnNonMarked) { + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(0, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(1, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(2, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(3, kDefaultCommitmentSeed), false, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(4, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + { + OrchardInput input; + input.note.orchard_commitment_tree_position = 2; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 1); + EXPECT_FALSE(witness_result.has_value()); + } +} + +TEST_F(OrchardSyncStateTest, NoWitnessOnWrongCheckpoint) { + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(0, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(1, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(2, kDefaultCommitmentSeed), + true, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(3, kDefaultCommitmentSeed), false, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(4, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + { + OrchardInput input; + input.note.orchard_commitment_tree_position = 2; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 2); + EXPECT_FALSE(witness_result.has_value()); + } +} + +TEST_F(OrchardSyncStateTest, TruncateTree) { + { + std::vector commitments; + + for (int i = 0; i < 10; i++) { + switch (i) { + case 2: + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(i, kDefaultCommitmentSeed), true, + std::nullopt)); + break; + case 3: + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(i, kDefaultCommitmentSeed), false, 1)); + break; + case 5: + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(i, kDefaultCommitmentSeed), false, 2)); + break; + default: + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(i, kDefaultCommitmentSeed), false, + std::nullopt)); + break; + } + } + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + } + + EXPECT_TRUE(sync_state()->Truncate(account_id(), 2)); + + { + std::vector commitments; + + for (int j = 0; j < 5; j++) { + if (j == 3) { + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(j, 5), false, 2)); + } else { + commitments.push_back(CreateCommitment(CreateMockCommitmentValue(j, 5), + false, std::nullopt)); + } + } + + OrchardTreeState tree_state; + tree_state.block_height = 1; + // Truncate was on position 5, so 5 elements left in the tre + tree_state.tree_size = 5; + auto result = + CreateResultForTesting(std::move(tree_state), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 2000, "2000") + .value()); + } + + { + OrchardInput input; + input.note.orchard_commitment_tree_position = 2; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 2); + EXPECT_TRUE(witness_result.has_value()); + } + + OrchardInput input; + input.note.orchard_commitment_tree_position = 2; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 1); + EXPECT_TRUE(witness_result.has_value()); + EXPECT_EQ( + witness_result.value()[0].witness.value(), + CreateWitness( + {"f342eb6489f4e5b5a0fb0a4ece48d137dcd5e80011aab4668913f98be2af3311", + "d4059d13ddcbe9ec7e6fc99bdf9bfd08b0a678d26e3bf6a734e7688eca669f37", + "c7413f4614cd64043abbab7cc1095c9bb104231cea89e2c3e0df83769556d030", + "2111fc397753e5fd50ec74816df27d6ada7ed2a9ac3816aab2573c8fac794204", + "806afbfeb45c64d4f2384c51eff30764b84599ae56a7ab3d4a46d9ce3aeab431", + "873e4157f2c0f0c645e899360069fcc9d2ed9bc11bf59827af0230ed52edab18", + "27ab1320953ae1ad70c8c15a1253a0a86fbc8a0aa36a84207293f8a495ffc402", + "4e14563df191a2a65b4b37113b5230680555051b22d74a8e1f1d706f90f3133" + "b"}, + 2)); +} + +TEST_F(OrchardSyncStateTest, TruncateTreeWrongCheckpoint) { + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(0, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(1, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(2, kDefaultCommitmentSeed), + true, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(3, kDefaultCommitmentSeed), false, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(4, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + EXPECT_FALSE(sync_state()->Truncate(account_id(), 2)); +} + +TEST_F(OrchardSyncStateTest, SimpleInsert) { + std::vector commitments; + + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(0, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(1, kDefaultCommitmentSeed), + false, std::nullopt)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(2, kDefaultCommitmentSeed), + true, std::nullopt)); + commitments.push_back(CreateCommitment( + CreateMockCommitmentValue(3, kDefaultCommitmentSeed), false, 1)); + commitments.push_back( + CreateCommitment(CreateMockCommitmentValue(4, kDefaultCommitmentSeed), + false, std::nullopt)); + + auto result = + CreateResultForTesting(OrchardTreeState(), std::move(commitments)); + EXPECT_EQ( + OrchardStorage::Result::kSuccess, + sync_state() + ->ApplyScanResults(account_id(), std::move(result), 1000, "1000") + .value()); + + OrchardInput input; + input.note.orchard_commitment_tree_position = 2; + auto witness_result = + sync_state()->CalculateWitnessForCheckpoint(account_id(), {input}, 1); + EXPECT_TRUE(witness_result.has_value()); + EXPECT_EQ( + witness_result.value()[0].witness.value(), + CreateWitness( + {"f342eb6489f4e5b5a0fb0a4ece48d137dcd5e80011aab4668913f98be2af3311", + "d4059d13ddcbe9ec7e6fc99bdf9bfd08b0a678d26e3bf6a734e7688eca669f37", + "c7413f4614cd64043abbab7cc1095c9bb104231cea89e2c3e0df83769556d030", + "2111fc397753e5fd50ec74816df27d6ada7ed2a9ac3816aab2573c8fac794204", + "806afbfeb45c64d4f2384c51eff30764b84599ae56a7ab3d4a46d9ce3aeab431", + "873e4157f2c0f0c645e899360069fcc9d2ed9bc11bf59827af0230ed52edab18", + "27ab1320953ae1ad70c8c15a1253a0a86fbc8a0aa36a84207293f8a495ffc402", + "4e14563df191a2a65b4b37113b5230680555051b22d74a8e1f1d706f90f3133" + "b"}, + 2)); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_test_utils.cc b/components/brave_wallet/browser/internal/orchard_test_utils.cc new file mode 100644 index 000000000000..e1e765559ba0 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_test_utils.cc @@ -0,0 +1,30 @@ +// 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/internal/orchard_test_utils.h" + +#include + +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_test_utils.h" + +namespace brave_wallet { + +OrchardBlockScanner::Result CreateResultForTesting( + OrchardTreeState tree_state, + std::vector commitments) { + auto builder = orchard::CreateTestingDecodedBundleBuilder(); + for (auto& commitment : commitments) { + builder->AddCommitment(std::move(commitment)); + } + builder->SetPriorTreeState(std::move(tree_state)); + return OrchardBlockScanner::Result{{}, {}, builder->Complete()}; +} + +OrchardCommitmentValue CreateMockCommitmentValue(uint32_t position, + uint32_t rseed) { + return orchard::CreateMockCommitmentValue(position, rseed); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_test_utils.h b/components/brave_wallet/browser/internal/orchard_test_utils.h new file mode 100644 index 000000000000..f60792c1b718 --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_test_utils.h @@ -0,0 +1,25 @@ +// 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_INTERNAL_ORCHARD_TEST_UTILS_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_TEST_UTILS_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/internal/orchard_block_scanner.h" + +namespace brave_wallet { + +OrchardBlockScanner::Result CreateResultForTesting( + OrchardTreeState tree_state, + std::vector commitments); + +OrchardCommitmentValue CreateMockCommitmentValue(uint32_t position, + uint32_t rseed); + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_TEST_UTILS_H_ diff --git a/components/brave_wallet/browser/test/BUILD.gn b/components/brave_wallet/browser/test/BUILD.gn index 1cb94f2a57e1..0cc66b28836c 100644 --- a/components/brave_wallet/browser/test/BUILD.gn +++ b/components/brave_wallet/browser/test/BUILD.gn @@ -161,11 +161,15 @@ source_set("brave_wallet_unit_tests") { "//brave/components/brave_wallet/browser/internal/hd_key_zip32_unittest.cc", "//brave/components/brave_wallet/browser/internal/orchard_block_scanner_unittest.cc", "//brave/components/brave_wallet/browser/internal/orchard_bundle_manager_unittest.cc", - "//brave/components/brave_wallet/browser/zcash/zcash_orchard_storage_unittest.cc", + "//brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage_unittest.cc", + "//brave/components/brave_wallet/browser/internal/orchard_sync_state_unittest.cc", "//brave/components/brave_wallet/browser/zcash/zcash_shield_sync_service_unittest.cc", ] - deps += - [ "//brave/components/brave_wallet/browser/internal:orchard_bundle" ] + deps += [ + "//brave/components/brave_wallet/browser/internal:orchard_bundle", + "//brave/components/brave_wallet/browser/internal:test_support", + "//brave/components/brave_wallet/browser/internal/orchard_storage", + ] } if (!is_ios) { diff --git a/components/brave_wallet/browser/zcash/rust/BUILD.gn b/components/brave_wallet/browser/zcash/rust/BUILD.gn index 673e5d05dfd6..ccec4aee65ba 100644 --- a/components/brave_wallet/browser/zcash/rust/BUILD.gn +++ b/components/brave_wallet/browser/zcash/rust/BUILD.gn @@ -17,35 +17,79 @@ group("rust") { deps = [ ":orchard_impl" ] } +group("test_support") { + visibility = + [ "//brave/components/brave_wallet/browser/internal:test_support" ] + + public_deps = [ ":test_support_headers" ] + deps = [ ":test_support_impl" ] +} + +source_set("test_support_headers") { + visibility = [ + ":*", + "//brave/components/brave_wallet/browser/internal:test_support", + ] + sources = [ + "orchard_test_utils.h", + "orchard_testing_shard_tree.h", + ] + + public_deps = [ ":orchard_headers" ] +} + +source_set("test_support_impl") { + visibility = [ ":test_support" ] + sources = [ + "orchard_test_utils.cc", + "orchard_testing_shard_tree.cc", + ] + + deps = [ + ":orchard_impl", + ":rust_lib", + ":test_support_headers", + ] +} + source_set("orchard_headers") { visibility = [ ":*" ] sources = [ - "authorized_orchard_bundle.h", - "extended_spending_key.h", + "orchard_authorized_bundle.h", "orchard_block_decoder.h", - "unauthorized_orchard_bundle.h", + "orchard_decoded_blocks_bundle.h", + "orchard_extended_spending_key.h", + "orchard_shard_tree.h", + "orchard_unauthorized_bundle.h", ] public_deps = [ "//base", + "//brave/components/brave_wallet/browser/internal/orchard_storage:headers", "//brave/components/brave_wallet/common", "//brave/components/services/brave_wallet/public/mojom", ] } source_set("orchard_impl") { - visibility = [ ":rust" ] + visibility = [ + ":rust", + ":test_support_impl", + ] sources = [ - "authorized_orchard_bundle_impl.cc", - "authorized_orchard_bundle_impl.h", - "extended_spending_key_impl.cc", - "extended_spending_key_impl.h", - "orchard_block_decoder_impl.cc", - "orchard_block_decoder_impl.h", - "unauthorized_orchard_bundle_impl.cc", - "unauthorized_orchard_bundle_impl.h", + "cxx_orchard_shard_tree_delegate.cc", + "orchard_authorized_bundle_impl.cc", + "orchard_authorized_bundle_impl.h", + "orchard_block_decoder.cc", + "orchard_decoded_blocks_bundle_impl.cc", + "orchard_decoded_blocks_bundle_impl.h", + "orchard_extended_spending_key_impl.cc", + "orchard_extended_spending_key_impl.h", + "orchard_shard_tree.cc", + "orchard_unauthorized_bundle_impl.cc", + "orchard_unauthorized_bundle_impl.h", ] deps = [ @@ -57,8 +101,22 @@ source_set("orchard_impl") { ] } +source_set("cxx_interfaces") { + visibility = [ ":*" ] + sources = [ "cxx_orchard_shard_tree_delegate.h" ] + + public_deps = [ + "//base", + "//brave/components/brave_wallet/common", + "//build/rust:cxx_cppdeps", + ] +} + rust_static_library("rust_lib") { - visibility = [ ":orchard_impl" ] + visibility = [ + ":orchard_impl", + ":test_support_impl", + ] crate_name = "zcash" crate_root = "lib.rs" @@ -69,14 +127,19 @@ rust_static_library("rust_lib") { deps = [ "librustzcash:zcash_client_backend", "librustzcash:zcash_primitives", + "librustzcash:zcash_protocol", "//brave/components/brave_wallet/rust:rust_lib", "//brave/third_party/rust/incrementalmerkletree/v0_5:lib", "//brave/third_party/rust/memuse/v0_2:lib", "//brave/third_party/rust/nonempty/v0_7:lib", "//brave/third_party/rust/orchard/v0_8:lib", + "//brave/third_party/rust/pasta_curves/v0_5:lib", + "//brave/third_party/rust/paste/v1:lib", "//brave/third_party/rust/rand/v0_8:lib", "//brave/third_party/rust/shardtree/v0_3:lib", "//brave/third_party/rust/zcash_note_encryption/v0_4:lib", "//third_party/rust/byteorder/v1:lib", ] + + public_deps = [ ":cxx_interfaces" ] } diff --git a/components/brave_wallet/browser/zcash/rust/Cargo.toml b/components/brave_wallet/browser/zcash/rust/Cargo.toml index 3cbfc762f8fb..6fc50d0a26d5 100644 --- a/components/brave_wallet/browser/zcash/rust/Cargo.toml +++ b/components/brave_wallet/browser/zcash/rust/Cargo.toml @@ -14,6 +14,9 @@ zcash_note_encryption = "0.4" zcash_client_backend = { version = "0.12.1", default-features = false } shardtree = { version="0.3.2", features=["legacy-api"] } +[dev-dependencies.paste] +version = "1" + [lib] name = "zcash" path = "lib.rs" diff --git a/components/brave_wallet/browser/zcash/rust/DEPS b/components/brave_wallet/browser/zcash/rust/DEPS index b6c9bddb9ffa..a216032727e2 100644 --- a/components/brave_wallet/browser/zcash/rust/DEPS +++ b/components/brave_wallet/browser/zcash/rust/DEPS @@ -1,12 +1,24 @@ specific_include_rules = { - "unauthorized_orchard_bundle_impl.h": [ + "orchard_unauthorized_bundle_impl.h": [ "+third_party/rust/cxx/v1/cxx.h", ], - "extended_spending_key_impl.h": [ + "orchard_extended_spending_key_impl.h": [ "+third_party/rust/cxx/v1/cxx.h", ], - "unauthorized_orchard_bundle_impl.h": [ + "orchard_unauthorized_bundle_impl.h": [ "+third_party/rust/cxx/v1/cxx.h", ], + "orchard_decoded_blocks_bundle_impl.h": [ + "+third_party/rust/cxx/v1/cxx.h", + ], + "cxx_orchard_shard_tree_delegate.h": [ + "+third_party/rust/cxx/v1/cxx.h", + ], + "orchard_shard_tree_impl.h": [ + "+third_party/rust/cxx/v1/cxx.h", + ], + "orchard_testing_shard_tree_impl.h": [ + "+third_party/rust/cxx/v1/cxx.h", + ] } diff --git a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.cc b/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.cc deleted file mode 100644 index 393355d2bffc..000000000000 --- a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.cc +++ /dev/null @@ -1,21 +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/rust/authorized_orchard_bundle_impl.h" - -namespace brave_wallet::orchard { - -AuthorizedOrchardBundleImpl::AuthorizedOrchardBundleImpl( - ::rust::Box orchard_authorized_bundle) - : orchard_authorized_bundle_(std::move(orchard_authorized_bundle)) {} - -AuthorizedOrchardBundleImpl::~AuthorizedOrchardBundleImpl() = default; - -std::vector AuthorizedOrchardBundleImpl::GetOrchardRawTxPart() { - auto data = orchard_authorized_bundle_->raw_tx(); - return std::vector(data.begin(), data.end()); -} - -} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.h b/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.h deleted file mode 100644 index 4a392244341a..000000000000 --- a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.h +++ /dev/null @@ -1,32 +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_RUST_AUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_AUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ - -#include - -#include "brave/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h" -#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" - -namespace brave_wallet::orchard { - -class AuthorizedOrchardBundleImpl : public AuthorizedOrchardBundle { - public: - ~AuthorizedOrchardBundleImpl() override; - - std::vector GetOrchardRawTxPart() override; - - private: - explicit AuthorizedOrchardBundleImpl( - ::rust::Box orchard_authorized_bundle); - friend class UnauthorizedOrchardBundleImpl; - - ::rust::Box orchard_authorized_bundle_; -}; - -} // namespace brave_wallet::orchard - -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_AUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.cc b/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.cc new file mode 100644 index 000000000000..6e30a4435698 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.cc @@ -0,0 +1,298 @@ +// 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/cxx_orchard_shard_tree_delegate.h" + +#include +#include + +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_storage.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +namespace { + +::brave_wallet::OrchardShardAddress FromRust( + const CxxOrchardShardAddress& addr) { + return ::brave_wallet::OrchardShardAddress{addr.level, addr.index}; +} + +CxxOrchardShardAddress ToRust(const ::brave_wallet::OrchardShardAddress& addr) { + return CxxOrchardShardAddress{addr.level, addr.index}; +} + +CxxOrchardShardTreeCap ToRust( + ::brave_wallet::OrchardShardTreeCap& shard_store_cap) { + ::rust::Vec data; + data.reserve(shard_store_cap.size()); + base::ranges::copy(shard_store_cap, std::back_inserter(data)); + return CxxOrchardShardTreeCap{std::move(data)}; +} + +::brave_wallet::OrchardShardTreeCap FromRust( + const CxxOrchardShardTreeCap& cap) { + ::brave_wallet::OrchardShardTreeCap shard_store_cap; + shard_store_cap.reserve(cap.data.size()); + base::ranges::copy(cap.data, std::back_inserter(shard_store_cap)); + return shard_store_cap; +} + +::brave_wallet::OrchardShard FromRust(const CxxOrchardShard& tree) { + std::optional shard_root_hash; + if (!tree.hash.empty()) { + CHECK_EQ(kOrchardShardTreeHashSize, tree.hash.size()); + OrchardShardRootHash hash_value; + base::ranges::copy(tree.hash, hash_value.begin()); + shard_root_hash = hash_value; + } + + std::vector data; + data.reserve(tree.data.size()); + base::ranges::copy(tree.data, std::back_inserter(data)); + + return ::brave_wallet::OrchardShard(FromRust(tree.address), shard_root_hash, + std::move(data)); +} + +CxxOrchardShard ToRust(const ::brave_wallet::OrchardShard& tree) { + ::rust::Vec data; + data.reserve(tree.shard_data.size()); + base::ranges::copy(tree.shard_data, std::back_inserter(data)); + + ::rust::Vec hash; + if (tree.root_hash) { + base::ranges::copy(tree.root_hash.value(), std::back_inserter(hash)); + } + return CxxOrchardShard{ToRust(tree.address), std::move(hash), + std::move(data)}; +} + +CxxOrchardCheckpoint ToRust( + const ::brave_wallet::OrchardCheckpoint& checkpoint) { + ::rust::Vec marks_removed; + base::ranges::copy(checkpoint.marks_removed, + std::back_inserter(marks_removed)); + return CxxOrchardCheckpoint{!checkpoint.tree_state_position.has_value(), + checkpoint.tree_state_position.value_or(0), + marks_removed}; +} + +CxxOrchardCheckpointBundle ToRust( + const ::brave_wallet::OrchardCheckpointBundle& checkpoint_bundle) { + return CxxOrchardCheckpointBundle(checkpoint_bundle.checkpoint_id, + ToRust(checkpoint_bundle.checkpoint)); +} + +::brave_wallet::OrchardCheckpoint FromRust( + const CxxOrchardCheckpoint& checkpoint) { + CheckpointTreeState checkpoint_tree_state = std::nullopt; + if (!checkpoint.empty) { + checkpoint_tree_state = checkpoint.position; + } + return ::brave_wallet::OrchardCheckpoint{ + checkpoint_tree_state, + std::vector(checkpoint.mark_removed.begin(), + checkpoint.mark_removed.end())}; +} + +} // namespace + +CxxOrchardShardTreeDelegate::CxxOrchardShardTreeDelegate( + OrchardStorage& storage, + const mojom::AccountIdPtr& account_id) + : storage_(storage), account_id_(account_id.Clone()) {} + +CxxOrchardShardTreeDelegate::~CxxOrchardShardTreeDelegate() = default; + +::rust::Box CxxOrchardShardTreeDelegate::GetShard( + const CxxOrchardShardAddress& addr) const { + auto shard = storage_->GetShard(account_id_, FromRust(addr)); + if (!shard.has_value()) { + return wrap_shard_tree_shard_error(); + } else if (!shard.value()) { + return wrap_shard_tree_shard_none(); + } + return wrap_shard_tree_shard(ToRust(**shard)); +} + +::rust::Box +CxxOrchardShardTreeDelegate::LastShard(uint8_t shard_level) const { + auto shard = storage_->LastShard(account_id_, shard_level); + if (!shard.has_value()) { + return wrap_shard_tree_shard_error(); + } else if (!shard.value()) { + return wrap_shard_tree_shard_none(); + } + return wrap_shard_tree_shard(ToRust(**shard)); +} + +::rust::Box CxxOrchardShardTreeDelegate::PutShard( + const CxxOrchardShard& tree) const { + auto result = storage_->PutShard(account_id_, FromRust(tree)); + if (!result.has_value()) { + return wrap_bool_error(); + } + + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::GetShardRoots(uint8_t shard_level) const { + auto shard = storage_->GetShardRoots(account_id_, shard_level); + if (!shard.has_value()) { + return wrap_shard_tree_roots_error(); + } + ::rust::Vec roots; + for (const auto& root : *shard) { + roots.push_back(ToRust(root)); + } + return wrap_shard_tree_roots(std::move(roots)); +} + +::rust::Box CxxOrchardShardTreeDelegate::Truncate( + const CxxOrchardShardAddress& address) const { + auto result = storage_->TruncateShards(account_id_, address.index); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::GetCap() const { + auto result = storage_->GetCap(account_id_); + if (!result.has_value()) { + return wrap_shard_tree_cap_error(); + } else if (!result.value()) { + return wrap_shard_tree_cap_none(); + } + return wrap_shard_tree_cap(ToRust(**result)); +} + +::rust::Box CxxOrchardShardTreeDelegate::PutCap( + const CxxOrchardShardTreeCap& tree) const { + auto result = storage_->PutCap(account_id_, FromRust(tree)); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::MinCheckpointId() const { + auto result = storage_->MinCheckpointId(account_id_); + if (!result.has_value()) { + return wrap_checkpoint_id_error(); + } else if (!result.value()) { + return wrap_checkpoint_id_none(); + } + return wrap_checkpoint_id(**result); +} + +::rust::Box +CxxOrchardShardTreeDelegate::MaxCheckpointId() const { + auto result = storage_->MaxCheckpointId(account_id_); + if (!result.has_value()) { + return wrap_checkpoint_id_error(); + } else if (!result.value()) { + return wrap_checkpoint_id_none(); + } + return wrap_checkpoint_id(**result); +} + +::rust::Box CxxOrchardShardTreeDelegate::AddCheckpoint( + uint32_t checkpoint_id, + const CxxOrchardCheckpoint& checkpoint) const { + auto result = + storage_->AddCheckpoint(account_id_, checkpoint_id, FromRust(checkpoint)); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::CheckpointCount() const { + auto result = storage_->CheckpointCount(account_id_); + if (!result.has_value()) { + return wrap_checkpoint_count_error(); + } + return wrap_checkpoint_count(result.value()); +} + +::rust::Box +CxxOrchardShardTreeDelegate::CheckpointAtDepth(size_t depth) const { + auto checkpoint_id = storage_->GetCheckpointAtDepth(account_id_, depth); + if (!checkpoint_id.has_value()) { + return wrap_checkpoint_bundle_error(); + } else if (!checkpoint_id.value()) { + return wrap_checkpoint_bundle_none(); + } + + auto checkpoint = storage_->GetCheckpoint(account_id_, **checkpoint_id); + if (!checkpoint.has_value()) { + return wrap_checkpoint_bundle_error(); + } else if (!checkpoint.value()) { + return wrap_checkpoint_bundle_none(); + } + return wrap_checkpoint_bundle(ToRust(**checkpoint)); +} + +::rust::Box +CxxOrchardShardTreeDelegate::GetCheckpoint(uint32_t checkpoint_id) const { + auto checkpoint = storage_->GetCheckpoint(account_id_, checkpoint_id); + if (!checkpoint.has_value()) { + return wrap_checkpoint_bundle_error(); + } else if (!checkpoint.value()) { + return wrap_checkpoint_bundle_none(); + } + return wrap_checkpoint_bundle(ToRust(**checkpoint)); +} + +::rust::Box CxxOrchardShardTreeDelegate::UpdateCheckpoint( + uint32_t checkpoint_id, + const CxxOrchardCheckpoint& checkpoint) const { + auto result = storage_->UpdateCheckpoint(account_id_, checkpoint_id, + FromRust(checkpoint)); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box CxxOrchardShardTreeDelegate::RemoveCheckpoint( + uint32_t checkpoint_id) const { + auto result = storage_->RemoveCheckpoint(account_id_, checkpoint_id); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::TruncateCheckpoint(uint32_t checkpoint_id) const { + auto result = storage_->TruncateCheckpoints(account_id_, checkpoint_id); + if (!result.has_value()) { + return wrap_bool_error(); + } + return wrap_bool(result.value() == OrchardStorage::Result::kSuccess); +} + +::rust::Box +CxxOrchardShardTreeDelegate::GetCheckpoints(size_t limit) const { + auto checkpoints = storage_->GetCheckpoints(account_id_, limit); + if (!checkpoints.has_value()) { + return wrap_checkpoints_error(); + } + ::rust::Vec result; + for (const auto& checkpoint : checkpoints.value()) { + result.push_back(ToRust(checkpoint)); + } + return wrap_checkpoints(std::move(result)); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h b/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h new file mode 100644 index 000000000000..1684130fe535 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h @@ -0,0 +1,78 @@ +// 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_RUST_CXX_ORCHARD_SHARD_TREE_DELEGATE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_CXX_ORCHARD_SHARD_TREE_DELEGATE_H_ + +#include "base/memory/raw_ref.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "third_party/rust/cxx/v1/cxx.h" + +namespace brave_wallet { + +class OrchardStorage; + +namespace orchard { + +struct CxxOrchardShard; +struct CxxOrchardShardTreeCap; +struct CxxOrchardShardAddress; +struct CxxOrchardCheckpoint; + +struct CxxBoolResultWrapper; +struct CxxCheckpointBundleResultWrapper; +struct CxxCheckpointCountResultWrapper; +struct CxxCheckpointIdResultWrapper; +struct CxxCheckpointsResultWrapper; +struct CxxOrchardShardResultWrapper; +struct CxxOrchardShardTreeCapResultWrapper; +struct CxxShardRootsResultWrapper; + +class CxxOrchardShardTreeDelegate { + public: + explicit CxxOrchardShardTreeDelegate(OrchardStorage& storage, + const mojom::AccountIdPtr& account_id); + ~CxxOrchardShardTreeDelegate(); + + ::rust::Box LastShard( + uint8_t shard_level) const; + ::rust::Box PutShard(const CxxOrchardShard& tree) const; + ::rust::Box GetShard( + const CxxOrchardShardAddress& addr) const; + ::rust::Box GetShardRoots( + uint8_t shard_level) const; + ::rust::Box Truncate( + const CxxOrchardShardAddress& address) const; + ::rust::Box GetCap() const; + ::rust::Box PutCap( + const CxxOrchardShardTreeCap& tree) const; + ::rust::Box MinCheckpointId() const; + ::rust::Box MaxCheckpointId() const; + ::rust::Box AddCheckpoint( + uint32_t checkpoint_id, + const CxxOrchardCheckpoint& checkpoint) const; + ::rust::Box CheckpointCount() const; + ::rust::Box CheckpointAtDepth( + size_t depth) const; + ::rust::Box GetCheckpoint( + uint32_t checkpoint_id) const; + ::rust::Box UpdateCheckpoint( + uint32_t checkpoint_id, + const CxxOrchardCheckpoint& checkpoint) const; + ::rust::Box RemoveCheckpoint( + uint32_t checkpoint_id) const; + ::rust::Box TruncateCheckpoint( + uint32_t checkpoint_id) const; + ::rust::Box GetCheckpoints(size_t limit) const; + + private: + raw_ref storage_; + ::brave_wallet::mojom::AccountIdPtr account_id_; +}; + +} // namespace orchard +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_CXX_ORCHARD_SHARD_TREE_DELEGATE_H_ 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 deleted file mode 100644 index 6ea834e82c2e..000000000000 --- a/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.cc +++ /dev/null @@ -1,54 +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/rust/extended_spending_key_impl.h" - -#include - -#include "base/memory/ptr_util.h" - -namespace brave_wallet::orchard { - -ExtendedSpendingKeyImpl::ExtendedSpendingKeyImpl( - 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 base::WrapUnique( - new ExtendedSpendingKeyImpl(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 base::WrapUnique( - new ExtendedSpendingKeyImpl(mk->unwrap())); - } - return nullptr; -} - -OrchardFullViewKey ExtendedSpendingKeyImpl::GetFullViewKey() { - return extended_spending_key_->full_view_key(); -} - -} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.h b/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.h deleted file mode 100644 index a146c396f38b..000000000000 --- a/components/brave_wallet/browser/zcash/rust/extended_spending_key_impl.h +++ /dev/null @@ -1,48 +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_RUST_EXTENDED_SPENDING_KEY_IMPL_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_EXTENDED_SPENDING_KEY_IMPL_H_ - -#include - -#include "brave/components/brave_wallet/browser/zcash/rust/extended_spending_key.h" -#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" -#include "third_party/rust/cxx/v1/cxx.h" - -namespace brave_wallet::orchard { - -// Implements Orchard key generation from -// https://zips.z.cash/zip-0032#orchard-child-key-derivation -class ExtendedSpendingKeyImpl : ExtendedSpendingKey { - public: - ExtendedSpendingKeyImpl(const ExtendedSpendingKeyImpl&) = delete; - ExtendedSpendingKeyImpl& operator=(const ExtendedSpendingKeyImpl&) = delete; - - ~ExtendedSpendingKeyImpl() override; - - // Derives hardened key using index and the current key - std::unique_ptr DeriveHardenedChild( - uint32_t index) override; - - // Returns public or internal address that may be used as a recipient address - // in transactions - std::optional GetDiversifiedAddress( - uint32_t div_index, - OrchardAddressKind kind) override; - - OrchardFullViewKey GetFullViewKey() override; - - private: - friend class ExtendedSpendingKey; - explicit ExtendedSpendingKeyImpl(rust::Box esk); - // Extended spending key is a root key of an account, all other keys can be - // derived from esk - rust::Box extended_spending_key_; -}; - -} // namespace brave_wallet::orchard - -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_EXTENDED_SPENDING_KEY_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/lib.rs b/components/brave_wallet/browser/zcash/rust/lib.rs index 4c34c3ad2f4e..5c92c3aae9cf 100644 --- a/components/brave_wallet/browser/zcash/rust/lib.rs +++ b/components/brave_wallet/browser/zcash/rust/lib.rs @@ -3,51 +3,81 @@ // 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/. -use std::fmt; +use std::{ + cell::RefCell, cmp::{Ord, Ordering}, + collections::BTreeSet, convert::TryFrom, error, fmt, io::Cursor, marker::PhantomData, + ops::{Add, Bound, RangeBounds, Sub}, rc::Rc, vec}; use orchard::{ builder:: { - BuildError as OrchardBuildError, - InProgress, - Unproven, - Unauthorized - }, - bundle::Bundle, - zip32::ChildIndex as OrchardChildIndex, - keys::Scope as OrchardScope, - keys::FullViewingKey as OrchardFVK, - keys::PreparedIncomingViewingKey, - zip32::Error as Zip32Error, - zip32::ExtendedSpendingKey, - tree::MerkleHashOrchard, - note_encryption:: { - OrchardDomain, - CompactAction - }, + BuildError as OrchardBuildError, InProgress, Unauthorized, Unproven + }, bundle::{commitments, Bundle}, + keys::{FullViewingKey as OrchardFVK, + PreparedIncomingViewingKey, + Scope as OrchardScope, SpendingKey}, note:: { - Nullifier, - ExtractedNoteCommitment - } + ExtractedNoteCommitment, Nullifier, RandomSeed, Rho + }, note_encryption:: { + CompactAction, OrchardDomain + }, keys::SpendAuthorizingKey, + tree::{MerkleHashOrchard, MerklePath}, + value::NoteValue, + zip32::{ + ChildIndex as OrchardChildIndex, + Error as Zip32Error, + ExtendedSpendingKey as OrchardExtendedSpendingKey}, + Anchor }; use zcash_note_encryption::EphemeralKeyBytes; -use zcash_primitives::transaction::components::amount::Amount; - -use ffi::OrchardOutput; +use zcash_protocol::consensus::BlockHeight; +use zcash_primitives::{ + merkle_tree::{read_commitment_tree, HashSer}, + transaction::components::amount::Amount}; -use rand::rngs::OsRng; -use rand::{RngCore, Error as OtherError}; -use rand::CryptoRng; +use incrementalmerkletree::{ + frontier::{self, Frontier}, + Address, + Position, + Retention}; -use brave_wallet::{ - impl_error -}; +use rand::{rngs::OsRng, CryptoRng, Error as OtherError, RngCore}; +use brave_wallet::impl_error; +use std::sync::Arc; use zcash_note_encryption::{ batch, Domain, ShieldedOutput, COMPACT_NOTE_SIZE, }; -use crate::ffi::OrchardCompactAction; +use shardtree::{ + error::ShardTreeError, + store::{Checkpoint, ShardStore, TreeState}, + LocatedPrunableTree, LocatedTree, PrunableTree, RetentionFlags, + ShardTree, +}; + +use zcash_client_backend::serialization::shardtree::{read_shard, write_shard}; +use cxx::UniquePtr; + +use pasta_curves::{group::ff::Field, pallas}; + +use crate::ffi::{ + CxxOrchardShardTreeDelegate, + CxxOrchardShardAddress, + CxxOrchardShardTreeCap, + CxxOrchardCheckpoint, + CxxOrchardCheckpointBundle, + CxxOrchardCheckpointRetention, + CxxOrchardShard, + CxxOrchardCompactAction, + CxxOrchardOutput, + CxxOrchardSpend, + CxxOrchardShardTreeLeaf, + CxxOrchardShardTreeLeafs, + CxxOrchardShardTreeState +}; + +use shardtree::error::QueryError; // The rest of the wallet code should be updated to use this version of unwrap // and then this code can be removed @@ -69,20 +99,15 @@ macro_rules! impl_result { } } - // Unfortunately cxx doesn't support passing $r by value here so - // we have to clone the inner value instead of passing ownership - // This is not really a big deal because eventually we want to - // replace this with mojo which would serialize this anyway - fn unwrap(self: &$r) -> Box<$t> { - Box::new(self.0.as_ref().expect( - "Unhandled error before unwrap call").clone()) + fn unwrap(self: &mut $r) -> Box<$t> { + Box::new((self.0.as_mut().unwrap()).take().unwrap()) } } impl From> for $r { fn from(result: Result<$f, Error>) -> Self { match result { - Ok(v) => Self(Ok($t(v))), + Ok(v) => Self(Ok(Some($t(v)))), Err(e) => Self(Err(e)), } } @@ -90,6 +115,47 @@ macro_rules! impl_result { }; } +use paste::item; + +macro_rules! impl_result_option_wrapper { + ($t: ty, $rt: ident, $l: ident) => { + paste::item! { + fn [](item: $t) -> Box<$rt> { + Box::new($rt(Ok(Option::Some(item)))) + } + fn []() -> Box<$rt> { + Box::new($rt(Err(Error::ShardStoreError))) + } + fn []() -> Box<$rt> { + Box::new($rt(Ok(Option::None))) + } + } + }; +} + +macro_rules! impl_result_wrapper { + ($t: ty, $rt: ident, $l: ident) => { + paste::item! { + fn [](item: $t) -> Box<$rt> { + Box::new($rt(Ok(item))) + } + fn []() -> Box<$rt> { + Box::new($rt(Err(Error::ShardStoreError))) + } + } + }; +} + +pub(crate) const PRUNING_DEPTH: u8 = 100; +pub(crate) const SHARD_HEIGHT: u8 = 16; +pub(crate) const TREE_HEIGHT: u8 = 32; +pub(crate) const CHUNK_SIZE: usize = 1024; + +pub(crate) const TESTING_PRUNING_DEPTH: u8 = 10; +pub(crate) const TESTING_SHARD_HEIGHT: u8 = 4; +pub(crate) const TESTING_TREE_HEIGHT: u8 = 8; +pub(crate) const TESTING_CHUNK_SIZE: usize = 16; + #[derive(Clone)] pub(crate) struct MockRng(u64); @@ -133,10 +199,13 @@ impl RngCore for MockRng { } +#[allow(unused)] #[allow(unsafe_op_in_unsafe_fn)] #[cxx::bridge(namespace = brave_wallet::orchard)] mod ffi { - struct OrchardOutput { + // Represents output of the Orchard transaction. + // https://github.com/zcash/orchard/blob/5c451beb05a10337a57a7fdf279c1dd6a533b805/src/pczt.rs#L210 + struct CxxOrchardOutput { // Amount of zashi being spend value: u32, // Recipient raw Orchard address. @@ -147,113 +216,399 @@ mod ffi { use_memo: bool } - // Encoded orchard output extracted from the transaction - struct OrchardCompactAction { + struct CxxMerkleHash { + hash: [u8; 32] + } + + // Merkle path for the leaf in the merkle tree. + // Used as the witness when signing transaction Orchard inputs. + // https://github.com/zcash/incrementalmerkletree/blob/382d915c068a5691e900e129e58b3da215cba6f2/incrementalmerkletree/src/lib.rs#L606 + struct CxxMerklePath { + position: u32, + auth_path: Vec, + root: CxxMerkleHash + } + + // Represents spending for a single Orchard note. + // https://github.com/zcash/orchard/blob/5c451beb05a10337a57a7fdf279c1dd6a533b805/src/pczt.rs#L129 + struct CxxOrchardSpend { + fvk: [u8; 96], + sk: [u8; 32], + // Note value + value: u32, + addr: [u8; 43], + rho: [u8; 32], + r: [u8; 32], + // Witness merkle path + merkle_path: CxxMerklePath + } + + // Represents compact Orchard action which is a piece of transaction data stored in the blockchain. + // Parts of this action are used for different purposes. + // cmx is used to construct commitment tree, nullifier is used to construct nullifier set. + // ephemeral_key add enc_cipher_text check whether this action is related to the account. + // https://github.com/zcash/orchard/blob/5c451beb05a10337a57a7fdf279c1dd6a533b805/src/note_encryption.rs#L271 + struct CxxOrchardCompactAction { nullifier: [u8; 32], // kOrchardNullifierSize ephemeral_key: [u8; 32], // kOrchardEphemeralKeySize cmx: [u8; 32], // kOrchardCmxSize - enc_cipher_text : [u8; 52] // kOrchardCipherTextSize + enc_cipher_text : [u8; 52], // kOrchardCipherTextSize + block_id: u32, + is_block_last_action: bool } - extern "Rust" { - type OrchardExtendedSpendingKey; - type OrchardUnauthorizedBundle; - type OrchardAuthorizedBundle; + // Represents information about tree state at the end of the block prior to the scan range. + // Used in batch decoding to allow inserting leafs to the tree. + // https://github.com/zcash/incrementalmerkletree/blob/382d915c068a5691e900e129e58b3da215cba6f2/incrementalmerkletree/src/frontier.rs#L35 + #[derive(Clone)] + struct CxxOrchardShardTreeState { + // Frontier is a compressed representation of merkle tree state at some leaf position + // It allows to compute merkle path to the next leafs without storing all the tree + // May be empty if no frontier inserted(In case of append) + frontier: Vec, + // Block height of the previous block prior to the scan range + // The height of the block + block_height: u32, + // Tree size of the tree at the end of the prior block, used to calculate leafs indexes + tree_size: u32 + } + + // Retention of the leaf in the shard tree descibes lifetime of the leaf. + // Leafs which are marked couldn't be pruned and checkpointed leafs also keep tree state for the leaf. + // https://github.com/zcash/incrementalmerkletree/blob/382d915c068a5691e900e129e58b3da215cba6f2/incrementalmerkletree/src/lib.rs#L82 + #[derive(Clone)] + struct CxxOrchardCheckpointRetention { + checkpoint: bool, + marked: bool, + checkpoint_id: u32 + } + + // Represents a leaf of the shard tree as a pair of + // the hash value and leaf retention. + #[derive(Clone)] + struct CxxOrchardShardTreeLeaf { + hash: [u8; 32], + retention: CxxOrchardCheckpointRetention + } - type BatchOrchardDecodeBundle; + #[derive(Clone)] + struct CxxOrchardShardTreeLeafs { + commitments: Vec + } + + // Address of the shard in the shard tree + // https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/incrementalmerkletree/src/lib.rs#L356 + #[derive(Default)] + struct CxxOrchardShardAddress { + level: u8, + index: u32 + } + + // Serialized binary tree representation of the shard tree cap + // To be inserted to the shard tree store + // https://github.com/zcash/librustzcash/blob/205d4c930319b7b6d24aeb4efde69e9b4d1b6f7b/zcash_client_sqlite/src/wallet/commitment_tree.rs#L558 + #[derive(Default)] + struct CxxOrchardShardTreeCap { + data: Vec, + } - type OrchardExtendedSpendingKeyResult; - type OrchardUnauthorizedBundleResult; - type OrchardAuthorizedBundleResult; + // Serialized binart representation of the shard tree shard + // To be inserted to the shard tree store + //https://github.com/zcash/librustzcash/blob/205d4c930319b7b6d24aeb4efde69e9b4d1b6f7b/zcash_client_sqlite/src/wallet/commitment_tree.rs#L478 + #[derive(Default)] + struct CxxOrchardShard { + address: CxxOrchardShardAddress, + // Maybe empty on uncompleted shards + hash: Vec, + data: Vec + } - type BatchOrchardDecodeBundleResult; + // Represents checkpoint data to be stored in the shard tree + // https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/shardtree/src/store.rs#L271 + #[derive(Default)] + struct CxxOrchardCheckpoint { + empty: bool, + position: u32, + mark_removed: Vec + } + + // Checkpoint Id + Checkpoint value + #[derive(Default)] + struct CxxOrchardCheckpointBundle { + checkpoint_id: u32, + checkpoint: CxxOrchardCheckpoint + } + + extern "Rust" { + // Extended spending key is a key used to derive new keys in the Orchard protocol. + // https://zips.z.cash/zip-0032#orchard-extended-keys + type CxxOrchardExtendedSpendingKey; + // Orchard transation part in unsigned state. + // https://github.com/zcash/orchard/blob/5c451beb05a10337a57a7fdf279c1dd6a533b805/src/bundle.rs#L152 + type CxxOrchardUnauthorizedBundle; + // Signed and ready-to-use Orchard transaction part. + type CxxOrchardAuthorizedBundle; + // Result of the batch decoding. + // Contains discovered notes and all nullifiers + // along with the data needed to update the shard tree. + type CxxOrchardDecodedBlocksBundle; + // Original shard tree with size of 32. + type CxxOrchardShardTree; + // Testing shard tree with size of 8. + type CxxOrchardTestingShardTree; + // Merkle path in the shard tree for the provided leaf position. + type CxxOrchardWitness; + + type CxxOrchardExtendedSpendingKeyResult; + type CxxOrchardUnauthorizedBundleResult; + type CxxOrchardAuthorizedBundleResult; + type CxxOrchardWitnessResult; + type CxxOrchardDecodedBlocksBundleResult; + type CxxOrchardTestingShardTreeResult; + type CxxOrchardShardTreeResult; + + type CxxOrchardShardResultWrapper; + type CxxBoolResultWrapper; + type CxxOrchardShardTreeCapResultWrapper; + type CxxCheckpointIdResultWrapper; + type CxxCheckpointBundleResultWrapper; + type CxxCheckpointCountResultWrapper; + type CxxCheckpointsResultWrapper; + type CxxShardRootsResultWrapper; // OsRng is used fn create_orchard_bundle( tree_state: &[u8], - outputs: Vec - ) -> Box; + spends: Vec, + outputs: Vec + ) -> Box; // Creates orchard bundle with mocked rng using provided rng seed. // Must not be used in production, only in tests. fn create_testing_orchard_bundle( tree_state: &[u8], - outputs: Vec, + spends: Vec, + outputs: Vec, rng_seed: u64 - ) -> Box; + ) -> Box; fn generate_orchard_extended_spending_key_from_seed( bytes: &[u8] - ) -> Box; + ) -> Box; - fn is_ok(self: &OrchardExtendedSpendingKeyResult) -> bool; - fn error_message(self: &OrchardExtendedSpendingKeyResult) -> String; - fn unwrap(self: &OrchardExtendedSpendingKeyResult) -> Box; + fn is_ok(self: &CxxOrchardExtendedSpendingKeyResult) -> bool; + fn error_message(self: &CxxOrchardExtendedSpendingKeyResult) -> String; + fn unwrap(self: &mut CxxOrchardExtendedSpendingKeyResult) -> Box; fn batch_decode( fvk_bytes: &[u8; 96], // Array size should match kOrchardFullViewKeySize - actions: Vec - ) -> Box; + prior_tree_state: CxxOrchardShardTreeState, + actions: Vec + ) -> Box; fn derive( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, index: u32 - ) -> Box; + ) -> Box; // External addresses can be used for receiving funds from external // senders. fn external_address( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, diversifier_index: u32 ) -> [u8; 43]; // Array size should match kOrchardRawBytesSize // Internal addresses are used for change or internal shielding and // shouldn't be exposed to public. fn internal_address( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, diversifier_index: u32 ) -> [u8; 43]; // Array size should match kOrchardRawBytesSize fn full_view_key( - self: &OrchardExtendedSpendingKey + self: &CxxOrchardExtendedSpendingKey ) -> [u8; 96]; // Array size sohuld match kOrchardFullViewKeySize - fn is_ok(self: &OrchardAuthorizedBundleResult) -> bool; - fn error_message(self: &OrchardAuthorizedBundleResult) -> String; - fn unwrap(self: &OrchardAuthorizedBundleResult) -> Box; + fn spending_key( + self: &CxxOrchardExtendedSpendingKey + ) -> [u8; 32]; // Array size should match kSpendingKeySize + + fn is_ok(self: &CxxOrchardAuthorizedBundleResult) -> bool; + fn error_message(self: &CxxOrchardAuthorizedBundleResult) -> String; + fn unwrap(self: &mut CxxOrchardAuthorizedBundleResult) -> Box; - fn is_ok(self: &OrchardUnauthorizedBundleResult) -> bool; - fn error_message(self: &OrchardUnauthorizedBundleResult) -> String; - fn unwrap(self: &OrchardUnauthorizedBundleResult) -> Box; + fn is_ok(self: &CxxOrchardUnauthorizedBundleResult) -> bool; + fn error_message(self: &CxxOrchardUnauthorizedBundleResult) -> String; + fn unwrap(self: &mut CxxOrchardUnauthorizedBundleResult) -> Box; - fn is_ok(self: &BatchOrchardDecodeBundleResult) -> bool; - fn error_message(self: &BatchOrchardDecodeBundleResult) -> String; - fn unwrap(self: &BatchOrchardDecodeBundleResult) -> Box; + fn is_ok(self: &CxxOrchardDecodedBlocksBundleResult) -> bool; + fn error_message(self: &CxxOrchardDecodedBlocksBundleResult) -> String; + fn unwrap(self: &mut CxxOrchardDecodedBlocksBundleResult) -> Box; - fn size(self :&BatchOrchardDecodeBundle) -> usize; - fn note_value(self :&BatchOrchardDecodeBundle, index: usize) -> u32; + fn size(self :&CxxOrchardDecodedBlocksBundle) -> usize; + fn note_value(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32; // Result array size should match kOrchardNullifierSize // fvk array size should match kOrchardFullViewKeySize - fn note_nullifier(self :&BatchOrchardDecodeBundle, fvk: &[u8; 96], index: usize) -> [u8; 32]; + fn note_nullifier(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32]; + fn note_rho(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32]; + fn note_rseed(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32]; + fn note_addr(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 43]; + fn note_block_height(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32; + fn note_commitment_tree_position(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32; + + fn is_ok(self: &CxxOrchardShardTreeResult) -> bool; + fn error_message(self: &CxxOrchardShardTreeResult) -> String; + fn unwrap(self: &mut CxxOrchardShardTreeResult) -> Box; + + fn is_ok(self: &CxxOrchardTestingShardTreeResult) -> bool; + fn error_message(self: &CxxOrchardTestingShardTreeResult) -> String; + fn unwrap(self: &mut CxxOrchardTestingShardTreeResult) -> Box; // Orchard digest is desribed here https://zips.z.cash/zip-0244#t-4-orchard-digest // Used in constructing signature digest and tx id - fn orchard_digest(self: &OrchardUnauthorizedBundle) -> [u8; 32]; // Array size should match kZCashDigestSize + fn orchard_digest(self: &CxxOrchardUnauthorizedBundle) -> [u8; 32]; // Array size should match kZCashDigestSize // Completes unauthorized bundle to authorized state // Signature digest should be constructed as desribed in https://zips.z.cash/zip-0244#signature-digest - fn complete(self: &OrchardUnauthorizedBundle, sighash: [u8; 32]) -> Box; // Array size should match kZCashDigestSize + fn complete(self: &CxxOrchardUnauthorizedBundle, sighash: [u8; 32]) -> Box; // Array size should match kZCashDigestSize // Orchard part of v5 transaction as described in // https://zips.z.cash/zip-0225 - fn raw_tx(self: &OrchardAuthorizedBundle) -> Vec; + fn raw_tx(self: &CxxOrchardAuthorizedBundle) -> Vec; + + // Witness is used to construct zk-proof for the transaction + fn is_ok(self: &CxxOrchardWitnessResult) -> bool; + fn error_message(self: &CxxOrchardWitnessResult) -> String; + fn unwrap(self: &mut CxxOrchardWitnessResult) -> Box; + fn size(self :&CxxOrchardWitness) -> usize; + fn item(self: &CxxOrchardWitness, index: usize) -> [u8; 32]; + + // Creates shard tree of default orchard height + fn create_orchard_shard_tree( + delegate: UniquePtr + ) -> Box; + // Creates shard tree of smaller size for testing purposes + fn create_orchard_testing_shard_tree( + delegate: UniquePtr + ) -> Box; + + fn insert_commitments( + self: &mut CxxOrchardShardTree, + scan_result: &mut CxxOrchardDecodedBlocksBundle) -> bool; + fn calculate_witness( + self: &mut CxxOrchardShardTree, + commitment_tree_position: u32, + checkpoint: u32) -> Box; + fn truncate(self: &mut CxxOrchardShardTree, checkpoint_id: u32) -> bool; + + fn insert_commitments( + self: &mut CxxOrchardTestingShardTree, + scan_result: &mut CxxOrchardDecodedBlocksBundle) -> bool; + fn calculate_witness( + self: &mut CxxOrchardTestingShardTree, + commitment_tree_position: u32, + checkpoint: u32) -> Box; + fn truncate(self: &mut CxxOrchardTestingShardTree, checkpoint_id: u32) -> bool; + + // Size matches kOrchardCmxSize in zcash_utils + fn create_mock_commitment(position: u32, seed: u32) -> [u8; 32]; + fn create_mock_decode_result( + prior_tree_state: CxxOrchardShardTreeState, + commitments: CxxOrchardShardTreeLeafs) -> Box; + + fn wrap_shard_tree_shard(item: CxxOrchardShard) -> Box; + fn wrap_shard_tree_shard_error()-> Box; + fn wrap_shard_tree_shard_none()-> Box; + + fn wrap_bool(item : bool) -> Box; + fn wrap_bool_error() -> Box; + + fn wrap_shard_tree_cap(item :CxxOrchardShardTreeCap) -> Box; + fn wrap_shard_tree_cap_error() -> Box; + fn wrap_shard_tree_cap_none() -> Box; + + fn wrap_checkpoint_id(item : u32) -> Box; + fn wrap_checkpoint_id_error() -> Box; + fn wrap_checkpoint_id_none() -> Box; + + fn wrap_checkpoint_bundle(item: CxxOrchardCheckpointBundle) -> Box; + fn wrap_checkpoint_bundle_error() -> Box; + fn wrap_checkpoint_bundle_none() -> Box; + + fn wrap_checkpoint_count(item: usize) -> Box; + fn wrap_checkpoint_count_error() -> Box; + + fn wrap_checkpoints(item: Vec) -> Box; + fn wrap_checkpoints_error() -> Box; + + fn wrap_shard_tree_roots(item: Vec) -> Box; + fn wrap_shard_tree_roots_error() -> Box; } + + unsafe extern "C++" { + include!("brave/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h"); + + type CxxOrchardShardTreeDelegate; + + fn LastShard( + &self, shard_level: u8) -> Box; + fn GetShard( + &self, + addr: &CxxOrchardShardAddress)-> Box; + fn PutShard( + &self, + tree: &CxxOrchardShard) -> Box; + fn GetShardRoots( + &self, shard_level: u8) -> Box; + fn Truncate( + &self, + address: &CxxOrchardShardAddress) -> Box; + fn GetCap( + &self) -> Box; + fn PutCap( + &self, + tree: &CxxOrchardShardTreeCap) -> Box; + fn MinCheckpointId( + &self) -> Box; + fn MaxCheckpointId( + &self) -> Box; + fn AddCheckpoint( + &self, + checkpoint_id: u32, + checkpoint: &CxxOrchardCheckpoint) -> Box; + fn UpdateCheckpoint( + &self, + checkpoint_id: u32, + checkpoint: &CxxOrchardCheckpoint) -> Box; + fn CheckpointCount( + &self) -> Box; + fn CheckpointAtDepth( + &self, + depth: usize) -> Box; + fn GetCheckpoint( + &self, + checkpoint_id: u32) -> Box; + fn RemoveCheckpoint( + &self, + checkpoint_id: u32) -> Box; + fn TruncateCheckpoint( + &self, + checkpoint_id: u32) -> Box; + fn GetCheckpoints( + &self, + limit: usize) -> Box; + } + } #[derive(Debug)] pub enum Error { Zip32(Zip32Error), - OrchardBuilder(OrchardBuildError), + WrongInputError, WrongOutputError, BuildError, FvkError, OrchardActionFormatError, + ShardStoreError, + OrchardBuilder(OrchardBuildError), + WitnessError, + SpendError, } impl_error!(Zip32Error, Zip32); @@ -267,11 +622,21 @@ impl fmt::Display for Error { Error::WrongOutputError => write!(f, "Error: Can't parse output"), Error::BuildError => write!(f, "Error, build error"), Error::OrchardActionFormatError => write!(f, "Error, orchard action format error"), - Error::FvkError => write!(f, "Error, fvk format error") + Error::FvkError => write!(f, "Error, fvk format error"), + Error::ShardStoreError => write!(f, "Shard store error"), + Error::WrongInputError => write!(f, "Wrong input error"), + Error::WitnessError => write!(f, "Witness error"), + Error::SpendError => write!(f, "Spend error"), } } } +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(self) + } +} + // Different random sources are used for testing and for release // Since Orchard uses randomness we need to mock it to get // deterministic resuluts in tests. @@ -286,7 +651,8 @@ enum OrchardRandomSource { #[derive(Clone)] pub struct OrchardUnauthorizedBundleValue { unauthorized_bundle: Bundle, Amount>, - rng: OrchardRandomSource + rng: OrchardRandomSource, + asks: Vec } // Authorized bundle is a bundle where inputs are signed with signature digests @@ -298,54 +664,109 @@ pub struct OrchardAuthorizedBundleValue { #[derive(Clone)] pub struct DecryptedOrchardOutput { - note: ::Note + note: ::Note, + block_height: u32, + commitment_tree_position: u32 } + #[derive(Clone)] -pub struct BatchOrchardDecodeBundleValue { - outputs: Vec +pub struct OrchardDecodedBlocksBundleValue { + fvk: [u8; 96], + outputs: Vec, + commitments: Vec<(MerkleHashOrchard, Retention)>, + prior_tree_state: CxxOrchardShardTreeState } -#[derive(Clone)] -struct OrchardExtendedSpendingKey(ExtendedSpendingKey); -#[derive(Clone)] -struct OrchardAuthorizedBundle(OrchardAuthorizedBundleValue); -#[derive(Clone)] -struct OrchardUnauthorizedBundle(OrchardUnauthorizedBundleValue); -#[derive(Clone)] -struct BatchOrchardDecodeBundle(BatchOrchardDecodeBundleValue); +pub struct OrchardWitnessValue { + path: MarkleHashVec +} -struct OrchardExtendedSpendingKeyResult(Result); -struct OrchardAuthorizedBundleResult(Result); -struct OrchardUnauthorizedBundleResult(Result); -struct BatchOrchardDecodeBundleResult(Result); +pub struct OrchardGenericShardTreeBundleValue { + tree: ShardTree, T, S> +} -impl_result!(OrchardExtendedSpendingKey, OrchardExtendedSpendingKeyResult, ExtendedSpendingKey); -impl_result!(OrchardAuthorizedBundle, OrchardAuthorizedBundleResult, OrchardAuthorizedBundleValue); -impl_result!(OrchardUnauthorizedBundle, OrchardUnauthorizedBundleResult, OrchardUnauthorizedBundleValue); +type OrchardShardTreeValue = + OrchardGenericShardTreeBundleValue; +type OrchardTestingShardTreeValue = + OrchardGenericShardTreeBundleValue; -impl_result!(BatchOrchardDecodeBundle, BatchOrchardDecodeBundleResult, BatchOrchardDecodeBundleValue); +#[derive(Clone)] +pub struct MarkleHashVec(Vec); + +impl From> for MarkleHashVec { + fn from(item: incrementalmerkletree::MerklePath) -> Self { + let mut result : Vec = vec![]; + for elem in item.path_elems() { + result.push(*elem); + } + MarkleHashVec(result) + } +} + +struct CxxOrchardExtendedSpendingKey(OrchardExtendedSpendingKey); +struct CxxOrchardAuthorizedBundle(OrchardAuthorizedBundleValue); +struct CxxOrchardUnauthorizedBundle(OrchardUnauthorizedBundleValue); +struct CxxOrchardDecodedBlocksBundle(OrchardDecodedBlocksBundleValue); +struct CxxOrchardShardTree(OrchardShardTreeValue); +struct CxxOrchardTestingShardTree(OrchardTestingShardTreeValue); +struct CxxOrchardWitness(OrchardWitnessValue); + +struct CxxOrchardExtendedSpendingKeyResult(Result, Error>); +struct CxxOrchardAuthorizedBundleResult(Result, Error>); +struct CxxOrchardUnauthorizedBundleResult(Result, Error>); +struct CxxOrchardDecodedBlocksBundleResult(Result, Error>); +struct CxxOrchardShardTreeResult(Result, Error>); +struct CxxOrchardWitnessResult(Result, Error>); +struct CxxOrchardTestingShardTreeResult(Result, Error>); + +impl_result!(CxxOrchardExtendedSpendingKey, CxxOrchardExtendedSpendingKeyResult, OrchardExtendedSpendingKey); +impl_result!(CxxOrchardAuthorizedBundle, CxxOrchardAuthorizedBundleResult, OrchardAuthorizedBundleValue); +impl_result!(CxxOrchardUnauthorizedBundle, CxxOrchardUnauthorizedBundleResult, OrchardUnauthorizedBundleValue); +impl_result!(CxxOrchardDecodedBlocksBundle, CxxOrchardDecodedBlocksBundleResult, OrchardDecodedBlocksBundleValue); +impl_result!(CxxOrchardShardTree, CxxOrchardShardTreeResult, OrchardShardTreeValue); +impl_result!(CxxOrchardTestingShardTree, CxxOrchardTestingShardTreeResult, OrchardTestingShardTreeValue); +impl_result!(CxxOrchardWitness, CxxOrchardWitnessResult, OrchardWitnessValue); + +// Shard store interface results +struct CxxOrchardShardResultWrapper(Result, Error>); +struct CxxBoolResultWrapper(Result); +struct CxxOrchardShardTreeCapResultWrapper(Result, Error>); +struct CxxCheckpointIdResultWrapper(Result, Error>); +struct CxxCheckpointBundleResultWrapper(Result, Error>); +struct CxxCheckpointsResultWrapper(Result, Error>); +struct CxxShardRootsResultWrapper(Result, Error>); +struct CxxCheckpointCountResultWrapper(Result); + +impl_result_option_wrapper!(CxxOrchardShard, CxxOrchardShardResultWrapper, shard_tree_shard); +impl_result_option_wrapper!(CxxOrchardShardTreeCap, CxxOrchardShardTreeCapResultWrapper, shard_tree_cap); +impl_result_option_wrapper!(u32, CxxCheckpointIdResultWrapper, checkpoint_id); +impl_result_option_wrapper!(CxxOrchardCheckpointBundle, CxxCheckpointBundleResultWrapper, checkpoint_bundle); +impl_result_wrapper!(bool, CxxBoolResultWrapper, bool); +impl_result_wrapper!(usize, CxxCheckpointCountResultWrapper, checkpoint_count); +impl_result_wrapper!(Vec, CxxCheckpointsResultWrapper, checkpoints); +impl_result_wrapper!(Vec, CxxShardRootsResultWrapper, shard_tree_roots); fn generate_orchard_extended_spending_key_from_seed( bytes: &[u8] -) -> Box { - Box::new(OrchardExtendedSpendingKeyResult::from( - ExtendedSpendingKey::master(bytes).map_err(Error::from)) +) -> Box { + Box::new(CxxOrchardExtendedSpendingKeyResult::from( + OrchardExtendedSpendingKey::master(bytes).map_err(Error::from)) ) } -impl OrchardExtendedSpendingKey { +impl CxxOrchardExtendedSpendingKey { fn derive( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, index: u32 - ) -> Box { - Box::new(OrchardExtendedSpendingKeyResult::from( + ) -> Box { + Box::new(CxxOrchardExtendedSpendingKeyResult::from( self.0.derive_child( OrchardChildIndex::hardened(index)) .map_err(Error::from))) } fn external_address( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, diversifier_index: u32 ) -> [u8; 43] { let address = OrchardFVK::from(&self.0).address_at( @@ -354,7 +775,7 @@ impl OrchardExtendedSpendingKey { } fn internal_address( - self: &OrchardExtendedSpendingKey, + self: &CxxOrchardExtendedSpendingKey, diversifier_index: u32 ) -> [u8; 43] { let address = OrchardFVK::from(&self.0).address_at( @@ -363,33 +784,38 @@ impl OrchardExtendedSpendingKey { } fn full_view_key( - self: &OrchardExtendedSpendingKey + self: &CxxOrchardExtendedSpendingKey ) -> [u8; 96] { OrchardFVK::from(&self.0).to_bytes() } + + fn spending_key( + self: &CxxOrchardExtendedSpendingKey + ) -> [u8; 32] { + *self.0.sk().to_bytes() + } } -impl OrchardAuthorizedBundle { - fn raw_tx(self: &OrchardAuthorizedBundle) -> Vec { +impl CxxOrchardAuthorizedBundle { + fn raw_tx(self: &CxxOrchardAuthorizedBundle) -> Vec { self.0.raw_tx.clone() } } fn create_orchard_builder_internal( orchard_tree_bytes: &[u8], - outputs: Vec, + spends: Vec, + outputs: Vec, random_source: OrchardRandomSource -) -> Box { - use orchard::Anchor; - use zcash_primitives::merkle_tree::read_commitment_tree; - +) -> Box { // To construct transaction orchard tree state of some block should be provided // But in tests we can use empty anchor. let anchor = if orchard_tree_bytes.len() > 0 { match read_commitment_tree::( &orchard_tree_bytes[..]) { Ok(tree) => Anchor::from(tree.root()), - Err(_e) => return Box::new(OrchardUnauthorizedBundleResult::from(Err(Error::from(OrchardBuildError::AnchorMismatch)))), + Err(_e) => return Box::new(CxxOrchardUnauthorizedBundleResult::from( + Err(Error::from(OrchardBuildError::AnchorMismatch)))), } } else { orchard::Anchor::empty_tree() @@ -399,24 +825,87 @@ fn create_orchard_builder_internal( orchard::builder::BundleType::DEFAULT, anchor); + let mut asks: Vec = vec![]; + + for spend in spends { + let fvk = OrchardFVK::from_bytes(&spend.fvk); + if fvk.is_none().into() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::FvkError))) + } + + let auth_path = spend.merkle_path.auth_path.iter().map(|v| { + let hash = MerkleHashOrchard::from_bytes(&v.hash); + if hash.is_some().into() { + Ok(hash.unwrap()) + } else { + Err(Error::WitnessError) + } + }).collect::, _>>(); + + if auth_path.is_err() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::WitnessError))) + } + + let auth_path_sized : Result<[MerkleHashOrchard; orchard::NOTE_COMMITMENT_TREE_DEPTH], _> = auth_path.unwrap().try_into(); + if auth_path_sized.is_err() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::WitnessError))) + } + + let merkle_path = MerklePath::from_parts( + spend.merkle_path.position, + auth_path_sized.unwrap(), + ); + + let rho = Rho::from_bytes(&spend.rho); + if rho.is_none().into() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::OrchardActionFormatError))) + } + let rseed = RandomSeed::from_bytes(spend.r, &rho.unwrap()); + if rseed.is_none().into() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::OrchardActionFormatError))) + } + let addr = orchard::Address::from_raw_address_bytes(&spend.addr); + if addr.is_none().into() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::WrongInputError))) + } + + let note = orchard::Note::from_parts( + addr.unwrap(), + NoteValue::from_raw(u64::from(spend.value)), + rho.unwrap().clone(), + rseed.unwrap()); + + if note.is_none().into() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::OrchardActionFormatError))) + } + + let add_spend_result = builder.add_spend(fvk.unwrap(), note.unwrap(), merkle_path); + if add_spend_result.is_err() { + return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::SpendError))) + } + asks.push(SpendAuthorizingKey::from(&SpendingKey::from_bytes(spend.sk).unwrap())); + } + for out in outputs { let _ = match Option::from(orchard::Address::from_raw_address_bytes(&out.addr)) { Some(addr) => { builder.add_output(None, addr, - orchard::value::NoteValue::from_raw(u64::from(out.value)), if out.use_memo { Some(out.memo)} else { Option::None }) + orchard::value::NoteValue::from_raw( + u64::from(out.value)), if out.use_memo { Some(out.memo)} else { Option::None }) }, - None => return Box::new(OrchardUnauthorizedBundleResult::from(Err(Error::WrongOutputError))) + None => return Box::new(CxxOrchardUnauthorizedBundleResult::from(Err(Error::WrongOutputError))) }; } - Box::new(OrchardUnauthorizedBundleResult::from(match random_source { + Box::new(CxxOrchardUnauthorizedBundleResult::from(match random_source { OrchardRandomSource::OsRng(mut rng) => { builder.build(&mut rng) .map_err(Error::from) .and_then(|builder| { builder.map(|bundle| OrchardUnauthorizedBundleValue { unauthorized_bundle: bundle.0, - rng: OrchardRandomSource::OsRng(rng) }).ok_or(Error::BuildError) + rng: OrchardRandomSource::OsRng(rng), + asks: asks }).ok_or(Error::BuildError) }) }, OrchardRandomSource::MockRng(mut rng) => { @@ -425,7 +914,8 @@ fn create_orchard_builder_internal( .and_then(|builder| { builder.map(|bundle| OrchardUnauthorizedBundleValue { unauthorized_bundle: bundle.0, - rng: OrchardRandomSource::MockRng(rng) }).ok_or(Error::BuildError) + rng: OrchardRandomSource::MockRng(rng), asks: asks + }).ok_or(Error::BuildError) }) } })) @@ -433,27 +923,29 @@ fn create_orchard_builder_internal( fn create_orchard_bundle( orchard_tree_bytes: &[u8], - outputs: Vec -) -> Box { - create_orchard_builder_internal(orchard_tree_bytes, outputs, OrchardRandomSource::OsRng(OsRng)) + spends: Vec, + outputs: Vec +) -> Box { + create_orchard_builder_internal(orchard_tree_bytes, spends, outputs, OrchardRandomSource::OsRng(OsRng)) } fn create_testing_orchard_bundle( orchard_tree_bytes: &[u8], - outputs: Vec, + spends: Vec, + outputs: Vec, rng_seed: u64 -) -> Box { - create_orchard_builder_internal(orchard_tree_bytes, outputs, OrchardRandomSource::MockRng(MockRng(rng_seed))) +) -> Box { + create_orchard_builder_internal(orchard_tree_bytes, spends, outputs, OrchardRandomSource::MockRng(MockRng(rng_seed))) } -impl OrchardUnauthorizedBundle { - fn orchard_digest(self: &OrchardUnauthorizedBundle) -> [u8; 32] { +impl CxxOrchardUnauthorizedBundle { + fn orchard_digest(self: &CxxOrchardUnauthorizedBundle) -> [u8; 32] { self.0.unauthorized_bundle.commitment().into() } - fn complete(self: &OrchardUnauthorizedBundle, sighash: [u8; 32]) -> Box { + fn complete(self: &CxxOrchardUnauthorizedBundle, sighash: [u8; 32]) -> Box { use zcash_primitives::transaction::components::orchard::write_v5_bundle; - Box::new(OrchardAuthorizedBundleResult::from(match self.0.rng.clone() { + Box::new(CxxOrchardAuthorizedBundleResult::from(match self.0.rng.clone() { OrchardRandomSource::OsRng(mut rng) => { self.0.unauthorized_bundle.clone() .create_proof(&orchard::circuit::ProvingKey::build(), &mut rng) @@ -461,7 +953,7 @@ impl OrchardUnauthorizedBundle { b.apply_signatures( &mut rng, sighash, - &[], + &self.0.asks, ) }) }, @@ -472,7 +964,7 @@ impl OrchardUnauthorizedBundle { b.apply_signatures( &mut rng, sighash, - &[], + &self.0.asks, ) }) } @@ -484,7 +976,7 @@ impl OrchardUnauthorizedBundle { } } -impl ShieldedOutput for OrchardCompactAction { +impl ShieldedOutput for CxxOrchardCompactAction { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.ephemeral_key) } @@ -500,11 +992,12 @@ impl ShieldedOutput for OrchardCompactAction { fn batch_decode( fvk_bytes: &[u8; 96], - actions: Vec -) -> Box { + prior_tree_state: CxxOrchardShardTreeState, + actions: Vec +) -> Box { let fvk = match OrchardFVK::from_bytes(fvk_bytes) { Some(fvk) => fvk, - None => return Box::new(BatchOrchardDecodeBundleResult::from(Err(Error::FvkError))) + None => return Box::new(CxxOrchardDecodedBlocksBundleResult::from(Err(Error::FvkError))) }; let ivks = [ @@ -512,7 +1005,7 @@ fn batch_decode( PreparedIncomingViewingKey::new(&fvk.to_ivk(OrchardScope::Internal)) ]; - let input_actions: Result, Error> = actions + let input_actions: Result, Error> = actions .into_iter() .map(|v| { let nullifier_ctopt = Nullifier::from_bytes(&v.nullifier); @@ -532,7 +1025,8 @@ fn batch_decode( let ephemeral_key = EphemeralKeyBytes(v.ephemeral_key); let enc_cipher_text = v.enc_cipher_text; - let compact_action = CompactAction::from_parts(nullifier, cmx, ephemeral_key, enc_cipher_text); + let compact_action = + CompactAction::from_parts(nullifier, cmx, ephemeral_key, enc_cipher_text); let orchard_domain = OrchardDomain::for_compact_action(&compact_action); Ok((orchard_domain, v)) @@ -541,34 +1035,626 @@ fn batch_decode( let input_actions = match input_actions { Ok(actions) => actions, - Err(e) => return Box::new(BatchOrchardDecodeBundleResult::from(Err(e.into()))) + Err(e) => return Box::new(CxxOrchardDecodedBlocksBundleResult::from(Err(e.into()))) }; - let decrypted_outputs = batch::try_compact_note_decryption(&ivks, &input_actions.as_slice()) - .into_iter() - .map(|res| { - res.map(|((note, _recipient), _ivk_idx)| DecryptedOrchardOutput { - note: note - }) - }) - .filter_map(|x| x) - .collect::>(); + let mut decrypted_len = 0; + let (decrypted_opts, _decrypted_len) = ( + batch::try_compact_note_decryption(&ivks, &input_actions) + .into_iter() + .map(|v| { + v.map(|((note, _), ivk_idx)| { + decrypted_len += 1; + (ivks[ivk_idx].clone(), note) + }) + }) + .collect::>(), + decrypted_len, + ); + + let mut found_notes: Vec = vec![]; + let mut note_commitments: Vec<(MerkleHashOrchard, Retention)> = vec![]; + + for (output_idx, ((_, output), decrypted_note)) in + input_actions.iter().zip(decrypted_opts).enumerate() { + // If the commitment is the last in the block, ensure that is is retained as a checkpoint + let is_checkpoint = &output.is_block_last_action; + let block_id = &output.block_id; + let retention : Retention = match (decrypted_note.is_some(), is_checkpoint) { + (is_marked, true) => Retention::Checkpoint { + id: BlockHeight::from_u32(*block_id), + is_marked, + }, + (true, false) => Retention::Marked, + (false, false) => Retention::Ephemeral, + }; + let commitment = MerkleHashOrchard::from_bytes(&output.cmx); + if commitment.is_none().into() { + return Box::new(CxxOrchardDecodedBlocksBundleResult::from(Err(Error::OrchardActionFormatError))) + } + note_commitments.push((commitment.unwrap(), retention)); + + if let Some((_key_id, note)) = decrypted_note { + found_notes.push(DecryptedOrchardOutput{ + note: note, + block_height: output.block_id, + commitment_tree_position: (output_idx as u32) + prior_tree_state.tree_size + }); + } + } - Box::new(BatchOrchardDecodeBundleResult::from(Ok(BatchOrchardDecodeBundleValue { outputs: decrypted_outputs }))) + Box::new(CxxOrchardDecodedBlocksBundleResult::from(Ok(OrchardDecodedBlocksBundleValue { + fvk: *fvk_bytes, + outputs: found_notes, + commitments: note_commitments, + prior_tree_state: prior_tree_state + }))) } -impl BatchOrchardDecodeBundle { - fn size(self :&BatchOrchardDecodeBundle) -> usize { +impl CxxOrchardDecodedBlocksBundle { + fn size(self :&CxxOrchardDecodedBlocksBundle) -> usize { self.0.outputs.len() } - fn note_value(self :&BatchOrchardDecodeBundle, index: usize) -> u32 { + fn note_value(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32 { u32::try_from(self.0.outputs[index].note.value().inner()).expect( "Outputs are always created from a u32, so conversion back will always succeed") } - fn note_nullifier(self :&BatchOrchardDecodeBundle, fvk: &[u8; 96], index: usize) -> [u8; 32] { - self.0.outputs[index].note.nullifier(&OrchardFVK::from_bytes(fvk).unwrap()).to_bytes() + fn note_nullifier(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32] { + self.0.outputs[index].note.nullifier(&OrchardFVK::from_bytes(&self.0.fvk).unwrap()).to_bytes() + } + + fn note_block_height(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32 { + self.0.outputs[index].block_height + } + + fn note_rho(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32] { + self.0.outputs[index].note.rho().to_bytes() + + } + + fn note_rseed(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 32] { + *self.0.outputs[index].note.rseed().as_bytes() } + + fn note_addr(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> [u8; 43] { + self.0.outputs[index].note.recipient().to_raw_address_bytes() + } + + fn note_commitment_tree_position(self :&CxxOrchardDecodedBlocksBundle, index: usize) -> u32 { + self.0.outputs[index].commitment_tree_position + } +} + +fn insert_frontier( + tree: &mut ShardTree, COMMITMENT_TREE_DEPTH, SHARD_HEIGHT>, + frontier: &Vec +) -> bool { + let frontier_commitment_tree = read_commitment_tree::( + &frontier[..]); + + if frontier_commitment_tree.is_err() { + return false; + } + + let frontier_result = tree.insert_frontier( + frontier_commitment_tree.unwrap().to_frontier(), + Retention::Marked, + ); + + frontier_result.is_ok() +} + +fn insert_commitments( + shard_tree: &mut ShardTree, COMMITMENT_TREE_DEPTH, SHARD_HEIGHT>, + scan_result: &mut CxxOrchardDecodedBlocksBundle) -> bool { + let start_position : u64 = scan_result.0.prior_tree_state.tree_size.into(); + + if !scan_result.0.prior_tree_state.frontier.is_empty() { + let frontier_result = insert_frontier::( + shard_tree, &scan_result.0.prior_tree_state.frontier); + if !frontier_result { + return false; + } + } + + let batch_insert_result = shard_tree.batch_insert( + Position::from(start_position), + scan_result.0.commitments.clone().into_iter()); + + if batch_insert_result.is_err() { + return false; + } + + true +} + +impl From<&[MerkleHashOrchard]> for MarkleHashVec { + fn from(item: &[MerkleHashOrchard]) -> Self { + let mut result : Vec = vec![]; + for elem in item { + result.push(*elem); + } + MarkleHashVec(result) + } +} + +impl CxxOrchardShardTree { + fn insert_commitments(self: &mut CxxOrchardShardTree, + scan_result: &mut CxxOrchardDecodedBlocksBundle) -> bool { + insert_commitments::(&mut self.0.tree, scan_result) + } + + fn calculate_witness(self: &mut CxxOrchardShardTree, + commitment_tree_position: u32, + checkpoint: u32) -> Box { + match self.0.tree.witness_at_checkpoint_id_caching(( + commitment_tree_position as u64).into(), &checkpoint.into()) { + Ok(witness) => Box::new(CxxOrchardWitnessResult::from( + Ok(OrchardWitnessValue { path: witness.path_elems().into() }))), + Err(_e) => Box::new(CxxOrchardWitnessResult::from(Err(Error::WitnessError))) + } + } + + fn truncate(self: &mut CxxOrchardShardTree, checkpoint: u32) -> bool { + self.0.tree.truncate_removing_checkpoint(&BlockHeight::from_u32(checkpoint)).is_ok() + } +} + +impl CxxOrchardTestingShardTree { + fn insert_commitments(self: &mut CxxOrchardTestingShardTree, scan_result: &mut CxxOrchardDecodedBlocksBundle) -> bool { + insert_commitments::(&mut self.0.tree, scan_result) + } + + fn calculate_witness(self: &mut CxxOrchardTestingShardTree, + commitment_tree_position: u32, checkpoint: u32) -> Box { + match self.0.tree.witness_at_checkpoint_id_caching((commitment_tree_position as u64).into(), &checkpoint.into()) { + Ok(witness) => Box::new(CxxOrchardWitnessResult::from(Ok(OrchardWitnessValue { path: witness.into() }))), + Err(_e) => Box::new(CxxOrchardWitnessResult::from(Err(Error::WitnessError))) + } + } + + fn truncate(self: &mut CxxOrchardTestingShardTree, checkpoint: u32) -> bool { + let result = self.0.tree.truncate_removing_checkpoint(&BlockHeight::from_u32(checkpoint)); + return result.is_ok() && result.unwrap(); + } +} + +impl CxxOrchardWitness { + fn size(self: &CxxOrchardWitness) -> usize { + self.0.path.0.len() + } + + fn item(self: &CxxOrchardWitness, index: usize) -> [u8; 32] { + self.0.path.0[index].to_bytes() + } +} + +pub struct ShardStoreImpl { + delegate: UniquePtr, + _hash_type: PhantomData, +} + +impl From<&CxxOrchardCheckpoint> for Checkpoint { + fn from(item: &CxxOrchardCheckpoint) -> Self { + let tree_state : TreeState = + if item.empty { TreeState::Empty } else { TreeState::AtPosition((item.position as u64).into()) }; + let marks_removed : BTreeSet = + item.mark_removed.iter().map(|x| Position::from(*x as u64)).collect(); + Checkpoint::from_parts(tree_state, marks_removed) + } +} + +impl TryFrom<&Checkpoint> for CxxOrchardCheckpoint { + type Error = Error; + fn try_from(item: &Checkpoint) -> Result { + let position: u32 = match item.tree_state() { + TreeState::Empty => 0, + TreeState::AtPosition(pos) => (u64::from(pos)).try_into().map_err(|_| Error::ShardStoreError)? + }; + let marks_removed : Result, Error> = item.marks_removed().into_iter().map( + |x| u32::try_from(u64::from(*x)).map_err(|_| Error::ShardStoreError)).collect(); + Ok(CxxOrchardCheckpoint { + empty: item.is_tree_empty(), + position: position, + mark_removed: marks_removed? + }) + } +} + +impl TryFrom<&Address> for CxxOrchardShardAddress { + type Error = Error; + + fn try_from(item: &Address) -> Result { + let index : u32 = item.index().try_into().map_err(|_| Error::ShardStoreError)?; + Ok(CxxOrchardShardAddress{ + level: item.level().into(), + index: index }) + } +} + +impl From<&CxxOrchardShardAddress> for Address { + fn from(item: &CxxOrchardShardAddress) -> Self { + Address::from_parts(item.level.into(), item.index.into()) + } +} + +impl TryFrom<&CxxOrchardShard> for LocatedPrunableTree { + type Error = Error; + + fn try_from(item: &CxxOrchardShard) -> Result { + let shard_tree = + read_shard(&mut Cursor::new(&item.data)).map_err(|_| Error::ShardStoreError)?; + let located_tree: LocatedTree<_, (_, RetentionFlags)> = + LocatedPrunableTree::from_parts(Address::from(&item.address), shard_tree); + if !item.hash.is_empty() { + let root_hash = H::read(Cursor::new(item.hash.clone())).map_err(|_| Error::ShardStoreError)?; + Ok(located_tree.reannotate_root(Some(Arc::new(root_hash)))) + } else { + Ok(located_tree) + } + } +} + +impl TryFrom<&CxxOrchardShardTreeCap> for PrunableTree { + type Error = Error; + + fn try_from(item: &CxxOrchardShardTreeCap) -> Result { + read_shard(&mut Cursor::new(&item.data)).map_err(|_| Error::ShardStoreError) + } +} + +impl TryFrom<&PrunableTree> for CxxOrchardShardTreeCap { + type Error = Error; + + fn try_from(item: &PrunableTree) -> Result { + let mut data = vec![]; + write_shard(&mut data, item).map_err(|_| Error::ShardStoreError)?; + Ok(CxxOrchardShardTreeCap { + data: data + }) + } +} + +impl TryFrom<&LocatedPrunableTree> for CxxOrchardShard { + type Error = Error; + + fn try_from(item: &LocatedPrunableTree) -> Result { + let subtree_root_hash : Option> = item + .root() + .annotation() + .and_then(|ann| { + ann.as_ref().map(|rc| { + let mut root_hash = vec![]; + rc.write(&mut root_hash)?; + Ok(root_hash) + }) + }) + .transpose() + .map_err(|_err : std::io::Error| Error::ShardStoreError)?; + + + let mut result = CxxOrchardShard { + address: CxxOrchardShardAddress::try_from(&item.root_addr()).map_err(|_| Error::ShardStoreError)?, + hash: subtree_root_hash.unwrap_or_else(|| vec![]).try_into().map_err(|_| Error::ShardStoreError)?, + data: vec![] + }; + + write_shard(&mut result.data, &item.root()).map_err(|_| Error::ShardStoreError)?; + Ok(result) + } +} + +type OrchardShardStoreImpl = ShardStoreImpl; +type TestingShardStoreImpl = ShardStoreImpl; + +impl ShardStore + for ShardStoreImpl +{ + type H = H; + type CheckpointId = BlockHeight; + type Error = Error; + + fn get_shard( + &self, + addr: Address, + ) -> Result>, Self::Error> { + let result = *self.delegate.GetShard( + &CxxOrchardShardAddress::try_from(&addr).map_err(|_| Error::ShardStoreError)?); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + match result.0.unwrap() { + Some(shard) => { + let tree = LocatedPrunableTree::::try_from(&shard)?; + return Ok(Some(tree)); + }, + None => { + return Ok(Option::None); + } + } + } + + fn last_shard(&self) -> Result>, Self::Error> { + let result : CxxOrchardShardResultWrapper = *self.delegate.LastShard(SHARD_HEIGHT); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + match result.0.unwrap() { + Some(shard) => { + let tree = LocatedPrunableTree::::try_from(&shard)?; + return Ok(Some(tree)); + }, + None => { + return Ok(Option::None); + } + } + } + + fn put_shard(&mut self, subtree: LocatedPrunableTree) -> Result<(), Self::Error> { + let shard = CxxOrchardShard::try_from(&subtree).map_err(|_| Error::ShardStoreError)?; + let result = + *self.delegate.PutShard(&shard); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(()) + } + + fn get_shard_roots(&self) -> Result, Self::Error> { + let result = *self.delegate.GetShardRoots(SHARD_HEIGHT); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + return Ok(result.0.unwrap().into_iter().map( + |addr| Address::from_parts(addr.level.into(), addr.index.into())).collect()); + } + + fn truncate(&mut self, from: Address) -> Result<(), Self::Error> { + let result = + *self.delegate.Truncate( + &CxxOrchardShardAddress::try_from(&from).map_err(|_| Error::ShardStoreError)?); + if result.0.is_err() { + return Err(Error::ShardStoreError) + } + Ok(()) + } + + fn get_cap(&self) -> Result, Self::Error> { + let result = *self.delegate.GetCap(); + + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + + match result.0.unwrap() { + Some(cap) => { + let tree = PrunableTree::::try_from(&cap)?; + return Ok(tree) + }, + None => { + return Ok(PrunableTree::empty()); + } + } + } + + fn put_cap(&mut self, cap: PrunableTree) -> Result<(), Self::Error> { + let mut result_cap = CxxOrchardShardTreeCap::default(); + write_shard(&mut result_cap.data, &cap).map_err(|_| Error::ShardStoreError)?; + + let result = *self.delegate.PutCap(&result_cap); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(()) + } + + fn min_checkpoint_id(&self) -> Result, Self::Error> { + let result = *self.delegate.MinCheckpointId(); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + match result.0.unwrap() { + Some(checkpoint_id) => { + return Ok(Some(checkpoint_id.into())); + }, + None => { + return Ok(Option::None); + } + } + } + + fn max_checkpoint_id(&self) -> Result, Self::Error> { + let result = *self.delegate.MaxCheckpointId(); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + match result.0.unwrap() { + Some(checkpoint_id) => { + return Ok(Some(checkpoint_id.into())); + }, + None => { + return Ok(Option::None); + } + } + } + + fn add_checkpoint( + &mut self, + checkpoint_id: Self::CheckpointId, + checkpoint: Checkpoint, + ) -> Result<(), Self::Error> { + let ffi_checkpoint_id : u32 = checkpoint_id.try_into().map_err(|_| Error::ShardStoreError)?; + let result = *self.delegate.AddCheckpoint( + ffi_checkpoint_id, + &CxxOrchardCheckpoint::try_from(&checkpoint)?); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(()) + } + + fn checkpoint_count(&self) -> Result { + let result = *self.delegate.CheckpointCount(); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(result.0.unwrap()) + } + + fn get_checkpoint_at_depth( + &self, + checkpoint_depth: usize, + ) -> Result, Self::Error> { + let result = *self.delegate.CheckpointAtDepth( + checkpoint_depth); + + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + + match result.0.unwrap() { + Some(checkpoint_bundle) => { + return Ok(Some((BlockHeight::from(checkpoint_bundle.checkpoint_id), Checkpoint::from(&checkpoint_bundle.checkpoint)))); + }, + None => { + return Ok(Option::None); + } + } + } + + fn get_checkpoint( + &self, + checkpoint_id: &Self::CheckpointId, + ) -> Result, Self::Error> { + let result = *self.delegate.GetCheckpoint( + (*checkpoint_id).into()); + + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + + match result.0.unwrap() { + Some(checkpoint) => { + return Ok(Some(Checkpoint::from(&checkpoint.checkpoint))); + }, + None => { + return Ok(Option::None); + } + } + } + + fn with_checkpoints(&mut self, limit: usize, mut callback: F) -> Result<(), Self::Error> + where + F: FnMut(&Self::CheckpointId, &Checkpoint) -> Result<(), Self::Error>, + { + let result = *self.delegate.GetCheckpoints(limit); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + + for item in result.0.unwrap() { + let checkpoint = Checkpoint::from(&item.checkpoint); + callback(&BlockHeight::from(item.checkpoint_id), &checkpoint).map_err(|_| Error::ShardStoreError)?; + } + return Ok(()) + } + + fn update_checkpoint_with( + &mut self, + checkpoint_id: &Self::CheckpointId, + update: F, + ) -> Result + where + F: Fn(&mut Checkpoint) -> Result<(), Self::Error>, + { + let result_get_checkpoint = + self.delegate.GetCheckpoint((*checkpoint_id).into()); + if result_get_checkpoint.0.is_err() { + return Err(Error::ShardStoreError); + } + if result_get_checkpoint.0.as_ref().unwrap().is_none() { + return Ok(false); + } + + let mut checkpoint = Checkpoint::from(&result_get_checkpoint.0.unwrap().unwrap().checkpoint); + + update(&mut checkpoint).map_err(|_| Error::ShardStoreError)?; + let result_update_checkpoint = + *self.delegate.UpdateCheckpoint( + (*checkpoint_id).into(), &CxxOrchardCheckpoint::try_from(&checkpoint)?); + if result_update_checkpoint.0.is_err() { + return Err(Error::ShardStoreError); + } + + Ok(result_update_checkpoint.0.unwrap()) + } + + fn remove_checkpoint(&mut self, checkpoint_id: &Self::CheckpointId) -> Result<(), Self::Error> { + let result = *self.delegate.RemoveCheckpoint((*checkpoint_id).into()); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(()) + } + + fn truncate_checkpoints( + &mut self, + checkpoint_id: &Self::CheckpointId, + ) -> Result<(), Self::Error> { + let result = *self.delegate.TruncateCheckpoint ((*checkpoint_id).into()); + if result.0.is_err() { + return Err(Error::ShardStoreError); + } + Ok(()) + } +} + +fn create_orchard_shard_tree(delegate: UniquePtr) -> Box { + let shard_store = OrchardShardStoreImpl { + delegate: delegate, + _hash_type: Default::default() + }; + let shardtree = ShardTree::new(shard_store, PRUNING_DEPTH.try_into().unwrap()); + Box::new(CxxOrchardShardTreeResult::from(Ok(OrchardShardTreeValue{tree: shardtree}))) +} + +fn convert_ffi_commitments(shard_tree_leafs: &CxxOrchardShardTreeLeafs) -> Vec<(MerkleHashOrchard, Retention)> { + shard_tree_leafs.commitments.iter().map(|c| { + let retention:Retention = { + if c.retention.checkpoint { + Retention::Checkpoint { id: c.retention.checkpoint_id.into(), is_marked: c.retention.marked } + } else if c.retention.marked { + Retention::Marked + } else { + Retention::Ephemeral + } + }; + let mh = MerkleHashOrchard::from_bytes(&c.hash); + (mh.unwrap(), retention) + }).collect() +} + +fn create_mock_decode_result(prior_tree_state: CxxOrchardShardTreeState, commitments: CxxOrchardShardTreeLeafs) -> Box { + Box::new(CxxOrchardDecodedBlocksBundleResult::from(Ok(OrchardDecodedBlocksBundleValue { + fvk: [0; 96], + outputs: vec![], + commitments: convert_ffi_commitments(&commitments), + prior_tree_state: prior_tree_state + }))) +} + +fn create_orchard_testing_shard_tree(delegate: UniquePtr) -> Box { + let shard_store: ShardStoreImpl = TestingShardStoreImpl { + delegate: delegate, + _hash_type: Default::default() + }; + let shardtree = ShardTree::new(shard_store, TESTING_PRUNING_DEPTH.try_into().unwrap()); + Box::new(CxxOrchardTestingShardTreeResult::from(Ok(OrchardTestingShardTreeValue{tree: shardtree}))) } +fn create_mock_commitment(position: u32, seed: u32) -> [u8; 32] { + MerkleHashOrchard::from_bytes( + &(pallas::Base::random(MockRng((position * seed).into())).into())).unwrap().to_bytes() +} \ No newline at end of file diff --git a/components/brave_wallet/browser/zcash/rust/librustzcash/BUILD.gn b/components/brave_wallet/browser/zcash/rust/librustzcash/BUILD.gn index 9fba4ac46629..1941eae34851 100644 --- a/components/brave_wallet/browser/zcash/rust/librustzcash/BUILD.gn +++ b/components/brave_wallet/browser/zcash/rust/librustzcash/BUILD.gn @@ -6,7 +6,11 @@ import("//build/rust/rust_static_library.gni") rust_static_library("zcash_protocol") { - visibility = [ ":zcash_primitives" ] + visibility = [ + ":zcash_primitives", + "//brave/components/brave_wallet/browser/zcash/rust:rust_lib", + "//brave/components/brave_wallet/browser/zcash/rust:rust_lib_cxx_generated", + ] crate_name = "zcash_protocol" crate_root = "src/components/zcash_protocol/src/lib.rs" sources = [ diff --git a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h similarity index 53% rename from components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h rename to components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h index b34a122bd13d..be05759748be 100644 --- a/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h @@ -3,20 +3,22 @@ // 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_RUST_AUTHORIZED_ORCHARD_BUNDLE_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_AUTHORIZED_ORCHARD_BUNDLE_H_ +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_AUTHORIZED_BUNDLE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_AUTHORIZED_BUNDLE_H_ #include namespace brave_wallet::orchard { -// Authorized orchard bundle resolved from UnauthorizedOrchardBundle +// Authorized orchard bundle resolved from OrchardUnauthorizedBundle // after zk-proof based on input data is created and signatures are applied. -// Reference librustzcash flow: +// References to the Bundle in the Orchard crate with Authorized state: +// https://github.com/zcash/orchard/blob/23a167e3972632586dc628ddbdd69d156dfd607b/src/bundle.rs#L152 +// Reference for the authorization librustzcash flow: // https://github.com/zcash/librustzcash/blob/5bd911f63bb9b41f97e4b37c32e79b52a7706543/zcash_primitives/src/transaction/builder.rs#L802 -class AuthorizedOrchardBundle { +class OrchardAuthorizedBundle { public: - virtual ~AuthorizedOrchardBundle() = default; + virtual ~OrchardAuthorizedBundle() = default; // Raw bytes that are used in Zcash nu5 transactions // https://zips.z.cash/zip-0225 @@ -25,4 +27,4 @@ class AuthorizedOrchardBundle { } // namespace brave_wallet::orchard -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_AUTHORIZED_ORCHARD_BUNDLE_H_ +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_AUTHORIZED_BUNDLE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.cc new file mode 100644 index 000000000000..43213b925477 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.cc @@ -0,0 +1,25 @@ +// 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/orchard_authorized_bundle_impl.h" + +#include + +namespace brave_wallet::orchard { + +OrchardAuthorizedBundleImpl::OrchardAuthorizedBundleImpl( + base::PassKey, + ::rust::Box cxx_orchard_authorized_bundle) + : cxx_orchard_authorized_bundle_(std::move(cxx_orchard_authorized_bundle)) { +} + +OrchardAuthorizedBundleImpl::~OrchardAuthorizedBundleImpl() = default; + +std::vector OrchardAuthorizedBundleImpl::GetOrchardRawTxPart() { + auto data = cxx_orchard_authorized_bundle_->raw_tx(); + return std::vector(data.begin(), data.end()); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.h new file mode 100644 index 000000000000..2b998b6cfbda --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.h @@ -0,0 +1,32 @@ +// 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_RUST_ORCHARD_AUTHORIZED_BUNDLE_IMPL_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_AUTHORIZED_BUNDLE_IMPL_H_ + +#include + +#include "base/types/pass_key.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h" + +namespace brave_wallet::orchard { + +class OrchardAuthorizedBundleImpl : public OrchardAuthorizedBundle { + public: + OrchardAuthorizedBundleImpl( + base::PassKey, + ::rust::Box); + ~OrchardAuthorizedBundleImpl() override; + + std::vector GetOrchardRawTxPart() override; + + private: + ::rust::Box cxx_orchard_authorized_bundle_; +}; + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_AUTHORIZED_BUNDLE_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.cc b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.cc new file mode 100644 index 000000000000..6a759c526efd --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.cc @@ -0,0 +1,79 @@ +/* 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/orchard_block_decoder.h" + +#include +#include +#include + +#include "base/memory/ptr_util.h" +#include "base/threading/thread_restrictions.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +OrchardBlockDecoder::OrchardBlockDecoder() = default; +OrchardBlockDecoder::~OrchardBlockDecoder() = default; + +// static +std::unique_ptr OrchardBlockDecoder::DecodeBlocks( + const OrchardFullViewKey& fvk, + const ::brave_wallet::OrchardTreeState& tree_state, + const std::vector<::brave_wallet::zcash::mojom::CompactBlockPtr>& blocks) { + base::AssertLongCPUWorkAllowed(); + ::rust::Vec orchard_actions; + for (const auto& block : blocks) { + bool block_has_orchard_action = false; + for (const auto& tx : block->vtx) { + for (const auto& orchard_action : tx->orchard_actions) { + block_has_orchard_action = true; + orchard::CxxOrchardCompactAction orchard_compact_action; + + if (orchard_action->nullifier.size() != kOrchardNullifierSize || + orchard_action->cmx.size() != kOrchardCmxSize || + orchard_action->ephemeral_key.size() != kOrchardEphemeralKeySize || + orchard_action->ciphertext.size() != kOrchardCipherTextSize) { + return nullptr; + } + + orchard_compact_action.block_id = block->height; + orchard_compact_action.is_block_last_action = false; + base::span(orchard_compact_action.nullifier) + .copy_from(orchard_action->nullifier); + base::span(orchard_compact_action.cmx).copy_from(orchard_action->cmx); + base::span(orchard_compact_action.ephemeral_key) + .copy_from(orchard_action->ephemeral_key); + base::span(orchard_compact_action.enc_cipher_text) + .copy_from(orchard_action->ciphertext); + + orchard_actions.push_back(std::move(orchard_compact_action)); + } + } + if (block_has_orchard_action) { + orchard_actions.back().is_block_last_action = true; + } + } + + CxxOrchardShardTreeState prior_tree_state; + prior_tree_state.block_height = tree_state.block_height; + prior_tree_state.tree_size = tree_state.tree_size; + + base::ranges::copy(tree_state.frontier, + std::back_inserter(prior_tree_state.frontier)); + + ::rust::Box decode_result = batch_decode( + fvk, std::move(prior_tree_state), std::move(orchard_actions)); + + if (decode_result->is_ok()) { + return std::make_unique( + base::PassKey(), decode_result->unwrap()); + } + return nullptr; +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h index 6e4d3a382ebf..9f368fa3be75 100644 --- a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h @@ -10,20 +10,21 @@ #include #include -#include "brave/components/brave_wallet/common/zcash_utils.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h" #include "brave/components/services/brave_wallet/public/mojom/zcash_decoder.mojom.h" namespace brave_wallet::orchard { class OrchardBlockDecoder { public: - virtual ~OrchardBlockDecoder() = default; - - virtual std::optional> ScanBlock( - const ::brave_wallet::zcash::mojom::CompactBlockPtr& block) = 0; - - static std::unique_ptr FromFullViewKey( - const OrchardFullViewKey& fvk); + static std::unique_ptr DecodeBlocks( + const OrchardFullViewKey& fvk, + const ::brave_wallet::OrchardTreeState& tree_state, + const std::vector<::brave_wallet::zcash::mojom::CompactBlockPtr>& blocks); + + private: + OrchardBlockDecoder(); + ~OrchardBlockDecoder(); }; } // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.cc deleted file mode 100644 index eeafe7863494..000000000000 --- a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.cc +++ /dev/null @@ -1,82 +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/rust/orchard_block_decoder_impl.h" - -#include -#include -#include - -#include "base/memory/ptr_util.h" -#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" -#include "brave/components/brave_wallet/common/zcash_utils.h" - -namespace brave_wallet::orchard { - -OrchardBlockDecoderImpl::OrchardBlockDecoderImpl(const OrchardFullViewKey& fvk) - : full_view_key_(fvk) {} - -OrchardBlockDecoderImpl::~OrchardBlockDecoderImpl() = default; - -std::optional> -OrchardBlockDecoderImpl::ScanBlock( - const ::brave_wallet::zcash::mojom::CompactBlockPtr& block) { - std::vector result; - for (const auto& tx : block->vtx) { - ::rust::Vec orchard_actions; - for (const auto& orchard_action : tx->orchard_actions) { - orchard::OrchardCompactAction orchard_compact_action; - - if (orchard_action->nullifier.size() != kOrchardNullifierSize || - orchard_action->cmx.size() != kOrchardCmxSize || - orchard_action->ephemeral_key.size() != kOrchardEphemeralKeySize || - orchard_action->ciphertext.size() != kOrchardCipherTextSize) { - return std::nullopt; - } - - base::ranges::copy(orchard_action->nullifier, - orchard_compact_action.nullifier.begin()); - base::ranges::copy(orchard_action->cmx, - orchard_compact_action.cmx.begin()); - base::ranges::copy(orchard_action->ephemeral_key, - orchard_compact_action.ephemeral_key.begin()); - base::ranges::copy(orchard_action->ciphertext, - orchard_compact_action.enc_cipher_text.begin()); - - orchard_actions.emplace_back(std::move(orchard_compact_action)); - } - - ::rust::Box<::brave_wallet::orchard::BatchOrchardDecodeBundleResult> - decode_result = ::brave_wallet::orchard::batch_decode( - full_view_key_, std::move(orchard_actions)); - - if (decode_result->is_ok()) { - ::rust::Box<::brave_wallet::orchard::BatchOrchardDecodeBundle> - result_bundle = decode_result->unwrap(); - for (size_t i = 0; i < result_bundle->size(); i++) { - result.emplace_back( - OrchardNote({{}, - block->height, - result_bundle->note_nullifier(full_view_key_, i), - result_bundle->note_value(i), - 0, - {}, - {}})); - } - } else { - return std::nullopt; - } - } - return result; -} - -// static -std::unique_ptr OrchardBlockDecoder::FromFullViewKey( - const OrchardFullViewKey& fvk) { - return base::WrapUnique( - new OrchardBlockDecoderImpl(fvk)); -} - -} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.h deleted file mode 100644 index f7cbb60a5d6a..000000000000 --- a/components/brave_wallet/browser/zcash/rust/orchard_block_decoder_impl.h +++ /dev/null @@ -1,30 +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_RUST_ORCHARD_BLOCK_DECODER_IMPL_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_BLOCK_DECODER_IMPL_H_ - -#include - -#include "brave/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.h" - -namespace brave_wallet::orchard { - -class OrchardBlockDecoderImpl : public OrchardBlockDecoder { - public: - ~OrchardBlockDecoderImpl() override; - - std::optional> ScanBlock( - const ::brave_wallet::zcash::mojom::CompactBlockPtr& block) override; - - private: - friend class OrchardBlockDecoder; - explicit OrchardBlockDecoderImpl(const OrchardFullViewKey& fvk); - OrchardFullViewKey full_view_key_; -}; - -} // namespace brave_wallet::orchard - -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_BLOCK_DECODER_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h new file mode 100644 index 000000000000..c26f20ff7194 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h @@ -0,0 +1,28 @@ +// 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_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" + +namespace brave_wallet::orchard { + +// Contains result of the batch block decoding. +// This includes Orchard leafs to be inserterted to the shard tree and +// a set of dicovered Orchard spendable notes. +class OrchardDecodedBlocksBundle { + public: + virtual ~OrchardDecodedBlocksBundle() = default; + virtual std::optional> + GetDiscoveredNotes() = 0; +}; + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.cc new file mode 100644 index 000000000000..9ed7af242716 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.cc @@ -0,0 +1,49 @@ +// 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/orchard_decoded_blocks_bundle_impl.h" + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +OrchardDecodedBlocksBundleImpl::OrchardDecodedBlocksBundleImpl( + absl::variant, + base::PassKey>, + rust::Box cxx_orchard_decoded_blocks_bundle) + : cxx_orchard_decoded_blocks_bundle_( + std::move(cxx_orchard_decoded_blocks_bundle)) {} + +OrchardDecodedBlocksBundleImpl::~OrchardDecodedBlocksBundleImpl() = default; + +std::optional> +OrchardDecodedBlocksBundleImpl::GetDiscoveredNotes() { + std::vector result; + result.reserve(cxx_orchard_decoded_blocks_bundle_->size()); + for (size_t i = 0; i < cxx_orchard_decoded_blocks_bundle_->size(); i++) { + result.push_back(OrchardNote{ + cxx_orchard_decoded_blocks_bundle_->note_addr(i), + cxx_orchard_decoded_blocks_bundle_->note_block_height(i), + cxx_orchard_decoded_blocks_bundle_->note_nullifier(i), + cxx_orchard_decoded_blocks_bundle_->note_value(i), + cxx_orchard_decoded_blocks_bundle_->note_commitment_tree_position(i), + cxx_orchard_decoded_blocks_bundle_->note_rho(i), + cxx_orchard_decoded_blocks_bundle_->note_rseed(i), + }); + } + + return result; +} + +CxxOrchardDecodedBlocksBundle& +OrchardDecodedBlocksBundleImpl::GetDecodeBundle() { + return *cxx_orchard_decoded_blocks_bundle_; +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h new file mode 100644 index 000000000000..e1dd47e3d14e --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h @@ -0,0 +1,37 @@ +// 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_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_IMPL_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_IMPL_H_ + +#include + +#include "base/types/pass_key.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h" +#include "third_party/rust/cxx/v1/cxx.h" + +namespace brave_wallet::orchard { + +struct CxxOrchardDecodedBlocksBundle; + +class OrchardDecodedBlocksBundleImpl : public OrchardDecodedBlocksBundle { + public: + ~OrchardDecodedBlocksBundleImpl() override; + OrchardDecodedBlocksBundleImpl( + absl::variant, + base::PassKey>, + ::rust::Box); + + std::optional> GetDiscoveredNotes() + override; + CxxOrchardDecodedBlocksBundle& GetDecodeBundle(); + + private: + ::rust::Box cxx_orchard_decoded_blocks_bundle_; +}; + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_DECODED_BLOCKS_BUNDLE_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/extended_spending_key.h b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h similarity index 67% rename from components/brave_wallet/browser/zcash/rust/extended_spending_key.h rename to components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h index 2e0636e97c9f..da17811a3ab2 100644 --- a/components/brave_wallet/browser/zcash/rust/extended_spending_key.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h @@ -3,8 +3,8 @@ // 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_RUST_EXTENDED_SPENDING_KEY_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_EXTENDED_SPENDING_KEY_H_ +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_EXTENDED_SPENDING_KEY_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_EXTENDED_SPENDING_KEY_H_ #include #include @@ -16,16 +16,16 @@ namespace brave_wallet::orchard { // Implements Orchard key generation from // https://zips.z.cash/zip-0032#orchard-child-key-derivation -class ExtendedSpendingKey { +class OrchardExtendedSpendingKey { public: - virtual ~ExtendedSpendingKey() = default; + virtual ~OrchardExtendedSpendingKey() = default; // Generates master key using provided seed - static std::unique_ptr GenerateFromSeed( + static std::unique_ptr GenerateFromSeed( base::span seed); // Derives hardened key using index and the current key - virtual std::unique_ptr DeriveHardenedChild( + virtual std::unique_ptr DeriveHardenedChild( uint32_t index) = 0; // Returns public or internal address that may be used as a recipient address @@ -39,4 +39,4 @@ class ExtendedSpendingKey { } // namespace brave_wallet::orchard -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_EXTENDED_SPENDING_KEY_H_ +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_EXTENDED_SPENDING_KEY_H_ 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 new file mode 100644 index 000000000000..1c63cd459599 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.cc @@ -0,0 +1,56 @@ +// 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/orchard_extended_spending_key_impl.h" + +#include + +#include "base/memory/ptr_util.h" + +namespace brave_wallet::orchard { + +OrchardExtendedSpendingKeyImpl::OrchardExtendedSpendingKeyImpl( + absl::variant, + base::PassKey>, + rust::Box cxx_extended_spending_key) + : cxx_extended_spending_key_(std::move(cxx_extended_spending_key)) {} + +OrchardExtendedSpendingKeyImpl::~OrchardExtendedSpendingKeyImpl() = default; + +std::unique_ptr +OrchardExtendedSpendingKeyImpl::DeriveHardenedChild(uint32_t index) { + auto esk = cxx_extended_spending_key_->derive(index); + if (esk->is_ok()) { + return std::make_unique( + base::PassKey(), esk->unwrap()); + } + return nullptr; +} + +std::optional +OrchardExtendedSpendingKeyImpl::GetDiversifiedAddress(uint32_t div_index, + OrchardAddressKind kind) { + return kind == OrchardAddressKind::External + ? cxx_extended_spending_key_->external_address(div_index) + : cxx_extended_spending_key_->internal_address(div_index); +} + +// static +std::unique_ptr +OrchardExtendedSpendingKey::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 OrchardExtendedSpendingKeyImpl::GetFullViewKey() { + return cxx_extended_spending_key_->full_view_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 new file mode 100644 index 000000000000..c1600dd7ac11 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key_impl.h @@ -0,0 +1,53 @@ +// 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_RUST_ORCHARD_EXTENDED_SPENDING_KEY_IMPL_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_EXTENDED_SPENDING_KEY_IMPL_H_ + +#include + +#include "base/types/pass_key.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_extended_spending_key.h" +#include "third_party/rust/cxx/v1/cxx.h" + +namespace brave_wallet::orchard { + +// Implements Orchard key generation from +// https://zips.z.cash/zip-0032#orchard-child-key-derivation +class OrchardExtendedSpendingKeyImpl : public OrchardExtendedSpendingKey { + public: + OrchardExtendedSpendingKeyImpl( + absl::variant, + base::PassKey>, + rust::Box); + OrchardExtendedSpendingKeyImpl(const OrchardExtendedSpendingKeyImpl&) = + delete; + OrchardExtendedSpendingKeyImpl& operator=( + const OrchardExtendedSpendingKeyImpl&) = delete; + + ~OrchardExtendedSpendingKeyImpl() override; + + // Derives hardened key using index and the current key + std::unique_ptr DeriveHardenedChild( + uint32_t index) override; + + // Returns public or internal address that may be used as a recipient address + // in transactions + std::optional GetDiversifiedAddress( + uint32_t div_index, + OrchardAddressKind kind) override; + + OrchardFullViewKey GetFullViewKey() override; + + private: + // Extended spending key is a root key of an account, all other keys can be + // derived from esk + rust::Box cxx_extended_spending_key_; +}; + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_EXTENDED_SPENDING_KEY_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.cc b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.cc new file mode 100644 index 000000000000..f0e60713ed75 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.cc @@ -0,0 +1,94 @@ +/* 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/orchard_shard_tree.h" + +#include +#include +#include +#include + +#include "base/memory/ptr_util.h" +#include "base/ranges/algorithm.h" +#include "brave/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +class OrchardShardTreeImpl : public OrchardShardTree { + public: + OrchardShardTreeImpl(base::PassKey, + rust::Box orcard_shard_tree); + ~OrchardShardTreeImpl() override; + + bool TruncateToCheckpoint(uint32_t checkpoint_id) override; + + bool ApplyScanResults( + std::unique_ptr commitments) override; + + base::expected CalculateWitness( + uint32_t note_commitment_tree_position, + uint32_t checkpoint) override; + + private: + ::rust::Box cxx_orchard_shard_tree_; +}; + +bool OrchardShardTreeImpl::ApplyScanResults( + std::unique_ptr commitments) { + auto* bundle_impl = + static_cast(commitments.get()); + return cxx_orchard_shard_tree_->insert_commitments( + bundle_impl->GetDecodeBundle()); +} + +base::expected +OrchardShardTreeImpl::CalculateWitness(uint32_t note_commitment_tree_position, + uint32_t checkpoint) { + auto result = cxx_orchard_shard_tree_->calculate_witness( + note_commitment_tree_position, checkpoint); + if (!result->is_ok()) { + return base::unexpected(result->error_message().c_str()); + } + + auto value = result->unwrap(); + + OrchardNoteWitness witness; + witness.position = note_commitment_tree_position; + for (size_t i = 0; i < value->size(); i++) { + witness.merkle_path.push_back(value->item(i)); + } + + return witness; +} + +bool OrchardShardTreeImpl::TruncateToCheckpoint(uint32_t checkpoint_id) { + return cxx_orchard_shard_tree_->truncate(checkpoint_id); +} + +OrchardShardTreeImpl::OrchardShardTreeImpl( + base::PassKey, + ::rust::Box orcard_shard_tree) + : cxx_orchard_shard_tree_(std::move(orcard_shard_tree)) {} + +OrchardShardTreeImpl::~OrchardShardTreeImpl() {} + +// static +std::unique_ptr OrchardShardTree::Create( + ::brave_wallet::OrchardStorage& storage, + const mojom::AccountIdPtr& account_id) { + auto orchard_shard_tree_result = create_orchard_shard_tree( + std::make_unique(storage, account_id)); + if (!orchard_shard_tree_result->is_ok()) { + return nullptr; + } + return std::make_unique( + base::PassKey(), + orchard_shard_tree_result->unwrap()); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h new file mode 100644 index 000000000000..6211534979f6 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h @@ -0,0 +1,49 @@ +// 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_RUST_ORCHARD_SHARD_TREE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_SHARD_TREE_H_ + +#include +#include + +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" + +namespace brave_wallet { + +class OrchardStorage; + +namespace orchard { + +// Facade for the ShardTree from the shardtree crate: +// https://github.com/zcash/incrementalmerkletree/blob/db4ad58965f1870d2dac1d8e0d594cfaa0541e98/shardtree/src/lib.rs#L68 +// backed by the provided OrchardStorage which represents ShardStore. +class OrchardShardTree { + public: + virtual ~OrchardShardTree() = default; + + // Truncates commitment tree to the provided checkpoint position; + virtual bool TruncateToCheckpoint(uint32_t checkpoint_id) = 0; + + // Applies previously decoded blocks to the commitment tree. + virtual bool ApplyScanResults( + std::unique_ptr commitments) = 0; + + virtual base::expected CalculateWitness( + uint32_t note_commitment_tree_position, + uint32_t checkpoint) = 0; + + // Creates original tree height of 32. + static std::unique_ptr Create( + ::brave_wallet::OrchardStorage& storage, + const mojom::AccountIdPtr& account_id); +}; + +} // namespace orchard + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_SHARD_TREE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_test_utils.cc b/components/brave_wallet/browser/zcash/rust/orchard_test_utils.cc new file mode 100644 index 000000000000..7c25739cb559 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_test_utils.cc @@ -0,0 +1,71 @@ +// 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/orchard_test_utils.h" + +#include +#include + +#include "base/check_is_test.h" +#include "base/memory/ptr_util.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h" + +namespace brave_wallet::orchard { + +class TestingDecodedBundleBuilderImpl : public TestingDecodedBundleBuilder { + public: + TestingDecodedBundleBuilderImpl() = default; + + ~TestingDecodedBundleBuilderImpl() override = default; + + void SetPriorTreeState(::brave_wallet::OrchardTreeState tree_state) override { + prior_tree_state_ = std::move(tree_state); + } + + void AddCommitment(::brave_wallet::OrchardCommitment commitment) override { + CxxOrchardCheckpointRetention retention; + retention.marked = commitment.is_marked; + retention.checkpoint = commitment.checkpoint_id.has_value(); + retention.checkpoint_id = commitment.checkpoint_id.value_or(0); + + CxxOrchardShardTreeLeaf leaf; + leaf.hash = commitment.cmu; + leaf.retention = retention; + + leafs_.commitments.push_back(std::move(leaf)); + } + + std::unique_ptr Complete() override { + ::rust::Vec frontier; + base::ranges::copy(prior_tree_state_->frontier, + std::back_inserter(frontier)); + auto prior_tree_state = + CxxOrchardShardTreeState{frontier, prior_tree_state_->block_height, + prior_tree_state_->tree_size}; + return std::make_unique( + base::PassKey(), + create_mock_decode_result(std::move(prior_tree_state), + std::move(leafs_)) + ->unwrap()); + } + + private: + std::optional<::brave_wallet::OrchardTreeState> prior_tree_state_; + CxxOrchardShardTreeLeafs leafs_; +}; + +std::unique_ptr +CreateTestingDecodedBundleBuilder() { + CHECK_IS_TEST(); + return std::make_unique(); +} + +OrchardCommitmentValue CreateMockCommitmentValue(uint32_t position, + uint32_t rseed) { + return create_mock_commitment(position, rseed); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_test_utils.h b/components/brave_wallet/browser/zcash/rust/orchard_test_utils.h new file mode 100644 index 000000000000..e231b49ee31d --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_test_utils.h @@ -0,0 +1,37 @@ +// 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_RUST_ORCHARD_TEST_UTILS_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_TEST_UTILS_H_ + +#include + +#include "brave/components/brave_wallet/browser/internal/orchard_storage/orchard_shard_tree_types.h" + +namespace brave_wallet::orchard { + +class OrchardDecodedBlocksBundle; + +// Builder is used in tests to create OrchardDecodedBlocksBundle with mocked +// commitments +class TestingDecodedBundleBuilder { + public: + TestingDecodedBundleBuilder() = default; + virtual ~TestingDecodedBundleBuilder() = default; + virtual void AddCommitment(::brave_wallet::OrchardCommitment commitment) = 0; + virtual void SetPriorTreeState( + ::brave_wallet::OrchardTreeState tree_state) = 0; + virtual std::unique_ptr Complete() = 0; +}; + +std::unique_ptr +CreateTestingDecodedBundleBuilder(); + +OrchardCommitmentValue CreateMockCommitmentValue(uint32_t position, + uint32_t rseed); + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_TEST_UTILS_H_ diff --git a/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.cc b/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.cc new file mode 100644 index 000000000000..058435bb1ce7 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.cc @@ -0,0 +1,91 @@ +// 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/orchard_testing_shard_tree.h" + +#include +#include +#include + +#include "base/check_is_test.h" +#include "brave/components/brave_wallet/browser/zcash/rust/cxx_orchard_shard_tree_delegate.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_decoded_blocks_bundle_impl.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h" + +namespace brave_wallet::orchard { + +class OrchardTestingShardTreeImpl : public OrchardShardTree { + public: + explicit OrchardTestingShardTreeImpl(::rust::Box); + ~OrchardTestingShardTreeImpl() override; + + bool TruncateToCheckpoint(uint32_t checkpoint_id) override; + + bool ApplyScanResults( + std::unique_ptr commitments) override; + + base::expected CalculateWitness( + uint32_t note_commitment_tree_position, + uint32_t checkpoint) override; + + private: + ::rust::Box cxx_orchard_testing_shard_tree_; +}; + +bool OrchardTestingShardTreeImpl::ApplyScanResults( + std::unique_ptr commitments) { + auto* bundle_impl = + static_cast(commitments.get()); + return cxx_orchard_testing_shard_tree_->insert_commitments( + bundle_impl->GetDecodeBundle()); +} + +base::expected +OrchardTestingShardTreeImpl::CalculateWitness( + uint32_t note_commitment_tree_position, + uint32_t checkpoint) { + auto result = cxx_orchard_testing_shard_tree_->calculate_witness( + note_commitment_tree_position, checkpoint); + if (!result->is_ok()) { + return base::unexpected(result->error_message().c_str()); + } + + auto value = result->unwrap(); + + OrchardNoteWitness witness; + witness.position = note_commitment_tree_position; + for (size_t i = 0; i < value->size(); i++) { + witness.merkle_path.push_back(value->item(i)); + } + + return witness; +} + +bool OrchardTestingShardTreeImpl::TruncateToCheckpoint(uint32_t checkpoint_id) { + return cxx_orchard_testing_shard_tree_->truncate(checkpoint_id); +} + +OrchardTestingShardTreeImpl::OrchardTestingShardTreeImpl( + rust::Box cxx_orchard_testing_shard_tree) + : cxx_orchard_testing_shard_tree_( + std::move(cxx_orchard_testing_shard_tree)) {} + +OrchardTestingShardTreeImpl::~OrchardTestingShardTreeImpl() = default; + +std::unique_ptr CreateShardTreeForTesting( // IN-TEST + ::brave_wallet::OrchardStorage& storage, + const mojom::AccountIdPtr& account_id) { + CHECK_IS_TEST(); + auto shard_tree_result = create_orchard_testing_shard_tree( + std::make_unique(storage, account_id)); + if (!shard_tree_result->is_ok()) { + return nullptr; + } + return std::make_unique( + shard_tree_result->unwrap()); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.h b/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.h new file mode 100644 index 000000000000..b7282a36bdaf --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_testing_shard_tree.h @@ -0,0 +1,29 @@ +// 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_RUST_ORCHARD_TESTING_SHARD_TREE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_TESTING_SHARD_TREE_H_ + +#include + +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" + +namespace brave_wallet { + +class OrchardStorage; + +namespace orchard { + +class OrchardShardTree; + +// Creates a small tree height of 8 for testing purposes. +std::unique_ptr CreateShardTreeForTesting( // IN-TEST + ::brave_wallet::OrchardStorage& storage, + const mojom::AccountIdPtr& account_id); + +} // namespace orchard +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_TESTING_SHARD_TREE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle.h b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h similarity index 64% rename from components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle.h rename to components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h index 393929e4e28c..58f48b4c0326 100644 --- a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle.h +++ b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h @@ -3,8 +3,8 @@ * 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_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_H_ +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_H_ #include #include @@ -12,21 +12,23 @@ #include #include "base/containers/span.h" -#include "brave/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle.h" #include "brave/components/brave_wallet/common/zcash_utils.h" namespace brave_wallet::orchard { -// UnauthorizedOrchardBundle represents input data needed to create +// OrchardUnauthorizedBundle represents input data needed to create // Orchard part for Zcash transaction. // Like anchor tree state(which is used for shielded inputs wittness // calculation), random number generator, shielded inputs and shielded outputs. -class UnauthorizedOrchardBundle { +// References to the Bundle in the Orchard crate with Unauthorized state: +// https://github.com/zcash/orchard/blob/23a167e3972632586dc628ddbdd69d156dfd607b/src/builder.rs#L375 +class OrchardUnauthorizedBundle { public: - virtual ~UnauthorizedOrchardBundle() = default; + virtual ~OrchardUnauthorizedBundle() = default; - // Creates UnauthorizedOrchardBundle without shielded inputs - static std::unique_ptr Create( + // Creates OrchardUnauthorizedBundle without shielded inputs + static std::unique_ptr Create( base::span tree_state, const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs, std::optional random_seed_for_testing); @@ -42,10 +44,10 @@ class UnauthorizedOrchardBundle { // Reference in the zcash_primitives crate: // https://github.com/zcash/librustzcash/blob/5bd911f63bb9b41f97e4b37c32e79b52a7706543/zcash_primitives/src/transaction/builder.rs#L802 // Note: this is CPU heavy method, should be executed on background thread. - virtual std::unique_ptr Complete( + virtual std::unique_ptr Complete( const std::array& sighash) = 0; }; } // namespace brave_wallet::orchard -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_H_ +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc similarity index 51% rename from components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.cc rename to components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc index 924c4c58cec2..435a07a4a246 100644 --- a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.cc +++ b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.cc @@ -3,7 +3,7 @@ * 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/unauthorized_orchard_bundle_impl.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.h" #include #include @@ -12,24 +12,26 @@ #include "base/check_is_test.h" #include "base/logging.h" #include "base/memory/ptr_util.h" -#include "brave/components/brave_wallet/browser/zcash/rust/authorized_orchard_bundle_impl.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_authorized_bundle_impl.h" namespace brave_wallet::orchard { -UnauthorizedOrchardBundleImpl::UnauthorizedOrchardBundleImpl( - ::rust::Box orchard_unauthorized_bundle) - : orchard_unauthorized_bundle_(std::move(orchard_unauthorized_bundle)) {} +OrchardUnauthorizedBundleImpl::OrchardUnauthorizedBundleImpl( + base::PassKey, + ::rust::Box cxx_orchard_unauthorized_bundle) + : cxx_orchard_unauthorized_bundle_( + std::move(cxx_orchard_unauthorized_bundle)) {} -UnauthorizedOrchardBundleImpl::~UnauthorizedOrchardBundleImpl() = default; +OrchardUnauthorizedBundleImpl::~OrchardUnauthorizedBundleImpl() = default; // static -std::unique_ptr UnauthorizedOrchardBundle::Create( +std::unique_ptr OrchardUnauthorizedBundle::Create( base::span tree_state, const std::vector<::brave_wallet::OrchardOutput>& orchard_outputs, std::optional random_seed_for_testing) { - ::rust::Vec outputs; + ::rust::Vec outputs; for (const auto& output : orchard_outputs) { - outputs.push_back(orchard::OrchardOutput{ + outputs.push_back(orchard::CxxOrchardOutput{ output.value, output.addr, output.memo ? *output.memo : OrchardMemo(), output.memo.has_value()}); } @@ -37,40 +39,44 @@ std::unique_ptr UnauthorizedOrchardBundle::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()); if (!bundle_result->is_ok()) { return nullptr; } - return base::WrapUnique( - new UnauthorizedOrchardBundleImpl(bundle_result->unwrap())); + return std::make_unique( + base::PassKey(), + bundle_result->unwrap()); } else { auto bundle_result = create_orchard_bundle( ::rust::Slice{tree_state.data(), tree_state.size()}, + ::rust::Vec<::brave_wallet::orchard::CxxOrchardSpend>(), std::move(outputs)); if (!bundle_result->is_ok()) { return nullptr; } - return base::WrapUnique( - new UnauthorizedOrchardBundleImpl(bundle_result->unwrap())); + return std::make_unique( + base::PassKey(), + bundle_result->unwrap()); } } std::array -UnauthorizedOrchardBundleImpl::GetDigest() { - return orchard_unauthorized_bundle_->orchard_digest(); +OrchardUnauthorizedBundleImpl::GetDigest() { + return cxx_orchard_unauthorized_bundle_->orchard_digest(); } -std::unique_ptr -UnauthorizedOrchardBundleImpl::Complete( +std::unique_ptr +OrchardUnauthorizedBundleImpl::Complete( const std::array& sighash) { auto authorized_orchard_bundle_result = - orchard_unauthorized_bundle_->complete(sighash); + cxx_orchard_unauthorized_bundle_->complete(sighash); if (!authorized_orchard_bundle_result->is_ok()) { return nullptr; } - return base::WrapUnique( - new AuthorizedOrchardBundleImpl( - authorized_orchard_bundle_result->unwrap())); + return std::make_unique( + base::PassKey(), + authorized_orchard_bundle_result->unwrap()); } } // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.h new file mode 100644 index 000000000000..69fa2db9003f --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle_impl.h @@ -0,0 +1,34 @@ +/* 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_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_IMPL_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_IMPL_H_ + +#include + +#include "base/types/pass_key.h" +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_unauthorized_bundle.h" +#include "third_party/rust/cxx/v1/cxx.h" + +namespace brave_wallet::orchard { + +class OrchardUnauthorizedBundleImpl : public OrchardUnauthorizedBundle { + public: + OrchardUnauthorizedBundleImpl(base::PassKey, + ::rust::Box); + ~OrchardUnauthorizedBundleImpl() override; + + std::array GetDigest() override; + std::unique_ptr Complete( + const std::array& sighash) override; + + private: + ::rust::Box cxx_orchard_unauthorized_bundle_; +}; + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_ORCHARD_UNAUTHORIZED_BUNDLE_IMPL_H_ diff --git a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.h b/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.h deleted file mode 100644 index acafc983fcee..000000000000 --- a/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle_impl.h +++ /dev/null @@ -1,36 +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_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ -#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ - -#include - -#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" -#include "brave/components/brave_wallet/browser/zcash/rust/unauthorized_orchard_bundle.h" -#include "third_party/rust/cxx/v1/cxx.h" - -namespace brave_wallet::orchard { - -class UnauthorizedOrchardBundleImpl : public UnauthorizedOrchardBundle { - public: - ~UnauthorizedOrchardBundleImpl() override; - - std::array GetDigest() override; - std::unique_ptr Complete( - const std::array& sighash) override; - - private: - friend class UnauthorizedOrchardBundle; - explicit UnauthorizedOrchardBundleImpl( - ::rust::Box - orchard_unauthorized_bundle); - - ::rust::Box orchard_unauthorized_bundle_; -}; - -} // namespace brave_wallet::orchard - -#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_UNAUTHORIZED_ORCHARD_BUNDLE_IMPL_H_ 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 3d29332ebfd5..cdb6f62e199d 100644 --- a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc +++ b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.cc @@ -5,6 +5,8 @@ #include "brave/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h" +#include + #include "brave/components/brave_wallet/common/common_utils.h" #include "components/grit/brave_components_strings.h" #include "ui/base/l10n/l10n_util.h" @@ -55,8 +57,8 @@ void ZCashResolveBalanceTask::WorkOnTask() { #if BUILDFLAG(ENABLE_ORCHARD) if (IsZCashShieldedTransactionsEnabled()) { if (!orchard_notes_) { - zcash_wallet_service_->orchard_storage() - .AsyncCall(&ZCashOrchardStorage::GetSpendableNotes) + zcash_wallet_service_->sync_state() + .AsyncCall(&OrchardSyncState::GetSpendableNotes) .WithArgs(account_id_.Clone()) .Then(base::BindOnce(&ZCashResolveBalanceTask::OnGetSpendableNotes, weak_ptr_factory_.GetWeakPtr())); @@ -76,8 +78,7 @@ void ZCashResolveBalanceTask::WorkOnTask() { #if BUILDFLAG(ENABLE_ORCHARD) void ZCashResolveBalanceTask::OnGetSpendableNotes( - base::expected, ZCashOrchardStorage::Error> - result) { + base::expected, OrchardStorage::Error> result) { if (!result.has_value()) { error_ = result.error().message; ScheduleWorkOnTask(); 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 e25184d59631..b4c170bde4e9 100644 --- a/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h +++ b/components/brave_wallet/browser/zcash/zcash_resolve_balance_task.h @@ -7,6 +7,8 @@ #define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_RESOLVE_BALANCE_TASK_H_ #include +#include +#include #include "base/memory/raw_ref.h" #include "base/types/expected.h" @@ -39,8 +41,8 @@ class ZCashResolveBalanceTask { base::expected result); #if BUILDFLAG(ENABLE_ORCHARD) - void OnGetSpendableNotes(base::expected, - ZCashOrchardStorage::Error> result); + void OnGetSpendableNotes( + base::expected, OrchardStorage::Error> result); #endif // BUILDFLAG(ENABLE_ORCHARD) 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 37a28bfeecef..fffc45cca813 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc @@ -52,23 +52,25 @@ ZCashShieldSyncService::OrchardBlockScannerProxy::~OrchardBlockScannerProxy() = default; void ZCashShieldSyncService::OrchardBlockScannerProxy::ScanBlocks( - std::vector known_notes, + OrchardTreeState tree_state, std::vector blocks, base::OnceCallback)> callback) { background_block_scanner_.AsyncCall(&OrchardBlockScanner::ScanBlocks) - .WithArgs(std::move(known_notes), std::move(blocks)) + .WithArgs(std::move(tree_state), std::move(blocks)) .Then(std::move(callback)); } ZCashShieldSyncService::ZCashShieldSyncService( - ZCashWalletService* zcash_wallet_service, + ZCashRpc& zcash_rpc, + base::SequenceBound& zcash_orchard_sync_state, const mojom::AccountIdPtr& account_id, const mojom::ZCashAccountShieldBirthdayPtr& account_birthday, const OrchardFullViewKey& fvk, base::WeakPtr observer) - : zcash_wallet_service_(zcash_wallet_service), + : zcash_rpc_(zcash_rpc), + zcash_orchard_sync_state_(zcash_orchard_sync_state), account_id_(account_id.Clone()), account_birthday_(account_birthday.Clone()), observer_(std::move(observer)) { @@ -161,48 +163,43 @@ void ZCashShieldSyncService::GetOrCreateAccount() { ScheduleWorkOnTask(); return; } - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::GetAccountMeta) + sync_state() + .AsyncCall(&OrchardSyncState::GetAccountMeta) .WithArgs(account_id_.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetAccountMeta, weak_ptr_factory_.GetWeakPtr())); } void ZCashShieldSyncService::OnGetAccountMeta( - base::expected - result) { - if (result.has_value()) { - account_meta_ = *result; - if (account_meta_->latest_scanned_block_id.value() && - (account_meta_->latest_scanned_block_id.value() < - account_meta_->account_birthday)) { - error_ = Error{ErrorCode::kFailedToRetrieveAccount, ""}; - } + base::expected, + OrchardStorage::Error> result) { + if (!result.has_value()) { + error_ = Error{ErrorCode::kFailedToRetrieveAccount, result.error().message}; ScheduleWorkOnTask(); - return; } - - if (result.error().error_code == - ZCashOrchardStorage::ErrorCode::kAccountNotFound) { + if (!result.value()) { InitAccount(); return; - } else { - error_ = Error{ErrorCode::kFailedToRetrieveAccount, result.error().message}; + } + account_meta_ = *result; + if (account_meta_->latest_scanned_block_id.value() && + (account_meta_->latest_scanned_block_id.value() < + account_meta_->account_birthday)) { + error_ = Error{ErrorCode::kFailedToRetrieveAccount, ""}; } ScheduleWorkOnTask(); } void ZCashShieldSyncService::InitAccount() { - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::RegisterAccount) + sync_state() + .AsyncCall(&OrchardSyncState::RegisterAccount) .WithArgs(account_id_.Clone(), account_birthday_->value) .Then(base::BindOnce(&ZCashShieldSyncService::OnAccountInit, weak_ptr_factory_.GetWeakPtr())); } void ZCashShieldSyncService::OnAccountInit( - base::expected - result) { + base::expected result) { if (!result.has_value()) { error_ = Error{ErrorCode::kFailedToInitAccount, result.error().message}; } else { @@ -212,7 +209,7 @@ void ZCashShieldSyncService::OnAccountInit( } void ZCashShieldSyncService::VerifyChainState( - ZCashOrchardStorage::AccountMeta account_meta) { + OrchardStorage::AccountMeta account_meta) { if (account_meta.account_birthday < kNu5BlockUpdate) { error_ = Error{ErrorCode::kFailedToRetrieveAccount, "Wrong birthday block height"}; @@ -226,7 +223,7 @@ void ZCashShieldSyncService::VerifyChainState( } // 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) { + 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); @@ -244,7 +241,7 @@ void ZCashShieldSyncService::VerifyChainState( } void ZCashShieldSyncService::OnGetTreeStateForChainVerification( - ZCashOrchardStorage::AccountMeta account_meta, + OrchardStorage::AccountMeta account_meta, base::expected tree_state) { if (!tree_state.has_value() || !tree_state.value()) { error_ = Error{ErrorCode::kFailedToReceiveTreeState, tree_state.error()}; @@ -291,8 +288,8 @@ void ZCashShieldSyncService::OnGetTreeStateForChainReorg( return; } else { // Reorg database so records related to removed blocks are wiped out - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::HandleChainReorg) + sync_state() + .AsyncCall(&OrchardSyncState::HandleChainReorg) .WithArgs(account_id_.Clone(), (*tree_state)->height, (*tree_state)->hash) .Then(base::BindOnce( @@ -303,9 +300,9 @@ void ZCashShieldSyncService::OnGetTreeStateForChainReorg( void ZCashShieldSyncService::OnDatabaseUpdatedForChainReorg( uint32_t new_block_height, - std::optional error) { - if (error) { - error_ = Error{ErrorCode::kFailedToUpdateDatabase, error->message}; + base::expected result) { + if (!result.has_value()) { + error_ = Error{ErrorCode::kFailedToUpdateDatabase, result.error().message}; ScheduleWorkOnTask(); return; } @@ -315,16 +312,15 @@ void ZCashShieldSyncService::OnDatabaseUpdatedForChainReorg( } void ZCashShieldSyncService::UpdateSpendableNotes() { - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::GetSpendableNotes) + sync_state() + .AsyncCall(&OrchardSyncState::GetSpendableNotes) .WithArgs(account_id_.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetSpendableNotes, weak_ptr_factory_.GetWeakPtr())); } void ZCashShieldSyncService::OnGetSpendableNotes( - base::expected, ZCashOrchardStorage::Error> - result) { + base::expected, OrchardStorage::Error> result) { if (!result.has_value()) { error_ = Error{ErrorCode::kFailedToRetrieveSpendableNotes, result.error().message}; @@ -383,7 +379,7 @@ void ZCashShieldSyncService::ScanBlocks() { auto last_block_height = downloaded_blocks_->back()->height; block_scanner_->ScanBlocks( - *spendable_notes_, std::move(downloaded_blocks_.value()), + OrchardTreeState(), std::move(downloaded_blocks_.value()), base::BindOnce(&ZCashShieldSyncService::OnBlocksScanned, weak_ptr_factory_.GetWeakPtr(), last_block_height, last_block_hash)); @@ -399,20 +395,18 @@ void ZCashShieldSyncService::OnBlocksScanned( error_ = Error{ErrorCode::kScannerError, ""}; ScheduleWorkOnTask(); } else { - UpdateNotes(result->discovered_notes, result->spent_notes, - last_block_height, last_block_hash); + UpdateNotes(std::move(result.value()), last_block_height, last_block_hash); } } void ZCashShieldSyncService::UpdateNotes( - const std::vector& found_notes, - const std::vector& notes_to_delete, + OrchardBlockScanner::Result result, uint32_t latest_scanned_block, std::string latest_scanned_block_hash) { - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::UpdateNotes) - .WithArgs(account_id_.Clone(), found_notes, notes_to_delete, - latest_scanned_block, 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)); @@ -420,9 +414,9 @@ void ZCashShieldSyncService::UpdateNotes( void ZCashShieldSyncService::UpdateNotesComplete( uint32_t new_latest_scanned_block, - std::optional error) { - if (error) { - error_ = Error{ErrorCode::kFailedToUpdateDatabase, error->message}; + 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; @@ -440,12 +434,11 @@ uint32_t ZCashShieldSyncService::GetSpendableBalance() { } ZCashRpc& ZCashShieldSyncService::zcash_rpc() { - return zcash_wallet_service_->zcash_rpc(); + return zcash_rpc_.get(); } -base::SequenceBound& -ZCashShieldSyncService::orchard_storage() { - return zcash_wallet_service_->orchard_storage(); +base::SequenceBound& ZCashShieldSyncService::sync_state() { + return zcash_orchard_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 b6be7cb9c5d2..af0284d67d0a 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h @@ -15,17 +15,16 @@ #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/zcash/zcash_orchard_storage.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" namespace brave_wallet { -class ZCashOrchardStorage; +class OrchardStorage; class ZCashRpc; -class ZCashWalletService; -// ZCashScanService downloads and scans blockchain blocks to find +// ZCashShieldSyncService downloads and scans blockchain blocks to find // spendable notes related to the account. // Provided full view key allows to decode orchard compact actions // related to the account. @@ -64,7 +63,7 @@ class ZCashShieldSyncService { explicit OrchardBlockScannerProxy(OrchardFullViewKey full_view_key); virtual ~OrchardBlockScannerProxy(); virtual void ScanBlocks( - std::vector known_notes, + OrchardTreeState tree_state, std::vector blocks, base::OnceCallback)> @@ -75,7 +74,8 @@ class ZCashShieldSyncService { }; ZCashShieldSyncService( - ZCashWalletService* zcash_wallet_service, + ZCashRpc& zcash_rpc, + base::SequenceBound& zcash_orchard_sync_state, const mojom::AccountIdPtr& account_id, const mojom::ZCashAccountShieldBirthdayPtr& account_birthday, const std::array& fvk, @@ -99,11 +99,12 @@ class ZCashShieldSyncService { // Setup account info void GetOrCreateAccount(); - void OnGetAccountMeta(base::expected result); + void OnGetAccountMeta( + base::expected, + OrchardStorage::Error> result); void InitAccount(); - void OnAccountInit(base::expected error); + void OnAccountInit( + base::expected error); // Get last known block in the blockchain void UpdateChainTip(); @@ -115,9 +116,9 @@ class ZCashShieldSyncService { // We assume that there is a limit of reorg depth - kChainReorgBlockDelta // Verifies that last known scanned block hash is unchanged - void VerifyChainState(ZCashOrchardStorage::AccountMeta account_meta); + void VerifyChainState(OrchardStorage::AccountMeta account_meta); void OnGetTreeStateForChainVerification( - ZCashOrchardStorage::AccountMeta account_meta, + OrchardStorage::AccountMeta account_meta, base::expected tree_state); // Resolves block hash for the block we are going to fallback @@ -127,12 +128,12 @@ class ZCashShieldSyncService { base::expected tree_state); void OnDatabaseUpdatedForChainReorg( uint32_t new_block_height, - std::optional error); + base::expected result); // Update spendable notes state void UpdateSpendableNotes(); - void OnGetSpendableNotes(base::expected, - ZCashOrchardStorage::Error> result); + void OnGetSpendableNotes( + base::expected, OrchardStorage::Error> result); // Download, scan, update flow // Download next bunch of blocks @@ -147,21 +148,22 @@ class ZCashShieldSyncService { std::string last_block_hash, base::expected result); - void UpdateNotes(const std::vector& found_notes, - const std::vector& notes_to_delete, + void UpdateNotes(OrchardBlockScanner::Result result, uint32_t latest_scanned_block, std::string latest_scanned_block_hash); - void UpdateNotesComplete(uint32_t new_latest_scanned_block, - std::optional error); + void UpdateNotesComplete( + uint32_t new_latest_scanned_block, + base::expected result); ZCashRpc& zcash_rpc(); - base::SequenceBound& orchard_storage(); + base::SequenceBound& sync_state(); uint32_t GetSpendableBalance(); std::optional error() { return error_; } // Params - raw_ptr zcash_wallet_service_ = nullptr; // Owns this + raw_ref zcash_rpc_; + raw_ref> zcash_orchard_sync_state_; mojom::AccountIdPtr account_id_; // Birthday of the account will be used to resolve initial scan range. mojom::ZCashAccountShieldBirthdayPtr account_birthday_; @@ -171,7 +173,7 @@ class ZCashShieldSyncService { std::unique_ptr block_scanner_; - std::optional account_meta_; + std::optional account_meta_; // Latest scanned block std::optional latest_scanned_block_; // Latest block in the blockchain 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 07553028d932..050fffdd6cb7 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 @@ -10,7 +10,9 @@ #include #include "base/files/scoped_temp_dir.h" +#include "base/task/thread_pool.h" #include "brave/components/brave_wallet/browser/brave_wallet_prefs.h" +#include "brave/components/brave_wallet/browser/internal/orchard_test_utils.h" #include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" #include "brave/components/brave_wallet/browser/zcash/zcash_test_utils.h" #include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" @@ -97,8 +99,8 @@ class MockOrchardBlockScannerProxy : public ZCashShieldSyncService::OrchardBlockScannerProxy { public: using Callback = base::RepeatingCallback known_notes, - std::vector blocks, + OrchardTreeState, + std::vector, base::OnceCallback)> callback)>; @@ -109,12 +111,12 @@ class MockOrchardBlockScannerProxy ~MockOrchardBlockScannerProxy() override = default; void ScanBlocks( - std::vector known_notes, + OrchardTreeState tree_state, std::vector blocks, base::OnceCallback)> callback) override { - callback_.Run(std::move(known_notes), std::move(blocks), + callback_.Run(std::move(tree_state), std::move(blocks), std::move(callback)); } @@ -126,7 +128,23 @@ class ZCashShieldSyncServiceTest : public testing::Test { public: ZCashShieldSyncServiceTest() : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} - void SetUp() override; + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::FilePath db_path( + temp_dir_.GetPath().Append(FILE_PATH_LITERAL("orchard.db"))); + + brave_wallet::RegisterProfilePrefs(prefs_.registry()); + brave_wallet::RegisterLocalStatePrefs(local_state_.registry()); + keyring_service_ = + std::make_unique(nullptr, &prefs_, &local_state_); + sync_state_.emplace( + base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}), + db_path.AppendASCII("orchard.db")); + + observer_ = std::make_unique(); + + ResetSyncService(); + } void ResetSyncService() { auto account_id = MakeIndexBasedAccountId(mojom::CoinType::ZEC, @@ -137,23 +155,16 @@ class ZCashShieldSyncServiceTest : public testing::Test { OrchardFullViewKey fvk; sync_service_ = std::make_unique( - zcash_wallet_service_.get(), account_id, account_birthday, fvk, + zcash_rpc_, sync_state_, account_id, account_birthday, fvk, observer_->GetWeakPtr()); // Ensure previous OrchardStorage is destroyed on background thread task_environment_.RunUntilIdle(); } - ZCashWalletService* zcash_wallet_service() { - return zcash_wallet_service_.get(); - } - ZCashShieldSyncService* sync_service() { return sync_service_.get(); } - testing::NiceMock& zcash_rpc() { - return static_cast&>( - zcash_wallet_service_->zcash_rpc()); - } + testing::NiceMock& zcash_rpc() { return zcash_rpc_; } MockZCashShieldSyncServiceObserver* observer() { return observer_.get(); } @@ -162,7 +173,7 @@ class ZCashShieldSyncServiceTest : public testing::Test { std::unique_ptr CreateMockOrchardBlockScannerProxy() { return std::make_unique(base::BindRepeating( - [](std::vector known_notes, + [](OrchardTreeState tree_state, std::vector blocks, base::OnceCallback()); for (const auto& block : blocks) { // 3 notes in the blockchain if (block->height == kNu5BlockUpdate + 105) { @@ -186,14 +198,14 @@ class ZCashShieldSyncServiceTest : public testing::Test { // First 2 notes are spent if (block->height == kNu5BlockUpdate + 255) { - result.spent_notes.push_back( - GenerateMockNoteSpend(account_id, block->height, 1)); + result.found_spends.push_back(OrchardNoteSpend{ + block->height, GenerateMockNullifier(account_id, 1)}); } else if (block->height == kNu5BlockUpdate + 265) { - result.spent_notes.push_back( - GenerateMockNoteSpend(account_id, block->height, 2)); + result.found_spends.push_back(OrchardNoteSpend{ + block->height, GenerateMockNullifier(account_id, 2)}); } } - std::move(callback).Run(result); + std::move(callback).Run(std::move(result)); })); } @@ -203,30 +215,13 @@ class ZCashShieldSyncServiceTest : public testing::Test { sync_preferences::TestingPrefServiceSyncable prefs_; sync_preferences::TestingPrefServiceSyncable local_state_; std::unique_ptr keyring_service_; - std::unique_ptr zcash_wallet_service_; + base::SequenceBound sync_state_; + testing::NiceMock zcash_rpc_; std::unique_ptr observer_; std::unique_ptr sync_service_; base::test::TaskEnvironment task_environment_; }; -void ZCashShieldSyncServiceTest::SetUp() { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - base::FilePath db_path( - temp_dir_.GetPath().Append(FILE_PATH_LITERAL("orchard.db"))); - - brave_wallet::RegisterProfilePrefs(prefs_.registry()); - brave_wallet::RegisterLocalStatePrefs(local_state_.registry()); - keyring_service_ = - std::make_unique(nullptr, &prefs_, &local_state_); - - observer_ = std::make_unique(); - auto zcash_rpc = std::make_unique>(); - zcash_wallet_service_ = std::make_unique( - db_path, *keyring_service_, std::move(zcash_rpc)); - - ResetSyncService(); -} - TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { auto mock_block_scanner = CreateMockOrchardBlockScannerProxy(); @@ -288,7 +283,7 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { sync_service()->SetOrchardBlockScannerProxyForTesting( std::make_unique(base::BindRepeating( - [](std::vector known_notes, + [](OrchardTreeState tree_state, std::vector blocks, base::OnceCallback()); for (const auto& block : blocks) { // 3 notes in the blockchain if (block->height == kNu5BlockUpdate + 605) { @@ -312,11 +308,11 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { // First 2 notes are spent if (block->height == kNu5BlockUpdate + 855) { - result.spent_notes.push_back( - GenerateMockNoteSpend(account_id, block->height, 3)); + result.found_spends.push_back(OrchardNoteSpend{ + block->height, GenerateMockNullifier(account_id, 3)}); } } - std::move(callback).Run(result); + std::move(callback).Run(std::move(result)); }))); ON_CALL(zcash_rpc(), GetTreeState(_, _, _)) @@ -361,13 +357,14 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { sync_service()->SetOrchardBlockScannerProxyForTesting( std::make_unique(base::BindRepeating( - [](std::vector known_notes, + [](OrchardTreeState tree_state, std::vector blocks, base::OnceCallback)> callback) { - OrchardBlockScanner::Result result; - std::move(callback).Run(result); + OrchardBlockScanner::Result result = CreateResultForTesting( + std::move(tree_state), std::vector()); + std::move(callback).Run(std::move(result)); }))); { @@ -401,7 +398,7 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { sync_service()->SetOrchardBlockScannerProxyForTesting( std::make_unique(base::BindRepeating( - [](std::vector known_notes, + [](OrchardTreeState tree_state, std::vector blocks, base::OnceCallback()); for (const auto& block : blocks) { // First block is the current chain tip - kChainReorgBlockDelta EXPECT_GE(block->height, @@ -425,11 +423,11 @@ TEST_F(ZCashShieldSyncServiceTest, ScanBlocks) { // Add a nullifier for previous note if (block->height == kNu5BlockUpdate + 905) { - result.spent_notes.push_back( - GenerateMockNoteSpend(account_id, block->height, 3)); + result.found_spends.push_back(OrchardNoteSpend{ + block->height, GenerateMockNullifier(account_id, 3)}); } } - std::move(callback).Run(result); + std::move(callback).Run(std::move(result)); }))); { diff --git a/components/brave_wallet/browser/zcash/zcash_test_utils.h b/components/brave_wallet/browser/zcash/zcash_test_utils.h index a8ec64abccdc..f0db39a75ced 100644 --- a/components/brave_wallet/browser/zcash/zcash_test_utils.h +++ b/components/brave_wallet/browser/zcash/zcash_test_utils.h @@ -16,11 +16,11 @@ namespace brave_wallet { std::array GenerateMockNullifier( const mojom::AccountIdPtr& account_id, uint8_t seed); - OrchardNoteSpend GenerateMockNoteSpend(const mojom::AccountIdPtr& account_id, uint32_t block_id, uint8_t seed); - +OrchardNullifier GenerateMockNullifier(const mojom::AccountIdPtr& account_id, + uint8_t seed); OrchardNote GenerateMockOrchardNote(const mojom::AccountIdPtr& account_id, uint32_t block_id, uint8_t seed); diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.cc b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc index 42777bd6a77e..36a837db43be 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc @@ -64,7 +64,7 @@ ZCashWalletService::ZCashWalletService( keyring_service_->AddObserver( keyring_observer_receiver_.BindNewPipeAndPassRemote()); #if BUILDFLAG(ENABLE_ORCHARD) - background_orchard_storage_.emplace( + sync_state_.emplace( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}), zcash_data_path_.AppendASCII(kOrchardDatabaseName)); #endif @@ -81,7 +81,7 @@ ZCashWalletService::ZCashWalletService(base::FilePath zcash_data_path, keyring_service_->AddObserver( keyring_observer_receiver_.BindNewPipeAndPassRemote()); #if BUILDFLAG(ENABLE_ORCHARD) - background_orchard_storage_.emplace( + sync_state_.emplace( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}), zcash_data_path_.AppendASCII(kOrchardDatabaseName)); #endif @@ -175,7 +175,7 @@ void ZCashWalletService::StartShieldSync(mojom::AccountIdPtr account_id, shield_sync_services_[account_id.Clone()] = std::make_unique( - this, account_id, account_birthday, fvk.value(), + *zcash_rpc_, sync_state_, account_id, account_birthday, fvk.value(), weak_ptr_factory_.GetWeakPtr()); shield_sync_services_[account_id.Clone()]->StartSyncing(); @@ -755,9 +755,8 @@ KeyringService& ZCashWalletService::keyring_service() { } #if BUILDFLAG(ENABLE_ORCHARD) -base::SequenceBound& -ZCashWalletService::orchard_storage() { - return background_orchard_storage_; +base::SequenceBound& ZCashWalletService::sync_state() { + return sync_state_; } #endif // BUILDFLAG(ENABLE_ORCHARD) @@ -780,7 +779,7 @@ void ZCashWalletService::Reset() { weak_ptr_factory_.InvalidateWeakPtrs(); #if BUILDFLAG(ENABLE_ORCHARD) shield_sync_services_.clear(); - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::ResetDatabase); + sync_state_.AsyncCall(&OrchardSyncState::ResetDatabase); #endif // BUILDFLAG(ENABLE_ORCHARD) } diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.h b/components/brave_wallet/browser/zcash/zcash_wallet_service.h index 56685bb270d3..46c40b45867c 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service.h +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.h @@ -25,11 +25,16 @@ #include "brave/components/brave_wallet/common/buildflags.h" #include "brave/components/brave_wallet/common/zcash_utils.h" +#if BUILDFLAG(ENABLE_ORCHARD) +#include "brave/components/brave_wallet/browser/internal/orchard_sync_state.h" +#endif + namespace brave_wallet { class ZCashCreateShieldTransactionTask; class ZCashCreateTransparentTransactionTask; class ZCashGetTransparentUtxosContext; +class OrchardSyncState; class ZCashResolveBalanceTask; class ZCashWalletService : public mojom::ZCashWalletService, @@ -249,7 +254,7 @@ class ZCashWalletService : public mojom::ZCashWalletService, const mojom::AccountIdPtr& account_id, const mojom::ZCashShieldSyncStatusPtr& status) override; - base::SequenceBound& orchard_storage(); + base::SequenceBound& sync_state(); #endif void UpdateNextUnusedAddressForAccount(const mojom::AccountIdPtr& account_id, @@ -268,7 +273,7 @@ class ZCashWalletService : public mojom::ZCashWalletService, std::list> resolve_balance_tasks_; #if BUILDFLAG(ENABLE_ORCHARD) - base::SequenceBound background_orchard_storage_; + base::SequenceBound sync_state_; std::list> create_shield_transaction_tasks_; std::map> 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 385b36876a5b..ef7bc9c58e79 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc @@ -36,7 +36,8 @@ #include "base/task/sequenced_task_runner.h" #include "base/test/scoped_run_loop_timeout.h" #include "brave/components/brave_wallet/browser/internal/orchard_bundle_manager.h" -#include "brave/components/brave_wallet/browser/zcash/zcash_orchard_storage.h" +#include "brave/components/brave_wallet/browser/internal/orchard_sync_state.h" +#include "brave/components/brave_wallet/browser/internal/orchard_test_utils.h" #endif using testing::_; @@ -141,8 +142,8 @@ class ZCashWalletServiceUnitTest : public testing::Test { } #if BUILDFLAG(ENABLE_ORCHARD) - base::SequenceBound& orchard_storage() { - return zcash_wallet_service_->orchard_storage(); + base::SequenceBound& sync_state() { + return zcash_wallet_service_->sync_state(); } #endif // BUILDFLAG(ENABLE_ORCHARD) @@ -326,12 +327,15 @@ TEST_F(ZCashWalletServiceUnitTest, GetBalanceWithShielded) { note.amount = 10u; auto update_notes_callback = base::BindLambdaForTesting( - [](std::optional) {}); + [](base::expected) {}); - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::UpdateNotes) - .WithArgs(account_id.Clone(), std::vector({note}), - std::vector(), 50000, "hash50000") + OrchardBlockScanner::Result result = CreateResultForTesting( + OrchardTreeState(), std::vector()); + result.discovered_notes = std::vector({note}); + + sync_state() + .AsyncCall(&OrchardSyncState::ApplyScanResults) + .WithArgs(account_id.Clone(), std::move(result), 50000, "hash50000") .Then(std::move(update_notes_callback)); task_environment_.RunUntilIdle(); @@ -411,12 +415,15 @@ TEST_F(ZCashWalletServiceUnitTest, GetBalanceWithShielded_FeatureDisabled) { note.amount = 10u; auto update_notes_callback = base::BindLambdaForTesting( - [](std::optional) {}); + [](base::expected) {}); + + OrchardBlockScanner::Result result = CreateResultForTesting( + OrchardTreeState(), std::vector()); + result.discovered_notes = std::vector({note}); - orchard_storage() - .AsyncCall(&ZCashOrchardStorage::UpdateNotes) - .WithArgs(account_id.Clone(), std::vector({note}), - std::vector(), 50000, "hash50000") + sync_state() + .AsyncCall(&OrchardSyncState::ApplyScanResults) + .WithArgs(account_id.Clone(), std::move(result), 50000, "hash50000") .Then(std::move(update_notes_callback)); task_environment_.RunUntilIdle(); diff --git a/components/brave_wallet/common/zcash_utils.cc b/components/brave_wallet/common/zcash_utils.cc index e0f152e3da36..0a688e7a7160 100644 --- a/components/brave_wallet/common/zcash_utils.cc +++ b/components/brave_wallet/common/zcash_utils.cc @@ -189,10 +189,6 @@ std::optional OrchardInput::FromValue( return result; } -OrchardTreeState::OrchardTreeState() {} -OrchardTreeState::~OrchardTreeState() {} -OrchardTreeState::OrchardTreeState(const OrchardTreeState&) = default; - OrchardNoteWitness::OrchardNoteWitness() = default; OrchardNoteWitness::~OrchardNoteWitness() = default; OrchardNoteWitness::OrchardNoteWitness(const OrchardNoteWitness& other) = @@ -253,45 +249,6 @@ std::optional OrchardOutput::FromValue( return result; } -OrchardCheckpoint::OrchardCheckpoint() {} -OrchardCheckpoint::OrchardCheckpoint(CheckpointTreeState tree_state_position, - std::vector marks_removed) - : tree_state_position(tree_state_position), - marks_removed(std::move(marks_removed)) {} -OrchardCheckpoint::~OrchardCheckpoint() {} -OrchardCheckpoint::OrchardCheckpoint(const OrchardCheckpoint& other) = default; -OrchardCheckpoint& OrchardCheckpoint::operator=( - const OrchardCheckpoint& other) = default; -OrchardCheckpoint::OrchardCheckpoint(OrchardCheckpoint&& other) = default; -OrchardCheckpoint& OrchardCheckpoint::operator=(OrchardCheckpoint&& other) = - default; - -OrchardCheckpointBundle::OrchardCheckpointBundle(uint32_t checkpoint_id, - OrchardCheckpoint checkpoint) - : checkpoint_id(checkpoint_id), checkpoint(std::move(checkpoint)) {} -OrchardCheckpointBundle::~OrchardCheckpointBundle() {} -OrchardCheckpointBundle::OrchardCheckpointBundle( - const OrchardCheckpointBundle& other) = default; -OrchardCheckpointBundle& OrchardCheckpointBundle::operator=( - const OrchardCheckpointBundle& other) = default; -OrchardCheckpointBundle::OrchardCheckpointBundle( - OrchardCheckpointBundle&& other) = default; -OrchardCheckpointBundle& OrchardCheckpointBundle::operator=( - OrchardCheckpointBundle&& other) = default; - -OrchardShard::OrchardShard() {} -OrchardShard::OrchardShard(OrchardShardAddress address, - std::optional root_hash, - std::vector shard_data) - : address(std::move(address)), - root_hash(std::move(root_hash)), - shard_data(std::move(shard_data)) {} -OrchardShard::~OrchardShard() = default; -OrchardShard::OrchardShard(const OrchardShard& other) = default; -OrchardShard& OrchardShard::operator=(const OrchardShard& other) = default; -OrchardShard::OrchardShard(OrchardShard&& other) = default; -OrchardShard& OrchardShard::operator=(OrchardShard&& other) = default; - bool OutputZCashAddressSupported(const std::string& address, bool is_testnet) { auto decoded_address = DecodeZCashAddress(address); if (!decoded_address) { diff --git a/components/brave_wallet/common/zcash_utils.h b/components/brave_wallet/common/zcash_utils.h index bd7edc7aa184..4a0b043c6b84 100644 --- a/components/brave_wallet/common/zcash_utils.h +++ b/components/brave_wallet/common/zcash_utils.h @@ -169,102 +169,6 @@ struct OrchardSpendsBundle { std::vector inputs; }; -// Leaf position of checkpoint. -using CheckpointTreeState = std::optional; - -// Checkpointed leafs are not pruned so they could be used -// as anchors for building shielded transactions. -// Last Orchard commitment in a block is used as a checkpoint. -struct OrchardCheckpoint { - OrchardCheckpoint(); - OrchardCheckpoint(CheckpointTreeState, std::vector); - ~OrchardCheckpoint(); - OrchardCheckpoint(const OrchardCheckpoint& other); - OrchardCheckpoint& operator=(const OrchardCheckpoint& other); - OrchardCheckpoint(OrchardCheckpoint&& other); - OrchardCheckpoint& operator=(OrchardCheckpoint&& other); - - bool operator==(const OrchardCheckpoint& other) const = default; - - CheckpointTreeState tree_state_position; - // List of note positions that were spent at this checkpoint. - std::vector marks_removed; -}; - -struct OrchardCheckpointBundle { - OrchardCheckpointBundle(uint32_t checkpoint_id, OrchardCheckpoint); - ~OrchardCheckpointBundle(); - OrchardCheckpointBundle(const OrchardCheckpointBundle& other); - OrchardCheckpointBundle& operator=(const OrchardCheckpointBundle& other); - OrchardCheckpointBundle(OrchardCheckpointBundle&& other); - OrchardCheckpointBundle& operator=(OrchardCheckpointBundle&& other); - - bool operator==(const OrchardCheckpointBundle& other) const = default; - - // The block height serves as the checkpoint identifier. - uint32_t checkpoint_id = 0; - OrchardCheckpoint checkpoint; -}; - -// Address of a subtree in the shard tree. -struct OrchardShardAddress { - uint8_t level = 0; - uint32_t index = 0; - - bool operator==(const OrchardShardAddress& other) const = default; -}; - -// Top part of the shard tree from the root to the shard roots level -// Used for optimization purposes in the shard tree crate. -using OrchardShardTreeCap = std::vector; - -// Subtree with root selected from the shard roots level. -struct OrchardShard { - OrchardShard(); - OrchardShard(OrchardShardAddress shard_addr, - std::optional shard_hash, - std::vector shard_data); - ~OrchardShard(); - - OrchardShard(const OrchardShard& other); - OrchardShard& operator=(const OrchardShard& other); - OrchardShard(OrchardShard&& other); - OrchardShard& operator=(OrchardShard&& other); - - bool operator==(const OrchardShard& other) const = default; - - // Subtree root address. - OrchardShardAddress address; - // Root hash exists only on completed shards. - std::optional root_hash; - std::vector shard_data; - // Right-most position of the subtree leaf. - size_t subtree_end_height = 0; -}; - -struct OrchardCommitment { - OrchardCommitmentValue cmu; - bool is_marked = false; - std::optional checkpoint_id; -}; - -// Compact representation of the Merkle tree on some point. -// Since batch inserting may contain gaps between scan ranges we insert -// frontier which allows to calculate node hashes and witnesses(merkle path from -// leaf to the tree root) even when previous scan ranges are not completed. -struct OrchardTreeState { - OrchardTreeState(); - ~OrchardTreeState(); - OrchardTreeState(const OrchardTreeState&); - - // Tree state is linked to the end of some block. - uint32_t block_height = 0u; - // Number of leafs at the position. - uint32_t tree_size = 0u; - // https://docs.aztec.network/protocol-specs/l1-smart-contracts/frontier - std::vector frontier; -}; - bool OutputZCashAddressSupported(const std::string& address, bool is_testnet); // https://zips.z.cash/zip-0317 uint64_t CalculateZCashTxFee(const uint32_t tx_input_count,