diff --git a/Cargo.lock b/Cargo.lock index 66b3e0b..bc3c60b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.9.0" @@ -529,7 +538,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "blake2b_simd", "byteorder", @@ -554,7 +563,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "blake2b_simd", ] @@ -934,8 +943,7 @@ dependencies = [ [[package]] name = "incrementalmerkletree" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361c467824d4d9d4f284be4b2608800839419dccc4d4608f28345237fe354623" +source = "git+https://github.com/zcash/incrementalmerkletree?rev=e1a7a80212c22e5a8912d05860f7eb6899c56a7c#e1a7a80212c22e5a8912d05860f7eb6899c56a7c" dependencies = [ "either", ] @@ -1045,6 +1053,12 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "log" version = "0.4.20" @@ -1227,9 +1241,8 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d31e68534df32024dcc89a8390ec6d7bef65edd87d91b45cfb481a2eb2d77c5" +version = "0.7.1" +source = "git+https://github.com/zcash/orchard?rev=33474bdbfd7268e1f84718078d47f63d01a879d5#33474bdbfd7268e1f84718078d47f63d01a879d5" dependencies = [ "aes", "bitvec", @@ -1251,6 +1264,8 @@ dependencies = [ "subtle", "tracing", "zcash_note_encryption", + "zcash_spec", + "zip32", ] [[package]] @@ -1709,6 +1724,37 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "sapling-crypto" +version = "0.1.2" +source = "git+https://github.com/zcash/sapling-crypto?rev=22412ae07644813253feb064d1692b0823242853#22412ae07644813253feb064d1692b0823242853" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "byteorder", + "document-features", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "rand", + "rand_core", + "redjubjub", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "schemer" version = "0.2.1" @@ -1841,8 +1887,7 @@ dependencies = [ [[package]] name = "shardtree" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735" +source = "git+https://github.com/zcash/incrementalmerkletree?rev=e1a7a80212c22e5a8912d05860f7eb6899c56a7c#e1a7a80212c22e5a8912d05860f7eb6899c56a7c" dependencies = [ "bitflags 2.4.1", "either", @@ -2608,19 +2653,20 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zcash_address" -version = "0.3.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +version = "0.3.2" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "bech32", "bs58", "f4jumble", "zcash_encoding", + "zcash_protocol", ] [[package]] name = "zcash_client_backend" -version = "0.10.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +version = "0.11.1" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "base64", "bech32", @@ -2628,6 +2674,7 @@ dependencies = [ "bs58", "byteorder", "crossbeam-channel", + "document-features", "group", "hex", "incrementalmerkletree", @@ -2637,7 +2684,9 @@ dependencies = [ "orchard", "percent-encoding", "prost", + "rand_core", "rayon", + "sapling-crypto", "secrecy", "shardtree", "subtle", @@ -2648,44 +2697,83 @@ dependencies = [ "which", "zcash_address", "zcash_encoding", + "zcash_keys", "zcash_note_encryption", "zcash_primitives", + "zcash_protocol", + "zip32", ] [[package]] name = "zcash_client_sqlite" -version = "0.8.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +version = "0.9.1" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "bs58", "byteorder", + "document-features", "group", "incrementalmerkletree", "jubjub", "maybe-rayon", + "nonempty", + "orchard", "prost", "rusqlite", + "sapling-crypto", "schemer", "schemer-rusqlite", "secrecy", "shardtree", + "subtle", "time 0.3.30", "tracing", "uuid", + "zcash_address", "zcash_client_backend", "zcash_encoding", + "zcash_keys", "zcash_primitives", + "zcash_protocol", + "zip32", ] [[package]] name = "zcash_encoding" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "byteorder", "nonempty", ] +[[package]] +name = "zcash_keys" +version = "0.1.1" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" +dependencies = [ + "bech32", + "blake2b_simd", + "bls12_381", + "bs58", + "byteorder", + "document-features", + "group", + "memuse", + "nonempty", + "orchard", + "rand_core", + "sapling-crypto", + "secrecy", + "subtle", + "tracing", + "zcash_address", + "zcash_encoding", + "zcash_primitives", + "zcash_protocol", + "zip32", +] + [[package]] name = "zcash_note_encryption" version = "0.4.0" @@ -2701,17 +2789,14 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +version = "0.14.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "aes", - "bellman", "bip0039", - "bitvec", "blake2b_simd", - "blake2s_simd", - "bls12_381", "byteorder", + "document-features", "equihash", "ff", "fpe", @@ -2719,29 +2804,33 @@ dependencies = [ "hex", "incrementalmerkletree", "jubjub", - "lazy_static", "memuse", "nonempty", "orchard", "rand", "rand_core", "redjubjub", + "sapling-crypto", "sha2", "subtle", "tracing", "zcash_address", "zcash_encoding", "zcash_note_encryption", + "zcash_protocol", + "zcash_spec", + "zip32", ] [[package]] name = "zcash_proofs" -version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=236cd569ee4a824d98920d1a61b8cc2b90ceba1d#236cd569ee4a824d98920d1a61b8cc2b90ceba1d" +version = "0.14.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" dependencies = [ "bellman", "blake2b_simd", "bls12_381", + "document-features", "group", "home", "jubjub", @@ -2749,11 +2838,30 @@ dependencies = [ "lazy_static", "rand_core", "redjubjub", + "sapling-crypto", "tracing", "xdg", "zcash_primitives", ] +[[package]] +name = "zcash_protocol" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=c45d3aed8ae2ac5ab41878df864eb4889793ac1b#c45d3aed8ae2ac5ab41878df864eb4889793ac1b" +dependencies = [ + "document-features", + "memuse", +] + +[[package]] +name = "zcash_spec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "zec-sqlite-cli" version = "0.1.0" @@ -2761,9 +2869,11 @@ dependencies = [ "anyhow", "futures-util", "gumdrop", + "orchard", "prost", "rayon", "rusqlite", + "sapling-crypto", "schemer", "secrecy", "time 0.2.27", @@ -2773,8 +2883,10 @@ dependencies = [ "tracing-subscriber", "zcash_client_backend", "zcash_client_sqlite", + "zcash_keys", "zcash_primitives", "zcash_proofs", + "zcash_protocol", ] [[package]] @@ -2816,3 +2928,14 @@ dependencies = [ "quote", "syn 2.0.38", ] + +[[package]] +name = "zip32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +dependencies = [ + "blake2b_simd", + "memuse", + "subtle", +] diff --git a/Cargo.toml b/Cargo.toml index 5716b37..4f24ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ gumdrop = "0.8" prost = "0.12" rayon = "1.7" rusqlite = { version = "0.29", features = ["time"] } +orchard = { version = "0.7.1", default-features = false } +sapling = { package = "sapling-crypto", version = "0.1.2" } schemer = "0.2" secrecy = "0.8" time = "0.2" @@ -20,13 +22,21 @@ tokio = { version = "1.21.0", features = ["fs", "macros", "rt-multi-thread"] } tonic = { version = "0.10", features = ["gzip", "tls-webpki-roots"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } -zcash_client_backend = { version = "0.10", features = ["lightwalletd-tonic"] } -zcash_client_sqlite = { version = "0.8", features = ["unstable"] } -zcash_primitives = "0.13" -zcash_proofs = "0.13" +zcash_client_backend = { version = "0.11", features = ["lightwalletd-tonic", "orchard"] } +zcash_client_sqlite = { version = "0.9", features = ["unstable", "orchard"] } +zcash_keys = { version = "0.1", features = ["unstable", "orchard"] } +zcash_primitives = "0.14" +zcash_proofs = "0.14" +zcash_protocol = "0.1" [patch.crates-io] -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } -zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } -zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "236cd569ee4a824d98920d1a61b8cc2b90ceba1d" } +zcash_keys = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "c45d3aed8ae2ac5ab41878df864eb4889793ac1b" } +orchard = { git = "https://github.com/zcash/orchard", rev = "33474bdbfd7268e1f84718078d47f63d01a879d5" } +incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree", rev = "e1a7a80212c22e5a8912d05860f7eb6899c56a7c" } +sapling = { git = "https://github.com/zcash/sapling-crypto", package = "sapling-crypto", rev = "22412ae07644813253feb064d1692b0823242853" } +shardtree = { git = "https://github.com/zcash/incrementalmerkletree", rev = "e1a7a80212c22e5a8912d05860f7eb6899c56a7c" } diff --git a/src/commands/balance.rs b/src/commands/balance.rs index 927b576..0d9f0fd 100644 --- a/src/commands/balance.rs +++ b/src/commands/balance.rs @@ -3,7 +3,6 @@ use gumdrop::Options; use zcash_client_backend::data_api::WalletRead; use zcash_client_sqlite::WalletDb; -use zcash_primitives::zip32::AccountId; use crate::{ data::{get_db_paths, get_wallet_network}, @@ -20,18 +19,21 @@ impl Command { pub(crate) fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { let params = get_wallet_network(wallet_dir.as_ref())?; - let account = AccountId::from(0); let (_, db_data) = get_db_paths(wallet_dir); let db_data = WalletDb::for_path(db_data, params)?; + let account_id = *db_data + .get_account_ids()? + .first() + .ok_or(anyhow!("Wallet has no accounts"))?; let address = db_data - .get_current_address(account)? + .get_current_address(account_id)? .ok_or(error::Error::InvalidRecipient)?; if let Some(wallet_summary) = db_data.get_wallet_summary(MIN_CONFIRMATIONS.into())? { let balance = wallet_summary .account_balances() - .get(&account) + .get(&account_id) .ok_or_else(|| anyhow!("Missing account 0"))?; println!("{:#?}", wallet_summary); @@ -45,8 +47,12 @@ impl Command { } println!(" Balance: {}", format_zec(balance.total())); println!( - " Spendable: {}", - format_zec(balance.sapling_balance.spendable_value) + " Sapling Spendable: {}", + format_zec(balance.sapling_balance().spendable_value()) + ); + println!( + " Orchard Spendable: {}", + format_zec(balance.orchard_balance().spendable_value()) ); } else { println!("Insufficient information to build a wallet summary."); diff --git a/src/commands/init.rs b/src/commands/init.rs index 9831e4d..56f58ba 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -11,7 +11,6 @@ use zcash_client_sqlite::{ }; use zcash_primitives::{ consensus::{self, Parameters}, - zip32::AccountId, zip339::{Count, Mnemonic}, }; @@ -102,8 +101,7 @@ impl Command { }; // Add one account. - let (account, _) = db_data.create_account(seed, birthday)?; - assert_eq!(account, AccountId::from(0)); + db_data.create_account(seed, &birthday)?; Ok(()) } diff --git a/src/commands/list_tx.rs b/src/commands/list_tx.rs index 0818697..2361fa4 100644 --- a/src/commands/list_tx.rs +++ b/src/commands/list_tx.rs @@ -112,9 +112,9 @@ impl WalletTx { txid: TxId::from_bytes(txid.try_into().map_err(|_| anyhow!("Invalid TxId"))?), expiry_height: expiry_height.map(BlockHeight::from_u32), account_balance_delta: Amount::from_i64(account_balance_delta) - .map_err(|()| anyhow!("Amount out of range"))?, + .map_err(|_| anyhow!("Amount out of range"))?, fee_paid: fee_paid - .map(|v| NonNegativeAmount::from_u64(v).map_err(|()| anyhow!("Fee out of range"))) + .map(|v| NonNegativeAmount::from_u64(v).map_err(|_| anyhow!("Fee out of range"))) .transpose()?, sent_note_count, received_note_count, diff --git a/src/commands/list_unspent.rs b/src/commands/list_unspent.rs index 958860c..eed6637 100644 --- a/src/commands/list_unspent.rs +++ b/src/commands/list_unspent.rs @@ -1,15 +1,12 @@ use anyhow::anyhow; use gumdrop::Options; -use zcash_client_backend::data_api::{SaplingInputSource, WalletRead}; -use zcash_client_sqlite::WalletDb; -use zcash_primitives::{ - transaction::components::{ - amount::{Amount, MAX_MONEY}, - sapling::fees::InputView, - }, - zip32::AccountId, +use zcash_client_backend::{ + data_api::{InputSource, WalletRead}, + ShieldedProtocol, }; +use zcash_client_sqlite::WalletDb; +use zcash_protocol::value::{Zatoshis, MAX_MONEY}; use crate::{ data::{get_db_paths, get_wallet_network}, @@ -25,9 +22,12 @@ impl Command { pub(crate) fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { let params = get_wallet_network(wallet_dir.as_ref())?; - let account = AccountId::from(0); let (_, db_data) = get_db_paths(wallet_dir); let db_data = WalletDb::for_path(db_data, params)?; + let account = *db_data + .get_account_ids()? + .first() + .ok_or(anyhow!("Wallet has no accounts"))?; // Use the height of the maximum scanned block as the anchor height, to emulate a // zero-conf transaction in order to select every note in the wallet. @@ -37,15 +37,28 @@ impl Command { .map_err(|e| anyhow!("{:?}", e))? .block_height(); - let notes = db_data.select_spendable_sapling_notes( + let notes = db_data.select_spendable_notes( account, - Amount::const_from_i64(MAX_MONEY), + Zatoshis::const_from_u64(MAX_MONEY), + &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard], anchor_height, &[], )?; - for note in notes { - println!("{}: {}", note.note_id(), format_zec(note.value())); + for note in notes.sapling() { + println!( + "Sapling {}: {}", + note.internal_note_id(), + format_zec(note.note_value()?) + ); + } + + for note in notes.orchard() { + println!( + "Orchard {}: {}", + note.internal_note_id(), + format_zec(note.note_value()?) + ); } Ok(()) diff --git a/src/commands/propose.rs b/src/commands/propose.rs index 78eac2c..0b7b771 100644 --- a/src/commands/propose.rs +++ b/src/commands/propose.rs @@ -1,16 +1,18 @@ +use anyhow::anyhow; use gumdrop::Options; use zcash_client_backend::{ - address::RecipientAddress, - data_api::wallet::{input_selection::GreedyInputSelector, propose_transfer}, + data_api::{ + wallet::{input_selection::GreedyInputSelector, propose_transfer}, + WalletRead, + }, fees::standard::SingleOutputChangeStrategy, zip321::{Payment, TransactionRequest}, + ShieldedProtocol, }; use zcash_client_sqlite::WalletDb; -use zcash_primitives::{ - transaction::{components::amount::NonNegativeAmount, fees::StandardFeeRule}, - zip32::AccountId, -}; +use zcash_keys::address::Address; +use zcash_primitives::transaction::{components::amount::NonNegativeAmount, fees::StandardFeeRule}; use crate::{ data::{get_db_paths, get_wallet_network}, @@ -71,17 +73,20 @@ impl Command { pub(crate) async fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { let params = get_wallet_network(wallet_dir.as_ref())?; - let account = AccountId::from(0); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let mut db_data = WalletDb::for_path(db_data, params)?; + let account = *db_data + .get_account_ids()? + .first() + .ok_or_else(|| anyhow!("Wallet has no accounts."))?; let input_selector = GreedyInputSelector::new( - SingleOutputChangeStrategy::new(self.fee_rule.into(), None), + SingleOutputChangeStrategy::new(self.fee_rule.into(), None, ShieldedProtocol::Orchard), Default::default(), ); let request = TransactionRequest::new(vec![Payment { - recipient_address: RecipientAddress::decode(¶ms, &self.address) + recipient_address: Address::decode(¶ms, &self.address) .ok_or(error::Error::InvalidRecipient)?, amount: NonNegativeAmount::from_u64(self.value) .map_err(|_| error::Error::InvalidAmount)?, diff --git a/src/commands/send.rs b/src/commands/send.rs index 62ac041..2223b79 100644 --- a/src/commands/send.rs +++ b/src/commands/send.rs @@ -1,21 +1,24 @@ +#![allow(deprecated)] +use anyhow::anyhow; use gumdrop::Options; use secrecy::ExposeSecret; use zcash_client_backend::{ - address::RecipientAddress, data_api::{ wallet::{input_selection::GreedyInputSelector, spend}, - WalletRead, + Account, AccountSource, WalletRead, }, fees::standard::SingleOutputChangeStrategy, keys::UnifiedSpendingKey, proto::service, wallet::OvkPolicy, zip321::{Payment, TransactionRequest}, + ShieldedProtocol, }; use zcash_client_sqlite::WalletDb; -use zcash_primitives::{transaction::components::amount::NonNegativeAmount, zip32::AccountId}; +use zcash_keys::address::Address; use zcash_proofs::prover::LocalTxProver; +use zcash_protocol::value::Zatoshis; use crate::{ commands::propose::{parse_fee_rule, FeeRule}, @@ -28,7 +31,10 @@ use crate::{ // Options accepted for the `send` command #[derive(Debug, Options)] pub(crate) struct Command { - #[options(required, help = "the recipient's Sapling or transparent address")] + #[options( + required, + help = "the recipient's Unified, Sapling or transparent address" + )] address: String, #[options(required, help = "the amount in zatoshis")] @@ -47,12 +53,23 @@ impl Command { let keys = read_keys(wallet_dir.as_ref())?; let params = keys.network(); - let account = AccountId::from(0); let (_, db_data) = get_db_paths(wallet_dir); let mut db_data = WalletDb::for_path(db_data, params)?; + let account_id = *db_data + .get_account_ids()? + .first() + .ok_or(anyhow!("Wallet has no accounts"))?; + let account = db_data + .get_account(account_id)? + .ok_or(anyhow!("Account missing: {:?}", account_id))?; + let account_index = match account.source() { + AccountSource::Derived { account_index, .. } => account_index, + AccountSource::Imported => unreachable!("Imported accounts are not yet supported."), + }; - let usk = UnifiedSpendingKey::from_seed(¶ms, keys.seed().expose_secret(), account) - .map_err(error::Error::from)?; + let usk = + UnifiedSpendingKey::from_seed(¶ms, keys.seed().expose_secret(), account_index) + .map_err(error::Error::from)?; let mut client = connect_to_lightwalletd(¶ms).await?; @@ -61,15 +78,14 @@ impl Command { let prover = LocalTxProver::with_default_location().ok_or(error::Error::MissingParameters)?; let input_selector = GreedyInputSelector::new( - SingleOutputChangeStrategy::new(self.fee_rule.into(), None), + SingleOutputChangeStrategy::new(self.fee_rule.into(), None, ShieldedProtocol::Orchard), Default::default(), ); let request = TransactionRequest::new(vec![Payment { - recipient_address: RecipientAddress::decode(¶ms, &self.address) + recipient_address: Address::decode(¶ms, &self.address) .ok_or(error::Error::InvalidRecipient)?, - amount: NonNegativeAmount::from_u64(self.value) - .map_err(|_| error::Error::InvalidAmount)?, + amount: Zatoshis::from_u64(self.value).map_err(|_| error::Error::InvalidAmount)?, memo: None, label: None, message: None, @@ -77,7 +93,7 @@ impl Command { }]) .map_err(error::Error::from)?; - let id_tx = spend( + let txids = spend( &mut db_data, ¶ms, &prover, @@ -90,13 +106,24 @@ impl Command { ) .map_err(error::Error::from)?; + if txids.len() > 1 { + return Err(anyhow!( + "Multi-transaction proposals are not yet supported." + )); + } + + let txid = *txids.first(); + // Send the transaction. println!("Sending transaction..."); - let (txid, raw_tx) = db_data.get_transaction(id_tx).map(|tx| { - let mut raw_tx = service::RawTransaction::default(); - tx.write(&mut raw_tx.data).unwrap(); - (tx.txid(), raw_tx) - })?; + let (txid, raw_tx) = db_data + .get_transaction(txid)? + .map(|tx| { + let mut raw_tx = service::RawTransaction::default(); + tx.write(&mut raw_tx.data).unwrap(); + (tx.txid(), raw_tx) + }) + .ok_or(anyhow!("Transaction not found for id {:?}", txid))?; let response = client.send_transaction(raw_tx).await?.into_inner(); if response.error_code != 0 { diff --git a/src/commands/sync.rs b/src/commands/sync.rs index 97781b4..b03f32a 100644 --- a/src/commands/sync.rs +++ b/src/commands/sync.rs @@ -3,6 +3,7 @@ use std::path::Path; use anyhow::anyhow; use futures_util::TryStreamExt; use gumdrop::Options; +use orchard::tree::MerkleHashOrchard; use prost::Message; use tokio::{fs::File, io::AsyncWriteExt, task::JoinHandle}; @@ -10,18 +11,18 @@ use tonic::transport::Channel; use tracing::{debug, error, info}; use zcash_client_backend::{ data_api::{ - chain::{error::Error as ChainError, scan_cached_blocks, BlockSource, CommitmentTreeRoot}, + chain::{ + error::Error as ChainError, scan_cached_blocks, BlockSource, ChainState, + CommitmentTreeRoot, + }, scanning::{ScanPriority, ScanRange}, WalletCommitmentTrees, WalletRead, WalletWrite, }, - proto::service::{self, compact_tx_streamer_client::CompactTxStreamerClient}, + proto::service::{self, compact_tx_streamer_client::CompactTxStreamerClient, BlockId}, }; use zcash_client_sqlite::{chain::BlockMeta, FsBlockDb, FsBlockDbError, WalletDb}; -use zcash_primitives::{ - consensus::{BlockHeight, Parameters}, - merkle_tree::HashSer, - sapling, -}; +use zcash_primitives::merkle_tree::HashSer; +use zcash_protocol::consensus::{BlockHeight, Parameters}; use crate::{ data::{get_block_path, get_db_paths, get_wallet_network}, @@ -79,11 +80,21 @@ impl Command { let block_meta = download_blocks(client, fsblockdb_root, db_cache, scan_range).await?; + let chain_state = + download_chain_state(client, scan_range.block_range().start - 1) + .await?; + // Scan the downloaded blocks and check for scanning errors that // indicate the wallet's chain tip is out of sync with blockchain // history. - let scan_ranges_updated = - scan_blocks(params, fsblockdb_root, db_cache, db_data, scan_range)?; + let scan_ranges_updated = scan_blocks( + params, + fsblockdb_root, + db_cache, + db_data, + &chain_state, + scan_range, + )?; // Delete the now-scanned blocks, because keeping the entire chain // in CompactBlock files on disk is horrendous for the filesystem. @@ -134,9 +145,18 @@ impl Command { let block_meta = download_blocks(client, fsblockdb_root, db_cache, &scan_range).await?; + let chain_state = + download_chain_state(client, scan_range.block_range().start - 1).await?; + // Scan the downloaded blocks. - let scan_ranges_updated = - scan_blocks(params, fsblockdb_root, db_cache, db_data, &scan_range)?; + let scan_ranges_updated = scan_blocks( + params, + fsblockdb_root, + db_cache, + db_data, + &chain_state, + &scan_range, + )?; // Delete the now-scanned blocks. block_deletions.push(delete_cached_blocks(fsblockdb_root, block_meta)); @@ -181,8 +201,7 @@ async fn update_subtree_roots( request.set_shielded_protocol(service::ShieldedProtocol::Sapling); // Hack to work around a bug in the initial lightwalletd implementation. request.max_entries = 65536; - - let roots: Vec> = client + let sapling_roots: Vec> = client .get_subtree_roots(request) .await? .into_inner() @@ -196,8 +215,29 @@ async fn update_subtree_roots( .try_collect() .await?; - info!("Sapling tree has {} subtrees", roots.len()); - db_data.put_sapling_subtree_roots(0, &roots)?; + info!("Sapling tree has {} subtrees", sapling_roots.len()); + db_data.put_sapling_subtree_roots(0, &sapling_roots)?; + + let mut request = service::GetSubtreeRootsArg::default(); + request.set_shielded_protocol(service::ShieldedProtocol::Orchard); + // Hack to work around a bug in the initial lightwalletd implementation. + request.max_entries = 65536; + let orchard_roots: Vec> = client + .get_subtree_roots(request) + .await? + .into_inner() + .and_then(|root| async move { + let root_hash = MerkleHashOrchard::read(&root.root_hash[..])?; + Ok(CommitmentTreeRoot::from_parts( + BlockHeight::from_u32(root.completing_block_height as u32), + root_hash, + )) + }) + .try_collect() + .await?; + + info!("Orchard tree has {} subtrees", orchard_roots.len()); + db_data.put_orchard_subtree_roots(0, &orchard_roots)?; Ok(()) } @@ -274,6 +314,20 @@ async fn download_blocks( Ok(block_meta) } +async fn download_chain_state( + client: &mut CompactTxStreamerClient, + block_height: BlockHeight, +) -> Result { + let tree_state = client + .get_tree_state(BlockId { + height: block_height.into(), + hash: vec![], + }) + .await?; + + Ok(tree_state.into_inner().to_chain_state()?) +} + fn delete_cached_blocks(fsblockdb_root: &Path, block_meta: Vec) -> JoinHandle<()> { let fsblockdb_root = fsblockdb_root.to_owned(); tokio::spawn(async move { @@ -294,6 +348,7 @@ fn scan_blocks( fsblockdb_root: &Path, db_cache: &mut FsBlockDb, db_data: &mut WalletDb, + initial_chain_state: &ChainState, scan_range: &ScanRange, ) -> Result { info!("Scanning {}", scan_range); @@ -302,6 +357,7 @@ fn scan_blocks( db_cache, db_data, scan_range.block_range().start, + initial_chain_state, scan_range.len(), ); diff --git a/src/ui.rs b/src/ui.rs index acb04dd..e591e72 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,14 @@ -use zcash_primitives::transaction::components::Amount; +use zcash_protocol::value::ZatBalance; const COIN: u64 = 1_0000_0000; -pub(crate) fn format_zec(value: impl Into) -> String { - let value = i64::from(value.into()); +pub(crate) fn format_zec(value: impl TryInto) -> String { + let value = i64::from( + value + .try_into() + .map_err(|_| ()) + .expect("Values are formattable"), + ); let abs_value = value.unsigned_abs(); let abs_zec = abs_value / COIN; let frac = abs_value % COIN;