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

This commit is contained in:
ardocrat 2024-04-30 18:15:03 +03:00
parent 1d9c8533ad
commit 12650c94fd
17 changed files with 1408 additions and 607 deletions

287
Cargo.lock generated
View file

@ -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]]

View file

@ -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"] }

View file

@ -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

View file

@ -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: Узел выключен

View file

@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use egui::{Align, Id, Layout, RichText, Rounding};
use url::Url;
use crate::tor::{TorServer, TorServerConfig};
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GEAR_SIX, PENCIL, POWER, TRASH, WARNING_CIRCLE, X_CIRCLE};
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, PENCIL, POWER, TRASH, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, NodeSetup, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions};
@ -101,15 +99,6 @@ impl ConnectionsContent {
// Show integrated node info content.
Self::integrated_node_item_ui(ui);
// Show transport connections.
ui.add_space(6.0);
let transport_text = format!("{}:", t!("wallets.transport"));
ui.label(RichText::new(transport_text).size(16.0).color(Colors::GRAY));
ui.add_space(6.0);
// Show Tor SOCKS server.
Self::tor_transport_item_ui(ui);
// Show external connections.
let ext_conn_list = ConnectionsConfig::ext_conn_list();
if !ext_conn_list.is_empty() {
@ -123,73 +112,6 @@ impl ConnectionsContent {
});
}
}
// Redraw after delay if Tor server is running.
if TorServer::is_running() || TorServer::is_starting() ||
TorServer::is_stopping() {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
}
}
/// Draw Tor connection item content.
fn tor_transport_item_ui(ui: &mut egui::Ui) {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
let rounding = View::item_rounding(0, 1, false);
ui.painter().rect(rect, rounding, Colors::FILL, View::ITEM_STROKE);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to show Tor connection settings.
View::item_button(ui, View::item_rounding(0, 1, true), GEAR_SIX, None, || {
AppConfig::toggle_show_connections_network_panel();
});
// Draw buttons to stop or start Tor server.
if !TorServer::is_stopping() && !TorServer::is_starting() {
if TorServer::is_running() {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::RED), || {
TorServer::stop();
});
} else {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::GREEN), || {
TorServer::start();
});
}
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("network.tor_network"))
.size(18.0)
.color(Colors::TITLE));
// Setup SOCKS server address.
let socks_port = TorServerConfig::socks_port();
let addr_text = format!("{} http://127.0.0.1:{}", COMPUTER_TOWER, socks_port);
ui.label(RichText::new(addr_text).size(15.0).color(Colors::TEXT));
ui.add_space(1.0);
// Setup server status text.
let (status_icon, status_text) = if TorServer::has_error() {
(WARNING_CIRCLE, t!("network.server_error"))
} else if TorServer::is_starting() {
(DOTS_THREE_CIRCLE, t!("network.server_starting"))
} else if TorServer::is_stopping() {
(DOTS_THREE_CIRCLE, t!("network.server_stopping"))
} else if TorServer::is_running() {
(CHECK_CIRCLE, t!("network.server_enabled"))
} else {
(X_CIRCLE, t!("network.server_disabled"))
};
let status_text = format!("{} {}", status_icon, status_text);
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
})
});
});
}
/// Draw integrated node connection item content.

View file

@ -83,7 +83,9 @@ pub struct TextEditOptions {
/// Flag to show copy button.
pub copy: bool,
/// Flag to show paste button.
pub paste: bool
pub paste: bool,
/// Flag to show button to scan QR code into text.
pub scan_qr: bool
}
impl TextEditOptions {
@ -95,6 +97,7 @@ impl TextEditOptions {
password: false,
copy: false,
paste: false,
scan_qr: false,
}
}
@ -127,4 +130,10 @@ impl TextEditOptions {
self.paste = true;
self
}
/// Show button to scan QR code to text.
pub fn scan_qr(mut self) -> Self {
self.scan_qr = true;
self
}
}

View file

