ui: txs limit and sort, wallet deletion from the list, fix tor conn on accounts and settings change

- Limit loading at tix list
- Sort txs by confirmation status to show txs waiting for an action at top
- Ability to delete wallet from the list without opening
- Optimize Tor connection on account switch

Reviewed-on: https://code.gri.mw/GUI/grim/pulls/53
This commit is contained in:
ardocrat
2026-03-05 11:48:23 +00:00
parent beb1a80c6a
commit f5f6141881
18 changed files with 436 additions and 257 deletions
Generated
+1
View File
@@ -4043,6 +4043,7 @@ dependencies = [
"local-ip-address",
"log",
"nokhwa",
"num-bigint 0.4.6",
"parking_lot 0.12.5",
"pin-project",
"qrcode",
+1
View File
@@ -88,6 +88,7 @@ hyper-proxy2 = "0.1.0"
hyper-tls = "0.6.0"
async-std = "1.13.2"
uuid = { version = "0.8.2", features = ["v4"] }
num-bigint = "0.4.6"
## tor
arti-client = { version = "0.38.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client"] }
+4 -4
View File
@@ -21,7 +21,7 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, TextEdit, View};
use crate::gui::views::network::settings::NetworkSettings;
use crate::gui::views::types::{ContentContainer, ModalPosition};
use crate::gui::views::wallets::modals::WalletsModal;
use crate::gui::views::wallets::modals::WalletListModal;
use crate::node::{Node, NodeConfig};
use crate::wallet::{WalletConfig, WalletList};
@@ -30,7 +30,7 @@ pub struct StratumSetup {
/// Wallet list to select for mining rewards.
wallets: WalletList,
/// Wallets [`Modal`] content.
wallets_modal: WalletsModal,
wallets_modal: WalletListModal,
/// IP Addresses available at system.
available_ips: Vec<String>,
@@ -80,7 +80,7 @@ impl Default for StratumSetup {
Self {
wallets: WalletList::default(),
wallets_modal: WalletsModal::new(wallet_id, None, false),
wallets_modal: WalletListModal::new(wallet_id, None, false),
available_ips: NodeConfig::get_ip_addrs(),
stratum_port_edit: port,
stratum_port_available_edit: is_port_available,
@@ -238,7 +238,7 @@ impl ContentContainer for StratumSetup {
impl StratumSetup {
/// Show wallet selection [`Modal`].
fn show_wallets_modal(&mut self) {
self.wallets_modal = WalletsModal::new(NodeConfig::get_stratum_wallet_id(), None, false);
self.wallets_modal = WalletListModal::new(NodeConfig::get_stratum_wallet_id(), None, false);
// Show modal.
Modal::new(WALLET_SELECTION_MODAL)
.position(ModalPosition::Center)
+32 -17
View File
@@ -16,12 +16,13 @@ use std::time::Duration;
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, CornerRadius, Id, Layout, Margin, RichText, ScrollArea, StrokeKind};
use egui::os::OperatingSystem;
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::settings::SettingsContent;
use crate::gui::views::types::{ContentContainer, LinePosition, ModalPosition, TitleContentType, TitleType};
use crate::gui::views::wallets::creation::WalletCreationContent;
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletConnectionModal, WalletsModal};
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletSettingsModal, WalletListModal};
use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletContentContainer};
use crate::gui::views::wallets::WalletContent;
use crate::gui::views::{Content, Modal, TitlePanel, View};
@@ -29,6 +30,7 @@ use crate::gui::Colors;
use crate::wallet::types::{ConnectionMethod, WalletTask};
use crate::wallet::{Wallet, WalletList};
use crate::AppConfig;
use crate::gui::views::wallets::wallet::RecoverySettings;
/// Wallets content.
pub struct WalletsContent {
@@ -39,10 +41,10 @@ pub struct WalletsContent {
add_wallet_modal_content: AddWalletModal,
/// Wallet opening [`Modal`] content.
open_wallet_content: OpenWalletModal,
/// Wallet connection selection [`Modal`] content.
conn_selection_content: WalletConnectionModal,
/// Wallet settings [`Modal`] content.
wallet_settings_content: WalletSettingsModal,
/// Wallet selection [`Modal`] content.
wallet_selection_content: WalletsModal,
wallet_selection_content: WalletListModal,
/// Selected [`Wallet`] content.
wallet_content: WalletContent,
@@ -53,19 +55,23 @@ pub struct WalletsContent {
settings_content: Option<SettingsContent>,
}
/// Identifier for [`Modal`] to add the wallet.
const ADD_WALLET_MODAL: &'static str = "wallets_add_modal";
/// Identifier for [`Modal`] to open the wallet.
const OPEN_WALLET_MODAL: &'static str = "wallets_open_wallet";
const SELECT_CONNECTION_MODAL: &'static str = "wallets_select_conn_modal";
/// Identifier for wallet settings [`Modal`].
const WALLET_SETTINGS_MODAL: &'static str = "wallets_settings_modal";
/// Identifier for wallet selection [`Modal`].
const SELECT_WALLET_MODAL: &'static str = "wallets_select_modal";
impl Default for WalletsContent {
fn default() -> Self {
Self {
wallets: WalletList::default(),
wallet_selection_content: WalletsModal::new(None, None, true),
wallet_selection_content: WalletListModal::new(None, None, true),
open_wallet_content: OpenWalletModal::new(),
add_wallet_modal_content: AddWalletModal::default(),
conn_selection_content: WalletConnectionModal::new(ConnectionMethod::Integrated),
wallet_settings_content: WalletSettingsModal::new(ConnectionMethod::Integrated),
wallet_content: WalletContent::default(),
creation_content: None,
settings_content: None,
@@ -78,8 +84,9 @@ impl ContentContainer for WalletsContent {
vec![
ADD_WALLET_MODAL,
OPEN_WALLET_MODAL,
SELECT_CONNECTION_MODAL,
WALLET_SETTINGS_MODAL,
SELECT_WALLET_MODAL,
Self::DELETE_CONFIRMATION_MODAL
]
}
@@ -103,8 +110,8 @@ impl ContentContainer for WalletsContent {
true
});
},
SELECT_CONNECTION_MODAL => {
self.conn_selection_content.ui(ui, modal, cb, |conn| {
WALLET_SETTINGS_MODAL => {
self.wallet_settings_content.ui(ui, modal, cb, |conn| {
if let Some(w) = self.wallets.selected().as_ref() {
w.update_connection(&conn);
}
@@ -125,6 +132,11 @@ impl ContentContainer for WalletsContent {
}
}
}
Self::DELETE_CONFIRMATION_MODAL => {
if let Some(w) = self.wallets.selected().as_ref() {
RecoverySettings::deletion_modal_ui(ui, w);
}
}
_ => {}
}
}
@@ -314,6 +326,9 @@ impl ContentContainer for WalletsContent {
}
impl WalletsContent {
/// Identifier for wallet deletion confirmation [`Modal`].
pub const DELETE_CONFIRMATION_MODAL: &'static str = "wallets_delete_confirmation_modal";
/// Called to navigate back, return `true` if action was not consumed.
pub fn on_back(&mut self, cb: &dyn PlatformCallbacks) -> bool {
if self.showing_settings() {
@@ -377,7 +392,7 @@ impl WalletsContent {
self.show_opening_modal(&w, data, cb);
}
} else {
self.wallet_selection_content = WalletsModal::new(None, data, true);
self.wallet_selection_content = WalletListModal::new(None, data, true);
Modal::new(SELECT_WALLET_MODAL)
.position(ModalPosition::Center)
.title(t!("network_settings.choose_wallet"))
@@ -548,14 +563,14 @@ impl WalletsContent {
self.show_opening_modal(wallet, None, cb);
});
if !wallet.is_repairing() {
View::item_button(ui, CornerRadius::default(), GLOBE, None, || {
View::item_button(ui, CornerRadius::default(), GEAR_FINE, None, || {
self.select_wallet(wallet, None, cb);
self.conn_selection_content =
WalletConnectionModal::new(wallet.get_current_connection());
let conn = wallet.get_current_connection();
self.wallet_settings_content = WalletSettingsModal::new(conn);
// Show connection selection modal.
Modal::new(SELECT_CONNECTION_MODAL)
Modal::new(WALLET_SETTINGS_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.conn_method"))
.title(t!("wallets.settings"))
.show();
});
}
@@ -23,7 +23,7 @@ use crate::wallet::types::ConnectionMethod;
use crate::wallet::{Wallet, WalletList};
/// Wallet list [`Modal`] content
pub struct WalletsModal {
pub struct WalletListModal {
/// Selected wallet id.
selected_id: Option<i64>,
@@ -34,7 +34,7 @@ pub struct WalletsModal {
can_open: bool,
}
impl WalletsModal {
impl WalletListModal {
/// Create new content instance.
pub fn new(selected_id: Option<i64>, data: Option<String>, can_open: bool) -> Self {
Self { selected_id, data, can_open }
+4 -4
View File
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod conn;
pub use conn::*;
mod settings;
pub use settings::*;
mod wallets;
pub use wallets::*;
mod list;
pub use list::*;
mod open;
pub use open::*;
@@ -16,16 +16,18 @@ use egui::{RichText, ScrollArea};
use egui::scroll_area::ScrollBarVisibility;
use crate::gui::Colors;
use crate::gui::icons::{CHECK, CHECK_FAT, PLUS_CIRCLE};
use crate::gui::icons::{CHECK, CHECK_FAT, PLUS_CIRCLE, TRASH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::network::ConnectionsContent;
use crate::gui::views::network::modals::ExternalConnectionModal;
use crate::gui::views::types::ModalPosition;
use crate::gui::views::wallets::WalletsContent;
use crate::wallet::{ConnectionsConfig, ExternalConnection};
use crate::wallet::types::ConnectionMethod;
/// Wallet connection selection [`Modal`] content.
pub struct WalletConnectionModal {
pub struct WalletSettingsModal {
/// Current connection method.
pub conn: ConnectionMethod,
@@ -33,7 +35,7 @@ pub struct WalletConnectionModal {
new_ext_conn_content: Option<ExternalConnectionModal>
}
impl WalletConnectionModal {
impl WalletSettingsModal {
/// Create from provided wallet connection.
pub fn new(conn: ConnectionMethod) -> Self {
Self {
@@ -141,6 +143,21 @@ impl WalletConnectionModal {
ui.add_space(2.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(6.0);
ui.vertical_centered(|ui| {
// Draw button to delete the wallet.
View::colored_text_button(ui,
format!("{} {}", TRASH, t!("wallets.delete")),
Colors::red(),
Colors::white_or_black(false), || {
Modal::new(WalletsContent::DELETE_CONFIRMATION_MODAL)
.position(ModalPosition::Center)
.title(t!("confirmation"))
.show();
});
});
ui.add_space(6.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(6.0);
// Show button to close modal.
ui.vertical_centered_justified(|ui| {
+6 -2
View File
@@ -360,7 +360,11 @@ impl WalletContent {
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
let has_wallet_data = wallet.get_data().is_some();
let can_send = wallet.get_data().unwrap().info.amount_currently_spendable > 0;
let can_send = if has_wallet_data {
wallet.get_data().unwrap().info.amount_currently_spendable > 0
} else {
false
};
let tabs_amount = if can_send { 5 } else { 4 };
ui.columns(tabs_amount, |columns| {
@@ -435,7 +439,7 @@ impl WalletContent {
/// Handle wallet task result.
fn handle_task_result(&mut self, wallet: &Wallet) {
let res = wallet.consume_task_result();
if res.is_none() {
if res.is_none() || wallet.get_data().is_none() {
return;
}
let (id, t) = res.unwrap();
+15 -6
View File
@@ -1,12 +1,13 @@
use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, RichText, ScrollArea};
use grin_wallet_libwallet::{Error, PaymentProof};
use grin_util::ToHex;
use grin_wallet_libwallet::{Error, PaymentProof, TxLogEntryType};
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, FILE_TEXT, SEAL_CHECK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, View};
use crate::gui::Colors;
use crate::wallet::types::WalletTask;
use crate::wallet::types::{WalletTask, WalletTransaction};
use crate::wallet::Wallet;
pub struct PaymentProofContent {
@@ -151,11 +152,19 @@ impl PaymentProofContent {
}
/// Draw transaction payment proof content to share.
pub fn share_ui(&mut self, ui: &mut egui::Ui, file_name: String, cb: &dyn PlatformCallbacks) {
pub fn share_ui(&mut self,
ui: &mut egui::Ui,
tx: &WalletTransaction,
cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
let desc = format!("{} {}:", SEAL_CHECK, t!("wallets.payment_proof"));
ui.label(RichText::new(desc).size(16.0).color(Colors::inactive_text()));
let (desc_text, color) = if tx.data.tx_type == TxLogEntryType::TxReceived {
(t!("wallets.payment_proof_valid"), Colors::green())
} else {
(format!("{}:", t!("wallets.payment_proof")), Colors::inactive_text())
};
let desc = format!("{} {}", SEAL_CHECK, desc_text);
ui.label(RichText::new(desc).size(16.0).color(color));
});
ui.add_space(6.0);
ui.vertical_centered(|ui| {
@@ -203,7 +212,7 @@ impl PaymentProofContent {
share_text,
Colors::blue(),
Colors::white_or_black(false), || {
let file_name = format!("{}.txt", file_name);
let file_name = format!("{}.txt", tx.data.kernel_excess.unwrap().to_hex());
let data = self.input_edit.as_bytes().to_vec();
cb.share_data(file_name, data).unwrap_or_default();
Modal::close();
@@ -59,7 +59,7 @@ impl WalletContentContainer for RecoverySettings {
self.recovery_phrase_modal_ui(ui, wallet, modal, cb);
}
DELETE_CONFIRMATION_MODAL => {
self.deletion_modal_ui(ui, wallet);
Self::deletion_modal_ui(ui, wallet);
}
_ => {}
}
@@ -260,9 +260,7 @@ impl RecoverySettings {
}
/// Draw wallet deletion [`Modal`] content.
fn deletion_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &Wallet) {
pub fn deletion_modal_ui(ui: &mut egui::Ui, wallet: &Wallet) {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.delete_conf"))
@@ -88,6 +88,7 @@ impl WalletTransportContent {
/// Navigate back on navigation stack.
pub fn back(&mut self) {
if self.settings_content.is_some() {
Tor::restart_services();
self.settings_content = None;
} else if self.qr_address_content.is_some() {
self.qr_address_content = None;
@@ -134,25 +135,25 @@ impl WalletTransportContent {
.with_max_size(320.0));
});
// Draw button to enable/disable Tor listener for current wallet.
let service_id = &wallet.identifier();
if !Tor::is_service_starting(service_id) && wallet.foreign_api_port().is_some() &&
wallet.secret_key().is_some() {
if !Tor::is_service_running(service_id) {
let r = CornerRadius::default();
View::item_button(ui, r, POWER, Some(Colors::green()), || {
let api_port = wallet.foreign_api_port().unwrap();
let key = wallet.secret_key().unwrap();
Tor::start_service(api_port, key, service_id);
});
} else {
let r = CornerRadius::default();
View::item_button(ui, r, POWER, Some(Colors::red()), || {
Tor::stop_service(service_id);
});
// Draw button to enable/disable Tor listener for current wallet.
if wallet.foreign_api_port().is_some() && wallet.secret_key().is_some() {
let port = wallet.foreign_api_port().unwrap();
let key = wallet.secret_key().unwrap();
if !Tor::is_service_starting(service_id) {
if !Tor::is_service_running(service_id) {
let r = CornerRadius::default();
View::item_button(ui, r, POWER, Some(Colors::green()), || {
Tor::start_service(port, key.clone(), service_id);
});
} else {
let r = CornerRadius::default();
View::item_button(ui, r, POWER, Some(Colors::red()), || {
Tor::stop_service(service_id);
});
}
}
}
// Draw button to show Tor transport settings.
let button_rounding = View::item_rounding(1, 3, true);
View::item_button(ui, button_rounding, WRENCH, None, || {
@@ -25,13 +25,13 @@ use crate::wallet::Wallet;
/// Wallet transport settings content.
pub struct WalletTransportSettingsContent {
/// Tor transport content settings.
tor_settings_content: TorSettingsContent,
pub tor_settings_content: TorSettingsContent,
}
impl Default for WalletTransportSettingsContent {
fn default() -> Self {
Self {
tor_settings_content: TorSettingsContent::default(),
tor_settings_content: TorSettingsContent::default()
}
}
}
@@ -62,19 +62,7 @@ impl WalletTransportSettingsContent {
ui.add_space(8.0);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
if self.tor_settings_content.settings_changed {
// Restart running service or rebuild client.
let service_id = &wallet.identifier();
if Tor::is_service_running(service_id) {
if let Some(key) = wallet.secret_key() {
if let Some(api_port) = wallet.foreign_api_port() {
Tor::restart_service(api_port, key, service_id);
}
}
} else {
Tor::rebuild_client();
}
}
Tor::restart_services();
on_close();
});
});
+25 -1
View File
@@ -130,11 +130,17 @@ impl WalletTransactionsContent {
.can_refresh(!refresh && !wallet.syncing() && !txs.is_empty())
.min_refresh_distance(70.0)
.scroll_area_ui(ui, |ui| {
let rows_size = if txs.is_empty() {
0
} else {
// Last index is for list pagination.
txs.len() + 1
};
ScrollArea::vertical()
.id_salt(Id::from("wallet_tx_list_scroll").with(config.id))
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.auto_shrink([false; 2])
.show_rows(ui, Self::TX_ITEM_HEIGHT, txs.len(), |ui, row_range| {
.show_rows(ui, Self::TX_ITEM_HEIGHT, rows_size, |ui, row_range| {
ui.add_space(1.0);
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
self.tx_list_ui(ui, row_range, &wallet, txs);
@@ -159,6 +165,24 @@ impl WalletTransactionsContent {
txs: &Vec<WalletTransaction>) {
let data = wallet.get_data().unwrap();
for index in row_range {
if index == txs.len() && ui.is_visible() {
// Load more txs when needed.
if !wallet.more_txs_loading() {
if let Some(data) = wallet.get_data() {
if txs.len() as u32 >= data.txs_limit {
wallet.load_more_txs();
}
}
}
// Show loader when more txs are loading.
if wallet.more_txs_loading() {
ui.vertical_centered(|ui| {
ui.add_space(24.0);
View::small_loading_spinner(ui);
});
}
return;
}
let mut rect = ui.available_rect_before_wrap();
rect.min += egui::emath::vec2(6.0, 0.0);
rect.max -= egui::emath::vec2(6.0, 0.0);
+1 -7
View File
@@ -102,14 +102,8 @@ impl WalletTransactionContent {
self.share_ui(ui, wallet, tx, cb);
} else {
if let Some(proof_content) = self.proof_content.as_mut() {
// Payment proof file name setup.
let file_name = if let Some(slate_id) = tx.data.tx_slate_id {
slate_id.to_string()
} else {
tx.data.id.to_string()
};
// Draw payment proof sharing content.
proof_content.share_ui(ui, file_name, cb);
proof_content.share_ui(ui, tx, cb);
} else if tx.proof.is_some() && !tx.sending_tor() &&
tx.action_error.is_none() {
ui.vertical_centered(|ui| {
+127 -78
View File
@@ -30,6 +30,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use std::{fs, thread};
use log::error;
use safelog::DisplayRedacted;
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
use tls_api_native_tls::TlsConnector;
@@ -45,7 +46,6 @@ use tor_hsservice::{
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
use tor_rtcompat::Runtime;
use crate::http::HttpClient;
use crate::tor::http::ArtiHttpConnector;
@@ -60,8 +60,14 @@ lazy_static! {
pub struct Tor {
/// Tor client and config.
client_config: Arc<RwLock<(TorClient<TokioNativeTlsRuntime>, TorClientConfig)>>,
/// Client to check services availability.
check_client: Arc<RwLock<
Option<hyper_tor::Client<ArtiHttpConnector<TokioNativeTlsRuntime, TlsConnector>>>
>>,
/// Mapping of running Onion services identifiers to proxy.
run: Arc<RwLock<BTreeMap<String, (Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>>>,
run: Arc<RwLock<
BTreeMap<String, (u16, SecretKey, Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>
>>,
/// Starting Onion services identifiers.
start: Arc<RwLock<BTreeSet<String>>>,
/// Failed Onion services identifiers.
@@ -79,7 +85,6 @@ impl Default for Tor {
fs::write(TorConfig::webtunnel_path(), webtunnel).unwrap_or_default();
}
}
// Create Tor client.
let runtime = TokioNativeTlsRuntime::create().unwrap();
let config = Self::build_config(true);
@@ -88,11 +93,12 @@ impl Default for Tor {
.create_unbootstrapped()
.unwrap();
Self {
client_config: Arc::new(RwLock::new((client, config))),
check_client: Arc::new(RwLock::new(None)),
run: Arc::new(RwLock::new(BTreeMap::new())),
start: Arc::new(RwLock::new(BTreeSet::new())),
fail: Arc::new(RwLock::new(BTreeSet::new())),
check: Arc::new(RwLock::new(BTreeSet::new())),
client_config: Arc::new(RwLock::new((client, config))),
}
}
}
@@ -122,13 +128,6 @@ impl Tor {
config
}
/// Recreate Tor client with configuration.
pub fn rebuild_client() {
let config = Self::build_config(false);
let r_client = TOR_SERVER_STATE.client_config.read();
r_client.0.reconfigure(&config, tor_config::Reconfigure::AllOrNothing).unwrap();
}
/// Send post request using Tor.
pub async fn post(body: String, url: String) -> Option<String> {
if let Some(proxy) = TorConfig::get_proxy() {
@@ -147,10 +146,25 @@ impl Tor {
};
match res {
Ok(res) => {
let body = res.into_body().collect().await.unwrap().to_bytes().into();
Some(String::from_utf8(body).unwrap())
match res.into_body().collect().await {
Ok(r) => {
let body = r.to_bytes().into();
match String::from_utf8(body) {
Ok(r) => Some(r),
Err(e) => {
error!("Tor: POST with proxy, response to string error: {}", e);
None
}
}
}
Err(e) => {
error!("Tor: POST with proxy, response parse error: {}", e);
None
}
}
}
Err(_) => {
Err(e) => {
error!("Tor: POST failed with proxy: {}", e);
None
}
}
@@ -162,12 +176,11 @@ impl Tor {
}
// Bootstrap client.
let (client, _) = Self::client_config();
let client = client.isolated_client();
client.bootstrap().await.unwrap();
client.bootstrap().await.unwrap_or_default();
// Create http tor-powered client to post data.
let tls_connector = TlsConnector::builder().unwrap().build().unwrap();
let tor_connector = ArtiHttpConnector::new(client, tls_connector);
let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(tor_connector);
let tls_conn = TlsConnector::builder().unwrap().build().unwrap();
let conn = ArtiHttpConnector::new(client, tls_conn);
let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn);
// Create request.
let req = hyper_tor::Request::builder()
.method(hyper_tor::Method::POST)
@@ -179,9 +192,13 @@ impl Tor {
match http.request(req).await {
Ok(r) => match hyper_tor::body::to_bytes(r).await {
Ok(raw) => resp = Some(String::from_utf8_lossy(&raw).to_string()),
Err(_) => {}
Err(e) => {
error!("Tor: POST response parse error: {}", e);
}
},
Err(_) => {}
Err(e) => {
error!("Tor: POST failed: {}", e);
}
}
resp
}
@@ -216,21 +233,45 @@ impl Tor {
r_services.contains(id)
}
// Restart Onion service.
pub fn restart_service(port: u16, key: SecretKey, id: &String) {
Self::stop_service(id);
Self::rebuild_client();
Self::start_service(port, key, id)
/// Restart running Onion services.
pub fn restart_services() {
// Stop all services saving port key for relaunch.
let service_ids = {
let r_services = TOR_SERVER_STATE.run.read().clone();
r_services.keys().map(|s| s.to_string()).collect::<Vec<String>>()
};
let mut services: BTreeMap<String, (u16, SecretKey)> = BTreeMap::new();
for id in service_ids.clone() {
if let Some(res) = Self::stop_service(&id) {
services.insert(id, res);
}
}
// Reconfigure client.
let config = Self::build_config(false);
let r_client = TOR_SERVER_STATE.client_config.read();
r_client.0.reconfigure(&config, tor_config::Reconfigure::WarnOnFailures).unwrap();
// Start services.
for id in services.keys() {
let (port, key) = services.get(id).unwrap();
Self::start_service(port.clone(), key.clone(), &id);
}
}
/// Stop running Onion service.
pub fn stop_service(id: &String) {
let mut w_services = TOR_SERVER_STATE.run.write();
if let Some((svc, proxy)) = w_services.remove(id) {
proxy.shutdown();
drop(svc);
drop(proxy);
/// Stop running Onion service returning port and key.
pub fn stop_service(id: &String) -> Option<(u16, SecretKey)> {
{
// Remove service from starting.
let mut w_services = TOR_SERVER_STATE.start.write();
w_services.remove(id);
}
let mut w_services = TOR_SERVER_STATE.run.write();
if let Some((port, key, svc, proxy)) = w_services.remove(id) {
proxy.shutdown();
drop(proxy);
drop(svc);
return Some((port, key));
}
None
}
/// Start Onion service from listening local port and [`SecretKey`].
@@ -278,49 +319,49 @@ impl Tor {
}
let (client, config) = Self::client_config();
let c = client.clone();
client
.runtime()
.spawn(async move {
// Add service key to keystore.
let hs_nickname = HsNickname::new(service_id.clone()).unwrap();
if let Err(_) = Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname) {
let hs = HsNickname::new(service_id.clone()).unwrap();
if let Err(_) = Self::add_service_key(config.fs_mistrust(), &key, &hs) {
on_error(service_id);
return;
}
let (c, _) = Self::client_config();
// Bootstrap client.
if let Err(_) = c.bootstrap().await {
on_error(service_id);
return;
if c.bootstrap_status().as_frac() == 0.0 {
if let Err(_) = c.bootstrap().await {
on_error(service_id);
return;
}
// Launch first time after delay.
thread::sleep(Duration::from_millis(10000));
}
// Launch Onion service.
let service_config = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
.nickname(hs.clone())
.build()
.unwrap();
if let Ok(res) = c.launch_onion_service(service_config) {
if let Some((service, request)) = res {
let onion_addr = service.onion_address();
// Launch service proxy.
let addr = SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), port);
tokio::spawn(Self::run_service_proxy(
let proxy = tokio::spawn(Self::run_service_proxy(
addr,
c.clone(),
service.clone(),
request,
hs_nickname.clone(),
hs.clone(),
)).await.unwrap();
// Save running service.
let mut w_services = TOR_SERVER_STATE.run.write();
let id = service_id.clone();
w_services.insert(id, (port, key.clone(), service, proxy));
// Check service availability.
let addr = service.onion_address()
.unwrap()
.display_unredacted()
.to_string();
let addr = onion_addr.unwrap().display_unredacted().to_string();
let url = format!("http://{}/", addr);
if !Self::is_service_checking(&service_id) {
Self::check_service(service_id, c, url, port, key)
} else {
// Remove service from starting.
let mut w_services = TOR_SERVER_STATE.start.write();
w_services.remove(&service_id);
Self::check_service(service_id, url, port, key)
}
return;
}
@@ -333,7 +374,6 @@ impl Tor {
/// Check service availability.
fn check_service(service_id: String,
client: TorClient<TokioNativeTlsRuntime>,
url: String,
port: u16,
key: SecretKey) {
@@ -341,14 +381,25 @@ impl Tor {
let mut w_services = TOR_SERVER_STATE.check.write();
w_services.insert(service_id.clone());
}
let client_check = client.clone();
let (client, _) = Self::client_config();
thread::spawn(move || {
// Wait 5 sec before check.
thread::sleep(Duration::from_millis(5000));
// Request client setup.
if TOR_SERVER_STATE.check_client.read().is_none() {
let (client_check, _) = Self::client_config();
let tls_conn = TlsConnector::builder().unwrap().build().unwrap();
let conn = ArtiHttpConnector::new(client_check, tls_conn);
let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn);
let mut w_client = TOR_SERVER_STATE.check_client.write();
*w_client = Some(http);
}
let r_client = TOR_SERVER_STATE.check_client.read();
let http = r_client.clone().unwrap();
let runtime = client.runtime();
runtime
.spawn(async move {
const MAX_ERRORS: i32 = 12;
const MAX_ERRORS: i32 = 16;
let mut errors_count = 0;
loop {
// Check if service is running.
@@ -365,18 +416,12 @@ impl Tor {
if !is_running(&service_id) {
break;
}
// Send request.
let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap();
let tls_conn = TlsConnector::builder().unwrap().build().unwrap();
let conn = ArtiHttpConnector::new(client_check.clone(), tls_conn);
let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn);
let duration = {
let check = http.get(uri);
let mut on_error = |service_id: &String| -> bool {
if !is_running(service_id) {
return true;
}
// Restart service on 3rd error.
// Restart service after maximum amount of errors.
errors_count += 1;
if errors_count == MAX_ERRORS {
// Remove service from checking.
@@ -390,13 +435,16 @@ impl Tor {
let key = key.clone();
let id = service_id.clone();
thread::spawn(move || {
thread::sleep(Duration::from_secs(1));
Self::restart_service(port, key, &id);
Self::stop_service(&id);
Self::start_service(port, key, &id);
});
return true;
}
false
};
// Send request.
let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap();
let check = http.get(uri);
// Check with timeout of 20s.
match tokio::time::timeout(Duration::from_millis(20000), check).await {
Ok(resp) => {
@@ -409,13 +457,15 @@ impl Tor {
let mut w_services = TOR_SERVER_STATE.start.write();
w_services.remove(&service_id);
errors_count = 0;
// Check again after 60s.
Duration::from_millis(60000)
// Check again after 20s.
Duration::from_millis(20000)
}
Err(_) => {
Err(e) => {
if on_error(&service_id) {
break;
}
error!("Tor check failed: {} for {}, errors: {}/{}",
e, service_id, errors_count, MAX_ERRORS);
// Check again after 5s.
Duration::from_millis(5000)
}
@@ -425,13 +475,15 @@ impl Tor {
if on_error(&service_id) {
break;
}
error!("Tor check for {} timeout, errors: {}/{}",
&service_id, errors_count, MAX_ERRORS);
// Check again after 5s.
Duration::from_millis(5000)
}
}
};
// Wait to check service again.
thread::park_timeout(duration);
thread::sleep(duration);
}
})
.unwrap();
@@ -439,17 +491,15 @@ impl Tor {
}
/// Launch Onion service proxy.
async fn run_service_proxy<R, S>(
async fn run_service_proxy<S>(
addr: SocketAddr,
client: TorClient<R>,
service: Arc<RunningOnionService>,
request: S,
nickname: HsNickname,
) where
R: Runtime,
) -> Arc<OnionServiceReverseProxy> where
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
{
let id = nickname.to_string();
let (client, _) = Self::client_config();
let runtime = client.runtime().clone();
// Setup proxy to forward request from Tor address to local address.
@@ -464,15 +514,13 @@ impl Tor {
// Remove service from failed.
let mut w_services = TOR_SERVER_STATE.fail.write();
w_services.remove(&id);
// Save running service.
let mut w_services = TOR_SERVER_STATE.run.write();
w_services.insert(id.clone(), (service.clone(), proxy.clone()));
// Start proxy for launched service.
let p = proxy.clone();
client
.runtime()
.spawn(async move {
match proxy.handle_requests(runtime, nickname.clone(), request).await {
match p.handle_requests(runtime, nickname.clone(), request).await {
Ok(()) => {
// Remove service from running.
let mut w_services = TOR_SERVER_STATE.run.write();
@@ -491,6 +539,7 @@ impl Tor {
}
})
.unwrap();
proxy
}
/// Save Onion service key to keystore.
+7 -1
View File
@@ -148,11 +148,17 @@ pub struct WalletAccount {
pub struct WalletData {
/// Balance data for current account.
pub info: WalletInfo,
/// Transactions data.
pub txs: Option<Vec<WalletTransaction>>
pub txs: Option<Vec<WalletTransaction>>,
/// 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;
/// Update transaction action status.
pub fn on_tx_action(&mut self, id: String, action: Option<WalletTransactionAction>) {
if self.txs.is_none() {
+168 -96
View File
@@ -23,7 +23,7 @@ use crate::AppConfig;
use futures::channel::oneshot;
use grin_api::{ApiServer, Router};
use grin_chain::SyncStatus;
use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_keychain::{ExtKeychain, Keychain};
use grin_util::secp::SecretKey;
use grin_util::types::ZeroingString;
use grin_util::{Mutex, ToHex};
@@ -33,7 +33,7 @@ use grin_wallet_controller::controller;
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient, LMDBBackend};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs, verify_payment_proof};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider};
use grin_wallet_util::OnionV3Address;
use parking_lot::RwLock;
use rand::Rng;
@@ -43,13 +43,15 @@ use std::io::Write;
use std::net::{SocketAddr, TcpListener, ToSocketAddrs};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{mpsc, Arc};
use std::thread::Thread;
use std::time::Duration;
use std::{fs, thread};
use chrono::Utc;
use log::error;
use num_bigint::BigInt;
use uuid::Uuid;
/// Contains wallet instance, configuration and state, handles wallet commands.
@@ -69,6 +71,8 @@ pub struct Wallet {
/// Wallet accounts.
accounts: Arc<RwLock<Vec<WalletAccount>>>,
/// Timestamp when wallet account was selected to form unique identifier for transport.
account_time: Arc<AtomicI64>,
/// Wallet sync thread.
sync_thread: Arc<RwLock<Option<Thread>>>,
@@ -85,6 +89,8 @@ pub struct Wallet {
data: Arc<RwLock<Option<WalletData>>>,
/// Flag to check if wallet data was synced from node.
from_node: Arc<AtomicBool>,
/// Flag to check if more transactions need to be loaded.
more_txs_loading: Arc<AtomicBool>,
/// Flag to check if wallet reopening is needed.
reopen: Arc<AtomicBool>,
@@ -138,20 +144,22 @@ impl Wallet {
connection: Arc::new(RwLock::new(connection)),
keychain_mask: Arc::new(RwLock::new(None)),
slatepack_address: Arc::new(RwLock::new(None)),
accounts: Arc::new(RwLock::new(vec![])),
account_time: Arc::new(Default::default()),
sync_thread: Arc::from(RwLock::new(None)),
foreign_api_server: Arc::new(RwLock::new(None)),
secret_key: Arc::new(RwLock::new(None)),
syncing: Arc::new(AtomicBool::new(false)),
info_sync_progress: Arc::from(AtomicU8::new(0)),
sync_error: Arc::from(AtomicBool::new(false)),
sync_attempts: Arc::new(AtomicU8::new(0)),
data: Arc::new(RwLock::new(None)),
from_node: Arc::new(AtomicBool::new(false)),
more_txs_loading: Arc::new(AtomicBool::new(false)),
reopen: Arc::new(AtomicBool::new(false)),
is_open: Arc::from(AtomicBool::new(false)),
closing: Arc::new(AtomicBool::new(false)),
deleted: Arc::new(AtomicBool::new(false)),
sync_error: Arc::from(AtomicBool::new(false)),
info_sync_progress: Arc::from(AtomicU8::new(0)),
accounts: Arc::new(RwLock::new(vec![])),
data: Arc::new(RwLock::new(None)),
from_node: Arc::new(AtomicBool::new(false)),
sync_attempts: Arc::new(AtomicU8::new(0)),
syncing: Arc::new(AtomicBool::new(false)),
foreign_api_server: Arc::new(RwLock::new(None)),
secret_key: Arc::new(RwLock::new(None)),
repair_needed: Arc::new(AtomicBool::new(false)),
repair_progress: Arc::new(AtomicU8::new(0)),
files_moving: Arc::new(AtomicBool::new(false)),
@@ -349,6 +357,7 @@ impl Wallet {
let wallet_inst = lc.wallet_inst()?;
let label = self.get_config().account.to_owned();
wallet_inst.set_parent_key_id_by_name(label.as_str())?;
self.account_time.store(Utc::now().timestamp(), Ordering::Relaxed);
// Start new synchronization thread or wake up existing one.
let mut thread_w = self.sync_thread.write();
@@ -370,29 +379,17 @@ impl Wallet {
}
}
// Set slatepack address.
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
// Set Slatepack address and secret key.
if let Ok((key, addr)) = self.get_secret_key_addr() {
let mut w_key = self.secret_key.write();
*w_key = Some(key);
let mut w_address = self.slatepack_address.write();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
})?;
*w_address = Some(addr.to_string());
}
Ok(())
}
/// Get parent key identifier for current account.
fn get_parent_key_id(&self) -> Result<Identifier, Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
Ok(w_inst.parent_key_id())
}
/// Get keychain mask [`SecretKey`].
pub fn keychain_mask(&self) -> Option<SecretKey> {
let r_key = self.keychain_mask.read();
@@ -405,8 +402,8 @@ impl Wallet {
r_key.clone()
}
/// Retrieve wallet [`SecretKey`] for transport.
fn get_secret_key(&self) -> Result<SecretKey, Error> {
/// Retrieve wallet [`SecretKey`] and Slatepack address for transport.
fn get_secret_key_addr(&self) -> Result<(SecretKey, SlatepackAddress), Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut w_lock = instance.lock();
@@ -416,13 +413,15 @@ impl Wallet {
let parent_key_id = w_inst.parent_key_id();
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
Ok(sec_key)
let addr = SlatepackAddress::try_from(&sec_key)?;
Ok((sec_key, addr))
}
/// Get unique opened wallet identifier, including current account.
pub fn identifier(&self) -> String {
let config = self.get_config();
format!("wallet_{}_{}", config.id, config.account.to_hex())
let account_ts = self.account_time.load(Ordering::Relaxed);
format!("{}_{}_{}", config.id, config.account.to_hex(), account_ts)
}
/// Get Slatepack address to receive txs at transport.
@@ -629,7 +628,7 @@ impl Wallet {
}
/// Select transaction by slate id.
fn tx_by_id(&self, id: Uuid) -> Option<TxLogEntry> {
fn retrieve_tx_by_id(&self, id: Uuid) -> Option<TxLogEntry> {
let r_inst = self.instance.as_ref().read();
let inst = r_inst.clone().unwrap();
let mask = self.keychain_mask();
@@ -642,6 +641,44 @@ impl Wallet {
None
}
/// Select transactions with provided limit.
fn retrieve_txs(&self, limit: u32) -> Result<Vec<TxLogEntry>, Error> {
let r_inst = self.instance.as_ref().read();
let inst = r_inst.clone().unwrap();
let mut wallet_lock = inst.lock();
let lc = wallet_lock.lc_provider()?;
let w = lc.wallet_inst()?;
let parent_key_id = w.parent_key_id();
// Retrieve txs from database.
let txs_iter = w.tx_log_iter()
.filter(|tx_entry| tx_entry.parent_key_id == parent_key_id)
.filter(|tx_entry| {
if tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled {
BigInt::from(tx_entry.amount_debited)
- BigInt::from(tx_entry.amount_credited)
>= BigInt::from(1)
} else {
BigInt::from(tx_entry.amount_credited)
- BigInt::from(tx_entry.amount_debited)
>= BigInt::from(1)
}
});
let mut return_txs: Vec<TxLogEntry> = txs_iter.collect();
// Sort txs by creation date and confirmation status reversing an order.
return_txs.sort_by_key(|tx| if !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
tx.tx_type == TxLogEntryType::TxReceived) {
i64::MAX
} else {
tx.creation_ts.timestamp()
});
// return_txs.sort_by_key(|tx| tx.confirmed);
return_txs.reverse();
// Apply limit.
return_txs = return_txs.into_iter().take(limit as usize).collect();
Ok(return_txs)
}
/// Send a task to the wallet.
pub fn task(&self, task: WalletTask) {
let r_tasks = self.tasks_sender.read();
@@ -683,21 +720,34 @@ impl Wallet {
/// Set active account from provided label.
pub fn set_active_account(&self, label: &String) -> Result<(), Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
api.set_active_account(m, label)?;
// Set Slatepack address.
let mut w_address = self.slatepack_address.write();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
})?;
// Stop service from previous account.
let cur_service_id = self.identifier();
Tor::stop_service(&cur_service_id);
// Clear secret key for previous account.
{
let mut w_key = self.secret_key.write();
*w_key = None;
}
// Set new active account.
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance.clone(), None);
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
api.set_active_account(m, label)?;
self.account_time.store(Utc::now().timestamp(), Ordering::Relaxed);
Ok(())
})?;
// Setup secret key and Slatepack address.
if let Ok((key, addr)) = self.get_secret_key_addr() {
let mut w_key = self.secret_key.write();
*w_key = Some(key);
let mut w_address = self.slatepack_address.write();
*w_address = Some(addr.to_string());
}
// Save account label into config.
let mut w_config = self.config.write();
w_config.account = label.to_owned();
@@ -707,10 +757,6 @@ impl Wallet {
let mut w_data = self.data.write();
*w_data = None;
// Clear secret key for previous account.
let mut w_key = self.secret_key.write();
*w_key = None;
// Reset progress values.
self.info_sync_progress.store(0, Ordering::Relaxed);
@@ -754,6 +800,32 @@ impl Wallet {
r_data.clone()
}
/// Load more transactions at list by increasing limit.
pub fn load_more_txs(&self) {
self.more_txs_loading.store(true, Ordering::Relaxed);
let wallet = self.clone();
thread::spawn(move || {
// Wait when current sync will be finished.
if wallet.syncing() {
thread::sleep(Duration::from_secs(1));
}
// Sync wallet data with new limit.
{
let mut w_data = wallet.data.write();
if w_data.is_some() {
w_data.as_mut().unwrap().txs_limit += WalletData::TXS_LIMIT;
}
}
sync_wallet_data(&wallet, false);
wallet.more_txs_loading.store(false, Ordering::Relaxed);
});
}
/// Check if more transaction are loading.
pub fn more_txs_loading(&self) -> bool {
self.more_txs_loading.load(Ordering::Relaxed)
}
/// Sync wallet data from node at sync thread or locally synchronously.
pub fn sync(&self) {
let thread_r = self.sync_thread.read();
@@ -1463,14 +1535,6 @@ fn start_sync(wallet: Wallet) -> Thread {
}
}
// Setup secret key if not set.
if wallet.secret_key().is_none() {
let mut w_key = wallet.secret_key.write();
if let Ok(key) = wallet.get_secret_key() {
*w_key = Some(key);
}
}
// Start unfailed Tor service if API server is running.
let service_id = wallet.identifier();
if wallet.auto_start_tor_listener() && api_server_running &&
@@ -1531,10 +1595,11 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(s.id) {
error!("send tor finalize error: {:?}", e);
sync_wallet_data(&w, false);
if let Some(tx) = w.retrieve_tx_by_id(s.id) {
let _ = w.cancel(&tx);
}
error!("send tor finalize error: {:?}", e);
w.on_tx_error(id, Some(e));
}
}
@@ -1607,7 +1672,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(s.id) {
if let Some(tx) = w.retrieve_tx_by_id(s.id) {
let _ = w.cancel(&tx);
}
error!("message tx finalize error: {:?}", e);
@@ -1686,7 +1751,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(slate.id) {
if let Some(tx) = w.retrieve_tx_by_id(slate.id) {
let _ = w.cancel(&tx);
}
error!("tx finalize error: {:?}", e);
@@ -1799,19 +1864,27 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
update_accounts(wallet, last_height, spendable);
if wallet.info_sync_progress() == 100 || !from_node {
// Transactions limit setup.
let txs_limit = {
let r_data = wallet.data.read();
if r_data.is_some() {
let data = r_data.as_ref().unwrap();
data.txs_limit
} else {
WalletData::TXS_LIMIT
}
};
// Update wallet info.
{
let mut w_data = wallet.data.write();
let txs = if w_data.is_some() {
w_data.clone().unwrap().txs
if w_data.is_some() {
w_data.as_mut().unwrap().info = info;
} else {
None
};
*w_data = Some(WalletData { info: info.clone(), txs });
*w_data = Some(WalletData { info, txs: None, txs_limit });
}
}
// Update wallet transactions.
if update_txs(wallet, instance.clone(), info).is_ok() {
if update_txs(wallet, txs_limit).is_ok() {
if !wallet.from_node.load(Ordering::Relaxed) {
wallet.from_node.store(from_node, Ordering::Relaxed);
}
@@ -1845,42 +1918,39 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
}
/// Update wallet transactions.
fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
-> Result<(), Error> {
// Retrieve txs from local database.
let txs_args = RetrieveTxQueryArgs {
exclude_cancelled: Some(false),
sort_field: Some(RetrieveTxQuerySortField::CreationTimestamp),
sort_order: Some(RetrieveTxQuerySortOrder::Desc),
min_amount: Some(1),
..Default::default()
};
let mask = wallet.keychain_mask();
let txs = retrieve_txs(inst, mask.as_ref(), &None, false, None, None, Some(txs_args.clone()))?;
fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
let txs = wallet.retrieve_txs(txs_limit)?;
// Exit if wallet was closed.
if !wallet.is_open() || wallet.is_closing() {
return Err(Error::GenericError("Wallet is not open".to_string()));
}
// Filter transactions for current account.
let account_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| {
match wallet.get_parent_key_id() {
Ok(key) => {
tx.parent_key_id == key && (tx.tx_slate_id.is_some() ||
(tx.tx_slate_id.is_none() && tx.payment_proof.is_some()))
}
Err(_) => {
true
}
}
// Filter transactions to not show txs without slate (usually unspent outputs).
let mut filter_txs = txs.iter().map(|v| v.clone()).filter(|tx| {
tx.tx_slate_id.is_some() || (tx.tx_slate_id.is_none() && tx.payment_proof.is_some())
}).collect::<Vec<TxLogEntry>>();
// Sort to show unconfirmed at top.
filter_txs.sort_by_key(|tx| {
tx.confirmed || tx.tx_type == TxLogEntryType::TxReceivedCancelled ||
tx.tx_type == TxLogEntryType::TxSentCancelled ||
tx.tx_type == TxLogEntryType::TxReverted
});
// Update limit with actual length.
let txs_size = txs.len() as u32;
let filter_size = filter_txs.len() as u32;
if txs_size > filter_size && txs_limit >= filter_size {
txs_limit = txs_limit - (txs_size - filter_size);
}
// Update existing tx list.
let tx_height_store = TxHeightStore::new(wallet.get_config().get_extra_db_path());
let data = wallet.get_data().unwrap();
let data_txs = data.txs.unwrap_or(vec![]);
let mut new_txs: Vec<WalletTransaction> = vec![];
for tx in &account_txs {
for tx in &filter_txs {
let mut height: Option<u64> = None;
let mut broadcasting_height: Option<u64> = None;
let mut action: Option<WalletTransactionAction> = None;
@@ -1892,6 +1962,7 @@ fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
action_error = t.action_error.clone();
height = t.height;
broadcasting_height = t.broadcasting_height;
proof = t.proof.clone();
break;
}
}
@@ -1908,7 +1979,6 @@ fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
if unconfirmed {
new.update_slate_state(wallet);
}
// Payment proof setup.
if proof.is_none() && tx.payment_proof.is_some() &&
tx.payment_proof.as_ref().unwrap().receiver_signature.is_some() &&
@@ -1919,7 +1989,6 @@ fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
new.proof = proof;
}
}
// Initial tx heights setup.
if let Some(slate_id) = tx.tx_slate_id {
let id = slate_id.to_string();
@@ -1952,7 +2021,10 @@ fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
}
// Update wallet txs.
let mut w_data = wallet.data.write();
*w_data = Some(WalletData { info, txs: Some(new_txs) });
if w_data.is_some() {
w_data.as_mut().unwrap().txs_limit = txs_limit;
w_data.as_mut().unwrap().txs = Some(new_txs);
}
Ok(())
}
+1 -1
Submodule wallet updated: c51433d02b...a6e0cdae0c