diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 68898aa560..458e9114f3 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -101,6 +101,9 @@ uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # We don't need the default web3 features at all since we added our own web3 transport using shared HYPER instance. web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } zbase32 = "0.1.2" +zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } +zcash_primitives = { features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } +zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } [target.'cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))'.dependencies] bincode = { version = "1.3.3", default-features = false, optional = true } @@ -140,10 +143,7 @@ tokio = { version = "1.20" } tokio-rustls = { version = "0.23" } tonic = { version = "0.7", features = ["tls", "tls-webpki-roots", "compression"] } webpki-roots = { version = "0.22" } -zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } -zcash_primitives = { features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } -zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.3.0" } [target.'cfg(windows)'.dependencies] winapi = "0.3" diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index b45c2ba617..39d9477ff2 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -81,6 +81,7 @@ use std::sync::atomic::Ordering as AtomicOrdering; use std::sync::Arc; use std::time::Duration; use utxo_signer::with_key_pair::UtxoSignWithKeyPairError; +use zcash_primitives::transaction::Transaction as ZTransaction; cfg_native! { use crate::lightning::LightningCoin; @@ -91,8 +92,6 @@ cfg_native! { use lightning_invoice::{Invoice, ParseOrSemanticError}; use std::io; use std::path::PathBuf; - use zcash_primitives::transaction::Transaction as ZTransaction; - use z_coin::ZcoinProtocolInfo; } cfg_wasm32! { @@ -287,8 +286,8 @@ use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; pub mod nft; use nft::nft_errors::GetNftInfoError; -#[cfg(not(target_arch = "wasm32"))] pub mod z_coin; -#[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; +pub mod z_coin; +use z_coin::{ZCoin, ZcoinProtocolInfo}; pub type TransactionFut = Box + Send>; pub type BalanceResult = Result>; @@ -467,7 +466,6 @@ pub trait Transaction: fmt::Debug + 'static { pub enum TransactionEnum { UtxoTx(UtxoTx), SignedEthTx(SignedEthTx), - #[cfg(not(target_arch = "wasm32"))] ZTransaction(ZTransaction), CosmosTransaction(CosmosTransaction), #[cfg(not(target_arch = "wasm32"))] @@ -476,7 +474,6 @@ pub enum TransactionEnum { ifrom!(TransactionEnum, UtxoTx); ifrom!(TransactionEnum, SignedEthTx); -#[cfg(not(target_arch = "wasm32"))] ifrom!(TransactionEnum, ZTransaction); #[cfg(not(target_arch = "wasm32"))] ifrom!(TransactionEnum, LightningPayment); @@ -496,7 +493,6 @@ impl Deref for TransactionEnum { match self { TransactionEnum::UtxoTx(ref t) => t, TransactionEnum::SignedEthTx(ref t) => t, - #[cfg(not(target_arch = "wasm32"))] TransactionEnum::ZTransaction(ref t) => t, TransactionEnum::CosmosTransaction(ref t) => t, #[cfg(not(target_arch = "wasm32"))] @@ -2341,7 +2337,6 @@ pub enum MmCoinEnum { QtumCoin(QtumCoin), Qrc20Coin(Qrc20Coin), EthCoin(EthCoin), - #[cfg(not(target_arch = "wasm32"))] ZCoin(ZCoin), Bch(BchCoin), SlpToken(SlpToken), @@ -2427,7 +2422,6 @@ impl From for MmCoinEnum { fn from(c: LightningCoin) -> MmCoinEnum { MmCoinEnum::LightningCoin(c) } } -#[cfg(not(target_arch = "wasm32"))] impl From for MmCoinEnum { fn from(c: ZCoin) -> MmCoinEnum { MmCoinEnum::ZCoin(c) } } @@ -2447,7 +2441,6 @@ impl Deref for MmCoinEnum { MmCoinEnum::TendermintToken(ref c) => c, #[cfg(not(target_arch = "wasm32"))] MmCoinEnum::LightningCoin(ref c) => c, - #[cfg(not(target_arch = "wasm32"))] MmCoinEnum::ZCoin(ref c) => c, MmCoinEnum::Test(ref c) => c, #[cfg(all( @@ -2840,7 +2833,6 @@ pub enum CoinProtocol { token_contract_address: String, decimals: u8, }, - #[cfg(not(target_arch = "wasm32"))] ZHTLC(ZcoinProtocolInfo), } @@ -3093,7 +3085,6 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("TENDERMINT protocol is not supported by lp_coininit"), CoinProtocol::TENDERMINTTOKEN(_) => return ERR!("TENDERMINTTOKEN protocol is not supported by lp_coininit"), - #[cfg(not(target_arch = "wasm32"))] CoinProtocol::ZHTLC { .. } => return ERR!("ZHTLC protocol is not supported by lp_coininit"), #[cfg(not(target_arch = "wasm32"))] CoinProtocol::LIGHTNING { .. } => return ERR!("Lightning protocol is not supported by lp_coininit"), @@ -3686,7 +3677,6 @@ pub fn address_by_coin_conf_and_pubkey_str( CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { ERR!("Solana pubkey is the public address - you do not need to use this rpc call.") }, - #[cfg(not(target_arch = "wasm32"))] CoinProtocol::ZHTLC { .. } => ERR!("address_by_coin_conf_and_pubkey_str is not supported for ZHTLC protocol!"), } } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 714856c826..4e3f6f2ecb 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1,43 +1,41 @@ use crate::coin_errors::MyAddressError; +#[cfg(not(target_arch = "wasm32"))] use crate::my_tx_history_v2::{MyTxHistoryErrorV2, MyTxHistoryRequestV2, MyTxHistoryResponseV2}; +#[cfg(not(target_arch = "wasm32"))] use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawInProgressStatus, WithdrawTaskHandle}; use crate::utxo::rpc_clients::{ElectrumRpcRequest, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, UtxoRpcResult}; -use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, - UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, - UtxoFieldsWithIguanaSecretBuilder}; -use crate::utxo::utxo_common::{addresses_from_script, big_decimal_from_sat, big_decimal_from_sat_unsigned, - payment_script}; +use crate::utxo::utxo_builder::UtxoCoinBuildError; +use crate::utxo::utxo_builder::{UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, + UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; +use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, payment_script}; use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, BroadcastTxErr, FeePolicy, GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, - UtxoCommonOps, UtxoFeeDetails, UtxoRpcMode, UtxoTxBroadcastOps, UtxoTxGenerationOps, - VerboseTransactionFrom}; + UtxoCommonOps, UtxoRpcMode, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, NumConversError, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, - RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionFut, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionEnum, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, + ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; use chain::constants::SEQUENCE_FINAL; use chain::{Transaction as UtxoTx, TransactionOutput}; use common::executor::{AbortableSystem, AbortedError}; -use common::{async_blocking, calc_total_pages, log, one_thousand_u32, sha256_digest, PagingOptionsEnum}; +use common::sha256_digest; +use common::{log, one_thousand_u32}; use crypto::privkey::{key_pair_from_secret, secp_privkey_from_hash}; -use crypto::{Bip32DerPathOps, GlobalHDAccountArc, StandardHDPathToCoin}; -use db_common::sqlite::offset_by_id; -use db_common::sqlite::rusqlite::{Error as SqlError, Row}; -use db_common::sqlite::sql_builder::{name, SqlBuilder, SqlName}; +use crypto::StandardHDPathToCoin; +use crypto::{Bip32DerPathOps, GlobalHDAccountArc}; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; @@ -48,7 +46,6 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; -use parking_lot::Mutex; use primitives::bytes::Bytes; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::{Builder as ScriptBuilder, Opcode, Script, TransactionInputSigner}; @@ -58,37 +55,54 @@ use std::collections::{HashMap, HashSet}; use std::iter; use std::path::PathBuf; use std::sync::Arc; -use zcash_client_backend::data_api::WalletRead; +#[cfg(target_arch = "wasm32")] +use z_coin_errors::ZCoinBalanceError; +use z_rpc::{SaplingSyncConnector, SaplingSyncGuard}; use zcash_client_backend::encoding::{decode_payment_address, encode_extended_spending_key, encode_payment_address}; -use zcash_client_backend::wallet::{AccountId, SpendableNote}; -use zcash_client_sqlite::error::SqliteClientError as ZcashClientError; -use zcash_client_sqlite::error::SqliteClientError; -use zcash_client_sqlite::wallet::get_balance; -use zcash_client_sqlite::wallet::transact::get_spendable_notes; +use zcash_client_backend::wallet::SpendableNote; use zcash_primitives::consensus::{BlockHeight, NetworkUpgrade, Parameters, H0}; use zcash_primitives::memo::MemoBytes; use zcash_primitives::sapling::keys::OutgoingViewingKey; use zcash_primitives::sapling::note_encryption::try_sapling_output_recovery; -use zcash_primitives::transaction::builder::Builder as ZTxBuilder; use zcash_primitives::transaction::components::{Amount, TxOut}; use zcash_primitives::transaction::Transaction as ZTransaction; use zcash_primitives::zip32::ChildIndex as Zip32Child; -use zcash_primitives::{consensus, constants::mainnet as z_mainnet_constants, sapling::PaymentAddress, +use zcash_primitives::{constants::mainnet as z_mainnet_constants, sapling::PaymentAddress, zip32::ExtendedFullViewingKey, zip32::ExtendedSpendingKey}; -use zcash_proofs::default_params_folder; use zcash_proofs::prover::LocalTxProver; mod z_htlc; use z_htlc::{z_p2sh_spend, z_send_dex_fee, z_send_htlc}; mod z_rpc; +use z_rpc::init_light_client; pub use z_rpc::SyncStatus; -use z_rpc::{init_light_client, init_native_client, SaplingSyncConnector, SaplingSyncGuard, WalletDbShared}; -mod z_coin_errors; -use crate::z_coin::z_rpc::{create_wallet_db, BlockDb}; +cfg_native!( + use crate::{NumConversError, TransactionDetails, TxFeeDetails}; + use crate::utxo::UtxoFeeDetails; + use crate::utxo::utxo_common::{addresses_from_script, big_decimal_from_sat}; + + use common::{async_blocking, calc_total_pages, PagingOptionsEnum}; + use db_common::sqlite::offset_by_id; + use db_common::sqlite::rusqlite::{Error as SqlError, Row}; + use db_common::sqlite::sql_builder::{name, SqlBuilder, SqlName}; + use zcash_client_backend::data_api::WalletRead; + use zcash_client_backend::wallet::{AccountId}; + use zcash_client_sqlite::error::SqliteClientError as ZcashClientError; + use zcash_client_sqlite::wallet::{get_balance}; + use zcash_client_sqlite::wallet::transact::get_spendable_notes; + use zcash_primitives::consensus; + use zcash_primitives::transaction::builder::Builder as ZTxBuilder; + use zcash_proofs::default_params_folder; + use z_rpc::{init_native_client}; +); + +#[allow(unused)] mod z_coin_errors; +use crate::z_coin::storage::{BlockDbImpl, WalletDbShared}; pub use z_coin_errors::*; +pub mod storage; #[cfg(all(test, feature = "zhtlc-native-tests"))] mod z_coin_native_tests; @@ -113,12 +127,14 @@ macro_rules! try_ztx_s { const DEX_FEE_OVK: OutgoingViewingKey = OutgoingViewingKey([7; 32]); const DEX_FEE_Z_ADDR: &str = "zs1rp6426e9r6jkq2nsanl66tkd34enewrmr0uvj0zelhkcwmsy0uvxz2fhm9eu9rl3ukxvgzy2v9f"; -const TRANSACTIONS_TABLE: &str = "transactions"; -const BLOCKS_TABLE: &str = "blocks"; const SAPLING_SPEND_NAME: &str = "sapling-spend.params"; const SAPLING_OUTPUT_NAME: &str = "sapling-output.params"; const SAPLING_SPEND_EXPECTED_HASH: &str = "8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"; const SAPLING_OUTPUT_EXPECTED_HASH: &str = "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"; +cfg_native!( + const BLOCKS_TABLE: &str = "blocks"; + const TRANSACTIONS_TABLE: &str = "transactions"; +); #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ZcoinConsensusParams { @@ -177,6 +193,7 @@ impl Parameters for ZcoinConsensusParams { fn b58_script_address_prefix(&self) -> [u8; 2] { self.b58_script_address_prefix } } +#[allow(unused)] pub struct ZCoinFields { dex_fee_addr: PaymentAddress, my_z_addr: PaymentAddress, @@ -216,6 +233,7 @@ pub struct ZOutput { pub memo: Option, } +#[cfg(not(target_arch = "wasm32"))] struct ZCoinSqlTxHistoryItem { tx_hash: Vec, internal_id: i64, @@ -225,6 +243,7 @@ struct ZCoinSqlTxHistoryItem { spent_amount: i64, } +#[cfg(not(target_arch = "wasm32"))] impl ZCoinSqlTxHistoryItem { fn try_from_sql_row(row: &Row<'_>) -> Result { let mut tx_hash: Vec = row.get(0)?; @@ -240,6 +259,7 @@ impl ZCoinSqlTxHistoryItem { } } +#[cfg(not(target_arch = "wasm32"))] struct SqlTxHistoryRes { transactions: Vec, total_tx_count: u32, @@ -317,30 +337,44 @@ impl ZCoin { }) } + #[cfg(not(target_arch = "wasm32"))] async fn my_balance_sat(&self) -> Result> { - let db = self.z_fields.light_wallet_db.clone(); + let wallet_db = self.z_fields.light_wallet_db.clone(); async_blocking(move || { - let balance = get_balance(&db.lock(), AccountId::default())?.into(); + let balance = get_balance(&wallet_db.db.lock(), AccountId::default())?.into(); Ok(balance) }) .await } - async fn get_spendable_notes(&self) -> Result, MmError> { - let db = self.z_fields.light_wallet_db.clone(); + #[cfg(target_arch = "wasm32")] + async fn my_balance_sat(&self) -> Result> { todo!() } + + #[cfg(not(target_arch = "wasm32"))] + async fn get_spendable_notes(&self) -> Result, MmError> { + let wallet_db = self.z_fields.light_wallet_db.clone(); async_blocking(move || { - let guard = db.lock(); - let latest_db_block = match guard.block_height_extrema()? { + let guard = wallet_db.db.lock(); + let latest_db_block = match guard + .block_height_extrema() + .map_err(|err| SpendableNotesError::DBClientError(err.to_string()))? + { Some((_, latest)) => latest, None => return Ok(Vec::new()), }; - get_spendable_notes(&guard, AccountId::default(), latest_db_block).map_err(MmError::new) + get_spendable_notes(&guard, AccountId::default(), latest_db_block) + .map_err(|err| MmError::new(SpendableNotesError::DBClientError(err.to_string()))) }) .await } + #[cfg(target_arch = "wasm32")] + #[allow(unused)] + async fn get_spendable_notes(&self) -> Result, MmError> { todo!() } + /// Returns spendable notes - async fn spendable_notes_ordered(&self) -> Result, MmError> { + #[allow(unused)] + async fn spendable_notes_ordered(&self) -> Result, MmError> { let mut unspents = self.get_spendable_notes().await?; unspents.sort_unstable_by(|a, b| a.note_value.cmp(&b.note_value)); @@ -357,6 +391,7 @@ impl ZCoin { } /// Generates a tx sending outputs from our address + #[cfg(not(target_arch = "wasm32"))] async fn gen_tx( &self, t_outputs: Vec, @@ -371,7 +406,10 @@ impl ZCoin { let total_output = big_decimal_from_sat_unsigned(total_output_sat, self.utxo_arc.decimals); let total_required = &total_output + &tx_fee; - let spendable_notes = self.spendable_notes_ordered().await?; + let spendable_notes = self + .spendable_notes_ordered() + .await + .map_err(|err| GenTxError::SpendableNotesError(err.to_string()))?; let mut total_input_amount = BigDecimal::from(0); let mut change = BigDecimal::from(0); @@ -456,6 +494,15 @@ impl ZCoin { Ok((tx, additional_data, sync_guard)) } + #[cfg(target_arch = "wasm32")] + async fn gen_tx( + &self, + _t_outputs: Vec, + _z_outputs: Vec, + ) -> Result<(ZTransaction, AdditionalTxData, SaplingSyncGuard<'_>), MmError> { + todo!() + } + pub async fn send_outputs( &self, t_outputs: Vec, @@ -474,6 +521,7 @@ impl ZCoin { Ok(tx) } + #[cfg(not(target_arch = "wasm32"))] async fn tx_history_from_sql( &self, limit: usize, @@ -481,7 +529,7 @@ impl ZCoin { ) -> Result> { let wallet_db = self.z_fields.light_wallet_db.clone(); async_blocking(move || { - let db_guard = wallet_db.lock(); + let db_guard = wallet_db.db.lock(); let conn = db_guard.sql_conn(); let total_sql = SqlBuilder::select_from(TRANSACTIONS_TABLE) @@ -538,6 +586,7 @@ impl ZCoin { .await } + #[cfg(not(target_arch = "wasm32"))] async fn z_transactions_from_cache_or_rpc( &self, hashes: HashSet, @@ -553,6 +602,7 @@ impl ZCoin { .map_to_mm(|e| UtxoRpcError::InvalidResponse(e.to_string())) } + #[cfg(not(target_arch = "wasm32"))] fn tx_details_from_sql_item( &self, sql_item: ZCoinSqlTxHistoryItem, @@ -648,6 +698,7 @@ impl ZCoin { }) } + #[cfg(not(target_arch = "wasm32"))] pub async fn tx_history( &self, request: MyTxHistoryRequestV2, @@ -707,6 +758,7 @@ impl AsRef for ZCoin { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "rpc", content = "rpc_data")] pub enum ZcoinRpcMode { + #[cfg(not(target_arch = "wasm32"))] Native, Light { electrum_servers: Vec, @@ -726,6 +778,7 @@ pub struct ZcoinActivationParams { pub scan_interval_ms: u64, } +#[cfg(not(target_arch = "wasm32"))] pub async fn z_coin_from_conf_and_params( ctx: &MmArc, ticker: &str, @@ -749,12 +802,14 @@ pub async fn z_coin_from_conf_and_params( builder.build().await } +#[allow(unused)] fn verify_checksum_zcash_params(spend_path: &PathBuf, output_path: &PathBuf) -> Result { let spend_hash = sha256_digest(spend_path)?; let out_hash = sha256_digest(output_path)?; Ok(spend_hash == SAPLING_SPEND_EXPECTED_HASH && out_hash == SAPLING_OUTPUT_EXPECTED_HASH) } +#[allow(unused)] fn get_spend_output_paths(params_dir: PathBuf) -> Result<(PathBuf, PathBuf), ZCoinBuildError> { if !params_dir.exists() { return Err(ZCoinBuildError::ZCashParamsNotFound); @@ -826,42 +881,19 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { .expect("DEX_FEE_Z_ADDR is a valid z-address") .expect("DEX_FEE_Z_ADDR is a valid z-address"); - let params_dir = match &self.z_coin_params.zcash_params_path { - None => default_params_folder().or_mm_err(|| ZCoinBuildError::ZCashParamsNotFound)?, - Some(file_path) => PathBuf::from(file_path), - }; - - let z_tx_prover = async_blocking(move || { - let (spend_path, output_path) = get_spend_output_paths(params_dir)?; - let verification_successful = verify_checksum_zcash_params(&spend_path, &output_path)?; - if verification_successful { - Ok(LocalTxProver::new(&spend_path, &output_path)) - } else { - MmError::err(ZCoinBuildError::SaplingParamsInvalidChecksum) - } - }) - .await?; - + let z_tx_prover = self.z_tx_prover().await?; let my_z_addr_encoded = encode_payment_address( self.protocol_info.consensus_params.hrp_sapling_payment_address(), &my_z_addr, ); - let evk = ExtendedFullViewingKey::from(&z_spending_key); - let cache_db_path = self.db_dir_path.join(format!("{}_cache.db", self.ticker)); - let wallet_db_path = self.db_dir_path.join(format!("{}_wallet.db", self.ticker)); - let blocks_db = - async_blocking(|| BlockDb::for_path(cache_db_path).map_to_mm(ZcoinClientInitError::BlocksDbInitFailure)) - .await?; - let wallet_db = create_wallet_db( - wallet_db_path, - self.protocol_info.consensus_params.clone(), - self.protocol_info.check_point_block.clone(), - evk, - ) - .await?; - let wallet_db = Arc::new(Mutex::new(wallet_db)); + let blocks_db = self.blocks_db().await?; + let wallet_db = WalletDbShared::new(&self) + .await + .map_err(|err| ZCoinBuildError::ZcashDBError(err.to_string()))?; + let (sync_state_connector, light_wallet_db) = match &self.z_coin_params.mode { + #[cfg(not(target_arch = "wasm32"))] ZcoinRpcMode::Native => { let native_client = self.native_client()?; init_native_client( @@ -890,7 +922,6 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { .await? }, }; - let z_fields = ZCoinFields { dex_fee_addr, my_z_addr, @@ -925,6 +956,7 @@ impl<'a> ZCoinBuilder<'a> { protocol_info: ZcoinProtocolInfo, ) -> ZCoinBuilder<'a> { let utxo_mode = match &z_coin_params.mode { + #[cfg(not(target_arch = "wasm32"))] ZcoinRpcMode::Native => UtxoRpcMode::Native, ZcoinRpcMode::Light { electrum_servers, .. } => UtxoRpcMode::Electrum { servers: electrum_servers.clone(), @@ -954,6 +986,37 @@ impl<'a> ZCoinBuilder<'a> { protocol_info, } } + + async fn blocks_db(&self) -> Result> { + let cache_db_path = self.db_dir_path.join(format!("{}_cache.db", self.ticker)); + let ctx = self.ctx.clone(); + let ticker = self.ticker.to_string(); + BlockDbImpl::new(ctx, ticker, cache_db_path) + .map_err(|err| MmError::new(ZcoinClientInitError::ZcashDBError(err.to_string()))) + .await + } + + #[cfg(not(target_arch = "wasm32"))] + async fn z_tx_prover(&self) -> Result> { + let params_dir = match &self.z_coin_params.zcash_params_path { + None => default_params_folder().or_mm_err(|| ZCoinBuildError::ZCashParamsNotFound)?, + Some(file_path) => PathBuf::from(file_path), + }; + + async_blocking(move || { + let (spend_path, output_path) = get_spend_output_paths(params_dir)?; + let verification_successful = verify_checksum_zcash_params(&spend_path, &output_path)?; + if verification_successful { + Ok(LocalTxProver::new(&spend_path, &output_path)) + } else { + MmError::err(ZCoinBuildError::SaplingParamsInvalidChecksum) + } + }) + .await + } + + #[cfg(target_arch = "wasm32")] + async fn z_tx_prover(&self) -> Result> { todo!() } } /// Initialize `ZCoin` with a forced `z_spending_key`. @@ -1491,18 +1554,11 @@ impl MakerSwapTakerCoin for ZCoin { #[async_trait] impl WatcherOps for ZCoin { - fn create_maker_payment_spend_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { + fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { unimplemented!(); } - fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } @@ -1518,7 +1574,14 @@ impl WatcherOps for ZCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + fn create_maker_payment_spend_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { unimplemented!(); } @@ -1834,6 +1897,7 @@ impl UtxoCommonOps for ZCoin { } } +#[cfg(not(target_arch = "wasm32"))] #[async_trait] impl InitWithdrawCoin for ZCoin { async fn init_withdraw( diff --git a/mm2src/coins/z_coin/storage.rs b/mm2src/coins/z_coin/storage.rs new file mode 100644 index 0000000000..548ea0303f --- /dev/null +++ b/mm2src/coins/z_coin/storage.rs @@ -0,0 +1,5 @@ +pub mod blockdb; +pub use blockdb::*; + +pub mod walletdb; +pub use walletdb::*; diff --git a/mm2src/coins/z_coin/storage/blockdb/block_idb.rs b/mm2src/coins/z_coin/storage/blockdb/block_idb.rs new file mode 100644 index 0000000000..e70cfce122 --- /dev/null +++ b/mm2src/coins/z_coin/storage/blockdb/block_idb.rs @@ -0,0 +1,53 @@ +use async_trait::async_trait; +use mm2_db::indexed_db::{BeBigUint, DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, + OnUpgradeResult, TableSignature}; + +const DB_NAME: &str = "z_compactblocks_cache"; +const DB_VERSION: u32 = 1; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BlockDbTable { + height: BeBigUint, + data: Vec, + ticker: String, +} + +impl BlockDbTable { + pub const TICKER_HEIGHT_INDEX: &str = "block_height_ticker_index"; +} + +impl TableSignature for BlockDbTable { + fn table_name() -> &'static str { "compactblocks" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_HEIGHT_INDEX, &["ticker", "height"], true)?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +pub struct BlockDbInner { + pub inner: IndexedDb, +} + +impl BlockDbInner { + pub fn _get_inner(&self) -> &IndexedDb { &self.inner } +} + +#[async_trait] +impl DbInstance for BlockDbInner { + fn db_name() -> &'static str { DB_NAME } + + async fn init(db_id: DbIdentifier) -> InitDbResult { + let inner = IndexedDbBuilder::new(db_id) + .with_version(DB_VERSION) + .with_table::() + .build() + .await?; + + Ok(Self { inner }) + } +} diff --git a/mm2src/coins/z_coin/storage/blockdb/mod.rs b/mm2src/coins/z_coin/storage/blockdb/mod.rs new file mode 100644 index 0000000000..3067162008 --- /dev/null +++ b/mm2src/coins/z_coin/storage/blockdb/mod.rs @@ -0,0 +1,202 @@ +#[cfg(target_arch = "wasm32")] pub(crate) mod block_idb; + +use mm2_core::mm_ctx::MmArc; +use std::path::Path; +use zcash_client_backend::data_api::BlockSource; +use zcash_client_backend::proto::compact_formats::CompactBlock; +use zcash_primitives::consensus::BlockHeight; + +cfg_native!( + use db_common::sqlite::rusqlite::{params, Connection}; + use db_common::sqlite::{query_single_row, run_optimization_pragmas}; + use protobuf::Message; + use mm2_err_handle::prelude::*; + use std::sync::{Arc, Mutex}; + use zcash_client_sqlite::error::{SqliteClientError as ZcashClientError, SqliteClientError}; + use zcash_client_sqlite::NoteId; + use zcash_client_backend::data_api::error::Error as ChainError; + + struct CompactBlockRow { + height: BlockHeight, + data: Vec, + } +); + +#[derive(Debug, Display)] +pub enum BlockDbError { + #[cfg(not(target_arch = "wasm32"))] + SqliteError(SqliteClientError), + #[cfg(target_arch = "wasm32")] + IndexedDBError(String), + CorruptedData(String), +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for BlockDbError { + fn from(value: SqliteClientError) -> Self { Self::SqliteError(value) } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From> for BlockDbError { + fn from(value: ChainError) -> Self { Self::SqliteError(SqliteClientError::from(value)) } +} + +/// A wrapper for the db connection to the block cache database. +pub struct BlockDbImpl { + #[cfg(not(target_arch = "wasm32"))] + pub db: Arc>, + #[cfg(target_arch = "wasm32")] + pub db: SharedDb, + #[allow(unused)] + ticker: String, +} + +#[cfg(not(target_arch = "wasm32"))] +impl BlockDbImpl { + pub async fn new(_ctx: MmArc, ticker: String, path: impl AsRef) -> MmResult { + let conn = Connection::open(path).map_err(|err| BlockDbError::SqliteError(SqliteClientError::from(err)))?; + run_optimization_pragmas(&conn).map_err(|err| BlockDbError::SqliteError(SqliteClientError::from(err)))?; + conn.execute( + "CREATE TABLE IF NOT EXISTS compactblocks ( + height INTEGER PRIMARY KEY, + data BLOB NOT NULL + )", + [], + ) + .map_to_mm(|err| BlockDbError::SqliteError(SqliteClientError::from(err)))?; + + Ok(Self { + db: Arc::new(Mutex::new(conn)), + ticker, + }) + } + + pub(crate) fn get_latest_block(&self) -> Result { + Ok(query_single_row( + &self.db.lock().unwrap(), + "SELECT height FROM compactblocks ORDER BY height DESC LIMIT 1", + [], + |row| row.get(0), + )? + .unwrap_or(0)) + } + + pub(crate) fn insert_block(&self, height: u32, cb_bytes: Vec) -> Result { + self.db + .lock() + .unwrap() + .prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)") + .map_err(|err| BlockDbError::SqliteError(SqliteClientError::from(err)))? + .execute(params![height, cb_bytes]) + .map_err(|err| BlockDbError::SqliteError(SqliteClientError::from(err))) + } + + pub(crate) fn rewind_to_height(&self, height: u32) -> Result { + self.db + .lock() + .unwrap() + .execute("DELETE from compactblocks WHERE height > ?1", [height]) + .map_err(|err| BlockDbError::SqliteError(SqliteClientError::from(err))) + } + + fn with_blocks( + &self, + from_height: BlockHeight, + limit: Option, + mut with_row: F, + ) -> Result<(), SqliteClientError> + where + F: FnMut(CompactBlock) -> Result<(), SqliteClientError>, + { + // Fetch the CompactBlocks we need to scan + let stmt_blocks = self.db.lock().unwrap(); + let mut stmt_blocks = stmt_blocks.prepare( + "SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC \ + LIMIT ?", + )?; + + let rows = stmt_blocks.query_map( + params![u32::from(from_height), limit.unwrap_or(u32::max_value()),], + |row| { + Ok(CompactBlockRow { + height: BlockHeight::from_u32(row.get(0)?), + data: row.get(1)?, + }) + }, + )?; + + for row_result in rows { + let cbr = row_result?; + let block = CompactBlock::parse_from_bytes(&cbr.data).map_err(ChainError::from)?; + + if block.height() != cbr.height { + return Err(SqliteClientError::CorruptedData(format!( + "Block height {} did not match row's height field value {}", + block.height(), + cbr.height + ))); + } + + with_row(block)?; + } + + Ok(()) + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl BlockSource for BlockDbImpl { + type Error = SqliteClientError; + + fn with_blocks(&self, from_height: BlockHeight, limit: Option, with_row: F) -> Result<(), Self::Error> + where + F: FnMut(CompactBlock) -> Result<(), Self::Error>, + { + self.with_blocks(from_height, limit, with_row) + } +} + +cfg_wasm32!( + use crate::z_coin::storage::blockdb::block_idb::BlockDbInner; + use mm2_db::indexed_db::{ConstructibleDb, DbLocked, SharedDb}; + use mm2_err_handle::prelude::*; + + pub type BlockDbRes = MmResult; + pub type BlockDbInnerLocked<'a> = DbLocked<'a, BlockDbInner>; + + impl BlockDbImpl { + pub async fn new(ctx: MmArc, ticker: String, _path: impl AsRef) -> Result { + Ok(Self { + db: ConstructibleDb::new(&ctx).into_shared(), + ticker, + }) + } + + #[allow(unused)] + async fn lock_db(&self) -> BlockDbRes> { + self.db + .get_or_initialize() + .await + .mm_err(|err| BlockDbError::IndexedDBError(err.to_string())) + } + + pub fn get_latest_block(&self) -> Result { todo!() } + + pub fn insert_block(&self, _height: u32, _cb_bytes: Vec) -> Result { todo!() } + + pub fn rewind_to_height(&self, _height: u32) -> Result { todo!() } + + pub fn with_blocks(&self, _from_height: BlockHeight, _limit: Option, mut _with_row: F) -> Result<(), + BlockDbError> + where F: FnMut(CompactBlock) -> Result<(), BlockDbError> + { todo!() } + } + + impl BlockSource for BlockDbImpl { + type Error = BlockDbError; + fn with_blocks(&self, _from_height: BlockHeight, _limit: Option, _with_row: F) -> Result<(), + Self::Error> + where F: FnMut(CompactBlock) -> Result<(), Self::Error>, + { todo!() } + } +); diff --git a/mm2src/coins/z_coin/storage/walletdb/mod.rs b/mm2src/coins/z_coin/storage/walletdb/mod.rs new file mode 100644 index 0000000000..c7773df4ab --- /dev/null +++ b/mm2src/coins/z_coin/storage/walletdb/mod.rs @@ -0,0 +1,87 @@ +use crate::z_coin::{ZCoinBuilder, ZcoinClientInitError}; +use mm2_err_handle::prelude::*; + +cfg_native!( + use crate::z_coin::{extended_spending_key_from_protocol_info_and_policy, ZcoinConsensusParams}; + use crate::z_coin::z_rpc::create_wallet_db; + + use parking_lot::Mutex; + use std::sync::Arc; + use zcash_client_sqlite::WalletDb; + use zcash_primitives::zip32::ExtendedFullViewingKey; +); + +cfg_wasm32!( + mod wallet_idb; + use wallet_idb::WalletDbInner; +); + +#[derive(Debug, Display)] +pub enum WalletDbError { + ZcoinClientInitError(ZcoinClientInitError), + ZCoinBuildError(String), + IndexedDBError(String), +} + +#[derive(Clone)] +pub struct WalletDbShared { + #[cfg(not(target_arch = "wasm32"))] + pub db: Arc>>, + #[cfg(target_arch = "wasm32")] + pub db: SharedDb, + #[allow(unused)] + ticker: String, +} + +#[cfg(not(target_arch = "wasm32"))] +impl<'a> WalletDbShared { + pub async fn new(zcoin_builder: &ZCoinBuilder<'a>) -> MmResult { + let z_spending_key = match zcoin_builder.z_spending_key { + Some(ref z_spending_key) => z_spending_key.clone(), + None => extended_spending_key_from_protocol_info_and_policy( + &zcoin_builder.protocol_info, + &zcoin_builder.priv_key_policy, + ) + .map_err(|err| WalletDbError::ZCoinBuildError(err.to_string()))?, + }; + let wallet_db = create_wallet_db( + zcoin_builder + .db_dir_path + .join(format!("{}_wallet.db", zcoin_builder.ticker)), + zcoin_builder.protocol_info.consensus_params.clone(), + zcoin_builder.protocol_info.check_point_block.clone(), + ExtendedFullViewingKey::from(&z_spending_key), + ) + .await + .map_err(|err| MmError::new(WalletDbError::ZcoinClientInitError(err.into_inner())))?; + + Ok(Self { + db: Arc::new(Mutex::new(wallet_db)), + ticker: zcoin_builder.ticker.to_string(), + }) + } +} + +cfg_wasm32!( + use mm2_db::indexed_db::{ConstructibleDb, DbLocked, SharedDb}; + + pub type WalletDbRes = MmResult; + pub type WalletDbInnerLocked<'a> = DbLocked<'a, WalletDbInner>; + + impl<'a> WalletDbShared { + pub async fn new(zcoin_builder: &ZCoinBuilder<'a>) -> MmResult { + Ok(Self { + db: ConstructibleDb::new(zcoin_builder.ctx).into_shared(), + ticker: zcoin_builder.ticker.to_string(), + }) + } + + #[allow(unused)] + async fn lock_db(&self) -> WalletDbRes> { + self.db + .get_or_initialize() + .await + .mm_err(|err| WalletDbError::IndexedDBError(err.to_string())) + } + } +); diff --git a/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs b/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs new file mode 100644 index 0000000000..129097dbe6 --- /dev/null +++ b/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs @@ -0,0 +1,243 @@ +use async_trait::async_trait; +use mm2_db::indexed_db::{BeBigUint, DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, + OnUpgradeResult, TableSignature}; + +const DB_NAME: &str = "wallet_db_cache"; +const DB_VERSION: u32 = 1; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbAccountsTable { + account: BeBigUint, + extfvk: String, + address: String, + ticker: String, +} + +impl WalletDbAccountsTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * account + pub const TICKER_ACCOUNT_INDEX: &str = "ticker_account_index"; +} + +impl TableSignature for WalletDbAccountsTable { + fn table_name() -> &'static str { "walletdb_accounts" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_ACCOUNT_INDEX, &["ticker", "account"], true)?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbBlocksTable { + height: BeBigUint, + hash: String, + time: BeBigUint, + sapling_tree: String, + ticker: String, +} + +impl WalletDbBlocksTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * height + pub const TICKER_HEIGHT_INDEX: &str = "ticker_height_index"; + /// A **unique** index that consists of the following properties: + /// * ticker + /// * hash + pub const TICKER_HASH_INDEX: &str = "ticker_hash_index"; +} + +impl TableSignature for WalletDbBlocksTable { + fn table_name() -> &'static str { "walletdb_blocks" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_HEIGHT_INDEX, &["ticker", "height"], true)?; + table.create_multi_index(Self::TICKER_HASH_INDEX, &["ticker", "hash"], true)?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbTransactionsTable { + id_tx: BeBigUint, + txid: String, // unique + created: String, + block: BeBigUint, + tx_index: BeBigUint, + expiry_height: BeBigUint, + raw: String, + ticker: String, +} + +impl WalletDbTransactionsTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * id_tx + /// * txid + pub const TICKER_ID_TX_INDEX: &'static str = "ticker_id_tx_index"; +} + +impl TableSignature for WalletDbTransactionsTable { + fn table_name() -> &'static str { "walletdb_transactions" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_ID_TX_INDEX, &["ticker", "id_tx", "txid"], true)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbReceivedNotesTable { + id_note: BeBigUint, + tx: BeBigUint, + output_index: BeBigUint, + account: BeBigUint, + diversifier: String, + value: BeBigUint, + rcm: String, + nf: String, // unique + is_change: BeBigUint, + memo: String, + spent: BeBigUint, + ticker: String, +} + +impl WalletDbReceivedNotesTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * note_id + /// * nf + pub const TICKER_NOTES_ID_NF_INDEX: &'static str = "ticker_note_id_nf_index"; + /// A **unique** index that consists of the following properties: + /// * ticker + /// * tx + /// * output_index + pub const TICKER_NOTES_TX_OUTPUT_INDEX: &'static str = "ticker_notes_tx_output_index"; +} + +impl TableSignature for WalletDbReceivedNotesTable { + fn table_name() -> &'static str { "walletdb_received_notes" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_NOTES_ID_NF_INDEX, &["ticker", "id_note", "nf"], true)?; + table.create_multi_index( + Self::TICKER_NOTES_TX_OUTPUT_INDEX, + &["ticker", "tx", "output_index"], + true, + )?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbSaplingWitnessesTable { + id_witness: BeBigUint, + note: BeBigUint, + block: BeBigUint, + witness: String, + ticker: String, +} + +impl WalletDbSaplingWitnessesTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * note + /// * block + pub const TICKER_NOTE_BLOCK_INDEX: &'static str = "ticker_note_block_index"; + /// A **unique** index that consists of the following properties: + /// * ticker + /// * id_witness + pub const TICKER_ID_WITNESS_INDEX: &'static str = "ticker_id_witness_index"; +} + +impl TableSignature for WalletDbSaplingWitnessesTable { + fn table_name() -> &'static str { "walletdb_sapling_witness" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_NOTE_BLOCK_INDEX, &["ticker", "note", "block"], true)?; + table.create_multi_index(Self::TICKER_ID_WITNESS_INDEX, &["ticker", "id_witness"], true)?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WalletDbSentNotesTable { + id_note: BeBigUint, + tx: BeBigUint, + output_index: BeBigUint, + from_account: BeBigUint, + address: String, + value: BeBigUint, + memo: String, + ticker: String, +} + +impl WalletDbSentNotesTable { + /// A **unique** index that consists of the following properties: + /// * ticker + /// * tx + /// * output_index + pub const TICKER_TX_OUTPUT_INDEX: &'static str = "ticker_tx_output_index"; +} + +impl TableSignature for WalletDbSentNotesTable { + fn table_name() -> &'static str { "walletdb_sent_notes" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_TX_OUTPUT_INDEX, &["ticker", "tx", "output_index"], true)?; + table.create_index("ticker", false)?; + } + Ok(()) + } +} + +pub struct WalletDbInner { + pub inner: IndexedDb, +} + +impl WalletDbInner { + pub fn _get_inner(&self) -> &IndexedDb { &self.inner } +} + +#[async_trait] +impl DbInstance for WalletDbInner { + fn db_name() -> &'static str { DB_NAME } + + async fn init(db_id: DbIdentifier) -> InitDbResult { + let inner = IndexedDbBuilder::new(db_id) + .with_version(DB_VERSION) + .with_table::() + .with_table::() + .with_table::() + .with_table::() + .with_table::() + .with_table::() + .build() + .await?; + + Ok(Self { inner }) + } +} diff --git a/mm2src/coins/z_coin/z_coin_errors.rs b/mm2src/coins/z_coin/z_coin_errors.rs index 8cb6da1359..b8e3875e81 100644 --- a/mm2src/coins/z_coin/z_coin_errors.rs +++ b/mm2src/coins/z_coin/z_coin_errors.rs @@ -1,41 +1,47 @@ use crate::my_tx_history_v2::MyTxHistoryErrorV2; use crate::utxo::rpc_clients::UtxoRpcError; use crate::utxo::utxo_builder::UtxoCoinBuildError; +use crate::z_coin::storage::WalletDbError; +use crate::NumConversError; +use crate::PrivKeyPolicyNotAllowed; use crate::WithdrawError; -use crate::{NumConversError, PrivKeyPolicyNotAllowed}; + use common::jsonrpc_client::JsonRpcError; +#[cfg(not(target_arch = "wasm32"))] use db_common::sqlite::rusqlite::Error as SqliteError; -use db_common::sqlite::rusqlite::Error as SqlError; use derive_more::Display; use http::uri::InvalidUri; use mm2_number::BigDecimal; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; +#[cfg(not(target_arch = "wasm32"))] use zcash_client_sqlite::error::SqliteClientError; -use zcash_client_sqlite::error::SqliteClientError as ZcashClientError; use zcash_primitives::transaction::builder::Error as ZTxBuilderError; #[derive(Debug, Display)] #[non_exhaustive] pub enum UpdateBlocksCacheErr { + #[cfg(not(target_arch = "wasm32"))] GrpcError(tonic::Status), - BlocksDbError(SqliteError), - ZcashSqliteError(ZcashClientError), UtxoRpcError(UtxoRpcError), InternalError(String), JsonRpcError(JsonRpcError), GetLiveLightClientError(String), + ZcashDBError(String), } +#[cfg(not(target_arch = "wasm32"))] impl From for UpdateBlocksCacheErr { fn from(err: tonic::Status) -> Self { UpdateBlocksCacheErr::GrpcError(err) } } +#[cfg(not(target_arch = "wasm32"))] impl From for UpdateBlocksCacheErr { - fn from(err: SqliteError) -> Self { UpdateBlocksCacheErr::BlocksDbError(err) } + fn from(err: SqliteError) -> Self { UpdateBlocksCacheErr::ZcashDBError(err.to_string()) } } -impl From for UpdateBlocksCacheErr { - fn from(err: ZcashClientError) -> Self { UpdateBlocksCacheErr::ZcashSqliteError(err) } +#[cfg(not(target_arch = "wasm32"))] +impl From for UpdateBlocksCacheErr { + fn from(err: SqliteClientError) -> Self { UpdateBlocksCacheErr::ZcashDBError(err.to_string()) } } impl From for UpdateBlocksCacheErr { @@ -49,29 +55,26 @@ impl From for UpdateBlocksCacheErr { #[derive(Debug, Display)] #[non_exhaustive] pub enum ZcoinClientInitError { - BlocksDbInitFailure(SqliteError), - WalletDbInitFailure(SqliteError), - ZcashSqliteError(ZcashClientError), + ZcashDBError(String), EmptyLightwalletdUris, #[display(fmt = "Fail to init clients while iterating lightwalletd urls {:?}", _0)] UrlIterFailure(Vec), } -impl From for ZcoinClientInitError { - fn from(err: ZcashClientError) -> Self { ZcoinClientInitError::ZcashSqliteError(err) } +#[cfg(not(target_arch = "wasm32"))] +impl From for ZcoinClientInitError { + fn from(err: SqliteClientError) -> Self { ZcoinClientInitError::ZcashDBError(err.to_string()) } } #[derive(Debug, Display)] pub enum UrlIterError { InvalidUri(InvalidUri), + #[cfg(not(target_arch = "wasm32"))] TlsConfigFailure(tonic::transport::Error), + #[cfg(not(target_arch = "wasm32"))] ConnectionFailure(tonic::transport::Error), } -#[derive(Debug, Display)] -#[display(fmt = "Blockchain scan process stopped")] -pub struct BlockchainScanStopped {} - #[derive(Debug, Display)] pub enum GenTxError { DecryptedOutputNotFound, @@ -98,8 +101,9 @@ pub enum GenTxError { err: std::io::Error, }, BlockchainScanStopped, - LightClientErr(SqliteClientError), + LightClientErr(String), FailedToCreateNote, + SpendableNotesError(String), } impl From for GenTxError { @@ -118,8 +122,9 @@ impl From for GenTxError { fn from(err: ZTxBuilderError) -> GenTxError { GenTxError::TxBuilderError(err) } } +#[cfg(not(target_arch = "wasm32"))] impl From for GenTxError { - fn from(err: SqliteClientError) -> Self { GenTxError::LightClientErr(err) } + fn from(err: SqliteClientError) -> Self { GenTxError::LightClientErr(err.to_string()) } } impl From for WithdrawError { @@ -144,11 +149,16 @@ impl From for WithdrawError { | GenTxError::TxReadError { .. } | GenTxError::BlockchainScanStopped | GenTxError::LightClientErr(_) + | GenTxError::SpendableNotesError(_) | GenTxError::FailedToCreateNote => WithdrawError::InternalError(gen_tx.to_string()), } } } +#[derive(Debug, Display)] +#[display(fmt = "Blockchain scan process stopped")] +pub struct BlockchainScanStopped {} + impl From for GenTxError { #[inline] fn from(_: BlockchainScanStopped) -> Self { GenTxError::BlockchainScanStopped } @@ -185,18 +195,19 @@ pub enum GetUnspentWitnessErr { EmptyDbResult, TreeOrWitnessAppendFailed, OutputCmuNotFoundInCache, - Sql(SqliteError), + ZcashDBError(String), } +#[cfg(not(target_arch = "wasm32"))] impl From for GetUnspentWitnessErr { - fn from(err: SqliteError) -> GetUnspentWitnessErr { GetUnspentWitnessErr::Sql(err) } + fn from(err: SqliteError) -> GetUnspentWitnessErr { GetUnspentWitnessErr::ZcashDBError(err.to_string()) } } #[derive(Debug, Display)] pub enum ZCoinBuildError { UtxoBuilderError(UtxoCoinBuildError), GetAddressError, - SqliteError(SqliteError), + ZcashDBError(String), Rpc(UtxoRpcError), #[display(fmt = "Sapling cache DB does not exist at {}. Please download it.", path)] SaplingCacheDbDoesNotExist { @@ -209,35 +220,39 @@ pub enum ZCoinBuildError { SaplingParamsInvalidChecksum, } +#[cfg(not(target_arch = "wasm32"))] impl From for ZCoinBuildError { - fn from(err: SqliteError) -> ZCoinBuildError { ZCoinBuildError::SqliteError(err) } + fn from(err: SqliteError) -> ZCoinBuildError { ZCoinBuildError::ZcashDBError(err.to_string()) } } impl From for ZCoinBuildError { fn from(err: UtxoRpcError) -> ZCoinBuildError { ZCoinBuildError::Rpc(err) } } -impl From for ZCoinBuildError { - fn from(err: UtxoCoinBuildError) -> Self { ZCoinBuildError::UtxoBuilderError(err) } -} - impl From for ZCoinBuildError { fn from(err: std::io::Error) -> ZCoinBuildError { ZCoinBuildError::Io(err) } } +impl From for ZCoinBuildError { + fn from(err: UtxoCoinBuildError) -> Self { ZCoinBuildError::UtxoBuilderError(err) } +} + impl From for ZCoinBuildError { fn from(err: ZcoinClientInitError) -> Self { ZCoinBuildError::RpcClientInitErr(err) } } +#[cfg(not(target_arch = "wasm32"))] pub(super) enum SqlTxHistoryError { - Sql(SqlError), + Sql(SqliteError), FromIdDoesNotExist(i64), } -impl From for SqlTxHistoryError { - fn from(err: SqlError) -> Self { SqlTxHistoryError::Sql(err) } +#[cfg(not(target_arch = "wasm32"))] +impl From for SqlTxHistoryError { + fn from(err: SqliteError) -> Self { SqlTxHistoryError::Sql(err) } } +#[cfg(not(target_arch = "wasm32"))] impl From for MyTxHistoryErrorV2 { fn from(err: SqlTxHistoryError) -> Self { match err { @@ -256,3 +271,11 @@ impl From for MyTxHistoryErrorV2 { MyTxHistoryErrorV2::RpcError(format!("No info about transaction {:02x}", err.0)) } } + +#[derive(Debug, Display)] +pub enum SpendableNotesError { + DBClientError(String), +} + +#[derive(Debug, Display)] +pub enum ZCoinBalanceError {} diff --git a/mm2src/coins/z_coin/z_htlc.rs b/mm2src/coins/z_coin/z_htlc.rs index 4914c0002d..edde4bbc37 100644 --- a/mm2src/coins/z_coin/z_htlc.rs +++ b/mm2src/coins/z_coin/z_htlc.rs @@ -9,24 +9,33 @@ use super::ZCoin; use crate::utxo::rpc_clients::{UtxoRpcClientEnum, UtxoRpcError}; use crate::utxo::utxo_common::payment_script; use crate::utxo::{sat_from_big_decimal, UtxoAddressFormat}; -use crate::z_coin::{SendOutputsErr, ZOutput, DEX_FEE_OVK}; -use crate::{NumConversError, PrivKeyPolicyNotAllowed, TransactionEnum}; +use crate::z_coin::SendOutputsErr; +use crate::z_coin::{ZOutput, DEX_FEE_OVK}; +use crate::NumConversError; +use crate::{PrivKeyPolicyNotAllowed, TransactionEnum}; use bitcrypto::dhash160; -use common::async_blocking; use derive_more::Display; use futures::compat::Future01CompatExt; -use keys::{Address, KeyPair, Public}; +use keys::Address; +use keys::{KeyPair, Public}; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; -use script::{Builder as ScriptBuilder, Opcode, Script}; -use secp256k1::SecretKey; -use zcash_primitives::consensus; +use script::Script; +use script::{Builder as ScriptBuilder, Opcode}; use zcash_primitives::legacy::Script as ZCashScript; use zcash_primitives::memo::MemoBytes; -use zcash_primitives::transaction::builder::{Builder as ZTxBuilder, Error as ZTxBuilderError}; -use zcash_primitives::transaction::components::{Amount, OutPoint as ZCashOutpoint, TxOut}; +use zcash_primitives::transaction::builder::Error as ZTxBuilderError; +use zcash_primitives::transaction::components::{Amount, TxOut}; use zcash_primitives::transaction::Transaction as ZTransaction; +cfg_native!( + use common::async_blocking; + use secp256k1::SecretKey; + use zcash_primitives::consensus; + use zcash_primitives::transaction::builder::Builder as ZTxBuilder; + use zcash_primitives::transaction::components::OutPoint as ZCashOutpoint; +); + /// Sends HTLC output from the coin's my_z_addr pub async fn z_send_htlc( coin: &ZCoin, @@ -93,7 +102,7 @@ pub async fn z_send_dex_fee( } #[derive(Debug, Display)] -#[allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] +#[allow(clippy::large_enum_variant, clippy::upper_case_acronyms, unused)] pub enum ZP2SHSpendError { ZTxBuilderError(ZTxBuilderError), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), @@ -130,6 +139,7 @@ impl ZP2SHSpendError { } /// Spends P2SH output 0 to the coin's my_z_addr +#[cfg(not(target_arch = "wasm32"))] pub async fn z_p2sh_spend( coin: &ZCoin, p2sh_tx: ZTransaction, @@ -181,3 +191,16 @@ pub async fn z_p2sh_spend( .map(|_| zcash_tx.clone()) .mm_err(|e| ZP2SHSpendError::TxRecoverable(zcash_tx.into(), e.to_string())) } + +#[cfg(target_arch = "wasm32")] +pub async fn z_p2sh_spend( + _coin: &ZCoin, + _p2sh_tx: ZTransaction, + _tx_locktime: u32, + _input_sequence: u32, + _redeem_script: Script, + _script_data: Script, + _htlc_keypair: &KeyPair, +) -> Result> { + todo!() +} diff --git a/mm2src/coins/z_coin/z_rpc.rs b/mm2src/coins/z_coin/z_rpc.rs index 64e1a67695..129abab700 100644 --- a/mm2src/coins/z_coin/z_rpc.rs +++ b/mm2src/coins/z_coin/z_rpc.rs @@ -1,59 +1,67 @@ -use super::{z_coin_errors::*, CheckPointBlockInfo, ZcoinConsensusParams}; -use crate::utxo::rpc_clients::{NativeClient, UtxoRpcClientOps, NO_TX_ERROR_CODE}; +use super::{z_coin_errors::*, ZcoinConsensusParams}; +use crate::utxo::rpc_clients::NativeClient; +use crate::z_coin::storage::{BlockDbImpl, WalletDbShared}; use async_trait::async_trait; -use common::executor::{spawn_abortable, AbortOnDropHandle, Timer}; -use common::log::{debug, error, info, LogOnError}; -use common::{async_blocking, Future01CompatExt}; -use db_common::sqlite::rusqlite::{params, Connection, Error as SqliteError}; -use db_common::sqlite::{query_single_row, run_optimization_pragmas}; -use futures::channel::mpsc::{channel, Receiver as AsyncReceiver, Sender as AsyncSender}; +use common::executor::{spawn_abortable, AbortOnDropHandle}; +use futures::channel::mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender}; use futures::channel::oneshot::{channel as oneshot_channel, Sender as OneshotSender}; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures::StreamExt; -use group::GroupEncoding; -use http::Uri; use mm2_err_handle::prelude::*; use parking_lot::Mutex; -use prost::Message; -use protobuf::Message as ProtobufMessage; -use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::str::FromStr; use std::sync::Arc; -use std::time::Duration; -use tokio::task::block_in_place; -use tonic::transport::{Channel, ClientTlsConfig}; -use zcash_client_backend::data_api::chain::{scan_cached_blocks, validate_chain}; -use zcash_client_backend::data_api::error::Error as ChainError; -use zcash_client_backend::data_api::{BlockSource, WalletRead, WalletWrite}; -use zcash_client_backend::proto::compact_formats::CompactBlock; -use zcash_client_sqlite::error::SqliteClientError as ZcashClientError; -use zcash_client_sqlite::wallet::init::{init_accounts_table, init_blocks_table, init_wallet_db}; -use zcash_client_sqlite::WalletDb; -use zcash_primitives::block::BlockHash; use zcash_primitives::consensus::BlockHeight; use zcash_primitives::transaction::TxId; -use zcash_primitives::zip32::ExtendedFullViewingKey; -mod z_coin_grpc { - tonic::include_proto!("cash.z.wallet.sdk.rpc"); -} -use crate::{RpcCommonOps, ZTransaction}; -use rpc::v1::types::H256 as H256Json; -use z_coin_grpc::compact_tx_streamer_client::CompactTxStreamerClient; -use z_coin_grpc::{BlockId, BlockRange, ChainSpec, CompactBlock as TonicCompactBlock, +cfg_native!( + use super::CheckPointBlockInfo; + use crate::{RpcCommonOps, ZTransaction}; + use crate::utxo::rpc_clients::{UtxoRpcClientOps, NO_TX_ERROR_CODE}; + use crate::z_coin::storage::BlockDbError; + + use db_common::sqlite::rusqlite::Connection; + use db_common::sqlite::{query_single_row, run_optimization_pragmas}; + use common::async_blocking; + use common::executor::Timer; + use common::log::{debug, error, info, LogOnError}; + use common::Future01CompatExt; + use futures::channel::mpsc::channel; + use group::GroupEncoding; + use http::Uri; + use prost::Message; + use rpc::v1::types::H256 as H256Json; + use std::path::PathBuf; + use std::pin::Pin; + use std::str::FromStr; + use std::time::Duration; + use tokio::task::block_in_place; + use tonic::transport::{Channel, ClientTlsConfig}; + use zcash_client_backend::data_api::{WalletRead, WalletWrite}; + use zcash_client_backend::data_api::chain::{scan_cached_blocks, validate_chain}; + use zcash_client_backend::data_api::error::Error as ChainError; + use zcash_primitives::block::BlockHash; + use zcash_primitives::zip32::ExtendedFullViewingKey; + use zcash_client_sqlite::error::SqliteClientError as ZcashClientError; + use zcash_client_sqlite::wallet::init::{init_accounts_table, init_blocks_table, init_wallet_db}; + use zcash_client_sqlite::WalletDb; + + mod z_coin_grpc { + tonic::include_proto!("cash.z.wallet.sdk.rpc"); + } + + use z_coin_grpc::compact_tx_streamer_client::CompactTxStreamerClient; + use z_coin_grpc::{BlockId, BlockRange, ChainSpec, CompactBlock as TonicCompactBlock, CompactOutput as TonicCompactOutput, CompactSpend as TonicCompactSpend, CompactTx as TonicCompactTx, TxFilter}; +); -pub type WalletDbShared = Arc>>; - -struct CompactBlockRow { - height: BlockHeight, - data: Vec, -} - +#[cfg(not(target_arch = "wasm32"))] pub type OnCompactBlockFn<'a> = dyn FnMut(TonicCompactBlock) -> Result<(), MmError> + Send + 'a; +#[cfg(target_arch = "wasm32")] +#[allow(unused)] +pub type OnCompactBlockFn<'a> = dyn FnMut(String) -> Result<(), MmError> + Send + 'a; + #[async_trait] pub trait ZRpcOps { async fn get_block_height(&mut self) -> Result>; @@ -68,10 +76,12 @@ pub trait ZRpcOps { async fn check_tx_existence(&mut self, tx_id: TxId) -> bool; } +#[cfg(not(target_arch = "wasm32"))] struct LightRpcClient { rpc_clients: AsyncMutex>>, } +#[cfg(not(target_arch = "wasm32"))] #[async_trait] impl RpcCommonOps for LightRpcClient { type RpcClient = CompactTxStreamerClient; @@ -93,6 +103,7 @@ impl RpcCommonOps for LightRpcClient { } } +#[cfg(not(target_arch = "wasm32"))] #[async_trait] impl ZRpcOps for LightRpcClient { async fn get_block_height(&mut self) -> Result> { @@ -167,6 +178,7 @@ impl ZRpcOps for LightRpcClient { } } +#[cfg(not(target_arch = "wasm32"))] #[async_trait] impl ZRpcOps for NativeClient { async fn get_block_height(&mut self) -> Result> { @@ -271,106 +283,7 @@ impl ZRpcOps for NativeClient { } } -/// A wrapper for the SQLite connection to the block cache database. -pub struct BlockDb(Connection); - -impl BlockDb { - /// Opens a connection to the wallet database stored at the specified path. - pub fn for_path>(path: P) -> Result { - let conn = Connection::open(path)?; - run_optimization_pragmas(&conn)?; - conn.execute( - "CREATE TABLE IF NOT EXISTS compactblocks ( - height INTEGER PRIMARY KEY, - data BLOB NOT NULL - )", - [], - )?; - Ok(BlockDb(conn)) - } - - fn with_blocks( - &self, - from_height: BlockHeight, - limit: Option, - mut with_row: F, - ) -> Result<(), ZcashClientError> - where - F: FnMut(CompactBlock) -> Result<(), ZcashClientError>, - { - // Fetch the CompactBlocks we need to scan - let mut stmt_blocks = self - .0 - .prepare("SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC LIMIT ?")?; - - let rows = stmt_blocks.query_map( - params![u32::from(from_height), limit.unwrap_or(u32::max_value()),], - |row| { - Ok(CompactBlockRow { - height: BlockHeight::from_u32(row.get(0)?), - data: row.get(1)?, - }) - }, - )?; - - for row_result in rows { - let cbr = row_result?; - let block = CompactBlock::parse_from_bytes(&cbr.data) - .map_err(zcash_client_backend::data_api::error::Error::from)?; - - if block.height() != cbr.height { - return Err(ZcashClientError::CorruptedData(format!( - "Block height {} did not match row's height field value {}", - block.height(), - cbr.height - ))); - } - - with_row(block)?; - } - - Ok(()) - } - - fn get_latest_block(&self) -> Result> { - Ok(query_single_row( - &self.0, - "SELECT height FROM compactblocks ORDER BY height DESC LIMIT 1", - [], - |row| row.get(0), - )? - .unwrap_or(0)) - } - - fn insert_block( - &self, - height: u32, - cb_bytes: Vec, - ) -> Result> { - self.0 - .prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)")? - .execute(params![height, cb_bytes]) - .map_err(MmError::new) - } - - fn rewind_to_height(&self, height: u32) -> Result> { - self.0 - .execute("DELETE from compactblocks WHERE height > ?1", [height]) - .map_err(MmError::new) - } -} - -impl BlockSource for BlockDb { - type Error = ZcashClientError; - - fn with_blocks(&self, from_height: BlockHeight, limit: Option, with_row: F) -> Result<(), Self::Error> - where - F: FnMut(CompactBlock) -> Result<(), Self::Error>, - { - self.with_blocks(from_height, limit, with_row) - } -} - +#[cfg(not(target_arch = "wasm32"))] pub async fn create_wallet_db( wallet_db_path: PathBuf, consensus_params: ZcoinConsensusParams, @@ -380,9 +293,10 @@ pub async fn create_wallet_db( async_blocking({ move || -> Result, MmError> { let db = WalletDb::for_path(wallet_db_path, consensus_params) - .map_to_mm(ZcoinClientInitError::WalletDbInitFailure)?; - run_optimization_pragmas(db.sql_conn()).map_to_mm(ZcoinClientInitError::WalletDbInitFailure)?; - init_wallet_db(&db).map_to_mm(ZcoinClientInitError::WalletDbInitFailure)?; + .map_to_mm(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; + run_optimization_pragmas(db.sql_conn()) + .map_to_mm(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; + init_wallet_db(&db).map_to_mm(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; if db.get_extended_full_viewing_keys()?.is_empty() { init_accounts_table(&db, &[evk])?; if let Some(check_point) = check_point_block { @@ -401,10 +315,11 @@ pub async fn create_wallet_db( .await } +#[cfg(not(target_arch = "wasm32"))] pub(super) async fn init_light_client( coin: String, lightwalletd_urls: Vec, - blocks_db: BlockDb, + blocks_db: BlockDbImpl, wallet_db: WalletDbShared, consensus_params: ZcoinConsensusParams, scan_blocks_per_iteration: u32, @@ -472,10 +387,25 @@ pub(super) async fn init_light_client( )) } +#[cfg(target_arch = "wasm32")] +#[allow(unused)] +pub(super) async fn init_light_client( + _coin: String, + _lightwalletd_urls: Vec, + _blocks_db: BlockDbImpl, + _wallet_db: WalletDbShared, + _consensus_params: ZcoinConsensusParams, + _scan_blocks_per_iteration: u32, + _scan_interval_ms: u64, +) -> Result<(AsyncMutex, WalletDbShared), MmError> { + todo!() +} + +#[cfg(not(target_arch = "wasm32"))] pub(super) async fn init_native_client( coin: String, native_client: NativeClient, - blocks_db: BlockDb, + blocks_db: BlockDbImpl, wallet_db: WalletDbShared, consensus_params: ZcoinConsensusParams, scan_blocks_per_iteration: u32, @@ -504,6 +434,19 @@ pub(super) async fn init_native_client( )) } +#[cfg(target_arch = "wasm32")] +pub(super) async fn _init_native_client( + _coin: String, + _native_client: NativeClient, + _blocks_db: BlockDbImpl, + _consensus_params: ZcoinConsensusParams, + _scan_blocks_per_iteration: u32, + _scan_interval_ms: u64, +) -> Result<(AsyncMutex, String), MmError> { + todo!() +} + +#[cfg(not(target_arch = "wasm32"))] fn is_tx_imported(conn: &Connection, tx_id: TxId) -> bool { const QUERY: &str = "SELECT id_tx FROM transactions WHERE txid = ?1;"; match query_single_row(conn, QUERY, [tx_id.0.to_vec()], |row| row.get::<_, i64>(0)) { @@ -512,6 +455,10 @@ fn is_tx_imported(conn: &Connection, tx_id: TxId) -> bool { } } +#[cfg(target_arch = "wasm32")] +#[allow(unused)] +fn is_tx_imported(_conn: String, _tx_id: TxId) -> bool { todo!() } + pub struct SaplingSyncRespawnGuard { pub(super) sync_handle: Option<(SaplingSyncLoopHandle, Box)>, pub(super) abort_handle: Arc>, @@ -525,6 +472,7 @@ impl Drop for SaplingSyncRespawnGuard { } } +#[allow(unused)] impl SaplingSyncRespawnGuard { pub(super) fn watch_for_tx(&mut self, tx_id: TxId) { if let Some(ref mut handle) = self.sync_handle { @@ -553,10 +501,11 @@ pub enum SyncStatus { }, } +#[allow(unused)] pub struct SaplingSyncLoopHandle { coin: String, current_block: BlockHeight, - blocks_db: BlockDb, + blocks_db: BlockDbImpl, wallet_db: WalletDbShared, consensus_params: ZcoinConsensusParams, /// Notifies about sync status without stopping the loop, e.g. on coin activation @@ -569,6 +518,7 @@ pub struct SaplingSyncLoopHandle { scan_interval_ms: u64, } +#[cfg(not(target_arch = "wasm32"))] impl SaplingSyncLoopHandle { fn notify_blocks_cache_status(&mut self, current_scanned_block: u64, latest_block: u64) { self.sync_status_notifier @@ -608,7 +558,11 @@ impl SaplingSyncLoopHandle { ) -> Result<(), MmError> { let current_block = rpc.get_block_height().await?; let current_block_in_db = block_in_place(|| self.blocks_db.get_latest_block())?; - let extrema = block_in_place(|| self.wallet_db.lock().block_height_extrema())?; + let wallet_db = self.wallet_db.clone(); + let extrema = block_in_place(|| { + let conn = wallet_db.db.lock(); + conn.block_height_extrema() + })?; let mut from_block = self .consensus_params .sapling_activation_height @@ -619,7 +573,8 @@ impl SaplingSyncLoopHandle { } if current_block >= from_block { rpc.scan_blocks(from_block, current_block, &mut |block: TonicCompactBlock| { - block_in_place(|| self.blocks_db.insert_block(block.height as u32, block.encode_to_vec()))?; + block_in_place(|| self.blocks_db.insert_block(block.height as u32, block.encode_to_vec())) + .map_err(|err| UpdateBlocksCacheErr::ZcashDBError(err.to_string()))?; self.notify_blocks_cache_status(block.height, current_block); Ok(()) }) @@ -631,10 +586,10 @@ impl SaplingSyncLoopHandle { /// Scans cached blocks, validates the chain and updates WalletDb. /// For more notes on the process, check https://github.com/zcash/librustzcash/blob/master/zcash_client_backend/src/data_api/chain.rs#L2 - fn scan_blocks(&mut self) -> Result<(), MmError> { + fn scan_blocks(&mut self) -> Result<(), MmError> { // required to avoid immutable borrow of self let wallet_db_arc = self.wallet_db.clone(); - let wallet_guard = wallet_db_arc.lock(); + let wallet_guard = wallet_db_arc.db.lock(); let mut wallet_ops = wallet_guard.get_update_ops().expect("get_update_ops always returns Ok"); if let Err(e) = validate_chain( @@ -652,7 +607,7 @@ impl SaplingSyncLoopHandle { wallet_ops.rewind_to_height(rewind_height)?; self.blocks_db.rewind_to_height(rewind_height.into())?; }, - e => return MmError::err(e), + e => return MmError::err(BlockDbError::SqliteError(e)), } } @@ -691,6 +646,31 @@ impl SaplingSyncLoopHandle { } } +#[cfg(target_arch = "wasm32")] +#[allow(unused)] +impl SaplingSyncLoopHandle { + fn notify_blocks_cache_status(&mut self, _current_scanned_block: u64, _latest_block: u64) { todo!() } + + fn notify_building_wallet_db(&mut self, _current_scanned_block: u64, _latest_block: u64) { todo!() } + + fn notify_on_error(&mut self, _error: String) { todo!() } + + fn notify_sync_finished(&mut self) { todo!() } + + async fn update_blocks_cache( + &mut self, + _rpc: &mut (dyn ZRpcOps + Send), + ) -> Result<(), MmError> { + todo!() + } + + /// Scans cached blocks, validates the chain and updates WalletDb. + /// For more notes on the process, check https://github.com/zcash/librustzcash/blob/master/zcash_client_backend/src/data_api/chain.rs#L2 + fn scan_blocks(&mut self) -> Result<(), MmError> { todo!() } + + async fn check_watch_for_tx_existence(&mut self, _rpc: &mut (dyn ZRpcOps + Send)) { todo!() } +} + /// For more info on shielded light client protocol, please check the https://zips.z.cash/zip-0307 /// /// It's important to note that unlike standard UTXOs, shielded outputs are not spendable until the transaction is confirmed. @@ -712,6 +692,7 @@ impl SaplingSyncLoopHandle { /// 6. Once the transaction is generated and sent, `SaplingSyncRespawnGuard::watch_for_tx` is called to update `SaplingSyncLoopHandle` state. /// 7. Once the loop is respawned, it will check that broadcast tx is imported (or not available anymore) before stopping in favor of /// next wait_for_gen_tx_blockchain_sync call. +#[cfg(not(target_arch = "wasm32"))] async fn light_wallet_db_sync_loop(mut sync_handle: SaplingSyncLoopHandle, mut client: Box) { info!( "(Re)starting light_wallet_db_sync_loop for {}, blocks per iteration {}, interval in ms {}", @@ -738,7 +719,7 @@ async fn light_wallet_db_sync_loop(mut sync_handle: SaplingSyncLoopHandle, mut c sync_handle.check_watch_for_tx_existence(client.as_mut()).await; if let Some(tx_id) = sync_handle.watch_for_tx { - if !block_in_place(|| is_tx_imported(sync_handle.wallet_db.lock().sql_conn(), tx_id)) { + if !block_in_place(|| is_tx_imported(sync_handle.wallet_db.db.lock().sql_conn(), tx_id)) { info!("Tx {} is not imported yet", tx_id); Timer::sleep(10.).await; continue; @@ -760,6 +741,11 @@ async fn light_wallet_db_sync_loop(mut sync_handle: SaplingSyncLoopHandle, mut c } } +#[cfg(target_arch = "wasm32")] +async fn light_wallet_db_sync_loop(mut _sync_handle: SaplingSyncLoopHandle, mut _client: Box) { + todo!() +} + type SyncWatcher = AsyncReceiver; type NewTxNotifier = AsyncSender)>>; @@ -770,6 +756,7 @@ pub(super) struct SaplingSyncConnector { } impl SaplingSyncConnector { + #[allow(unused)] #[inline] pub(super) fn new_mutex_wrapped( simple_sync_watcher: SyncWatcher, diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 76fa3da103..739b918a38 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -5849,12 +5849,13 @@ fn orderbook_address( CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { MmError::err(OrderbookAddrErr::CoinIsNotSupported(coin.to_owned())) }, + CoinProtocol::ZHTLC { .. } => Ok(OrderbookAddress::Shielded), #[cfg(not(target_arch = "wasm32"))] // Todo: Shielded address is used for lightning for now, the lightning node public key can be used for the orderbook entry pubkey // Todo: instead of the platform coin pubkey which is used right now. But lightning payments are supposed to be private, // Todo: so maybe we should hide the node address in the orderbook, only the sending node and the receiving node should know about a payment, // Todo: a routing node will know about a payment it routed but not the sender or the receiver. This will require using a new keypair for every order/swap // Todo: similar to how it's done for zcoin. - CoinProtocol::ZHTLC { .. } | CoinProtocol::LIGHTNING { .. } => Ok(OrderbookAddress::Shielded), + CoinProtocol::LIGHTNING { .. } => Ok(OrderbookAddress::Shielded), } }