feat(swap): Use art_client to dial over Tor (#196)

- Upgrade `sqlx` to `0.8`
- Use `arti_client@0.24` in combination with [`libp2p-community-tor`](https://crates.io/crates/libp2p-community-tor/0.4.1). https://github.com/umgefahren/libp2p-tor/pull/18 was required for this.
- Display spinner in GUI while Tor circuits are being established
- Remove unused dependencies (`once_cell`, `tauri-plugin-devtools`, `digest`, `hyper`, `itertools`, `erased_serde`)
- Bundle roboto font from npm registry
This commit is contained in:
binarybaron 2024-11-21 01:00:36 +01:00 committed by GitHub
parent 3aef92e848
commit 6cd228fada
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 2689 additions and 871 deletions

View file

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- CLI + GUI: Tor is now bundled with the application. All libp2p connections between peers are routed through Tor, if the `--enable-tor` flag is set. The `--tor-socks5-port` argument has been removed. This feature is powered by [arti](https://tpo.pages.torproject.net/core/arti/), an implementation of the Tor protocol in Rust by the Tor Project.
## [1.0.0-rc.5] - 2024-11-19
- GUI: Set new Discord invite link to non-expired one

2703
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,36 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/renderer/index.tsx"></script>
<style>
::-webkit-scrollbar {
display: none;
}
*,
*::after,
*::before {
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
html,
body {
height: 100%;
margin: 0;
overflow: auto;
}
</style>
</body>
</html>
<body>
<div id="root"></div>
<script type="module" src="/src/renderer/index.tsx"></script>
<style>
::-webkit-scrollbar {
display: none;
}
*,
*::after,
*::before {
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}
html,
body {
height: 100%;
margin: 0;
overflow: auto;
}
</style>
</body>
</html>

View file

@ -15,6 +15,7 @@
"tauri": "tauri"
},
"dependencies": {
"@fontsource/roboto": "^5.1.0",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.61",
@ -66,4 +67,4 @@
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.1.1"
}
}
}

View file

@ -17,6 +17,7 @@ export interface CliLog {
name: CliLogSpanType;
[index: string]: unknown;
}[];
target?: string;
}
function isCliLog(log: unknown): log is CliLog {

View file

@ -13,6 +13,7 @@ import { useSettings } from "store/hooks";
import { themes } from "./theme";
import { useEffect } from "react";
import { setupBackgroundTasks } from "renderer/background";
import "@fontsource/roboto";
const useStyles = makeStyles((theme) => ({
innerContent: {

View file

@ -57,6 +57,12 @@ export default function DaemonStatusAlert() {
Opening the local database
</LoadingSpinnerAlert>
);
case "EstablishingTorCircuits":
return (
<LoadingSpinnerAlert severity="warning">
Connecting to the Tor network
</LoadingSpinnerAlert>
);
}
break;
case "Available":

View file

@ -5,7 +5,7 @@ import { logsToRawString } from "utils/parseUtils";
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
function RenderedCliLog({ log }: { log: CliLog }) {
const { timestamp, level, fields } = log;
const { timestamp, level, fields, target } = log;
const levelColorMap = {
DEBUG: "#1976d2", // Blue
@ -29,6 +29,9 @@ function RenderedCliLog({ log }: { log: CliLog }) {
size="small"
style={{ backgroundColor: levelColorMap[level], color: "white" }}
/>
{target && (
<Chip label={target.split("::")[0]} size="small" variant="outlined" />
)}
<Chip label={timestamp} size="small" variant="outlined" />
<Typography variant="subtitle2">{fields.message}</Typography>
</Box>

View file

@ -399,6 +399,11 @@
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843"
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
"@fontsource/roboto@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@fontsource/roboto/-/roboto-5.1.0.tgz#00230737ec09c60ae877a5e33d067c0607fdd5ba"
integrity sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"

View file

@ -16,14 +16,12 @@ tauri-build = { version = "2.0", features = [ "config-json5" ] }
[dependencies]
anyhow = "1"
once_cell = "1"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
swap = { path = "../swap", features = [ "tauri" ] }
sysinfo = "=0.32.0"
tauri = { version = "2.0", features = [ "config-json5" ] }
tauri-plugin-clipboard-manager = "2.0"
tauri-plugin-devtools = "2.0"
tauri-plugin-process = "2.0"
tauri-plugin-shell = "2.0"
tauri-plugin-store = "2.1.0"

View file

@ -299,6 +299,7 @@ async fn initialize_context(
})
.with_json(false)
.with_debug(true)
.with_tor(true)
.with_tauri(tauri_handle.clone())
.build()
.await;

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT peer_id\n FROM peers\n WHERE swap_id = ?\n ",
"describe": {
"columns": [
{
"name": "peer_id",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "081c729a0f1ad6e4ff3e13d6702c946bc4d37d50f40670b4f51d2efcce595aa6"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n insert into peer_addresses (\n peer_id,\n address\n ) values (?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "0ab84c094964968e96a3f2bf590d9ae92227d057386921e0e57165b887de3c75"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT min(entered_at) as start_date\n FROM swap_states\n WHERE swap_id = ?\n ",
"describe": {
"columns": [
{
"name": "start_date",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true
]
},
"hash": "0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n insert into peers (\n swap_id,\n peer_id\n ) values (?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "2a356078a41b321234adf2aa385b501749f907f7c422945a8bdda2b6274f5225"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n insert into monero_addresses (\n swap_id,\n address\n ) values (?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "50a5764546f69c118fa0b64120da50f51073d36257d49768de99ff863e3511e0"
}

View file

@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "\n SELECT swap_id, state\n FROM (\n SELECT max(id), swap_id, state\n FROM swap_states\n GROUP BY swap_id\n )\n ",
"describe": {
"columns": [
{
"name": "swap_id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "state",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
true
]
},
"hash": "5cc61dd0315571bc198401a354cd9431ee68360941f341386cbacf44ea598de8"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n ORDER BY id desc\n LIMIT 1;\n\n ",
"describe": {
"columns": [
{
"name": "state",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "88f761a4f7a0429cad1df0b1bebb1c0a27b2a45656549b23076d7542cfa21ecf"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT DISTINCT address FROM monero_addresses",
"describe": {
"columns": [
{
"name": "address",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
]
},
"hash": "98a8b7f4971e0eb4ab8f5aa688aa22e7fdc6b925de211f7784782f051c2dcd8c"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n insert into swap_states (\n swap_id,\n entered_at,\n state\n ) values (?, ?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "b703032b4ddc627a1124817477e7a8e5014bdc694c36a14053ef3bb2fc0c69b0"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT address\n FROM monero_addresses\n WHERE swap_id = ?\n ",
"describe": {
"columns": [
{
"name": "address",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "ce270dd4a4b9615695a79864240c5401e2122077365e5e5a19408c068c7f9454"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT DISTINCT address\n FROM peer_addresses\n WHERE peer_id = ?\n ",
"describe": {
"columns": [
{
"name": "address",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "d78acba5eb8563826dd190e0886aa665aae3c6f1e312ee444e65df1c95afe8b2"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n ",
"describe": {
"columns": [
{
"name": "state",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "e05620f420f8c1022971eeb66a803323a8cf258cbebb2834e3f7cf8f812fa646"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO buffered_transfer_proofs (\n swap_id,\n proof\n ) VALUES (?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "e36c287aa98ae80ad4b6bb6f7e4b59cced041406a9db71da827b09f0d3bacfd6"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT proof\n FROM buffered_transfer_proofs\n WHERE swap_id = ?\n ",
"describe": {
"columns": [
{
"name": "proof",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "e9d422daf774d099fcbde6c4cda35821da948bd86cc57798b4d8375baf0b51ae"
}

View file

@ -13,6 +13,7 @@ tauri = [ "dep:tauri" ]
[dependencies]
anyhow = "1"
arti-client = { version = "0.24.0", features = [ "static-sqlite", "tokio", "rustls" ], default-features = false }
async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
async-trait = "0.1"
asynchronous-codec = "0.7.0"
@ -29,7 +30,6 @@ conquer-once = "0.4"
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
data-encoding = "2.6"
dialoguer = "0.11"
digest = "0.10.7"
directories-next = "2"
ecdsa_fun = { version = "0.10", default-features = false, features = [
"libsecp_compat",
@ -37,14 +37,12 @@ ecdsa_fun = { version = "0.10", default-features = false, features = [
"adaptor",
] }
ed25519-dalek = "1"
erased-serde = "0.4.5"
futures = { version = "0.3", default-features = false }
hex = "0.4"
hyper = "0.14.20"
itertools = "0.13"
jsonrpsee = { version = "0.16.2", features = [ "server" ] }
jsonrpsee-core = "0.16.2"
libp2p = { version = "0.53.2", features = [ "tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa" ] }
libp2p-community-tor = { git = "https://github.com/UnstoppableSwap/libp2p-tor", branch = "main" }
monero = { version = "0.12", features = [ "serde_support" ] }
monero-rpc = { path = "../monero-rpc" }
once_cell = "1.19"
@ -73,10 +71,9 @@ sigma_fun = { version = "0.7", default-features = false, features = [
"secp256k1",
"alloc",
] }
sqlx = { version = "0.6.3", features = [
sqlx = { version = "0.8", features = [
"sqlite",
"runtime-tokio-rustls",
"offline",
] }
structopt = "0.3"
strum = { version = "0.26", features = [ "derive" ] }
@ -95,10 +92,10 @@ tokio = { version = "1", features = [
"net",
"parking_lot",
] }
tokio-socks = "0.5"
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
toml = "0.8"
tor-rtcompat = "0.24.0"
torut = { version = "0.2", default-features = false, features = [
"v3",
"control",
@ -107,7 +104,6 @@ tower = { version = "0.4.13", features = [ "full" ] }
tower-http = { version = "0.3.4", features = [ "full" ] }
tracing = { version = "0.1", features = [ "attributes" ] }
tracing-appender = "0.2"
tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] }
tracing-subscriber = { version = "0.3", default-features = false, features = [
"fmt",
"ansi",
@ -131,13 +127,10 @@ zip = "0.5"
[dev-dependencies]
bitcoin-harness = { git = "https://github.com/delta1/bitcoin-harness-rs.git", rev = "80cc8d05db2610d8531011be505b7bee2b5cdf9f" }
get-port = "3"
hyper = "1.3"
jsonrpsee = { version = "0.16.2", features = [ "ws-client" ] }
mockito = "1.4"
monero-harness = { path = "../monero-harness" }
port_check = "0.2"
proptest = "1"
sequential-test = "0.2.4"
serde_cbor = "0.11"
serial_test = "3.1"
tempfile = "3"

View file

@ -4,6 +4,7 @@ pub mod cancel_and_refund;
pub mod command;
mod event_loop;
mod list_sellers;
mod tor;
pub mod transport;
pub mod watcher;
@ -45,7 +46,7 @@ mod tests {
rendezvous_peer_id,
rendezvous_address,
namespace,
0,
None,
identity::Keypair::generate_ed25519(),
);
let sellers = tokio::time::timeout(Duration::from_secs(15), list_sellers)

View file

@ -1,7 +1,7 @@
pub mod request;
pub mod tauri_bindings;
use crate::cli::command::{Bitcoin, Monero, Tor};
use crate::cli::command::{Bitcoin, Monero};
use crate::common::tracing_util::Format;
use crate::database::{open_db, AccessMode};
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
@ -12,6 +12,7 @@ use crate::seed::Seed;
use crate::{bitcoin, common, monero};
use anyhow::anyhow;
use anyhow::{bail, Context as AnyContext, Error, Result};
use arti_client::TorClient;
use futures::future::try_join_all;
use std::fmt;
use std::future::Future;
@ -22,18 +23,19 @@ use tauri_bindings::{
};
use tokio::sync::{broadcast, broadcast::Sender, Mutex as TokioMutex, RwLock};
use tokio::task::JoinHandle;
use tor_rtcompat::tokio::TokioRustlsRuntime;
use tracing::level_filters::LevelFilter;
use tracing::Level;
use url::Url;
use uuid::Uuid;
use super::tor::init_tor_client;
use super::watcher::Watcher;
static START: Once = Once::new();
#[derive(Clone, PartialEq, Debug)]
pub struct Config {
tor_socks5_port: u16,
namespace: XmrBtcNamespace,
pub env_config: EnvConfig,
seed: Option<Seed>,
@ -188,6 +190,7 @@ pub struct Context {
bitcoin_wallet: Option<Arc<bitcoin::Wallet>>,
monero_wallet: Option<Arc<monero::Wallet>>,
monero_rpc_process: Option<Arc<SyncMutex<monero::WalletRpcProcess>>>,
tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
}
/// A conveniant builder struct for [`Context`].
@ -196,11 +199,11 @@ pub struct Context {
pub struct ContextBuilder {
monero: Option<Monero>,
bitcoin: Option<Bitcoin>,
tor: Option<Tor>,
data: Option<PathBuf>,
is_testnet: bool,
debug: bool,
json: bool,
tor: bool,
tauri_handle: Option<TauriHandle>,
}
@ -219,11 +222,11 @@ impl ContextBuilder {
ContextBuilder {
monero: None,
bitcoin: None,
tor: None,
data: None,
is_testnet: false,
debug: false,
json: false,
tor: false,
tauri_handle: None,
}
}
@ -247,12 +250,6 @@ impl ContextBuilder {
self
}
/// Configures the Context to use Tor with the given configuration.
pub fn with_tor(mut self, tor: impl Into<Option<Tor>>) -> Self {
self.tor = tor.into();
self
}
/// Attach a handle to Tauri to the Context for emitting events etc.
pub fn with_tauri(mut self, tauri: impl Into<Option<TauriHandle>>) -> Self {
self.tauri_handle = tauri.into();
@ -277,6 +274,12 @@ impl ContextBuilder {
self
}
/// Whether to initialize a Tor client (default false)
pub fn with_tor(mut self, tor: bool) -> Self {
self.tor = tor;
self
}
/// Takes the builder, initializes the context by initializing the wallets and other components and returns the Context.
pub async fn build(self) -> Result<Context> {
let data_dir = data::data_dir_from(self.data, self.is_testnet)?;
@ -343,6 +346,7 @@ impl ContextBuilder {
self.tauri_handle.clone(),
)
.await?;
(Some(Arc::new(wlt)), Some(Arc::new(SyncMutex::new(prc))))
} else {
(None, None)
@ -356,8 +360,6 @@ impl ContextBuilder {
TauriContextInitializationProgress::OpeningDatabase,
));
let tor_socks5_port = self.tor.map_or(9050, |tor| tor.tor_socks5_port);
// If we are connected to the Bitcoin blockchain and if there is a handle to Tauri present,
// we start a background task to watch for timelock changes.
if let Some(wallet) = bitcoin_wallet.clone() {
@ -372,13 +374,28 @@ impl ContextBuilder {
}
}
self.tauri_handle
.emit_context_init_progress_event(TauriContextStatusEvent::Initializing(
TauriContextInitializationProgress::EstablishingTorCircuits,
));
let tor = if self.tor {
init_tor_client(&data_dir)
.await
.inspect_err(|err| {
tracing::error!(%err, "Failed to establish Tor client");
})
.ok()
} else {
None
};
let context = Context {
db,
bitcoin_wallet,
monero_wallet,
monero_rpc_process,
config: Config {
tor_socks5_port,
namespace: XmrBtcNamespace::from_is_testnet(self.is_testnet),
env_config,
seed: Some(seed),
@ -390,6 +407,7 @@ impl ContextBuilder {
swap_lock,
tasks: Arc::new(PendingTaskList::default()),
tauri_handle: self.tauri_handle,
tor_client: tor,
};
Ok(context)
@ -423,6 +441,7 @@ impl Context {
swap_lock: Arc::new(SwapLock::new()),
tasks: Arc::new(PendingTaskList::default()),
tauri_handle: None,
tor_client: None,
}
}
@ -539,7 +558,6 @@ impl Config {
let data_dir = data::data_dir_from(None, false).expect("Could not find data directory");
Self {
tor_socks5_port: 9050,
namespace: XmrBtcNamespace::from_is_testnet(false),
env_config,
seed: Some(seed),
@ -576,7 +594,6 @@ pub mod api_test {
let env_config = env_config_from(is_testnet);
Self {
tor_socks5_port: 9050,
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
env_config,
seed: Some(seed),

View file

@ -625,7 +625,7 @@ pub async fn buy_xmr(
let mut swarm = swarm::cli(
seed.derive_libp2p_identity(),
context.config.tor_socks5_port,
context.tor_client.clone(),
behaviour,
)
.await?;
@ -813,7 +813,7 @@ pub async fn resume_swap(
),
(seed.clone(), context.config.namespace),
);
let mut swarm = swarm::cli(seed.clone(), context.config.tor_socks5_port, behaviour).await?;
let mut swarm = swarm::cli(seed.clone(), context.tor_client.clone(), behaviour).await?;
let our_peer_id = swarm.local_peer_id();
tracing::debug!(peer_id = %our_peer_id, "Network layer initialized");
@ -1083,7 +1083,7 @@ pub async fn list_sellers(
rendezvous_node_peer_id,
rendezvous_point,
context.config.namespace,
context.config.tor_socks5_port,
context.tor_client.clone(),
identity,
)
.await?;
@ -1224,17 +1224,9 @@ where
}
loop {
println!("max_giveable: {}", max_giveable);
println!("bid_quote.min_quantity: {}", bid_quote.min_quantity);
let min_outstanding = bid_quote.min_quantity - max_giveable;
println!("min_outstanding: {}", min_outstanding);
let min_bitcoin_lock_tx_fee = estimate_fee(min_outstanding).await?;
println!("min_bitcoin_lock_tx_fee: {}", min_bitcoin_lock_tx_fee);
let min_deposit_until_swap_will_start = min_outstanding + min_bitcoin_lock_tx_fee;
println!(
"min_deposit_until_swap_will_start: {}",
min_deposit_until_swap_will_start
);
let max_deposit_until_maximum_amount_is_reached =
maximum_amount - max_giveable + min_bitcoin_lock_tx_fee;

View file

@ -123,6 +123,7 @@ pub enum TauriContextInitializationProgress {
},
OpeningMoneroWallet,
OpeningDatabase,
EstablishingTorCircuits,
}
#[typeshare]

View file

@ -33,8 +33,6 @@ pub const DEFAULT_ELECTRUM_RPC_URL_TESTNET: &str = "tcp://electrum.blockstream.i
const DEFAULT_BITCOIN_CONFIRMATION_TARGET: u16 = 1;
pub const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: u16 = 1;
const DEFAULT_TOR_SOCKS5_PORT: &str = "9050";
/// Represents the result of parsing the command-line parameters.
#[derive(Debug)]
@ -85,9 +83,9 @@ where
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_tor(tor.enable_tor)
.with_bitcoin(bitcoin)
.with_monero(monero)
.with_tor(tor)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
@ -184,9 +182,9 @@ where
} => {
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_tor(tor.enable_tor)
.with_bitcoin(bitcoin)
.with_monero(monero)
.with_tor(tor)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
@ -231,9 +229,9 @@ where
} => {
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_tor(tor.enable_tor)
.with_bitcoin(bitcoin)
.with_monero(monero)
.with_tor(tor)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
@ -248,12 +246,10 @@ where
CliCommand::CancelAndRefund {
swap_id: SwapId { swap_id },
bitcoin,
tor,
} => {
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_bitcoin(bitcoin)
.with_tor(tor)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
@ -273,7 +269,7 @@ where
} => {
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_tor(tor)
.with_tor(tor.enable_tor)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
@ -475,9 +471,6 @@ enum CliCommand {
#[structopt(flatten)]
bitcoin: Bitcoin,
#[structopt(flatten)]
tor: Tor,
},
/// Discover and list sellers (i.e. ASB providers)
ListSellers {
@ -562,11 +555,10 @@ impl Bitcoin {
#[derive(structopt::StructOpt, Debug)]
pub struct Tor {
#[structopt(
long = "tor-socks5-port",
help = "Your local Tor socks5 proxy port",
default_value = DEFAULT_TOR_SOCKS5_PORT
long = "enable-tor",
help = "Bootstrap a tor client and use it for all libp2p connections"
)]
pub tor_socks5_port: u16,
pub enable_tor: bool,
}
#[derive(structopt::StructOpt, Debug)]

View file

@ -2,6 +2,7 @@ use crate::network::quote::BidQuote;
use crate::network::rendezvous::XmrBtcNamespace;
use crate::network::{quote, swarm};
use anyhow::{Context, Result};
use arti_client::TorClient;
use futures::StreamExt;
use libp2p::multiaddr::Protocol;
use libp2p::request_response;
@ -12,7 +13,9 @@ use serde::Serialize;
use serde_with::{serde_as, DisplayFromStr};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tor_rtcompat::tokio::TokioRustlsRuntime;
use typeshare::typeshare;
/// Returns sorted list of sellers, with [Online](Status::Online) listed first.
@ -25,7 +28,7 @@ pub async fn list_sellers(
rendezvous_node_peer_id: PeerId,
rendezvous_node_addr: Multiaddr,
namespace: XmrBtcNamespace,
tor_socks5_port: u16,
maybe_tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
identity: identity::Keypair,
) -> Result<Vec<Seller>> {
let behaviour = Behaviour {
@ -33,7 +36,7 @@ pub async fn list_sellers(
quote: quote::cli(),
ping: ping::Behaviour::new(ping::Config::new().with_timeout(Duration::from_secs(60))),
};
let mut swarm = swarm::cli(identity, tor_socks5_port, behaviour).await?;
let mut swarm = swarm::cli(identity, maybe_tor_client, behaviour).await?;
swarm.add_peer_address(rendezvous_node_peer_id, rendezvous_node_addr.clone());

30
swap/src/cli/tor.rs Normal file
View file

@ -0,0 +1,30 @@
use std::path::Path;
use std::sync::Arc;
use arti_client::{config::TorClientConfigBuilder, Error, TorClient};
use tor_rtcompat::tokio::TokioRustlsRuntime;
pub async fn init_tor_client(data_dir: &Path) -> Result<Arc<TorClient<TokioRustlsRuntime>>, Error> {
// We store the Tor state in the data directory
let data_dir = data_dir.join("tor");
let state_dir = data_dir.join("state");
let cache_dir = data_dir.join("cache");
// The client configuration describes how to connect to the Tor network,
// and what directories to use for storing persistent state.
let config = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
.build()
.expect("We initialized the Tor client all required attributes");
// Start the Arti client, and let it bootstrap a connection to the Tor network.
// (This takes a while to gather the necessary directory information.
// It uses cached information when possible.)
let runtime = TokioRustlsRuntime::current().expect("We are always running with tokio");
let tor_client = TorClient::with_runtime(runtime)
.config(config)
.create_bootstrapped()
.await?;
Ok(Arc::new(tor_client))
}

View file

@ -1,11 +1,15 @@
use crate::network::tor_transport::TorDialOnlyTransport;
use std::sync::Arc;
use crate::network::transport::authenticate_and_multiplex;
use anyhow::Result;
use arti_client::TorClient;
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::transport::{Boxed, OptionalTransport};
use libp2p::dns;
use libp2p::tcp;
use libp2p::{identity, PeerId, Transport};
use libp2p_community_tor::{AddressConversion, TorTransport};
use tor_rtcompat::tokio::TokioRustlsRuntime;
/// Creates the libp2p transport for the swap CLI.
///
@ -17,13 +21,16 @@ use libp2p::{identity, PeerId, Transport};
/// TCP transport.
pub fn new(
identity: &identity::Keypair,
maybe_tor_socks5_port: Option<u16>,
maybe_tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
) -> Result<Boxed<(PeerId, StreamMuxerBox)>> {
let tcp = tcp::tokio::Transport::new(tcp::Config::new().nodelay(true));
let tcp_with_dns = dns::tokio::Transport::system(tcp)?;
let maybe_tor_transport = match maybe_tor_socks5_port {
Some(port) => OptionalTransport::some(TorDialOnlyTransport::new(port)),
let maybe_tor_transport: OptionalTransport<TorTransport> = match maybe_tor_client {
Some(client) => OptionalTransport::some(libp2p_community_tor::TorTransport::from_client(
client,
AddressConversion::IpAndDns,
)),
None => OptionalTransport::none(),
};

View file

@ -72,7 +72,7 @@ pub fn init(
.with_writer(TauriWriter::new(tauri_handle))
.with_ansi(false)
.with_timer(UtcTime::rfc_3339())
.with_target(false)
.with_target(true)
.json()
.with_filter(env_filter(level_filter)?);
@ -101,6 +101,12 @@ fn env_filter(level_filter: LevelFilter) -> Result<EnvFilter> {
Ok(EnvFilter::from_default_env()
.add_directive(Directive::from_str(&format!("asb={}", &level_filter))?)
.add_directive(Directive::from_str(&format!("swap={}", &level_filter))?)
.add_directive(Directive::from_str(&format!("arti={}", &level_filter))?)
.add_directive(Directive::from_str(&format!("libp2p={}", &level_filter))?)
.add_directive(Directive::from_str(&format!(
"libp2p_community_tor={}",
&level_filter
))?)
.add_directive(Directive::from_str(&format!(
"unstoppableswap-gui-rs={}",
&level_filter

View file

@ -29,7 +29,7 @@ impl SqliteDatabase {
let path_str = format!("sqlite:{}", path.as_ref().display());
let mut options = SqliteConnectOptions::from_str(&path_str)?.read_only(read_only);
let options = SqliteConnectOptions::from_str(&path_str)?.read_only(read_only);
let options = options.disable_statement_logging();
let pool = SqlitePool::connect_with(options.to_owned()).await?;
@ -59,8 +59,6 @@ impl SqliteDatabase {
#[async_trait]
impl Database for SqliteDatabase {
async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let peer_id = peer_id.to_string();
@ -74,15 +72,13 @@ impl Database for SqliteDatabase {
swap_id,
peer_id
)
.execute(&mut conn)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
@ -93,7 +89,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_one(&mut conn)
.fetch_one(&self.pool)
.await?;
let peer_id = PeerId::from_str(&row.peer_id)?;
@ -101,8 +97,6 @@ impl Database for SqliteDatabase {
}
async fn insert_monero_address(&self, swap_id: Uuid, address: Address) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let address = address.to_string();
@ -116,15 +110,13 @@ impl Database for SqliteDatabase {
swap_id,
address
)
.execute(&mut conn)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_monero_address(&self, swap_id: Uuid) -> Result<Address> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
@ -135,7 +127,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_one(&mut conn)
.fetch_one(&self.pool)
.await?;
let address = row.address.parse()?;
@ -144,10 +136,8 @@ impl Database for SqliteDatabase {
}
async fn get_monero_addresses(&self) -> Result<Vec<monero::Address>> {
let mut conn = self.pool.acquire().await?;
let rows = sqlx::query!("SELECT DISTINCT address FROM monero_addresses")
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
let addresses = rows
@ -159,8 +149,6 @@ impl Database for SqliteDatabase {
}
async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let peer_id = peer_id.to_string();
let address = address.to_string();
@ -174,15 +162,13 @@ impl Database for SqliteDatabase {
peer_id,
address
)
.execute(&mut conn)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>> {
let mut conn = self.pool.acquire().await?;
let peer_id = peer_id.to_string();
let rows = sqlx::query!(
@ -193,7 +179,7 @@ impl Database for SqliteDatabase {
"#,
peer_id,
)
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
let addresses = rows
@ -208,7 +194,6 @@ impl Database for SqliteDatabase {
}
async fn get_swap_start_date(&self, swap_id: Uuid) -> Result<String> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
@ -219,7 +204,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_one(&mut conn)
.fetch_one(&self.pool)
.await?;
row.start_date
@ -227,7 +212,6 @@ impl Database for SqliteDatabase {
}
async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let entered_at = OffsetDateTime::now_utc();
let swap = serde_json::to_string(&Swap::from(state))?;
@ -246,7 +230,7 @@ impl Database for SqliteDatabase {
entered_at,
swap
)
.execute(&mut conn)
.execute(&self.pool)
.await?;
// Emit event to Tauri, the frontend will then send another request to get the latest state
@ -257,7 +241,6 @@ impl Database for SqliteDatabase {
}
async fn get_state(&self, swap_id: Uuid) -> Result<State> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
r#"
@ -270,7 +253,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
let row = row
@ -282,34 +265,38 @@ impl Database for SqliteDatabase {
}
async fn all(&self) -> Result<Vec<(Uuid, State)>> {
let mut conn = self.pool.acquire().await?;
let rows = sqlx::query!(
r#"
SELECT swap_id, state
FROM (
SELECT max(id), swap_id, state
FROM swap_states
GROUP BY swap_id
)
SELECT swap_id, state
FROM (
SELECT max(id), swap_id, state
FROM swap_states
GROUP BY swap_id
)
"#
)
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
let result = rows
.iter()
.filter_map(|row| {
let swap_id = match Uuid::from_str(&row.swap_id) {
let (Some(swap_id), Some(state)) = (&row.swap_id, &row.state) else {
tracing::error!("Row didn't contain state or swap_id when it should have");
return None;
};
let swap_id = match Uuid::from_str(swap_id) {
Ok(id) => id,
Err(e) => {
tracing::error!(swap_id = %row.swap_id, error = ?e, "Failed to parse UUID");
tracing::error!(%swap_id, error = ?e, "Failed to parse UUID");
return None;
}
};
let state = match serde_json::from_str::<Swap>(&row.state) {
let state = match serde_json::from_str::<Swap>(state) {
Ok(a) => State::from(a),
Err(e) => {
tracing::error!(swap_id = %swap_id, error = ?e, "Failed to deserialize state");
tracing::error!(%swap_id, error = ?e, "Failed to deserialize state");
return None;
}
};
@ -322,7 +309,6 @@ impl Database for SqliteDatabase {
}
async fn get_states(&self, swap_id: Uuid) -> Result<Vec<State>> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
// TODO: We should use query! instead of query here to allow for at-compile-time validation
@ -335,7 +321,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
let result = rows
@ -359,7 +345,6 @@ impl Database for SqliteDatabase {
swap_id: Uuid,
proof: TransferProof,
) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let proof = serde_json::to_string(&proof)?;
@ -373,14 +358,13 @@ impl Database for SqliteDatabase {
swap_id,
proof
)
.execute(&mut conn)
.execute(&self.pool)
.await?;
Ok(())
}
async fn get_buffered_transfer_proof(&self, swap_id: Uuid) -> Result<Option<TransferProof>> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
@ -391,7 +375,7 @@ impl Database for SqliteDatabase {
"#,
swap_id
)
.fetch_all(&mut conn)
.fetch_all(&self.pool)
.await?;
if row.is_empty() {

View file

@ -27,7 +27,7 @@ use crate::cli::api::tauri_bindings::{
// See: https://www.moneroworld.com/#nodes, https://monero.fail
// We don't need any testnet nodes because we don't support testnet at all
const MONERO_DAEMONS: Lazy<[MoneroDaemon; 16]> = Lazy::new(|| {
static MONERO_DAEMONS: Lazy<[MoneroDaemon; 16]> = Lazy::new(|| {
[
MoneroDaemon::new("xmr-node.cakewallet.com", 18081, Network::Mainnet),
MoneroDaemon::new("nodex.monerujo.io", 18081, Network::Mainnet),
@ -169,8 +169,9 @@ async fn choose_monero_daemon(network: Network) -> Result<MoneroDaemon, Error> {
.build()?;
// We only want to check for daemons that match the specified network
let daemons = &*MONERO_DAEMONS;
let network_matching_daemons = daemons.iter().filter(|daemon| daemon.network == network);
let network_matching_daemons = MONERO_DAEMONS
.iter()
.filter(|daemon| daemon.network == network);
for daemon in network_matching_daemons {
match daemon.is_available(&client).await {

View file

@ -7,7 +7,6 @@ pub mod redial;
pub mod rendezvous;
pub mod swap_setup;
pub mod swarm;
pub mod tor_transport;
pub mod transfer_proof;
pub mod transport;

View file

@ -2,13 +2,16 @@ use crate::asb::{LatestRate, RendezvousNode};
use crate::libp2p_ext::MultiAddrExt;
use crate::network::rendezvous::XmrBtcNamespace;
use crate::seed::Seed;
use crate::{asb, bitcoin, cli, env, tor};
use crate::{asb, bitcoin, cli, env};
use anyhow::Result;
use arti_client::TorClient;
use libp2p::swarm::NetworkBehaviour;
use libp2p::SwarmBuilder;
use libp2p::{identity, Multiaddr, Swarm};
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;
use tor_rtcompat::tokio::TokioRustlsRuntime;
#[allow(clippy::too_many_arguments)]
pub fn asb<LR>(
@ -61,18 +64,13 @@ where
pub async fn cli<T>(
identity: identity::Keypair,
tor_socks5_port: u16,
maybe_tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
behaviour: T,
) -> Result<Swarm<T>>
where
T: NetworkBehaviour,
{
let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await {
Ok(()) => Some(tor_socks5_port),
Err(_) => None,
};
let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?;
let transport = cli::transport::new(&identity, maybe_tor_client)?;
let swarm = SwarmBuilder::with_existing_identity(identity)
.with_tokio()

View file

@ -1,258 +0,0 @@
use anyhow::Result;
use data_encoding::BASE32;
use futures::future::{BoxFuture, Ready};
use libp2p::core::multiaddr::{Multiaddr, Protocol};
use libp2p::core::transport::{ListenerId, TransportError};
use libp2p::core::Transport;
use libp2p::tcp::tokio::TcpStream;
use std::borrow::Cow;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::{fmt, io};
use tokio_socks::tcp::Socks5Stream;
/// A [`Transport`] that can dial onion addresses through a running Tor daemon.
#[derive(Clone)]
pub struct TorDialOnlyTransport {
socks_port: u16,
}
impl TorDialOnlyTransport {
pub fn new(socks_port: u16) -> Self {
Self { socks_port }
}
}
impl Transport for TorDialOnlyTransport {
type Output = TcpStream;
type Error = io::Error;
type ListenerUpgrade = Ready<Result<Self::Output, Self::Error>>;
type Dial = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn listen_on(
&mut self,
_id: ListenerId,
addr: Multiaddr,
) -> Result<(), TransportError<Self::Error>> {
Err(TransportError::MultiaddrNotSupported(addr))
}
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
let address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&addr))?;
if address.is_certainly_not_reachable_via_tor_daemon() {
return Err(TransportError::MultiaddrNotSupported(addr));
}
let socks_port = self.socks_port;
Ok(Box::pin(async move {
tracing::debug!(address = %addr, "Establishing connection through Tor proxy");
let stream =
Socks5Stream::connect((Ipv4Addr::LOCALHOST, socks_port), address.to_string())
.await
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
tracing::debug!("Connection through Tor established");
Ok(TcpStream(stream.into_inner()))
}))
}
fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
None
}
fn dial_as_listener(
&mut self,
addr: Multiaddr,
) -> Result<Self::Dial, TransportError<Self::Error>> {
let address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&addr))?;
if address.is_certainly_not_reachable_via_tor_daemon() {
return Err(TransportError::MultiaddrNotSupported(addr));
}
let socks_port = self.socks_port;
Ok(Box::pin(async move {
tracing::debug!(address = %addr, "Establishing connection through Tor proxy");
let stream =
Socks5Stream::connect((Ipv4Addr::LOCALHOST, socks_port), address.to_string())
.await
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
tracing::debug!("Connection through Tor established");
Ok(TcpStream(stream.into_inner()))
}))
}
fn remove_listener(&mut self, _id: ListenerId) -> bool {
// TODO(Libp2p Migration): What do we need to do here?
// I believe nothing because we are not using the transport to listen.
false
}
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<libp2p::core::transport::TransportEvent<Self::ListenerUpgrade, Self::Error>>
{
// TODO(Libp2p Migration): What do we need to do here?
// See: https://github.com/libp2p/rust-libp2p/pull/2652
// I believe we do not need to do anything here because we are not using the transport to listen.
// But we need to verify this before merging.
std::task::Poll::Pending
}
}
/// Represents an address that is _compatible_ with Tor, i.e. can be resolved by
/// the Tor daemon.
#[derive(Debug)]
enum TorCompatibleAddress {
Onion3 { host: String, port: u16 },
Dns { address: String, port: u16 },
Ip4 { address: Ipv4Addr, port: u16 },
Ip6 { address: Ipv6Addr, port: u16 },
}
impl TorCompatibleAddress {
/// Constructs a new [`TorCompatibleAddress`] from a [`Multiaddr`].
fn from_multiaddr(multi: Cow<'_, Multiaddr>) -> Result<Self, TransportError<io::Error>> {
match multi.iter().collect::<Vec<_>>().as_slice() {
[Protocol::Onion3(onion), ..] => Ok(TorCompatibleAddress::Onion3 {
host: BASE32.encode(onion.hash()).to_lowercase(),
port: onion.port(),
}),
[Protocol::Ip4(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => {
Ok(TorCompatibleAddress::Ip4 {
address: *address,
port: *port,
})
}
[Protocol::Dns(address) | Protocol::Dns4(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => {
Ok(TorCompatibleAddress::Dns {
address: format!("{}", address),
port: *port,
})
}
[Protocol::Ip6(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => {
Ok(TorCompatibleAddress::Ip6 {
address: *address,
port: *port,
})
}
_ => Err(TransportError::MultiaddrNotSupported(multi.into_owned())),
}
}
/// Checks if the address is reachable via the Tor daemon.
///
/// The Tor daemon can dial onion addresses, resolve DNS names and dial
/// IP4/IP6 addresses reachable via the public Internet.
/// We can't guarantee that an address is reachable via the Internet but we
/// can say that some addresses are almost certainly not reachable, for
/// example, loopback addresses.
fn is_certainly_not_reachable_via_tor_daemon(&self) -> bool {
match self {
TorCompatibleAddress::Onion3 { .. } => false,
TorCompatibleAddress::Dns { address, .. } => address == "localhost",
TorCompatibleAddress::Ip4 { address, .. } => address.is_loopback(),
TorCompatibleAddress::Ip6 { address, .. } => address.is_loopback(),
}
}
}
impl fmt::Display for TorCompatibleAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TorCompatibleAddress::Onion3 { host, port } => write!(f, "{}.onion:{}", host, port),
TorCompatibleAddress::Dns { address, port } => write!(f, "{}:{}", address, port),
TorCompatibleAddress::Ip4 { address, port } => write!(f, "{}:{}", address, port),
TorCompatibleAddress::Ip6 { address, port } => write!(f, "{}:{}", address, port),
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
#[test]
fn test_tor_address_string() {
let address = tor_compatible_address_from_str("/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9");
assert!(!address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(
address.to_string(),
"oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024"
);
}
#[test]
fn tcp_to_address_string_should_be_some() {
let address = tor_compatible_address_from_str("/ip4/127.0.0.1/tcp/7777");
assert!(address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(address.to_string(), "127.0.0.1:7777");
}
#[test]
fn ip6_to_address_string_should_be_some() {
let address =
tor_compatible_address_from_str("/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777");
assert!(!address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(
address.to_string(),
"2001:db8:85a3:8d3:1319:8a2e:370:7348:7777"
);
}
#[test]
fn udp_to_address_string_should_be_some() {
let address = tor_compatible_address_from_str("/ip4/127.0.0.1/udp/7777");
assert!(address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(address.to_string(), "127.0.0.1:7777");
}
#[test]
fn ws_to_address_string_should_be_some() {
let address = tor_compatible_address_from_str("/ip4/127.0.0.1/tcp/7777/ws");
assert!(address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(address.to_string(), "127.0.0.1:7777");
}
#[test]
fn dns4_to_address_string_should_be_some() {
let address = tor_compatible_address_from_str("/dns4/randomdomain.com/tcp/7777");
assert!(!address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(address.to_string(), "randomdomain.com:7777");
}
#[test]
fn dns_to_address_string_should_be_some() {
let address = tor_compatible_address_from_str("/dns/randomdomain.com/tcp/7777");
assert!(!address.is_certainly_not_reachable_via_tor_daemon());
assert_eq!(address.to_string(), "randomdomain.com:7777");
}
#[test]
fn dnsaddr_to_address_string_should_be_error() {
let address = "/dnsaddr/randomdomain.com";
let _ =
TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())).unwrap_err();
}
fn tor_compatible_address_from_str(str: &str) -> TorCompatibleAddress {
TorCompatibleAddress::from_multiaddr(Cow::Owned(str.parse().unwrap())).unwrap()
}
}

View file

@ -494,8 +494,6 @@ impl BobParams {
swap_id: Uuid,
db: Arc<dyn Database + Send + Sync>,
) -> Result<(cli::EventLoop, cli::EventLoopHandle)> {
let tor_socks5_port = get_port()
.expect("We don't care about Tor in the tests so we get a free port to disable it.");
let identity = self.seed.derive_libp2p_identity();
let behaviour = cli::Behaviour::new(
@ -504,7 +502,7 @@ impl BobParams {
self.bitcoin_wallet.clone(),
(identity.clone(), XmrBtcNamespace::Testnet),
);
let mut swarm = swarm::cli(identity.clone(), tor_socks5_port, behaviour).await?;
let mut swarm = swarm::cli(identity.clone(), None, behaviour).await?;
swarm.add_peer_address(self.alice_peer_id, self.alice_address.clone());
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, db.clone())