build: v0.3.6, format code

This commit is contained in:
ardocrat
2026-05-21 00:56:28 +03:00
parent a4eadebef2
commit 4aeda9c9dc
114 changed files with 24876 additions and 23319 deletions
+218 -218
View File
@@ -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();
}
}
+81 -74
View File
@@ -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
View File
@@ -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();
});
});
}
+1 -1
View File
@@ -16,4 +16,4 @@ mod external;
pub use external::ExternalConnection;
mod config;
pub use config::ConnectionsConfig;
pub use config::ConnectionsConfig;
+90 -90
View File
@@ -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
View File
@@ -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
View File
@@ -32,5 +32,5 @@ pub use list::*;
mod utils;
pub use utils::WalletUtils;
mod seed;
pub mod store;
mod seed;
+63 -63
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff