diff --git a/Cargo.lock b/Cargo.lock index 320a299..e4afdd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,7 +319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" dependencies = [ "android_log-sys", - "env_logger", + "env_logger 0.10.2", "log", "once_cell", ] @@ -504,7 +504,6 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", - "visibility", "winapi 0.3.9", ] @@ -556,6 +555,25 @@ dependencies = [ "void", ] +[[package]] +name = "arti-hyper" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7105f9e5214f3447e6b80f93c819b8399e6bc2d61e6b8b025ec205bebe8c3f" +dependencies = [ + "anyhow", + "arti-client", + "educe", + "hyper 0.14.28", + "pin-project", + "thiserror", + "tls-api", + "tls-api-native-tls", + "tokio 1.37.0", + "tor-error", + "tor-rtcompat", +] + [[package]] name = "as-raw-xcb-connection" version = "1.0.1" @@ -869,6 +887,17 @@ dependencies = [ "zbus", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "0.1.8" @@ -2619,6 +2648,19 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "env_logger" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -3435,6 +3477,7 @@ dependencies = [ "arboard", "arti", "arti-client", + "arti-hyper", "built", "chrono", "curve25519-dalek 4.1.2", @@ -3443,7 +3486,7 @@ dependencies = [ "eframe", "egui", "egui_extras", - "env_logger", + "env_logger 0.10.2", "fs-mistrust", "futures 0.3.30", "grin_api", @@ -3459,6 +3502,7 @@ dependencies = [ "grin_wallet_impls", "grin_wallet_libwallet", "grin_wallet_util", + "hyper 0.14.28", "image 0.25.1", "jni", "lazy_static", @@ -3473,6 +3517,8 @@ dependencies = [ "sha2 0.10.8", "sys-locale", "thiserror", + "tls-api", + "tls-api-native-tls", "tokio 1.37.0", "tokio-util 0.7.10", "toml 0.8.12", @@ -3504,13 +3550,13 @@ dependencies = [ "grin_store", "grin_util", "http 0.2.12", - "hyper", + "hyper 0.13.10", "hyper-rustls 0.20.0", "hyper-timeout", "lazy_static", "log", "regex", - "ring", + "ring 0.16.20", "rustls 0.17.0", "serde", "serde_derive", @@ -3686,7 +3732,7 @@ dependencies = [ "grin_store", "grin_util", "http 0.2.12", - "hyper", + "hyper 0.13.10", "hyper-rustls 0.20.0", "lmdb-zero", "log", @@ -3760,7 +3806,7 @@ dependencies = [ "grin_wallet_util", "log", "rand 0.6.5", - "ring", + "ring 0.16.20", "serde", "serde_derive", "serde_json", @@ -3801,13 +3847,13 @@ dependencies = [ "grin_wallet_impls", "grin_wallet_libwallet", "grin_wallet_util", - "hyper", + "hyper 0.13.10", "lazy_static", "log", "prettytable-rs", "qr_code", "rand 0.7.3", - "ring", + "ring 0.16.20", "serde", "serde_derive", "serde_json", @@ -3845,7 +3891,7 @@ dependencies = [ "rand 0.6.5", "regex", "reqwest", - "ring", + "ring 0.16.20", "serde", "serde_derive", "serde_json", @@ -3955,6 +4001,25 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes 1.6.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio 1.37.0", + "tokio-util 0.7.10", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -4026,6 +4091,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -4153,6 +4227,17 @@ dependencies = [ "http 0.2.12", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes 1.6.0", + "http 0.2.12", + "pin-project-lite 0.2.14", +] + [[package]] name = "httparse" version = "1.8.0" @@ -4206,9 +4291,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.2.7", "http 0.2.12", - "http-body", + "http-body 0.3.1", "httparse", "httpdate 0.3.2", "itoa 0.4.8", @@ -4220,6 +4305,30 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes 1.6.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate 1.0.3", + "itoa 1.0.11", + "pin-project-lite 0.2.14", + "socket2 0.5.6", + "tokio 1.37.0", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper-rustls" version = "0.20.0" @@ -4229,13 +4338,13 @@ dependencies = [ "bytes 0.5.6", "ct-logs", "futures-util", - "hyper", + "hyper 0.13.10", "log", "rustls 0.17.0", "rustls-native-certs", "tokio 0.2.25", "tokio-rustls 0.13.1", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -4246,12 +4355,12 @@ checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6" dependencies = [ "bytes 0.5.6", "futures-util", - "hyper", + "hyper 0.13.10", "log", "rustls 0.18.1", "tokio 0.2.25", "tokio-rustls 0.14.1", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -4261,7 +4370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d1f9b0b8258e3ef8f45928021d3ef14096c2b93b99e4b8cfcabf1f58ec84b0a" dependencies = [ "bytes 0.5.6", - "hyper", + "hyper 0.13.10", "tokio 0.2.25", "tokio-io-timeout", ] @@ -4273,7 +4382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" dependencies = [ "bytes 0.5.6", - "hyper", + "hyper 0.13.10", "native-tls", "tokio 0.2.25", "tokio-tls", @@ -4579,7 +4688,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -4605,7 +4714,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] @@ -5608,7 +5717,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -6001,6 +6110,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -6177,7 +6297,7 @@ checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if 1.0.0", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.3.9", "pin-project-lite 0.2.14", "rustix 0.38.34", "tracing", @@ -6818,8 +6938,8 @@ dependencies = [ "futures-core", "futures-util", "http 0.2.12", - "http-body", - "hyper", + "http-body 0.3.1", + "hyper 0.13.10", "hyper-rustls 0.21.0", "hyper-tls", "ipnet", @@ -6881,11 +7001,26 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi 0.3.9", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom 0.2.14", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -7082,9 +7217,9 @@ checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" dependencies = [ "base64 0.11.0", "log", - "ring", + "ring 0.16.20", "sct", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -7095,9 +7230,9 @@ checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" dependencies = [ "base64 0.12.3", "log", - "ring", + "ring 0.16.20", "sct", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -7210,8 +7345,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -8036,6 +8171,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "test-cert-gen" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345f92b7cac59507cdaba298c5493f7c40e2063d31f6fc621105183344d5d50a" +dependencies = [ + "once_cell", + "pem", + "tempfile", +] + [[package]] name = "thiserror" version = "1.0.59" @@ -8177,6 +8323,53 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d1b3dfb0a60da3e8a130c9f2432063d9979928a05c2b2cdcfc9fd05e4f53a3" +dependencies = [ + "anyhow", + "log", + "pem", + "tempfile", + "thiserror", + "tokio 1.37.0", + "void", + "webpki 0.22.4", +] + +[[package]] +name = "tls-api-native-tls" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b935bda2458120a5d2cea542013796fcf13937566580027f6a08f42a52206f7" +dependencies = [ + "anyhow", + "native-tls", + "thiserror", + "tls-api", + "tls-api-test", + "tokio 1.37.0", +] + +[[package]] +name = "tls-api-test" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df107843d725428d76bb159040fbae6d1524dcf25d5b24c56daa6b37ce9dbb5" +dependencies = [ + "anyhow", + "env_logger 0.5.13", + "log", + "pem", + "test-cert-gen", + "tls-api", + "tokio 1.37.0", + "untrusted 0.6.2", + "webpki 0.22.4", +] + [[package]] name = "tokio" version = "0.2.25" @@ -8261,7 +8454,7 @@ dependencies = [ "futures-core", "rustls 0.17.0", "tokio 0.2.25", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -8273,7 +8466,7 @@ dependencies = [ "futures-core", "rustls 0.18.1", "tokio 0.2.25", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -9592,12 +9785,24 @@ dependencies = [ "traitobject", ] +[[package]] +name = "untrusted" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -9949,8 +10154,18 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -9959,7 +10174,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" dependencies = [ - "webpki", + "webpki 0.21.4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5f13005..8a84cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,19 +55,23 @@ serde_json = "1.0.115" tokio = { version = "1.37.0", features = ["full"] } ## tor -arti = { version = "1.2.0", features = ["experimental-api", "pt-client", "static"] } -arti-client = { version = "0.17.0", features = ["experimental-api", "pt-client", "static", "onion-service-service"] } +arti = { version = "1.2.0", features = ["pt-client", "static"] } +arti-client = { version = "0.17.0", features = ["pt-client", "static", "onion-service-service"] } tor-rtcompat = { version = "0.17.0", features = ["static"] } tor-config = "0.17.0" fs-mistrust = "0.7.9" tor-hsservice = "0.17.0" tor-hsrproxy = "0.17.0" tor-keymgr = "0.17.0" -ed25519-dalek = "2.1.1" tor-llcrypto = "0.17.0" tor-hscrypto = "0.17.0" +arti-hyper = "0.17.0" sha2 = "0.10.0" +ed25519-dalek = "2.1.1" curve25519-dalek = "4.1.2" +hyper = { version = "0.14.28", features = ["full"] } +tls-api = "0.9.0" +tls-api-native-tls = "0.9.0" ## stratum server tokio-util = { version = "0.7.8", features = ["codec"] } @@ -86,6 +90,6 @@ image = "0.25.1" android_logger = "0.13.1" jni = "0.21.1" android-activity = { version = "0.6.0", features = ["game-activity"] } -wgpu = "0.19" -winit = { version = "0.29", features = ["android-game-activity"] } +wgpu = "0.19.1" +winit = { version = "0.29.4", features = ["android-game-activity"] } eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] } \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml index 64aa8f8..5192cf6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -15,6 +15,9 @@ id: Identifier kernel: Kernel settings: Settings language: Language +scan: Scan +qr_code: QR code +repeat: Repeat wallets: await_conf_amount: Awaiting confirmation await_fin_amount: Awaiting finalization @@ -109,6 +112,19 @@ wallets: receive: Receive settings: Wallet settings change_server_confirmation: To apply change of connection settings, you need to re-open your wallet. Reopen it now? +transport: + desc: 'Use transport to receive or send messages synchronously:' + tor_network: Tor network + connected: Connected + connecting: Connecting + disconnecting: Disconnecting + conn_error: Connection error + disconnected: Disconnected + receiver_address: 'Address of the receiver:' + incorrect_addr_err: 'Entered address is incorrect:' + tor_send_error: An error occurred during sending over Tor, make sure receiver is online, transaction was canceled. + tor_autorun_desc: Whether to launch Tor service on wallet opening to receive transactions synchronously. + tor_sending: Sending over Tor network: self: Network type: 'Network type:' @@ -126,12 +142,6 @@ network: available: Available not_available: Not available availability_check: Availability check - tor_network: Tor network - server_enabled: Server enabled - server_starting: Server is starting - server_stopping: Server is stopping - server_error: Server error - server_disabled: Server disabled sync_status: node_restarting: Node is restarting node_down: Node is down diff --git a/locales/ru.yml b/locales/ru.yml index 7e66a9f..b8adce0 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -15,6 +15,9 @@ id: Идентификатор kernel: Ядро settings: Настройки language: Язык +scan: Сканировать +qr_code: QR-код +repeat: Повторить wallets: await_conf_amount: Ожидает подтверждения await_fin_amount: Ожидает завершения @@ -109,6 +112,19 @@ wallets: receive: Получить settings: Настройки кошелька change_server_confirmation: Для применения изменения настроек соединения необходимо переоткрыть кошелёк. Переоткрыть его сейчас? +transport: + desc: 'Используйте транспорт для синхронного получения или отправки сообщений:' + tor_network: Сеть Tor + connected: Подключено + connecting: Подключение + disconnecting: Отключение + conn_error: Ошибка подключения + disconnected: Отключено + receiver_address: 'Адрес получателя:' + incorrect_addr_err: 'Введённый адрес неверен:' + tor_send_error: Во время отправки через Tor произошла ошибка, убедитесь, что получатель находится онлайн, транзакция была отменена. + tor_autorun_desc: Запускать ли Tor сервис при открытии кошелька для синхронного получения транзакций. + tor_sending: Отправка через Tor network: self: Сеть type: 'Тип сети:' @@ -126,12 +142,6 @@ network: available: Доступно not_available: Недоступно availability_check: Проверка доступности - tor_network: Сеть Tor - server_enabled: Сервер включен - server_starting: Сервер запускается - server_stopping: Сервер останавливается - server_error: Ошибка сервера - server_disabled: Сервер отключен sync_status: node_restarting: Узел перезапускается node_down: Узел выключен diff --git a/src/gui/views/network/connections.rs b/src/gui/views/network/connections.rs index b208879..9e79ed2 100644 --- a/src/gui/views/network/connections.rs +++ b/src/gui/views/network/connections.rs @@ -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. diff --git a/src/gui/views/types.rs b/src/gui/views/types.rs index 202aab4..44eef18 100644 --- a/src/gui/views/types.rs +++ b/src/gui/views/types.rs @@ -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 + } } \ No newline at end of file diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index f6c6193..911c765 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -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. diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index a875d1e..e63819c 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -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. }); diff --git a/src/gui/views/wallets/wallet/messages.rs b/src/gui/views/wallets/wallet/messages.rs index f67bf27..dd55051 100644 --- a/src/gui/views/wallets/wallet/messages.rs +++ b/src/gui/views/wallets/wallet/messages.rs @@ -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, } -/// 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(); } diff --git a/src/gui/views/wallets/wallet/transport.rs b/src/gui/views/wallets/wallet/transport.rs index 53f2adf..d105bcb 100644 --- a/src/gui/views/wallets/wallet/transport.rs +++ b/src/gui/views/wallets/wallet/transport.rs @@ -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>, + /// Flag to check if error occurred during sending of transaction over Tor at [`Modal`]. + tor_send_error: Arc>, + /// Flag to check if transaction sent successfully over Tor [`Modal`]. + tor_success: Arc>, + /// 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::>(); + 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(); + } + } } } \ No newline at end of file diff --git a/src/settings/settings.rs b/src/settings/settings.rs index 67f2258..d63466e 100644 --- a/src/settings/settings.rs +++ b/src/settings/settings.rs @@ -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>, /// Tor server configuration. - tor_config: Arc> + tor_config: Arc> } impl Settings { @@ -55,8 +55,8 @@ impl Settings { let app_config = Self::init_config::(app_config_path); // Initialize tor config. - let tor_config_path = Settings::get_config_path(TorServerConfig::FILE_NAME, None); - let tor_config = Self::init_config::(tor_config_path); + let tor_config_path = Settings::get_config_path(TorConfig::FILE_NAME, None); + let tor_config = Self::init_config::(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() } diff --git a/src/tor/config.rs b/src/tor/config.rs index 2e16538..97dad16 100644 --- a/src/tor/config.rs +++ b/src/tor/config.rs @@ -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 } -/// 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() } } \ No newline at end of file diff --git a/src/tor/mod.rs b/src/tor/mod.rs index 6d3a498..5380d98 100644 --- a/src/tor/mod.rs +++ b/src/tor/mod.rs @@ -13,7 +13,7 @@ // limitations under the License. mod config; -pub use config::TorServerConfig; +pub use config::TorConfig; mod tor; -pub use tor::TorServer; \ No newline at end of file +pub use tor::Tor; \ No newline at end of file diff --git a/src/tor/tor.rs b/src/tor/tor.rs index 6e8771f..571c893 100644 --- a/src/tor/tor.rs +++ b/src/tor/tor.rs @@ -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 = Arc::new(TorServer::default()); + static ref TOR_SERVER_STATE: Arc = 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>>>, - /// Running Tor client configuration. - config: Arc>>, - - /// 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, TorClientConfig)>>, /// Mapping of running Onion services identifiers to proxy. - running_services: Arc>>> + running_services: Arc, Arc)>>>, + /// Starting Onion services identifiers. + starting_services: Arc>>, + /// Failed Onion services identifiers. + failed_services: Arc>> } -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 { + // 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(runtime: R, tor_client: TorClient) -> Result<()> { - let proxy_handle: JoinHandle> = 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( + addr: SocketAddr, + client: TorClient, + service: Arc, + request: S, + nickname: HsNickname + ) + where + R: Runtime, + S: futures::Stream + 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); - } } \ No newline at end of file diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 7d5686e..c303885 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -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 + pub use_dandelion: Option, + /// Flag to enable Tor listener on start. + pub enable_tor_listener: Option } /// 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 diff --git a/src/wallet/types.rs b/src/wallet/types.rs index e7b1b66..7ad3d8c 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -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 } diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index f00b768..f3b6ccd 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -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, + /// Wallet Slatepack address to receive txs at transport. + slatepack_address: Arc>>, + /// Wallet sync thread. sync_thread: Arc>>, - /// Foreign API server. - foreign_api_server: Arc>>, + /// Running wallet foreign API server and port. + foreign_api_server: Arc>>, /// Flag to check if wallet reopening is needed. reopen: Arc, @@ -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 { + 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 { + 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 { + 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 { 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 { + 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 { + // 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 { + 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::>(); - 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::>(); + 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::>(); + 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 { - // 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::(); // 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::(); 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) { // Update only current account if list is not empty.