mirror of
https://github.com/jwinterm/grim.git
synced 2026-10-24 16:38:35 +02:00
build: v0.3.6, format code
This commit is contained in:
+218
-218
@@ -17,43 +17,43 @@ use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_wallet_libwallet::{Slate};
|
||||
use grin_wallet_libwallet::Slate;
|
||||
use rand::Rng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::wallet::ConnectionsConfig;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTx};
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
/// Wallet configuration.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct WalletConfig {
|
||||
/// Current wallet account label.
|
||||
pub account: String,
|
||||
/// Chain type for current wallet.
|
||||
pub chain_type: ChainTypes,
|
||||
/// Identifier for a wallet.
|
||||
pub id: i64,
|
||||
/// Wallet name.
|
||||
pub name: String,
|
||||
/// External connection identifier.
|
||||
pub ext_conn_id: Option<i64>,
|
||||
/// Minimal amount of confirmations.
|
||||
pub min_confirmations: u64,
|
||||
/// Flag to use Dandelion to broadcast transactions.
|
||||
pub use_dandelion: Option<bool>,
|
||||
/// Flag to enable Tor listener on start.
|
||||
pub enable_tor_listener: Option<bool>,
|
||||
/// Wallet API port.
|
||||
pub api_port: Option<u16>,
|
||||
/// Delay in blocks before another transaction broadcasting attempt.
|
||||
pub tx_broadcast_timeout: Option<u64>,
|
||||
/// Current wallet account label.
|
||||
pub account: String,
|
||||
/// Chain type for current wallet.
|
||||
pub chain_type: ChainTypes,
|
||||
/// Identifier for a wallet.
|
||||
pub id: i64,
|
||||
/// Wallet name.
|
||||
pub name: String,
|
||||
/// External connection identifier.
|
||||
pub ext_conn_id: Option<i64>,
|
||||
/// Minimal amount of confirmations.
|
||||
pub min_confirmations: u64,
|
||||
/// Flag to use Dandelion to broadcast transactions.
|
||||
pub use_dandelion: Option<bool>,
|
||||
/// Flag to enable Tor listener on start.
|
||||
pub enable_tor_listener: Option<bool>,
|
||||
/// Wallet API port.
|
||||
pub api_port: Option<u16>,
|
||||
/// Delay in blocks before another transaction broadcasting attempt.
|
||||
pub tx_broadcast_timeout: Option<u64>,
|
||||
|
||||
/// Wallet data directory path.
|
||||
pub data_path: Option<String>,
|
||||
/// Wallet data directory path.
|
||||
pub data_path: Option<String>,
|
||||
|
||||
/// Config version.
|
||||
ver: Option<i32>
|
||||
/// Config version.
|
||||
ver: Option<i32>,
|
||||
}
|
||||
|
||||
/// Base wallets directory name.
|
||||
@@ -73,215 +73,215 @@ const MIN_CONFIRMATIONS_DEFAULT: u64 = 10;
|
||||
const WALLET_CONFIG_VERSION: i32 = 1;
|
||||
|
||||
impl WalletConfig {
|
||||
/// Wallet data directory name.
|
||||
pub const DATA_DIR_NAME: &'static str = "wallet_data";
|
||||
/// Wallet data directory name.
|
||||
pub const DATA_DIR_NAME: &'static str = "wallet_data";
|
||||
|
||||
/// Default account name value.
|
||||
pub const DEFAULT_ACCOUNT_LABEL: &'static str = "default";
|
||||
/// Default account name value.
|
||||
pub const DEFAULT_ACCOUNT_LABEL: &'static str = "default";
|
||||
|
||||
/// Default value of timeout for broadcasting transaction in blocks.
|
||||
pub const BROADCASTING_TIMEOUT_DEFAULT: u64 = 10;
|
||||
/// Default value of timeout for broadcasting transaction in blocks.
|
||||
pub const BROADCASTING_TIMEOUT_DEFAULT: u64 = 10;
|
||||
|
||||
/// Create new wallet config.
|
||||
pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig {
|
||||
// Setup configuration path.
|
||||
let id = chrono::Utc::now().timestamp();
|
||||
let chain_type = AppConfig::chain_type();
|
||||
let config_path = Self::get_config_file_path(chain_type, id);
|
||||
// Write configuration to the file.
|
||||
let config = WalletConfig {
|
||||
account: Self::DEFAULT_ACCOUNT_LABEL.to_string(),
|
||||
chain_type,
|
||||
id,
|
||||
name,
|
||||
ext_conn_id: match conn_method {
|
||||
ConnectionMethod::Integrated => None,
|
||||
ConnectionMethod::External(id, _) => Some(*id)
|
||||
},
|
||||
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
|
||||
use_dandelion: Some(true),
|
||||
enable_tor_listener: Some(false),
|
||||
api_port: Some(rand::rng().random_range(10000..30000)),
|
||||
tx_broadcast_timeout: Some(Self::BROADCASTING_TIMEOUT_DEFAULT),
|
||||
data_path: Some(Self::wallet_path(id.to_string())),
|
||||
ver: Some(WALLET_CONFIG_VERSION),
|
||||
};
|
||||
Settings::write_to_file(&config, config_path);
|
||||
config
|
||||
}
|
||||
/// Create new wallet config.
|
||||
pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig {
|
||||
// Setup configuration path.
|
||||
let id = chrono::Utc::now().timestamp();
|
||||
let chain_type = AppConfig::chain_type();
|
||||
let config_path = Self::get_config_file_path(chain_type, id);
|
||||
// Write configuration to the file.
|
||||
let config = WalletConfig {
|
||||
account: Self::DEFAULT_ACCOUNT_LABEL.to_string(),
|
||||
chain_type,
|
||||
id,
|
||||
name,
|
||||
ext_conn_id: match conn_method {
|
||||
ConnectionMethod::Integrated => None,
|
||||
ConnectionMethod::External(id, _) => Some(*id),
|
||||
},
|
||||
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
|
||||
use_dandelion: Some(true),
|
||||
enable_tor_listener: Some(false),
|
||||
api_port: Some(rand::rng().random_range(10000..30000)),
|
||||
tx_broadcast_timeout: Some(Self::BROADCASTING_TIMEOUT_DEFAULT),
|
||||
data_path: Some(Self::wallet_path(id.to_string())),
|
||||
ver: Some(WALLET_CONFIG_VERSION),
|
||||
};
|
||||
Settings::write_to_file(&config, config_path);
|
||||
config
|
||||
}
|
||||
|
||||
/// Load config from provided wallet dir.
|
||||
pub fn load(wallet_dir: PathBuf) -> Option<WalletConfig> {
|
||||
let mut config_path: PathBuf = wallet_dir.clone();
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
if let Ok(mut config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||
config.migrate();
|
||||
return Some(config)
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Load config from provided wallet dir.
|
||||
pub fn load(wallet_dir: PathBuf) -> Option<WalletConfig> {
|
||||
let mut config_path: PathBuf = wallet_dir.clone();
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
if let Ok(mut config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||
config.migrate();
|
||||
return Some(config);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get wallet name by provided identifier.
|
||||
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) {
|
||||
return Some(cfg.name);
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Get wallet name by provided identifier.
|
||||
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) {
|
||||
return Some(cfg.name);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get wallet API port by provided identifier.
|
||||
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) {
|
||||
return cfg.api_port;
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Get wallet API port by provided identifier.
|
||||
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) {
|
||||
return cfg.api_port;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get wallet connection method.
|
||||
pub fn connection(&self) -> ConnectionMethod {
|
||||
if let Some(ext_conn_id) = self.ext_conn_id {
|
||||
if let Some(conn) = ConnectionsConfig::ext_conn(ext_conn_id) {
|
||||
return ConnectionMethod::External(conn.id, conn.url);
|
||||
}
|
||||
}
|
||||
ConnectionMethod::Integrated
|
||||
}
|
||||
/// Get wallet connection method.
|
||||
pub fn connection(&self) -> ConnectionMethod {
|
||||
if let Some(ext_conn_id) = self.ext_conn_id {
|
||||
if let Some(conn) = ConnectionsConfig::ext_conn(ext_conn_id) {
|
||||
return ConnectionMethod::External(conn.id, conn.url);
|
||||
}
|
||||
}
|
||||
ConnectionMethod::Integrated
|
||||
}
|
||||
|
||||
/// Save wallet config.
|
||||
pub fn save(&self) {
|
||||
let config_path = Self::get_config_file_path(self.chain_type, self.id);
|
||||
Settings::write_to_file(self, config_path);
|
||||
}
|
||||
/// Save wallet config.
|
||||
pub fn save(&self) {
|
||||
let config_path = Self::get_config_file_path(self.chain_type, self.id);
|
||||
Settings::write_to_file(self, config_path);
|
||||
}
|
||||
|
||||
/// Get wallets base directory path for provided [`ChainTypes`].
|
||||
pub fn get_base_path(chain_type: ChainTypes) -> PathBuf {
|
||||
let sub_dir = Some(chain_type.shortname());
|
||||
let mut wallets_path = Settings::base_path(sub_dir);
|
||||
wallets_path.push(BASE_DIR_NAME);
|
||||
// Create wallets base directory if it doesn't exist.
|
||||
if !wallets_path.exists() {
|
||||
let _ = fs::create_dir_all(wallets_path.clone());
|
||||
}
|
||||
wallets_path
|
||||
}
|
||||
/// Get wallets base directory path for provided [`ChainTypes`].
|
||||
pub fn get_base_path(chain_type: ChainTypes) -> PathBuf {
|
||||
let sub_dir = Some(chain_type.shortname());
|
||||
let mut wallets_path = Settings::base_path(sub_dir);
|
||||
wallets_path.push(BASE_DIR_NAME);
|
||||
// Create wallets base directory if it doesn't exist.
|
||||
if !wallets_path.exists() {
|
||||
let _ = fs::create_dir_all(wallets_path.clone());
|
||||
}
|
||||
wallets_path
|
||||
}
|
||||
|
||||
/// Get config file path for provided [`ChainTypes`] and wallet identifier.
|
||||
fn get_config_file_path(chain_type: ChainTypes, id: i64) -> PathBuf {
|
||||
let mut config_path = Self::get_base_path(chain_type);
|
||||
config_path.push(id.to_string());
|
||||
// Create if the config path doesn't exist.
|
||||
if !config_path.exists() {
|
||||
let _ = fs::create_dir_all(config_path.clone());
|
||||
}
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
config_path
|
||||
}
|
||||
/// Get config file path for provided [`ChainTypes`] and wallet identifier.
|
||||
fn get_config_file_path(chain_type: ChainTypes, id: i64) -> PathBuf {
|
||||
let mut config_path = Self::get_base_path(chain_type);
|
||||
config_path.push(id.to_string());
|
||||
// Create if the config path doesn't exist.
|
||||
if !config_path.exists() {
|
||||
let _ = fs::create_dir_all(config_path.clone());
|
||||
}
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
config_path
|
||||
}
|
||||
|
||||
/// Get wallet path from provided identifier.
|
||||
fn wallet_path(id: String) -> String {
|
||||
let chain_type = AppConfig::chain_type();
|
||||
let mut data_path = Self::get_base_path(chain_type);
|
||||
data_path.push(id);
|
||||
data_path.to_str().unwrap().to_string()
|
||||
}
|
||||
/// Get wallet path from provided identifier.
|
||||
fn wallet_path(id: String) -> String {
|
||||
let chain_type = AppConfig::chain_type();
|
||||
let mut data_path = Self::get_base_path(chain_type);
|
||||
data_path.push(id);
|
||||
data_path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get current wallet path.
|
||||
pub fn get_wallet_path(&self) -> String {
|
||||
Self::wallet_path(self.id.to_string())
|
||||
}
|
||||
/// Get current wallet path.
|
||||
pub fn get_wallet_path(&self) -> String {
|
||||
Self::wallet_path(self.id.to_string())
|
||||
}
|
||||
|
||||
/// Get wallet data path.
|
||||
pub fn get_data_path(&self) -> String {
|
||||
if let Some(path) = &self.data_path {
|
||||
path.clone()
|
||||
} else {
|
||||
self.get_wallet_path()
|
||||
}
|
||||
}
|
||||
/// Get wallet data path.
|
||||
pub fn get_data_path(&self) -> String {
|
||||
if let Some(path) = &self.data_path {
|
||||
path.clone()
|
||||
} else {
|
||||
self.get_wallet_path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get base wallet data path.
|
||||
fn get_base_data_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
/// Get base wallet data path.
|
||||
fn get_base_data_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get wallet seed path.
|
||||
pub fn seed_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SEED_FILE);
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
/// Get wallet seed path.
|
||||
pub fn seed_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_base_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_base_data_path());
|
||||
path.push(DB_DIR_NAME);
|
||||
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_base_data_path());
|
||||
path.push(DB_DIR_NAME);
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get Slatepack file path for transaction.
|
||||
pub fn get_tx_slate_path(&self, tx: &WalletTx) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
let file = format!("{}.{}.slatepack", tx.data.tx_slate_id.unwrap(), tx.state);
|
||||
path.push(file);
|
||||
path
|
||||
}
|
||||
/// Get Slatepack file path for transaction.
|
||||
pub fn get_tx_slate_path(&self, tx: &WalletTx) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
let file = format!("{}.{}.slatepack", tx.data.tx_slate_id.unwrap(), tx.state);
|
||||
path.push(file);
|
||||
path
|
||||
}
|
||||
|
||||
/// Get Slatepack file path for Slate.
|
||||
pub fn get_slate_path(&self, slate: &Slate) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
let file = format!("{}.{}.slatepack", slate.id, slate.state);
|
||||
path.push(file);
|
||||
path
|
||||
}
|
||||
/// Get Slatepack file path for Slate.
|
||||
pub fn get_slate_path(&self, slate: &Slate) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_base_data_path());
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
let file = format!("{}.{}.slatepack", slate.id, slate.state);
|
||||
path.push(file);
|
||||
path
|
||||
}
|
||||
|
||||
/// Get path to extra db storage.
|
||||
pub fn get_extra_db_path(&self) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_db_path());
|
||||
path.push("extra");
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
path
|
||||
}
|
||||
/// Get path to extra db storage.
|
||||
pub fn get_extra_db_path(&self) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_db_path());
|
||||
path.push("extra");
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/// Check config version to migrate if needed.
|
||||
fn migrate(&mut self) {
|
||||
match self.ver {
|
||||
None => {
|
||||
// Migrate Slatepack data.
|
||||
let mut old_slate_path = PathBuf::from(self.get_wallet_path());
|
||||
old_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
if old_slate_path.exists() {
|
||||
let mut new_slate_path = PathBuf::from(self.get_data_path());
|
||||
new_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
let _ = fs::rename(&old_slate_path, &new_slate_path);
|
||||
}
|
||||
// Write data path to config.
|
||||
if self.data_path.is_none() {
|
||||
self.data_path = Some(self.get_wallet_path());
|
||||
}
|
||||
// Migrate to 1st version.
|
||||
self.ver = Some(1);
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
self.save();
|
||||
}
|
||||
}
|
||||
/// Check config version to migrate if needed.
|
||||
fn migrate(&mut self) {
|
||||
match self.ver {
|
||||
None => {
|
||||
// Migrate Slatepack data.
|
||||
let mut old_slate_path = PathBuf::from(self.get_wallet_path());
|
||||
old_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
if old_slate_path.exists() {
|
||||
let mut new_slate_path = PathBuf::from(self.get_data_path());
|
||||
new_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
let _ = fs::rename(&old_slate_path, &new_slate_path);
|
||||
}
|
||||
// Write data path to config.
|
||||
if self.data_path.is_none() {
|
||||
self.data_path = Some(self.get_wallet_path());
|
||||
}
|
||||
// Migrate to 1st version.
|
||||
self.ver = Some(1);
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
self.save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,92 +15,99 @@
|
||||
use grin_core::global::ChainTypes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::wallet::ExternalConnection;
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
/// Wallet connections configuration.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ConnectionsConfig {
|
||||
/// Network type for connections.
|
||||
chain_type: ChainTypes,
|
||||
/// URLs of external connections for wallets.
|
||||
external: Vec<ExternalConnection>
|
||||
/// Network type for connections.
|
||||
chain_type: ChainTypes,
|
||||
/// URLs of external connections for wallets.
|
||||
external: Vec<ExternalConnection>,
|
||||
}
|
||||
|
||||
impl ConnectionsConfig {
|
||||
/// Wallet connections configuration file name.
|
||||
pub const FILE_NAME: &'static str = "connections.toml";
|
||||
/// Wallet connections configuration file name.
|
||||
pub const FILE_NAME: &'static str = "connections.toml";
|
||||
|
||||
/// Initialize configuration for provided [`ChainTypes`].
|
||||
pub fn for_chain_type(chain_type: &ChainTypes) -> Self {
|
||||
let path = Settings::config_path(Self::FILE_NAME, Some(chain_type.shortname()));
|
||||
let parsed = Settings::read_from_file::<ConnectionsConfig>(path.clone());
|
||||
if !path.exists() || parsed.is_err() {
|
||||
let default_config = ConnectionsConfig {
|
||||
chain_type: *chain_type,
|
||||
external: ExternalConnection::default(chain_type),
|
||||
};
|
||||
Settings::write_to_file(&default_config, path);
|
||||
default_config
|
||||
} else {
|
||||
parsed.unwrap()
|
||||
}
|
||||
}
|
||||
/// Initialize configuration for provided [`ChainTypes`].
|
||||
pub fn for_chain_type(chain_type: &ChainTypes) -> Self {
|
||||
let path = Settings::config_path(Self::FILE_NAME, Some(chain_type.shortname()));
|
||||
let parsed = Settings::read_from_file::<ConnectionsConfig>(path.clone());
|
||||
if !path.exists() || parsed.is_err() {
|
||||
let default_config = ConnectionsConfig {
|
||||
chain_type: *chain_type,
|
||||
external: ExternalConnection::default(chain_type),
|
||||
};
|
||||
Settings::write_to_file(&default_config, path);
|
||||
default_config
|
||||
} else {
|
||||
parsed.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Save connections configuration.
|
||||
pub fn save(&mut self) {
|
||||
let config = self.clone();
|
||||
let sub_dir = Some(AppConfig::chain_type().shortname());
|
||||
Settings::write_to_file(&config, Settings::config_path(Self::FILE_NAME, sub_dir));
|
||||
}
|
||||
/// Save connections configuration.
|
||||
pub fn save(&mut self) {
|
||||
let config = self.clone();
|
||||
let sub_dir = Some(AppConfig::chain_type().shortname());
|
||||
Settings::write_to_file(&config, Settings::config_path(Self::FILE_NAME, sub_dir));
|
||||
}
|
||||
|
||||
/// Get [`ExternalConnection`] list.
|
||||
pub fn ext_conn_list() -> Vec<ExternalConnection> {
|
||||
let r_config = Settings::conn_config_to_read();
|
||||
r_config.external.clone()
|
||||
}
|
||||
/// Get [`ExternalConnection`] list.
|
||||
pub fn ext_conn_list() -> Vec<ExternalConnection> {
|
||||
let r_config = Settings::conn_config_to_read();
|
||||
r_config.external.clone()
|
||||
}
|
||||
|
||||
/// Save [`ExternalConnection`] in configuration.
|
||||
pub fn add_ext_conn(conn: ExternalConnection) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
if let Some(pos) = w_config.external.iter().position(|c| {
|
||||
c.id == conn.id || c.url == conn.url
|
||||
}) {
|
||||
w_config.external.remove(pos);
|
||||
w_config.external.insert(pos, conn);
|
||||
} else {
|
||||
w_config.external.push(conn);
|
||||
}
|
||||
w_config.save();
|
||||
}
|
||||
/// Save [`ExternalConnection`] in configuration.
|
||||
pub fn add_ext_conn(conn: ExternalConnection) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
if let Some(pos) = w_config
|
||||
.external
|
||||
.iter()
|
||||
.position(|c| c.id == conn.id || c.url == conn.url)
|
||||
{
|
||||
w_config.external.remove(pos);
|
||||
w_config.external.insert(pos, conn);
|
||||
} else {
|
||||
w_config.external.push(conn);
|
||||
}
|
||||
w_config.save();
|
||||
}
|
||||
|
||||
/// Get external node connection with provided identifier.
|
||||
pub fn ext_conn(id: i64) -> Option<ExternalConnection> {
|
||||
let r_config = Settings::conn_config_to_read();
|
||||
for c in &r_config.external {
|
||||
if c.id == id {
|
||||
return Some(c.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Get external node connection with provided identifier.
|
||||
pub fn ext_conn(id: i64) -> Option<ExternalConnection> {
|
||||
let r_config = Settings::conn_config_to_read();
|
||||
for c in &r_config.external {
|
||||
if c.id == id {
|
||||
return Some(c.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Set [`ExternalConnection`] availability flag.
|
||||
pub fn update_ext_conn_status(id: i64, available: Option<bool>) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
for c in w_config.external.iter_mut() {
|
||||
if c.id == id {
|
||||
c.available = available;
|
||||
w_config.save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Set [`ExternalConnection`] availability flag.
|
||||
pub fn update_ext_conn_status(id: i64, available: Option<bool>) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
for c in w_config.external.iter_mut() {
|
||||
if c.id == id {
|
||||
c.available = available;
|
||||
w_config.save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove [`ExternalConnection`] with provided identifier.
|
||||
pub fn remove_ext_conn(id: i64) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
w_config.external = w_config.external.iter().filter(|c| c.id != id).cloned().collect();
|
||||
w_config.save();
|
||||
}
|
||||
}
|
||||
/// Remove [`ExternalConnection`] with provided identifier.
|
||||
pub fn remove_ext_conn(id: i64) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
w_config.external = w_config
|
||||
.external
|
||||
.iter()
|
||||
.filter(|c| c.id != id)
|
||||
.cloned()
|
||||
.collect();
|
||||
w_config.save();
|
||||
}
|
||||
}
|
||||
|
||||
+126
-125
@@ -24,146 +24,147 @@ use crate::wallet::ConnectionsConfig;
|
||||
/// External connection for the wallet.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct ExternalConnection {
|
||||
/// Connection identifier.
|
||||
pub id: i64,
|
||||
/// Node URL.
|
||||
pub url: String,
|
||||
/// Optional username.
|
||||
pub username: Option<String>,
|
||||
/// Optional API secret key.
|
||||
pub secret: Option<String>,
|
||||
/// Connection identifier.
|
||||
pub id: i64,
|
||||
/// Node URL.
|
||||
pub url: String,
|
||||
/// Optional username.
|
||||
pub username: Option<String>,
|
||||
/// Optional API secret key.
|
||||
pub secret: Option<String>,
|
||||
|
||||
/// Flag to check if server is available.
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub available: Option<bool>,
|
||||
/// Flag to check if server is available.
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub available: Option<bool>,
|
||||
}
|
||||
|
||||
/// Default external node URL for main network.
|
||||
const DEFAULT_MAIN_URLS: [&'static str; 3] = [
|
||||
"https://main.gri.mw",
|
||||
"https://grincoin.org",
|
||||
"https://mainnet.grinffindor.org"
|
||||
"https://main.gri.mw",
|
||||
"https://grincoin.org",
|
||||
"https://mainnet.grinffindor.org",
|
||||
];
|
||||
|
||||
/// Default external node URL for main network.
|
||||
const DEFAULT_TEST_URLS: [&'static str; 3] = [
|
||||
"https://test.gri.mw",
|
||||
"https://testnet.grincoin.org",
|
||||
"https://testnet.grinffindor.org"
|
||||
"https://test.gri.mw",
|
||||
"https://testnet.grincoin.org",
|
||||
"https://testnet.grinffindor.org",
|
||||
];
|
||||
|
||||
impl ExternalConnection {
|
||||
/// Get default connections for provided chain type.
|
||||
pub fn default(chain_type: &ChainTypes) -> Vec<ExternalConnection> {
|
||||
let urls = match chain_type {
|
||||
ChainTypes::Mainnet => DEFAULT_MAIN_URLS.to_vec(),
|
||||
_ => DEFAULT_TEST_URLS.to_vec()
|
||||
};
|
||||
urls.iter().enumerate().map(|(index, url)| {
|
||||
ExternalConnection {
|
||||
id: index as i64,
|
||||
url: url.to_string(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: None,
|
||||
}
|
||||
}).collect::<Vec<ExternalConnection>>()
|
||||
}
|
||||
/// Get default connections for provided chain type.
|
||||
pub fn default(chain_type: &ChainTypes) -> Vec<ExternalConnection> {
|
||||
let urls = match chain_type {
|
||||
ChainTypes::Mainnet => DEFAULT_MAIN_URLS.to_vec(),
|
||||
_ => DEFAULT_TEST_URLS.to_vec(),
|
||||
};
|
||||
urls.iter()
|
||||
.enumerate()
|
||||
.map(|(index, url)| ExternalConnection {
|
||||
id: index as i64,
|
||||
url: url.to_string(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: None,
|
||||
})
|
||||
.collect::<Vec<ExternalConnection>>()
|
||||
}
|
||||
|
||||
/// Create new external connection.
|
||||
pub fn new(url: String, username: Option<String>, secret: Option<String>) -> Self {
|
||||
let id = chrono::Utc::now().timestamp();
|
||||
Self {
|
||||
id,
|
||||
url,
|
||||
username,
|
||||
secret,
|
||||
available: None,
|
||||
}
|
||||
}
|
||||
/// Create new external connection.
|
||||
pub fn new(url: String, username: Option<String>, secret: Option<String>) -> Self {
|
||||
let id = chrono::Utc::now().timestamp();
|
||||
Self {
|
||||
id,
|
||||
url,
|
||||
username,
|
||||
secret,
|
||||
available: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check external connection availability.
|
||||
pub fn check(id: Option<i64>, ui_ctx: &egui::Context) {
|
||||
let conn_list = ConnectionsConfig::ext_conn_list();
|
||||
for conn in conn_list {
|
||||
if let Some(id) = id {
|
||||
if id == conn.id {
|
||||
check_ext_conn(&conn, ui_ctx);
|
||||
}
|
||||
} else {
|
||||
check_ext_conn(&conn, ui_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Check external connection availability.
|
||||
pub fn check(id: Option<i64>, ui_ctx: &egui::Context) {
|
||||
let conn_list = ConnectionsConfig::ext_conn_list();
|
||||
for conn in conn_list {
|
||||
if let Some(id) = id {
|
||||
if id == conn.id {
|
||||
check_ext_conn(&conn, ui_ctx);
|
||||
}
|
||||
} else {
|
||||
check_ext_conn(&conn, ui_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check connection availability.
|
||||
fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
|
||||
let conn = conn.clone();
|
||||
let ui_ctx = ui_ctx.clone();
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, None);
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
// Check URL format.
|
||||
let conn_url = url::Url::parse(conn.url.as_str());
|
||||
let url_res = match conn_url {
|
||||
Ok(url) => Some(url),
|
||||
Err(_) => {
|
||||
let mut url_res = None;
|
||||
if !conn.url.starts_with("http") {
|
||||
let mut conn = conn.clone();
|
||||
let url_text = format!("https://{}", conn.url);
|
||||
let url = url::Url::parse(url_text.as_str());
|
||||
if let Ok(url) = url {
|
||||
url_res = Some(url);
|
||||
conn.url = url_text;
|
||||
ConnectionsConfig::add_ext_conn(conn.clone());
|
||||
}
|
||||
}
|
||||
url_res
|
||||
}
|
||||
};
|
||||
if url_res.is_none() {
|
||||
ConnectionsConfig::remove_ext_conn(conn.id);
|
||||
return;
|
||||
}
|
||||
let url = url_res.unwrap();
|
||||
if let Ok(_) = url.socket_addrs(|| None) {
|
||||
let addr = format!("{}v2/foreign", url.to_string());
|
||||
let mut req_setup = hyper::Request::builder()
|
||||
.method(hyper::Method::POST)
|
||||
.uri(addr.clone());
|
||||
// Setup secret key auth.
|
||||
if let Some(key) = conn.secret {
|
||||
let username = conn.username.unwrap_or("grin".to_string());
|
||||
let basic_auth = format!(
|
||||
"Basic {}",
|
||||
to_base64(&format!("{}:{}", username, key))
|
||||
);
|
||||
req_setup = req_setup
|
||||
.header(hyper::header::AUTHORIZATION, basic_auth.clone());
|
||||
}
|
||||
let req: hyper::Request<Full<Bytes>> = req_setup.body(Full::from(
|
||||
r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#)
|
||||
).unwrap();
|
||||
// Send request.
|
||||
match HttpClient::send(req).await {
|
||||
Ok(res) => {
|
||||
let status = res.status().as_u16();
|
||||
// Available on 200 HTTP status code.
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(status == 200));
|
||||
}
|
||||
Err(_) => ConnectionsConfig::update_ext_conn_status(conn.id, Some(false))
|
||||
}
|
||||
} else {
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(false));
|
||||
}
|
||||
// Repaint ui on change.
|
||||
ui_ctx.request_repaint();
|
||||
});
|
||||
});
|
||||
}
|
||||
let conn = conn.clone();
|
||||
let ui_ctx = ui_ctx.clone();
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, None);
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
// Check URL format.
|
||||
let conn_url = url::Url::parse(conn.url.as_str());
|
||||
let url_res = match conn_url {
|
||||
Ok(url) => Some(url),
|
||||
Err(_) => {
|
||||
let mut url_res = None;
|
||||
if !conn.url.starts_with("http") {
|
||||
let mut conn = conn.clone();
|
||||
let url_text = format!("https://{}", conn.url);
|
||||
let url = url::Url::parse(url_text.as_str());
|
||||
if let Ok(url) = url {
|
||||
url_res = Some(url);
|
||||
conn.url = url_text;
|
||||
ConnectionsConfig::add_ext_conn(conn.clone());
|
||||
}
|
||||
}
|
||||
url_res
|
||||
}
|
||||
};
|
||||
if url_res.is_none() {
|
||||
ConnectionsConfig::remove_ext_conn(conn.id);
|
||||
return;
|
||||
}
|
||||
let url = url_res.unwrap();
|
||||
if let Ok(_) = url.socket_addrs(|| None) {
|
||||
let addr = format!("{}v2/foreign", url.to_string());
|
||||
let mut req_setup = hyper::Request::builder()
|
||||
.method(hyper::Method::POST)
|
||||
.uri(addr.clone());
|
||||
// Setup secret key auth.
|
||||
if let Some(key) = conn.secret {
|
||||
let username = conn.username.unwrap_or("grin".to_string());
|
||||
let basic_auth =
|
||||
format!("Basic {}", to_base64(&format!("{}:{}", username, key)));
|
||||
req_setup =
|
||||
req_setup.header(hyper::header::AUTHORIZATION, basic_auth.clone());
|
||||
}
|
||||
let req: hyper::Request<Full<Bytes>> = req_setup
|
||||
.body(Full::from(
|
||||
r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#,
|
||||
))
|
||||
.unwrap();
|
||||
// Send request.
|
||||
match HttpClient::send(req).await {
|
||||
Ok(res) => {
|
||||
let status = res.status().as_u16();
|
||||
// Available on 200 HTTP status code.
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(status == 200));
|
||||
}
|
||||
Err(_) => ConnectionsConfig::update_ext_conn_status(conn.id, Some(false)),
|
||||
}
|
||||
} else {
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(false));
|
||||
}
|
||||
// Repaint ui on change.
|
||||
ui_ctx.request_repaint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@ mod external;
|
||||
pub use external::ExternalConnection;
|
||||
|
||||
mod config;
|
||||
pub use config::ConnectionsConfig;
|
||||
pub use config::ConnectionsConfig;
|
||||
|
||||
+90
-90
@@ -20,106 +20,106 @@ use crate::wallet::{Wallet, WalletConfig};
|
||||
/// [`Wallet`] list container.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletList {
|
||||
/// List of wallets for [`ChainTypes::Mainnet`].
|
||||
pub main_list: Vec<Wallet>,
|
||||
/// List of wallets for [`ChainTypes::Testnet`].
|
||||
pub test_list: Vec<Wallet>,
|
||||
/// List of wallets for [`ChainTypes::Mainnet`].
|
||||
pub main_list: Vec<Wallet>,
|
||||
/// List of wallets for [`ChainTypes::Testnet`].
|
||||
pub test_list: Vec<Wallet>,
|
||||
|
||||
/// Selected wallet id.
|
||||
selected: Option<i64>,
|
||||
/// Selected wallet id.
|
||||
selected: Option<i64>,
|
||||
}
|
||||
|
||||
impl Default for WalletList {
|
||||
fn default() -> Self {
|
||||
let (main_list, test_list) = Self::init();
|
||||
Self {
|
||||
main_list,
|
||||
test_list,
|
||||
selected: None
|
||||
}
|
||||
}
|
||||
fn default() -> Self {
|
||||
let (main_list, test_list) = Self::init();
|
||||
Self {
|
||||
main_list,
|
||||
test_list,
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletList {
|
||||
/// Initialize [`Wallet`] lists for [`ChainTypes::Mainnet`] and [`ChainTypes::Testnet`].
|
||||
fn init() -> (Vec<Wallet>, Vec<Wallet>) {
|
||||
let mut main_wallets = Vec::new();
|
||||
let mut test_wallets = Vec::new();
|
||||
let chain_types = vec![ChainTypes::Mainnet, ChainTypes::Testnet];
|
||||
for chain in chain_types {
|
||||
// initialize wallets from base directory.
|
||||
let wallets_dir = WalletConfig::get_base_path(chain);
|
||||
for dir in wallets_dir.read_dir().unwrap() {
|
||||
let wallet_dir = dir.unwrap().path();
|
||||
if wallet_dir.is_dir() {
|
||||
let wallet = Wallet::init(wallet_dir);
|
||||
if let Some(w) = wallet {
|
||||
if chain == ChainTypes::Testnet {
|
||||
test_wallets.push(w);
|
||||
} else if chain == ChainTypes::Mainnet {
|
||||
main_wallets.push(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort wallets by id.
|
||||
main_wallets.sort_by_key(|w| -w.get_config().id);
|
||||
test_wallets.sort_by_key(|w| -w.get_config().id);
|
||||
(main_wallets, test_wallets)
|
||||
}
|
||||
/// Initialize [`Wallet`] lists for [`ChainTypes::Mainnet`] and [`ChainTypes::Testnet`].
|
||||
fn init() -> (Vec<Wallet>, Vec<Wallet>) {
|
||||
let mut main_wallets = Vec::new();
|
||||
let mut test_wallets = Vec::new();
|
||||
let chain_types = vec![ChainTypes::Mainnet, ChainTypes::Testnet];
|
||||
for chain in chain_types {
|
||||
// initialize wallets from base directory.
|
||||
let wallets_dir = WalletConfig::get_base_path(chain);
|
||||
for dir in wallets_dir.read_dir().unwrap() {
|
||||
let wallet_dir = dir.unwrap().path();
|
||||
if wallet_dir.is_dir() {
|
||||
let wallet = Wallet::init(wallet_dir);
|
||||
if let Some(w) = wallet {
|
||||
if chain == ChainTypes::Testnet {
|
||||
test_wallets.push(w);
|
||||
} else if chain == ChainTypes::Mainnet {
|
||||
main_wallets.push(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort wallets by id.
|
||||
main_wallets.sort_by_key(|w| -w.get_config().id);
|
||||
test_wallets.sort_by_key(|w| -w.get_config().id);
|
||||
(main_wallets, test_wallets)
|
||||
}
|
||||
|
||||
/// Get [`Wallet`] list for current [`ChainTypes`].
|
||||
pub fn list(&self) -> &Vec<Wallet> {
|
||||
if AppConfig::chain_type() == ChainTypes::Mainnet {
|
||||
&self.main_list
|
||||
} else {
|
||||
&self.test_list
|
||||
}
|
||||
}
|
||||
/// Get [`Wallet`] list for current [`ChainTypes`].
|
||||
pub fn list(&self) -> &Vec<Wallet> {
|
||||
if AppConfig::chain_type() == ChainTypes::Mainnet {
|
||||
&self.main_list
|
||||
} else {
|
||||
&self.test_list
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable [`Wallet`] list for current [`ChainTypes`].
|
||||
pub fn mut_list(&mut self) -> &mut Vec<Wallet> {
|
||||
if AppConfig::chain_type() == ChainTypes::Mainnet {
|
||||
&mut self.main_list
|
||||
} else {
|
||||
&mut self.test_list
|
||||
}
|
||||
}
|
||||
/// Get mutable [`Wallet`] list for current [`ChainTypes`].
|
||||
pub fn mut_list(&mut self) -> &mut Vec<Wallet> {
|
||||
if AppConfig::chain_type() == ChainTypes::Mainnet {
|
||||
&mut self.main_list
|
||||
} else {
|
||||
&mut self.test_list
|
||||
}
|
||||
}
|
||||
|
||||
/// Add created [`Wallet`] to the list.
|
||||
pub fn add(&mut self, wallet: Wallet) {
|
||||
let list = self.mut_list();
|
||||
list.insert(0, wallet);
|
||||
}
|
||||
/// Add created [`Wallet`] to the list.
|
||||
pub fn add(&mut self, wallet: Wallet) {
|
||||
let list = self.mut_list();
|
||||
list.insert(0, wallet);
|
||||
}
|
||||
|
||||
/// Remove [`Wallet`] with provided identifier.
|
||||
pub fn remove(&mut self, id: i64) {
|
||||
let list = self.mut_list();
|
||||
for (index, wallet) in list.iter().enumerate() {
|
||||
if wallet.get_config().id == id {
|
||||
list.remove(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Remove [`Wallet`] with provided identifier.
|
||||
pub fn remove(&mut self, id: i64) {
|
||||
let list = self.mut_list();
|
||||
for (index, wallet) in list.iter().enumerate() {
|
||||
if wallet.get_config().id == id {
|
||||
list.remove(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Select wallet.
|
||||
pub fn select(&mut self, id: Option<i64>) {
|
||||
self.selected = id;
|
||||
}
|
||||
/// Select wallet.
|
||||
pub fn select(&mut self, id: Option<i64>) {
|
||||
self.selected = id;
|
||||
}
|
||||
|
||||
/// Get selected wallet.
|
||||
pub fn selected(&self) -> Box<Option<&Wallet>> {
|
||||
if self.selected.is_none() {
|
||||
return Box::new(None);
|
||||
}
|
||||
let list = self.list();
|
||||
for wallet in list {
|
||||
if wallet.get_config().id == self.selected.unwrap() {
|
||||
return Box::new(Some(wallet));
|
||||
}
|
||||
}
|
||||
Box::new(None)
|
||||
}
|
||||
}
|
||||
/// Get selected wallet.
|
||||
pub fn selected(&self) -> Box<Option<&Wallet>> {
|
||||
if self.selected.is_none() {
|
||||
return Box::new(None);
|
||||
}
|
||||
let list = self.list();
|
||||
for wallet in list {
|
||||
if wallet.get_config().id == self.selected.unwrap() {
|
||||
return Box::new(Some(wallet));
|
||||
}
|
||||
}
|
||||
Box::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
+213
-197
@@ -20,221 +20,237 @@ use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord};
|
||||
|
||||
/// Mnemonic phrase container.
|
||||
pub struct Mnemonic {
|
||||
/// Phrase setup mode.
|
||||
mode: PhraseMode,
|
||||
/// Size of phrase based on words count.
|
||||
size: PhraseSize,
|
||||
/// Generated words.
|
||||
words: Vec<PhraseWord>,
|
||||
/// Words to confirm the phrase.
|
||||
confirmation: Vec<PhraseWord>,
|
||||
/// Flag to check if entered phrase if valid.
|
||||
valid: bool,
|
||||
/// Phrase setup mode.
|
||||
mode: PhraseMode,
|
||||
/// Size of phrase based on words count.
|
||||
size: PhraseSize,
|
||||
/// Generated words.
|
||||
words: Vec<PhraseWord>,
|
||||
/// Words to confirm the phrase.
|
||||
confirmation: Vec<PhraseWord>,
|
||||
/// Flag to check if entered phrase if valid.
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
impl Default for Mnemonic {
|
||||
fn default() -> Self {
|
||||
let size = PhraseSize::Words24;
|
||||
let mode = PhraseMode::Generate;
|
||||
let words = Self::generate_words(&mode, &size);
|
||||
let confirmation = Self::empty_words(&size);
|
||||
Self { mode, size, words, confirmation, valid: true }
|
||||
}
|
||||
fn default() -> Self {
|
||||
let size = PhraseSize::Words24;
|
||||
let mode = PhraseMode::Generate;
|
||||
let words = Self::generate_words(&mode, &size);
|
||||
let confirmation = Self::empty_words(&size);
|
||||
Self {
|
||||
mode,
|
||||
size,
|
||||
words,
|
||||
confirmation,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mnemonic {
|
||||
/// Generate words based on provided [`PhraseMode`].
|
||||
pub fn set_mode(&mut self, mode: PhraseMode) {
|
||||
self.mode = mode;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirmation = Self::empty_words(&self.size);
|
||||
self.valid = true;
|
||||
}
|
||||
/// Generate words based on provided [`PhraseMode`].
|
||||
pub fn set_mode(&mut self, mode: PhraseMode) {
|
||||
self.mode = mode;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirmation = Self::empty_words(&self.size);
|
||||
self.valid = true;
|
||||
}
|
||||
|
||||
/// Get current phrase mode.
|
||||
pub fn mode(&self) -> PhraseMode {
|
||||
self.mode.clone()
|
||||
}
|
||||
/// Get current phrase mode.
|
||||
pub fn mode(&self) -> PhraseMode {
|
||||
self.mode.clone()
|
||||
}
|
||||
|
||||
/// Generate words based on provided [`PhraseSize`].
|
||||
pub fn set_size(&mut self, size: PhraseSize) {
|
||||
self.size = size;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirmation = Self::empty_words(&self.size);
|
||||
self.valid = true;
|
||||
}
|
||||
/// Generate words based on provided [`PhraseSize`].
|
||||
pub fn set_size(&mut self, size: PhraseSize) {
|
||||
self.size = size;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirmation = Self::empty_words(&self.size);
|
||||
self.valid = true;
|
||||
}
|
||||
|
||||
/// Get current phrase size.
|
||||
pub fn size(&self) -> PhraseSize {
|
||||
self.size.clone()
|
||||
}
|
||||
/// Get current phrase size.
|
||||
pub fn size(&self) -> PhraseSize {
|
||||
self.size.clone()
|
||||
}
|
||||
|
||||
/// Get words based on current [`PhraseMode`].
|
||||
pub fn words(&self, edit: bool) -> Vec<PhraseWord> {
|
||||
match self.mode {
|
||||
PhraseMode::Generate => {
|
||||
if edit {
|
||||
&self.confirmation
|
||||
} else {
|
||||
&self.words
|
||||
}
|
||||
}
|
||||
PhraseMode::Import => &self.words
|
||||
}.clone()
|
||||
}
|
||||
/// Get words based on current [`PhraseMode`].
|
||||
pub fn words(&self, edit: bool) -> Vec<PhraseWord> {
|
||||
match self.mode {
|
||||
PhraseMode::Generate => {
|
||||
if edit {
|
||||
&self.confirmation
|
||||
} else {
|
||||
&self.words
|
||||
}
|
||||
}
|
||||
PhraseMode::Import => &self.words,
|
||||
}
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Check if current phrase is valid.
|
||||
pub fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
/// Check if current phrase is valid.
|
||||
pub fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
|
||||
/// Get phrase from words.
|
||||
pub fn get_phrase(&self) -> String {
|
||||
self.words.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| if i == 0 { "" } else { " " }.to_owned() + &x.text)
|
||||
.collect::<String>()
|
||||
}
|
||||
/// Get phrase from words.
|
||||
pub fn get_phrase(&self) -> String {
|
||||
self.words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| if i == 0 { "" } else { " " }.to_owned() + &x.text)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
/// Generate [`PhraseWord`] list based on provided [`PhraseMode`] and [`PhraseSize`].
|
||||
fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<PhraseWord> {
|
||||
match mode {
|
||||
PhraseMode::Generate => {
|
||||
let mut rng = rand::rng();
|
||||
let mut entropy: Vec<u8> = Vec::with_capacity(size.entropy_size());
|
||||
for _ in 0..size.entropy_size() {
|
||||
entropy.push(rng.random());
|
||||
}
|
||||
from_entropy(&entropy).unwrap()
|
||||
.split(" ")
|
||||
.map(|s| {
|
||||
let text = s.to_string();
|
||||
PhraseWord {
|
||||
text,
|
||||
valid: true,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<PhraseWord>>()
|
||||
},
|
||||
PhraseMode::Import => {
|
||||
Self::empty_words(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generate [`PhraseWord`] list based on provided [`PhraseMode`] and [`PhraseSize`].
|
||||
fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<PhraseWord> {
|
||||
match mode {
|
||||
PhraseMode::Generate => {
|
||||
let mut rng = rand::rng();
|
||||
let mut entropy: Vec<u8> = Vec::with_capacity(size.entropy_size());
|
||||
for _ in 0..size.entropy_size() {
|
||||
entropy.push(rng.random());
|
||||
}
|
||||
from_entropy(&entropy)
|
||||
.unwrap()
|
||||
.split(" ")
|
||||
.map(|s| {
|
||||
let text = s.to_string();
|
||||
PhraseWord { text, valid: true }
|
||||
})
|
||||
.collect::<Vec<PhraseWord>>()
|
||||
}
|
||||
PhraseMode::Import => Self::empty_words(size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate empty list of words based on provided [`PhraseSize`].
|
||||
fn empty_words(size: &PhraseSize) -> Vec<PhraseWord> {
|
||||
let mut words = Vec::with_capacity(size.value());
|
||||
for _ in 0..size.value() {
|
||||
words.push(PhraseWord {
|
||||
text: "".to_string(),
|
||||
valid: true,
|
||||
});
|
||||
}
|
||||
words
|
||||
}
|
||||
/// Generate empty list of words based on provided [`PhraseSize`].
|
||||
fn empty_words(size: &PhraseSize) -> Vec<PhraseWord> {
|
||||
let mut words = Vec::with_capacity(size.value());
|
||||
for _ in 0..size.value() {
|
||||
words.push(PhraseWord {
|
||||
text: "".to_string(),
|
||||
valid: true,
|
||||
});
|
||||
}
|
||||
words
|
||||
}
|
||||
|
||||
/// Insert word into provided index and return validation result.
|
||||
pub fn insert(&mut self, index: usize, word: &String) -> bool {
|
||||
// Check if word is valid.
|
||||
let found = search(word).is_ok();
|
||||
if !found {
|
||||
return false;
|
||||
}
|
||||
let is_confirmation = self.mode == PhraseMode::Generate;
|
||||
if is_confirmation {
|
||||
let w = self.words.get(index).unwrap();
|
||||
if word != &w.text {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// Insert word into provided index and return validation result.
|
||||
pub fn insert(&mut self, index: usize, word: &String) -> bool {
|
||||
// Check if word is valid.
|
||||
let found = search(word).is_ok();
|
||||
if !found {
|
||||
return false;
|
||||
}
|
||||
let is_confirmation = self.mode == PhraseMode::Generate;
|
||||
if is_confirmation {
|
||||
let w = self.words.get(index).unwrap();
|
||||
if word != &w.text {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Save valid word at list.
|
||||
let words = if is_confirmation {
|
||||
&mut self.confirmation
|
||||
} else {
|
||||
&mut self.words
|
||||
};
|
||||
words.remove(index);
|
||||
words.insert(index, PhraseWord { text: word.to_owned(), valid: true });
|
||||
// Save valid word at list.
|
||||
let words = if is_confirmation {
|
||||
&mut self.confirmation
|
||||
} else {
|
||||
&mut self.words
|
||||
};
|
||||
words.remove(index);
|
||||
words.insert(
|
||||
index,
|
||||
PhraseWord {
|
||||
text: word.to_owned(),
|
||||
valid: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Validate phrase when all words are entered.
|
||||
let mut has_empty = false;
|
||||
let _: Vec<_> = words.iter().map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
}).collect();
|
||||
if !has_empty {
|
||||
self.valid = to_entropy(self.get_phrase().as_str()).is_ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
// Validate phrase when all words are entered.
|
||||
let mut has_empty = false;
|
||||
let _: Vec<_> = words
|
||||
.iter()
|
||||
.map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !has_empty {
|
||||
self.valid = to_entropy(self.get_phrase().as_str()).is_ok();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Get word from provided index.
|
||||
pub fn get(&self, index: usize) -> Option<PhraseWord> {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words
|
||||
};
|
||||
let word = words.get(index);
|
||||
if let Some(w) = word {
|
||||
return Some(PhraseWord {
|
||||
text: w.text.clone(),
|
||||
valid: w.valid
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Get word from provided index.
|
||||
pub fn get(&self, index: usize) -> Option<PhraseWord> {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words,
|
||||
};
|
||||
let word = words.get(index);
|
||||
if let Some(w) = word {
|
||||
return Some(PhraseWord {
|
||||
text: w.text.clone(),
|
||||
valid: w.valid,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Setup phrase from provided text if possible.
|
||||
pub fn import(&mut self, text: &ZeroingString) {
|
||||
let words_split = text.trim().split(" ");
|
||||
let count = words_split.clone().count();
|
||||
if let Some(size) = PhraseSize::type_for_value(count) {
|
||||
// Setup phrase size.
|
||||
let confirm = self.mode == PhraseMode::Generate;
|
||||
if !confirm {
|
||||
self.words = Self::empty_words(&size);
|
||||
self.size = size;
|
||||
} else if self.size != size {
|
||||
return;
|
||||
}
|
||||
/// Setup phrase from provided text if possible.
|
||||
pub fn import(&mut self, text: &ZeroingString) {
|
||||
let words_split = text.trim().split(" ");
|
||||
let count = words_split.clone().count();
|
||||
if let Some(size) = PhraseSize::type_for_value(count) {
|
||||
// Setup phrase size.
|
||||
let confirm = self.mode == PhraseMode::Generate;
|
||||
if !confirm {
|
||||
self.words = Self::empty_words(&size);
|
||||
self.size = size;
|
||||
} else if self.size != size {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup word list.
|
||||
let mut words = vec![];
|
||||
words_split.for_each(|w| {
|
||||
let mut text = w.to_string();
|
||||
text.retain(|c| c.is_alphabetic());
|
||||
let valid = search(&text).is_ok();
|
||||
words.push(PhraseWord { text, valid });
|
||||
});
|
||||
let mut has_invalid = false;
|
||||
for (i, w) in words.iter().enumerate() {
|
||||
if !self.insert(i, &w.text) {
|
||||
has_invalid = true;
|
||||
}
|
||||
}
|
||||
self.valid = !has_invalid;
|
||||
}
|
||||
}
|
||||
// Setup word list.
|
||||
let mut words = vec![];
|
||||
words_split.for_each(|w| {
|
||||
let mut text = w.to_string();
|
||||
text.retain(|c| c.is_alphabetic());
|
||||
let valid = search(&text).is_ok();
|
||||
words.push(PhraseWord { text, valid });
|
||||
});
|
||||
let mut has_invalid = false;
|
||||
for (i, w) in words.iter().enumerate() {
|
||||
if !self.insert(i, &w.text) {
|
||||
has_invalid = true;
|
||||
}
|
||||
}
|
||||
self.valid = !has_invalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if phrase has invalid or empty words.
|
||||
pub fn has_empty_or_invalid(&self) -> bool {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words
|
||||
};
|
||||
let mut has_empty = false;
|
||||
let mut has_invalid = false;
|
||||
let _: Vec<_> = words.iter().map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
if !w.valid {
|
||||
has_invalid = true;
|
||||
}
|
||||
}).collect();
|
||||
has_empty || has_invalid
|
||||
}
|
||||
}
|
||||
/// Check if phrase has invalid or empty words.
|
||||
pub fn has_empty_or_invalid(&self) -> bool {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words,
|
||||
};
|
||||
let mut has_empty = false;
|
||||
let mut has_invalid = false;
|
||||
let _: Vec<_> = words
|
||||
.iter()
|
||||
.map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
if !w.valid {
|
||||
has_invalid = true;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
has_empty || has_invalid
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -32,5 +32,5 @@ pub use list::*;
|
||||
mod utils;
|
||||
pub use utils::WalletUtils;
|
||||
|
||||
mod seed;
|
||||
pub mod store;
|
||||
mod seed;
|
||||
+63
-63
@@ -15,7 +15,7 @@
|
||||
use core::num::NonZeroU32;
|
||||
use grin_util::{ToHex, ZeroingString};
|
||||
use grin_wallet_impls::Error;
|
||||
use rand::{rng, Rng};
|
||||
use rand::{Rng, rng};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::fs::File;
|
||||
@@ -28,75 +28,75 @@ use ring::pbkdf2;
|
||||
pub struct WalletSeed(Vec<u8>);
|
||||
|
||||
impl WalletSeed {
|
||||
pub fn from_bytes(bytes: &[u8]) -> WalletSeed {
|
||||
WalletSeed(bytes.to_vec())
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
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,
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+82
-72
@@ -12,90 +12,100 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use rkv::backend::{SafeMode, SafeModeDatabase, SafeModeEnvironment};
|
||||
use rkv::{Manager, Rkv, SingleStore, StoreOptions, Value};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// Transaction height storage.
|
||||
pub struct TxHeightStore {
|
||||
env: Arc<RwLock<Rkv<SafeModeEnvironment>>>,
|
||||
/// Confirmed heights.
|
||||
confirmed: SingleStore<SafeModeDatabase>,
|
||||
/// Broadcasting heights.
|
||||
broadcasting: SingleStore<SafeModeDatabase>
|
||||
env: Arc<RwLock<Rkv<SafeModeEnvironment>>>,
|
||||
/// Confirmed heights.
|
||||
confirmed: SingleStore<SafeModeDatabase>,
|
||||
/// Broadcasting heights.
|
||||
broadcasting: SingleStore<SafeModeDatabase>,
|
||||
}
|
||||
|
||||
impl TxHeightStore {
|
||||
/// Create new transaction height storage from provided directory.
|
||||
pub fn new(dir: PathBuf) -> Self {
|
||||
let mut manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
|
||||
let created_arc = manager.get_or_create(dir.as_path(), Rkv::new::<SafeMode>).unwrap();
|
||||
let env = created_arc.clone();
|
||||
let k = created_arc.read().unwrap();
|
||||
/// Create new transaction height storage from provided directory.
|
||||
pub fn new(dir: PathBuf) -> Self {
|
||||
let mut manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
|
||||
let created_arc = manager
|
||||
.get_or_create(dir.as_path(), Rkv::new::<SafeMode>)
|
||||
.unwrap();
|
||||
let env = created_arc.clone();
|
||||
let k = created_arc.read().unwrap();
|
||||
|
||||
let confirmed = k.open_single("tx_height", StoreOptions::create()).unwrap();
|
||||
let broadcasting = k.open_single("broadcast_tx_height", StoreOptions::create()).unwrap();
|
||||
Self {
|
||||
env,
|
||||
confirmed,
|
||||
broadcasting
|
||||
}
|
||||
}
|
||||
let confirmed = k.open_single("tx_height", StoreOptions::create()).unwrap();
|
||||
let broadcasting = k
|
||||
.open_single("broadcast_tx_height", StoreOptions::create())
|
||||
.unwrap();
|
||||
Self {
|
||||
env,
|
||||
confirmed,
|
||||
broadcasting,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read transaction height from database.
|
||||
pub fn read_tx_height(&self, slate_id: &String) -> Option<u64> {
|
||||
let env = self.env.read().unwrap();
|
||||
let reader = env.read().unwrap();
|
||||
if let Ok(value) = self.confirmed.get(&reader, slate_id) {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
return None;
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Read transaction height from database.
|
||||
pub fn read_tx_height(&self, slate_id: &String) -> Option<u64> {
|
||||
let env = self.env.read().unwrap();
|
||||
let reader = env.read().unwrap();
|
||||
if let Ok(value) = self.confirmed.get(&reader, slate_id) {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
return None;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Write transaction height to database.
|
||||
pub fn write_tx_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.confirmed.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
/// Write transaction height to database.
|
||||
pub fn write_tx_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.confirmed
|
||||
.put(&mut writer, slate_id, &Value::U64(height))
|
||||
.unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
|
||||
/// Read broadcasting height from database.
|
||||
pub fn read_broadcasting_height(&self, slate_id: &String) -> Option<u64> {
|
||||
let env = self.env.read().unwrap();
|
||||
let reader = env.read().unwrap();
|
||||
if let Ok(value) = self.broadcasting.get(&reader, slate_id) {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
return None;
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Read broadcasting height from database.
|
||||
pub fn read_broadcasting_height(&self, slate_id: &String) -> Option<u64> {
|
||||
let env = self.env.read().unwrap();
|
||||
let reader = env.read().unwrap();
|
||||
if let Ok(value) = self.broadcasting.get(&reader, slate_id) {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
return None;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Write broadcasting height to database.
|
||||
pub fn write_broadcasting_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
/// Write broadcasting height to database.
|
||||
pub fn write_broadcasting_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting
|
||||
.put(&mut writer, slate_id, &Value::U64(height))
|
||||
.unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
|
||||
/// Delete broadcasting height from database.
|
||||
pub fn delete_broadcasting_height(&self, slate_id: &String) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting.delete(&mut writer, slate_id).unwrap_or_default();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
/// Delete broadcasting height from database.
|
||||
pub fn delete_broadcasting_height(&self, slate_id: &String) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting
|
||||
.delete(&mut writer, slate_id)
|
||||
.unwrap_or_default();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+366
-357
@@ -15,7 +15,10 @@
|
||||
use grin_keychain::ExtKeychain;
|
||||
use grin_util::Mutex;
|
||||
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
|
||||
use grin_wallet_libwallet::{Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
|
||||
use grin_wallet_libwallet::{
|
||||
Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType,
|
||||
WalletInfo, WalletInst,
|
||||
};
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
@@ -26,433 +29,439 @@ use crate::wallet::Wallet;
|
||||
/// Mnemonic phrase word.
|
||||
#[derive(Clone)]
|
||||
pub struct PhraseWord {
|
||||
/// Word text.
|
||||
pub text: String,
|
||||
/// Flag to check if word is valid.
|
||||
pub valid: bool,
|
||||
/// Word text.
|
||||
pub text: String,
|
||||
/// Flag to check if word is valid.
|
||||
pub valid: bool,
|
||||
}
|
||||
|
||||
/// Mnemonic phrase setup mode.
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum PhraseMode {
|
||||
/// Generate new mnemonic phrase.
|
||||
Generate,
|
||||
/// Import existing mnemonic phrase.
|
||||
Import
|
||||
/// Generate new mnemonic phrase.
|
||||
Generate,
|
||||
/// Import existing mnemonic phrase.
|
||||
Import,
|
||||
}
|
||||
|
||||
/// Mnemonic phrase size based on entropy.
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
|
||||
pub enum PhraseSize {
|
||||
Words12,
|
||||
Words15,
|
||||
Words18,
|
||||
Words21,
|
||||
Words24,
|
||||
}
|
||||
|
||||
impl PhraseSize {
|
||||
pub const VALUES: [PhraseSize; 5] = [
|
||||
PhraseSize::Words12,
|
||||
PhraseSize::Words15,
|
||||
PhraseSize::Words18,
|
||||
PhraseSize::Words21,
|
||||
PhraseSize::Words24
|
||||
];
|
||||
pub const VALUES: [PhraseSize; 5] = [
|
||||
PhraseSize::Words12,
|
||||
PhraseSize::Words15,
|
||||
PhraseSize::Words18,
|
||||
PhraseSize::Words21,
|
||||
PhraseSize::Words24,
|
||||
];
|
||||
|
||||
/// Get entropy value.
|
||||
pub fn value(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 12,
|
||||
PhraseSize::Words15 => 15,
|
||||
PhraseSize::Words18 => 18,
|
||||
PhraseSize::Words21 => 21,
|
||||
PhraseSize::Words24 => 24
|
||||
}
|
||||
}
|
||||
/// Get entropy value.
|
||||
pub fn value(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 12,
|
||||
PhraseSize::Words15 => 15,
|
||||
PhraseSize::Words18 => 18,
|
||||
PhraseSize::Words21 => 21,
|
||||
PhraseSize::Words24 => 24,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get entropy size for current phrase size.
|
||||
pub fn entropy_size(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 16,
|
||||
PhraseSize::Words15 => 20,
|
||||
PhraseSize::Words18 => 24,
|
||||
PhraseSize::Words21 => 28,
|
||||
PhraseSize::Words24 => 32
|
||||
}
|
||||
}
|
||||
/// Get entropy size for current phrase size.
|
||||
pub fn entropy_size(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 16,
|
||||
PhraseSize::Words15 => 20,
|
||||
PhraseSize::Words18 => 24,
|
||||
PhraseSize::Words21 => 28,
|
||||
PhraseSize::Words24 => 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get phrase type for entropy size.
|
||||
pub fn type_for_value(count: usize) -> Option<PhraseSize> {
|
||||
if Self::is_correct_count(count) {
|
||||
match count {
|
||||
12 => {
|
||||
Some(PhraseSize::Words12)
|
||||
}
|
||||
15 => {
|
||||
Some(PhraseSize::Words15)
|
||||
}
|
||||
18 => {
|
||||
Some(PhraseSize::Words18)
|
||||
}
|
||||
21 => {
|
||||
Some(PhraseSize::Words21)
|
||||
}
|
||||
24 => {
|
||||
Some(PhraseSize::Words24)
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Get phrase type for entropy size.
|
||||
pub fn type_for_value(count: usize) -> Option<PhraseSize> {
|
||||
if Self::is_correct_count(count) {
|
||||
match count {
|
||||
12 => Some(PhraseSize::Words12),
|
||||
15 => Some(PhraseSize::Words15),
|
||||
18 => Some(PhraseSize::Words18),
|
||||
21 => Some(PhraseSize::Words21),
|
||||
24 => Some(PhraseSize::Words24),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if correct entropy size was provided.
|
||||
pub fn is_correct_count(count: usize) -> bool {
|
||||
count == 12 || count == 15 || count == 18 || count == 21 || count == 24
|
||||
}
|
||||
/// Check if correct entropy size was provided.
|
||||
pub fn is_correct_count(count: usize) -> bool {
|
||||
count == 12 || count == 15 || count == 18 || count == 21 || count == 24
|
||||
}
|
||||
}
|
||||
|
||||
/// Wallet connection method.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum ConnectionMethod {
|
||||
/// Integrated node.
|
||||
Integrated,
|
||||
/// External node, contains connection identifier and URL.
|
||||
External(i64, String)
|
||||
/// Integrated node.
|
||||
Integrated,
|
||||
/// External node, contains connection identifier and URL.
|
||||
External(i64, String),
|
||||
}
|
||||
|
||||
/// Wallet instance type.
|
||||
pub type WalletInstance = Arc<
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// Wallet account data.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletAccount {
|
||||
/// Spendable balance amount.
|
||||
pub spendable_amount: u64,
|
||||
/// Account label.
|
||||
pub label: String,
|
||||
/// Account BIP32 derivation path.
|
||||
pub path: String
|
||||
/// Spendable balance amount.
|
||||
pub spendable_amount: u64,
|
||||
/// Account label.
|
||||
pub label: String,
|
||||
/// Account BIP32 derivation path.
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// Wallet balance and transactions data.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletData {
|
||||
/// Balance data for current account.
|
||||
pub info: WalletInfo,
|
||||
/// Balance data for current account.
|
||||
pub info: WalletInfo,
|
||||
|
||||
/// Transactions data.
|
||||
pub txs: Option<Vec<WalletTx>>,
|
||||
/// Number of txs to show on select from database.
|
||||
pub txs_limit: u32,
|
||||
/// Transactions data.
|
||||
pub txs: Option<Vec<WalletTx>>,
|
||||
/// Number of txs to show on select from database.
|
||||
pub txs_limit: u32,
|
||||
}
|
||||
|
||||
impl WalletData {
|
||||
/// Number of transactions per select to show at list.
|
||||
pub const TXS_LIMIT: u32 = 30;
|
||||
/// Number of transactions per select to show at list.
|
||||
pub const TXS_LIMIT: u32 = 30;
|
||||
|
||||
/// Update transaction action status.
|
||||
pub fn on_tx_action(&mut self, id: u32, action: Option<WalletTxAction>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if id == tx.data.id {
|
||||
tx.action = action;
|
||||
tx.action_error = None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Update transaction action status.
|
||||
pub fn on_tx_action(&mut self, id: u32, action: Option<WalletTxAction>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if id == tx.data.id {
|
||||
tx.action = action;
|
||||
tx.action_error = None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update transaction action error status.
|
||||
pub fn on_tx_error(&mut self, id: u32, err: Option<Error>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if id == tx.data.id {
|
||||
tx.action_error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Update transaction action error status.
|
||||
pub fn on_tx_error(&mut self, id: u32, err: Option<Error>) {
|
||||
if self.txs.is_none() {
|
||||
return;
|
||||
}
|
||||
for tx in self.txs.as_mut().unwrap() {
|
||||
if id == tx.data.id {
|
||||
tx.action_error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get transaction by identifier.
|
||||
pub fn tx_by_id(&self, id: u32) -> Option<WalletTx> {
|
||||
if self.txs.is_none() {
|
||||
return None;
|
||||
}
|
||||
for tx in self.txs.as_ref().unwrap() {
|
||||
if tx.data.id == id {
|
||||
return Some(tx.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Get transaction by identifier.
|
||||
pub fn tx_by_id(&self, id: u32) -> Option<WalletTx> {
|
||||
if self.txs.is_none() {
|
||||
return None;
|
||||
}
|
||||
for tx in self.txs.as_ref().unwrap() {
|
||||
if tx.data.id == id {
|
||||
return Some(tx.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Wallet transaction action.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum WalletTxAction {
|
||||
Cancelling, Finalizing, Posting, SendingTor, Deleting
|
||||
Cancelling,
|
||||
Finalizing,
|
||||
Posting,
|
||||
SendingTor,
|
||||
Deleting,
|
||||
}
|
||||
|
||||
/// Wallet transaction data.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletTx {
|
||||
/// Information from database.
|
||||
pub data: TxLogEntry,
|
||||
/// State of transaction Slate.
|
||||
pub state: SlateState,
|
||||
/// Payment proof.
|
||||
pub(crate) proof: Option<PaymentProof>,
|
||||
/// Information from database.
|
||||
pub data: TxLogEntry,
|
||||
/// State of transaction Slate.
|
||||
pub state: SlateState,
|
||||
/// Payment proof.
|
||||
pub(crate) proof: Option<PaymentProof>,
|
||||
|
||||
/// Transaction amount without fees.
|
||||
pub amount: u64,
|
||||
/// Possible receiver of transaction.
|
||||
pub receiver: Option<SlatepackAddress>,
|
||||
/// Possible sender of transaction.
|
||||
pub sender: Option<SlatepackAddress>,
|
||||
/// Block height where tx was included.
|
||||
pub height: Option<u64>,
|
||||
/// Block height where tx started broadcasting.
|
||||
pub broadcasting_height: Option<u64>,
|
||||
/// Transaction amount without fees.
|
||||
pub amount: u64,
|
||||
/// Possible receiver of transaction.
|
||||
pub receiver: Option<SlatepackAddress>,
|
||||
/// Possible sender of transaction.
|
||||
pub sender: Option<SlatepackAddress>,
|
||||
/// Block height where tx was included.
|
||||
pub height: Option<u64>,
|
||||
/// Block height where tx started broadcasting.
|
||||
pub broadcasting_height: Option<u64>,
|
||||
|
||||
/// Action on transaction.
|
||||
pub action: Option<WalletTxAction>,
|
||||
/// Action result error.
|
||||
pub action_error: Option<Error>
|
||||
/// Action on transaction.
|
||||
pub action: Option<WalletTxAction>,
|
||||
/// Action result error.
|
||||
pub action_error: Option<Error>,
|
||||
}
|
||||
|
||||
impl WalletTx {
|
||||
/// Create new wallet transaction.
|
||||
pub fn new(tx: TxLogEntry,
|
||||
proof: Option<PaymentProof>,
|
||||
wallet: &Wallet,
|
||||
height: Option<u64>,
|
||||
broadcasting_height: Option<u64>,
|
||||
action: Option<WalletTxAction>,
|
||||
action_error: Option<Error>) -> Self {
|
||||
let amount = if tx.amount_debited > tx.amount_credited {
|
||||
tx.amount_debited - tx.amount_credited
|
||||
} else {
|
||||
tx.amount_credited - tx.amount_debited
|
||||
};
|
||||
let mut receiver: Option<SlatepackAddress> = None;
|
||||
let mut sender: Option<SlatepackAddress> = None;
|
||||
if let Some(proof) = &tx.payment_proof {
|
||||
let rec_onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes());
|
||||
if let Ok(addr) = SlatepackAddress::try_from(rec_onion_addr) {
|
||||
receiver = Some(addr);
|
||||
}
|
||||
let send_onion_addr = OnionV3Address::from_bytes(proof.sender_address.to_bytes());
|
||||
if let Ok(addr) = SlatepackAddress::try_from(send_onion_addr) {
|
||||
sender = Some(addr);
|
||||
}
|
||||
}
|
||||
let mut t = Self {
|
||||
data: tx,
|
||||
state: SlateState::Unknown,
|
||||
proof,
|
||||
amount,
|
||||
receiver,
|
||||
sender,
|
||||
height,
|
||||
broadcasting_height,
|
||||
action,
|
||||
action_error,
|
||||
};
|
||||
// Update Slate state for unconfirmed.
|
||||
if !t.data.confirmed {
|
||||
t.update_slate_state(wallet);
|
||||
}
|
||||
t
|
||||
}
|
||||
/// Create new wallet transaction.
|
||||
pub fn new(
|
||||
tx: TxLogEntry,
|
||||
proof: Option<PaymentProof>,
|
||||
wallet: &Wallet,
|
||||
height: Option<u64>,
|
||||
broadcasting_height: Option<u64>,
|
||||
action: Option<WalletTxAction>,
|
||||
action_error: Option<Error>,
|
||||
) -> Self {
|
||||
let amount = if tx.amount_debited > tx.amount_credited {
|
||||
tx.amount_debited - tx.amount_credited
|
||||
} else {
|
||||
tx.amount_credited - tx.amount_debited
|
||||
};
|
||||
let mut receiver: Option<SlatepackAddress> = None;
|
||||
let mut sender: Option<SlatepackAddress> = None;
|
||||
if let Some(proof) = &tx.payment_proof {
|
||||
let rec_onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes());
|
||||
if let Ok(addr) = SlatepackAddress::try_from(rec_onion_addr) {
|
||||
receiver = Some(addr);
|
||||
}
|
||||
let send_onion_addr = OnionV3Address::from_bytes(proof.sender_address.to_bytes());
|
||||
if let Ok(addr) = SlatepackAddress::try_from(send_onion_addr) {
|
||||
sender = Some(addr);
|
||||
}
|
||||
}
|
||||
let mut t = Self {
|
||||
data: tx,
|
||||
state: SlateState::Unknown,
|
||||
proof,
|
||||
amount,
|
||||
receiver,
|
||||
sender,
|
||||
height,
|
||||
broadcasting_height,
|
||||
action,
|
||||
action_error,
|
||||
};
|
||||
// Update Slate state for unconfirmed.
|
||||
if !t.data.confirmed {
|
||||
t.update_slate_state(wallet);
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
/// Update transaction [`Slate`] state for provided wallet.
|
||||
pub fn update_slate_state(&mut self, wallet: &Wallet) {
|
||||
let tx = &self.data;
|
||||
let mut slate = Slate::blank(1, false);
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice3,
|
||||
_ => SlateState::Standard3
|
||||
};
|
||||
// Transaction was finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Standard2,
|
||||
_ => SlateState::Invoice2
|
||||
};
|
||||
// Transaction signed to be finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
// Transaction just was created.
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice1,
|
||||
_ => SlateState::Standard1
|
||||
};
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
self.state = SlateState::Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Update transaction [`Slate`] state for provided wallet.
|
||||
pub fn update_slate_state(&mut self, wallet: &Wallet) {
|
||||
let tx = &self.data;
|
||||
let mut slate = Slate::blank(1, false);
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice3,
|
||||
_ => SlateState::Standard3,
|
||||
};
|
||||
// Transaction was finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Standard2,
|
||||
_ => SlateState::Invoice2,
|
||||
};
|
||||
// Transaction signed to be finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
// Transaction just was created.
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice1,
|
||||
_ => SlateState::Standard1,
|
||||
};
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
} else {
|
||||
self.state = SlateState::Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if transactions can be finalized after receiving response.
|
||||
pub fn can_finalize(&self) -> bool {
|
||||
!self.cancelling() && !self.data.confirmed &&
|
||||
(!self.sending_tor() || self.action_error.is_some()) &&
|
||||
(self.data.tx_type == TxLogEntryType::TxSent ||
|
||||
self.data.tx_type == TxLogEntryType::TxReceived) &&
|
||||
(self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
|
||||
}
|
||||
/// Check if transactions can be finalized after receiving response.
|
||||
pub fn can_finalize(&self) -> bool {
|
||||
!self.cancelling()
|
||||
&& !self.data.confirmed
|
||||
&& (!self.sending_tor() || self.action_error.is_some())
|
||||
&& (self.data.tx_type == TxLogEntryType::TxSent
|
||||
|| self.data.tx_type == TxLogEntryType::TxReceived)
|
||||
&& (self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
|
||||
}
|
||||
|
||||
/// Check if transaction was finalized.
|
||||
pub fn finalized(&self) -> bool {
|
||||
(self.data.tx_type == TxLogEntryType::TxSent ||
|
||||
self.data.tx_type == TxLogEntryType::TxReceived) &&
|
||||
self.state == SlateState::Invoice3 || self.state == SlateState::Standard3
|
||||
}
|
||||
/// Check if transaction was finalized.
|
||||
pub fn finalized(&self) -> bool {
|
||||
(self.data.tx_type == TxLogEntryType::TxSent
|
||||
|| self.data.tx_type == TxLogEntryType::TxReceived)
|
||||
&& self.state == SlateState::Invoice3
|
||||
|| self.state == SlateState::Standard3
|
||||
}
|
||||
|
||||
/// Check if transaction is sending over Tor.
|
||||
pub fn sending_tor(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::SendingTor;
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if transaction is sending over Tor.
|
||||
pub fn sending_tor(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::SendingTor;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if transaction is cancelling.
|
||||
pub fn cancelling(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Cancelling;
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if transaction is cancelling.
|
||||
pub fn cancelling(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Cancelling;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if transaction is posting.
|
||||
pub fn posting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Posting;
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if transaction is posting.
|
||||
pub fn posting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Posting;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if transaction can be cancelled.
|
||||
pub fn can_cancel(&self) -> bool {
|
||||
!self.cancelling() && !self.data.confirmed && !self.broadcasting() &&
|
||||
(!self.sending_tor() || self.action_error.is_some()) &&
|
||||
self.data.tx_type != TxLogEntryType::TxReceivedCancelled &&
|
||||
self.data.tx_type != TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
/// Check if transaction can be cancelled.
|
||||
pub fn can_cancel(&self) -> bool {
|
||||
!self.cancelling()
|
||||
&& !self.data.confirmed
|
||||
&& !self.broadcasting()
|
||||
&& (!self.sending_tor() || self.action_error.is_some())
|
||||
&& self.data.tx_type != TxLogEntryType::TxReceivedCancelled
|
||||
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
|
||||
/// Check if transaction was canceled.
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.data.tx_type == TxLogEntryType::TxReceivedCancelled ||
|
||||
self.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
/// Check if transaction was canceled.
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.data.tx_type == TxLogEntryType::TxReceivedCancelled
|
||||
|| self.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
|
||||
/// Check if transaction is finalizing.
|
||||
pub fn finalizing(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Finalizing;
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if transaction is finalizing.
|
||||
pub fn finalizing(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Finalizing;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if possible to repeat transaction action.
|
||||
pub fn can_repeat_action(&self, wallet: &Wallet) -> bool {
|
||||
if let Some(a) = &self.action {
|
||||
self.action_error.is_some() && a != &WalletTxAction::Cancelling
|
||||
} else {
|
||||
// Can resend over Tor.
|
||||
!self.data.confirmed && !self.sending_tor() &&
|
||||
Tor::is_service_running(&wallet.identifier()) && !self.broadcasting() &&
|
||||
self.receiver.is_some()
|
||||
}
|
||||
}
|
||||
/// Check if possible to repeat transaction action.
|
||||
pub fn can_repeat_action(&self, wallet: &Wallet) -> bool {
|
||||
if let Some(a) = &self.action {
|
||||
self.action_error.is_some() && a != &WalletTxAction::Cancelling
|
||||
} else {
|
||||
// Can resend over Tor.
|
||||
!self.data.confirmed
|
||||
&& !self.sending_tor()
|
||||
&& Tor::is_service_running(&wallet.identifier())
|
||||
&& !self.broadcasting()
|
||||
&& self.receiver.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if transaction is broadcasting after finalization.
|
||||
pub fn broadcasting(&self) -> bool {
|
||||
!self.data.confirmed && self.finalized()
|
||||
}
|
||||
/// Check if transaction is broadcasting after finalization.
|
||||
pub fn broadcasting(&self) -> bool {
|
||||
!self.data.confirmed && self.finalized()
|
||||
}
|
||||
|
||||
/// Check if broadcasting of transaction was timed out.
|
||||
pub fn broadcasting_timed_out(&self, wallet: &Wallet) -> bool {
|
||||
if let Some(data) = wallet.get_data() {
|
||||
if self.broadcasting() {
|
||||
let last_height = data.info.last_confirmed_height;
|
||||
let broadcasting_height = self.broadcasting_height.unwrap_or(0);
|
||||
if broadcasting_height == 0 {
|
||||
return false;
|
||||
}
|
||||
let delay = wallet.broadcasting_delay();
|
||||
return last_height - broadcasting_height > delay;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if broadcasting of transaction was timed out.
|
||||
pub fn broadcasting_timed_out(&self, wallet: &Wallet) -> bool {
|
||||
if let Some(data) = wallet.get_data() {
|
||||
if self.broadcasting() {
|
||||
let last_height = data.info.last_confirmed_height;
|
||||
let broadcasting_height = self.broadcasting_height.unwrap_or(0);
|
||||
if broadcasting_height == 0 {
|
||||
return false;
|
||||
}
|
||||
let delay = wallet.broadcasting_delay();
|
||||
return last_height - broadcasting_height > delay;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if transaction is deleting.
|
||||
pub fn deleting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Deleting;
|
||||
}
|
||||
false
|
||||
}
|
||||
/// Check if transaction is deleting.
|
||||
pub fn deleting(&self) -> bool {
|
||||
if let Some(a) = self.action.as_ref() {
|
||||
return a == &WalletTxAction::Deleting;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Task for the wallet.
|
||||
#[derive(Clone)]
|
||||
pub enum WalletTask {
|
||||
/// Open Slatepack message parsing result and making an action.
|
||||
OpenMessage(String),
|
||||
/// Calculate fee to send amount.
|
||||
/// * amount
|
||||
/// * fee (to read at result)
|
||||
CalculateFee(u64, u64),
|
||||
/// Verify payment proof.
|
||||
/// * payment proof
|
||||
/// * result (tx id, sender mine, receiver mine)
|
||||
VerifyProof(PaymentProof, Option<Result<(u32, bool, bool), Error>>),
|
||||
/// Create request to send.
|
||||
/// * amount
|
||||
/// * receiver
|
||||
Send(u64, Option<SlatepackAddress>),
|
||||
/// Send request over Tor.
|
||||
/// * tx
|
||||
/// * receiver
|
||||
SendTor(TxLogEntry, SlatepackAddress),
|
||||
/// Invoice creation.
|
||||
/// * amount
|
||||
Receive(u64),
|
||||
/// Transaction finalization.
|
||||
/// * tx id
|
||||
Finalize(u32),
|
||||
/// Post transaction to blockchain.
|
||||
/// * tx id
|
||||
Post(u32),
|
||||
/// Cancel transaction.
|
||||
/// * tx id
|
||||
Cancel(u32),
|
||||
/// Delete transaction.
|
||||
/// * tx id
|
||||
Delete(u32)
|
||||
}
|
||||
/// Open Slatepack message parsing result and making an action.
|
||||
OpenMessage(String),
|
||||
/// Calculate fee to send amount.
|
||||
/// * amount
|
||||
/// * fee (to read at result)
|
||||
CalculateFee(u64, u64),
|
||||
/// Verify payment proof.
|
||||
/// * payment proof
|
||||
/// * result (tx id, sender mine, receiver mine)
|
||||
VerifyProof(PaymentProof, Option<Result<(u32, bool, bool), Error>>),
|
||||
/// Create request to send.
|
||||
/// * amount
|
||||
/// * receiver
|
||||
Send(u64, Option<SlatepackAddress>),
|
||||
/// Send request over Tor.
|
||||
/// * tx
|
||||
/// * receiver
|
||||
SendTor(TxLogEntry, SlatepackAddress),
|
||||
/// Invoice creation.
|
||||
/// * amount
|
||||
Receive(u64),
|
||||
/// Transaction finalization.
|
||||
/// * tx id
|
||||
Finalize(u32),
|
||||
/// Post transaction to blockchain.
|
||||
/// * tx id
|
||||
Post(u32),
|
||||
/// Cancel transaction.
|
||||
/// * tx id
|
||||
Cancel(u32),
|
||||
/// Delete transaction.
|
||||
/// * tx id
|
||||
Delete(u32),
|
||||
}
|
||||
|
||||
+9
-9
@@ -12,17 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// Wallet utilities functions.
|
||||
pub struct WalletUtils {}
|
||||
|
||||
impl WalletUtils {
|
||||
/// Setup entropy data checksum.
|
||||
pub fn setup_checksum(data: &mut Vec<u8>) {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data.clone());
|
||||
let checksum = hasher.finalize();
|
||||
data.extend(checksum);
|
||||
}
|
||||
}
|
||||
/// Setup entropy data checksum.
|
||||
pub fn setup_checksum(data: &mut Vec<u8>) {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data.clone());
|
||||
let checksum = hasher.finalize();
|
||||
data.extend(checksum);
|
||||
}
|
||||
}
|
||||
|
||||
+2158
-2078
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user