From 314f4537a3920c58b742f0d4cfc2f44f41eb8895 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 3 Nov 2023 17:58:02 +0200 Subject: [PATCH 1/5] update account and wallet import handlers --- cli/src/modules/account.rs | 98 ++++++++++++++++++++++++- cli/src/modules/mod.rs | 6 +- cli/src/modules/wallet.rs | 18 ++++- cli/src/wizards/import.rs | 16 ++-- cli/src/wizards/wallet.rs | 118 +++++++++++++++++++----------- wallet/core/src/runtime/wallet.rs | 9 +-- 6 files changed, 198 insertions(+), 67 deletions(-) diff --git a/cli/src/modules/account.rs b/cli/src/modules/account.rs index 24afd2ef6..17fb16be1 100644 --- a/cli/src/modules/account.rs +++ b/cli/src/modules/account.rs @@ -59,6 +59,98 @@ impl Account { let account_name = account_name.as_deref(); wizards::account::create(&ctx, prv_key_data_info, account_kind, account_name).await?; } + "import" => { + if argv.is_empty() { + tprintln!(ctx, "usage: 'account import [extra keys]'"); + tprintln!(ctx, ""); + tprintln!(ctx, "examples:"); + tprintln!(ctx, ""); + ctx.term().help( + &[ + ("account import legacy-data", "Import KDX keydata file or kaspanet web wallet data on the same domain"), + ( + "account import mnemonic bip32", + "Import Bip32 (12 or 24 word mnemonics used by kaspawallet, kaspium, onekey, tangem etc.)", + ), + ( + "account import mnemonic legacy", + "Import accounts 12 word mnemonic used by legacy applications (KDX and kaspanet web wallet)", + ), + ( + "account import mnemonic multisig [additional keys]", + "Import mnemonic and additional keys for a multisig account", + ), + ], + None, + )?; + + return Ok(()); + } + + let import_kind = argv.remove(0); + match import_kind.as_ref() { + "legacy-data" => { + if !argv.is_empty() { + tprintln!(ctx, "usage: 'account import legacy-data'"); + tprintln!(ctx, "too many arguments: {}\r\n", argv.join(" ")); + return Ok(()); + } + + if exists_legacy_v0_keydata().await? { + let import_secret = Secret::new( + ctx.term() + .ask(true, "Enter the password for the account you are importing: ") + .await? + .trim() + .as_bytes() + .to_vec(), + ); + let wallet_secret = + Secret::new(ctx.term().ask(true, "Enter wallet password: ").await?.trim().as_bytes().to_vec()); + wallet.import_gen0_keydata(import_secret, wallet_secret, None).await?; + } else if application_runtime::is_web() { + return Err("'kaspanet' web wallet storage not found at this domain name".into()); + } else { + return Err("KDX keydata file not found".into()); + } + } + "mnemonic" => { + if argv.is_empty() { + tprintln!(ctx, "usage: 'account import mnemonic '"); + tprintln!(ctx, "please specify the mnemonic type"); + tprintln!(ctx, "please use 'legacy' for 12-word KDX and kaspanet web wallet mnemonics\r\n"); + return Ok(()); + } + + let account_kind = argv.remove(0); + let account_kind = account_kind.parse::()?; + + match account_kind { + AccountKind::Legacy | AccountKind::Bip32 => { + if !argv.is_empty() { + tprintln!(ctx, "too many arguments: {}\r\n", argv.join(" ")); + return Ok(()); + } + crate::wizards::import::import_with_mnemonic(&ctx, account_kind, &argv).await?; + } + AccountKind::MultiSig => { + crate::wizards::import::import_with_mnemonic(&ctx, account_kind, &argv).await?; + } + _ => { + tprintln!(ctx, "account import is not supported for this account type: '{account_kind}'\r\n"); + return Ok(()); + } + } + + return Ok(()); + } + _ => { + tprintln!(ctx, "unknown account import type: '{import_kind}'"); + tprintln!(ctx, "supported import types are: 'mnemonic' or 'legacy-data'\r\n"); + return Ok(()); + } + } + } "scan" | "sweep" => { let len = argv.len(); let mut start = 0; @@ -90,7 +182,11 @@ impl Account { ctx.term().help( &[ ("create [] []", "Create a new account (types: 'bip32' (default), 'legacy', 'multisig')"), - // ("import", "Import a private key using 24 or 12 word mnemonic"), + ( + "import [ [extra keys]]", + "Import accounts from a private key using 24 or 12 word mnemonic or legacy data \ + (KDX and kaspanet web wallet). Use 'account import' for additional help.", + ), ("name ", "Name or rename the selected account (use 'remove' to remove the name"), ("scan [] or scan [] []", "Scan extended address derivation chain (legacy accounts)"), ( diff --git a/cli/src/modules/mod.rs b/cli/src/modules/mod.rs index 8e9a6fc3a..398c1ae9f 100644 --- a/cli/src/modules/mod.rs +++ b/cli/src/modules/mod.rs @@ -16,7 +16,7 @@ pub mod guide; pub mod halt; pub mod help; pub mod history; -pub mod import; +// pub mod import; pub mod list; pub mod message; pub mod miner; @@ -52,8 +52,8 @@ pub fn register_handlers(cli: &Arc) -> Result<()> { cli, cli.handlers(), [ - account, address, close, connect, details, disconnect, estimate, exit, export, guide, help, history, import, rpc, list, - miner, message, monitor, mute, network, node, open, ping, reload, select, send, server, settings, sweep, track, transfer, + account, address, close, connect, details, disconnect, estimate, exit, export, guide, help, history, rpc, list, miner, + message, monitor, mute, network, node, open, ping, reload, select, send, server, settings, sweep, track, transfer, wallet, // halt, // theme, start, stop diff --git a/cli/src/modules/wallet.rs b/cli/src/modules/wallet.rs index a5da5dd57..177b80067 100644 --- a/cli/src/modules/wallet.rs +++ b/cli/src/modules/wallet.rs @@ -13,7 +13,8 @@ impl Wallet { return self.display_help(ctx, argv).await; } - match argv.remove(0).as_str() { + let op = argv.remove(0); + match op.as_str() { "list" => { let wallets = ctx.store().wallet_list().await?; if wallets.is_empty() { @@ -32,7 +33,7 @@ impl Wallet { tprintln!(ctx, ""); } } - "create" => { + "create" | "import" => { let wallet_name = if argv.is_empty() { None } else { @@ -46,7 +47,8 @@ impl Wallet { }; let wallet_name = wallet_name.as_deref(); - wizards::wallet::create(&ctx, wallet_name).await?; + let import_with_mnemonic = op.as_str() == "import"; + wizards::wallet::create(&ctx, wallet_name, import_with_mnemonic).await?; } "open" => { let name = if let Some(name) = argv.first().cloned() { @@ -98,7 +100,15 @@ impl Wallet { ctx.term().help( &[ ("list", "List available local wallet files"), - ("create []", "Create a new wallet"), + ("create []", "Create a new bip32 wallet"), + ( + "import []", + "Create a wallet from an existing mnemonic (bip32 only). \r\n\r\n\ + To import legacy wallets (KDX or kaspanet) please create \ + a new bip32 wallet and use the 'account import' command. \ + Legacy wallets can only be imported as accounts. \ + \r\n", + ), ("open []", "Open an existing wallet (shorthand: 'open []')"), ("close", "Close an opened wallet (shorthand: 'close')"), ("hint", "Change the wallet phishing hint"), diff --git a/cli/src/wizards/import.rs b/cli/src/wizards/import.rs index a661bbfbb..0c2e7c2df 100644 --- a/cli/src/wizards/import.rs +++ b/cli/src/wizards/import.rs @@ -6,7 +6,7 @@ use kaspa_bip32::{Language, Mnemonic}; use kaspa_wallet_core::storage::AccountKind; use std::sync::Arc; -pub async fn ask(term: &Arc) -> Result> { +pub async fn prompt_for_mnemonic(term: &Arc) -> Result> { let mut words: Vec = vec![]; loop { if words.is_empty() { @@ -52,7 +52,7 @@ pub(crate) async fn import_with_mnemonic(ctx: &Arc, account_kind: Acco tprintln!(ctx); let wallet_secret = Secret::new(term.ask(true, "Enter wallet password: ").await?.trim().as_bytes().to_vec()); tprintln!(ctx); - let mnemonic = ask(&term).await?; + let mnemonic = prompt_for_mnemonic(&term).await?; tprintln!(ctx); let length = mnemonic.len(); match account_kind { @@ -70,15 +70,15 @@ pub(crate) async fn import_with_mnemonic(ctx: &Arc, account_kind: Acco ctx, "\ \ - If your original wallet has a recovery passphrase, please enter it now.\ + If your original wallet has a bip39 recovery passphrase, please enter it now.\ \ - Specifically, this is not a wallet password. This is a secondary payment password\ - used to encrypt your private key. This is known as a 'payment passphrase'\ - 'mnemonic password', or a 'recovery passphrase'. If your mnemonic was created\ + Specifically, this is not a wallet password. This is a secondary mnemonic passphrase\ + used to encrypt your mnemonic. This is known as a 'payment passphrase'\ + 'mnemonic passphrase', or a 'recovery passphrase'. If your mnemonic was created\ with a payment passphrase and you do not enter it now, the import process\ will generate a different private key.\ \ - If you do not have a payment password, just press ENTER.\ + If you do not have a bip39 recovery passphrase, press ENTER.\ \ ", ); @@ -103,7 +103,7 @@ pub(crate) async fn import_with_mnemonic(ctx: &Arc, account_kind: Acco "y" | "Y" | "YES" | "yes" ) { tprintln!(ctx); - let mnemonic = ask(&term).await?; + let mnemonic = prompt_for_mnemonic(&term).await?; tprintln!(ctx); let payment_secret = term.ask(true, "Enter payment password (optional): ").await?; let payment_secret = payment_secret.trim().is_not_empty().then(|| Secret::new(payment_secret.trim().as_bytes().to_vec())); diff --git a/cli/src/wizards/wallet.rs b/cli/src/wizards/wallet.rs index e58714d24..15144aa9e 100644 --- a/cli/src/wizards/wallet.rs +++ b/cli/src/wizards/wallet.rs @@ -4,7 +4,7 @@ use crate::result::Result; use kaspa_wallet_core::runtime::{PrvKeyDataCreateArgs, WalletCreateArgs}; use kaspa_wallet_core::storage::{AccessContextT, AccountKind, Hint}; -pub(crate) async fn create(ctx: &Arc, name: Option<&str>) -> Result<()> { +pub(crate) async fn create(ctx: &Arc, name: Option<&str>, import_with_mnemonic: bool) -> Result<()> { let term = ctx.term(); let wallet = ctx.wallet(); @@ -37,14 +37,14 @@ pub(crate) async fn create(ctx: &Arc, name: Option<&str>) -> Result<() tpara!( ctx, "\n\ - \"Phishing hint\" is a secret word or a phrase that is displayed \ - when you open your wallet. If you do not see the hint when opening \ - your wallet, you may be accessing a fake wallet designed to steal \ - your private key. If this occurs, stop using the wallet immediately, \ - check the browser URL domain name and seek help on social networks \ - (Kaspa Discord or Telegram). \ - \n\ - ", + \"Phishing hint\" is a secret word or a phrase that is displayed \ + when you open your wallet. If you do not see the hint when opening \ + your wallet, you may be accessing a fake wallet designed to steal \ + your private key. If this occurs, stop using the wallet immediately, \ + check the browser URL domain name and seek help on social networks \ + (Kaspa Discord or Telegram). \ + \n\ + ", ); let hint = term.ask(false, "Create phishing hint (optional, press to skip): ").await?.trim().to_string(); @@ -62,24 +62,44 @@ pub(crate) async fn create(ctx: &Arc, name: Option<&str>) -> Result<() } tprintln!(ctx, ""); - tpara!( - ctx, - "\ - PLEASE NOTE: The optional payment password, if provided, will be required to \ - issue transactions. This password will also be required when recovering your wallet \ - in addition to your private key or mnemonic. If you loose this password, you will not \ - be able to use mnemonic to recover your wallet! \ - ", - ); + if import_with_mnemonic { + tpara!( + ctx, + "\ + \ + If your original wallet has a bip39 recovery passphrase, please enter it now.\ + \ + Specifically, this is not a wallet password. This is a secondary mnemonic passphrase\ + used to encrypt your mnemonic. This is known as a 'payment passphrase'\ + 'mnemonic passphrase', or a 'recovery passphrase'. If your mnemonic was created\ + with a payment passphrase and you do not enter it now, the import process\ + will generate a different private key.\ + \ + If you do not have a bip39 recovery passphrase, press ENTER.\ + \ + ", + ); + } else { + tpara!( + ctx, + "\ + PLEASE NOTE: The optional bip39 mnemonic passphrase, if provided, will be required to \ + issue transactions. This passphrase will also be required when recovering your wallet \ + in addition to your private key or mnemonic. If you loose this passphrase, you will not \ + be able to use or recover your wallet! \ + \ + If you do not want to use bip39 recovery passphrase, press ENTER.\ + ", + ); + } - let payment_secret = term.ask(true, "Enter payment password (optional): ").await?; + let payment_secret = term.ask(true, "Enter bip39 mnemonic passphrase (optional): ").await?; let payment_secret = if payment_secret.trim().is_empty() { None } else { Some(Secret::new(payment_secret.trim().as_bytes().to_vec())) }; if let Some(payment_secret) = payment_secret.as_ref() { - let payment_secret_validate = Secret::new( - term.ask(true, "Enter payment (private key encryption) password (optional): ").await?.trim().as_bytes().to_vec(), - ); + let payment_secret_validate = + Secret::new(term.ask(true, "Please re-enter mnemonic passphrase: ").await?.trim().as_bytes().to_vec()); if payment_secret_validate.as_ref() != payment_secret.as_ref() { return Err(Error::PaymentSecretMatch); } @@ -87,13 +107,20 @@ pub(crate) async fn create(ctx: &Arc, name: Option<&str>) -> Result<() tprintln!(ctx, ""); + let prv_key_data_args = if import_with_mnemonic { + let words = crate::wizards::import::prompt_for_mnemonic(&term).await?; + PrvKeyDataCreateArgs::new_with_mnemonic(None, wallet_secret.clone(), payment_secret.clone(), words.join(" ")) + } else { + PrvKeyDataCreateArgs::new(None, wallet_secret.clone(), payment_secret.clone()) + }; + let notifier = ctx.notifier().show(Notification::Processing).await; + // suspend commits for multiple operations wallet.store().batch().await?; let account_kind = AccountKind::Bip32; let wallet_args = WalletCreateArgs::new(name.map(String::from), None, hint, wallet_secret.clone(), true); - let prv_key_data_args = PrvKeyDataCreateArgs::new(None, wallet_secret.clone(), payment_secret.clone()); let account_args = AccountCreateArgs::new(account_name, account_title, account_kind, wallet_secret.clone(), payment_secret); let descriptor = ctx.wallet().create_wallet(wallet_args).await?; let (prv_key_data_id, mnemonic) = wallet.create_prv_key_data(prv_key_data_args).await?; @@ -102,31 +129,34 @@ pub(crate) async fn create(ctx: &Arc, name: Option<&str>) -> Result<() // flush data to storage let access_ctx: Arc = Arc::new(AccessContext::new(wallet_secret.clone())); wallet.store().flush(&access_ctx).await?; - notifier.hide(); - tprintln!(ctx, ""); - tprintln!(ctx, "---"); - tprintln!(ctx, ""); - tprintln!(ctx, "{}", style("IMPORTANT:").red()); - tprintln!(ctx, ""); + notifier.hide(); - tpara!( - ctx, - "Your mnemonic phrase allows your to re-create your private key. \ - The person who has access to this mnemonic will have full control of \ - the Kaspa stored in it. Keep your mnemonic safe. Write it down and \ - store it in a safe, preferably in a fire-resistant location. Do not \ - store your mnemonic on this computer or a mobile device. This wallet \ - will never ask you for this mnemonic phrase unless you manually \ - initiate a private key recovery. \ - ", - ); + if !import_with_mnemonic { + tprintln!(ctx, ""); + tprintln!(ctx, "---"); + tprintln!(ctx, ""); + tprintln!(ctx, "{}", style("IMPORTANT:").red()); + tprintln!(ctx, ""); + + tpara!( + ctx, + "Your mnemonic phrase allows your to re-create your private key. \ + The person who has access to this mnemonic will have full control of \ + the Kaspa stored in it. Keep your mnemonic safe. Write it down and \ + store it in a safe, preferably in a fire-resistant location. Do not \ + store your mnemonic on this computer or a mobile device. This wallet \ + will never ask you for this mnemonic phrase unless you manually \ + initiate a private key recovery. \ + ", + ); - // descriptor + // descriptor - ["", "Never share your mnemonic with anyone!", "---", "", "Your default wallet account mnemonic:", mnemonic.phrase()] - .into_iter() - .for_each(|line| term.writeln(line)); + ["", "Never share your mnemonic with anyone!", "---", "", "Your default wallet account mnemonic:", mnemonic.phrase()] + .into_iter() + .for_each(|line| term.writeln(line)); + } term.writeln(""); if let Some(descriptor) = descriptor { diff --git a/wallet/core/src/runtime/wallet.rs b/wallet/core/src/runtime/wallet.rs index 5e2327d6d..e1fb7c473 100644 --- a/wallet/core/src/runtime/wallet.rs +++ b/wallet/core/src/runtime/wallet.rs @@ -66,13 +66,8 @@ impl PrvKeyDataCreateArgs { Self { name, wallet_secret, payment_secret, mnemonic: None } } - pub fn new_with_mnemonic( - name: Option, - wallet_secret: Secret, - payment_secret: Option, - mnemonic: Option, - ) -> Self { - Self { name, wallet_secret, payment_secret, mnemonic } + pub fn new_with_mnemonic(name: Option, wallet_secret: Secret, payment_secret: Option, mnemonic: String) -> Self { + Self { name, wallet_secret, payment_secret, mnemonic: Some(mnemonic) } } } From 19ddda06e8d702cea7ff04900afb3fe8073dfbfe Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 3 Nov 2023 23:51:34 +0530 Subject: [PATCH 2/5] fix: address derivation issue while importing Gen0 wallet scan notification message while importing gen0 wallet file --- cli/src/modules/account.rs | 29 +++++++++++++++++++++++++- wallet/core/src/runtime/account/mod.rs | 4 ++-- wallet/core/src/runtime/wallet.rs | 27 +++++++++++++++++------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/cli/src/modules/account.rs b/cli/src/modules/account.rs index 17fb16be1..5b2fd9858 100644 --- a/cli/src/modules/account.rs +++ b/cli/src/modules/account.rs @@ -107,7 +107,34 @@ impl Account { ); let wallet_secret = Secret::new(ctx.term().ask(true, "Enter wallet password: ").await?.trim().as_bytes().to_vec()); - wallet.import_gen0_keydata(import_secret, wallet_secret, None).await?; + let ctx_ = ctx.clone(); + wallet + .import_gen0_keydata( + import_secret, + wallet_secret, + None, + Some(Arc::new(move |processed: usize, balance, txid| { + if let Some(txid) = txid { + tprintln!( + ctx_, + "Scan detected {} KAS at index {}; transfer txid: {}", + sompi_to_kaspa_string(balance), + processed, + txid + ); + } else if processed > 0 { + tprintln!( + ctx_, + "Scanned {} derivations, found {} KAS", + processed, + sompi_to_kaspa_string(balance) + ); + } else { + tprintln!(ctx_, "Please wait... scanning for account UTXOs..."); + } + })), + ) + .await?; } else if application_runtime::is_web() { return Err("'kaspanet' web wallet storage not found at this domain name".into()); } else { diff --git a/wallet/core/src/runtime/account/mod.rs b/wallet/core/src/runtime/account/mod.rs index 9245ff42c..315391566 100644 --- a/wallet/core/src/runtime/account/mod.rs +++ b/wallet/core/src/runtime/account/mod.rs @@ -33,7 +33,7 @@ use super::AtomicBalance; pub const DEFAULT_AMOUNT_PADDING: usize = 19; pub type GenerationNotifier = Arc; -pub type DeepScanNotifier = Arc) + Send + Sync>; +pub type ScanNotifier = Arc) + Send + Sync>; pub struct Context { pub settings: Option, @@ -412,7 +412,7 @@ pub trait DerivationCapableAccount: Account { window: usize, sweep: bool, abortable: &Abortable, - notifier: Option, + notifier: Option, ) -> Result<()> { self.clone().initialize_private_data(wallet_secret.clone(), payment_secret.as_ref(), None).await?; diff --git a/wallet/core/src/runtime/wallet.rs b/wallet/core/src/runtime/wallet.rs index e1fb7c473..92816ac40 100644 --- a/wallet/core/src/runtime/wallet.rs +++ b/wallet/core/src/runtime/wallet.rs @@ -1,6 +1,6 @@ use crate::imports::*; use crate::result::Result; -use crate::runtime::{try_from_storage, Account, AccountId, ActiveAccountMap}; +use crate::runtime::{account::ScanNotifier, try_from_storage, Account, AccountId, ActiveAccountMap}; use crate::secret::Secret; use crate::settings::{SettingsStore, WalletSettings}; use crate::storage::interface::{AccessContext, CreateArgs, OpenArgs}; @@ -843,7 +843,9 @@ impl Wallet { import_secret: Secret, wallet_secret: Secret, payment_secret: Option<&Secret>, + notifier: Option, ) -> Result> { + let notifier = notifier.as_ref(); let keydata = load_v0_keydata(&import_secret).await?; let ctx: Arc = Arc::new(AccessContext::new(wallet_secret.clone())); @@ -859,25 +861,34 @@ impl Wallet { let settings = storage::Settings::default(); let account = Arc::new(runtime::account::Legacy::try_new(self, prv_key_data.id, settings, data, None).await?); - account.clone().initialize_private_data(wallet_secret, payment_secret, None).await?; - // activate account (add it to wallet active account list) self.active_accounts().insert(account.clone().as_dyn_arc()); self.legacy_accounts().insert(account.clone().as_dyn_arc()); - if self.is_connected() { - account.clone().scan(Some(100), Some(50000)).await?; - } - let account_store = self.inner.store.as_account_store()?; let stored_account = account.as_storable()?; - // store private key and account self.inner.store.batch().await?; prv_key_data_store.store(&ctx, prv_key_data).await?; account_store.store_single(&stored_account, None).await?; self.inner.store.flush(&ctx).await?; + account.clone().initialize_private_data(wallet_secret, payment_secret, None).await?; + + if self.is_connected() { + if let Some(notifier) = notifier { + notifier(0, 0, None); + } + account.clone().scan(Some(100), Some(5000)).await?; + } + + let derivation = account.clone().as_derivation_capable()?.derivation(); + let m = derivation.receive_address_manager(); + m.get_range(0..(m.index() + CACHE_ADDRESS_OFFSET))?; + let m = derivation.change_address_manager(); + m.get_range(0..(m.index() + CACHE_ADDRESS_OFFSET))?; + account.clone().clear_private_data().await?; + account.clone().clear_private_data().await?; Ok(account) From 6da507a798e104d40aaae5f6049c4e05a4da7300 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 4 Nov 2023 00:21:07 +0530 Subject: [PATCH 3/5] fix for wallet file override check --- wallet/core/src/storage/local/interface.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/storage/local/interface.rs b/wallet/core/src/storage/local/interface.rs index b13adf48e..70ac4977e 100644 --- a/wallet/core/src/storage/local/interface.rs +++ b/wallet/core/src/storage/local/interface.rs @@ -221,7 +221,7 @@ impl Interface for LocalStore { async fn exists(&self, name: Option<&str>) -> Result { let location = self.location.lock().unwrap().clone().unwrap(); - let store = Storage::try_new_with_folder(&location.folder, name.unwrap_or(super::DEFAULT_WALLET_FILE))?; + let store = Storage::try_new_with_folder(&location.folder, &format!("{}.wallet", name.unwrap_or(super::DEFAULT_WALLET_FILE)))?; store.exists().await } From 4dde0d31f65fe57cad748f823d80e827392b275d Mon Sep 17 00:00:00 2001 From: aspect Date: Fri, 3 Nov 2023 22:04:38 +0200 Subject: [PATCH 4/5] Minor documentation updates (#313) * linux instructions * Update dependency and Windows LLVM documentation; Add README.md to the kaspad crate. * Fix kos/build.ps1 quotes when calling wasm-pack #245 * content updates (PR review feedback) --- Cargo.toml | 1 + README.md | 40 ++++++++++++++++++++++++++-------------- kaspad/Cargo.toml | 1 + kaspad/README.md | 5 +++++ kos/build.ps1 | 4 ++-- 5 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 kaspad/README.md diff --git a/Cargo.toml b/Cargo.toml index 3ce16e1a2..ab5492284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ members = [ version = "0.1.7" authors = ["Kaspa developers"] license = "MIT/Apache-2.0" +repository = "https://github.com/kaspanet/rusty-kaspa" edition = "2021" include = [ "src/**/*.rs", diff --git a/README.md b/README.md index ada10e506..e0a7e5252 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,16 @@ This repository contains the implementation of the Kaspa full-node and related l ## Getting started -- Install Protobuf (required for grpc) +- General prerequisites: + - Linux: `sudo apt install build-essential libssl-dev pkg-config` + - Windows: [Git for Windows](https://gitforwindows.org/) or an alternative Git distribution. +- Install Protobuf (required for gRPC) - Linux: `sudo apt install protobuf-compiler libprotobuf-dev` - - Windows: [protoc-21.10-win64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v21.10/protoc-21.10-win64.zip) and add `bin` dir to `Path` + - Windows: [protoc-21.10-win64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v21.10/protoc-21.10-win64.zip) and add `bin` directory to `Path` - MacOS: `brew install protobuf` -- Install the [clang toolchain](https://clang.llvm.org/) (required for RocksDB) +- Install the [clang toolchain](https://clang.llvm.org/) (required for RocksDB and WASM `secp256k1` builds) - Linux: `apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang` - - Windows: [LLVM-15.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/LLVM-15.0.6-win64.exe) and set `LIBCLANG_PATH` env var pointing to the `bin` dir of the llvm installation + - Windows: Please see [Installing clang toolchain on Windows](#installing-clang-toolchain-on-windows) - MacOS: Please see [Installing clang toolchain on MacOS](#installing-clang-toolchain-on-macos) - Install the [rust toolchain](https://rustup.rs/) - If you already have rust installed, update it by running: `rustup update` @@ -83,7 +86,7 @@ It will produce `{bin-name}-heap.json` file in the root of the workdir, that can ## Tests & Benchmarks -- To run all current tests use: +- To run unit and most integration tests use: ```bash $ cd rusty-kaspa @@ -109,6 +112,17 @@ cd wasm ``` This will produce a wasm library in `/web-root` directory +## Installing clang toolchain on Windows + +Install [LLVM-15.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/LLVM-15.0.6-win64.exe) + +Once LLVM is installed: +- Add the `bin` directory of the LLVM installation (`C:\Program Files\LLVM\bin`) to PATH +- set `LIBCLANG_PATH` environment variable to point to the `bin` directory as well + +**IMPORTANT:** Due to C++ dependency configuration issues, LLVM `AR` installation on Windows may not function correctly when switching between WASM and native C++ code compilation (native `RocksDB+secp256k1` vs WASM32 builds of `secp256k1`). Unfortunately, manually setting `AR` environment variable also confuses C++ build toolchain (it should not be set for native but should be set for WASM32 targets). Currently, the best way to address this, is as follows: after installing LLVM on Windows, go to the target `bin` installation directory and copy or rename `LLVM_AR.exe` to `AR.exe`. + + ## Installing clang toolchain on MacOS The default XCode installation of `llvm` does not support WASM build targets. @@ -117,18 +131,16 @@ To build WASM on MacOS you need to install `llvm` from homebrew (at the time of ```bash brew install llvm ``` -NOTE: depending on your setup, the installation location may be different. -To determine the installation location you can use `which llvm`, `which clang` -or `brew list llvm` commands -and then modify the paths below accordingly. +**NOTE:** depending on your homebrew configuration, the installation location may be different. +In some homebrew configurations it can be `/opt/homebrew/opt/llvm` while in others it can be `/usr/local/Cellar/llvm`. +To determine the installation location you can use `brew list llvm` command and then modify the paths below accordingly: ```bash % brew list llvm /usr/local/Cellar/llvm/15.0.7_1/bin/FileCheck /usr/local/Cellar/llvm/15.0.7_1/bin/UnicodeNameMappingGenerator +... ``` -should i replace 'opt/homebrew/opt' to ''/usr/local/Cellar/llvm/15.0.7_1"? - Add the following to your `~/.zshrc` file: ```bash @@ -138,7 +150,6 @@ export CPPFLAGS="-I/opt/homebrew/opt/llvm/include" export AR=/opt/homebrew/opt/llvm/bin/llvm-ar ``` - Reload the `~/.zshrc` file ```bash source ~/.zshrc @@ -171,7 +182,8 @@ wRPC to gRPC Proxy is deprecated and no longer supported. Integration in a Browser and Node.js environments is possible using WASM. The JavaScript code is agnostic to which environment it runs in. -NOTE: to run in Node.js environment, you must instantiate a W3C WebSocket + +**NOTE:** to run in Node.js environment, you must instantiate a W3C WebSocket shim using a `WebSocket` crate before initializing Kaspa environment: `globalThis.WebSocket = require('websocket').w3cwebsocket;` @@ -193,7 +205,7 @@ node index You can take a look at `rpc/wrpc/wasm/nodejs/index.js` to see the use of the native JavaScript & TypeScript APIs. -NOTE: `npm install` is needed to install [WebSocket](https://github.com/theturtle32/WebSocket-Node) module. +**NOTE:** `npm install` is needed to install [WebSocket](https://github.com/theturtle32/WebSocket-Node) module. When running in the Browser environment, no additional dependencies are necessary because the browser provides the W3C WebSocket class natively. diff --git a/kaspad/Cargo.toml b/kaspad/Cargo.toml index 41f400b64..d15517eb9 100644 --- a/kaspad/Cargo.toml +++ b/kaspad/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true authors.workspace = true include.workspace = true license.workspace = true +repository.workspace = true [lib] name = "kaspad_lib" diff --git a/kaspad/README.md b/kaspad/README.md new file mode 100644 index 000000000..4359055ce --- /dev/null +++ b/kaspad/README.md @@ -0,0 +1,5 @@ +# Kaspa p2p Node + +High-performance p2p node library and daemon for high-BPS BlockDAG [Kaspa](https://kaspa.org) network developed in Rust. + +For more information please refer to the GitHub repository `README.md` located at https://github.com/kaspanet/rusty-kaspa diff --git a/kos/build.ps1 b/kos/build.ps1 index a5a1060c5..604ff73d1 100644 --- a/kos/build.ps1 +++ b/kos/build.ps1 @@ -1,7 +1,7 @@ cargo fmt --all if ($args.Contains("--dev")) { - & "wasm-pack build --dev --target web --out-name kaspa --out-dir app/wasm" + & "wasm-pack" build --dev --target web --out-name kaspa --out-dir app/wasm } else { - & "wasm-pack build --target web --out-name kaspa --out-dir app/wasm" + & "wasm-pack" build --target web --out-name kaspa --out-dir app/wasm } From 070f51c923e0c588153078aae7c6c05867dd711e Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 4 Nov 2023 01:38:11 +0530 Subject: [PATCH 5/5] wallet filename slugify --- cli/src/wizards/wallet.rs | 6 +++--- wallet/core/src/runtime/wallet.rs | 10 +++++++--- wallet/core/src/storage/local/interface.rs | 2 +- wallet/core/src/storage/mod.rs | 1 + 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cli/src/wizards/wallet.rs b/cli/src/wizards/wallet.rs index 15144aa9e..2415f7fcc 100644 --- a/cli/src/wizards/wallet.rs +++ b/cli/src/wizards/wallet.rs @@ -2,7 +2,7 @@ use crate::cli::KaspaCli; use crate::imports::*; use crate::result::Result; use kaspa_wallet_core::runtime::{PrvKeyDataCreateArgs, WalletCreateArgs}; -use kaspa_wallet_core::storage::{AccessContextT, AccountKind, Hint}; +use kaspa_wallet_core::storage::{make_filename, AccessContextT, AccountKind, Hint}; pub(crate) async fn create(ctx: &Arc, name: Option<&str>, import_with_mnemonic: bool) -> Result<()> { let term = ctx.term(); @@ -16,8 +16,8 @@ pub(crate) async fn create(ctx: &Arc, name: Option<&str>, import_with_ tprintln!(ctx); return Err(err.into()); } - - if wallet.exists(name).await? { + let filename = make_filename(&name.map(String::from), &None); + if wallet.exists(Some(&filename)).await? { tprintln!(ctx, "{}", style("WARNING - A previously created wallet already exists!").red().to_string()); tprintln!(ctx, "NOTE: You can create a differently named wallet by using 'wallet create '"); tprintln!(ctx); diff --git a/wallet/core/src/runtime/wallet.rs b/wallet/core/src/runtime/wallet.rs index 92816ac40..fe16b27ac 100644 --- a/wallet/core/src/runtime/wallet.rs +++ b/wallet/core/src/runtime/wallet.rs @@ -6,7 +6,9 @@ use crate::settings::{SettingsStore, WalletSettings}; use crate::storage::interface::{AccessContext, CreateArgs, OpenArgs}; use crate::storage::local::interface::LocalStore; use crate::storage::local::Storage; -use crate::storage::{self, AccessContextT, AccountData, AccountKind, Hint, Interface, PrvKeyData, PrvKeyDataId, PrvKeyDataInfo}; +use crate::storage::{ + self, make_filename, AccessContextT, AccountData, AccountKind, Hint, Interface, PrvKeyData, PrvKeyDataId, PrvKeyDataInfo, +}; use crate::utxo::UtxoProcessor; #[allow(unused_imports)] use crate::{derivation::gen0, derivation::gen0::import::*, derivation::gen1, derivation::gen1::import::*}; @@ -275,8 +277,9 @@ impl Wallet { /// Loads a wallet from storage. Accounts are not activated by this call. async fn load_impl(self: &Arc, secret: Secret, name: Option) -> Result<()> { let name = name.or_else(|| self.settings().get(WalletSettings::Wallet)); + let name = Some(make_filename(&name, &None)); let ctx: Arc = Arc::new(AccessContext::new(secret)); - self.store().open(&ctx, OpenArgs::new(name.clone())).await?; + self.store().open(&ctx, OpenArgs::new(name)).await?; // reset current state only after we have successfully opened another wallet self.reset(true).await?; @@ -302,8 +305,9 @@ impl Wallet { /// Loads a wallet from storage. Accounts are activated by this call. pub async fn load_and_activate(self: &Arc, secret: Secret, name: Option) -> Result<()> { let name = name.or_else(|| self.settings().get(WalletSettings::Wallet)); + let name = Some(make_filename(&name, &None)); let ctx: Arc = Arc::new(AccessContext::new(secret.clone())); - self.store().open(&ctx, OpenArgs::new(name.clone())).await?; + self.store().open(&ctx, OpenArgs::new(name)).await?; // reset current state only after we have successfully opened another wallet self.reset(true).await?; diff --git a/wallet/core/src/storage/local/interface.rs b/wallet/core/src/storage/local/interface.rs index 70ac4977e..733799f63 100644 --- a/wallet/core/src/storage/local/interface.rs +++ b/wallet/core/src/storage/local/interface.rs @@ -18,7 +18,7 @@ use std::sync::atomic::Ordering; use workflow_core::runtime::is_web; use workflow_store::fs; -fn make_filename(title: &Option, filename: &Option) -> String { +pub fn make_filename(title: &Option, filename: &Option) -> String { if let Some(filename) = filename { filename.to_string() } else if let Some(title) = title { diff --git a/wallet/core/src/storage/mod.rs b/wallet/core/src/storage/mod.rs index 5a665e347..44d05c0f9 100644 --- a/wallet/core/src/storage/mod.rs +++ b/wallet/core/src/storage/mod.rs @@ -19,6 +19,7 @@ pub use hint::Hint; pub use id::IdT; pub use interface::{AccessContextT, AccountStore, Interface, PrvKeyDataStore, TransactionRecordStore, WalletDescriptor}; pub use keydata::{KeyCaps, PrvKeyData, PrvKeyDataId, PrvKeyDataInfo, PrvKeyDataMap, PrvKeyDataPayload}; +pub use local::interface::make_filename; pub use metadata::Metadata; pub use transaction::{TransactionMetadata, TransactionRecord, TransactionType};