diff --git a/src/commands/create_multisig_address.rs b/src/commands/create_multisig_address.rs index 98ef86d..d2ad3d7 100644 --- a/src/commands/create_multisig_address.rs +++ b/src/commands/create_multisig_address.rs @@ -15,7 +15,7 @@ use sha2::{Digest, Sha256}; use transparent::address::TransparentAddress; use zcash_keys::encoding::AddressCodec; -use zcash_protocol::consensus::Network; +use zcash_protocol::{consensus, local_consensus::LocalNetwork, consensus::BlockHeight}; use zcash_script::{ pattern::check_multisig, script::{self, Evaluable}, @@ -54,7 +54,25 @@ impl Command { } = self; let (multisig_script, addr) = multisig_script(threshold, pub_keys)?; - let addr = addr.encode(&Network::from(network)); + let params = match network { + crate::data::Network::Main => crate::data::NetworkParams::Consensus(consensus::Network::MainNetwork), + crate::data::Network::Test => crate::data::NetworkParams::Consensus(consensus::Network::TestNetwork), + crate::data::Network::Regtest => { + // Create a LocalNetwork with all upgrades at height 1 + let height_1 = Some(BlockHeight::from_u32(1)); + crate::data::NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) + } + }; + let addr = addr.encode(¶ms); println!("Created multisig address: {addr}"); println!("Redeem script: {}", hex::encode(multisig_script.to_bytes())); diff --git a/src/commands/inspect/block.rs b/src/commands/inspect/block.rs index e9a26d8..a65bbbe 100644 --- a/src/commands/inspect/block.rs +++ b/src/commands/inspect/block.rs @@ -75,6 +75,26 @@ impl BlockParams for Network { } } +impl BlockParams for zcash_protocol::local_consensus::LocalNetwork { + // Equihash parameters for Regtest obtained from + // https://github.com/zcash/zcash/blob/16ac743764a513e41dafb2cd79c2417c5bb41e81/src/chainparams.cpp#L795-L799 + fn equihash_n(&self) -> u32 { + 48 + } + + fn equihash_k(&self) -> u32 { + 5 + } + + fn pow_limit(&self) -> U256 { + // Regtest uses same PoW limit as testnet (easier than mainnet) + U256::from_big_endian( + &hex::decode("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f") + .unwrap(), + ) + } +} + pub(crate) fn guess_params(header: &BlockHeader) -> Option { // If the block target falls between the testnet and mainnet powLimit, assume testnet. let (target, is_negative, did_overflow) = U256::from_compact(header.bits); diff --git a/src/commands/keystone.rs b/src/commands/keystone.rs index 65cfcc7..4021106 100644 --- a/src/commands/keystone.rs +++ b/src/commands/keystone.rs @@ -45,7 +45,7 @@ impl Enroll { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; let key_derivation = account diff --git a/src/commands/pczt/create.rs b/src/commands/pczt/create.rs index d3290e7..8a84852 100644 --- a/src/commands/pczt/create.rs +++ b/src/commands/pczt/create.rs @@ -64,7 +64,7 @@ impl Command { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; // Create the PCZT. diff --git a/src/commands/pczt/create_manual.rs b/src/commands/pczt/create_manual.rs index 10ff282..3d4f617 100644 --- a/src/commands/pczt/create_manual.rs +++ b/src/commands/pczt/create_manual.rs @@ -21,7 +21,8 @@ use zcash_primitives::transaction::{ Transaction, }; use zcash_protocol::{ - consensus::{self, Parameters}, + consensus::{self, BlockHeight, Parameters}, + local_consensus::LocalNetwork, memo::{Memo, MemoBytes}, value::Zatoshis, }; @@ -29,7 +30,7 @@ use zcash_script::script; use crate::{ config::WalletConfig, - data::Network, + data::{Network, NetworkParams}, error, remote::{tor_client, Servers}, }; @@ -144,7 +145,23 @@ enum SpendInfo { impl Command { pub(crate) async fn run(self, wallet_dir: Option) -> anyhow::Result<()> { let params = if let Some(network) = self.network { - consensus::Network::from(network) + match network { + Network::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + Network::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), + Network::Regtest => { + let height_1 = Some(BlockHeight::from_u32(1)); + NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) + } + } } else { let config = WalletConfig::read(wallet_dir.as_ref())?; config.network() @@ -166,7 +183,7 @@ impl Command { .transpose()? .map(MemoBytes::from); - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { @@ -308,7 +325,7 @@ impl Command { memo: Option| -> anyhow::Result<_> { let mut builder = Builder::new( - params, + params.clone(), target_height, zcash_primitives::transaction::builder::BuildConfig::Standard { sapling_anchor, diff --git a/src/commands/pczt/send.rs b/src/commands/pczt/send.rs index 603d0b3..f4ff033 100644 --- a/src/commands/pczt/send.rs +++ b/src/commands/pczt/send.rs @@ -36,9 +36,9 @@ impl Command { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/pczt/send_without_storing.rs b/src/commands/pczt/send_without_storing.rs index 3f994de..783ca0d 100644 --- a/src/commands/pczt/send_without_storing.rs +++ b/src/commands/pczt/send_without_storing.rs @@ -32,7 +32,7 @@ impl Command { let config = WalletConfig::read(wallet_dir.as_ref())?; let params = config.network(); - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/pczt/shield.rs b/src/commands/pczt/shield.rs index d24510a..2d81569 100644 --- a/src/commands/pczt/shield.rs +++ b/src/commands/pczt/shield.rs @@ -51,7 +51,7 @@ impl Command { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; let addresses = self diff --git a/src/commands/wallet/balance.rs b/src/commands/wallet/balance.rs index aafb398..3c07557 100644 --- a/src/commands/wallet/balance.rs +++ b/src/commands/wallet/balance.rs @@ -34,7 +34,7 @@ impl Command { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let db_data = WalletDb::for_path(db_data, params, (), ())?; + let db_data = WalletDb::for_path(db_data, params.clone(), (), ())?; let account = select_account(&db_data, self.account_id)?; let address = db_data diff --git a/src/commands/wallet/enhance.rs b/src/commands/wallet/enhance.rs index dea9cae..820377e 100644 --- a/src/commands/wallet/enhance.rs +++ b/src/commands/wallet/enhance.rs @@ -18,11 +18,11 @@ use zcash_client_backend::{ use zcash_client_sqlite::{util::SystemClock, WalletDb}; use zcash_keys::encoding::AddressCodec; use zcash_primitives::transaction::{Transaction, TxId}; -use zcash_protocol::consensus::{BlockHeight, BranchId, Network}; +use zcash_protocol::consensus::{BlockHeight, BranchId}; use crate::{ config::get_wallet_network, - data::get_db_paths, + data::{get_db_paths, NetworkParams}, remote::{tor_client, Servers}, }; @@ -40,7 +40,7 @@ pub(crate) struct Command { } fn parse_raw_transaction( - params: &Network, + params: &NetworkParams, chain_tip: BlockHeight, tx: RawTransaction, ) -> Result<(Transaction, Option), anyhow::Error> { @@ -58,7 +58,7 @@ fn parse_raw_transaction( async fn fetch_transaction( client: &mut CompactTxStreamerClient, - params: &Network, + params: &NetworkParams, chain_tip: BlockHeight, txid: TxId, ) -> Result)>, anyhow::Error> { @@ -88,7 +88,7 @@ impl Command { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let chain_tip = db_data.chain_height()?.ok_or_else(|| { anyhow!("Chain height must be available to perform transaction enhancement.") })?; @@ -98,7 +98,7 @@ impl Command { // - Create an isolated `lightwalletd` connection for each transaction. // - Spread transactions across all available servers. // - Fetch transactions in parallel, with timing noise. - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/wallet/gen_account.rs b/src/commands/wallet/gen_account.rs index 318231b..07be81f 100644 --- a/src/commands/wallet/gen_account.rs +++ b/src/commands/wallet/gen_account.rs @@ -38,7 +38,7 @@ impl Command { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; // Decrypt the mnemonic to access the seed. let identities = age::IdentityFile::from_file(self.identity)?.into_identities()?; @@ -48,7 +48,7 @@ impl Command { "Seed must be present to enable generating a new account" ))?; - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/wallet/gen_addr.rs b/src/commands/wallet/gen_addr.rs index f6d570d..5ce6d47 100644 --- a/src/commands/wallet/gen_addr.rs +++ b/src/commands/wallet/gen_addr.rs @@ -30,7 +30,7 @@ impl Command { pub(crate) fn run(self, wallet_dir: Option) -> anyhow::Result<()> { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; diff --git a/src/commands/wallet/import_ufvk.rs b/src/commands/wallet/import_ufvk.rs index f015a8b..483f8cb 100644 --- a/src/commands/wallet/import_ufvk.rs +++ b/src/commands/wallet/import_ufvk.rs @@ -9,11 +9,11 @@ use zcash_client_backend::{ }; use zcash_client_sqlite::{util::SystemClock, WalletDb}; use zcash_keys::keys::UnifiedFullViewingKey; -use zcash_protocol::consensus; +use zcash_protocol::{consensus::{self, BlockHeight}, local_consensus::LocalNetwork}; use zip32::fingerprint::SeedFingerprint; use crate::{ - data::get_db_paths, + data::{get_db_paths, NetworkParams}, error, parse_hex, remote::{tor_client, Servers}, }; @@ -56,21 +56,32 @@ impl Command { let ufvk = UnifiedFullViewingKey::parse(&ufvk).map_err(|e| anyhow!("{e}"))?; let params = match network { - consensus::NetworkType::Main => Ok(consensus::Network::MainNetwork), - consensus::NetworkType::Test => Ok(consensus::Network::TestNetwork), + consensus::NetworkType::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + consensus::NetworkType::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), consensus::NetworkType::Regtest => { - Err(anyhow!("UFVK is for regtest, which is unsupported")) + // Create a LocalNetwork with all upgrades at height 1 + let height_1 = Some(BlockHeight::from_u32(1)); + NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) } - }?; + }; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; // Construct an `AccountBirthday` for the account's birthday. let birthday = { // Fetch the tree state corresponding to the last block prior to the wallet's // birthday height. NOTE: THIS APPROACH LEAKS THE BIRTHDAY TO THE SERVER! - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/wallet/init.rs b/src/commands/wallet/init.rs index 1fa6712..a785448 100644 --- a/src/commands/wallet/init.rs +++ b/src/commands/wallet/init.rs @@ -9,11 +9,11 @@ use zcash_client_backend::{ data_api::{AccountBirthday, WalletWrite}, proto::service::{self, compact_tx_streamer_client::CompactTxStreamerClient}, }; -use zcash_protocol::consensus::{self, BlockHeight, Parameters}; +use zcash_protocol::{consensus::{self, BlockHeight, Parameters}, local_consensus::LocalNetwork}; use crate::{ config::WalletConfig, - data::{init_dbs, Network}, + data::{init_dbs, Network, NetworkParams}, error, remote::{tor_client, Servers}, }; @@ -51,9 +51,26 @@ pub(crate) struct Command { impl Command { pub(crate) async fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { let opts = self; - let params = consensus::Network::from(opts.network); + let params = match opts.network { + Network::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + Network::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), + Network::Regtest => { + // Create a LocalNetwork with all upgrades at height 1 + let height_1 = Some(BlockHeight::from_u32(1)); + NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) + } + }; - let server = opts.server.pick(params)?; + let server = opts.server.pick(¶ms)?; let mut client = if opts.disable_tor { server.connect_direct().await? } else { @@ -123,7 +140,7 @@ impl Command { recipients.iter().map(|r| r.as_ref() as _), &mnemonic, birthday.height(), - opts.network.into(), + ¶ms, )?; let seed = { diff --git a/src/commands/wallet/init_fvk.rs b/src/commands/wallet/init_fvk.rs index 8e064a6..8e40443 100644 --- a/src/commands/wallet/init_fvk.rs +++ b/src/commands/wallet/init_fvk.rs @@ -7,12 +7,12 @@ use zcash_client_backend::{ proto::service, }; use zcash_keys::{encoding::decode_extfvk_with_network, keys::UnifiedFullViewingKey}; -use zcash_protocol::consensus::{self, NetworkType}; +use zcash_protocol::{consensus::{self, BlockHeight, NetworkType}, local_consensus::LocalNetwork}; use zip32::fingerprint::SeedFingerprint; use crate::{ config::WalletConfig, - data::init_dbs, + data::{init_dbs, NetworkParams}, parse_hex, remote::{tor_client, Servers}, }; @@ -72,14 +72,25 @@ impl Command { )?; let network = match network_type { - NetworkType::Main => consensus::Network::MainNetwork, - NetworkType::Test => consensus::Network::TestNetwork, + NetworkType::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + NetworkType::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), NetworkType::Regtest => { - return Err(anyhow!("the regtest network is not supported")); + // Create a LocalNetwork with all upgrades at height 1 + let height_1 = Some(BlockHeight::from_u32(1)); + NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) } }; - let server = opts.server.pick(network)?; + let server = opts.server.pick(&network)?; let mut client = if opts.disable_tor { server.connect_direct().await? } else { @@ -121,9 +132,9 @@ impl Command { }?; // Save the wallet config to disk. - WalletConfig::init_without_mnemonic(wallet_dir.as_ref(), birthday.height(), network)?; + WalletConfig::init_without_mnemonic(wallet_dir.as_ref(), birthday.height(), &network)?; - let mut wallet_db = init_dbs(network, wallet_dir.as_ref())?; + let mut wallet_db = init_dbs(network.clone(), wallet_dir.as_ref())?; wallet_db.import_account_ufvk(&opts.name, &ufvk, &birthday, purpose, None)?; Ok(()) diff --git a/src/commands/wallet/list_accounts.rs b/src/commands/wallet/list_accounts.rs index 45978b9..5b7b04d 100644 --- a/src/commands/wallet/list_accounts.rs +++ b/src/commands/wallet/list_accounts.rs @@ -12,7 +12,7 @@ impl Command { pub(crate) fn run(self, wallet_dir: Option) -> anyhow::Result<()> { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let db_data = WalletDb::for_path(db_data, params, (), ())?; + let db_data = WalletDb::for_path(db_data, params.clone(), (), ())?; for account_id in db_data.get_account_ids()?.iter() { let account = db_data.get_account(*account_id)?.unwrap(); diff --git a/src/commands/wallet/list_addresses.rs b/src/commands/wallet/list_addresses.rs index 6faa4b0..cea92e6 100644 --- a/src/commands/wallet/list_addresses.rs +++ b/src/commands/wallet/list_addresses.rs @@ -25,7 +25,7 @@ impl Command { pub(crate) fn run(self, wallet_dir: Option) -> anyhow::Result<()> { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let db_data = WalletDb::for_path(db_data, params, (), ())?; + let db_data = WalletDb::for_path(db_data, params.clone(), (), ())?; let account = select_account(&db_data, self.account_id)?; diff --git a/src/commands/wallet/propose.rs b/src/commands/wallet/propose.rs index 6d2a8e7..4c065bf 100644 --- a/src/commands/wallet/propose.rs +++ b/src/commands/wallet/propose.rs @@ -48,7 +48,7 @@ impl Command { let params = get_wallet_network(wallet_dir.as_ref())?; let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; let change_strategy = MultiOutputChangeStrategy::new( diff --git a/src/commands/wallet/reset.rs b/src/commands/wallet/reset.rs index 0b53405..f5e2a8c 100644 --- a/src/commands/wallet/reset.rs +++ b/src/commands/wallet/reset.rs @@ -37,7 +37,7 @@ impl Command { let params = config.network(); // Connect to the client (for re-initializing the wallet). - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { @@ -56,7 +56,7 @@ impl Command { // Get the account name and key source to preserve them. let (account_name, key_source) = { let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account_id = *db_data .get_account_ids()? diff --git a/src/commands/wallet/send.rs b/src/commands/wallet/send.rs index 66de878..e58a6ef 100644 --- a/src/commands/wallet/send.rs +++ b/src/commands/wallet/send.rs @@ -153,7 +153,7 @@ pub(crate) async fn pay( let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, context.spending_account())?; let derivation = account .source() @@ -170,7 +170,7 @@ pub(crate) async fn pay( UnifiedSpendingKey::from_seed(¶ms, seed.expose_secret(), derivation.account_index()) .map_err(error::Error::from)?; - let server = context.servers().pick(params)?; + let server = context.servers().pick(¶ms)?; let mut client = if context.disable_tor() { server.connect_direct().await? } else { diff --git a/src/commands/wallet/shield.rs b/src/commands/wallet/shield.rs index 812ab40..9649e84 100644 --- a/src/commands/wallet/shield.rs +++ b/src/commands/wallet/shield.rs @@ -73,7 +73,7 @@ impl Command { let params = config.network(); let (_, db_data) = get_db_paths(wallet_dir.as_ref()); - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; let account = select_account(&db_data, self.account_id)?; let derivation = account.source().key_derivation().ok_or(anyhow!( "Cannot spend from view-only accounts; did you mean to use `pczt shield` instead?" @@ -98,7 +98,7 @@ impl Command { ) .map_err(error::Error::from)?; - let server = self.server.pick(params)?; + let server = self.server.pick(¶ms)?; let mut client = if self.disable_tor { server.connect_direct().await? } else { diff --git a/src/commands/wallet/sync.rs b/src/commands/wallet/sync.rs index 696ca18..a1f42e9 100644 --- a/src/commands/wallet/sync.rs +++ b/src/commands/wallet/sync.rs @@ -25,7 +25,7 @@ use zcash_client_sqlite::{ chain::BlockMeta, util::SystemClock, FsBlockDb, FsBlockDbError, WalletDb, }; use zcash_primitives::merkle_tree::HashSer; -use zcash_protocol::consensus::{BlockHeight, Parameters}; +use zcash_protocol::consensus::{BlockHeight, NetworkUpgrade, Parameters}; use crate::{ config::get_wallet_network, @@ -87,8 +87,8 @@ impl Command { let (fsblockdb_root, db_data) = get_db_paths(wallet_dir.as_ref()); let fsblockdb_root = fsblockdb_root.as_path(); let mut db_cache = FsBlockDb::for_path(fsblockdb_root).map_err(error::Error::from)?; - let mut db_data = WalletDb::for_path(db_data, params, SystemClock, OsRng)?; - let mut client = self.server.pick(params)?.connect_direct().await?; + let mut db_data = WalletDb::for_path(db_data, params.clone(), SystemClock, OsRng)?; + let mut client = self.server.pick(¶ms)?.connect_direct().await?; #[cfg(feature = "tui")] let wallet_birthday = db_data @@ -109,8 +109,12 @@ impl Command { None }; - // 1) Download note commitment tree data from lightwalletd - // 2) Pass the commitment tree data to the database. + // 1) Download chain tip metadata from lightwalletd + // 2) Notify the wallet of the updated chain tip. + update_chain_tip(&mut client, &mut db_data).await?; + + // 3) Download note commitment tree data from lightwalletd + // 4) Pass the commitment tree data to the database. update_subtree_roots(&mut client, &mut db_data).await?; #[allow(clippy::too_many_arguments)] @@ -123,8 +127,7 @@ impl Command { db_data: &mut WalletDb, #[cfg(feature = "tui")] tui_handle: Option<&defrag::AppHandle>, ) -> Result { - // 3) Download chain tip metadata from lightwalletd - // 4) Notify the wallet of the updated chain tip. + // 5) Update chain tip metadata from lightwalletd for this sync iteration. let _chain_tip = update_chain_tip(client, db_data).await?; #[cfg(feature = "tui")] if let Some(handle) = tui_handle { @@ -133,21 +136,29 @@ impl Command { ); } - // Refresh UTXOs for the accounts in the wallet. - #[cfg(feature = "transparent-inputs")] - for account_id in db_data.get_account_ids()? { - info!( - "Refreshing UTXOs for {:?} from height {}", - account_id, - BlockHeight::from(0), - ); - refresh_utxos(params, client, db_data, account_id, BlockHeight::from(0)).await?; - } - - // 5) Get the suggested scan ranges from the wallet database + // 6) Get the suggested scan ranges from the wallet database info!("Fetching scan ranges"); let mut scan_ranges = db_data.suggest_scan_ranges()?; info!("Fetched {} scan ranges", scan_ranges.len()); + + // Refresh UTXOs for the accounts in the wallet. + // Only do this if Sapling is activated, as update_chain_tip requires this + // to properly set the chain tip in the scan_queue table. + #[cfg(feature = "transparent-inputs")] + if let Some(sapling_activation) = params.activation_height(NetworkUpgrade::Sapling) { + if _chain_tip >= sapling_activation { + for account_id in db_data.get_account_ids()? { + info!( + "Refreshing UTXOs for {:?} from height {}", + account_id, + BlockHeight::from(0), + ); + refresh_utxos(params, client, db_data, account_id, BlockHeight::from(0)).await?; + } + } else { + info!("Skipping UTXO refresh: chain tip {} is before Sapling activation at {}", _chain_tip, sapling_activation); + } + } #[cfg(feature = "tui")] if let Some(handle) = tui_handle { if handle.set_scan_ranges(&scan_ranges, _chain_tip) { @@ -163,7 +174,7 @@ impl Command { // tasks to allow us to continue downloading and scanning other ranges). let mut block_deletions = vec![]; - // 6) Run the following loop until the wallet's view of the chain tip as of + // 7) Run the following loop until the wallet's view of the chain tip as of // the previous wallet session is valid. loop { // If there is a range of blocks that needs to be verified, it will always @@ -240,7 +251,7 @@ impl Command { } } - // 7) Loop over the remaining suggested scan ranges, retrieving the requested data + // 8) Loop over the remaining suggested scan ranges, retrieving the requested data // and calling `scan_cached_blocks` on each range. let scan_ranges = db_data.suggest_scan_ranges()?; debug!("Suggested ranges: {:?}", scan_ranges); diff --git a/src/commands/wallet/tree/explore.rs b/src/commands/wallet/tree/explore.rs index 5dfe8f7..bccabad 100644 --- a/src/commands/wallet/tree/explore.rs +++ b/src/commands/wallet/tree/explore.rs @@ -23,13 +23,13 @@ use zcash_client_backend::data_api::{WalletCommitmentTrees, WalletRead}; use zcash_client_sqlite::{wallet::commitment_tree::SqliteShardStore, WalletDb}; use zcash_primitives::merkle_tree::HashSer; use zcash_protocol::{ - consensus::{BlockHeight, Network}, + consensus::BlockHeight, ShieldedProtocol, }; use crate::{ config::get_wallet_network, - data::get_db_paths, + data::{get_db_paths, NetworkParams}, tui::{self, Tui}, ShutdownListener, }; @@ -105,7 +105,7 @@ impl Command { pub(super) struct App { should_quit: bool, notify_shutdown: Option>, - db_data: WalletDb, + db_data: WalletDb, pool: ShieldedProtocol, address: Address, action_tx: mpsc::UnboundedSender, @@ -117,7 +117,7 @@ pub(super) struct App { impl App { pub(super) fn new( notify_shutdown: oneshot::Sender<()>, - db_data: WalletDb, + db_data: WalletDb, pool: ShieldedProtocol, address: Option
, show_block_boundaries: bool, diff --git a/src/commands/zip48/init.rs b/src/commands/zip48/init.rs index 2457499..1c62441 100644 --- a/src/commands/zip48/init.rs +++ b/src/commands/zip48/init.rs @@ -5,11 +5,11 @@ use age::secrecy::{ExposeSecret, SecretString}; use bip0039::{Count, English, Mnemonic}; use clap::Args; -use zcash_protocol::consensus::{self, Parameters}; +use zcash_protocol::{consensus::{self, BlockHeight, Parameters}, local_consensus::LocalNetwork}; use crate::{ config::WalletConfig, - data::{init_dbs, Network}, + data::{init_dbs, Network, NetworkParams}, }; // Options accepted for the `zip48 init` command @@ -31,7 +31,24 @@ pub(crate) struct Command { impl Command { pub(crate) fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { - let params = consensus::Network::from(self.network); + let params = match self.network { + Network::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + Network::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), + Network::Regtest => { + // Create a LocalNetwork with all upgrades at height 1 + let height_1 = Some(BlockHeight::from_u32(1)); + NetworkParams::Local(LocalNetwork { + overwinter: height_1, + sapling: height_1, + blossom: height_1, + heartwood: height_1, + canopy: height_1, + nu5: height_1, + nu6: height_1, + nu6_1: height_1, + }) + } + }; let recipients = if fs::exists(&self.identity)? { age::IdentityFile::from_file(self.identity)?.to_recipients()? @@ -77,7 +94,7 @@ impl Command { params .activation_height(consensus::NetworkUpgrade::Nu6) .expect("active"), - self.network.into(), + ¶ms, )?; // Initialise the block and wallet DBs. diff --git a/src/config.rs b/src/config.rs index ece2fa5..10bc4ce 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,17 +7,18 @@ use std::path::Path; use secrecy::{ExposeSecret, SecretVec, Zeroize}; use serde::{Deserialize, Serialize}; -use zcash_protocol::consensus::{self, BlockHeight, Parameters}; +use zcash_protocol::consensus::{self, BlockHeight, NetworkUpgrade, Parameters}; +use zcash_protocol::local_consensus::LocalNetwork; use crate::{ - data::{Network, DEFAULT_WALLET_DIR}, + data::{Network, NetworkParams, DEFAULT_WALLET_DIR}, error, }; const KEYS_FILE: &str = "keys.toml"; pub(crate) struct WalletConfig { - network: consensus::Network, + network_params: NetworkParams, seed_ciphertext: Option, birthday: BlockHeight, } @@ -28,22 +29,22 @@ impl WalletConfig { recipients: impl Iterator, mnemonic: &Mnemonic, birthday: BlockHeight, - network: consensus::Network, + network_params: &NetworkParams, ) -> Result<(), anyhow::Error> { init_wallet_config( wallet_dir, Some(encrypt_mnemonic(recipients, mnemonic)?), birthday, - network, + network_params, ) } pub(crate) fn init_without_mnemonic>( wallet_dir: Option

, birthday: BlockHeight, - network: consensus::Network, + network_params: &NetworkParams, ) -> Result<(), anyhow::Error> { - init_wallet_config(wallet_dir, None, birthday, network) + init_wallet_config(wallet_dir, None, birthday, network_params) } pub(crate) fn decrypt_seed<'a>( @@ -66,8 +67,8 @@ impl WalletConfig { .transpose() } - pub(crate) fn network(&self) -> consensus::Network { - self.network + pub(crate) fn network(&self) -> NetworkParams { + self.network_params.clone() } pub(crate) fn birthday(&self) -> BlockHeight { @@ -79,7 +80,7 @@ fn init_wallet_config>( wallet_dir: Option

, mnemonic: Option, birthday: BlockHeight, - network: consensus::Network, + network_params: &NetworkParams, ) -> Result<(), anyhow::Error> { // Create the wallet directory. let wallet_dir = wallet_dir @@ -95,10 +96,37 @@ fn init_wallet_config>( fs::OpenOptions::new().create_new(true).write(true).open(p) }?; + // Extract network name and activation heights from NetworkParams + let (network_str, regtest_activations) = match network_params { + NetworkParams::Consensus(consensus::Network::MainNetwork) => ("main", None), + NetworkParams::Consensus(consensus::Network::TestNetwork) => ("test", None), + NetworkParams::Local(local) => { + let heights = ( + local.activation_height(NetworkUpgrade::Overwinter).map(u32::from), + local.activation_height(NetworkUpgrade::Sapling).map(u32::from), + local.activation_height(NetworkUpgrade::Blossom).map(u32::from), + local.activation_height(NetworkUpgrade::Heartwood).map(u32::from), + local.activation_height(NetworkUpgrade::Canopy).map(u32::from), + local.activation_height(NetworkUpgrade::Nu5).map(u32::from), + local.activation_height(NetworkUpgrade::Nu6).map(u32::from), + local.activation_height(NetworkUpgrade::Nu6_1).map(u32::from), + ); + ("regtest", Some(heights)) + } + }; + let config = ConfigEncoding { mnemonic, - network: Some(Network::from(network).name().to_string()), + network: Some(network_str.to_string()), birthday: Some(u32::from(birthday)), + regtest_activation_overwinter: regtest_activations.and_then(|h| h.0), + regtest_activation_sapling: regtest_activations.and_then(|h| h.1), + regtest_activation_blossom: regtest_activations.and_then(|h| h.2), + regtest_activation_heartwood: regtest_activations.and_then(|h| h.3), + regtest_activation_canopy: regtest_activations.and_then(|h| h.4), + regtest_activation_nu5: regtest_activations.and_then(|h| h.5), + regtest_activation_nu6: regtest_activations.and_then(|h| h.6), + regtest_activation_nu6_1: regtest_activations.and_then(|h| h.7), }; let config_str = toml::to_string(&config) @@ -126,22 +154,44 @@ impl WalletConfig { let config: ConfigEncoding = toml::from_str(&conf_str)?; let network = config.network.map_or_else( - || Ok(consensus::Network::TestNetwork), + || Ok(Network::Test), |network_name| { Network::parse(network_name.trim()) - .map(consensus::Network::from) .map_err(|_| error::Error::InvalidKeysFile) }, )?; - let birthday = config.birthday.map(BlockHeight::from).unwrap_or( - network - .activation_height(consensus::NetworkUpgrade::Sapling) - .expect("Sapling activation height is known."), - ); + let network_params = match network { + Network::Main => NetworkParams::Consensus(consensus::Network::MainNetwork), + Network::Test => NetworkParams::Consensus(consensus::Network::TestNetwork), + Network::Regtest => { + // Helper to get activation height with default of 1 + let height_or_default = |opt: Option| { + opt.map(BlockHeight::from).or(Some(BlockHeight::from_u32(1))) + }; + + let local_network = LocalNetwork { + overwinter: height_or_default(config.regtest_activation_overwinter), + sapling: height_or_default(config.regtest_activation_sapling), + blossom: height_or_default(config.regtest_activation_blossom), + heartwood: height_or_default(config.regtest_activation_heartwood), + canopy: height_or_default(config.regtest_activation_canopy), + nu5: height_or_default(config.regtest_activation_nu5), + nu6: height_or_default(config.regtest_activation_nu6), + nu6_1: height_or_default(config.regtest_activation_nu6_1), + }; + NetworkParams::Local(local_network) + } + }; + + let birthday = config.birthday.map(BlockHeight::from).unwrap_or_else(|| { + network_params + .activation_height(NetworkUpgrade::Sapling) + .expect("Sapling activation height is known.") + }); Ok(Self { - network, + network_params, seed_ciphertext: config.mnemonic, birthday, }) @@ -153,6 +203,16 @@ struct ConfigEncoding { mnemonic: Option, network: Option, birthday: Option, + + // Regtest activation heights (all optional, default to 1) + regtest_activation_overwinter: Option, + regtest_activation_sapling: Option, + regtest_activation_blossom: Option, + regtest_activation_heartwood: Option, + regtest_activation_canopy: Option, + regtest_activation_nu5: Option, + regtest_activation_nu6: Option, + regtest_activation_nu6_1: Option, } fn encrypt_mnemonic<'a>( @@ -203,8 +263,8 @@ fn decrypt_seed<'a>( pub(crate) fn get_wallet_network>( wallet_dir: Option

, -) -> Result { - Ok(WalletConfig::read(wallet_dir)?.network) +) -> Result { + Ok(WalletConfig::read(wallet_dir)?.network_params) } pub(crate) fn get_wallet_seed<'a, P: AsRef>( diff --git a/src/data.rs b/src/data.rs index 5bc2c70..5831edd 100644 --- a/src/data.rs +++ b/src/data.rs @@ -8,7 +8,8 @@ use zcash_client_sqlite::{FsBlockDb, WalletDb}; use tracing::error; use zcash_client_sqlite::chain::BlockMeta; -use zcash_protocol::consensus::{self, Parameters}; +use zcash_protocol::consensus::{self, BlockHeight, NetworkType, NetworkUpgrade, Parameters}; +use zcash_protocol::local_consensus::LocalNetwork; use crate::error; @@ -17,11 +18,49 @@ const BLOCKS_FOLDER: &str = "blocks"; const DATA_DB: &str = "data.sqlite"; const TOR_DIR: &str = "tor"; +/// Wrapper enum for network parameters that supports both standard networks +/// (MainNetwork, TestNetwork) and custom local networks (regtest). +#[derive(Clone, Debug)] +pub(crate) enum NetworkParams { + Consensus(consensus::Network), + Local(LocalNetwork), +} + +impl NetworkParams { + pub(crate) fn is_regtest(&self) -> bool { + matches!(self, Self::Local(_)) + } + + pub(crate) fn consensus_network(&self) -> Option { + match self { + Self::Consensus(n) => Some(*n), + Self::Local(_) => None, + } + } +} + +impl Parameters for NetworkParams { + fn network_type(&self) -> NetworkType { + match self { + Self::Consensus(n) => n.network_type(), + Self::Local(n) => n.network_type(), + } + } + + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match self { + Self::Consensus(n) => n.activation_height(nu), + Self::Local(n) => n.activation_height(nu), + } + } +} + #[derive(Clone, Copy, Debug, Default)] pub(crate) enum Network { #[default] Test, Main, + Regtest, } impl Network { @@ -29,6 +68,7 @@ impl Network { match name { "main" => Ok(Network::Main), "test" => Ok(Network::Test), + "regtest" => Ok(Network::Regtest), other => Err(format!("Unsupported network: {other}")), } } @@ -37,24 +77,7 @@ impl Network { match self { Network::Test => "test", Network::Main => "main", - } - } -} - -impl From for consensus::Network { - fn from(value: Network) -> Self { - match value { - Network::Test => consensus::Network::TestNetwork, - Network::Main => consensus::Network::MainNetwork, - } - } -} - -impl From for Network { - fn from(value: consensus::Network) -> Self { - match value { - consensus::Network::TestNetwork => Network::Test, - consensus::Network::MainNetwork => Network::Main, + Network::Regtest => "regtest", } } } diff --git a/src/remote.rs b/src/remote.rs index d74338d..9f5822d 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -9,7 +9,7 @@ use zcash_client_backend::{ }; use zcash_protocol::consensus::Network; -use crate::data::get_tor_dir; +use crate::data::{get_tor_dir, NetworkParams}; const ECC_TESTNET: &[Server<'_>] = &[Server::fixed("lightwalletd.testnet.electriccoin.co", 9067)]; @@ -81,8 +81,27 @@ impl Servers { } } - pub(crate) fn pick(&self, network: Network) -> anyhow::Result<&Server<'_>> { - // For now just use the first server in the list. + pub(crate) fn pick(&self, params: &NetworkParams) -> anyhow::Result<&Server<'_>> { + // For regtest, only custom servers are supported + if params.is_regtest() { + match self { + Servers::Custom(servers) => return Ok(servers.first().expect("not empty")), + Servers::Hosted(op) => { + return Err(anyhow!( + "Regtest network requires a custom server.\n\ + Use: --server localhost:9067 (or your lightwalletd address)\n\ + Hosted servers ({:?}) only support mainnet and testnet.", + op + )) + } + } + } + + // For main/test, use existing logic + let network = params + .consensus_network() + .expect("main/test should be consensus network"); + match self { Servers::Hosted(server_operator) => server_operator .servers(network)