tor: fix launch onion server, wallet tor service, send over tor

This commit is contained in:
ardocrat
2024-04-30 18:15:03 +03:00
parent 1d9c8533ad
commit 12650c94fd
17 changed files with 1408 additions and 607 deletions
+1 -79
View File
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use egui::{Align, Id, Layout, RichText, Rounding};
use url::Url;
use crate::tor::{TorServer, TorServerConfig};
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GEAR_SIX, PENCIL, POWER, TRASH, WARNING_CIRCLE, X_CIRCLE};
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, PENCIL, POWER, TRASH, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, NodeSetup, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions};
@@ -101,15 +99,6 @@ impl ConnectionsContent {
// Show integrated node info content.
Self::integrated_node_item_ui(ui);
// Show transport connections.
ui.add_space(6.0);
let transport_text = format!("{}:", t!("wallets.transport"));
ui.label(RichText::new(transport_text).size(16.0).color(Colors::GRAY));
ui.add_space(6.0);
// Show Tor SOCKS server.
Self::tor_transport_item_ui(ui);
// Show external connections.
let ext_conn_list = ConnectionsConfig::ext_conn_list();
if !ext_conn_list.is_empty() {
@@ -123,73 +112,6 @@ impl ConnectionsContent {
});
}
}
// Redraw after delay if Tor server is running.
if TorServer::is_running() || TorServer::is_starting() ||
TorServer::is_stopping() {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
}
}
/// Draw Tor connection item content.
fn tor_transport_item_ui(ui: &mut egui::Ui) {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
let rounding = View::item_rounding(0, 1, false);
ui.painter().rect(rect, rounding, Colors::FILL, View::ITEM_STROKE);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to show Tor connection settings.
View::item_button(ui, View::item_rounding(0, 1, true), GEAR_SIX, None, || {
AppConfig::toggle_show_connections_network_panel();
});
// Draw buttons to stop or start Tor server.
if !TorServer::is_stopping() && !TorServer::is_starting() {
if TorServer::is_running() {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::RED), || {
TorServer::stop();
});
} else {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::GREEN), || {
TorServer::start();
});
}
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("network.tor_network"))
.size(18.0)
.color(Colors::TITLE));
// Setup SOCKS server address.
let socks_port = TorServerConfig::socks_port();
let addr_text = format!("{} http://127.0.0.1:{}", COMPUTER_TOWER, socks_port);
ui.label(RichText::new(addr_text).size(15.0).color(Colors::TEXT));
ui.add_space(1.0);
// Setup server status text.
let (status_icon, status_text) = if TorServer::has_error() {
(WARNING_CIRCLE, t!("network.server_error"))
} else if TorServer::is_starting() {
(DOTS_THREE_CIRCLE, t!("network.server_starting"))
} else if TorServer::is_stopping() {
(DOTS_THREE_CIRCLE, t!("network.server_stopping"))
} else if TorServer::is_running() {
(CHECK_CIRCLE, t!("network.server_enabled"))
} else {
(X_CIRCLE, t!("network.server_disabled"))
};
let status_text = format!("{} {}", status_icon, status_text);
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
})
});
});
}
/// Draw integrated node connection item content.
+10 -1
View File
@@ -83,7 +83,9 @@ pub struct TextEditOptions {
/// Flag to show copy button.
pub copy: bool,
/// Flag to show paste button.
pub paste: bool
pub paste: bool,
/// Flag to show button to scan QR code into text.
pub scan_qr: bool
}
impl TextEditOptions {
@@ -95,6 +97,7 @@ impl TextEditOptions {
password: false,
copy: false,
paste: false,
scan_qr: false,
}
}
@@ -127,4 +130,10 @@ impl TextEditOptions {
self.paste = true;
self
}
/// Show button to scan QR code to text.
pub fn scan_qr(mut self) -> Self {
self.scan_qr = true;
self
}
}
+10 -1
View File
@@ -25,7 +25,7 @@ use egui::text::{LayoutJob, TextFormat};
use egui::text_edit::TextEditState;
use crate::gui::Colors;
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SQUARE};
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::TextEditOptions;
@@ -346,6 +346,15 @@ impl View {
ui.add_space(8.0);
}
// Setup scan QR code button.
if options.paste {
let scan_icon = SCAN.to_string();
View::button(ui, scan_icon, Colors::WHITE, || {
//TODO: open scanner
});
ui.add_space(8.0);
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
// Setup text edit size.
+2 -2
View File
@@ -19,7 +19,7 @@ use grin_core::core::amount_to_hr_string;
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, QR_CODE, REPEAT, USERS_THREE};
use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, REPEAT, SCAN, USERS_THREE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions};
@@ -176,7 +176,7 @@ impl WalletContent {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to scan QR code.
View::item_button(ui, View::item_rounding(0, 2, true), QR_CODE, None, || {
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
//TODO: Scan with QR code.
});
+58 -59
View File
@@ -53,9 +53,6 @@ impl MessageError {
/// Slatepacks messages interaction tab content.
pub struct WalletMessages {
/// Flag to check if send or invoice request was opened.
send_request: bool,
/// Slatepack message to create response message.
message_edit: String,
/// Parsed Slatepack message.
@@ -67,15 +64,17 @@ pub struct WalletMessages {
/// Flag to check if Dandelion is needed to finalize transaction.
dandelion: bool,
/// Amount to send or receive.
/// Flag to check if send or invoice request was opened for [`Modal`].
send_request: bool,
/// Amount to send or receive at [`Modal`].
amount_edit: String,
/// Generated Slatepack message as request to send or receive funds.
/// Generated Slatepack message as request to send or receive funds at [`Modal`].
request_edit: String,
/// Flag to check if there is an error happened on invoice creation.
/// Flag to check if there is an error happened on request creation at [`Modal`].
request_error: Option<MessageError>,
}
/// Identifier for invoice amount [`Modal`].
/// Identifier for amount input [`Modal`].
const AMOUNT_MODAL: &'static str = "amount_modal";
impl WalletMessages {
@@ -127,7 +126,7 @@ impl WalletTab for WalletMessages {
.show_inside(ui, |ui| {
ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.id_source(Id::from("wallet_manual").with(wallet.get_config().id))
.id_source(Id::from("wallet_messages").with(wallet.get_config().id))
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.vertical_centered(|ui| {
@@ -181,6 +180,54 @@ impl WalletMessages {
}
}
/// Draw creation of request to send or receive funds.
fn request_ui(&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks) {
ui.label(RichText::new(t!("wallets.create_request_desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw send request creation button.
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
View::button(ui, send_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = true;
self.amount_edit = "".to_string();
self.request_error = None;
// Show send amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
});
});
columns[1].vertical_centered_justified(|ui| {
// Draw invoice request creation button.
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
View::button(ui, receive_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = false;
self.amount_edit = "".to_string();
self.request_error = None;
// Show receive amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
cb.show_keyboard();
});
});
});
}
/// Draw Slatepack message input content.
fn input_slatepack_ui(&mut self,
ui: &mut egui::Ui,
@@ -509,55 +556,7 @@ impl WalletMessages {
}
}
/// Draw creation of request to send or receive funds.
fn request_ui(&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks) {
ui.label(RichText::new(t!("wallets.create_request_desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw send request creation button.
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
View::button(ui, send_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = true;
self.amount_edit = "".to_string();
self.request_error = None;
// Show send amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
});
});
columns[1].vertical_centered_justified(|ui| {
// Draw invoice request creation button.
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
View::button(ui, receive_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = false;
self.amount_edit = "".to_string();
self.request_error = None;
// Show receive amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
cb.show_keyboard();
});
});
});
}
/// Draw invoice amount [`Modal`] content.
/// Draw amount input [`Modal`] content to create invoice or request to send funds.
fn amount_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
@@ -621,7 +620,7 @@ impl WalletMessages {
}
}
// Show invoice creation error.
// Show request creation error.
if self.request_error.is_some() {
ui.add_space(12.0);
ui.vertical_centered(|ui| {
@@ -658,7 +657,7 @@ impl WalletMessages {
wallet.issue_invoice(a)
};
match message {
Ok(message) => {
Ok((_, message)) => {
self.request_edit = message;
cb.hide_keyboard();
}
+514 -10
View File
@@ -12,17 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::Margin;
use std::sync::{Arc, RwLock};
use std::thread;
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
use egui::scroll_area::ScrollBarVisibility;
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::SlatepackAddress;
use crate::gui::Colors;
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, UPLOAD, WARNING_CIRCLE, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent;
use crate::tor::Tor;
use crate::wallet::Wallet;
/// Sending tab content.
#[derive(Default)]
pub struct WalletTransport;
/// Wallet transport tab content.
pub struct WalletTransport {
/// Flag to check if transaction is sending over Tor to show progress at [`Modal`].
tor_sending: Arc<RwLock<bool>>,
/// Flag to check if error occurred during sending of transaction over Tor at [`Modal`].
tor_send_error: Arc<RwLock<bool>>,
/// Flag to check if transaction sent successfully over Tor [`Modal`].
tor_success: Arc<RwLock<bool>>,
/// Entered amount value for [`Modal`].
amount_edit: String,
/// Entered address value for [`Modal`].
address_edit: String,
/// Flag to check if entered address is incorrect at [`Modal`].
address_error: bool,
/// Flag to check if [`Modal`] was just opened to focus on first field.
modal_just_opened: bool,
}
impl Default for WalletTransport {
fn default() -> Self {
Self {
tor_sending: Arc::new(RwLock::new(false)),
tor_send_error: Arc::new(RwLock::new(false)),
tor_success: Arc::new(RwLock::new(false)),
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
modal_just_opened: false,
}
}
}
impl WalletTab for WalletTransport {
fn get_type(&self) -> WalletTabType {
@@ -33,11 +70,14 @@ impl WalletTab for WalletTransport {
ui: &mut egui::Ui,
_: &mut eframe::Frame,
wallet: &mut Wallet,
_: &dyn PlatformCallbacks) {
cb: &dyn PlatformCallbacks) {
if WalletContent::sync_ui(ui, wallet) {
return;
}
// Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb);
// Show transport content panel.
egui::CentralPanel::default()
.frame(egui::Frame {
@@ -52,14 +92,478 @@ impl WalletTab for WalletTransport {
..Default::default()
})
.show_inside(ui, |ui| {
self.transport_ui(ui, wallet);
ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.vertical_centered(|ui| {
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
self.ui(ui, wallet, cb);
});
});
});
});
}
}
impl WalletTransport {
/// Draw transport content.
pub fn transport_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet) {
/// Identifier for [`Modal`] to send amount over Tor.
const SEND_TOR_MODAL: &'static str = "send_tor_modal";
impl WalletTransport {
/// Draw wallet transport content.
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
ui.add_space(3.0);
ui.label(RichText::new(t!("transport.desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Draw Tor content.
self.tor_ui(ui, wallet, cb);
}
/// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
match Modal::opened() {
None => {}
Some(id) => {
match id {
SEND_TOR_MODAL => {
Modal::ui(ui.ctx(), |ui, modal| {
self.send_tor_modal_ui(ui, wallet, modal, cb);
});
}
_ => {}
}
}
}
}
/// Draw Tor transport content.
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Draw header content.
self.tor_header_ui(ui, wallet, cb);
// Draw receive info content.
if wallet.slatepack_address().is_some() {
self.tor_receive_ui(ui, wallet, cb);
}
// Draw send content.
self.tor_send_ui(ui, wallet, cb);
}
/// Draw Tor transport header content.
fn tor_header_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(0, 2, false);
ui.painter().rect(bg_rect, item_rounding, Colors::BUTTON, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to setup Tor transport.
let button_rounding = View::item_rounding(0, 2, true);
View::item_button(ui, button_rounding, GEAR_SIX, None, || {
//TODO: tor settings
});
// Draw button to enable/disable Tor listener for current wallet.
let service_id = &wallet.identifier();
if !Tor::is_service_running(service_id) &&
wallet.foreign_api_port().is_some() {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::GREEN), || {
if let Ok(key) = wallet.secret_key() {
Tor::start_service(wallet.foreign_api_port().unwrap(), key, service_id);
}
});
} else if !Tor::is_service_starting(service_id) {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::RED), || {
Tor::stop_service(service_id);
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("transport.tor_network"))
.size(18.0)
.color(Colors::TITLE));
// Setup wallet API address text.
let port = wallet.foreign_api_port().unwrap();
let address_text = format!("{} http://127.0.0.1:{}", GLOBE_SIMPLE, port);
ui.label(RichText::new(address_text).size(15.0).color(Colors::TEXT));
ui.add_space(1.0);
// Setup Tor status text.
let is_running = Tor::is_service_running(service_id);
let is_starting = Tor::is_service_starting(service_id);
let has_error = Tor::is_service_failed(service_id);
let (icon, text) = if is_starting {
(DOTS_THREE_CIRCLE, t!("transport.connecting"))
} else if has_error {
(WARNING_CIRCLE, t!("transport.conn_error"))
} else if is_running {
(CHECK_CIRCLE, t!("transport.connected"))
} else {
(X_CIRCLE, t!("transport.disconnected"))
};
let status_text = format!("{} {}", icon, text);
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
});
});
});
});
}
/// Draw Tor send content.
fn tor_receive_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
let slatepack_addr = wallet.slatepack_address().unwrap();
let service_id = &wallet.identifier();
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(52.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(1, 3, false);
ui.painter().rect(bg_rect, item_rounding, Colors::BUTTON, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to setup Tor transport.
let button_rounding = View::item_rounding(1, 3, true);
View::item_button(ui, button_rounding, QR_CODE, None, || {
//TODO: qr for address
});
// Show button to enable/disable Tor listener for current wallet.
View::item_button(ui, Rounding::default(), COPY, None, || {
cb.copy_string_to_buffer(slatepack_addr.clone());
});
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
// Show wallet Slatepack address.
let address_color = if Tor::is_service_starting(service_id) {
Colors::INACTIVE_TEXT
} else if Tor::is_service_running(service_id) {
Colors::GREEN
} else {
Colors::RED
};
View::ellipsize_text(ui, slatepack_addr, 15.0, address_color);
let address_label = format!("{} {}",
COMPUTER_TOWER,
t!("network_mining.address"));
ui.label(RichText::new(address_label).size(15.0).color(Colors::GRAY));
});
});
});
});
}
/// Draw Tor receive content.
fn tor_send_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(55.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(1, 2, false);
ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::top_down(Align::Center), |ui| {
ui.add_space(7.0);
// Draw button to open sending modal.
let send_text = format!("{} {}", EXPORT, t!("wallets.send"));
View::button(ui, send_text, Colors::WHITE, || {
self.show_send_tor_modal(cb);
});
});
});
}
/// Show [`Modal`] to send over Tor.
fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks) {
// Setup modal values.
let mut w_send_err = self.tor_send_error.write().unwrap();
*w_send_err = false;
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = false;
let mut w_success = self.tor_success.write().unwrap();
*w_success = false;
self.modal_just_opened = true;
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
self.address_error = false;
// Show modal.
Modal::new(SEND_TOR_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
}
/// Check if error occurred during sending over Tor at [`Modal`].
fn has_tor_send_error(&self) -> bool {
let r_send_err = self.tor_send_error.read().unwrap();
r_send_err.clone()
}
/// Check if transaction is sending over Tor to show progress at [`Modal`].
fn tor_sending(&self) -> bool {
let r_sending = self.tor_sending.read().unwrap();
r_sending.clone()
}
/// Check if transaction sent over Tor with success at [`Modal`].
fn tor_success(&self) -> bool {
let r_success = self.tor_success.read().unwrap();
r_success.clone()
}
/// Draw amount input [`Modal`] content to send over Tor.
/// Draw amount input [`Modal`] content to send over Tor.
fn send_tor_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
let has_send_err = self.has_tor_send_error();
let sending = self.tor_sending();
if !has_send_err && !sending {
ui.vertical_centered(|ui| {
let data = wallet.get_data().unwrap();
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
let enter_text = t!("wallets.enter_amount_send","amount" => amount);
ui.label(RichText::new(enter_text)
.size(17.0)
.color(Colors::GRAY));
});
ui.add_space(8.0);
// Draw amount text edit.
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center().no_focus();
let amount_edit_before = self.amount_edit.clone();
if self.modal_just_opened {
self.modal_just_opened = false;
amount_edit_opts.focus = true;
}
View::text_edit(ui, cb, &mut self.amount_edit, amount_edit_opts);
ui.add_space(8.0);
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
if !self.amount_edit.is_empty() {
match amount_from_hr_string(self.amount_edit.as_str()) {
Ok(a) => {
if !self.amount_edit.contains(".") {
// To avoid input of several "0".
if a == 0 {
self.amount_edit = "0".to_string();
return;
}
} else {
// Check input after ".".
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
if parts.len() == 2 && parts[1].len() > 9 {
self.amount_edit = amount_edit_before;
return;
}
}
// Do not input amount more than balance in sending.
let b = wallet.get_data().unwrap().info.amount_currently_spendable;
if b < a {
self.amount_edit = amount_edit_before;
}
}
Err(_) => {
self.amount_edit = amount_edit_before;
}
}
}
}
// Show address error or input description.
ui.vertical_centered(|ui| {
if self.address_error {
ui.label(RichText::new(t!("transport.incorrect_addr_err"))
.size(17.0)
.color(Colors::RED));
} else {
ui.label(RichText::new(t!("transport.receiver_address"))
.size(17.0)
.color(Colors::GRAY));
}
});
ui.add_space(8.0);
// Draw address text edit.
let addr_edit_before = self.address_edit.clone();
let address_edit_id = Id::from(modal.id).with("address").with(wallet.get_config().id);
let address_edit_opts = TextEditOptions::new(address_edit_id)
.paste()
.scan_qr()
.no_focus();
View::text_edit(ui, cb, &mut self.address_edit, address_edit_opts);
ui.add_space(12.0);
// Check value if input was changed.
if addr_edit_before != self.address_edit {
self.address_error = false;
}
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
cb.hide_keyboard();
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("continue"), Colors::WHITE, || {
if self.amount_edit.is_empty() {
return;
}
// Check entered address.
let addr_str = self.address_edit.as_str();
if let Ok(addr) = SlatepackAddress::try_from(addr_str) {
// Parse amount and send over Tor.
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
cb.hide_keyboard();
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = true;
{
let send_error = self.tor_send_error.clone();
let send_success = self.tor_success.clone();
let mut wallet = wallet.clone();
thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
if wallet.send_tor(a, &addr).await.is_some() {
let mut w_send_success
= send_success.write().unwrap();
*w_send_success = true;
} else {
let mut w_send_error
= send_error.write().unwrap();
*w_send_error = true;
}
});
});
}
}
} else {
self.address_error = true;
}
});
});
});
ui.add_space(6.0);
} else if has_send_err {
ui.add_space(6.0);
ui.label(RichText::new(t!("transport.tor_send_error"))
.size(17.0)
.color(Colors::RED));
ui.add_space(12.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
cb.hide_keyboard();
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("repeat"), Colors::WHITE, || {
// Parse amount and send over Tor.
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
let mut w_send_error = self.tor_send_error.write().unwrap();
*w_send_error = false;
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = true;
{
let addr_text = self.address_edit.clone();
let send_error = self.tor_send_error.clone();
let send_success = self.tor_success.clone();
let mut wallet = wallet.clone();
thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let addr_str = addr_text.as_str();
let addr = &SlatepackAddress::try_from(addr_str)
.unwrap();
if wallet.send_tor(a, &addr).await.is_some() {
let mut w_send_success
= send_success.write().unwrap();
*w_send_success = true;
} else {
let mut w_send_error
= send_error.write().unwrap();
*w_send_error = true;
}
});
});
}
}
});
});
});
} else {
ui.add_space(16.0);
ui.vertical_centered(|ui| {
View::small_loading_spinner(ui);
ui.add_space(12.0);
ui.label(RichText::new(t!("transport.tor_sending"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
// Close modal on success sending.
if self.tor_success() {
modal.close();
}
}
}
}
+6 -6
View File
@@ -24,7 +24,7 @@ use serde::Serialize;
use crate::node::NodeConfig;
use crate::settings::AppConfig;
use crate::tor::TorServerConfig;
use crate::tor::TorConfig;
use crate::wallet::ConnectionsConfig;
lazy_static! {
@@ -44,7 +44,7 @@ pub struct Settings {
/// Wallet connections configuration.
conn_config: Arc<RwLock<ConnectionsConfig>>,
/// Tor server configuration.
tor_config: Arc<RwLock<TorServerConfig>>
tor_config: Arc<RwLock<TorConfig>>
}
impl Settings {
@@ -55,8 +55,8 @@ impl Settings {
let app_config = Self::init_config::<AppConfig>(app_config_path);
// Initialize tor config.
let tor_config_path = Settings::get_config_path(TorServerConfig::FILE_NAME, None);
let tor_config = Self::init_config::<TorServerConfig>(tor_config_path);
let tor_config_path = Settings::get_config_path(TorConfig::FILE_NAME, None);
let tor_config = Self::init_config::<TorConfig>(tor_config_path);
let chain_type = &app_config.chain_type;
Self {
@@ -110,12 +110,12 @@ impl Settings {
}
/// Get tor server configuration to read values.
pub fn tor_config_to_read() -> RwLockReadGuard<'static, TorServerConfig> {
pub fn tor_config_to_read() -> RwLockReadGuard<'static, TorConfig> {
SETTINGS_STATE.tor_config.read().unwrap()
}
/// Get tor server configuration to update values.
pub fn tor_config_to_update() -> RwLockWriteGuard<'static, TorServerConfig> {
pub fn tor_config_to_update() -> RwLockWriteGuard<'static, TorConfig> {
SETTINGS_STATE.tor_config.write().unwrap()
}
+14 -27
View File
@@ -12,31 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::path::PathBuf;
use serde_derive::{Deserialize, Serialize};
use crate::Settings;
/// Tor SOCKS proxy server configuration.
/// Tor configuration.
#[derive(Serialize, Deserialize, Clone)]
pub struct TorServerConfig {
socks_port: u16
pub struct TorConfig {
// Flag to check if Tor bridges usage is needed.
pub(crate) use_bridges: Option<bool>
}
/// Default SOCKS port value.
const DEFAULT_SOCKS_PORT: u16 = 9060;
impl Default for TorServerConfig {
impl Default for TorConfig {
fn default() -> Self {
Self {
socks_port: DEFAULT_SOCKS_PORT,
use_bridges: Some(false)
}
}
}
impl TorServerConfig {
impl TorConfig {
/// Tor configuration file name.
pub const FILE_NAME: &'static str = "tor.toml";
/// Directory for config and Tor related files.
/// Directory for Tor data files.
const DIR_NAME: &'static str = "tor";
/// Subdirectory name for Tor state.
@@ -48,11 +47,10 @@ impl TorServerConfig {
/// Save application configuration to the file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME,
Some(Self::DIR_NAME.to_string())));
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None));
}
/// Get subdirectory path from dir name.
/// Get path from subdirectory name.
fn sub_dir_path(name: &str) -> String {
let mut base = Settings::get_base_path(Some(Self::DIR_NAME.to_string()));
base.push(name);
@@ -71,19 +69,8 @@ impl TorServerConfig {
/// Get Tor keystore directory path.
pub fn keystore_path() -> String {
Self::sub_dir_path(Self::KEYSTORE_DIR)
}
/// Get SOCKS port value.
pub fn socks_port() -> u16 {
let r_config = Settings::tor_config_to_read();
r_config.socks_port
}
/// Save SOCKS port value.
pub fn save_socks_port(port: u16) {
let mut w_config = Settings::tor_config_to_update();
w_config.socks_port = port;
w_config.save();
let mut base = PathBuf::from(Self::state_path());
base.push(Self::KEYSTORE_DIR);
base.to_str().unwrap().to_string()
}
}
+2 -2
View File
@@ -13,7 +13,7 @@
// limitations under the License.
mod config;
pub use config::TorServerConfig;
pub use config::TorConfig;
mod tor;
pub use tor::TorServer;
pub use tor::Tor;
+173 -242
View File
@@ -12,203 +12,129 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use std::net::SocketAddr;
use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use futures::executor::block_on;
use lazy_static::lazy_static;
use futures::task::SpawnExt;
use tokio::task::JoinHandle;
use anyhow::Result;
use tokio::time::sleep;
use arti::socks::run_socks_proxy;
use arti_client::{TorClient, TorClientConfig};
use arti_client::config::pt::TransportConfigBuilder;
use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder};
use arti_client::config::TorClientConfigBuilder;
use fs_mistrust::Mistrust;
use grin_util::secp::SecretKey;
use grin_wallet_util::OnionV3Address;
use ed25519_dalek::hazmat::ExpandedSecretKey;
use curve25519_dalek::digest::Digest;
use sha2::Sha512;
use tor_config::{CfgPath, Listen};
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
use tor_rtcompat::{BlockOn, Runtime};
use tor_rtcompat::Runtime;
use tor_hsrproxy::OnionServiceReverseProxy;
use tor_hsrproxy::config::{Encapsulation, ProxyAction, ProxyPattern, ProxyRule, TargetAddr, ProxyConfigBuilder};
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname};
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname, RunningOnionService};
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
use arti_hyper::ArtiHttpConnector;
use futures::TryFutureExt;
use hyper::Body;
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
use crate::tor::TorServerConfig;
// On aarch64-apple-darwin targets there is an issue with the native and rustls
// tls implementation so this makes it fall back to the openssl variant.
//
// https://gitlab.torproject.org/tpo/core/arti/-/issues/715
#[cfg(not(all(target_vendor = "apple", target_arch = "aarch64")))]
use tls_api_native_tls::TlsConnector;
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
use tls_api_openssl::TlsConnector;
use crate::tor::TorConfig;
lazy_static! {
/// Static thread-aware state of [`Node`] to be updated from separate thread.
static ref TOR_SERVER_STATE: Arc<TorServer> = Arc::new(TorServer::default());
static ref TOR_SERVER_STATE: Arc<Tor> = Arc::new(Tor::default());
}
/// Tor server to use as SOCKS proxy for requests and to launch Onion services.
pub struct TorServer {
/// Running Tor client.
client: Arc<RwLock<Option<TorClient<TokioNativeTlsRuntime>>>>,
/// Running Tor client configuration.
config: Arc<RwLock<Option<TorClientConfig>>>,
/// Flag to check if server is running.
running: AtomicBool,
/// Flag to check if server is starting.
starting: AtomicBool,
/// Flag to check if server needs to stop.
stopping: AtomicBool,
/// Flag to check if error happened.
error: AtomicBool,
pub struct Tor {
/// [`TorClient`] used for connections with configuration.
client: Arc<RwLock<(TorClient<TokioNativeTlsRuntime>, TorClientConfig)>>,
/// Mapping of running Onion services identifiers to proxy.
running_services: Arc<RwLock<HashMap<String, Arc<OnionServiceReverseProxy>>>>
running_services: Arc<RwLock<BTreeMap<String,
(Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>>>,
/// Starting Onion services identifiers.
starting_services: Arc<RwLock<BTreeSet<String>>>,
/// Failed Onion services identifiers.
failed_services: Arc<RwLock<BTreeSet<String>>>
}
impl Default for TorServer {
impl Default for Tor {
fn default() -> Self {
// Create Tor client config.
let mut builder =
TorClientConfigBuilder::from_directories(TorConfig::state_path(),
TorConfig::cache_path());
builder.address_filter().allow_onion_addrs(true);
// Create connected Tor client from config.
let runtime = TokioNativeTlsRuntime::create().unwrap();
let config = builder.build().unwrap();
let client = TorClient::with_runtime(runtime)
.config(config.clone())
.create_unbootstrapped()
.unwrap();
Self {
running: AtomicBool::new(false),
starting: AtomicBool::new(false),
stopping: AtomicBool::new(false),
error: AtomicBool::new(false),
client: Arc::new(RwLock::new(None)),
running_services: Arc::new(RwLock::new(HashMap::new())),
config: Arc::new(RwLock::new(None)),
client: Arc::new(RwLock::new((client, config))),
running_services: Arc::new(RwLock::new(BTreeMap::new())),
starting_services: Arc::new(RwLock::new(BTreeSet::new())),
failed_services: Arc::new(RwLock::new(BTreeSet::new()))
}
}
}
impl TorServer {
/// Check if server is running.
pub fn is_running() -> bool {
TOR_SERVER_STATE.running.load(Ordering::Relaxed)
}
impl Tor {
/// Send post request using Tor.
pub async fn post(body: String, url: String) -> Option<String> {
// Bootstrap client.
let client_config = TOR_SERVER_STATE.client.read().unwrap();
let client = client_config.0.clone();
client.bootstrap().await.unwrap();
/// Check if server is running.
pub fn is_starting() -> bool {
TOR_SERVER_STATE.starting.load(Ordering::Relaxed)
}
// 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::Client::builder().build::<_, Body>(tor_connector);
// Create request.
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(url)
.body(Body::from(body))
.unwrap();
/// Check if server is stopping.
pub fn is_stopping() -> bool {
TOR_SERVER_STATE.stopping.load(Ordering::Relaxed)
}
/// Check if server has error.
pub fn has_error() -> bool {
TOR_SERVER_STATE.error.load(Ordering::Relaxed)
}
/// Stop the server.
pub fn stop() {
TOR_SERVER_STATE.stopping.store(true, Ordering::Relaxed);
}
/// Start or restart the server if already running.
pub fn start() {
if Self::is_running() {
Self::stop();
}
thread::spawn(|| {
while Self::is_stopping() {
thread::sleep(Duration::from_millis(1000));
}
TOR_SERVER_STATE.starting.store(true, Ordering::Relaxed);
TOR_SERVER_STATE.error.store(false, Ordering::Relaxed);
// Check if Tor client is already running.
if TOR_SERVER_STATE.client.read().unwrap().is_some() {
let r_client = TOR_SERVER_STATE.client.read().unwrap();
let client = r_client.as_ref().unwrap().clone();
let runtime = client.runtime().clone();
let _ = runtime.clone().block_on(Self::launch_socks_proxy(runtime, client));
} else {
// Create Tor client config to connect.
let mut builder =
TorClientConfigBuilder::from_directories(TorServerConfig::state_path(),
TorServerConfig::cache_path());
builder.address_filter().allow_onion_addrs(true);
// Setup Snowflake bridges.
Self::setup_bridges(&mut builder);
// Create Tor client from config.
if let Ok(config) = builder.build() {
let mut w_config = TOR_SERVER_STATE.config.write().unwrap();
*w_config = Some(config.clone());
// Restart server on connection timeout.
thread::spawn(|| {
thread::sleep(Duration::from_millis(30000));
let r_client = TOR_SERVER_STATE.client.read().unwrap();
if r_client.is_none() {
Self::start();
}
});
// Create Tor client.
let runtime = TokioNativeTlsRuntime::create().unwrap();
match TorClient::with_runtime(runtime.clone())
.config(config)
.bootstrap_behavior(arti_client::BootstrapBehavior::OnDemand)
.create_unbootstrapped() {
Ok(tor_client) => {
let mut w_client = TOR_SERVER_STATE.client.write().unwrap();
if w_client.is_some() {
return;
}
*w_client = Some(tor_client.clone());
let _ = runtime.clone().block_on(
// Launch SOCKS proxy server.
Self::launch_socks_proxy(runtime, tor_client)
);
}
Err(e) => {
eprintln!("{}", e);
TOR_SERVER_STATE.starting.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.error.store(true, Ordering::Relaxed);
}
}
} else {
TOR_SERVER_STATE.starting.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.error.store(true, Ordering::Relaxed);
// Send request.
let mut resp = None;
match http.request(req).await {
Ok(r) => {
match hyper::body::to_bytes(r).await {
Ok(raw) => {
resp = Some(String::from_utf8_lossy(&raw).to_string())
},
Err(_) => {},
}
}
});
},
Err(_) => {},
}
resp
}
/// Launch SOCKS proxy server to send connections.
async fn launch_socks_proxy<R: Runtime>(runtime: R, tor_client: TorClient<R>) -> Result<()> {
let proxy_handle: JoinHandle<Result<()>> = tokio::spawn(
run_socks_proxy(
runtime,
tor_client,
Listen::new_localhost(TorServerConfig::socks_port()),
)
);
// Setup server state flags.
TOR_SERVER_STATE.starting.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.running.store(true, Ordering::Relaxed);
loop {
if Self::is_stopping() || proxy_handle.is_finished() {
proxy_handle.abort();
TOR_SERVER_STATE.stopping.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.running.store(false, Ordering::Relaxed);
return Ok(());
}
sleep(Duration::from_millis(3000)).await;
}
/// Check if Onion service is starting.
pub fn is_service_starting(id: &String) -> bool {
let r_services = TOR_SERVER_STATE.starting_services.read().unwrap();
r_services.contains(id)
}
/// Check if Onion service is running.
@@ -217,36 +143,80 @@ impl TorServer {
r_services.contains_key(id)
}
/// Check if Onion service failed on start.
pub fn is_service_failed(id: &String) -> bool {
let r_services = TOR_SERVER_STATE.failed_services.read().unwrap();
r_services.contains(id)
}
/// Stop running Onion service.
pub fn stop_service(id: &String) {
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
if let Some(proxy) = w_services.remove(id) {
if let Some((svc, proxy)) = w_services.remove(id) {
proxy.shutdown();
drop(svc);
}
}
/// Run Onion service from listening local address, secret key and identifier.
pub fn run_service(addr: SocketAddr, key: SecretKey, id: &String) {
/// Start Onion service from listening local port and [`SecretKey`].
pub fn start_service(port: u16, key: SecretKey, id: &String) {
// Check if service is already running.
if Self::is_service_running(id) {
return;
} else {
// Save starting service.
let mut w_services = TOR_SERVER_STATE.starting_services.write().unwrap();
w_services.insert(id.clone());
// Remove service from failed.
let mut w_services = TOR_SERVER_STATE.failed_services.write().unwrap();
w_services.remove(id);
}
let hs_nickname = HsNickname::new(id.clone()).unwrap();
let service_config = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
.build()
.unwrap();
let r_client = TOR_SERVER_STATE.client.read().unwrap();
let client = r_client.clone().unwrap();
let service_id = id.clone();
let client_config = TOR_SERVER_STATE.client.read().unwrap();
let client = client_config.0.clone();
let config = client_config.1.clone();
client.clone().runtime().spawn(async move {
// Add service key to keystore.
let hs_nickname = HsNickname::new(service_id.clone()).unwrap();
Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname);
// Add service key to keystore.
let r_config = TOR_SERVER_STATE.config.read().unwrap();
let config = r_config.clone().unwrap();
Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname);
// Bootstrap client and launch Onion service.
client.bootstrap().await.unwrap();
let service_config = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
.build()
.unwrap();
let (service, request) = client.launch_onion_service(service_config).unwrap();
// Launch Onion service.
let (_, request) = client.launch_onion_service(service_config).unwrap();
// Launch service proxy.
let addr = SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), port);
tokio::spawn(
Self::run_service_proxy(addr, client, service.clone(), request, hs_nickname.clone())
).await.unwrap();
println!(
"Onion service {} launched at: {}",
hs_nickname,
service.onion_name().unwrap().to_string()
);
}).unwrap();
}
/// Launch Onion service proxy.
async fn run_service_proxy<R, S>(
addr: SocketAddr,
client: TorClient<R>,
service: Arc<RunningOnionService>,
request: S,
nickname: HsNickname
)
where
R: Runtime,
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
{
let id = nickname.to_string();
let runtime = client.runtime().clone();
// Setup proxy to forward request from Tor address to local address.
let proxy_rule = ProxyRule::new(
@@ -257,43 +227,49 @@ impl TorServer {
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap());
// Launch proxy at client runtime.
let proxy_service = proxy.clone();
let runtime = client.runtime().clone();
let nickname = hs_nickname.clone();
// Save running service.
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
w_services.insert(id.clone(), (service.clone(), proxy.clone()));
// Remove service from starting.
let mut w_services = TOR_SERVER_STATE.starting_services.write().unwrap();
w_services.remove(&id);
// Start proxy for launched service.
client
.runtime()
.spawn(async move {
// Launch proxy for launched service.
match proxy_service.handle_requests(runtime, nickname.clone(), request).await {
match proxy
.handle_requests(runtime, nickname.clone(), request)
.await {
Ok(()) => {
eprintln!("Onion service {} stopped.", nickname);
// Remove service from running.
let mut w_services =
TOR_SERVER_STATE.running_services.write().unwrap();
w_services.remove(&id);
println!("Onion service {} stopped.", nickname);
}
Err(e) => {
// Remove service from running.
let mut w_services =
TOR_SERVER_STATE.running_services.write().unwrap();
w_services.remove(&id);
// Save failed service.
let mut w_services =
TOR_SERVER_STATE.failed_services.write().unwrap();
w_services.insert(id);
eprintln!("Onion service {} exited with an error: {}", nickname, e);
}
}
}).unwrap();
// Save running service.
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
w_services.insert(id.clone(), proxy);
let onion_addr = OnionV3Address::from_private(&key.0).unwrap();
eprintln!("Onion service {} launched at {}", hs_nickname, onion_addr.to_ov3_str());
}
/// Add Onion service key to keystore.
/// Save Onion service key to keystore.
fn add_service_key(mistrust: &Mistrust, key: &SecretKey, hs_nickname: &HsNickname) {
let mut client_config_builder = TorClientConfigBuilder::from_directories(
TorServerConfig::state_path(),
TorServerConfig::cache_path()
);
client_config_builder
.address_filter()
.allow_onion_addrs(true);
let arti_store =
ArtiNativeKeystore::from_path_and_mistrust(TorServerConfig::keystore_path(), &mistrust)
ArtiNativeKeystore::from_path_and_mistrust(TorConfig::keystore_path(), &mistrust)
.unwrap();
let key_manager = KeyMgrBuilder::default()
@@ -329,49 +305,4 @@ impl TorServer {
)
.unwrap();
}
/// Setup Tor Snowflake bridges.
fn setup_bridges(builder: &mut TorClientConfigBuilder) {
// Add a single bridge to the list of bridges, from a bridge line.
// This line comes from https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/blob/main/projects/common/bridges_list.snowflake.txt
// this is a real bridge line you can use as-is, after making sure it's still up to date with
// above link.
const BRIDGE1_LINE: &str = "Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn";
let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse().unwrap();
builder.bridges().bridges().push(bridge_1);
// Add a second bridge, built by hand. We use the 2nd bridge line from above, but modify some
// parameters to use AMP Cache instead of Fastly as a signaling channel. The difference in
// configuration is detailed in
// https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/tree/main/client#amp-cache
let mut bridge2_builder = BridgeConfigBuilder::default();
bridge2_builder
.transport("snowflake")
.push_setting(
"fingerprint",
"8838024498816A039FCBBAB14E6F40A0843051FA"
)
.push_setting("url", "https://snowflake-broker.torproject.net/")
.push_setting("ampcache", "https://cdn.ampproject.org/")
.push_setting("front", "www.google.com")
.push_setting(
"ice",
"stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478",
)
.push_setting("utls-imitate", "hellorandomizedalpn");
bridge2_builder.set_addrs(vec!["192.0.2.4:80".parse().unwrap()]);
bridge2_builder.set_ids(vec!["8838024498816A039FCBBAB14E6F40A0843051FA".parse().unwrap()]);
// Now insert the second bridge into our config builder.
builder.bridges().bridges().push(bridge2_builder);
// Now configure an snowflake transport. (Requires the "pt-client" feature)
let mut transport = TransportConfigBuilder::default();
transport
.protocols(vec!["snowflake".parse().unwrap()])
// this might be named differently on some systems, this should work on Debian,
// but Archlinux is known to use `snowflake-pt-client` instead for instance.
.path(CfgPath::new("snowflake-client".into()))
.run_on_startup(true);
builder.bridges().transports().push(transport);
}
}
+4 -1
View File
@@ -39,7 +39,9 @@ pub struct WalletConfig {
/// Minimal amount of confirmations.
pub min_confirmations: u64,
/// Flag to use Dandelion to broadcast transactions.
pub use_dandelion: Option<bool>
pub use_dandelion: Option<bool>,
/// Flag to enable Tor listener on start.
pub enable_tor_listener: Option<bool>
}
/// Base wallets directory name.
@@ -74,6 +76,7 @@ impl WalletConfig {
},
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
use_dandelion: Some(true),
enable_tor_listener: Some(true),
};
Settings::write_to_file(&config, config_path);
config
+3 -1
View File
@@ -147,6 +147,8 @@ pub struct WalletTransaction {
pub data: TxLogEntry,
/// Calculated transaction amount between debited and credited amount.
pub amount: u64,
/// Flag to check if transaction is cancelling.
pub cancelling: bool,
/// Flag to check if transaction is posting after finalization.
pub posting: bool,
/// Flag to check if transaction can be finalized based on Slatepack message state.
@@ -158,7 +160,7 @@ pub struct WalletTransaction {
impl WalletTransaction {
/// Check if transaction can be cancelled.
pub fn can_cancel(&self) -> bool {
!self.posting && !self.data.confirmed &&
!self.cancelling && !self.posting && !self.data.confirmed &&
self.data.tx_type != TxLogEntryType::TxReceivedCancelled
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
}
+319 -123
View File
@@ -27,17 +27,21 @@ use grin_api::{ApiServer, Router};
use grin_chain::SyncStatus;
use grin_core::global;
use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_util::Mutex;
use grin_util::{Mutex, ToHex};
use grin_util::secp::SecretKey;
use grin_util::types::ZeroingString;
use grin_wallet_api::Owner;
use grin_wallet_controller::controller;
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::{Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, StatusMessage, TxLogEntry, TxLogEntryType, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlatepackAddress, SlateState, SlateVersion, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, retrieve_summary_info, retrieve_txs};
use crate::AppConfig;
use grin_wallet_util::OnionV3Address;
use serde_json::{json, Value};
use crate::AppConfig;
use crate::node::{Node, NodeConfig};
use crate::tor::Tor;
use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig};
use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction};
@@ -51,11 +55,14 @@ pub struct Wallet {
/// [`WalletInstance`] external connection id applied after opening.
instance_ext_conn_id: Arc<AtomicI64>,
/// Wallet Slatepack address to receive txs at transport.
slatepack_address: Arc<RwLock<Option<String>>>,
/// Wallet sync thread.
sync_thread: Arc<RwLock<Option<Thread>>>,
/// Foreign API server.
foreign_api_server: Arc<RwLock<Option<ApiServer>>>,
/// Running wallet foreign API server and port.
foreign_api_server: Arc<RwLock<Option<(ApiServer, u16)>>>,
/// Flag to check if wallet reopening is needed.
reopen: Arc<AtomicBool>,
@@ -91,7 +98,7 @@ pub struct Wallet {
/// Default Foreign API server host.
const DEFAULT_FOREIGN_API_HOST: &str = "127.0.0.1";
/// Default Foreign API server port.
const DEFAULT_FOREIGN_API_PORT: u16 = 3421;
const DEFAULT_FOREIGN_API_PORT: u16 = 3415;
impl Wallet {
/// Create new [`Wallet`] instance with provided [`WalletConfig`].
@@ -100,6 +107,7 @@ impl Wallet {
config: Arc::new(RwLock::new(config)),
instance: None,
instance_ext_conn_id: Arc::new(AtomicI64::new(0)),
slatepack_address: Arc::new(RwLock::new(None)),
sync_thread: Arc::from(RwLock::new(None)),
foreign_api_server: Arc::new(RwLock::new(None)),
reopen: Arc::new(AtomicBool::new(false)),
@@ -210,6 +218,35 @@ impl Wallet {
Ok(w_inst.parent_key_id())
}
/// Get wallet [`SecretKey`] for transports.
pub fn secret_key(&self) -> Result<SecretKey, Error> {
let instance = self.instance.clone().unwrap();
let mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&None).as_ref())?;
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)
}
/// 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())
}
/// Get Slatepack address to receive txs at transport.
pub fn slatepack_address(&self) -> Option<String> {
let r_address = self.slatepack_address.read().unwrap();
if r_address.is_some() {
let addr = r_address.clone().unwrap();
return Some(addr)
}
None
}
/// Get wallet config.
pub fn get_config(&self) -> WalletConfig {
self.config.read().unwrap().clone()
@@ -222,10 +259,23 @@ impl Wallet {
w_config.save();
}
/// Check if start of Tor listener on wallet opening is needed.
pub fn auto_start_tor_listener(&self) -> bool {
let r_config = self.config.read().unwrap();
r_config.enable_tor_listener.unwrap_or(true)
}
/// Update start of Tor listener on wallet opening.
pub fn update_auto_start_tor_listener(&self, start: bool) {
let mut w_config = self.config.write().unwrap();
w_config.enable_tor_listener = Some(start);
w_config.save();
}
/// Check if Dandelion usage is needed to post transactions.
pub fn can_use_dandelion(&self) -> bool {
let r_config = self.config.read().unwrap();
r_config.use_dandelion.unwrap_or(false)
r_config.use_dandelion.unwrap_or(true)
}
/// Update usage of Dandelion to post transactions.
@@ -267,36 +317,47 @@ impl Wallet {
}
// Open the wallet.
let instance = self.instance.clone().unwrap();
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?;
match lc.open_wallet(None, ZeroingString::from(password), false, false) {
Ok(_) => {
// Reset an error on opening.
self.set_sync_error(false);
self.reset_sync_attempts();
{
let instance = self.instance.clone().unwrap();
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?;
match lc.open_wallet(None, ZeroingString::from(password), false, false) {
Ok(_) => {
// Reset an error on opening.
self.set_sync_error(false);
self.reset_sync_attempts();
// Set current account.
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())?;
// Set current account.
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())?;
// Start new synchronization thread or wake up existing one.
let mut thread_w = self.sync_thread.write().unwrap();
if thread_w.is_none() {
let thread = start_sync(self.clone());
*thread_w = Some(thread);
} else {
println!("unfreeze thread");
thread_w.clone().unwrap().unpark();
// Start new synchronization thread or wake up existing one.
let mut thread_w = self.sync_thread.write().unwrap();
if thread_w.is_none() {
let thread = start_sync(self.clone());
*thread_w = Some(thread);
} else {
println!("unfreeze thread");
thread_w.clone().unwrap().unpark();
}
self.is_open.store(true, Ordering::Relaxed);
}
Err(e) => {
self.instance = None;
return Err(e)
}
self.is_open.store(true, Ordering::Relaxed);
}
Err(e) => {
self.instance = None;
return Err(e)
}
}
// Set slatepack address.
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
let mut w_address = self.slatepack_address.write().unwrap();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
})?;
Ok(())
}
@@ -335,17 +396,21 @@ impl Wallet {
// Close wallet at separate thread.
let wallet_close = self.clone();
let instance = wallet_close.instance.clone().unwrap();
let service_id = wallet_close.identifier();
thread::spawn(move || {
// Stop created API server.
// Stop running API server.
let api_server_exists = {
wallet_close.foreign_api_server.read().unwrap().is_some()
};
if api_server_exists {
let mut api_server_w = wallet_close.foreign_api_server.write().unwrap();
api_server_w.as_mut().unwrap().stop();
*api_server_w = None;
let mut w_api_server = wallet_close.foreign_api_server.write().unwrap();
w_api_server.as_mut().unwrap().0.stop();
*w_api_server = None;
}
// Stop running Tor service.
Tor::stop_service(&service_id);
// Close the wallet.
Self::close_wallet(&instance);
@@ -391,9 +456,16 @@ impl Wallet {
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
api.set_active_account(m, label)?;
// Set Slatepack address.
let mut w_address = self.slatepack_address.write().unwrap();
*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);
// Save account label into config.
let mut w_config = self.config.write().unwrap();
w_config.account = label.to_owned();
@@ -478,6 +550,16 @@ impl Wallet {
}
}
/// Get running Foreign API server port.
pub fn foreign_api_port(&self) -> Option<u16> {
let r_api = self.foreign_api_server.read().unwrap();
if r_api.is_some() {
let api = r_api.as_ref().unwrap();
return Some(api.1);
}
None
}
/// Parse Slatepack message into [`Slate`].
pub fn parse_slatepack(&self, message: &String) -> Result<Slate, Error> {
let api = Owner::new(self.instance.clone().unwrap(), None);
@@ -570,7 +652,7 @@ impl Wallet {
}
/// Initialize a transaction to send amount, return request for funds receiver.
pub fn send(&self, amount: u64) -> Result<String, Error> {
pub fn send(&self, amount: u64) -> Result<(Slate, String), Error> {
let config = self.get_config();
let args = InitTxArgs {
src_acct_name: Some(config.account),
@@ -587,16 +669,102 @@ impl Wallet {
api.tx_lock_outputs(None, &slate)?;
// Create Slatepack message response.
let response = self.create_slatepack_message(&slate)?;
let message_resp = self.create_slatepack_message(&slate)?;
// Sync wallet info.
self.sync();
Ok(response)
Ok((slate, message_resp))
}
/// Send amount to provided address with Tor transport.
pub async fn send_tor(&mut self, amount: u64, addr: &SlatepackAddress) -> Option<Slate> {
// Initialize transaction.
let send_res = self.send(amount);
if send_res.is_err() {
return None;
}
let slate = send_res.unwrap().0;
// Function to cancel initialized tx in case of error.
let cancel_tx = || {
let instance = self.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, None, Some(slate.clone().id));
};
// Initialize parameters.
let tor_addr = OnionV3Address::try_from(addr).unwrap().to_http_str();
let url = format!("{}/v2/foreign", tor_addr);
let slate_send = VersionedSlate::into_version(slate.clone(), SlateVersion::V4).unwrap();
let body = json!({
"jsonrpc": "2.0",
"method": "receive_tx",
"id": 1,
"params": [
slate_send,
null,
null
]
}).to_string();
// Send request to receiver.
let req_res = Tor::post(body, url).await;
if req_res.is_none() {
cancel_tx();
return None;
}
// Parse response and finalize transaction.
let res: Value = serde_json::from_str(&req_res.unwrap()).unwrap();
println!("Response: {}", res);
if res["error"] != json!(null) {
let report = format!(
"Posting transaction slate: Error: {}, Message: {}",
res["error"]["code"], res["error"]["message"]
);
println!("{}", report);
cancel_tx();
return None;
}
let slate_value = res["result"]["Ok"].clone();
println!("slate_value: {}", slate_value);
let mut ret_slate = None;
match Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) {
Ok(s) => {
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
return if let Ok(slate) = api.finalize_tx(m, &s) {
ret_slate = Some(slate.clone());
let result = api.post_tx(m, &slate, self.can_use_dandelion());
match result {
Ok(_) => {
println!("Tx sent successfully", );
Ok(())
}
Err(e) => {
eprintln!("Tx sent fail: {}", e);
Err(e)
}
}
} else {
Err(Error::GenericError("TX finalization error".to_string()))
};
}).unwrap();
}
Err(_) => {}
};
if ret_slate.is_none() {
cancel_tx();
}
ret_slate
}
/// Initialize an invoice transaction to receive amount, return request for funds sender.
pub fn issue_invoice(&self, amount: u64) -> Result<String, Error> {
pub fn issue_invoice(&self, amount: u64) -> Result<(Slate, String), Error> {
let args = IssueInvoiceTxArgs {
dest_acct_name: None,
amount,
@@ -606,12 +774,12 @@ impl Wallet {
let slate = api.issue_invoice_tx(None, args)?;
// Create Slatepack message response.
let response = self.create_slatepack_message(&slate)?;
let response = self.create_slatepack_message(&slate.clone())?;
// Sync wallet info.
self.sync();
Ok(response)
Ok((slate, response))
}
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
@@ -699,27 +867,46 @@ impl Wallet {
/// Cancel transaction.
pub fn cancel(&mut self, id: u32) {
let instance = self.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, Some(id), None);
// Setup cancelling status, posting flag, and ability to finalize.
let mut w_data = self.data.write().unwrap();
let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id {
tx.posting = false;
tx.can_finalize = false;
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
TxLogEntryType::TxReceivedCancelled
} else {
TxLogEntryType::TxSentCancelled
};
}
tx.clone()
}).collect::<Vec<WalletTransaction>>();
data.txs = txs;
*w_data = Some(data);
// Refresh wallet info to update statuses.
self.sync();
// Setup cancelling status.
{
let mut w_data = self.data.write().unwrap();
let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id {
tx.cancelling = true;
tx.can_finalize = false;
}
tx.clone()
}).collect::<Vec<WalletTransaction>>();
data.txs = txs;
*w_data = Some(data);
}
let wallet = self.clone();
thread::spawn(move || {
let instance = wallet.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, Some(id), None);
// Setup posting flag, and ability to finalize.
let mut w_data = wallet.data.write().unwrap();
let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id {
tx.cancelling = false;
tx.posting = false;
tx.can_finalize = false;
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
TxLogEntryType::TxReceivedCancelled
} else {
TxLogEntryType::TxSentCancelled
};
}
tx.clone()
}).collect::<Vec<WalletTransaction>>();
data.txs = txs;
*w_data = Some(data);
// Refresh wallet info to update statuses.
wallet.sync();
});
}
/// Change wallet password.
@@ -803,9 +990,8 @@ fn start_sync(mut wallet: Wallet) -> Thread {
wallet.txs_sync_progress.store(0, Ordering::Relaxed);
wallet.repair_progress.store(0, Ordering::Relaxed);
println!("create new thread");
thread::spawn(move || loop {
println!("start new cycle");
println!("SYNC {}, attempts: {}", wallet.get_config().name, wallet.get_sync_attempts());
// Close wallet on chain type change.
if wallet.get_config().chain_type != AppConfig::chain_type() {
@@ -814,7 +1000,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Stop syncing if wallet was closed.
if !wallet.is_open() {
println!("finishing thread at start");
// Clear thread instance.
let mut thread_w = wallet.sync_thread.write().unwrap();
*thread_w = None;
@@ -822,7 +1007,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Clear wallet info.
let mut w_data = wallet.data.write().unwrap();
*w_data = None;
println!("finish at start complete");
return;
}
@@ -838,26 +1022,36 @@ fn start_sync(mut wallet: Wallet) -> Thread {
wallet.set_sync_error(not_enabled);
// Skip cycle when node sync is not finished.
if !Node::is_running() || Node::get_sync_status() != Some(SyncStatus::NoSync) {
println!("integrated node wait");
thread::park_timeout(ATTEMPT_DELAY);
continue;
}
}
// Start Foreign API listener if API server was not created.
let api_server_exists = {
// Start Foreign API listener if API server is not running.
let mut api_server_running = {
wallet.foreign_api_server.read().unwrap().is_some()
};
if !api_server_exists {
if !api_server_running && wallet.is_open() {
match start_api_server(&mut wallet) {
Ok(api_server) => {
let mut api_server_w = wallet.foreign_api_server.write().unwrap();
*api_server_w = Some(api_server);
api_server_running = true;
}
Err(_) => {}
}
}
// Start Tor service if API server is running and wallet is open.
if wallet.auto_start_tor_listener() && wallet.is_open() && api_server_running &&
!Tor::is_service_running(&wallet.identifier()) {
let r_foreign_api = wallet.foreign_api_server.read().unwrap();
let api = r_foreign_api.as_ref().unwrap();
if let Ok(sec_key) = wallet.secret_key() {
Tor::start_service(api.1, sec_key, &wallet.identifier());
}
}
// Scan outputs if repair is needed or sync data if there is no error.
if !wallet.sync_error() {
if wallet.is_repairing() {
@@ -869,7 +1063,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Stop sync if wallet was closed.
if !wallet.is_open() {
println!("finishing thread after updating");
// Clear thread instance.
let mut thread_w = wallet.sync_thread.write().unwrap();
*thread_w = None;
@@ -877,73 +1070,37 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Clear wallet info.
let mut w_data = wallet.data.write().unwrap();
*w_data = None;
println!("finishing after updating complete");
return;
}
// Repeat after default or attempt delay if synchronization was not successful.
let delay = if wallet.sync_error()
|| wallet.get_sync_attempts() != 0 {
let failed_sync = wallet.sync_error() || wallet.get_sync_attempts() != 0;
let delay = if failed_sync {
ATTEMPT_DELAY
} else {
SYNC_DELAY
};
println!("park for {}", delay.as_millis());
if failed_sync {
println!("SYNC {} failed, attempts: {}, wait {}ms",
wallet.get_config().name,
wallet.get_sync_attempts(),
delay.as_millis());
} else {
println!("SYNC success for {}, wait {}ms",
wallet.get_config().name,
delay.as_millis());
}
thread::park_timeout(delay);
}).thread().clone()
}
/// Start Foreign API server to accept txs via Tor and receive mining rewards from Stratum server.
fn start_api_server(wallet: &mut Wallet) -> Result<ApiServer, Error> {
// Find free port.
let free_port = (DEFAULT_FOREIGN_API_PORT..).find(|port| {
return match TcpListener::bind((DEFAULT_FOREIGN_API_HOST, port.to_owned())) {
Ok(_) => {
let node_p2p_port = NodeConfig::get_p2p_port();
let node_api_port = NodeConfig::get_api_ip_port().1;
port.to_string() != node_p2p_port && port.to_string() != node_api_port
},
Err(_) => false
}
}).unwrap();
// Setup API server address.
let api_addr = format!("{}:{}", DEFAULT_FOREIGN_API_HOST, free_port);
// Start Foreign API server thread.
let instance = wallet.instance.clone().unwrap();
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)),
false,
Mutex::new(None));
let mut router = Router::new();
router
.add_route("/v2/foreign", Arc::new(api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
println!("Starting HTTP Foreign listener API server at {}.", api_addr);
let socket_addr: SocketAddr = api_addr.parse().unwrap();
let _ = apis.start(socket_addr, router, None, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
println!("HTTP Foreign listener started.");
Ok(apis)
}
/// Retrieve [`WalletData`] from node.
fn sync_wallet_data(wallet: &Wallet) {
println!("SYNC start, attempts: {}", wallet.get_sync_attempts());
let wallet_info = wallet.clone();
let (info_tx, info_rx) = mpsc::channel::<StatusMessage>();
// Update info sync progress at separate thread.
thread::spawn(move || {
while let Ok(m) = info_rx.recv() {
println!("SYNC INFO MESSAGE");
match m {
StatusMessage::UpdatingOutputs(_) => {}
StatusMessage::UpdatingTransactions(_) => {}
@@ -990,7 +1147,6 @@ fn sync_wallet_data(wallet: &Wallet) {
let (txs_tx, txs_rx) = mpsc::channel::<StatusMessage>();
thread::spawn(move || {
while let Ok(m) = txs_rx.recv() {
println!("SYNC TXS MESSAGE");
match m {
StatusMessage::UpdatingOutputs(_) => {}
StatusMessage::UpdatingTransactions(_) => {}
@@ -1116,6 +1272,7 @@ fn sync_wallet_data(wallet: &Wallet) {
new_txs.push(WalletTransaction {
data: tx.clone(),
amount,
cancelling: false,
posting,
can_finalize,
repost_height,
@@ -1148,8 +1305,6 @@ fn sync_wallet_data(wallet: &Wallet) {
wallet.increment_sync_attempts();
}
println!("SYNC cycle finished, attempts: {}", wallet.get_sync_attempts());
// Set an error if maximum number of attempts was reached.
if wallet.get_sync_attempts() >= SYNC_ATTEMPTS {
wallet.reset_sync_attempts();
@@ -1157,6 +1312,47 @@ fn sync_wallet_data(wallet: &Wallet) {
}
}
/// Start Foreign API server to receive mining rewards from Stratum server.
fn start_api_server(wallet: &mut Wallet) -> Result<(ApiServer, u16), Error> {
// Find free port.
let free_port = (DEFAULT_FOREIGN_API_PORT..).find(|port| {
return match TcpListener::bind((DEFAULT_FOREIGN_API_HOST, port.to_owned())) {
Ok(_) => {
let node_p2p_port = NodeConfig::get_p2p_port();
let node_api_port = NodeConfig::get_api_ip_port().1;
port.to_string() != node_p2p_port && port.to_string() != node_api_port
},
Err(_) => false
}
}).unwrap();
// Setup API server address.
let api_addr = format!("{}:{}", DEFAULT_FOREIGN_API_HOST, free_port);
// Start Foreign API server thread.
let instance = wallet.instance.clone().unwrap();
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)),
false,
Mutex::new(None));
let mut router = Router::new();
router
.add_route("/v2/foreign", Arc::new(api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
println!("Starting HTTP Foreign listener API server at {}.", api_addr);
let socket_addr: SocketAddr = api_addr.parse().unwrap();
let _ = apis.start(socket_addr, router, None, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
println!("HTTP Foreign listener started.");
Ok((apis, free_port))
}
/// Update wallet accounts data.
fn update_accounts(wallet: &Wallet, current_height: u64, current_spendable: Option<u64>) {
// Update only current account if list is not empty.