wallet: do not scan outputs for new wallet

This commit is contained in:
ardocrat
2025-05-29 00:38:45 +03:00
parent d42ef102b2
commit fbb084f636
10 changed files with 199 additions and 50 deletions
Generated
+1
View File
@@ -3551,6 +3551,7 @@ dependencies = [
"qrcodegen",
"rand 0.9.1",
"rfd",
"ring",
"rkv",
"rqrr",
"rust-i18n",
+1
View File
@@ -76,6 +76,7 @@ ur = "0.4.1"
gif = "0.13.1"
rkv = { version = "0.19.0", features = ["lmdb"] }
usvg = "0.45.1"
ring = "0.16.20"
## tor
arti-client = { version = "0.29.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client"] }
+2 -2
View File
@@ -73,7 +73,7 @@ impl Default for StratumSetup {
// Setup mining rewards wallet name and identifier.
let mut wallet_id = NodeConfig::get_stratum_wallet_id();
let wallet_name = if let Some(id) = wallet_id {
WalletConfig::name_by_id(id)
WalletConfig::read_name_by_id(id)
} else {
None
};
@@ -115,7 +115,7 @@ impl ModalContainer for StratumSetup {
self.wallets_modal.ui(ui, modal, &mut self.wallets, cb, |wallet, _| {
let id = wallet.get_config().id;
NodeConfig::save_stratum_wallet_id(id);
self.wallet_name = WalletConfig::name_by_id(id);
self.wallet_name = WalletConfig::read_name_by_id(id);
})
},
STRATUM_PORT_MODAL => self.port_modal(ui, modal, cb),
+1 -1
View File
@@ -588,7 +588,7 @@ impl Handler {
let mut state = self.current_state.write();
let wallet_listener_url = if !config.burn_reward {
if let Ok(id) = config.wallet_listener_url.parse::<i64>() {
if let Some(port) = WalletConfig::api_port_by_id(id) {
if let Some(port) = WalletConfig::read_api_port_by_id(id) {
let url = format!("http://127.0.0.1:{}", port);
Some(url)
} else {
+7
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use grin_core::global;
use grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize};
use crate::gui::views::Content;
@@ -118,6 +119,12 @@ impl AppConfig {
*w_conn_config = ConnectionsConfig::for_chain_type(chain_type);
}
}
if !global::GLOBAL_CHAIN_TYPE.is_init() {
global::init_global_chain_type(*chain_type);
} else {
global::set_global_chain_type(*chain_type);
global::set_local_chain_type(*chain_type);
}
}
/// Get current [`ChainTypes`] for node and wallets.
+9 -1
View File
@@ -21,7 +21,7 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use serde::de::DeserializeOwned;
use serde::Serialize;
use grin_config::ConfigError;
use grin_core::global;
use crate::node::NodeConfig;
use crate::settings::AppConfig;
use crate::tor::TorConfig;
@@ -62,7 +62,15 @@ impl Settings {
let tor_config_path = Settings::config_path(TorConfig::FILE_NAME, None);
let tor_config = Self::init_config::<TorConfig>(tor_config_path);
// Setup chain type.
let chain_type = &app_config.chain_type;
if !global::GLOBAL_CHAIN_TYPE.is_init() {
global::init_global_chain_type(*chain_type);
} else {
global::set_global_chain_type(*chain_type);
global::set_local_chain_type(*chain_type);
}
Self {
node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(chain_type))),
conn_config: Arc::new(RwLock::new(ConnectionsConfig::for_chain_type(chain_type))),
+26 -7
View File
@@ -50,10 +50,16 @@ pub struct WalletConfig {
/// Base wallets directory name.
const BASE_DIR_NAME: &'static str = "wallets";
/// Base wallets directory name.
const DB_DIR_NAME: &'static str = "db";
/// Wallet data directory name.
const DATA_DIR_NAME: &'static str = "wallet_data";
/// Wallet configuration file name.
const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml";
/// Slatepacks directory name.
const SLATEPACKS_DIR_NAME: &'static str = "slatepacks";
/// Seed file name.
const SEED_FILE: &str = "wallet.seed";
/// Default value of minimal amount of confirmations.
const MIN_CONFIRMATIONS_DEFAULT: u64 = 10;
@@ -98,7 +104,7 @@ impl WalletConfig {
}
/// Get wallet name by provided identifier.
pub fn name_by_id(id: i64) -> Option<String> {
pub fn read_name_by_id(id: i64) -> Option<String> {
let mut wallet_dir = WalletConfig::get_base_path(AppConfig::chain_type());
wallet_dir.push(id.to_string());
if let Some(cfg) = Self::load(wallet_dir) {
@@ -108,7 +114,7 @@ impl WalletConfig {
}
/// Get wallet API port by provided identifier.
pub fn api_port_by_id(id: i64) -> Option<u16> {
pub fn read_api_port_by_id(id: i64) -> Option<u16> {
let mut wallet_dir = WalletConfig::get_base_path(AppConfig::chain_type());
wallet_dir.push(id.to_string());
if let Some(cfg) = Self::load(wallet_dir) {
@@ -157,25 +163,38 @@ impl WalletConfig {
config_path
}
/// Get current wallet data path.
pub fn get_data_path(&self) -> String {
/// Get current wallet path.
pub fn get_wallet_path(&self) -> String {
let chain_type = AppConfig::chain_type();
let mut data_path = Self::get_base_path(chain_type);
data_path.push(self.id.to_string());
data_path.to_str().unwrap().to_string()
}
/// Get wallet data path.
pub fn get_data_path(&self) -> String {
let mut data_path = PathBuf::from(self.get_wallet_path());
data_path.push(DATA_DIR_NAME);
data_path.to_str().unwrap().to_string()
}
/// Get wallet seed path.
pub fn seed_path(&self) -> String {
let mut path = PathBuf::from(self.get_data_path());
path.push(SEED_FILE);
path.to_str().unwrap().to_string()
}
/// Get wallet database data path.
pub fn get_db_path(&self) -> String {
let mut path = PathBuf::from(self.get_data_path());
path.push("wallet_data");
path.push("db");
path.push(DB_DIR_NAME);
path.to_str().unwrap().to_string()
}
/// Get Slatepacks data path for current wallet.
pub fn get_slatepack_path(&self, slate: &Slate) -> PathBuf {
let mut path = PathBuf::from(self.get_data_path());
let mut path = PathBuf::from(self.get_wallet_path());
path.push(SLATEPACKS_DIR_NAME);
if !path.exists() {
let _ = fs::create_dir_all(path.clone());
+2 -1
View File
@@ -32,4 +32,5 @@ pub use list::*;
mod utils;
pub use utils::WalletUtils;
pub mod store;
pub mod store;
mod seed;
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2025 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::num::NonZeroU32;
use grin_util::{ToHex, ZeroingString};
use grin_wallet_impls::Error;
use rand::{rng, Rng};
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::fs::File;
use std::io::Write;
use ring::aead;
use ring::pbkdf2;
#[derive(Clone, Debug, PartialEq)]
pub struct WalletSeed(Vec<u8>);
impl WalletSeed {
pub fn from_bytes(bytes: &[u8]) -> WalletSeed {
WalletSeed(bytes.to_vec())
}
pub fn from_mnemonic(word_list: ZeroingString) -> Result<WalletSeed, Error> {
let res = grin_keychain::mnemonic::to_entropy(&word_list);
match res {
Ok(s) => Ok(WalletSeed::from_bytes(&s)),
Err(_) => Err(Error::Mnemonic.into()),
}
}
pub fn init_file(
seed_file_path: &str,
recovery_phrase: ZeroingString,
password: ZeroingString,
) -> Result<WalletSeed, Error> {
let seed = WalletSeed::from_mnemonic(recovery_phrase)?;
let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?;
let enc_seed_json = serde_json::to_string_pretty(&enc_seed).map_err(|_| Error::Format)?;
let mut file = File::create(seed_file_path).map_err(|_| Error::IO)?;
file.write_all(&enc_seed_json.as_bytes())
.map_err(|_| Error::IO)?;
Ok(seed)
}
}
/// Encrypted wallet seed, for storing on disk and decrypting with provided password.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct EncryptedWalletSeed {
encrypted_seed: String,
pub salt: String,
pub nonce: String,
}
impl EncryptedWalletSeed {
/// Create a new encrypted seed from the given seed + password.
pub fn from_seed(
seed: &WalletSeed,
password: ZeroingString,
) -> Result<EncryptedWalletSeed, Error> {
let salt: [u8; 8] = rng().random();
let nonce: [u8; 12] = rng().random();
let password = password.as_bytes();
let mut key = [0; 32];
pbkdf2::derive(
pbkdf2::PBKDF2_HMAC_SHA512,
NonZeroU32::new(100).unwrap(),
&salt,
password,
&mut key,
);
let content = seed.0.to_vec();
let mut enc_bytes = content;
let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap();
let sealing_key: aead::LessSafeKey = aead::LessSafeKey::new(unbound_key);
let aad = aead::Aad::from(&[]);
let res = sealing_key.seal_in_place_append_tag(
aead::Nonce::assume_unique_for_key(nonce),
aad,
&mut enc_bytes,
);
if let Err(_) = res {
return Err(Error::Encryption);
}
Ok(EncryptedWalletSeed {
encrypted_seed: enc_bytes.to_hex(),
salt: salt.to_hex(),
nonce: nonce.to_hex(),
})
}
}
+48 -38
View File
@@ -12,42 +12,42 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{fs, thread};
use futures::channel::oneshot;
use parking_lot::RwLock;
use rand::Rng;
use serde_json::{json, Value};
use std::fs::File;
use std::io::Write;
use std::net::{SocketAddr, TcpListener};
use std::path::PathBuf;
use std::sync::{Arc, mpsc};
use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::{mpsc, Arc};
use std::thread::Thread;
use std::time::Duration;
use futures::channel::oneshot;
use serde_json::{json, Value};
use std::{fs, thread};
use grin_api::{ApiServer, Router};
use grin_chain::SyncStatus;
use grin_core::global;
use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_util::{Mutex, ToHex};
use grin_util::secp::SecretKey;
use grin_util::types::ZeroingString;
use grin_util::{Mutex, ToHex};
use grin_wallet_api::Owner;
use grin_wallet_controller::command::parse_slatepack;
use grin_wallet_controller::controller;
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlatepackAddress, SlateState, SlateVersion, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletInst, WalletLCProvider};
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient, LMDBBackend};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, retrieve_summary_info, retrieve_txs};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider};
use grin_wallet_util::OnionV3Address;
use rand::Rng;
use crate::AppConfig;
use crate::node::{Node, NodeConfig};
use crate::tor::Tor;
use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig};
use crate::wallet::seed::WalletSeed;
use crate::wallet::store::TxHeightStore;
use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction};
use crate::wallet::types::{ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTransaction};
use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig};
use crate::AppConfig;
/// Contains wallet instance, configuration and state, handles wallet commands.
#[derive(Clone)]
@@ -132,18 +132,32 @@ impl Wallet {
mnemonic: &Mnemonic,
conn_method: &ConnectionMethod
) -> Result<Wallet, Error> {
let mut config = WalletConfig::create(name.clone(), conn_method);
let config = WalletConfig::create(name.clone(), conn_method);
let w = Wallet::new(config.clone());
{
let instance = Self::create_wallet_instance(&mut config)?;
let mut w_lock = instance.lock();
let p = w_lock.lc_provider()?;
p.create_wallet(None,
Some(ZeroingString::from(mnemonic.get_phrase())),
mnemonic.size().entropy_size(),
password.clone(),
false,
)?;
// create directory if it doesn't exist
fs::create_dir_all(config.get_data_path())
.map_err(|_| Error::IO("Directory creation error".to_string()))?;
// Create seed file.
let _ = WalletSeed::init_file(config.seed_path().as_str(),
ZeroingString::from(mnemonic.get_phrase()),
password.clone())
.map_err(|_| Error::IO("Seed file creation error".to_string()))?;
let node_client = Self::create_node_client(&config)?;
let mut wallet: LMDBBackend<'static, HTTPNodeClient, ExtKeychain> =
match LMDBBackend::new(config.get_data_path().as_str(), node_client) {
Err(_) => {
return Err(Error::Lifecycle("DB creation error".to_string()).into());
}
Ok(d) => d,
};
// Save init status of this wallet, to determine whether it needs a full UTXO scan
let mut batch = wallet.batch_no_mask()?;
match mnemonic.mode() {
PhraseMode::Generate => batch.save_init_status(WalletInitStatus::InitNoScanning)?,
PhraseMode::Import => batch.save_init_status(WalletInitStatus::InitNeedsScanning)?,
}
batch.commit()?;
}
Ok(w)
}
@@ -157,18 +171,8 @@ impl Wallet {
None
}
/// Create [`WalletInstance`] from provided [`WalletConfig`].
fn create_wallet_instance(config: &mut WalletConfig) -> Result<WalletInstance, Error> {
// Assume global chain type has already been initialized.
let chain_type = config.chain_type;
if !global::GLOBAL_CHAIN_TYPE.is_init() {
global::init_global_chain_type(chain_type);
} else {
global::set_global_chain_type(chain_type);
global::set_local_chain_type(chain_type);
}
// Setup node client.
/// Create [`HTTPNodeClient`] from provided config.
fn create_node_client(config: &WalletConfig) -> Result<HTTPNodeClient, Error> {
let integrated = || {
let api_url = format!("http://{}", NodeConfig::get_api_address());
let api_secret = NodeConfig::get_api_secret(true);
@@ -183,7 +187,13 @@ impl Wallet {
} else {
integrated()
};
let node_client = HTTPNodeClient::new(&node_api_url, node_secret)?;
Ok(HTTPNodeClient::new(&node_api_url, node_secret)?)
}
/// Create [`WalletInstance`] from provided [`WalletConfig`].
fn create_wallet_instance(config: &mut WalletConfig) -> Result<WalletInstance, Error> {
// Setup node client.
let node_client = Self::create_node_client(config)?;
// Create wallet instance.
let wallet = Self::inst_wallet::<
@@ -208,7 +218,7 @@ impl Wallet {
let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client).unwrap())
as Box<dyn WalletInst<'static, L, C, K>>;
let lc = wallet.lc_provider()?;
lc.set_top_level_directory(config.get_data_path().as_str())?;
lc.set_top_level_directory(config.get_wallet_path().as_str())?;
Ok(Arc::new(Mutex::new(wallet)))
}
@@ -1065,7 +1075,7 @@ impl Wallet {
thread::sleep(Duration::from_millis(100));
}
// Remove wallet files.
let _ = fs::remove_dir_all(wallet_delete.get_config().get_data_path());
let _ = fs::remove_dir_all(wallet_delete.get_config().get_wallet_path());
// Mark wallet as deleted.
wallet_delete.deleted.store(true, Ordering::Relaxed);
// Start sync to close thread.