From bbaaa849fa309241563c37a5f660319f902ac222 Mon Sep 17 00:00:00 2001 From: oisupov Date: Thu, 1 Aug 2024 10:38:21 +0300 Subject: [PATCH] Shielded inputs support WIP --- components/brave_wallet/browser/BUILD.gn | 2 + .../brave_wallet/browser/internal/BUILD.gn | 2 + .../browser/internal/orchard_block_scanner.cc | 21 +- .../browser/internal/orchard_block_scanner.h | 5 +- .../internal/orchard_shard_tree_manager.cc | 40 ++ .../internal/orchard_shard_tree_manager.h | 34 + .../brave_wallet/browser/zcash/rust/BUILD.gn | 17 + .../browser/zcash/rust/cxx/src/shard_store.h | 70 ++ .../brave_wallet/browser/zcash/rust/lib.rs | 609 +++++++++++++++++- .../browser/zcash/rust/librustzcash/BUILD.gn | 6 +- .../browser/zcash/rust/orchard_shard_tree.h | 24 + .../zcash/rust/orchard_shard_tree_impl.cc | 324 ++++++++++ .../zcash/rust/orchard_shard_tree_impl.h | 25 + .../browser/zcash/zcash_orchard_storage.cc | 587 +++++++++++++++++ .../browser/zcash/zcash_orchard_storage.h | 66 +- .../browser/zcash/zcash_orchard_sync_state.cc | 267 ++++++++ .../browser/zcash/zcash_orchard_sync_state.h | 63 ++ .../brave_wallet/browser/zcash/zcash_rpc.cc | 87 +++ .../brave_wallet/browser/zcash/zcash_rpc.h | 17 + .../zcash/zcash_shield_sync_service.cc | 73 ++- .../browser/zcash/zcash_shield_sync_service.h | 15 +- components/brave_wallet/common/zcash_utils.cc | 43 ++ components/brave_wallet/common/zcash_utils.h | 120 ++++ .../public/mojom/zcash_decoder.mojom | 7 + .../public/proto/zcash_grpc_data.proto | 16 + .../brave_wallet/zcash/zcash_decoder.cc | 15 + .../brave_wallet/zcash/zcash_decoder.h | 2 + 27 files changed, 2535 insertions(+), 22 deletions(-) create mode 100644 components/brave_wallet/browser/internal/orchard_shard_tree_manager.cc create mode 100644 components/brave_wallet/browser/internal/orchard_shard_tree_manager.h create mode 100644 components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h create mode 100644 components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h create mode 100644 components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.cc create mode 100644 components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.h create mode 100644 components/brave_wallet/browser/zcash/zcash_orchard_sync_state.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_orchard_sync_state.h diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index 85d9576aebab..bccd544baf23 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -304,6 +304,8 @@ static_library("browser") { "zcash/zcash_create_shield_all_transaction_task.h", "zcash/zcash_orchard_storage.cc", "zcash/zcash_orchard_storage.h", + "zcash/zcash_orchard_sync_state.cc", + "zcash/zcash_orchard_sync_state.h", "zcash/zcash_shield_sync_service.cc", "zcash/zcash_shield_sync_service.h", ] diff --git a/components/brave_wallet/browser/internal/BUILD.gn b/components/brave_wallet/browser/internal/BUILD.gn index c957e9fbd3b8..453ff3ec4748 100644 --- a/components/brave_wallet/browser/internal/BUILD.gn +++ b/components/brave_wallet/browser/internal/BUILD.gn @@ -45,6 +45,8 @@ if (enable_orchard) { "orchard_block_scanner.h", "orchard_bundle_manager.cc", "orchard_bundle_manager.h", + "orchard_shard_tree_manager.cc", + "orchard_shard_tree_manager.h", ] deps = [ "//brave/components/brave_wallet/browser/zcash/rust" ] } diff --git a/components/brave_wallet/browser/internal/orchard_block_scanner.cc b/components/brave_wallet/browser/internal/orchard_block_scanner.cc index 186d740b0c34..0de0d1b1ad96 100644 --- a/components/brave_wallet/browser/internal/orchard_block_scanner.cc +++ b/components/brave_wallet/browser/internal/orchard_block_scanner.cc @@ -10,9 +10,11 @@ namespace brave_wallet { OrchardBlockScanner::Result::Result() = default; OrchardBlockScanner::Result::Result(std::vector discovered_notes, - std::vector spent_notes) + std::vector spent_notes, + std::vector commitments) : discovered_notes(std::move(discovered_notes)), - spent_notes(std::move(spent_notes)) {} + spent_notes(std::move(spent_notes)), + commitments(std::move(commitments)) {} OrchardBlockScanner::Result::Result(const Result&) = default; @@ -33,7 +35,9 @@ OrchardBlockScanner::ScanBlocks( std::vector blocks) { std::vector found_nullifiers; std::vector found_notes; + std::vector commitments; + uint32_t cmu_index = 0; for (const auto& block : blocks) { // Scan block using the decoder initialized with the provided fvk // to find new spendable notes. @@ -54,6 +58,16 @@ OrchardBlockScanner::ScanBlocks( return base::unexpected(ErrorCode::kInputError); } + if (orchard_action->cmx.size() != kOrchardCmxSize) { + return base::unexpected(ErrorCode::kInputError); + } + + OrchardCommitment commitment; + base::ranges::copy(orchard_action->cmx.begin(), + orchard_action->cmx.end(), commitment.cmu.begin()); + commitment.block_id = block->height; + commitment.index = cmu_index++; + std::array action_nullifier; base::ranges::copy(orchard_action->nullifier, action_nullifier.begin()); @@ -72,7 +86,8 @@ OrchardBlockScanner::ScanBlocks( } } } - return Result({std::move(found_notes), std::move(found_nullifiers)}); + return Result({std::move(found_notes), std::move(found_nullifiers), + std::move(commitments)}); } } // 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 e5380b02edd4..ca68633aea6c 100644 --- a/components/brave_wallet/browser/internal/orchard_block_scanner.h +++ b/components/brave_wallet/browser/internal/orchard_block_scanner.h @@ -26,7 +26,8 @@ class OrchardBlockScanner { struct Result { Result(); Result(std::vector discovered_notes, - std::vector spent_notes); + std::vector spent_notes, + std::vector commitments); Result(const Result&); Result& operator=(const Result&); ~Result(); @@ -35,6 +36,8 @@ class OrchardBlockScanner { std::vector discovered_notes; // Nullifiers for the previously discovered notes std::vector spent_notes; + + std::vector commitments; }; explicit OrchardBlockScanner(const OrchardFullViewKey& full_view_key); diff --git a/components/brave_wallet/browser/internal/orchard_shard_tree_manager.cc b/components/brave_wallet/browser/internal/orchard_shard_tree_manager.cc new file mode 100644 index 000000000000..7d0f4bd51c3b --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_shard_tree_manager.cc @@ -0,0 +1,40 @@ +/* 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_shard_tree_manager.h" + +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +// static +std::unique_ptr OrchardShardTreeManager::Create( + std::unique_ptr delegate) { + auto shard_tree = orchard::OrchardShardTree::Create(std::move(delegate)); + if (!shard_tree) { + // NOTREACHED + return nullptr; + } + return std::make_unique(std::move(shard_tree)); +} + +OrchardShardTreeManager::OrchardShardTreeManager( + std::unique_ptr<::brave_wallet::orchard::OrchardShardTree> shard_tree) { + orchard_shard_tree_ = std::move(shard_tree); +} + +OrchardShardTreeManager::~OrchardShardTreeManager() {} + +bool OrchardShardTreeManager::InsertCommitments( + std::vector commitments) { + return orchard_shard_tree_->InsertCommitments(std::move(commitments)); +} + +bool OrchardShardTreeManager::InsertSubtreeRoots( + std::vector roots) { + return orchard_shard_tree_->InsertSubtreeRoots(std::move(roots)); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/orchard_shard_tree_manager.h b/components/brave_wallet/browser/internal/orchard_shard_tree_manager.h new file mode 100644 index 000000000000..794a5dabd0cd --- /dev/null +++ b/components/brave_wallet/browser/internal/orchard_shard_tree_manager.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_INTERNAL_ORCHARD_SHARD_TREE_MANAGER_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_SHARD_TREE_MANAGER_H_ + +#include "base/types/expected.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_block_decoder.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 { + +class OrchardShardTreeManager { + public: + OrchardShardTreeManager( + std::unique_ptr<::brave_wallet::orchard::OrchardShardTree> shard_tree); + ~OrchardShardTreeManager(); + bool InsertCommitments(std::vector commitments); + bool InsertSubtreeRoots(std::vector roots); + + static std::unique_ptr Create( + std::unique_ptr delegate); + + private: + std::unique_ptr<::brave_wallet::orchard::OrchardShardTree> + orchard_shard_tree_; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_ORCHARD_SHARD_TREE_MANAGER_H_ diff --git a/components/brave_wallet/browser/zcash/rust/BUILD.gn b/components/brave_wallet/browser/zcash/rust/BUILD.gn index 5b4044bdb3e1..a5d96257694e 100644 --- a/components/brave_wallet/browser/zcash/rust/BUILD.gn +++ b/components/brave_wallet/browser/zcash/rust/BUILD.gn @@ -24,6 +24,7 @@ source_set("orchard_headers") { "authorized_orchard_bundle.h", "extended_spending_key.h", "orchard_block_decoder.h", + "orchard_shard_tree.h", "unauthorized_orchard_bundle.h", ] @@ -44,6 +45,8 @@ source_set("orchard_impl") { "extended_spending_key_impl.h", "orchard_block_decoder_impl.cc", "orchard_block_decoder_impl.h", + "orchard_shard_tree_impl.cc", + "orchard_shard_tree_impl.h", "unauthorized_orchard_bundle_impl.cc", "unauthorized_orchard_bundle_impl.h", ] @@ -57,6 +60,16 @@ source_set("orchard_impl") { ] } +source_set("shard_store") { + visibility = [ ":*" ] + sources = [ "cxx/src/shard_store.h" ] + + public_deps = [ + "//base", + "//build/rust:cxx_cppdeps", + ] +} + rust_static_library("rust_lib") { visibility = [ ":orchard_impl" ] @@ -69,6 +82,7 @@ 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/byteorder/v1:lib", "//brave/third_party/rust/incrementalmerkletree/v0_5:lib", @@ -76,6 +90,9 @@ rust_static_library("rust_lib") { "//brave/third_party/rust/nonempty/v0_7:lib", "//brave/third_party/rust/orchard/v0_8: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", ] + + public_deps = [ ":shard_store" ] } diff --git a/components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h b/components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h new file mode 100644 index 000000000000..72429dc9096f --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h @@ -0,0 +1,70 @@ +// 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 http://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_CXX_SRC_SHARD_STORE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_CXX_SRC_SHARD_STORE_H_ + +#include "brave/components/brave_wallet/common/zcash_utils.h" +#include "third_party/rust/cxx/v1/cxx.h" + +namespace brave_wallet::orchard { + +enum class ShardStoreStatusCode : uint32_t; +struct FfiShardTree; +struct FfiShardAddress; +struct FfiCheckpoint; +struct FfiCap; + +using ShardStoreContext = ::brave_wallet::OrchardShardTreeDelegate; + +ShardStoreStatusCode orchard_last_shard(const ShardStoreContext& ctx, + FfiShardTree& into); +ShardStoreStatusCode orchard_put_shard(ShardStoreContext& ctx, + const FfiShardTree& tree); +ShardStoreStatusCode orchard_get_shard(const ShardStoreContext& ctx, + const FfiShardAddress& addr, + FfiShardTree& tree); +ShardStoreStatusCode orchard_get_shard_roots( + const ShardStoreContext& ctx, + ::rust::Vec& into); +ShardStoreStatusCode orchard_truncate(ShardStoreContext& ctx, + const FfiShardAddress& address); +ShardStoreStatusCode orchard_get_cap(const ShardStoreContext& ctx, + FfiCap& into); +ShardStoreStatusCode orchard_put_cap(ShardStoreContext& ctx, + const FfiCap& tree); +ShardStoreStatusCode orchard_min_checkpoint_id(const ShardStoreContext& ctx, uint32_t& into); +ShardStoreStatusCode orchard_max_checkpoint_id(const ShardStoreContext& ctx, uint32_t& into); +ShardStoreStatusCode orchard_add_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id, + const FfiCheckpoint& checkpoint); +ShardStoreStatusCode orchard_checkpoint_count(const ShardStoreContext& ctx, size_t& into); +ShardStoreStatusCode orchard_get_checkpoint_at_depth( + const ShardStoreContext& ctx, + size_t depth, + uint32_t& into_checkpoint_id, + FfiCheckpoint& into_checpoint); +ShardStoreStatusCode orchard_get_checkpoint(const ShardStoreContext& ctx, + uint32_t checkpoint_id, + FfiCheckpoint& into); +ShardStoreStatusCode orchard_update_checkpoint( + ShardStoreContext& ctx, + uint32_t checkpoint_id, + const FfiCheckpoint& checkpoint); +ShardStoreStatusCode orchard_remove_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id); +ShardStoreStatusCode orchard_truncate_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id); +ShardStoreStatusCode orchard_with_checkpoints(const ShardStoreContext& ctx, + size_t limit, + rust::cxxbridge1::Fn fn); +ShardStoreStatusCode orchard_get_checkpoints(const ShardStoreContext& ctx, + size_t limit, + ::rust::Vec& into); + +} // namespace brave_wallet::orchard + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_RUST_CXX_SRC_SHARD_STORE_H_ diff --git a/components/brave_wallet/browser/zcash/rust/lib.rs b/components/brave_wallet/browser/zcash/rust/lib.rs index f35291090c55..7c77fd7a8317 100644 --- a/components/brave_wallet/browser/zcash/rust/lib.rs +++ b/components/brave_wallet/browser/zcash/rust/lib.rs @@ -32,8 +32,10 @@ use orchard::{ use zcash_note_encryption::EphemeralKeyBytes; use zcash_primitives::transaction::components::amount::Amount; +use zcash_protocol::consensus::BlockHeight; use ffi::OrchardOutput; +use ffi::ShardStoreContext as ShardStoreContext; use rand::rngs::OsRng; use rand::{RngCore, Error as OtherError}; @@ -42,13 +44,64 @@ use rand::CryptoRng; 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 std::rc::Rc; +use std::cell::RefCell; +use std::marker::PhantomData; +use std::error; +use incrementalmerkletree::Address; +use zcash_primitives::merkle_tree::HashSer; + +use crate::ffi::FfiShardTree; +use crate::ffi::FfiShardAddress; +use crate::ffi::FfiCheckpoint; +use crate::ffi::FfiCap; + +use std::cmp::{Ord, Ordering}; +use std::convert::TryFrom; +use std::ops::{Add, Bound, RangeBounds, Sub}; + +use crate::ffi::orchard_get_checkpoint; +use crate::ffi::orchard_remove_checkpoint; +use crate::ffi::orchard_min_checkpoint_id; +use crate::ffi::orchard_max_checkpoint_id; +use crate::ffi::orchard_checkpoint_count; +use crate::ffi::orchard_get_checkpoint_at_depth; +use crate::ffi::orchard_truncate_checkpoint; +use crate::ffi::orchard_get_cap; +use crate::ffi::orchard_last_shard; +use crate::ffi::orchard_get_shard; +use crate::ffi::orchard_get_shard_roots; +use crate::ffi::orchard_truncate; +use crate::ffi::orchard_put_cap; +use crate::ffi::orchard_add_checkpoint; +use crate::ffi::orchard_put_shard; +use crate::ffi::orchard_with_checkpoints; +use crate::ffi::orchard_update_checkpoint; +use crate::ffi::orchard_get_checkpoints; + +use crate::ffi::ShardStoreStatusCode; +use incrementalmerkletree::Position; +use std::io::Cursor; +use std::collections::BTreeSet; +// pub use brave_wallet::orchard; + // The rest of the wallet code should be updated to use this version of unwrap // and then this code can be removed #[macro_export] @@ -90,6 +143,9 @@ macro_rules! impl_result { }; } +pub(crate) const PRUNING_DEPTH: u8 = 100; +pub(crate) const SHARD_HEIGHT: u8 = 16; + #[derive(Clone)] pub(crate) struct MockRng(u64); @@ -132,7 +188,7 @@ impl RngCore for MockRng { } } - +#[allow(unused)] #[allow(unsafe_op_in_unsafe_fn)] #[cxx::bridge(namespace = brave_wallet::orchard)] mod ffi { @@ -152,6 +208,44 @@ mod ffi { enc_cipher_text : [u8; 52] // kOrchardCipherTextSize } + #[repr(u32)] + enum ShardStoreStatusCode { + Ok = 0, + None = 1, + Error = 2 + } + + #[derive(Default)] + struct FfiShardAddress { + level: u8, + index: u32 + } + + #[derive(Default)] + struct FfiCap { + data: Vec, + } + + #[derive(Default)] + struct FfiShardTree { + address: FfiShardAddress, + hash: [u8; 32], + data: Vec, + contains_marked: bool + } + + #[derive(Default)] + struct FfiCheckpoint { + empty: bool, + position: u32, + mark_removed: Vec + } + + struct FfiCheckpointBundle { + checkpoint_id: u32, + checkpoint: FfiCheckpoint + } + extern "Rust" { type OrchardExtendedSpendingKey; type OrchardUnauthorizedBundle; @@ -159,12 +253,16 @@ mod ffi { type BatchOrchardDecodeBundle; + type OrchardShardTreeBundle; + type OrchardExtendedSpendingKeyResult; type OrchardUnauthorizedBundleResult; type OrchardAuthorizedBundleResult; type BatchOrchardDecodeBundleResult; + type OrchardShardTreeBundleResult; + // OsRng is used fn create_orchard_bundle( tree_state: &[u8], @@ -230,6 +328,10 @@ mod ffi { // fvk array size should match kOrchardFullViewKeySize fn note_nullifier(self :&BatchOrchardDecodeBundle, fvk: &[u8; 96], index: usize) -> [u8; 32]; + fn is_ok(self: &OrchardShardTreeBundleResult) -> bool; + fn error_message(self: &OrchardShardTreeBundleResult) -> String; + fn unwrap(self: &OrchardShardTreeBundleResult) -> 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 @@ -240,7 +342,76 @@ mod ffi { // Orchard part of v5 transaction as described in // https://zips.z.cash/zip-0225 fn raw_tx(self: &OrchardAuthorizedBundle) -> Vec; + + fn create_shard_tree(ctx: UniquePtr) -> Box; + fn insert_subtree_roots(self: &OrchardShardTreeBundle, roots: Vec) -> bool; + fn insert_commitments(self: &OrchardShardTreeBundle, commitments: Vec) -> bool; } + + unsafe extern "C++" { + include!("brave/components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h"); + + type ShardStoreContext; + // type ShardStoreStatusCode; + + fn orchard_last_shard( + ctx: &ShardStoreContext, into: &mut FfiShardTree) -> ShardStoreStatusCode; + fn orchard_get_shard( + ctx: &ShardStoreContext, + addr: &FfiShardAddress, + tree: &mut FfiShardTree) -> ShardStoreStatusCode; + fn orchard_put_shard( + ctx: Pin<&mut ShardStoreContext>, + tree: &FfiShardTree) -> ShardStoreStatusCode; + fn orchard_get_shard_roots( + ctx: &ShardStoreContext, into: &mut Vec) -> ShardStoreStatusCode; + fn orchard_truncate( + ctx: Pin<&mut ShardStoreContext>, + address: &FfiShardAddress) -> ShardStoreStatusCode; + fn orchard_get_cap( + ctx: &ShardStoreContext, + into: &mut FfiCap) -> ShardStoreStatusCode; + fn orchard_put_cap( + ctx: Pin<&mut ShardStoreContext>, + tree: &FfiCap) -> ShardStoreStatusCode; + fn orchard_min_checkpoint_id( + ctx: &ShardStoreContext, into: &mut u32) -> ShardStoreStatusCode; + fn orchard_max_checkpoint_id( + ctx: &ShardStoreContext, into: &mut u32) -> ShardStoreStatusCode; + fn orchard_add_checkpoint( + ctx: Pin<&mut ShardStoreContext>, + checkpoint_id: u32, + checkpoint: &FfiCheckpoint) -> ShardStoreStatusCode; + fn orchard_update_checkpoint( + ctx: Pin<&mut ShardStoreContext>, + checkpoint_id: u32, + checkpoint: &FfiCheckpoint) -> ShardStoreStatusCode; + fn orchard_checkpoint_count(ctx: &ShardStoreContext, into: &mut usize) -> ShardStoreStatusCode; + fn orchard_get_checkpoint_at_depth( + ctx: &ShardStoreContext, + depth: usize, + into_checkpoint_id: &mut u32, + into_checkpoint: &mut FfiCheckpoint) -> ShardStoreStatusCode; + fn orchard_get_checkpoint( + ctx: &ShardStoreContext, + checkpoint_id: u32, + into: &mut FfiCheckpoint) -> ShardStoreStatusCode; + fn orchard_remove_checkpoint( + ctx: Pin<&mut ShardStoreContext>, + checkpoint_id: u32) -> ShardStoreStatusCode; + fn orchard_truncate_checkpoint( + ctx: Pin<&mut ShardStoreContext>, + checkpoint_id: u32) -> ShardStoreStatusCode; + fn orchard_with_checkpoints( + ctx: &ShardStoreContext, + limit: usize, + callback: fn(u32, &FfiCheckpoint) -> ShardStoreStatusCode) ->ShardStoreStatusCode; + fn orchard_get_checkpoints( + ctx: &ShardStoreContext, + limit: usize, + into: &mut Vec) -> ShardStoreStatusCode; + } + } #[derive(Debug)] @@ -251,6 +422,7 @@ pub enum Error { BuildError, FvkError, OrchardActionFormatError, + ShardTreeError, } impl_error!(Zip32Error, Zip32); @@ -264,11 +436,25 @@ 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::ShardTreeError => write!(f, "Failed to create shard tree") } } } +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + // match &self { + // Error::Serialization(e) => Some(e), + // Error::Query(e) => Some(e), + // Error::CheckpointConflict { .. } => None, + // Error::SubtreeDiscontinuity { .. } => None, + // } + } +} + + // 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. @@ -302,6 +488,17 @@ pub struct BatchOrchardDecodeBundleValue { outputs: Vec } +#[allow(dead_code)] +pub struct OrchardShardTreeBundleValue { + tree: ShardTree +} + +impl Clone for OrchardShardTreeBundleValue { + fn clone(&self) -> Self { + OrchardShardTreeBundleValue { tree : ShardTree::new(self.tree.store().clone(), PRUNING_DEPTH.try_into().unwrap()) } + } +} + #[derive(Clone)] struct OrchardExtendedSpendingKey(ExtendedSpendingKey); #[derive(Clone)] @@ -310,17 +507,22 @@ struct OrchardAuthorizedBundle(OrchardAuthorizedBundleValue); struct OrchardUnauthorizedBundle(OrchardUnauthorizedBundleValue); #[derive(Clone)] struct BatchOrchardDecodeBundle(BatchOrchardDecodeBundleValue); +#[allow(dead_code)] +#[derive(Clone)] +struct OrchardShardTreeBundle(OrchardShardTreeBundleValue); struct OrchardExtendedSpendingKeyResult(Result); struct OrchardAuthorizedBundleResult(Result); struct OrchardUnauthorizedBundleResult(Result); struct BatchOrchardDecodeBundleResult(Result); +struct OrchardShardTreeBundleResult(Result); impl_result!(OrchardExtendedSpendingKey, OrchardExtendedSpendingKeyResult, ExtendedSpendingKey); impl_result!(OrchardAuthorizedBundle, OrchardAuthorizedBundleResult, OrchardAuthorizedBundleValue); impl_result!(OrchardUnauthorizedBundle, OrchardUnauthorizedBundleResult, OrchardUnauthorizedBundleValue); impl_result!(BatchOrchardDecodeBundle, BatchOrchardDecodeBundleResult, BatchOrchardDecodeBundleValue); +impl_result!(OrchardShardTreeBundle, OrchardShardTreeBundleResult, OrchardShardTreeBundleValue); fn generate_orchard_extended_spending_key_from_seed( bytes: &[u8] @@ -569,3 +771,404 @@ impl BatchOrchardDecodeBundle { } } + +impl OrchardShardTreeBundle { + fn insert_subtree_roots(self: &OrchardShardTreeBundle, _roots: Vec) -> bool { + false + } + + fn insert_commitments(self: &OrchardShardTreeBundle, _commitments: Vec) -> bool { + false + } +} +#[allow(dead_code)] +#[derive(Clone)] +pub struct CxxShardStoreImpl { + native_context: Rc>>, + _hash_type: PhantomData, +} + +impl From<&FfiCheckpoint> for Checkpoint { + fn from(item: &FfiCheckpoint) -> 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 FfiCheckpoint { + 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::ShardTreeError)? + }; + let marks_removed : Result, Error> = item.marks_removed().into_iter().map( + |x| u32::try_from(u64::from(*x)).map_err(|_| Error::ShardTreeError)).collect(); + Ok(FfiCheckpoint { + empty: item.is_tree_empty(), + position: position, + mark_removed: marks_removed? + }) + } +} + +impl TryFrom<&Address> for FfiShardAddress { + type Error = Error; + + fn try_from(item: &Address) -> Result { + let index : u32 = item.index().try_into().map_err(|_| Error::ShardTreeError)?; + Ok(FfiShardAddress{ + level: item.level().into(), + index: index }) + } +} + +impl From<&FfiShardAddress> for Address { + fn from(item: &FfiShardAddress) -> Self { + Address::from_parts(item.level.into(), item.index.into()) + } +} + +impl TryFrom<&FfiShardTree> for LocatedPrunableTree { + type Error = Error; + + fn try_from(item: &FfiShardTree) -> Result { + let shard_tree = read_shard(&mut Cursor::new(&item.data)).map_err(|_| Error::ShardTreeError)?; + 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)).map_err(|_| Error::ShardTreeError)?; + Ok(located_tree.reannotate_root(Some(Arc::new(root_hash)))) + } else { + Ok(located_tree) + } + } +} + +impl TryFrom<&FfiCap> for PrunableTree { + type Error = Error; + + fn try_from(item: &FfiCap) -> Result { + read_shard(&mut Cursor::new(&item.data)).map_err(|_| Error::ShardTreeError) + } +} + +impl TryFrom<&PrunableTree> for FfiCap { + type Error = Error; + + fn try_from(item: &PrunableTree) -> Result { + let mut data = vec![]; + write_shard(&mut data, item).map_err(|_| Error::ShardTreeError)?; + Ok(FfiCap { + data: data + }) + } +} + +impl TryFrom<&LocatedPrunableTree> for FfiShardTree { + 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::ShardTreeError)?; + + let mut result = FfiShardTree { + address: FfiShardAddress::try_from(&item.root_addr()).map_err(|_| Error::ShardTreeError)?, + hash: subtree_root_hash.unwrap_or_else(|| vec![0; 32]).try_into().map_err(|_| Error::ShardTreeError)?, + data: vec![], + contains_marked: false, + }; + + write_shard(&mut result.data, &item.root()).map_err(|_| Error::ShardTreeError)?; + Ok(result) + } +} + +type OrchardCxxShardStoreImpl = CxxShardStoreImpl; + +impl ShardStore + for CxxShardStoreImpl +{ + type H = H; + type CheckpointId = BlockHeight; + type Error = Error; + + fn get_shard( + &self, + addr: Address, + ) -> Result>, Self::Error> { + let ctx = self.native_context.clone(); + let mut into = FfiShardTree::default(); + let result = orchard_get_shard(&*ctx.try_borrow().unwrap(), + &FfiShardAddress::try_from(&addr).map_err(|_| Error::ShardTreeError)?, + &mut into); + if result == ShardStoreStatusCode::Ok { + let tree = LocatedPrunableTree::::try_from(&into)?; + return Ok(Some(tree)); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::None); + } else { + return Err(Error::ShardTreeError); + } + } + + fn last_shard(&self) -> Result>, Self::Error> { + let ctx = self.native_context.clone(); + let mut into = FfiShardTree::default(); + let result = orchard_last_shard(&*ctx.try_borrow().unwrap(), &mut into); + if result == ShardStoreStatusCode::Ok { + let tree = LocatedPrunableTree::::try_from(&into)?; + return Ok(Some(tree)); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::None); + } else { + return Err(Error::ShardTreeError); + } + } + + fn put_shard(&mut self, subtree: LocatedPrunableTree) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let ffi_tree = FfiShardTree::try_from(&subtree).map_err(|_| Error::ShardTreeError)?; + let result = orchard_put_shard(ctx.try_borrow_mut().unwrap().pin_mut(), &ffi_tree); + if result == ShardStoreStatusCode::Ok { + return Ok(()); + } + return Err(Error::ShardTreeError); + } + + fn get_shard_roots(&self) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input : Vec = vec![]; + let result = orchard_get_shard_roots(&*ctx.try_borrow().unwrap(), &mut input); + if result == ShardStoreStatusCode::Ok { + return Ok(input.into_iter().map(|res| { + Address::from_parts(res.level.into(), res.index.into()) + }).collect()) + } else if result == ShardStoreStatusCode::None { + return Ok(vec![]) + } else { + return Err(Error::ShardTreeError) + } + } + + fn truncate(&mut self, from: Address) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let result = orchard_truncate(ctx.try_borrow_mut().unwrap().pin_mut(), + &FfiShardAddress::try_from(&from).map_err(|_| Error::ShardTreeError)?); + if result == ShardStoreStatusCode::Ok || result == ShardStoreStatusCode::None { + return Ok(()); + } else { + return Err(Error::ShardTreeError) + } + } + + fn get_cap(&self) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input = FfiCap::default(); + let result = orchard_get_cap(&*ctx.try_borrow().unwrap(), &mut input); + + if result == ShardStoreStatusCode::Ok { + let tree = PrunableTree::::try_from(&input)?; + return Ok(tree) + } else + if result == ShardStoreStatusCode::None { + return Ok(PrunableTree::empty()); + } else { + return Err(Error::ShardTreeError); + } + } + + fn put_cap(&mut self, cap: PrunableTree) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let mut ffi_cap = FfiCap::default(); + write_shard(&mut ffi_cap.data, &cap).map_err(|_| Error::ShardTreeError)?; + + let result = orchard_put_cap(ctx.try_borrow_mut().unwrap().pin_mut(), &ffi_cap); + if result == ShardStoreStatusCode::Ok { + return Ok(()); + } + return Err(Error::ShardTreeError); + } + + fn min_checkpoint_id(&self) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input : u32 = 0; + let result = orchard_min_checkpoint_id(&*ctx.try_borrow().unwrap(), &mut input); + if result == ShardStoreStatusCode::Ok { + return Ok(Some(input.into())); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::None); + } + return Err(Error::ShardTreeError); + } + + fn max_checkpoint_id(&self) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input : u32 = 0; + let result = orchard_max_checkpoint_id(&*ctx.try_borrow().unwrap(), &mut input); + if result == ShardStoreStatusCode::Ok { + return Ok(Some(input.into())); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::None); + } + return Err(Error::ShardTreeError); + } + + fn add_checkpoint( + &mut self, + checkpoint_id: Self::CheckpointId, + checkpoint: Checkpoint, + ) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let ffi_checkpoint_id : u32 = checkpoint_id.try_into().map_err(|_| Error::ShardTreeError)?; + let result = orchard_add_checkpoint(ctx.try_borrow_mut().unwrap().pin_mut(), ffi_checkpoint_id, &FfiCheckpoint::try_from(&checkpoint)?); + if result == ShardStoreStatusCode::Ok { + return Ok(()); + } + return Err(Error::ShardTreeError); + } + + fn checkpoint_count(&self) -> Result { + let ctx = self.native_context.clone(); + let mut input : usize = 0; + let result = orchard_checkpoint_count(&*ctx.try_borrow().unwrap(), &mut input); + if result == ShardStoreStatusCode::Ok { + return Ok(input.into()); + } else if result == ShardStoreStatusCode::None { + return Ok(0); + } + return Ok(0); + } + + fn get_checkpoint_at_depth( + &self, + checkpoint_depth: usize, + ) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input_checkpoint_id : u32 = 0; + let mut input_checkpoint : FfiCheckpoint = FfiCheckpoint::default(); + + let result = orchard_get_checkpoint_at_depth(&*ctx.try_borrow().unwrap(), + checkpoint_depth, + &mut input_checkpoint_id, + &mut input_checkpoint); + + if result == ShardStoreStatusCode::Ok { + return Ok(Some((BlockHeight::from(input_checkpoint_id), Checkpoint::from(&input_checkpoint)))); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::None); + } + return Ok(Option::None); + } + + fn get_checkpoint( + &self, + checkpoint_id: &Self::CheckpointId, + ) -> Result, Self::Error> { + let ctx = self.native_context.clone(); + let mut input_checkpoint : FfiCheckpoint = FfiCheckpoint::default(); + + let result = orchard_get_checkpoint(&*ctx.try_borrow().unwrap(), + (*checkpoint_id).into(), + &mut input_checkpoint); + + if result == ShardStoreStatusCode::Ok { + return Ok(Some(Checkpoint::from(&input_checkpoint))); + } else if result == ShardStoreStatusCode::None { + return Ok(Option::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 ctx = self.native_context.clone(); + let mut into : Vec = vec![]; + let result = orchard_get_checkpoints(&*ctx.try_borrow().unwrap(), limit, &mut into); + if result == ShardStoreStatusCode::Ok { + for item in into { + let checkpoint = Checkpoint::from(&item); + callback(&BlockHeight::from(item.position), &checkpoint).map_err(|_| Error::ShardTreeError)?; + } + return Ok(()) + } else if result == ShardStoreStatusCode::None { + return Ok(()) + } + Err(Error::ShardTreeError) + } + + fn update_checkpoint_with( + &mut self, + checkpoint_id: &Self::CheckpointId, + update: F, + ) -> Result + where + F: Fn(&mut Checkpoint) -> Result<(), Self::Error>, + { + let ctx = self.native_context.clone(); + let mut input_checkpoint = FfiCheckpoint::default(); + let result_get_checkpoint = orchard_get_checkpoint(&*ctx.try_borrow().unwrap(), (*checkpoint_id).into(), &mut input_checkpoint); + if result_get_checkpoint == ShardStoreStatusCode::Ok { + return Ok(true); + } else if result_get_checkpoint == ShardStoreStatusCode::None { + return Ok(false); + } + + let mut checkpoint = Checkpoint::from(&input_checkpoint); + + update(&mut checkpoint).map_err(|_| Error::ShardTreeError)?; + let result_update_checkpoint = orchard_update_checkpoint(ctx.try_borrow_mut().unwrap().pin_mut(), (*checkpoint_id).into(), &FfiCheckpoint::try_from(&checkpoint)?); + if result_update_checkpoint == ShardStoreStatusCode::Ok { + return Ok(true); + } else if result_update_checkpoint == ShardStoreStatusCode::None { + return Ok(false); + } + return Err(Error::ShardTreeError); + } + + fn remove_checkpoint(&mut self, checkpoint_id: &Self::CheckpointId) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let result = orchard_remove_checkpoint(ctx.try_borrow_mut().unwrap().pin_mut(), (*checkpoint_id).into()); + if result == ShardStoreStatusCode::Ok { + return Ok(()); + } else if result == ShardStoreStatusCode::None { + return Ok(()); + } + return Err(Error::ShardTreeError); + } + + fn truncate_checkpoints( + &mut self, + checkpoint_id: &Self::CheckpointId, + ) -> Result<(), Self::Error> { + let ctx = self.native_context.clone(); + let result = orchard_truncate_checkpoint(ctx.try_borrow_mut().unwrap().pin_mut(), (*checkpoint_id).into()); + if result == ShardStoreStatusCode::Ok { + return Ok(()); + } else if result == ShardStoreStatusCode::None { + return Ok(()); + } + return Err(Error::ShardTreeError); + } +} + +fn create_shard_tree(context: UniquePtr) -> Box { + let shard_store = OrchardCxxShardStoreImpl { + native_context: Rc::new(RefCell::new(context)), + _hash_type: Default::default() + }; + let shardtree = ShardTree::new(shard_store, PRUNING_DEPTH.try_into().unwrap()); + Box::new(OrchardShardTreeBundleResult::from(Ok(OrchardShardTreeBundleValue{tree: shardtree}))) +} \ 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 dade16e02910..471cfe2490cc 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/orchard_shard_tree.h b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h new file mode 100644 index 000000000000..55bb88d7a98e --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h @@ -0,0 +1,24 @@ +// 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 + +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +class OrchardShardTree { + public: + virtual ~OrchardShardTree() = 0; + + virtual bool InsertSubtreeRoots(std::vector subtrees) = 0; + virtual bool InsertCommitments( + std::vector commitments) = 0; + + static std::unique_ptr Create( + std::unique_ptr<::brave_wallet::OrchardShardTreeDelegate> delegate); +}; + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.cc b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.cc new file mode 100644 index 000000000000..5e5685bfd3fc --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.cc @@ -0,0 +1,324 @@ +// 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 http://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.h" + +#include "base/memory/ptr_util.h" +#include "base/ranges/algorithm.h" +#include "brave/components/brave_wallet/browser/zcash/rust/cxx/src/shard_store.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet::orchard { + +::brave_wallet::OrchardShardAddress From(const FfiShardAddress& addr) { + return ::brave_wallet::OrchardShardAddress{addr.level, addr.index}; +} + +FfiShardAddress From(const ::brave_wallet::OrchardShardAddress& addr) { + return FfiShardAddress{addr.level, addr.index}; +} + +FfiCap From(::brave_wallet::OrchardCap& cap) { + ::rust::Vec data; + base::ranges::copy(cap.data.begin(), cap.data.end(), data.begin()); + return FfiCap{std::move(data)}; +} + +::brave_wallet::OrchardCap From(const FfiCap& cap) { + std::vector data; + base::ranges::copy(cap.data.begin(), cap.data.end(), data.begin()); + return ::brave_wallet::OrchardCap{std::move(data)}; +} + +::brave_wallet::OrchardShard From(const FfiShardTree& tree) { + std::vector data; + base::ranges::copy(tree.data.begin(), tree.data.end(), data.begin()); + return ::brave_wallet::OrchardShard(From(tree.address), tree.hash, + std::move(data), tree.contains_marked); +} + +FfiShardTree From(const ::brave_wallet::OrchardShard& tree) { + ::rust::Vec data; + base::ranges::copy(tree.shard_data.begin(), tree.shard_data.end(), + data.begin()); + + return FfiShardTree{From(tree.address), tree.root_hash, std::move(data), + tree.contains_marked}; +} + +FfiCheckpoint From(const ::brave_wallet::OrchardCheckpoint& checkpoint) { + return FfiCheckpoint{}; +} + +FfiCheckpointBundle From(const ::brave_wallet::OrchardCheckpointBundle& checkpoint_bundle) { + return FfiCheckpointBundle(checkpoint_bundle.checkpoint_id, From(checkpoint_bundle.checkpoint)); +} + +::brave_wallet::OrchardCheckpoint From(const FfiCheckpoint& checkpoint) { + return ::brave_wallet::OrchardCheckpoint{ checkpoint.empty, checkpoint.position, std::vector(checkpoint.mark_removed.begin(), checkpoint.mark_removed.end()) }; +} + +ShardStoreStatusCode orchard_get_shard(const ShardStoreContext& ctx, + const FfiShardAddress& addr, + FfiShardTree& input) { + auto shard = ctx.GetShard(From(addr)); + if (!shard.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!shard.value()) { + return ShardStoreStatusCode::None; + } + input = From(**shard); + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_last_shard(const ShardStoreContext& ctx, + FfiShardTree& input) { + auto shard = ctx.LastShard(0); + if (!shard.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!shard.value()) { + return ShardStoreStatusCode::None; + } + input = From(**shard); + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_put_shard(ShardStoreContext& ctx, + const FfiShardTree& tree) { + auto result = ctx.PutShard(From(tree)); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_get_shard_roots( + const ShardStoreContext& ctx, + ::rust::Vec& input) { + auto shard = ctx.GetShardRoots(0); + if (!shard.has_value()) { + return ShardStoreStatusCode::Error; + } + for (const auto& root : *shard) { + input.push_back(From(root)); + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_truncate(ShardStoreContext& ctx, + const FfiShardAddress& address) { + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_get_cap(const ShardStoreContext& ctx, + FfiCap& input) { + auto result = ctx.GetCap(); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + input = From(**result); + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_put_cap(ShardStoreContext& ctx, + const FfiCap& tree) { + auto result = ctx.PutCap(From(tree)); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_min_checkpoint_id(const ShardStoreContext& ctx, + uint32_t& input) { + auto result = ctx.MinCheckpointId(); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + input = **result; + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_max_checkpoint_id(const ShardStoreContext& ctx, + uint32_t& input) { + auto result = ctx.MaxCheckpointId(); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + input = **result; + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_add_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id, + const FfiCheckpoint& checkpoint) { + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_checkpoint_count(const ShardStoreContext& ctx, + size_t& into) { + auto result = ctx.CheckpointCount(); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } + into = *result; + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_get_checkpoint_at_depth( + const ShardStoreContext& ctx, + size_t depth, + uint32_t& into_checkpoint_id, + FfiCheckpoint& into_checkpoint) { + auto checkpoint_id = ctx.GetCheckpointAtDepth(depth); + if (!checkpoint_id.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!checkpoint_id.value()) { + return ShardStoreStatusCode::None; + } + into_checkpoint_id = **checkpoint_id; + + auto checkpoint = ctx.GetCheckpoint(into_checkpoint_id); + if (!checkpoint.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!checkpoint.value()) { + return ShardStoreStatusCode::None; + } + into_checkpoint = From((**checkpoint).checkpoint); + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_get_checkpoint(const ShardStoreContext& ctx, + uint32_t checkpoint_id, + FfiCheckpoint& input) { + auto checkpoint = ctx.GetCheckpoint(checkpoint_id); + if (!checkpoint.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!checkpoint.value()) { + return ShardStoreStatusCode::None; + } + input = From((**checkpoint).checkpoint); + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_update_checkpoint( + ShardStoreContext& ctx, + uint32_t checkpoint_id, + const FfiCheckpoint& checkpoint) { + auto result = ctx.UpdateCheckpoint(checkpoint_id, From(checkpoint)); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_remove_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id) { + auto result = ctx.RemoveCheckpoint(checkpoint_id); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_truncate_checkpoint(ShardStoreContext& ctx, + uint32_t checkpoint_id) { + auto result = ctx.TruncateCheckpoints(checkpoint_id); + if (!result.has_value()) { + return ShardStoreStatusCode::Error; + } else if (!result.value()) { + return ShardStoreStatusCode::None; + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_with_checkpoints(const ShardStoreContext& ctx, + size_t limit, + rust::cxxbridge1::Fn fn) { + auto checkpoints = ctx.GetCheckpoints(limit); + if (!checkpoints.has_value()) { + return ShardStoreStatusCode::Error; + } else if (checkpoints->empty()) { + return ShardStoreStatusCode::None; + } + + // TODO(cypt4): Make via a call to the DB + for (const auto& checkpoint : checkpoints.value()) { + auto r = fn(checkpoint.checkpoint_id, From(checkpoint.checkpoint)); + if (r != ShardStoreStatusCode::Ok) { + return r; + } + } + return ShardStoreStatusCode::Ok; +} + +ShardStoreStatusCode orchard_get_checkpoints(const ShardStoreContext& ctx, + size_t limit, + ::rust::Vec& into) { + auto checkpoints = ctx.GetCheckpoints(limit); + if (!checkpoints.has_value()) { + return ShardStoreStatusCode::Error; + } + if (checkpoints->empty()) { + return ShardStoreStatusCode::None; + } + for (const auto& checkpoint : checkpoints.value()) { + into.push_back(From(checkpoint.checkpoint)); + } + return ShardStoreStatusCode::Ok; +} + +bool OrchardShardTreeImpl::InsertCommitments( + std::vector<::brave_wallet::OrchardCommitment> commitments) { + // ::rust::Vec rust_commitments; + // for (const auto& item : commitments) { + // OrchardCommitment commitment; + // rust_commitments.emplace_back(std::move(orchard_compact_action)); + // } + // return orcard_shard_tree_->insert_commitments(std::move(rust_commitments)); + return false; +} + +bool OrchardShardTreeImpl::InsertSubtreeRoots( + std::vector<::brave_wallet::OrchardShard> subtrees) { + // ::rust::Vec rust_subtrees; + // for (const auto& item : subtrees) { + // FfiShardTree rust_subtree; + // rust_subtrees.emplace_back(std::move(rust_subtree)); + // } + // return orcard_shard_tree_->insert_subtree_roots(std::move(rust_subtrees)); + return false; +} + +OrchardShardTreeImpl::~OrchardShardTreeImpl() {} + +// static +std::unique_ptr OrchardShardTree::Create( + std::unique_ptr<::brave_wallet::OrchardShardTreeDelegate> delegate) { + // auto shard_tree_context = base::WrapUnique( + // new ShardStoreContextImpl(std::move(delegate))); + auto shard_tree_result = + ::brave_wallet::orchard::create_shard_tree(std::move(delegate)); + if (!shard_tree_result->is_ok()) { + return nullptr; + } + return base::WrapUnique( + new OrchardShardTreeImpl(shard_tree_result->unwrap())); +} + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.h b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.h new file mode 100644 index 000000000000..c29884d40d25 --- /dev/null +++ b/components/brave_wallet/browser/zcash/rust/orchard_shard_tree_impl.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 http://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/rust/lib.rs.h" +#include "brave/components/brave_wallet/browser/zcash/rust/orchard_shard_tree.h" + +namespace brave_wallet::orchard { + +class OrchardShardTreeImpl : public OrchardShardTree { + public: + OrchardShardTreeImpl(rust::Box orcard_shard_tree); + ~OrchardShardTreeImpl() override; + bool InsertCommitments(std::vector commitments) override; + bool InsertSubtreeRoots(std::vector subtrees) override; + + private: + static std::unique_ptr Create( + std::unique_ptr<::brave_wallet::orchard::ShardStoreContext> context); + + ::rust::Box orcard_shard_tree_; +}; + +} // namespace brave_wallet::orchard diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc b/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc index bea195b8c843..222115319ac8 100644 --- a/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc +++ b/components/brave_wallet/browser/zcash/zcash_orchard_storage.cc @@ -25,6 +25,9 @@ namespace { #define kNotesTable "notes" #define kSpentNotesTable "spent_notes" #define kAccountMeta "account_meta" +#define kShardTree "shard_tree" +#define kShardTreeCheckpoints "checkpoints" +#define kCheckpointsMarksRemoved "checkpoints_mark_removed" const int kEmptyDbVersionNumber = 1; const int kCurrentVersionNumber = 2; @@ -124,6 +127,28 @@ bool ZCashOrchardStorage::CreateSchema() { "account_birthday INTEGER NOT NULL," "latest_scanned_block INTEGER NOT NULL," "latest_scanned_block_hash TEXT NOT NULL);") && + database_->Execute("CREATE TABLE " kShardTree + " (" + "shard_index INTEGER PRIMARY KEY," + "subtree_end_height INTEGER," + "root_hash BLOB," + "shard_data BLOB," + "contains_marked INTEGER," + "CONSTRAINT root_unique UNIQUE (root_hash));") && + database_->Execute("CREATE TABLE " kShardTreeCheckpoints + " (" + "checkpoint_id INTEGER PRIMARY KEY," + "position INTEGER)") && + database_->Execute("CREATE TABLE " kCheckpointsMarksRemoved + " (" + "checkpoint_id INTEGER NOT NULL," + "mark_removed_position INTEGER NOT NULL," + "FOREIGN KEY (checkpoint_id) REFERENCES " + "orchard_tree_checkpoints(checkpoint_id)" + "ON DELETE CASCADE," + "CONSTRAINT spend_position_unique UNIQUE " + "(checkpoint_id, mark_removed_position)" + ")") && transaction.Commit(); } @@ -420,4 +445,566 @@ std::optional ZCashOrchardStorage::UpdateNotes( return std::nullopt; } +base::expected +ZCashOrchardStorage::GetLatestShardIndex(mojom::AccountIdPtr account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement resolve_max_shard_id( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT " + "max(shard_index) FROM " kShardTree " " + "WHERE account_id = ?;")); + + resolve_max_shard_id.BindString(0, account_id->unique_key); + if (resolve_max_shard_id.Step()) { + auto shard_index = ReadUint32(resolve_max_shard_id, 0); + if (!shard_index) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + return shard_index.value(); + } + + return 0; +} + +base::expected +ZCashOrchardStorage::PutShardRoots( + mojom::AccountIdPtr account_id, + uint8_t shard_roots_height, + uint32_t start_position, + std::vector roots) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Transaction transaction(database_.get()); + if (!transaction.Begin()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + // Insert found notes to the notes table + sql::Statement statement_populate_roots(database_->GetCachedStatement( + SQL_FROM_HERE, "INSERT INTO " kShardTree " " + "(shard_index, subtree_end_height, root_hash, shard_data, " + "contains_marked) " + "VALUES (?, ?, ?, ?, ?);")); + + uint32_t counter = start_position; + for (const auto& root : roots) { + statement_populate_roots.Reset(true); + statement_populate_roots.BindInt64(0, counter++); + statement_populate_roots.BindInt64(1, shard_roots_height); + statement_populate_roots.BindBlob(2, root.root_hash); + statement_populate_roots.BindBlob(3, root.shard_data); + statement_populate_roots.BindInt64(4, 0); + if (!statement_populate_roots.Run()) { + NOTREACHED_IN_MIGRATION(); + transaction.Rollback(); + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + } + + if (!transaction.Commit()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + + return true; +} + +base::expected +ZCashOrchardStorage::TruncateShards(mojom::AccountIdPtr account_id, + uint32_t shard_index) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement remove_checkpoint_by_id( + database_->GetCachedStatement(SQL_FROM_HERE, "DELETE FROM " kShardTree " " + "WHERE shard_index >= ?")); + + remove_checkpoint_by_id.BindInt64(0, shard_index); + + if (!remove_checkpoint_by_id.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + return true; +} + +base::expected ZCashOrchardStorage::PutShard( + mojom::AccountIdPtr account_id, + OrchardShard shard) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Transaction transaction(database_.get()); + if (!transaction.Begin()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + // Insert found notes to the notes table + sql::Statement statement_put_shard(database_->GetCachedStatement( + SQL_FROM_HERE, "INSERT INTO " kShardTree " " + "(shard_index, root_hash, shard_data) " + "VALUES (:shard_index, :root_hash, :shard_data) " + "ON CONFLICT (shard_index) DO UPDATE " + "SET root_hash = :root_hash, " + "shard_data = :shard_data")); + + statement_put_shard.BindInt64(0, shard.address.index); + statement_put_shard.BindBlob(1, shard.root_hash); + statement_put_shard.BindBlob(2, shard.shard_data); + + if (!statement_put_shard.Run()) { + NOTREACHED_IN_MIGRATION(); + transaction.Rollback(); + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + + if (!transaction.Commit()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + + return true; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::LastShard(mojom::AccountIdPtr account_id, + uint8_t shard_height) { + auto shard_index = GetLatestShardIndex(account_id.Clone()); + if (!shard_index.has_value()) { + return base::unexpected(shard_index.error()); + } + + return GetShard(account_id.Clone(), + OrchardShardAddress{shard_height, shard_index.value()}); +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::GetShardRoots(mojom::AccountIdPtr account_id, + uint8_t shard_level) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + std::vector result; + + sql::Statement resolve_shards_statement(database_->GetCachedStatement( + SQL_FROM_HERE, + "SELECT shard_index FROM " kShardTree " ORDER BY shard_index")); + + while (!resolve_shards_statement.Step()) { + auto shard_index = ReadUint32(resolve_shards_statement, 0); + if (!shard_index) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, ""}); + } + result.push_back(OrchardShardAddress{shard_level, shard_index.value()}); + } + + return result; +} + +base::expected +ZCashOrchardStorage::CheckpointCount(mojom::AccountIdPtr account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement resolve_checkpoints_count(database_->GetCachedStatement( + SQL_FROM_HERE, "SELECT COUNT(*) FROM " kShardTreeCheckpoints)); + + if (!resolve_checkpoints_count.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + auto value = ReadUint32(resolve_checkpoints_count, 0); + + if (!value) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + return *value; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::MinCheckpointId(mojom::AccountIdPtr account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement resolve_min_checkpoint_id(database_->GetCachedStatement( + SQL_FROM_HERE, "SELECT MIN(checkpoint_id) " kShardTreeCheckpoints)); + + if (!resolve_min_checkpoint_id.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + auto value = ReadUint32(resolve_min_checkpoint_id, 0); + + if (!value) { + NOTREACHED_IN_MIGRATION(); + return std::nullopt; + } + + return *value; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::MaxCheckpointId(mojom::AccountIdPtr account_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement resolve_max_checkpoint_id(database_->GetCachedStatement( + SQL_FROM_HERE, "SELECT MAX(checkpoint_id) " kShardTreeCheckpoints)); + + if (!resolve_max_checkpoint_id.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + auto value = ReadUint32(resolve_max_checkpoint_id, 0); + + if (!value) { + NOTREACHED_IN_MIGRATION(); + return std::nullopt; + } + + return *value; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::GetCheckpointAtDepth(mojom::AccountIdPtr account_id, + uint32_t depth) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement get_checkpoint_at_depth_statement( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT checkpoint_id, position " + "FROM " kShardTreeCheckpoints " " + "ORDER BY checkpoint_id DESC " + "LIMIT 1 " + "OFFSET ?")); + + get_checkpoint_at_depth_statement.BindInt64(0, depth); + + if (!get_checkpoint_at_depth_statement.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + auto value = ReadUint32(get_checkpoint_at_depth_statement, 0); + + if (!value) { + NOTREACHED_IN_MIGRATION(); + return std::nullopt; + } + + return *value; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::GetCheckpoint(mojom::AccountIdPtr account_id, + uint32_t checkpoint_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected(Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement get_checkpoint_statement( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT position " + "FROM " kShardTreeCheckpoints " " + "WHERE checkpoint_id = ?")); + + get_checkpoint_statement.BindInt64(0, checkpoint_id); + if (!get_checkpoint_statement.Run()) { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + + sql::Statement marks_removed_statement( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT mark_removed_position " + "FROM " kCheckpointsMarksRemoved " " + "WHERE checkpoint_id = ?")); + + std::vector positions; + while (!marks_removed_statement.Run()) { + auto position = ReadUint32(marks_removed_statement, 0); + if (position) { + positions.push_back(*position); + } else { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + } + + auto checkpoint_position = ReadUint32(get_checkpoint_statement, 0); + + if (checkpoint_position) { + return OrchardCheckpointBundle{checkpoint_id, OrchardCheckpoint{ false, *checkpoint_position, std::move(positions) }}; + } else { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } +} + +base::expected, ZCashOrchardStorage::Error> ZCashOrchardStorage::GetCheckpoints(mojom::AccountIdPtr account_id, size_t limit) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected(Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement get_checkpoints_statement( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT checkpoint_id, position " + "FROM " kShardTreeCheckpoints " " + "ORDER BY position " + "LIMIT ?")); + + get_checkpoints_statement.BindInt64(0, limit); + + std::vector checkpoints; + while (!get_checkpoints_statement.Step()) { + sql::Statement marks_removed_statement( + database_->GetCachedStatement(SQL_FROM_HERE, + "SELECT mark_removed_position " + "FROM " kCheckpointsMarksRemoved " " + "WHERE checkpoint_id = ?")); + + std::vector positions; + while (!marks_removed_statement.Run()) { + auto position = ReadUint32(marks_removed_statement, 0); + if (position) { + positions.push_back(*position); + } else { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + } + + auto checkpoint_id = ReadUint32(get_checkpoints_statement, 0); + auto checkpoint_position = ReadUint32(get_checkpoints_statement, 1); + + if (checkpoint_id && checkpoint_position) { + checkpoints.push_back(OrchardCheckpointBundle { + *checkpoint_id, OrchardCheckpoint { false, *checkpoint_position, std::move(positions)}}); + } else { + return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, + database_->GetErrorMessage()}); + } + } + return checkpoints; +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardStorage::GetMaxCheckpointedHeight(mojom::AccountIdPtr account_id, + uint32_t chain_tip_height, + uint32_t min_confirmations) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + uint32_t max_checkpointed_height = chain_tip_height - min_confirmations - 1; + + sql::Statement get_max_checkpointed_height(database_->GetCachedStatement( + SQL_FROM_HERE, "SELECT checkpoint_id FROM " kShardTreeCheckpoints " " + "WHERE checkpoint_id <= :max_checkpoint_height " + "ORDER BY checkpoint_id DESC " + "LIMIT 1")); + + get_max_checkpointed_height.BindInt64(0, max_checkpointed_height); + + if (!get_max_checkpointed_height.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + auto value = ReadUint32(get_max_checkpointed_height, 0); + + if (!value) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + return *value; +} + +base::expected +ZCashOrchardStorage::RemoveCheckpoint(mojom::AccountIdPtr account_id, + uint32_t checkpoint_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement remove_checkpoint_by_id(database_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM " kShardTreeCheckpoints " " + "WHERE checkpoint_id = ?")); + + remove_checkpoint_by_id.BindInt64(0, checkpoint_id); + + if (!remove_checkpoint_by_id.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + return true; +} + +base::expected +ZCashOrchardStorage::TruncateCheckpoints(mojom::AccountIdPtr account_id, + uint32_t checkpoint_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!EnsureDbInit()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); + } + + sql::Statement truncate_checkpoints(database_->GetCachedStatement( + SQL_FROM_HERE, + "DELETE FROM " kShardTreeCheckpoints " WHERE checkpoint_id >= ?")); + + truncate_checkpoints.BindInt64(0, checkpoint_id); + + if (!truncate_checkpoints.Step()) { + NOTREACHED_IN_MIGRATION(); + return base::unexpected( + Error{ErrorCode::kNoCheckpoints, database_->GetErrorMessage()}); + } + + return true; +} + +// // TODO(cypt4): Rewrite on JOIN +// base::expected ZCashOrchardStorage::WithCheckpoints( +// mojom::AccountIdPtr account_id, +// uint32_t limit, +// WithCheckpointsCallback callback) { +// DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + +// if (!EnsureDbInit()) { +// NOTREACHED_IN_MIGRATION(); +// return base::unexpected(Error{ErrorCode::kDbInitError, database_->GetErrorMessage()}); +// } + +// sql::Statement get_checkpoints_statement( +// database_->GetCachedStatement(SQL_FROM_HERE, +// "SELECT checkpoint_id, position " +// "FROM " kShardTreeCheckpoints " " +// "ORDER BY position " +// "LIMIT ?")); + +// get_checkpoints_statement.BindInt64(0, limit); + +// while (!get_checkpoints_statement.Step()) { +// sql::Statement marks_removed_statement( +// database_->GetCachedStatement(SQL_FROM_HERE, +// "SELECT mark_removed_position " +// "FROM " kCheckpointsMarksRemoved " " +// "WHERE checkpoint_id = ?")); + +// std::vector positions; +// while (!marks_removed_statement.Run()) { +// auto position = ReadUint32(marks_removed_statement, 0); +// if (position) { +// positions.push_back(*position); +// } else { +// return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, +// database_->GetErrorMessage()}); +// } +// } + +// auto checkpoint_id = ReadUint32(get_checkpoints_statement, 0); +// auto checkpoint_position = ReadUint32(get_checkpoints_statement, 1); + +// if (checkpoint_id && checkpoint_position) { +// callback.Run(*checkpoint_id, OrchardCheckpoint{ false, *checkpoint_position, std::move(positions) }); +// } else { +// return base::unexpected(Error{ErrorCode::kFailedToExecuteStatement, +// database_->GetErrorMessage()}); +// } +// } +// return true; +// } + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_storage.h b/components/brave_wallet/browser/zcash/zcash_orchard_storage.h index 6594af6ca9b7..ebd51be2a9fc 100644 --- a/components/brave_wallet/browser/zcash/zcash_orchard_storage.h +++ b/components/brave_wallet/browser/zcash/zcash_orchard_storage.h @@ -26,8 +26,11 @@ namespace brave_wallet { // Implements SQLite database to store found incoming notes, // nullifiers, wallet zcash accounts and commitment trees. -class ZCashOrchardStorage { +class ZCashOrchardStorage : public base::RefCounted { public: + using WithCheckpointsCallback = + base::RepeatingCallback; + struct AccountMeta { uint32_t account_birthday = 0; uint32_t latest_scanned_block_id = 0; @@ -38,7 +41,8 @@ class ZCashOrchardStorage { kDbInitError, kAccountNotFound, kFailedToExecuteStatement, - kInternalError + kInternalError, + kNoCheckpoints }; struct Error { @@ -47,7 +51,6 @@ class ZCashOrchardStorage { }; explicit ZCashOrchardStorage(base::FilePath path_to_database); - ~ZCashOrchardStorage(); base::expected RegisterAccount( mojom::AccountIdPtr account_id, @@ -79,7 +82,64 @@ class ZCashOrchardStorage { const std::string& latest_scanned_block_hash); void ResetDatabase(); + // Shard tree + base::expected GetCap(mojom::AccountIdPtr account_id); + base::expected PutCap(mojom::AccountIdPtr account_id, + OrchardCap cap); + + base::expected TruncateShards(mojom::AccountIdPtr account_id, + uint32_t shard_index); + base::expected GetLatestShardIndex( + mojom::AccountIdPtr account_id); + base::expected PutShard(mojom::AccountIdPtr account_id, + OrchardShard shard); + base::expected, Error> GetShard( + mojom::AccountIdPtr account_id, + OrchardShardAddress address); + base::expected, Error> LastShard( + mojom::AccountIdPtr account_id, + uint8_t shard_height); + + base::expected CheckpointCount( + mojom::AccountIdPtr account_id); + base::expected, Error> MinCheckpointId( + mojom::AccountIdPtr account_id); + base::expected, Error> MaxCheckpointId( + mojom::AccountIdPtr account_id); + base::expected, Error> GetCheckpointAtDepth( + mojom::AccountIdPtr account_id, + uint32_t depth); + base::expected, Error> GetMaxCheckpointedHeight( + mojom::AccountIdPtr account_id, + uint32_t chain_tip_height, + uint32_t min_confirmations); + base::expected RemoveCheckpoint(mojom::AccountIdPtr account_id, + uint32_t checkpoint_id); + base::expected RemoveCheckpointAt(mojom::AccountIdPtr account_id, + uint32_t depth); + base::expected TruncateCheckpoints( + mojom::AccountIdPtr account_id, + uint32_t checkpoint_id); + base::expected AddCheckpoint(mojom::AccountIdPtr account_id, + uint32_t checkpoint_id, + OrchardCheckpoint checkpoint); + base::expected, Error> GetCheckpoints(mojom::AccountIdPtr account_id, size_t limit); + base::expected, Error> GetCheckpoint(mojom::AccountIdPtr account_id, uint32_t checkpoint_id); + + base::expected PutShardRoots( + mojom::AccountIdPtr account_id, + uint8_t shard_roots_height, + uint32_t start_position, + std::vector roots); + base::expected, Error> GetShardRoots( + mojom::AccountIdPtr account_id, + uint8_t shard_level); + private: + friend class base::RefCounted; + + ~ZCashOrchardStorage(); + bool EnsureDbInit(); bool CreateOrUpdateDatabase(); bool CreateSchema(); diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.cc b/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.cc new file mode 100644 index 000000000000..f5de72b54484 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.cc @@ -0,0 +1,267 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.h" + +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +namespace { + +OrchardShardTreeDelegate::Error From(ZCashOrchardStorage::Error) { + return OrchardShardTreeDelegate::Error::kStorageError; +} + +} // namespace + +class OrchardShardTreeDelegateImpl : public OrchardShardTreeDelegate { + public: + OrchardShardTreeDelegateImpl(mojom::AccountIdPtr account_id, + scoped_refptr storage) + : account_id_(std::move(account_id)), storage_(storage) {} + + ~OrchardShardTreeDelegateImpl() override {} + + base::expected, Error> GetCap() const override { + auto result = storage_->GetCap(account_id_.Clone()); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(*result); + } + + base::expected PutCap(OrchardCap cap) override { + auto result = storage_->PutCap(account_id_.Clone(), cap); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return *result; + } + + base::expected Truncate(uint32_t block_height) override { + auto result = storage_->TruncateShards(account_id_.Clone(), block_height); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return *result; + } + + base::expected, Error> GetLatestShardIndex() + const override { + auto result = storage_->GetLatestShardIndex(account_id_.Clone()); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(*result); + } + + base::expected PutShard(OrchardShard shard) override { + auto result = storage_->PutShard(account_id_.Clone(), shard); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return *result; + } + + base::expected, Error> GetShard( + OrchardShardAddress address) const override { + auto result = storage_->GetShard(account_id_.Clone(), address); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(*result); + } + + base::expected, Error> LastShard( + uint8_t shard_height) const override { + auto result = storage_->LastShard(account_id_.Clone(), shard_height); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(*result); + } + + base::expected CheckpointCount() const override { + auto result = storage_->CheckpointCount(account_id_.Clone()); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return *result; + } + + base::expected, Error> MinCheckpointId() + const override { + auto result = storage_->MinCheckpointId(account_id_.Clone()); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected, Error> MaxCheckpointId() + const override { + auto result = storage_->MaxCheckpointId(account_id_.Clone()); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected, Error> GetCheckpointAtDepth( + uint32_t depth) const override { + auto result = storage_->GetCheckpointAtDepth(account_id_.Clone(), depth); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected, Error> GetCheckpoint(uint32_t checkpoint_id) + const override { + auto result = storage_->GetCheckpoint(account_id_.Clone(), checkpoint_id); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected, Error> GetCheckpoints(size_t limit) + const override { + auto result = storage_->GetCheckpoints(account_id_.Clone(), limit); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected TruncateCheckpoints(uint32_t checkpoint_id) override { + auto result = storage_->TruncateCheckpoints(account_id_.Clone(), checkpoint_id); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected RemoveCheckpoint( + uint32_t checkpoint_id) override { + auto result = storage_->RemoveCheckpoint(account_id_.Clone(), checkpoint_id); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected RemoveCheckpointAt(uint32_t depth) override { + auto result = storage_->RemoveCheckpointAt(account_id_.Clone(), depth); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected PutShardRoots( + uint8_t shard_roots_height, + uint32_t start_position, + std::vector roots) override { + auto result = storage_->PutShardRoots( + account_id_.Clone(), shard_roots_height, + start_position, std::move(roots)); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + base::expected, Error> GetShardRoots( + uint8_t shard_level) const override { + auto result = storage_->GetShardRoots( + account_id_.Clone(), shard_level); + if (!result.has_value()) { + return base::unexpected(From(result.error())); + } + return std::move(result.value()); + } + + private: + mojom::AccountIdPtr account_id_; + scoped_refptr storage_; +}; + +ZCashOrchardSyncState::ZCashOrchardSyncState(base::FilePath path_to_database) { + storage_ = base::MakeRefCounted(path_to_database); +} + +ZCashOrchardSyncState::~ZCashOrchardSyncState() {} + +OrchardShardTreeManager* ZCashOrchardSyncState::GetOrCreateShardTreeManager(const mojom::AccountIdPtr& account_id) { + if (shard_tree_managers_.find(account_id) == shard_tree_managers_.end()) { + shard_tree_managers_[account_id.Clone()] = OrchardShardTreeManager::Create( + std::make_unique(account_id.Clone(), storage_)); + } + return shard_tree_managers_[account_id.Clone()].get(); +} + +base::expected +ZCashOrchardSyncState::RegisterAccount( + mojom::AccountIdPtr account_id, + uint64_t account_birthday_block, + const std::string& account_bithday_block_hash) { + return storage_->RegisterAccount(std::move(account_id), + account_birthday_block, + account_bithday_block_hash); +} + +base::expected +ZCashOrchardSyncState::GetAccountMeta(mojom::AccountIdPtr account_id) { + return storage_->GetAccountMeta(std::move(account_id)); +} + +std::optional +ZCashOrchardSyncState::HandleChainReorg(mojom::AccountIdPtr account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash) { + return storage_->HandleChainReorg(std::move(account_id), reorg_block_id, + reorg_block_hash); +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardSyncState::GetSpendableNotes(mojom::AccountIdPtr account_id) { + return storage_->GetSpendableNotes(std::move(account_id)); +} + +base::expected, ZCashOrchardStorage::Error> +ZCashOrchardSyncState::GetNullifiers(mojom::AccountIdPtr account_id) { + return storage_->GetNullifiers(std::move(account_id)); +} + +base::expected +ZCashOrchardSyncState::GetLatestShardIndex(mojom::AccountIdPtr account_id) { + return storage_->GetLatestShardIndex(std::move(account_id)); +} + +std::optional +ZCashOrchardSyncState::UpdateSubtreeRoots( + mojom::AccountIdPtr account_id, + std::vector roots) { + return std::nullopt; +} + +std::optional ZCashOrchardSyncState::UpdateNotes( + mojom::AccountIdPtr account_id, + const std::vector& notes_to_add, + const std::vector& notes_to_delete, + const uint32_t latest_scanned_block, + const std::string& latest_scanned_block_hash) { + return storage_->UpdateNotes(std::move(account_id), notes_to_add, + notes_to_delete, latest_scanned_block, + latest_scanned_block_hash); +} + +void ZCashOrchardSyncState::ResetDatabase() { + storage_->ResetDatabase(); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.h b/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.h new file mode 100644 index 000000000000..fa0a78525988 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_orchard_sync_state.h @@ -0,0 +1,63 @@ +/* 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 + +#include "brave/components/brave_wallet/browser/internal/orchard_shard_tree_manager.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_orchard_storage.h" +#include "brave/components/brave_wallet/common/zcash_utils.h" + +namespace brave_wallet { + +class ZCashOrchardSyncState { + public: + ZCashOrchardSyncState(base::FilePath path_to_database); + ~ZCashOrchardSyncState(); + + base::expected RegisterAccount( + mojom::AccountIdPtr account_id, + uint64_t account_birthday_block, + const std::string& account_bithday_block_hash); + + base::expected + GetAccountMeta(mojom::AccountIdPtr account_id); + + std::optional HandleChainReorg( + mojom::AccountIdPtr account_id, + uint32_t reorg_block_id, + const std::string& reorg_block_hash); + + base::expected, ZCashOrchardStorage::Error> + GetSpendableNotes(mojom::AccountIdPtr account_id); + + base::expected, ZCashOrchardStorage::Error> + GetNullifiers(mojom::AccountIdPtr account_id); + + std::optional UpdateNotes( + mojom::AccountIdPtr account_id, + const std::vector& notes_to_add, + const std::vector& notes_to_delete, + const uint32_t latest_scanned_block, + const std::string& latest_scanned_block_hash); + + void ResetDatabase(); + + base::expected GetLatestShardIndex( + mojom::AccountIdPtr account_id); + std::optional UpdateSubtreeRoots( + mojom::AccountIdPtr account_id, + std::vector roots); + void AppendLeafs(mojom::AccountIdPtr account_id, + std::vector commitments); + + private: + + OrchardShardTreeManager* GetOrCreateShardTreeManager(const mojom::AccountIdPtr& account_id); + + scoped_refptr storage_; + std::map> shard_tree_managers_; +}; + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.cc b/components/brave_wallet/browser/zcash/zcash_rpc.cc index 34c512a745b8..449ad1b6ed80 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.cc +++ b/components/brave_wallet/browser/zcash/zcash_rpc.cc @@ -259,6 +259,23 @@ const GURL MakeGetCompactBlocksURL(const GURL& base_url) { return base_url.ReplaceComponents(replacements); } +const GURL MakeGetSubtreeRootsURL(const GURL& base_url) { + if (!base_url.is_valid()) { + return GURL(); + } + if (!UrlPathEndsWithSlash(base_url)) { + return GURL(); + } + + GURL::Replacements replacements; + std::string path = + base::StrCat({base_url.path(), + "cash.z.wallet.sdk.rpc.CompactTxStreamer/GetSubtreeRoots"}); + replacements.SetPathStr(path); + + return base_url.ReplaceComponents(replacements); +} + std::string MakeGetTreeStateURLParams( const zcash::mojom::BlockIDPtr& block_id) { ::zcash::BlockID request; @@ -336,6 +353,16 @@ std::string MakeGetCompactBlocksParams(uint32_t block_start, return GetPrefixedProtobuf(range.SerializeAsString()); } +std::string MakeGetSubtreeRootsParams(uint32_t start, uint32_t entries) { + ::zcash::GetSubtreeRootsArg arg; + + arg.set_startindex(start); + arg.set_maxentries(entries); + arg.set_shieldedprotocol(::zcash::ShieldedProtocol::orchard); + + return GetPrefixedProtobuf(arg.SerializeAsString()); +} + std::unique_ptr MakeGRPCLoader( const GURL& url, const std::string& body) { @@ -517,6 +544,36 @@ void ZCashRpc::GetCompactBlocks(const std::string& chain_id, (*it)->DownloadAsStream(url_loader_factory_.get(), handler_it->get()); } +void ZCashRpc::GetSubtreeRoots(const std::string& chain_id, + uint32_t start, + uint32_t entries, + GetSubtreeRootsCallback callback) { + GURL request_url = MakeGetSubtreeRootsURL(GetNetworkURL(chain_id)); + + if (!request_url.is_valid()) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + auto url_loader = + MakeGRPCLoader(request_url, MakeGetSubtreeRootsParams(start, entries)); + + UrlLoadersList::iterator it = url_loaders_list_.insert( + url_loaders_list_.begin(), std::move(url_loader)); + + StreamHandlersList::iterator handler_it = stream_handlers_list_.insert( + stream_handlers_list_.begin(), + std::make_unique()); + + static_cast(handler_it->get()) + ->set_callback(base::BindOnce(&ZCashRpc::OnGetSubtreeRootsResponse, + weak_ptr_factory_.GetWeakPtr(), + std::move(callback), it, handler_it)); + + (*it)->DownloadAsStream(url_loader_factory_.get(), handler_it->get()); +} + void ZCashRpc::OnGetCompactBlocksResponse( ZCashRpc::GetCompactBlocksCallback callback, UrlLoadersList::iterator it, @@ -537,6 +594,26 @@ void ZCashRpc::OnGetCompactBlocksResponse( weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } +void ZCashRpc::OnGetSubtreeRootsResponse( + ZCashRpc::GetSubtreeRootsCallback callback, + UrlLoadersList::iterator it, + StreamHandlersList::iterator handler_it, + base::expected, std::string> result) { + url_loaders_list_.erase(it); + stream_handlers_list_.erase(handler_it); + + if (!result.has_value()) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + GetDecoder()->ParseSubtreeRoots( + *result, + base::BindOnce(&ZCashRpc::OnParseSubtreeRoots, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + void ZCashRpc::OnGetUtxosResponse(ZCashRpc::GetUtxoListCallback callback, UrlLoadersList::iterator it, std::unique_ptr response_body) { @@ -571,6 +648,16 @@ void ZCashRpc::OnParseCompactBlocks( } } +void ZCashRpc::OnParseSubtreeRoots( + GetSubtreeRootsCallback callback, + std::optional> subtree_roots) { + if (subtree_roots) { + std::move(callback).Run(std::move(subtree_roots.value())); + } else { + std::move(callback).Run(base::unexpected("Cannot parse subtree roots")); + } +} + template void ZCashRpc::OnParseResult( base::OnceCallback)> callback, diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.h b/components/brave_wallet/browser/zcash/zcash_rpc.h index 3234c255ad00..f657932d5d53 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.h +++ b/components/brave_wallet/browser/zcash/zcash_rpc.h @@ -44,6 +44,8 @@ class ZCashRpc { base::expected)>; using GetCompactBlocksCallback = base::OnceCallback, std::string>)>; + using GetSubtreeRootsCallback = base::OnceCallback, std::string>)>; ZCashRpc(NetworkManager* network_manager, scoped_refptr url_loader_factory); @@ -81,6 +83,11 @@ class ZCashRpc { uint32_t to, GetCompactBlocksCallback callback); + virtual void GetSubtreeRoots(const std::string& chain_id, + uint32_t start, + uint32_t entries, + GetSubtreeRootsCallback); + private: friend class base::RefCountedThreadSafe; @@ -119,6 +126,12 @@ class ZCashRpc { StreamHandlersList::iterator handler_it, base::expected, std::string> result); + void OnGetSubtreeRootsResponse( + ZCashRpc::GetSubtreeRootsCallback callback, + UrlLoadersList::iterator it, + StreamHandlersList::iterator handler_it, + base::expected, std::string> result); + template void OnParseResult(base::OnceCallback)>, T value); @@ -127,6 +140,10 @@ class ZCashRpc { GetCompactBlocksCallback callback, std::optional> compact_blocks); + void OnParseSubtreeRoots( + GetSubtreeRootsCallback callback, + std::optional> subtree_roots); + mojo::AssociatedRemote& GetDecoder(); GURL GetNetworkURL(const std::string& chain_id); diff --git a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc index eaf114185ff5..9e65a1d6caeb 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.cc @@ -33,8 +33,10 @@ size_t GetCode(ZCashShieldSyncService::ErrorCode error) { return 5; case ZCashShieldSyncService::ErrorCode::kFailedToRetrieveAccount: return 6; - case ZCashShieldSyncService::ErrorCode::kScannerError: + case ZCashShieldSyncService::ErrorCode::kDatabaseError: return 7; + case ZCashShieldSyncService::ErrorCode::kScannerError: + return 8; } } @@ -75,7 +77,7 @@ ZCashShieldSyncService::ZCashShieldSyncService( observer_(std::move(observer)) { chain_id_ = GetNetworkForZCashKeyring(account_id->keyring_id); block_scanner_ = std::make_unique(fvk); - background_orchard_storage_.emplace( + sync_state_.emplace( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}), db_dir_path_); } @@ -133,6 +135,11 @@ void ZCashShieldSyncService::WorkOnTask() { return; } + if (!subtree_roots_updated_) { + UpdateSubtreeRoots(); + return; + } + if (!spendable_notes_) { UpdateSpendableNotes(); return; @@ -159,7 +166,7 @@ void ZCashShieldSyncService::WorkOnTask() { } void ZCashShieldSyncService::GetOrCreateAccount() { - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::GetAccountMeta) + sync_state_.AsyncCall(&ZCashOrchardSyncState::GetAccountMeta) .WithArgs(account_id_.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetAccountMeta, weak_ptr_factory_.GetWeakPtr())); @@ -189,7 +196,7 @@ void ZCashShieldSyncService::OnGetAccountMeta( } void ZCashShieldSyncService::InitAccount() { - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::RegisterAccount) + sync_state_.AsyncCall(&ZCashOrchardSyncState::RegisterAccount) .WithArgs(account_id_.Clone(), account_birthday_->value, account_birthday_->hash) .Then(base::BindOnce(&ZCashShieldSyncService::OnAccountInit, @@ -275,8 +282,7 @@ void ZCashShieldSyncService::OnGetTreeStateForChainReorg( return; } else { // Reorg database so records related to removed blocks are wiped out - background_orchard_storage_ - .AsyncCall(&ZCashOrchardStorage::HandleChainReorg) + sync_state_.AsyncCall(&ZCashOrchardSyncState::HandleChainReorg) .WithArgs(account_id_.Clone(), (*tree_state)->height, (*tree_state)->hash) .Then(base::BindOnce( @@ -299,7 +305,8 @@ void ZCashShieldSyncService::OnDatabaseUpdatedForChainReorg( } void ZCashShieldSyncService::UpdateSpendableNotes() { - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::GetSpendableNotes) + spendable_notes_ = std::nullopt; + sync_state_.AsyncCall(&ZCashOrchardSyncState::GetSpendableNotes) .WithArgs(account_id_.Clone()) .Then(base::BindOnce(&ZCashShieldSyncService::OnGetSpendableNotes, weak_ptr_factory_.GetWeakPtr())); @@ -336,6 +343,54 @@ void ZCashShieldSyncService::OnGetLatestBlock( ScheduleWorkOnTask(); } +void ZCashShieldSyncService::UpdateSubtreeRoots() { + sync_state_.AsyncCall(&ZCashOrchardSyncState::GetLatestShardIndex) + .WithArgs(account_id_.Clone()) + .Then(base::BindOnce(&ZCashShieldSyncService::OnGetLatestShardIndex, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashShieldSyncService::OnGetLatestShardIndex( + base::expected result) { + if (!result.has_value()) { + error_ = Error{ErrorCode::kDatabaseError, ""}; + ScheduleWorkOnTask(); + return; + } + + auto latest_shard_index = result.value(); + zcash_rpc_->GetSubtreeRoots( + chain_id_, latest_shard_index, 0, + base::BindOnce(&ZCashShieldSyncService::OnGetSubtreeRoots, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashShieldSyncService::OnGetSubtreeRoots( + base::expected, std::string> + result) { + if (!result.has_value()) { + error_ = Error{ErrorCode::kDatabaseError, ""}; + ScheduleWorkOnTask(); + return; + } + + sync_state_.AsyncCall(&ZCashOrchardSyncState::UpdateSubtreeRoots) + .WithArgs(account_id_.Clone(), std::move(result.value())) + .Then(base::BindOnce(&ZCashShieldSyncService::OnSubtreeRootsUpdated, + weak_ptr_factory_.GetWeakPtr())); +} + +void ZCashShieldSyncService::OnSubtreeRootsUpdated( + std::optional error) { + if (error) { + error_ = Error{ErrorCode::kDatabaseError, ""}; + } + + subtree_roots_updated_ = true; + + ScheduleWorkOnTask(); +} + void ZCashShieldSyncService::DownloadBlocks() { zcash_rpc_->GetCompactBlocks( chain_id_, *latest_scanned_block_ + 1, @@ -392,7 +447,7 @@ void ZCashShieldSyncService::UpdateNotes( const std::vector& notes_to_delete, uint32_t latest_scanned_block, std::string latest_scanned_block_hash) { - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::UpdateNotes) + sync_state_.AsyncCall(&ZCashOrchardSyncState::UpdateNotes) .WithArgs(account_id_.Clone(), found_notes, notes_to_delete, latest_scanned_block, latest_scanned_block_hash) .Then(base::BindOnce(&ZCashShieldSyncService::UpdateNotesComplete, @@ -422,7 +477,7 @@ uint32_t ZCashShieldSyncService::GetSpendableBalance() { } void ZCashShieldSyncService::Reset() { - background_orchard_storage_.AsyncCall(&ZCashOrchardStorage::ResetDatabase); + // sync_state_.AsyncCall(&ZCashOrchardStorage::ResetDatabase); } } // 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 1de6a8f3e271..c8a1e06f6d06 100644 --- a/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h +++ b/components/brave_wallet/browser/zcash/zcash_shield_sync_service.h @@ -15,7 +15,7 @@ #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/zcash/zcash_orchard_sync_state.h" #include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "mojo/public/cpp/bindings/remote.h" @@ -36,6 +36,7 @@ class ZCashShieldSyncService { kFailedToReceiveTreeState, kFailedToInitAccount, kFailedToRetrieveAccount, + kDatabaseError, kScannerError, }; @@ -110,6 +111,14 @@ class ZCashShieldSyncService { void OnGetLatestBlock( base::expected result); + void UpdateSubtreeRoots(); + void OnGetLatestShardIndex( + base::expected result); + void OnGetSubtreeRoots( + base::expected, std::string> + root); + void OnSubtreeRootsUpdated(std::optional error); + // Chain reorg flow // Chain reorg happens when latest blocks are removed from the blockchain // We assume that there is a limit of reorg depth - kChainReorgBlockDelta @@ -166,7 +175,7 @@ class ZCashShieldSyncService { base::WeakPtr observer_; std::string chain_id_; - base::SequenceBound background_orchard_storage_; + base::SequenceBound sync_state_; std::unique_ptr block_scanner_; std::optional account_meta_; @@ -175,6 +184,8 @@ class ZCashShieldSyncService { // Latest block in the blockchain std::optional chain_tip_block_; std::optional> downloaded_blocks_; + bool subtree_roots_updated_ = false; + // Local cache of spendable notes to fast check on discovered nullifiers std::optional> spendable_notes_; std::optional error_; diff --git a/components/brave_wallet/common/zcash_utils.cc b/components/brave_wallet/common/zcash_utils.cc index ac1e8406db1c..8f3a5a2b50ef 100644 --- a/components/brave_wallet/common/zcash_utils.cc +++ b/components/brave_wallet/common/zcash_utils.cc @@ -117,6 +117,49 @@ DecodedZCashAddress::DecodedZCashAddress(DecodedZCashAddress&& other) = default; DecodedZCashAddress& DecodedZCashAddress::operator=( DecodedZCashAddress&& other) = default; + +OrchardCheckpoint::OrchardCheckpoint(bool empty, uint32_t position, std::vector marks_removed) : + empty(empty), position(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( + OrchardShardAddress address, + std::array root_hash, + std::vector shard_data, + bool contains_marked) + : address(std::move(address)), + root_hash(std::move(root_hash)), + shard_data(std::move(shard_data)), + contains_marked(contains_marked) {} +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; + +OrchardCap::OrchardCap(std::vector data) + : data(std::move(data)) {} +OrchardCap::~OrchardCap() {} +OrchardCap::OrchardCap(const OrchardCap& other) = default; +OrchardCap& OrchardCap::operator=(const OrchardCap& other) = default; +OrchardCap::OrchardCap(OrchardCap&& other) = default; +OrchardCap& OrchardCap::operator=(OrchardCap&& 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 77e0e88ef94a..8347d81f41e1 100644 --- a/components/brave_wallet/common/zcash_utils.h +++ b/components/brave_wallet/common/zcash_utils.h @@ -12,6 +12,7 @@ #include #include "base/containers/span.h" +#include "base/types/expected.h" namespace brave_wallet { @@ -30,6 +31,9 @@ inline constexpr size_t kOrchardNullifierSize = 32u; inline constexpr size_t kOrchardCmxSize = 32u; inline constexpr size_t kOrchardEphemeralKeySize = 32u; inline constexpr size_t kOrchardCipherTextSize = 52u; +inline constexpr size_t kOrchardShardTreeHashSize = 32u; +inline constexpr uint8_t kOrchardShardSubtreeHeight = 8; +inline constexpr uint8_t kOrchardShardTreeHeight = 32; using OrchardFullViewKey = std::array; @@ -56,6 +60,8 @@ enum class OrchardAddressKind { Internal }; +enum class ShardTreeRetention { Ephemeral, Marked, Checkpoint }; + using ParsedAddress = std::pair>; struct DecodedZCashAddress { @@ -95,6 +101,120 @@ struct OrchardNote { bool operator==(const OrchardNote& other) const = default; }; +struct OrchardCheckpoint { + OrchardCheckpoint(bool, uint32_t, std::vector); + ~OrchardCheckpoint(); + OrchardCheckpoint(const OrchardCheckpoint& other); + OrchardCheckpoint& operator=(const OrchardCheckpoint& other); + OrchardCheckpoint(OrchardCheckpoint&& other); + OrchardCheckpoint& operator=(OrchardCheckpoint&& other); + + bool empty = false; + uint32_t position = 0; + 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); + + uint32_t checkpoint_id; + OrchardCheckpoint checkpoint; +}; + +struct OrchardShardAddress { + uint8_t level = 0; + uint32_t index = 0; +}; + +struct OrchardCap { + OrchardCap(std::vector data); + ~OrchardCap(); + + OrchardCap(const OrchardCap& other); + OrchardCap& operator=(const OrchardCap& other); + OrchardCap(OrchardCap&& other); + OrchardCap& operator=(OrchardCap&& other); + + std::array root_hash; + std::vector data; +}; + +struct OrchardShard { + OrchardShard(OrchardShardAddress, + std::array, + std::vector, + bool); + ~OrchardShard(); + + OrchardShard(const OrchardShard& other); + OrchardShard& operator=(const OrchardShard& other); + OrchardShard(OrchardShard&& other); + OrchardShard& operator=(OrchardShard&& other); + + OrchardShardAddress address; + std::array root_hash; + std::vector shard_data; + bool contains_marked = 0; +}; + +struct OrchardCommitment { + uint32_t block_id = 0; + uint32_t index = 0; + std::array cmu; + ShardTreeRetention retention; +}; + +class OrchardShardTreeDelegate { + public: + enum Error { kStorageError = 0, kConsistensyError = 1 }; + + virtual ~OrchardShardTreeDelegate() = 0; + + virtual base::expected, Error> GetCap() const = 0; + virtual base::expected PutCap(OrchardCap shard) = 0; + + virtual base::expected, Error> GetLatestShardIndex() + const = 0; + virtual base::expected PutShard(OrchardShard shard) = 0; + virtual base::expected, Error> GetShard( + OrchardShardAddress address) const = 0; + virtual base::expected, Error> LastShard( + uint8_t shard_height) const = 0; + virtual base::expected Truncate(uint32_t block_height) = 0; + + virtual base::expected TruncateCheckpoints( + uint32_t checkpoint_id) = 0; + virtual base::expected CheckpointCount() const = 0; + virtual base::expected, Error> MinCheckpointId() + const = 0; + virtual base::expected, Error> MaxCheckpointId() + const = 0; + virtual base::expected, Error> GetCheckpointAtDepth( + uint32_t depth) const = 0; + virtual base::expected, Error> GetCheckpoint( + uint32_t checkpoint_id) const = 0; + virtual base::expected, Error> GetCheckpoints(size_t limit) + const = 0; + virtual base::expected RemoveCheckpointAt(uint32_t depth) = 0; + virtual base::expected RemoveCheckpoint( + uint32_t checkpoint_id) = 0; + virtual base::expected AddCheckpoint(uint32_t id, OrchardCheckpoint checkpoint); + virtual base::expected UpdateCheckpoint(uint32_t id, OrchardCheckpoint checkpoint); + + + virtual base::expected PutShardRoots( + uint8_t shard_roots_height, + uint32_t start_position, + std::vector roots) = 0; + virtual base::expected, Error> GetShardRoots( + uint8_t shard_level) const = 0; +}; + bool OutputZCashAddressSupported(const std::string& address, bool is_testnet); // https://zips.z.cash/zip-0317 uint64_t CalculateZCashTxFee(const uint32_t tx_input_count, diff --git a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom index df368e94e250..0b0f996bc981 100644 --- a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom +++ b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom @@ -71,6 +71,12 @@ struct CompactBlock { ChainMetadata chain_metadata; }; +struct SubtreeRoot { + array root_hash; + array complete_block_hash; + uint32 complete_block_height; +}; + interface ZCashDecoder { ParseBlockID(string data) => (BlockID? value); ParseGetAddressUtxos(string data) => (GetAddressUtxosResponse? value); @@ -78,5 +84,6 @@ interface ZCashDecoder { ParseRawTransaction(string data) => (RawTransaction? tx); ParseTreeState(string data) => (TreeState? tree_state); ParseCompactBlocks(array data) => (array? compact_blocks); + ParseSubtreeRoots(array data) => (array? subtree_roots); }; diff --git a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto index 89ae1120db39..73657e8b68bb 100644 --- a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto +++ b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto @@ -110,3 +110,19 @@ message CompactBlock { repeated CompactTx vtx = 7; ChainMetadata chainMetadata = 8; } + +enum ShieldedProtocol { + sapling = 0; + orchard = 1; +} + +message GetSubtreeRootsArg { + uint32 startIndex = 1; + ShieldedProtocol shieldedProtocol = 2; + uint32 maxEntries = 3; +} +message SubtreeRoot { + bytes rootHash = 2; + bytes completingBlockHash = 3; + uint64 completingBlockHeight = 4; +} diff --git a/components/services/brave_wallet/zcash/zcash_decoder.cc b/components/services/brave_wallet/zcash/zcash_decoder.cc index 73dd5d220a38..c9d4270d5121 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.cc +++ b/components/services/brave_wallet/zcash/zcash_decoder.cc @@ -138,4 +138,19 @@ void ZCashDecoder::ParseCompactBlocks(const std::vector& data, std::move(callback).Run(std::move(parsed_blocks)); } +void ZCashDecoder::ParseSubtreeRoots(const std::vector& data, + ParseSubtreeRootsCallback callback) { + std::vector roots; + for (const auto& data_block : data) { + ::zcash::SubtreeRoot result; + auto serialized_message = ResolveSerializedMessage(data_block); + if (!serialized_message || + !result.ParseFromString(serialized_message.value()) || + serialized_message->empty()) { + std::move(callback).Run(std::nullopt); + return; + } + } +} + } // namespace brave_wallet diff --git a/components/services/brave_wallet/zcash/zcash_decoder.h b/components/services/brave_wallet/zcash/zcash_decoder.h index b7136ccb0e50..cfba78838283 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.h +++ b/components/services/brave_wallet/zcash/zcash_decoder.h @@ -36,6 +36,8 @@ class ZCashDecoder : public zcash::mojom::ZCashDecoder { ParseTreeStateCallback callback) override; void ParseCompactBlocks(const std::vector& data, ParseCompactBlocksCallback callback) override; + void ParseSubtreeRoots(const std::vector& data, + ParseSubtreeRootsCallback callback) override; }; } // namespace brave_wallet