mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-20 16:06:00 -04:00
Merge branch 'master' into feature/asb-tor
This commit is contained in:
commit
fb4a0e241f
8
.github/workflows/build-release-binaries.yml
vendored
8
.github/workflows/build-release-binaries.yml
vendored
@ -45,16 +45,16 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout tagged commit
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.release.target_commitish }}
|
||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.63
|
||||
toolchain: "1.70"
|
||||
targets: armv7-unknown-linux-gnueabihf
|
||||
|
||||
- name: Build ${{ matrix.target }} ${{ matrix.bin }} release binary
|
||||
@ -69,7 +69,7 @@ jobs:
|
||||
run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help
|
||||
|
||||
# Remove once python 3 is the default
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@ -4,8 +4,6 @@ on:
|
||||
pull_request: # Need to run on pull-requests, otherwise PRs from forks don't run
|
||||
push:
|
||||
branches:
|
||||
- "staging" # Bors uses this branch
|
||||
- "trying" # Bors uses this branch
|
||||
- "master" # Always build head of master for the badge in the README
|
||||
|
||||
jobs:
|
||||
@ -13,14 +11,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.67
|
||||
toolchain: "1.70"
|
||||
components: clippy,rustfmt
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- name: Check formatting
|
||||
uses: dprint/check@v2.2
|
||||
@ -37,9 +35,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- name: Build swap
|
||||
run: cargo build --bin swap
|
||||
@ -51,12 +49,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- name: Install sqlx-cli
|
||||
run: cargo install sqlx-cli
|
||||
run: cargo install sqlx-cli --locked
|
||||
|
||||
- name: Run sqlite_dev_setup.sh script
|
||||
run: |
|
||||
@ -78,13 +76,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.67
|
||||
toolchain: "1.70"
|
||||
targets: armv7-unknown-linux-gnueabihf
|
||||
|
||||
- name: Build binary
|
||||
@ -100,13 +98,13 @@ jobs:
|
||||
run: cross build -p swap --target ${{ matrix.target }}
|
||||
|
||||
- name: Upload swap binary
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: swap-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/debug/swap
|
||||
|
||||
- name: Upload asb binary
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: asb-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/debug/asb
|
||||
@ -118,9 +116,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- name: Build tests
|
||||
run: cargo build --tests --workspace --all-features
|
||||
@ -155,9 +153,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.6.0
|
||||
- uses: Swatinem/rust-cache@v2.7.1
|
||||
|
||||
- name: Run test ${{ matrix.test_name }}
|
||||
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture
|
||||
|
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.3
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Extract version from branch name
|
||||
id: extract-version
|
||||
|
8
.github/workflows/draft-new-release.yml
vendored
8
.github/workflows/draft-new-release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
name: "Draft a new release"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.3
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||
|
||||
@ -41,8 +41,12 @@ jobs:
|
||||
|
||||
- name: Commit changelog and manifest files
|
||||
id: make-commit
|
||||
env:
|
||||
DPRINT_VERSION: 0.39.1
|
||||
RUST_TOOLCHAIN: 1.70
|
||||
run: |
|
||||
curl -fsSL https://dprint.dev/install.sh | sh
|
||||
rustup component add rustfmt --toolchain "$RUST_TOOLCHAIN-x86_64-unknown-linux-gnu"
|
||||
curl -fsSL https://dprint.dev/install.sh | sh -s $DPRINT_VERSION
|
||||
/home/runner/.dprint/bin/dprint fmt
|
||||
|
||||
git add CHANGELOG.md Cargo.lock swap/Cargo.toml
|
||||
|
2
.github/workflows/preview-release.yml
vendored
2
.github/workflows/preview-release.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
name: Create preview release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.3
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Delete 'preview' release
|
||||
uses: larryjoelane/delete-release-action@v1.0.24
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Minimum Supported Rust Version (MSRV) bumped to 1.70
|
||||
|
||||
## [0.12.3] - 2023-09-20
|
||||
|
||||
- Swap: If no Monero daemon is manually specified, we will automatically choose one from a list of public daemons by connecting to each and checking their availability.
|
||||
|
||||
## [0.12.2] - 2023-08-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Minimum Supported Rust Version (MSRV) bumped to 1.67
|
||||
@ -343,7 +351,9 @@ It is possible to migrate critical data from the old db to the sqlite but there
|
||||
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
|
||||
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
|
||||
|
||||
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.1...HEAD
|
||||
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.3...HEAD
|
||||
[0.12.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.2...0.12.3
|
||||
[0.12.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.1...0.12.2
|
||||
[0.12.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.0...0.12.1
|
||||
[0.12.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.11.0...0.12.0
|
||||
[0.11.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...0.11.0
|
||||
|
980
Cargo.lock
generated
980
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,7 @@ Please have a look at the [contribution guidelines](./CONTRIBUTING.md).
|
||||
## Rust Version Support
|
||||
|
||||
Please note that only the latest stable Rust toolchain is supported.
|
||||
All stable toolchains since 1.67 _should_ work.
|
||||
All stable toolchains since 1.70 _should_ work.
|
||||
|
||||
## Contact
|
||||
|
||||
|
25
bors.toml
25
bors.toml
@ -1,25 +0,0 @@
|
||||
status = [
|
||||
"static_analysis",
|
||||
"bdk_test",
|
||||
"sqlx_test",
|
||||
"build (x86_64-unknown-linux-gnu, ubuntu-latest)",
|
||||
"build (armv7-unknown-linux-gnueabihf, ubuntu-latest)",
|
||||
"build (x86_64-apple-darwin, macos-latest)",
|
||||
"build (x86_64-pc-windows-msvc, windows-latest)",
|
||||
"test (ubuntu-latest)",
|
||||
"test (macos-latest)",
|
||||
"docker_tests (happy_path)",
|
||||
"docker_tests (happy_path_restart_bob_after_xmr_locked)",
|
||||
"docker_tests (happy_path_restart_alice_after_xmr_locked)",
|
||||
"docker_tests (happy_path_restart_bob_before_xmr_locked)",
|
||||
"docker_tests (alice_and_bob_refund_using_cancel_and_refund_command)",
|
||||
"docker_tests (alice_and_bob_refund_using_cancel_then_refund_command)",
|
||||
"docker_tests (alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired)",
|
||||
"docker_tests (punish)",
|
||||
"docker_tests (alice_punishes_after_restart_bob_dead)",
|
||||
"docker_tests (alice_manually_punishes_after_bob_dead)",
|
||||
"docker_tests (alice_refunds_after_restart_bob_refunded)",
|
||||
"docker_tests (ensure_same_swap_id)",
|
||||
"docker_tests (concurrent_bobs_before_xmr_lock_proof_sent)",
|
||||
"docker_tests (alice_manually_redeems_after_enc_sig_learned)"
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.67" # also update this in the readme, changelog, and github actions
|
||||
channel = "1.70" # also update this in the readme, changelog, and github actions
|
||||
components = ["clippy"]
|
||||
targets = ["armv7-unknown-linux-gnueabihf"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swap"
|
||||
version = "0.12.1"
|
||||
version = "0.12.3"
|
||||
authors = [ "The COMIT guys <hello@comit.network>" ]
|
||||
edition = "2021"
|
||||
description = "XMR/BTC trustless atomic swaps."
|
||||
@ -19,11 +19,11 @@ bdk = "0.28"
|
||||
big-bytes = "1"
|
||||
bitcoin = { version = "0.29", features = [ "rand", "serde" ] }
|
||||
bmrng = "0.5"
|
||||
comfy-table = "6.1"
|
||||
comfy-table = "7.1"
|
||||
config = { version = "0.13", default-features = false, features = [ "toml" ] }
|
||||
conquer-once = "0.4"
|
||||
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
|
||||
data-encoding = "2.4"
|
||||
data-encoding = "2.5"
|
||||
dialoguer = "0.10"
|
||||
directories-next = "2"
|
||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde", "adaptor" ] }
|
||||
@ -34,9 +34,9 @@ itertools = "0.10"
|
||||
libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] }
|
||||
monero = { version = "0.12", features = [ "serde_support" ] }
|
||||
monero-rpc = { path = "../monero-rpc" }
|
||||
pem = "1.1"
|
||||
pem = "3.0"
|
||||
proptest = "1"
|
||||
qrcode = "0.12"
|
||||
qrcode = "0.13"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
reqwest = { version = "0.11", features = [ "rustls-tls", "stream", "socks" ], default-features = false }
|
||||
@ -50,21 +50,21 @@ sha2 = "0.10"
|
||||
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde", "secp256k1", "alloc" ] }
|
||||
sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.24", features = [ "derive" ] }
|
||||
strum = { version = "0.25", features = [ "derive" ] }
|
||||
thiserror = "1"
|
||||
time = "0.3"
|
||||
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
|
||||
tokio-socks = "0.5"
|
||||
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
|
||||
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
|
||||
toml = "0.5"
|
||||
toml = "0.7"
|
||||
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] }
|
||||
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", "env-filter", "time", "tracing-log", "json" ] }
|
||||
url = { version = "2", features = [ "serde" ] }
|
||||
uuid = { version = "1.4", features = [ "serde", "v4" ] }
|
||||
uuid = { version = "1.6", features = [ "serde", "v4" ] }
|
||||
void = "1"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
@ -76,7 +76,8 @@ zip = "0.5"
|
||||
[dev-dependencies]
|
||||
bitcoin-harness = "0.2.2"
|
||||
get-port = "3"
|
||||
hyper = "0.14"
|
||||
hyper = "1.0"
|
||||
mockito = "1.2.0"
|
||||
monero-harness = { path = "../monero-harness" }
|
||||
port_check = "0.1"
|
||||
proptest = "1"
|
||||
|
@ -522,7 +522,7 @@ async fn init_bitcoin_wallet(
|
||||
|
||||
async fn init_monero_wallet(
|
||||
data_dir: PathBuf,
|
||||
monero_daemon_address: String,
|
||||
monero_daemon_address: Option<String>,
|
||||
env_config: Config,
|
||||
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
||||
let network = env_config.monero_network;
|
||||
@ -532,7 +532,7 @@ async fn init_monero_wallet(
|
||||
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
||||
|
||||
let monero_wallet_rpc_process = monero_wallet_rpc
|
||||
.run(network, monero_daemon_address.as_str())
|
||||
.run(network, monero_daemon_address)
|
||||
.await?;
|
||||
|
||||
let monero_wallet = monero::Wallet::open_or_create(
|
||||
|
@ -767,9 +767,10 @@ impl Client {
|
||||
self.blockchain.get_tx(txid)
|
||||
}
|
||||
|
||||
fn update_state(&mut self) -> Result<()> {
|
||||
fn update_state(&mut self, force_sync: bool) -> Result<()> {
|
||||
let now = Instant::now();
|
||||
if now < self.last_sync + self.sync_interval {
|
||||
|
||||
if !force_sync && now < self.last_sync + self.sync_interval {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -789,9 +790,14 @@ impl Client {
|
||||
|
||||
if !self.script_history.contains_key(&script) {
|
||||
self.script_history.insert(script.clone(), vec![]);
|
||||
}
|
||||
|
||||
self.update_state()?;
|
||||
// When we first subscribe to a script we want to immediately fetch its status
|
||||
// Otherwise we would have to wait for the next sync interval, which can take a minute
|
||||
// This would result in potentially inaccurate status updates until that next sync interval is hit
|
||||
self.update_state(true)?;
|
||||
} else {
|
||||
self.update_state(false)?;
|
||||
}
|
||||
|
||||
let history = self.script_history.entry(script).or_default();
|
||||
|
||||
|
@ -14,10 +14,6 @@ use structopt::{clap, StructOpt};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
// See: https://moneroworld.com/
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081";
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081";
|
||||
|
||||
// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700";
|
||||
// See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc
|
||||
@ -80,11 +76,11 @@ where
|
||||
} => {
|
||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||
bitcoin.apply_defaults(is_testnet)?;
|
||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
||||
let monero_receive_address =
|
||||
validate_monero_address(monero_receive_address, is_testnet)?;
|
||||
let bitcoin_change_address =
|
||||
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
|
||||
let monero_daemon_address = monero.monero_daemon_address;
|
||||
|
||||
Arguments {
|
||||
env_config: env_config_from(is_testnet),
|
||||
@ -167,7 +163,7 @@ where
|
||||
} => {
|
||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||
bitcoin.apply_defaults(is_testnet)?;
|
||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
||||
let monero_daemon_address = monero.monero_daemon_address;
|
||||
|
||||
Arguments {
|
||||
env_config: env_config_from(is_testnet),
|
||||
@ -254,7 +250,7 @@ pub enum Command {
|
||||
bitcoin_target_block: usize,
|
||||
bitcoin_change_address: bitcoin::Address,
|
||||
monero_receive_address: monero::Address,
|
||||
monero_daemon_address: String,
|
||||
monero_daemon_address: Option<String>,
|
||||
tor_socks5_port: u16,
|
||||
namespace: XmrBtcNamespace,
|
||||
},
|
||||
@ -274,7 +270,7 @@ pub enum Command {
|
||||
swap_id: Uuid,
|
||||
bitcoin_electrum_rpc_url: Url,
|
||||
bitcoin_target_block: usize,
|
||||
monero_daemon_address: String,
|
||||
monero_daemon_address: Option<String>,
|
||||
tor_socks5_port: u16,
|
||||
namespace: XmrBtcNamespace,
|
||||
},
|
||||
@ -436,23 +432,11 @@ enum RawCommand {
|
||||
struct Monero {
|
||||
#[structopt(
|
||||
long = "monero-daemon-address",
|
||||
help = "Specify to connect to a monero daemon of your choice: <host>:<port>"
|
||||
help = "Specify to connect to a monero daemon of your choice: <host>:<port>. If none is specified, we will connect to a public node."
|
||||
)]
|
||||
monero_daemon_address: Option<String>,
|
||||
}
|
||||
|
||||
impl Monero {
|
||||
fn apply_defaults(self, testnet: bool) -> String {
|
||||
if let Some(address) = self.monero_daemon_address {
|
||||
address
|
||||
} else if testnet {
|
||||
DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string()
|
||||
} else {
|
||||
DEFAULT_MONERO_DAEMON_ADDRESS.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
struct Bitcoin {
|
||||
#[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")]
|
||||
@ -1174,7 +1158,7 @@ mod tests {
|
||||
bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(),
|
||||
monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS)
|
||||
.unwrap(),
|
||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
|
||||
monero_daemon_address: None,
|
||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||
namespace: XmrBtcNamespace::Testnet,
|
||||
},
|
||||
@ -1194,7 +1178,7 @@ mod tests {
|
||||
bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(),
|
||||
monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS)
|
||||
.unwrap(),
|
||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
|
||||
monero_daemon_address: None,
|
||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||
namespace: XmrBtcNamespace::Mainnet,
|
||||
},
|
||||
@ -1212,7 +1196,7 @@ mod tests {
|
||||
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)
|
||||
.unwrap(),
|
||||
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET,
|
||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
|
||||
monero_daemon_address: None,
|
||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||
namespace: XmrBtcNamespace::Testnet,
|
||||
},
|
||||
@ -1229,7 +1213,7 @@ mod tests {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
|
||||
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
|
||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
|
||||
monero_daemon_address: None,
|
||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||
namespace: XmrBtcNamespace::Mainnet,
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ pub struct Regtest;
|
||||
impl GetConfig for Mainnet {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
||||
bitcoin_lock_mempool_timeout: 10.std_minutes(),
|
||||
bitcoin_lock_confirmed_timeout: 2.std_hours(),
|
||||
bitcoin_finality_confirmations: 1,
|
||||
bitcoin_avg_block_time: 10.std_minutes(),
|
||||
@ -63,7 +63,7 @@ impl GetConfig for Mainnet {
|
||||
impl GetConfig for Testnet {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
||||
bitcoin_lock_mempool_timeout: 10.std_minutes(),
|
||||
bitcoin_lock_confirmed_timeout: 1.std_hours(),
|
||||
bitcoin_finality_confirmations: 1,
|
||||
bitcoin_avg_block_time: 10.std_minutes(),
|
||||
|
@ -174,11 +174,6 @@ impl Wallet {
|
||||
pub async fn transfer(&self, request: TransferRequest) -> Result<TransferProof> {
|
||||
let inner = self.inner.lock().await;
|
||||
|
||||
inner
|
||||
.open_wallet(self.name.clone())
|
||||
.await
|
||||
.with_context(|| format!("Failed to open wallet {}", self.name))?;
|
||||
|
||||
let TransferRequest {
|
||||
public_spend_key,
|
||||
public_view_key,
|
||||
|
@ -1,19 +1,45 @@
|
||||
use ::monero::Network;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use big_bytes::BigByte;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use monero_rpc::wallet::{Client, MoneroWalletRpc as _};
|
||||
use reqwest::header::CONTENT_LENGTH;
|
||||
use reqwest::Url;
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
use tokio::fs::{remove_file, OpenOptions};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
// 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: [MoneroDaemon; 17] = [
|
||||
MoneroDaemon::new("xmr-node.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("nodex.monerujo.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.moneroworld.com", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("nodes.hashvault.pro", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("p2pmd.xmrvsbeast.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-usa-east.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-uk.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.community.rino.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("testingjohnross.com", 20031, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr.litepay.ch", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.trocador.app", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("stagenet.xmr-tw.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 38089, Network::Stagenet),
|
||||
MoneroDaemon::new("singapore.node.xmr.pm", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("xmr-lux.boldsuck.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("stagenet.community.rino.io", 38081, Network::Stagenet),
|
||||
];
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
compile_error!("unsupported operating system");
|
||||
|
||||
@ -50,6 +76,91 @@ pub struct WalletRpcProcess {
|
||||
port: u16,
|
||||
}
|
||||
|
||||
struct MoneroDaemon {
|
||||
address: &'static str,
|
||||
port: u16,
|
||||
network: Network,
|
||||
}
|
||||
|
||||
impl MoneroDaemon {
|
||||
const fn new(address: &'static str, port: u16, network: Network) -> Self {
|
||||
Self {
|
||||
address,
|
||||
port,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the Monero daemon is available by sending a request to its `get_info` endpoint.
|
||||
async fn is_available(&self, client: &reqwest::Client) -> Result<bool, Error> {
|
||||
let url = format!("http://{}:{}/get_info", self.address, self.port);
|
||||
let res = client
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to send request to get_info endpoint")?;
|
||||
|
||||
let json: MoneroDaemonGetInfoResponse = res
|
||||
.json()
|
||||
.await
|
||||
.context("Failed to deserialize daemon get_info response")?;
|
||||
|
||||
let is_status_ok = json.status == "OK";
|
||||
let is_synchronized = json.synchronized;
|
||||
let is_correct_network = match self.network {
|
||||
Network::Mainnet => json.mainnet,
|
||||
Network::Stagenet => json.stagenet,
|
||||
Network::Testnet => json.testnet,
|
||||
};
|
||||
|
||||
Ok(is_status_ok && is_synchronized && is_correct_network)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MoneroDaemon {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.address, self.port)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MoneroDaemonGetInfoResponse {
|
||||
status: String,
|
||||
synchronized: bool,
|
||||
mainnet: bool,
|
||||
stagenet: bool,
|
||||
testnet: bool,
|
||||
}
|
||||
|
||||
/// Chooses an available Monero daemon based on the specified network.
|
||||
async fn choose_monero_daemon(network: Network) -> Result<&'static MoneroDaemon, Error> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.https_only(false)
|
||||
.build()?;
|
||||
|
||||
// We only want to check for daemons that match the specified 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 {
|
||||
Ok(true) => {
|
||||
tracing::debug!(%daemon, "Found available Monero daemon");
|
||||
return Ok(daemon);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!(%err, %daemon, "Failed to connect to Monero daemon");
|
||||
continue;
|
||||
}
|
||||
Ok(false) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
bail!("No Monero daemon could be found. Please specify one manually or try again later.")
|
||||
}
|
||||
|
||||
impl WalletRpcProcess {
|
||||
pub fn endpoint(&self) -> Url {
|
||||
Url::parse(&format!("http://127.0.0.1:{}/json_rpc", self.port))
|
||||
@ -153,13 +264,23 @@ impl WalletRpc {
|
||||
Ok(monero_wallet_rpc)
|
||||
}
|
||||
|
||||
pub async fn run(&self, network: Network, daemon_address: &str) -> Result<WalletRpcProcess> {
|
||||
pub async fn run(
|
||||
&self,
|
||||
network: Network,
|
||||
daemon_address: Option<String>,
|
||||
) -> Result<WalletRpcProcess> {
|
||||
let port = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||
.await?
|
||||
.local_addr()?
|
||||
.port();
|
||||
|
||||
let daemon_address = match daemon_address {
|
||||
Some(daemon_address) => daemon_address,
|
||||
None => choose_monero_daemon(network).await?.to_string(),
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
%daemon_address,
|
||||
%port,
|
||||
"Starting monero-wallet-rpc"
|
||||
);
|
||||
@ -232,7 +353,6 @@ impl WalletRpc {
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
async fn extract_archive(monero_wallet_rpc: &Self) -> Result<()> {
|
||||
use anyhow::bail;
|
||||
use tokio_tar::Archive;
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
@ -297,3 +417,123 @@ impl WalletRpc {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn extract_host_and_port(address: String) -> (&'static str, u16) {
|
||||
let parts: Vec<&str> = address.split(':').collect();
|
||||
|
||||
if parts.len() == 2 {
|
||||
let host = parts[0].to_string();
|
||||
let port = parts[1].parse::<u16>().unwrap();
|
||||
let static_str_host: &'static str = Box::leak(host.into_boxed_str());
|
||||
return (static_str_host, port);
|
||||
}
|
||||
panic!("Could not extract host and port from address: {}", address)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_daemon_available_success() {
|
||||
let mut server = mockito::Server::new();
|
||||
|
||||
let _ = server
|
||||
.mock("GET", "/get_info")
|
||||
.with_status(200)
|
||||
.with_body(
|
||||
r#"
|
||||
{
|
||||
"status": "OK",
|
||||
"synchronized": true,
|
||||
"mainnet": true,
|
||||
"stagenet": false,
|
||||
"testnet": false
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.create();
|
||||
|
||||
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let result = MoneroDaemon::new(host, port, Network::Mainnet)
|
||||
.is_available(&client)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_daemon_available_wrong_network_failure() {
|
||||
let mut server = mockito::Server::new();
|
||||
|
||||
let _ = server
|
||||
.mock("GET", "/get_info")
|
||||
.with_status(200)
|
||||
.with_body(
|
||||
r#"
|
||||
{
|
||||
"status": "OK",
|
||||
"synchronized": true,
|
||||
"mainnet": true,
|
||||
"stagenet": false,
|
||||
"testnet": false
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.create();
|
||||
|
||||
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let result = MoneroDaemon::new(host, port, Network::Stagenet)
|
||||
.is_available(&client)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_daemon_available_not_synced_failure() {
|
||||
let mut server = mockito::Server::new();
|
||||
|
||||
let _ = server
|
||||
.mock("GET", "/get_info")
|
||||
.with_status(200)
|
||||
.with_body(
|
||||
r#"
|
||||
{
|
||||
"status": "OK",
|
||||
"synchronized": false,
|
||||
"mainnet": true,
|
||||
"stagenet": false,
|
||||
"testnet": false
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.create();
|
||||
|
||||
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let result = MoneroDaemon::new(host, port, Network::Mainnet)
|
||||
.is_available(&client)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_daemon_available_network_error_failure() {
|
||||
let client = reqwest::Client::new();
|
||||
let result = MoneroDaemon::new("does.not.exist.com", 18081, Network::Mainnet)
|
||||
.is_available(&client)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
@ -106,11 +106,12 @@ impl Seed {
|
||||
}
|
||||
|
||||
fn from_pem(pem: pem::Pem) -> Result<Self, Error> {
|
||||
if pem.contents.len() != SEED_LENGTH {
|
||||
Err(Error::IncorrectLength(pem.contents.len()))
|
||||
let contents = pem.contents();
|
||||
if contents.len() != SEED_LENGTH {
|
||||
Err(Error::IncorrectLength(contents.len()))
|
||||
} else {
|
||||
let mut array = [0; SEED_LENGTH];
|
||||
for (i, b) in pem.contents.iter().enumerate() {
|
||||
for (i, b) in contents.iter().enumerate() {
|
||||
array[i] = *b;
|
||||
}
|
||||
|
||||
@ -122,10 +123,7 @@ impl Seed {
|
||||
ensure_directory_exists(&seed_file)?;
|
||||
|
||||
let data = self.bytes();
|
||||
let pem = Pem {
|
||||
tag: String::from("SEED"),
|
||||
contents: data.to_vec(),
|
||||
};
|
||||
let pem = Pem::new("SEED", data);
|
||||
|
||||
let pem_string = encode(&pem);
|
||||
|
||||
@ -224,19 +222,20 @@ VnZUNFZ4dlY=
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn seed_from_pem_fails_for_long_seed() {
|
||||
let long = "-----BEGIN SEED-----
|
||||
mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE=
|
||||
mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE=
|
||||
MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
|
||||
dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
|
||||
-----END SEED-----
|
||||
";
|
||||
let pem = pem::parse(long).unwrap();
|
||||
assert_eq!(pem.contents().len(), 96);
|
||||
|
||||
match Seed::from_pem(pem) {
|
||||
Ok(_) => panic!("should fail for long payload"),
|
||||
Err(e) => {
|
||||
match e {
|
||||
Error::IncorrectLength(_) => {} // pass
|
||||
Error::IncorrectLength(len) => assert_eq!(len, 96), // pass
|
||||
_ => panic!("should fail with IncorrectLength error"),
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user