diff --git a/Cargo.lock b/Cargo.lock index de8e16a..20947fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -65,6 +77,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anyhow" version = "1.0.86" @@ -426,6 +444,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "daggy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +dependencies = [ + "petgraph", +] + [[package]] name = "delegate-display" version = "2.1.1" @@ -506,6 +533,18 @@ dependencies = [ "blake2b_simd", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fancy_constructor" version = "1.2.2" @@ -749,6 +788,19 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "heck" @@ -993,6 +1045,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1355,6 +1418,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "poly1305" version = "0.8.0" @@ -1626,6 +1695,21 @@ dependencies = [ "digest", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "time", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1719,6 +1803,29 @@ dependencies = [ "zip32", ] +[[package]] +name = "schemer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d8f9478fd2936195fc941a8666b0d0894d5bf3631cbb884a8ce8ba631f339" +dependencies = [ + "daggy", + "log", + "thiserror", + "uuid", +] + +[[package]] +name = "schemer-rusqlite" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb5ac1fa52c58e2c6a618e3149d464e7ad8d0effca74990ea29c1fe2338b3b1" +dependencies = [ + "rusqlite", + "schemer", + "uuid", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1914,11 +2021,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", "js-sys", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -1927,6 +2036,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2249,6 +2368,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2470,6 +2595,8 @@ dependencies = [ "ripemd", "secrecy", "sha2", + "subtle", + "tempfile", "thiserror", "tokio", "tonic", @@ -2484,6 +2611,7 @@ dependencies = [ "zcash_address", "zcash_client_backend", "zcash_client_memory", + "zcash_client_sqlite", "zcash_keys", "zcash_primitives", "zcash_proofs", @@ -2700,6 +2828,42 @@ dependencies = [ "zip32", ] +[[package]] +name = "zcash_client_sqlite" +version = "0.11.2" +source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +dependencies = [ + "bs58", + "byteorder", + "document-features", + "group", + "incrementalmerkletree", + "jubjub", + "maybe-rayon", + "nonempty", + "orchard", + "prost 0.13.2", + "regex", + "rusqlite", + "sapling-crypto", + "schemer", + "schemer-rusqlite", + "secrecy", + "shardtree", + "static_assertions", + "subtle", + "time", + "tracing", + "uuid", + "zcash_address", + "zcash_client_backend", + "zcash_encoding", + "zcash_keys", + "zcash_primitives", + "zcash_protocol", + "zip32", +] + [[package]] name = "zcash_encoding" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index bdec4fa..43a8a20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ edition = "2021" [lib] crate-type = ["cdylib", "rlib"] +[[example]] +name = "simple-sync" +path = "examples/simple-sync.rs" + [profile.release] # Tell `rustc` to optimize for small code size. opt-level = 3 @@ -24,7 +28,7 @@ default = ["native"] wasm = ["console_error_panic_hook", "dep:tracing-web"] native = ["dep:tokio", "tonic/channel", "tonic/gzip", "tonic/tls-webpki-roots"] - +sqlite-db = ["dep:zcash_client_sqlite"] console_error_panic_hook = ["dep:console_error_panic_hook"] [dependencies] @@ -54,7 +58,8 @@ tonic = { version = "0.12", default-features = false, features = [ ] } # Used in Native tests -tokio = { version = "1.0", features = ["rt", "macros"], optional = true } +tokio = { version = "1.0", features = ["rt", "macros", "rt-multi-thread"], optional = true } +zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96", default-features = false, features = ["unstable", "orchard"], optional = true } getrandom = { version = "0.2", features = ["js"] } thiserror = "1.0.63" @@ -68,10 +73,11 @@ nonempty = "0.7" hex = "0.4.3" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing = "0.1.40" - +subtle = "2.6.1" [dev-dependencies] wasm-bindgen-test = "0.3.42" +tempfile = "3.12" [patch.crates-io] zip32 = { git = "https://github.com/zcash/zip32.git", branch = "diversifier_index_ord"} diff --git a/examples/simple-sync.rs b/examples/simple-sync.rs new file mode 100644 index 0000000..fb7dcf1 --- /dev/null +++ b/examples/simple-sync.rs @@ -0,0 +1,82 @@ +use std::sync::Once; + +use std::num::NonZeroU32; +use webz_core::Wallet; +use zcash_address::ZcashAddress; +use zcash_primitives::consensus::Network; + +const SEED: &str = "visit armed kite pen cradle toward reward clay marble oil write dove blind oyster silk oyster original message skate bench tone enable stadium element"; +const HD_INDEX: u32 = 0; +const BIRTHDAY: Option = Some(2577329); + +static INIT: Once = Once::new(); +pub fn initialize() { + INIT.call_once(|| { + webz_core::init::start(); + }); +} + +#[cfg(feature = "native")] +#[tokio::main] +async fn main() { + let db_cache = tempfile::tempdir().unwrap(); + let _db_data = tempfile::NamedTempFile::new_in(db_cache.path()).unwrap(); + + initialize(); + let url = "https://testnet.zec.rocks:443"; + let c = tonic::transport::Channel::from_shared(url).unwrap(); + + let tls = tonic::transport::ClientTlsConfig::new() + .domain_name("testnet.zec.rocks") + .with_webpki_roots(); + let channel = c.tls_config(tls).unwrap(); + + #[cfg(feature = "sqlite-db")] + let wallet_db = { + use zcash_client_sqlite::{ + chain::init::init_blockmeta_db, wallet::init::init_wallet_db, FsBlockDb, WalletDb, + }; + + let mut db_cache = FsBlockDb::for_path(&db_cache).unwrap(); + let mut wallet_db = WalletDb::for_path(&_db_data, Network::TestNetwork).unwrap(); + init_blockmeta_db(&mut db_cache).unwrap(); + init_wallet_db(&mut wallet_db, None).unwrap(); + wallet_db + }; + + #[cfg(not(feature = "sqlite-db"))] + let wallet_db = + zcash_client_memory::MemoryWalletDb::new(Network::TestNetwork, webz_core::PRUNING_DEPTH); + + let mut w = Wallet::new( + wallet_db, + channel.connect().await.unwrap(), + Network::TestNetwork, + NonZeroU32::try_from(1).unwrap(), + ) + .unwrap(); + + let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); + tracing::info!("Created account with id: {}", id); + + tracing::info!("Syncing wallet"); + w.sync(|scanned_to, tip| { + println!("Scanned: {}/{}", scanned_to, tip); + }) + .await + .unwrap(); + + tracing::info!("Syncing complete :)"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); + + tracing::info!("Proposing a transaction"); + let addr = ZcashAddress::try_from_encoded("utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu"); + + w.transfer(SEED, 0, addr.unwrap(), 1000).await.unwrap(); + tracing::info!("Transaction proposed"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); +} diff --git a/justfile b/justfile index 7b4d8bc..d328e04 100644 --- a/justfile +++ b/justfile @@ -7,8 +7,11 @@ build: test-web: WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --firefox --no-default-features --features="wasm" -Z build-std="panic_abort,std" -test-native: - cargo test -r -- --nocapture +run-example-native: + cargo run -r --example simple-sync + +run-example-sqlite: + cargo run -r --example simple-sync --features="sqlite-db" check: cargo check diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index 9c1530c..916a58e 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -6,10 +6,11 @@ use wasm_bindgen::prelude::*; use tonic_web_wasm_client::Client; use zcash_address::ZcashAddress; +use zcash_client_memory::MemoryWalletDb; use zcash_primitives::consensus::{self, BlockHeight}; use crate::error::Error; -use crate::{BlockRange, Wallet}; +use crate::{BlockRange, MemoryWallet, Wallet, PRUNING_DEPTH}; /// # A Zcash wallet /// @@ -32,7 +33,7 @@ use crate::{BlockRange, Wallet}; /// #[wasm_bindgen] pub struct WebWallet { - inner: Wallet, + inner: MemoryWallet, } #[wasm_bindgen] @@ -56,7 +57,12 @@ impl WebWallet { let client = Client::new(lightwalletd_url.to_string()); Ok(Self { - inner: Wallet::new(client, network, min_confirmations)?, + inner: Wallet::new( + MemoryWalletDb::new(network, PRUNING_DEPTH), + client, + network, + min_confirmations, + )?, }) } diff --git a/src/error.rs b/src/error.rs index 283a6ef..fb12f12 100644 --- a/src/error.rs +++ b/src/error.rs @@ -44,6 +44,10 @@ pub enum Error { InvalidAmount(#[from] zcash_primitives::transaction::components::amount::BalanceError), #[error("Failed to send transaction")] SendFailed { code: i32, reason: String }, + + #[cfg(feature = "sqlite-db")] + #[error("Sqlite error: {0}")] + SqliteError(#[from] zcash_client_sqlite::error::SqliteClientError), } impl From for JsValue { diff --git a/src/lib.rs b/src/lib.rs index 85be778..2f54314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,13 @@ pub mod wallet; pub use wallet::Wallet; use wasm_bindgen::prelude::*; +use zcash_client_memory::MemoryWalletDb; +use zcash_primitives::consensus; + +/// The maximum number of checkpoints to store in each shard-tree +pub const PRUNING_DEPTH: usize = 100; #[wasm_bindgen] pub struct BlockRange(pub u32, pub u32); + +pub type MemoryWallet = Wallet, T>; diff --git a/src/wallet.rs b/src/wallet.rs index 6a5f4e4..481cf49 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -9,11 +9,16 @@ use tonic::{ codegen::{Body, Bytes, StdError}, }; +use crate::error::Error; +use crate::BlockRange; +use std::fmt::Debug; +use std::hash::Hash; +use subtle::ConditionallySelectable; use zcash_address::ZcashAddress; -use zcash_client_backend::data_api::scanning::ScanRange; use zcash_client_backend::data_api::wallet::{ create_proposed_transactions, input_selection::GreedyInputSelector, propose_transfer, }; +use zcash_client_backend::data_api::{scanning::ScanRange, WalletCommitmentTrees}; use zcash_client_backend::data_api::{ AccountBirthday, AccountPurpose, InputSource, NullifierQuery, WalletRead, WalletSummary, WalletWrite, @@ -34,15 +39,9 @@ use zcash_primitives::transaction::fees::zip317::FeeRule; use zcash_primitives::transaction::TxId; use zcash_proofs::prover::LocalTxProver; -use crate::error::Error; -use crate::BlockRange; -const BATCH_SIZE: u32 = 10000; - -/// The maximum number of checkpoints to store in each shard-tree -const PRUNING_DEPTH: usize = 100; +use zcash_client_backend::proposal::Proposal; -type Proposal = - zcash_client_backend::proposal::Proposal; +const BATCH_SIZE: u32 = 10000; /// # A Zcash wallet /// @@ -63,18 +62,30 @@ type Proposal = /// /// TODO /// - -pub struct Wallet { +pub struct Wallet { /// Internal database used to maintain wallet data (e.g. accounts, transactions, cached blocks) - pub(crate) db: MemoryWalletDb, + pub(crate) db: W, // gRPC client used to connect to a lightwalletd instance for network data pub(crate) client: CompactTxStreamerClient, pub(crate) network: consensus::Network, pub(crate) min_confirmations: NonZeroU32, } -impl Wallet +impl Wallet where + W: WalletRead + + WalletWrite + + InputSource< + AccountId = ::AccountId, + Error = ::Error, + NoteRef = NoteRef, + > + WalletCommitmentTrees, + + AccountId: Copy + Debug + Eq + Hash + Default + Send + ConditionallySelectable + 'static, + NoteRef: Copy + Eq + Ord + Debug, + Error: From<::Error>, + + // GRPC connection Trait Bounds T: GrpcService, T::Error: Into, T::ResponseBody: Body + std::marker::Send + 'static, @@ -82,12 +93,13 @@ where { /// Create a new instance of a Zcash wallet for a given network pub fn new( + db: W, client: T, network: Network, min_confirmations: NonZeroU32, - ) -> Result, Error> { + ) -> Result { Ok(Wallet { - db: MemoryWalletDb::new(network, PRUNING_DEPTH), + db, client: CompactTxStreamerClient::new(client), network, min_confirmations, @@ -257,16 +269,7 @@ where Ok(()) } - pub fn get_wallet_summary( - &self, - ) -> Result< - Option< - WalletSummary< - as WalletRead>::AccountId, - >, - >, - Error, - > { + pub fn get_wallet_summary(&self) -> Result>, Error> { Ok(self.db.get_wallet_summary(self.min_confirmations.into())?) } @@ -296,7 +299,7 @@ where account_index: usize, to_address: ZcashAddress, value: u64, - ) -> Result { + ) -> Result, Error> { let account_id = self.db.get_account_ids()?[account_index]; let input_selector = GreedyInputSelector::new( @@ -317,12 +320,7 @@ where .get_target_and_anchor_heights(self.min_confirmations)? ); - let proposal = propose_transfer::< - _, - _, - _, - as InputSource>::Error, - >( + let proposal = propose_transfer::<_, _, _, ::Error>( &mut self.db, &self.network, account_id, @@ -343,7 +341,7 @@ where /// pub(crate) fn create_proposed_transactions( &mut self, - proposal: Proposal, + proposal: Proposal, usk: &UnifiedSpendingKey, ) -> Result, Error> { let prover = LocalTxProver::bundled(); diff --git a/tests/tests.rs b/tests/tests.rs index 300bb33..610cd67 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,16 +1,14 @@ use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -use webz_core::{bindgen::wallet::WebWallet, Wallet}; -use zcash_address::ZcashAddress; -use zcash_primitives::consensus::Network; +use std::sync::Once; +use webz_core::bindgen::wallet::WebWallet; const SEED: &str = "visit armed kite pen cradle toward reward clay marble oil write dove blind oyster silk oyster original message skate bench tone enable stadium element"; const HD_INDEX: u32 = 0; const BIRTHDAY: Option = Some(2577329); // Required to initialize the logger and panic hooks only once -use std::{num::NonZeroU32, sync::Once}; static INIT: Once = Once::new(); pub fn initialize() { INIT.call_once(|| { @@ -46,46 +44,3 @@ async fn test_get_and_scan_range() { let summary = w.get_wallet_summary().unwrap(); tracing::info!("Wallet summary: {:?}", summary); } - -#[cfg(feature = "native")] -#[tokio::test] -async fn test_get_and_scan_range_native() { - initialize(); - let url = "https://testnet.zec.rocks:443"; - let c = tonic::transport::Channel::from_shared(url).unwrap(); - - let tls = tonic::transport::ClientTlsConfig::new() - .domain_name("testnet.zec.rocks") - .with_webpki_roots(); - let channel = c.tls_config(tls).unwrap(); - let mut w = Wallet::new( - channel.connect().await.unwrap(), - Network::TestNetwork, - NonZeroU32::try_from(1).unwrap(), - ) - .unwrap(); - - let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); - tracing::info!("Created account with id: {}", id); - - tracing::info!("Syncing wallet"); - w.sync(|scanned_to, tip| { - println!("Scanned: {}/{}", scanned_to, tip); - }) - .await - .unwrap(); - - tracing::info!("Syncing complete :)"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); - - tracing::info!("Proposing a transaction"); - let addr = ZcashAddress::try_from_encoded("utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu"); - - w.transfer(SEED, 0, addr.unwrap(), 1000).await.unwrap(); - tracing::info!("Transaction proposed"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); -}