@ -25,7 +25,7 @@ use egui::text::{LayoutJob, TextFormat};
use egui::text_edit::TextEditState;
use crate::gui::Colors;
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SQUARE};
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::TextEditOptions;
@ -346,6 +346,15 @@ impl View {
ui.add_space(8.0);
}
// Setup scan QR code button.
if options.paste {
let scan_icon = SCAN.to_string();
View::button(ui, scan_icon, Colors::WHITE, || {
//TODO: open scanner
});
ui.add_space(8.0);
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
// Setup text edit size.

View file

@ -19,7 +19,7 @@ use grin_core::core::amount_to_hr_string;
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, QR_CODE, REPEAT, USERS_THREE};
use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, REPEAT, SCAN, USERS_THREE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions};
@ -176,7 +176,7 @@ impl WalletContent {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to scan QR code.
View::item_button(ui, View::item_rounding(0, 2, true), QR_CODE, None, || {
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
//TODO: Scan with QR code.
});

View file

@ -53,9 +53,6 @@ impl MessageError {
/// Slatepacks messages interaction tab content.
pub struct WalletMessages {
/// Flag to check if send or invoice request was opened.
send_request: bool,
/// Slatepack message to create response message.
message_edit: String,
/// Parsed Slatepack message.
@ -67,15 +64,17 @@ pub struct WalletMessages {
/// Flag to check if Dandelion is needed to finalize transaction.
dandelion: bool,
/// Amount to send or receive.
/// Flag to check if send or invoice request was opened for [`Modal`].
send_request: bool,
/// Amount to send or receive at [`Modal`].
amount_edit: String,
/// Generated Slatepack message as request to send or receive funds.
/// Generated Slatepack message as request to send or receive funds at [`Modal`].
request_edit: String,
/// Flag to check if there is an error happened on invoice creation.
/// Flag to check if there is an error happened on request creation at [`Modal`].
request_error: Option<MessageError>,
}
/// Identifier for invoice amount [`Modal`].
/// Identifier for amount input [`Modal`].
const AMOUNT_MODAL: &'static str = "amount_modal";
impl WalletMessages {
@ -127,7 +126,7 @@ impl WalletTab for WalletMessages {
.show_inside(ui, |ui| {
ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.id_source(Id::from("wallet_manual").with(wallet.get_config().id))
.id_source(Id::from("wallet_messages").with(wallet.get_config().id))
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.vertical_centered(|ui| {
@ -181,6 +180,54 @@ impl WalletMessages {
}
}
/// Draw creation of request to send or receive funds.
fn request_ui(&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks) {
ui.label(RichText::new(t!("wallets.create_request_desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw send request creation button.
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
View::button(ui, send_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = true;
self.amount_edit = "".to_string();
self.request_error = None;
// Show send amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
});
});
columns[1].vertical_centered_justified(|ui| {
// Draw invoice request creation button.
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
View::button(ui, receive_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = false;
self.amount_edit = "".to_string();
self.request_error = None;
// Show receive amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
cb.show_keyboard();
});
});
});
}
/// Draw Slatepack message input content.
fn input_slatepack_ui(&mut self,
ui: &mut egui::Ui,
@ -509,55 +556,7 @@ impl WalletMessages {
}
}
/// Draw creation of request to send or receive funds.
fn request_ui(&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks) {
ui.label(RichText::new(t!("wallets.create_request_desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw send request creation button.
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
View::button(ui, send_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = true;
self.amount_edit = "".to_string();
self.request_error = None;
// Show send amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
});
});
columns[1].vertical_centered_justified(|ui| {
// Draw invoice request creation button.
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
View::button(ui, receive_text, Colors::BUTTON, || {
// Setup modal values.
self.send_request = false;
self.amount_edit = "".to_string();
self.request_error = None;
// Show receive amount modal.
Modal::new(AMOUNT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
cb.show_keyboard();
});
});
});
}
/// Draw invoice amount [`Modal`] content.
/// Draw amount input [`Modal`] content to create invoice or request to send funds.
fn amount_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
@ -621,7 +620,7 @@ impl WalletMessages {
}
}
// Show invoice creation error.
// Show request creation error.
if self.request_error.is_some() {
ui.add_space(12.0);
ui.vertical_centered(|ui| {
@ -658,7 +657,7 @@ impl WalletMessages {
wallet.issue_invoice(a)
};
match message {
Ok(message) => {
Ok((_, message)) => {
self.request_edit = message;
cb.hide_keyboard();
}

View file

@ -12,17 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::Margin;
use std::sync::{Arc, RwLock};
use std::thread;
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
use egui::scroll_area::ScrollBarVisibility;
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::SlatepackAddress;
use crate::gui::Colors;
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, UPLOAD, WARNING_CIRCLE, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent;
use crate::tor::Tor;
use crate::wallet::Wallet;
/// Sending tab content.
#[derive(Default)]
pub struct WalletTransport;
/// Wallet transport tab content.
pub struct WalletTransport {
/// Flag to check if transaction is sending over Tor to show progress at [`Modal`].
tor_sending: Arc<RwLock<bool>>,
/// Flag to check if error occurred during sending of transaction over Tor at [`Modal`].
tor_send_error: Arc<RwLock<bool>>,
/// Flag to check if transaction sent successfully over Tor [`Modal`].
tor_success: Arc<RwLock<bool>>,
/// Entered amount value for [`Modal`].
amount_edit: String,
/// Entered address value for [`Modal`].
address_edit: String,
/// Flag to check if entered address is incorrect at [`Modal`].
address_error: bool,
/// Flag to check if [`Modal`] was just opened to focus on first field.
modal_just_opened: bool,
}
impl Default for WalletTransport {
fn default() -> Self {
Self {
tor_sending: Arc::new(RwLock::new(false)),
tor_send_error: Arc::new(RwLock::new(false)),
tor_success: Arc::new(RwLock::new(false)),
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
modal_just_opened: false,
}
}
}
impl WalletTab for WalletTransport {
fn get_type(&self) -> WalletTabType {
@ -33,11 +70,14 @@ impl WalletTab for WalletTransport {
ui: &mut egui::Ui,
_: &mut eframe::Frame,
wallet: &mut Wallet,
_: &dyn PlatformCallbacks) {
cb: &dyn PlatformCallbacks) {
if WalletContent::sync_ui(ui, wallet) {
return;
}
// Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb);
// Show transport content panel.
egui::CentralPanel::default()
.frame(egui::Frame {
@ -52,14 +92,478 @@ impl WalletTab for WalletTransport {
..Default::default()
})
.show_inside(ui, |ui| {
self.transport_ui(ui, wallet);
ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.vertical_centered(|ui| {
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
self.ui(ui, wallet, cb);
});
});
});
});
}
}
impl WalletTransport {
/// Draw transport content.
pub fn transport_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet) {
/// Identifier for [`Modal`] to send amount over Tor.
const SEND_TOR_MODAL: &'static str = "send_tor_modal";
impl WalletTransport {
/// Draw wallet transport content.
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
ui.add_space(3.0);
ui.label(RichText::new(t!("transport.desc"))
.size(16.0)
.color(Colors::INACTIVE_TEXT));
ui.add_space(7.0);
// Draw Tor content.
self.tor_ui(ui, wallet, cb);
}
/// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
match Modal::opened() {
None => {}
Some(id) => {
match id {
SEND_TOR_MODAL => {
Modal::ui(ui.ctx(), |ui, modal| {
self.send_tor_modal_ui(ui, wallet, modal, cb);
});
}
_ => {}
}
}
}
}
/// Draw Tor transport content.
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Draw header content.
self.tor_header_ui(ui, wallet, cb);
// Draw receive info content.
if wallet.slatepack_address().is_some() {
self.tor_receive_ui(ui, wallet, cb);
}
// Draw send content.
self.tor_send_ui(ui, wallet, cb);
}
/// Draw Tor transport header content.
fn tor_header_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(0, 2, false);
ui.painter().rect(bg_rect, item_rounding, Colors::BUTTON, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to setup Tor transport.
let button_rounding = View::item_rounding(0, 2, true);
View::item_button(ui, button_rounding, GEAR_SIX, None, || {
//TODO: tor settings
});
// Draw button to enable/disable Tor listener for current wallet.
let service_id = &wallet.identifier();
if !Tor::is_service_running(service_id) &&
wallet.foreign_api_port().is_some() {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::GREEN), || {
if let Ok(key) = wallet.secret_key() {
Tor::start_service(wallet.foreign_api_port().unwrap(), key, service_id);
}
});
} else if !Tor::is_service_starting(service_id) {
View::item_button(ui, Rounding::default(), POWER, Some(Colors::RED), || {
Tor::stop_service(service_id);
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("transport.tor_network"))
.size(18.0)
.color(Colors::TITLE));
// Setup wallet API address text.
let port = wallet.foreign_api_port().unwrap();
let address_text = format!("{} http://127.0.0.1:{}", GLOBE_SIMPLE, port);
ui.label(RichText::new(address_text).size(15.0).color(Colors::TEXT));
ui.add_space(1.0);
// Setup Tor status text.
let is_running = Tor::is_service_running(service_id);
let is_starting = Tor::is_service_starting(service_id);
let has_error = Tor::is_service_failed(service_id);
let (icon, text) = if is_starting {
(DOTS_THREE_CIRCLE, t!("transport.connecting"))
} else if has_error {
(WARNING_CIRCLE, t!("transport.conn_error"))
} else if is_running {
(CHECK_CIRCLE, t!("transport.connected"))
} else {
(X_CIRCLE, t!("transport.disconnected"))
};
let status_text = format!("{} {}", icon, text);
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
});
});
});
});
}
/// Draw Tor send content.
fn tor_receive_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
let slatepack_addr = wallet.slatepack_address().unwrap();
let service_id = &wallet.identifier();
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(52.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(1, 3, false);
ui.painter().rect(bg_rect, item_rounding, Colors::BUTTON, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to setup Tor transport.
let button_rounding = View::item_rounding(1, 3, true);
View::item_button(ui, button_rounding, QR_CODE, None, || {
//TODO: qr for address
});
// Show button to enable/disable Tor listener for current wallet.
View::item_button(ui, Rounding::default(), COPY, None, || {
cb.copy_string_to_buffer(slatepack_addr.clone());
});
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
// Show wallet Slatepack address.
let address_color = if Tor::is_service_starting(service_id) {
Colors::INACTIVE_TEXT
} else if Tor::is_service_running(service_id) {
Colors::GREEN
} else {
Colors::RED
};
View::ellipsize_text(ui, slatepack_addr, 15.0, address_color);
let address_label = format!("{} {}",
COMPUTER_TOWER,
t!("network_mining.address"));
ui.label(RichText::new(address_label).size(15.0).color(Colors::GRAY));
});
});
});
});
}
/// Draw Tor receive content.
fn tor_send_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(55.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(1, 2, false);
ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::top_down(Align::Center), |ui| {
ui.add_space(7.0);
// Draw button to open sending modal.
let send_text = format!("{} {}", EXPORT, t!("wallets.send"));
View::button(ui, send_text, Colors::WHITE, || {
self.show_send_tor_modal(cb);
});
});
});
}
/// Show [`Modal`] to send over Tor.
fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks) {
// Setup modal values.
let mut w_send_err = self.tor_send_error.write().unwrap();
*w_send_err = false;
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = false;
let mut w_success = self.tor_success.write().unwrap();
*w_success = false;
self.modal_just_opened = true;
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
self.address_error = false;
// Show modal.
Modal::new(SEND_TOR_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
cb.show_keyboard();
}
/// Check if error occurred during sending over Tor at [`Modal`].
fn has_tor_send_error(&self) -> bool {
let r_send_err = self.tor_send_error.read().unwrap();
r_send_err.clone()
}
/// Check if transaction is sending over Tor to show progress at [`Modal`].
fn tor_sending(&self) -> bool {
let r_sending = self.tor_sending.read().unwrap();
r_sending.clone()
}
/// Check if transaction sent over Tor with success at [`Modal`].
fn tor_success(&self) -> bool {
let r_success = self.tor_success.read().unwrap();
r_success.clone()
}
/// Draw amount input [`Modal`] content to send over Tor.
/// Draw amount input [`Modal`] content to send over Tor.
fn send_tor_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
let has_send_err = self.has_tor_send_error();
let sending = self.tor_sending();
if !has_send_err && !sending {
ui.vertical_centered(|ui| {
let data = wallet.get_data().unwrap();
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
let enter_text = t!("wallets.enter_amount_send","amount" => amount);
ui.label(RichText::new(enter_text)
.size(17.0)
.color(Colors::GRAY));
});
ui.add_space(8.0);
// Draw amount text edit.
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center().no_focus();
let amount_edit_before = self.amount_edit.clone();
if self.modal_just_opened {
self.modal_just_opened = false;
amount_edit_opts.focus = true;
}
View::text_edit(ui, cb, &mut self.amount_edit, amount_edit_opts);
ui.add_space(8.0);
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
if !self.amount_edit.is_empty() {
match amount_from_hr_string(self.amount_edit.as_str()) {
Ok(a) => {
if !self.amount_edit.contains(".") {
// To avoid input of several "0".
if a == 0 {
self.amount_edit = "0".to_string();
return;
}
} else {
// Check input after ".".
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
if parts.len() == 2 && parts[1].len() > 9 {
self.amount_edit = amount_edit_before;
return;
}
}
// Do not input amount more than balance in sending.
let b = wallet.get_data().unwrap().info.amount_currently_spendable;
if b < a {
self.amount_edit = amount_edit_before;
}
}
Err(_) => {
self.amount_edit = amount_edit_before;
}
}
}
}
// Show address error or input description.
ui.vertical_centered(|ui| {
if self.address_error {
ui.label(RichText::new(t!("transport.incorrect_addr_err"))
.size(17.0)
.color(Colors::RED));
} else {
ui.label(RichText::new(t!("transport.receiver_address"))
.size(17.0)
.color(Colors::GRAY));
}
});
ui.add_space(8.0);
// Draw address text edit.
let addr_edit_before = self.address_edit.clone();
let address_edit_id = Id::from(modal.id).with("address").with(wallet.get_config().id);
let address_edit_opts = TextEditOptions::new(address_edit_id)
.paste()
.scan_qr()
.no_focus();
View::text_edit(ui, cb, &mut self.address_edit, address_edit_opts);
ui.add_space(12.0);
// Check value if input was changed.
if addr_edit_before != self.address_edit {
self.address_error = false;
}
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
cb.hide_keyboard();
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("continue"), Colors::WHITE, || {
if self.amount_edit.is_empty() {
return;
}
// Check entered address.
let addr_str = self.address_edit.as_str();
if let Ok(addr) = SlatepackAddress::try_from(addr_str) {
// Parse amount and send over Tor.
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
cb.hide_keyboard();
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = true;
{
let send_error = self.tor_send_error.clone();
let send_success = self.tor_success.clone();
let mut wallet = wallet.clone();
thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
if wallet.send_tor(a, &addr).await.is_some() {
let mut w_send_success
= send_success.write().unwrap();
*w_send_success = true;
} else {
let mut w_send_error
= send_error.write().unwrap();
*w_send_error = true;
}
});
});
}
}
} else {
self.address_error = true;
}
});
});
});
ui.add_space(6.0);
} else if has_send_err {
ui.add_space(6.0);
ui.label(RichText::new(t!("transport.tor_send_error"))
.size(17.0)
.color(Colors::RED));
ui.add_space(12.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
cb.hide_keyboard();
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("repeat"), Colors::WHITE, || {
// Parse amount and send over Tor.
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
let mut w_send_error = self.tor_send_error.write().unwrap();
*w_send_error = false;
let mut w_sending = self.tor_sending.write().unwrap();
*w_sending = true;
{
let addr_text = self.address_edit.clone();
let send_error = self.tor_send_error.clone();
let send_success = self.tor_success.clone();
let mut wallet = wallet.clone();
thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let addr_str = addr_text.as_str();
let addr = &SlatepackAddress::try_from(addr_str)
.unwrap();
if wallet.send_tor(a, &addr).await.is_some() {
let mut w_send_success
= send_success.write().unwrap();
*w_send_success = true;
} else {
let mut w_send_error
= send_error.write().unwrap();
*w_send_error = true;
}
});
});
}
}
});
});
});
} else {
ui.add_space(16.0);
ui.vertical_centered(|ui| {
View::small_loading_spinner(ui);
ui.add_space(12.0);
ui.label(RichText::new(t!("transport.tor_sending"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
// Close modal on success sending.
if self.tor_success() {
modal.close();
}
}
}
}

View file

@ -24,7 +24,7 @@ use serde::Serialize;
use crate::node::NodeConfig;
use crate::settings::AppConfig;
use crate::tor::TorServerConfig;
use crate::tor::TorConfig;
use crate::wallet::ConnectionsConfig;
lazy_static! {
@ -44,7 +44,7 @@ pub struct Settings {
/// Wallet connections configuration.
conn_config: Arc<RwLock<ConnectionsConfig>>,
/// Tor server configuration.
tor_config: Arc<RwLock<TorServerConfig>>
tor_config: Arc<RwLock<TorConfig>>
}
impl Settings {
@ -55,8 +55,8 @@ impl Settings {
let app_config = Self::init_config::<AppConfig>(app_config_path);
// Initialize tor config.
let tor_config_path = Settings::get_config_path(TorServerConfig::FILE_NAME, None);
let tor_config = Self::init_config::<TorServerConfig>(tor_config_path);
let tor_config_path = Settings::get_config_path(TorConfig::FILE_NAME, None);
let tor_config = Self::init_config::<TorConfig>(tor_config_path);
let chain_type = &app_config.chain_type;
Self {
@ -110,12 +110,12 @@ impl Settings {
}
/// Get tor server configuration to read values.
pub fn tor_config_to_read() -> RwLockReadGuard<'static, TorServerConfig> {
pub fn tor_config_to_read() -> RwLockReadGuard<'static, TorConfig> {
SETTINGS_STATE.tor_config.read().unwrap()
}
/// Get tor server configuration to update values.
pub fn tor_config_to_update() -> RwLockWriteGuard<'static, TorServerConfig> {
pub fn tor_config_to_update() -> RwLockWriteGuard<'static, TorConfig> {
SETTINGS_STATE.tor_config.write().unwrap()
}

View file

@ -12,31 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::path::PathBuf;
use serde_derive::{Deserialize, Serialize};
use crate::Settings;
/// Tor SOCKS proxy server configuration.
/// Tor configuration.
#[derive(Serialize, Deserialize, Clone)]
pub struct TorServerConfig {
socks_port: u16
pub struct TorConfig {
// Flag to check if Tor bridges usage is needed.
pub(crate) use_bridges: Option<bool>
}
/// Default SOCKS port value.
const DEFAULT_SOCKS_PORT: u16 = 9060;
impl Default for TorServerConfig {
impl Default for TorConfig {
fn default() -> Self {
Self {
socks_port: DEFAULT_SOCKS_PORT,
use_bridges: Some(false)
}
}
}
impl TorServerConfig {
impl TorConfig {
/// Tor configuration file name.
pub const FILE_NAME: &'static str = "tor.toml";
/// Directory for config and Tor related files.
/// Directory for Tor data files.
const DIR_NAME: &'static str = "tor";
/// Subdirectory name for Tor state.
@ -48,11 +47,10 @@ impl TorServerConfig {
/// Save application configuration to the file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME,
Some(Self::DIR_NAME.to_string())));
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None));
}
/// Get subdirectory path from dir name.
/// Get path from subdirectory name.
fn sub_dir_path(name: &str) -> String {
let mut base = Settings::get_base_path(Some(Self::DIR_NAME.to_string()));
base.push(name);
@ -71,19 +69,8 @@ impl TorServerConfig {
/// Get Tor keystore directory path.
pub fn keystore_path() -> String {
Self::sub_dir_path(Self::KEYSTORE_DIR)
}
/// Get SOCKS port value.
pub fn socks_port() -> u16 {
let r_config = Settings::tor_config_to_read();
r_config.socks_port
}
/// Save SOCKS port value.
pub fn save_socks_port(port: u16) {
let mut w_config = Settings::tor_config_to_update();
w_config.socks_port = port;
w_config.save();
let mut base = PathBuf::from(Self::state_path());
base.push(Self::KEYSTORE_DIR);
base.to_str().unwrap().to_string()
}
}

View file

@ -13,7 +13,7 @@
// limitations under the License.
mod config;
pub use config::TorServerConfig;
pub use config::TorConfig;
mod tor;
pub use tor::TorServer;
pub use tor::Tor;

View file

@ -12,203 +12,129 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use std::net::SocketAddr;
use std::collections::{BTreeMap, BTreeSet};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use futures::executor::block_on;
use lazy_static::lazy_static;
use futures::task::SpawnExt;
use tokio::task::JoinHandle;
use anyhow::Result;
use tokio::time::sleep;
use arti::socks::run_socks_proxy;
use arti_client::{TorClient, TorClientConfig};
use arti_client::config::pt::TransportConfigBuilder;
use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder};
use arti_client::config::TorClientConfigBuilder;
use fs_mistrust::Mistrust;
use grin_util::secp::SecretKey;
use grin_wallet_util::OnionV3Address;
use ed25519_dalek::hazmat::ExpandedSecretKey;
use curve25519_dalek::digest::Digest;
use sha2::Sha512;
use tor_config::{CfgPath, Listen};
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
use tor_rtcompat::{BlockOn, Runtime};
use tor_rtcompat::Runtime;
use tor_hsrproxy::OnionServiceReverseProxy;
use tor_hsrproxy::config::{Encapsulation, ProxyAction, ProxyPattern, ProxyRule, TargetAddr, ProxyConfigBuilder};
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname};
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname, RunningOnionService};
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
use arti_hyper::ArtiHttpConnector;
use futures::TryFutureExt;
use hyper::Body;
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
use crate::tor::TorServerConfig;
// On aarch64-apple-darwin targets there is an issue with the native and rustls
// tls implementation so this makes it fall back to the openssl variant.
//
// https://gitlab.torproject.org/tpo/core/arti/-/issues/715
#[cfg(not(all(target_vendor = "apple", target_arch = "aarch64")))]
use tls_api_native_tls::TlsConnector;
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
use tls_api_openssl::TlsConnector;
use crate::tor::TorConfig;
lazy_static! {
/// Static thread-aware state of [`Node`] to be updated from separate thread.
static ref TOR_SERVER_STATE: Arc<TorServer> = Arc::new(TorServer::default());
static ref TOR_SERVER_STATE: Arc<Tor> = Arc::new(Tor::default());
}
/// Tor server to use as SOCKS proxy for requests and to launch Onion services.
pub struct TorServer {
/// Running Tor client.
client: Arc<RwLock<Option<TorClient<TokioNativeTlsRuntime>>>>,
/// Running Tor client configuration.
config: Arc<RwLock<Option<TorClientConfig>>>,
/// Flag to check if server is running.
running: AtomicBool,
/// Flag to check if server is starting.
starting: AtomicBool,
/// Flag to check if server needs to stop.
stopping: AtomicBool,
/// Flag to check if error happened.
error: AtomicBool,
pub struct Tor {
/// [`TorClient`] used for connections with configuration.
client: Arc<RwLock<(TorClient<TokioNativeTlsRuntime>, TorClientConfig)>>,
/// Mapping of running Onion services identifiers to proxy.
running_services: Arc<RwLock<HashMap<String, Arc<OnionServiceReverseProxy>>>>
running_services: Arc<RwLock<BTreeMap<String,
(Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>>>,
/// Starting Onion services identifiers.
starting_services: Arc<RwLock<BTreeSet<String>>>,
/// Failed Onion services identifiers.
failed_services: Arc<RwLock<BTreeSet<String>>>
}
impl Default for TorServer {
impl Default for Tor {
fn default() -> Self {
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)),
}
}
}
impl TorServer {
/// Check if server is running.
pub fn is_running() -> bool {
TOR_SERVER_STATE.running.load(Ordering::Relaxed)
}
/// Check if server is running.
pub fn is_starting() -> bool {
TOR_SERVER_STATE.starting.load(Ordering::Relaxed)
}
/// 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.
// Create Tor client config.
let mut builder =
TorClientConfigBuilder::from_directories(TorServerConfig::state_path(),
TorServerConfig::cache_path());
TorClientConfigBuilder::from_directories(TorConfig::state_path(),
TorConfig::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.
// Create connected Tor client from config.
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);
let config = builder.build().unwrap();
let client = TorClient::with_runtime(runtime)
.config(config.clone())
.create_unbootstrapped()
.unwrap();
Self {
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()))
}
}
} else {
TOR_SERVER_STATE.starting.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.error.store(true, Ordering::Relaxed);
}
impl Tor {
/// Send post request using Tor.
pub async fn post(body: String, url: String) -> Option<String> {
// Bootstrap client.
let client_config = TOR_SERVER_STATE.client.read().unwrap();
let client = client_config.0.clone();
client.bootstrap().await.unwrap();
// 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();
// Send request.
let mut resp = None;
match http.request(req).await {
Ok(r) => {
match hyper::body::to_bytes(r).await {
Ok(raw) => {
resp = Some(String::from_utf8_lossy(&raw).to_string())
},
Err(_) => {},
}
},
Err(_) => {},
}
});
resp
}
/// Launch SOCKS proxy server to send connections.
async fn launch_socks_proxy<R: Runtime>(runtime: R, tor_client: TorClient<R>) -> Result<()> {
let proxy_handle: JoinHandle<Result<()>> = tokio::spawn(
run_socks_proxy(
runtime,
tor_client,
Listen::new_localhost(TorServerConfig::socks_port()),
)
);
// Setup server state flags.
TOR_SERVER_STATE.starting.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.running.store(true, Ordering::Relaxed);
loop {
if Self::is_stopping() || proxy_handle.is_finished() {
proxy_handle.abort();
TOR_SERVER_STATE.stopping.store(false, Ordering::Relaxed);
TOR_SERVER_STATE.running.store(false, Ordering::Relaxed);
return Ok(());
}
sleep(Duration::from_millis(3000)).await;
}
/// Check if Onion service is starting.
pub fn is_service_starting(id: &String) -> bool {
let r_services = TOR_SERVER_STATE.starting_services.read().unwrap();
r_services.contains(id)
}
/// Check if Onion service is running.
@ -217,36 +143,80 @@ impl TorServer {
r_services.contains_key(id)
}
/// Check if Onion service failed on start.
pub fn is_service_failed(id: &String) -> bool {
let r_services = TOR_SERVER_STATE.failed_services.read().unwrap();
r_services.contains(id)
}
/// Stop running Onion service.
pub fn stop_service(id: &String) {
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
if let Some(proxy) = w_services.remove(id) {
if let Some((svc, proxy)) = w_services.remove(id) {
proxy.shutdown();
drop(svc);
}
}
/// Run Onion service from listening local address, secret key and identifier.
pub fn run_service(addr: SocketAddr, key: SecretKey, id: &String) {
/// Start Onion service from listening local port and [`SecretKey`].
pub fn start_service(port: u16, key: SecretKey, id: &String) {
// Check if service is already running.
if Self::is_service_running(id) {
return;
} else {
// Save starting service.
let mut w_services = TOR_SERVER_STATE.starting_services.write().unwrap();
w_services.insert(id.clone());
// Remove service from failed.
let mut w_services = TOR_SERVER_STATE.failed_services.write().unwrap();
w_services.remove(id);
}
let hs_nickname = HsNickname::new(id.clone()).unwrap();
let service_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);
// Bootstrap client and launch Onion service.
client.bootstrap().await.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, request) = client.launch_onion_service(service_config).unwrap();
// 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);
// 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();
// Launch Onion service.
let (_, request) = client.launch_onion_service(service_config).unwrap();
println!(
"Onion service {} launched at: {}",
hs_nickname,
service.onion_name().unwrap().to_string()
);
}).unwrap();
}
/// Launch Onion service proxy.
async fn run_service_proxy<R, S>(
addr: SocketAddr,
client: TorClient<R>,
service: Arc<RunningOnionService>,
request: S,
nickname: HsNickname
)
where
R: Runtime,
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
{
let id = nickname.to_string();
let runtime = client.runtime().clone();
// Setup proxy to forward request from Tor address to local address.
let proxy_rule = ProxyRule::new(
@ -257,43 +227,49 @@ impl TorServer {
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap());
// Launch proxy at client runtime.
let proxy_service = proxy.clone();
let runtime = client.runtime().clone();
let nickname = hs_nickname.clone();
// Save running service.
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
w_services.insert(id.clone(), (service.clone(), proxy.clone()));
// Remove service from starting.
let mut w_services = TOR_SERVER_STATE.starting_services.write().unwrap();
w_services.remove(&id);
// Start proxy for launched service.
client
.runtime()
.spawn(async move {
// Launch proxy for launched service.
match proxy_service.handle_requests(runtime, nickname.clone(), request).await {
match proxy
.handle_requests(runtime, nickname.clone(), request)
.await {
Ok(()) => {
eprintln!("Onion service {} stopped.", nickname);
// Remove service from running.
let mut w_services =
TOR_SERVER_STATE.running_services.write().unwrap();
w_services.remove(&id);
println!("Onion service {} stopped.", nickname);
}
Err(e) => {
// Remove service from running.
let mut w_services =
TOR_SERVER_STATE.running_services.write().unwrap();
w_services.remove(&id);
// Save failed service.
let mut w_services =
TOR_SERVER_STATE.failed_services.write().unwrap();
w_services.insert(id);
eprintln!("Onion service {} exited with an error: {}", nickname, e);
}
}
}).unwrap();
// Save running service.
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
w_services.insert(id.clone(), proxy);
let onion_addr = OnionV3Address::from_private(&key.0).unwrap();
eprintln!("Onion service {} launched at {}", hs_nickname, onion_addr.to_ov3_str());
}
/// Add Onion service key to keystore.
/// Save Onion service key to keystore.
fn add_service_key(mistrust: &Mistrust, key: &SecretKey, hs_nickname: &HsNickname) {
let mut client_config_builder = TorClientConfigBuilder::from_directories(
TorServerConfig::state_path(),
TorServerConfig::cache_path()
);
client_config_builder
.address_filter()
.allow_onion_addrs(true);
let arti_store =
ArtiNativeKeystore::from_path_and_mistrust(TorServerConfig::keystore_path(), &mistrust)
ArtiNativeKeystore::from_path_and_mistrust(TorConfig::keystore_path(), &mistrust)
.unwrap();
let key_manager = KeyMgrBuilder::default()
@ -329,49 +305,4 @@ impl TorServer {
)
.unwrap();
}
/// Setup Tor Snowflake bridges.
fn setup_bridges(builder: &mut TorClientConfigBuilder) {
// Add a single bridge to the list of bridges, from a bridge line.
// This line comes from https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/blob/main/projects/common/bridges_list.snowflake.txt
// this is a real bridge line you can use as-is, after making sure it's still up to date with
// above link.
const BRIDGE1_LINE: &str = "Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn";
let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse().unwrap();
builder.bridges().bridges().push(bridge_1);
// Add a second bridge, built by hand. We use the 2nd bridge line from above, but modify some
// parameters to use AMP Cache instead of Fastly as a signaling channel. The difference in
// configuration is detailed in
// https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/tree/main/client#amp-cache
let mut bridge2_builder = BridgeConfigBuilder::default();
bridge2_builder
.transport("snowflake")
.push_setting(
"fingerprint",
"8838024498816A039FCBBAB14E6F40A0843051FA"
)
.push_setting("url", "https://snowflake-broker.torproject.net/")
.push_setting("ampcache", "https://cdn.ampproject.org/")
.push_setting("front", "www.google.com")
.push_setting(
"ice",
"stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478",
)
.push_setting("utls-imitate", "hellorandomizedalpn");
bridge2_builder.set_addrs(vec!["192.0.2.4:80".parse().unwrap()]);
bridge2_builder.set_ids(vec!["8838024498816A039FCBBAB14E6F40A0843051FA".parse().unwrap()]);
// Now insert the second bridge into our config builder.
builder.bridges().bridges().push(bridge2_builder);
// Now configure an snowflake transport. (Requires the "pt-client" feature)
let mut transport = TransportConfigBuilder::default();
transport
.protocols(vec!["snowflake".parse().unwrap()])
// this might be named differently on some systems, this should work on Debian,
// but Archlinux is known to use `snowflake-pt-client` instead for instance.
.path(CfgPath::new("snowflake-client".into()))
.run_on_startup(true);
builder.bridges().transports().push(transport);
}
}

View file

@ -39,7 +39,9 @@ pub struct WalletConfig {
/// Minimal amount of confirmations.
pub min_confirmations: u64,
/// Flag to use Dandelion to broadcast transactions.
pub use_dandelion: Option<bool>
pub use_dandelion: Option<bool>,
/// Flag to enable Tor listener on start.
pub enable_tor_listener: Option<bool>
}
/// Base wallets directory name.
@ -74,6 +76,7 @@ impl WalletConfig {
},
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
use_dandelion: Some(true),
enable_tor_listener: Some(true),
};
Settings::write_to_file(&config, config_path);
config

View file

@ -147,6 +147,8 @@ pub struct WalletTransaction {
pub data: TxLogEntry,
/// Calculated transaction amount between debited and credited amount.
pub amount: u64,
/// Flag to check if transaction is cancelling.
pub cancelling: bool,
/// Flag to check if transaction is posting after finalization.
pub posting: bool,
/// Flag to check if transaction can be finalized based on Slatepack message state.
@ -158,7 +160,7 @@ pub struct WalletTransaction {
impl WalletTransaction {
/// Check if transaction can be cancelled.
pub fn can_cancel(&self) -> bool {
!self.posting && !self.data.confirmed &&
!self.cancelling && !self.posting && !self.data.confirmed &&
self.data.tx_type != TxLogEntryType::TxReceivedCancelled
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
}

View file

@ -27,17 +27,21 @@ use grin_api::{ApiServer, Router};
use grin_chain::SyncStatus;
use grin_core::global;
use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_util::Mutex;
use grin_util::{Mutex, ToHex};
use grin_util::secp::SecretKey;
use grin_util::types::ZeroingString;
use grin_wallet_api::Owner;
use grin_wallet_controller::controller;
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::{Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, StatusMessage, TxLogEntry, TxLogEntryType, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlatepackAddress, SlateState, SlateVersion, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, retrieve_summary_info, retrieve_txs};
use crate::AppConfig;
use grin_wallet_util::OnionV3Address;
use serde_json::{json, Value};
use crate::AppConfig;
use crate::node::{Node, NodeConfig};
use crate::tor::Tor;
use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig};
use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction};
@ -51,11 +55,14 @@ pub struct Wallet {
/// [`WalletInstance`] external connection id applied after opening.
instance_ext_conn_id: Arc<AtomicI64>,
/// Wallet Slatepack address to receive txs at transport.
slatepack_address: Arc<RwLock<Option<String>>>,
/// Wallet sync thread.
sync_thread: Arc<RwLock<Option<Thread>>>,
/// Foreign API server.
foreign_api_server: Arc<RwLock<Option<ApiServer>>>,
/// Running wallet foreign API server and port.
foreign_api_server: Arc<RwLock<Option<(ApiServer, u16)>>>,
/// Flag to check if wallet reopening is needed.
reopen: Arc<AtomicBool>,
@ -91,7 +98,7 @@ pub struct Wallet {
/// Default Foreign API server host.
const DEFAULT_FOREIGN_API_HOST: &str = "127.0.0.1";
/// Default Foreign API server port.
const DEFAULT_FOREIGN_API_PORT: u16 = 3421;
const DEFAULT_FOREIGN_API_PORT: u16 = 3415;
impl Wallet {
/// Create new [`Wallet`] instance with provided [`WalletConfig`].
@ -100,6 +107,7 @@ impl Wallet {
config: Arc::new(RwLock::new(config)),
instance: None,
instance_ext_conn_id: Arc::new(AtomicI64::new(0)),
slatepack_address: Arc::new(RwLock::new(None)),
sync_thread: Arc::from(RwLock::new(None)),
foreign_api_server: Arc::new(RwLock::new(None)),
reopen: Arc::new(AtomicBool::new(false)),
@ -210,6 +218,35 @@ impl Wallet {
Ok(w_inst.parent_key_id())
}
/// Get wallet [`SecretKey`] for transports.
pub fn secret_key(&self) -> Result<SecretKey, Error> {
let instance = self.instance.clone().unwrap();
let mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&None).as_ref())?;
let parent_key_id = w_inst.parent_key_id();
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
Ok(sec_key)
}
/// Get unique opened wallet identifier, including current account.
pub fn identifier(&self) -> String {
let config = self.get_config();
format!("wallet_{}_{}", config.id, config.account.to_hex())
}
/// Get Slatepack address to receive txs at transport.
pub fn slatepack_address(&self) -> Option<String> {
let r_address = self.slatepack_address.read().unwrap();
if r_address.is_some() {
let addr = r_address.clone().unwrap();
return Some(addr)
}
None
}
/// Get wallet config.
pub fn get_config(&self) -> WalletConfig {
self.config.read().unwrap().clone()
@ -222,10 +259,23 @@ impl Wallet {
w_config.save();
}
/// Check if start of Tor listener on wallet opening is needed.
pub fn auto_start_tor_listener(&self) -> bool {
let r_config = self.config.read().unwrap();
r_config.enable_tor_listener.unwrap_or(true)
}
/// Update start of Tor listener on wallet opening.
pub fn update_auto_start_tor_listener(&self, start: bool) {
let mut w_config = self.config.write().unwrap();
w_config.enable_tor_listener = Some(start);
w_config.save();
}
/// Check if Dandelion usage is needed to post transactions.
pub fn can_use_dandelion(&self) -> bool {
let r_config = self.config.read().unwrap();
r_config.use_dandelion.unwrap_or(false)
r_config.use_dandelion.unwrap_or(true)
}
/// Update usage of Dandelion to post transactions.
@ -267,6 +317,7 @@ impl Wallet {
}
// Open the wallet.
{
let instance = self.instance.clone().unwrap();
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?;
@ -297,6 +348,16 @@ impl Wallet {
return Err(e)
}
}
}
// Set slatepack address.
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
let mut w_address = self.slatepack_address.write().unwrap();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
})?;
Ok(())
}
@ -335,17 +396,21 @@ impl Wallet {
// Close wallet at separate thread.
let wallet_close = self.clone();
let instance = wallet_close.instance.clone().unwrap();
let service_id = wallet_close.identifier();
thread::spawn(move || {
// Stop created API server.
// Stop running API server.
let api_server_exists = {
wallet_close.foreign_api_server.read().unwrap().is_some()
};
if api_server_exists {
let mut api_server_w = wallet_close.foreign_api_server.write().unwrap();
api_server_w.as_mut().unwrap().stop();
*api_server_w = None;
let mut w_api_server = wallet_close.foreign_api_server.write().unwrap();
w_api_server.as_mut().unwrap().0.stop();
*w_api_server = None;
}
// Stop running Tor service.
Tor::stop_service(&service_id);
// Close the wallet.
Self::close_wallet(&instance);
@ -391,9 +456,16 @@ impl Wallet {
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
api.set_active_account(m, label)?;
// Set Slatepack address.
let mut w_address = self.slatepack_address.write().unwrap();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
})?;
// Stop service from previous account.
let cur_service_id = self.identifier();
Tor::stop_service(&cur_service_id);
// Save account label into config.
let mut w_config = self.config.write().unwrap();
w_config.account = label.to_owned();
@ -478,6 +550,16 @@ impl Wallet {
}
}
/// Get running Foreign API server port.
pub fn foreign_api_port(&self) -> Option<u16> {
let r_api = self.foreign_api_server.read().unwrap();
if r_api.is_some() {
let api = r_api.as_ref().unwrap();
return Some(api.1);
}
None
}
/// Parse Slatepack message into [`Slate`].
pub fn parse_slatepack(&self, message: &String) -> Result<Slate, Error> {
let api = Owner::new(self.instance.clone().unwrap(), None);
@ -570,7 +652,7 @@ impl Wallet {
}
/// Initialize a transaction to send amount, return request for funds receiver.
pub fn send(&self, amount: u64) -> Result<String, Error> {
pub fn send(&self, amount: u64) -> Result<(Slate, String), Error> {
let config = self.get_config();
let args = InitTxArgs {
src_acct_name: Some(config.account),
@ -587,16 +669,102 @@ impl Wallet {
api.tx_lock_outputs(None, &slate)?;
// Create Slatepack message response.
let response = self.create_slatepack_message(&slate)?;
let message_resp = self.create_slatepack_message(&slate)?;
// Sync wallet info.
self.sync();
Ok(response)
Ok((slate, message_resp))
}
/// Send amount to provided address with Tor transport.
pub async fn send_tor(&mut self, amount: u64, addr: &SlatepackAddress) -> Option<Slate> {
// Initialize transaction.
let send_res = self.send(amount);
if send_res.is_err() {
return None;
}
let slate = send_res.unwrap().0;
// Function to cancel initialized tx in case of error.
let cancel_tx = || {
let instance = self.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, None, Some(slate.clone().id));
};
// Initialize parameters.
let tor_addr = OnionV3Address::try_from(addr).unwrap().to_http_str();
let url = format!("{}/v2/foreign", tor_addr);
let slate_send = VersionedSlate::into_version(slate.clone(), SlateVersion::V4).unwrap();
let body = json!({
"jsonrpc": "2.0",
"method": "receive_tx",
"id": 1,
"params": [
slate_send,
null,
null
]
}).to_string();
// Send request to receiver.
let req_res = Tor::post(body, url).await;
if req_res.is_none() {
cancel_tx();
return None;
}
// Parse response and finalize transaction.
let res: Value = serde_json::from_str(&req_res.unwrap()).unwrap();
println!("Response: {}", res);
if res["error"] != json!(null) {
let report = format!(
"Posting transaction slate: Error: {}, Message: {}",
res["error"]["code"], res["error"]["message"]
);
println!("{}", report);
cancel_tx();
return None;
}
let slate_value = res["result"]["Ok"].clone();
println!("slate_value: {}", slate_value);
let mut ret_slate = None;
match Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) {
Ok(s) => {
let mut api = Owner::new(self.instance.clone().unwrap(), None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
return if let Ok(slate) = api.finalize_tx(m, &s) {
ret_slate = Some(slate.clone());
let result = api.post_tx(m, &slate, self.can_use_dandelion());
match result {
Ok(_) => {
println!("Tx sent successfully", );
Ok(())
}
Err(e) => {
eprintln!("Tx sent fail: {}", e);
Err(e)
}
}
} else {
Err(Error::GenericError("TX finalization error".to_string()))
};
}).unwrap();
}
Err(_) => {}
};
if ret_slate.is_none() {
cancel_tx();
}
ret_slate
}
/// Initialize an invoice transaction to receive amount, return request for funds sender.
pub fn issue_invoice(&self, amount: u64) -> Result<String, Error> {
pub fn issue_invoice(&self, amount: u64) -> Result<(Slate, String), Error> {
let args = IssueInvoiceTxArgs {
dest_acct_name: None,
amount,
@ -606,12 +774,12 @@ impl Wallet {
let slate = api.issue_invoice_tx(None, args)?;
// Create Slatepack message response.
let response = self.create_slatepack_message(&slate)?;
let response = self.create_slatepack_message(&slate.clone())?;
// Sync wallet info.
self.sync();
Ok(response)
Ok((slate, response))
}
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
@ -699,13 +867,31 @@ 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.
// Setup cancelling status.
{
let mut w_data = self.data.write().unwrap();
let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id {
tx.cancelling = true;
tx.can_finalize = false;
}
tx.clone()
}).collect::<Vec<WalletTransaction>>();
data.txs = txs;
*w_data = Some(data);
}
let wallet = self.clone();
thread::spawn(move || {
let instance = wallet.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, Some(id), None);
// Setup posting flag, and ability to finalize.
let mut w_data = wallet.data.write().unwrap();
let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id {
tx.cancelling = false;
tx.posting = false;
tx.can_finalize = false;
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
@ -719,7 +905,8 @@ impl Wallet {
data.txs = txs;
*w_data = Some(data);
// Refresh wallet info to update statuses.
self.sync();
wallet.sync();
});
}
/// Change wallet password.
@ -803,9 +990,8 @@ fn start_sync(mut wallet: Wallet) -> Thread {
wallet.txs_sync_progress.store(0, Ordering::Relaxed);
wallet.repair_progress.store(0, Ordering::Relaxed);
println!("create new thread");
thread::spawn(move || loop {
println!("start new cycle");
println!("SYNC {}, attempts: {}", wallet.get_config().name, wallet.get_sync_attempts());
// Close wallet on chain type change.
if wallet.get_config().chain_type != AppConfig::chain_type() {
@ -814,7 +1000,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Stop syncing if wallet was closed.
if !wallet.is_open() {
println!("finishing thread at start");
// Clear thread instance.
let mut thread_w = wallet.sync_thread.write().unwrap();
*thread_w = None;
@ -822,7 +1007,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Clear wallet info.
let mut w_data = wallet.data.write().unwrap();
*w_data = None;
println!("finish at start complete");
return;
}
@ -838,26 +1022,36 @@ fn start_sync(mut wallet: Wallet) -> Thread {
wallet.set_sync_error(not_enabled);
// Skip cycle when node sync is not finished.
if !Node::is_running() || Node::get_sync_status() != Some(SyncStatus::NoSync) {
println!("integrated node wait");
thread::park_timeout(ATTEMPT_DELAY);
continue;
}
}
// Start Foreign API listener if API server was not created.
let api_server_exists = {
// Start Foreign API listener if API server is not running.
let mut api_server_running = {
wallet.foreign_api_server.read().unwrap().is_some()
};
if !api_server_exists {
if !api_server_running && wallet.is_open() {
match start_api_server(&mut wallet) {
Ok(api_server) => {
let mut api_server_w = wallet.foreign_api_server.write().unwrap();
*api_server_w = Some(api_server);
api_server_running = true;
}
Err(_) => {}
}
}
// Start Tor service if API server is running and wallet is open.
if wallet.auto_start_tor_listener() && wallet.is_open() && api_server_running &&
!Tor::is_service_running(&wallet.identifier()) {
let r_foreign_api = wallet.foreign_api_server.read().unwrap();
let api = r_foreign_api.as_ref().unwrap();
if let Ok(sec_key) = wallet.secret_key() {
Tor::start_service(api.1, sec_key, &wallet.identifier());
}
}
// Scan outputs if repair is needed or sync data if there is no error.
if !wallet.sync_error() {
if wallet.is_repairing() {
@ -869,7 +1063,6 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Stop sync if wallet was closed.
if !wallet.is_open() {
println!("finishing thread after updating");
// Clear thread instance.
let mut thread_w = wallet.sync_thread.write().unwrap();
*thread_w = None;
@ -877,73 +1070,37 @@ fn start_sync(mut wallet: Wallet) -> Thread {
// Clear wallet info.
let mut w_data = wallet.data.write().unwrap();
*w_data = None;
println!("finishing after updating complete");
return;
}
// Repeat after default or attempt delay if synchronization was not successful.
let delay = if wallet.sync_error()
|| wallet.get_sync_attempts() != 0 {
let failed_sync = wallet.sync_error() || wallet.get_sync_attempts() != 0;
let delay = if failed_sync {
ATTEMPT_DELAY
} else {
SYNC_DELAY
};
println!("park for {}", delay.as_millis());
if failed_sync {
println!("SYNC {} failed, attempts: {}, wait {}ms",
wallet.get_config().name,
wallet.get_sync_attempts(),
delay.as_millis());
} else {
println!("SYNC success for {}, wait {}ms",
wallet.get_config().name,
delay.as_millis());
}
thread::park_timeout(delay);
}).thread().clone()
}
/// Start Foreign API server to accept txs via Tor and receive mining rewards from Stratum server.
fn start_api_server(wallet: &mut Wallet) -> Result<ApiServer, Error> {
// Find free port.
let free_port = (DEFAULT_FOREIGN_API_PORT..).find(|port| {
return match TcpListener::bind((DEFAULT_FOREIGN_API_HOST, port.to_owned())) {
Ok(_) => {
let node_p2p_port = NodeConfig::get_p2p_port();
let node_api_port = NodeConfig::get_api_ip_port().1;
port.to_string() != node_p2p_port && port.to_string() != node_api_port
},
Err(_) => false
}
}).unwrap();
// Setup API server address.
let api_addr = format!("{}:{}", DEFAULT_FOREIGN_API_HOST, free_port);
// Start Foreign API server thread.
let instance = wallet.instance.clone().unwrap();
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)),
false,
Mutex::new(None));
let mut router = Router::new();
router
.add_route("/v2/foreign", Arc::new(api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
println!("Starting HTTP Foreign listener API server at {}.", api_addr);
let socket_addr: SocketAddr = api_addr.parse().unwrap();
let _ = apis.start(socket_addr, router, None, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
println!("HTTP Foreign listener started.");
Ok(apis)
}
/// Retrieve [`WalletData`] from node.
fn sync_wallet_data(wallet: &Wallet) {
println!("SYNC start, attempts: {}", wallet.get_sync_attempts());
let wallet_info = wallet.clone();
let (info_tx, info_rx) = mpsc::channel::<StatusMessage>();
// Update info sync progress at separate thread.
thread::spawn(move || {
while let Ok(m) = info_rx.recv() {
println!("SYNC INFO MESSAGE");
match m {
StatusMessage::UpdatingOutputs(_) => {}
StatusMessage::UpdatingTransactions(_) => {}
@ -990,7 +1147,6 @@ fn sync_wallet_data(wallet: &Wallet) {
let (txs_tx, txs_rx) = mpsc::channel::<StatusMessage>();
thread::spawn(move || {
while let Ok(m) = txs_rx.recv() {
println!("SYNC TXS MESSAGE");
match m {
StatusMessage::UpdatingOutputs(_) => {}
StatusMessage::UpdatingTransactions(_) => {}
@ -1116,6 +1272,7 @@ fn sync_wallet_data(wallet: &Wallet) {
new_txs.push(WalletTransaction {
data: tx.clone(),
amount,
cancelling: false,
posting,
can_finalize,
repost_height,
@ -1148,8 +1305,6 @@ fn sync_wallet_data(wallet: &Wallet) {
wallet.increment_sync_attempts();
}
println!("SYNC cycle finished, attempts: {}", wallet.get_sync_attempts());
// Set an error if maximum number of attempts was reached.
if wallet.get_sync_attempts() >= SYNC_ATTEMPTS {
wallet.reset_sync_attempts();
@ -1157,6 +1312,47 @@ fn sync_wallet_data(wallet: &Wallet) {
}
}
/// Start Foreign API server to receive mining rewards from Stratum server.
fn start_api_server(wallet: &mut Wallet) -> Result<(ApiServer, u16), Error> {
// Find free port.
let free_port = (DEFAULT_FOREIGN_API_PORT..).find(|port| {
return match TcpListener::bind((DEFAULT_FOREIGN_API_HOST, port.to_owned())) {
Ok(_) => {
let node_p2p_port = NodeConfig::get_p2p_port();
let node_api_port = NodeConfig::get_api_ip_port().1;
port.to_string() != node_p2p_port && port.to_string() != node_api_port
},
Err(_) => false
}
}).unwrap();
// Setup API server address.
let api_addr = format!("{}:{}", DEFAULT_FOREIGN_API_HOST, free_port);
// Start Foreign API server thread.
let instance = wallet.instance.clone().unwrap();
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)),
false,
Mutex::new(None));
let mut router = Router::new();
router
.add_route("/v2/foreign", Arc::new(api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
println!("Starting HTTP Foreign listener API server at {}.", api_addr);
let socket_addr: SocketAddr = api_addr.parse().unwrap();
let _ = apis.start(socket_addr, router, None, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
println!("HTTP Foreign listener started.");
Ok((apis, free_port))
}
/// Update wallet accounts data.
fn update_accounts(wallet: &Wallet, current_height: u64, current_spendable: Option<u64>) {
// Update only current account if list is not empty.