Merge branch 'master' into update-monero-17.3.0

pull/924/head
Byron Hambly 2 years ago committed by GitHub
commit cf248ae1e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,12 +45,12 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout tagged commit - name: Checkout tagged commit
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3.0.1
with: with:
ref: ${{ github.event.release.target_commitish }} ref: ${{ github.event.release.target_commitish }}
token: ${{ secrets.BOTTY_GITHUB_TOKEN }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
- uses: Swatinem/rust-cache@v1.3.0 - uses: Swatinem/rust-cache@v1.4.0
- name: Install compiler for armhf arch - name: Install compiler for armhf arch
if: matrix.target == 'armv7-unknown-linux-gnueabihf' if: matrix.target == 'armv7-unknown-linux-gnueabihf'
@ -67,9 +67,9 @@ jobs:
run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help
# Remove once python 3 is the default # Remove once python 3 is the default
- uses: actions/setup-python@v2.3.1 - uses: actions/setup-python@v3
with: with:
python-version: '3.x' python-version: "3.x"
- id: create-archive-name - id: create-archive-name
shell: python # Use python to have a prettier name for the archive on Windows. shell: python # Use python to have a prettier name for the archive on Windows.

@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3.0.1
- uses: Swatinem/rust-cache@v1.3.0 - uses: Swatinem/rust-cache@v1.4.0
- name: Check formatting - name: Check formatting
uses: dprint/check@v2.0 uses: dprint/check@v2.0
@ -42,9 +42,9 @@ jobs:
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3.0.1
- uses: Swatinem/rust-cache@v1.3.0 - uses: Swatinem/rust-cache@v1.4.0
- name: Install compiler for armhf arch - name: Install compiler for armhf arch
if: matrix.target == 'armv7-unknown-linux-gnueabihf' if: matrix.target == 'armv7-unknown-linux-gnueabihf'
@ -75,9 +75,9 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3.0.1
- uses: Swatinem/rust-cache@v1.3.0 - uses: Swatinem/rust-cache@v1.4.0
- name: Build tests - name: Build tests
run: cargo build --tests --workspace --all-features run: cargo build --tests --workspace --all-features
@ -111,9 +111,9 @@ jobs:
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3.0.1
- uses: Swatinem/rust-cache@v1.3.0 - uses: Swatinem/rust-cache@v1.4.0
- name: Run test ${{ matrix.test_name }} - name: Run test ${{ matrix.test_name }}
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture

@ -11,7 +11,7 @@ jobs:
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3.0.1
- name: Extract version from branch name - name: Extract version from branch name
id: extract-version id: extract-version

@ -12,7 +12,7 @@ jobs:
name: "Draft a new release" name: "Draft a new release"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3.0.1
with: with:
token: ${{ secrets.BOTTY_GITHUB_TOKEN }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
@ -20,7 +20,7 @@ jobs:
run: git checkout -b release/${{ github.event.inputs.version }} run: git checkout -b release/${{ github.event.inputs.version }}
- name: Update changelog - name: Update changelog
uses: thomaseizinger/keep-a-changelog-new-release@1.2.1 uses: thomaseizinger/keep-a-changelog-new-release@1.3.0
with: with:
version: ${{ github.event.inputs.version }} version: ${{ github.event.inputs.version }}
changelogPath: CHANGELOG.md changelogPath: CHANGELOG.md

@ -10,7 +10,7 @@ jobs:
name: Create preview release name: Create preview release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3.0.1
- name: Delete 'preview' release - name: Delete 'preview' release
uses: larryjoelane/delete-release-action@v1.0.24 uses: larryjoelane/delete-release-action@v1.0.24

@ -9,8 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Change Monero nodes to [Rino tool nodes](https://community.rino.io/nodes.html)
- Revert logs to use rfc3339 local time formatting. - Revert logs to use rfc3339 local time formatting.
- Update from monero v17.2.0 to monero v17.3.0 - Update from monero v17.2.0 to monero v17.3.0
- Always write logs as JSON to files
### Added
- Adjust quote based on Bitcoin balance.
If the max_buy_btc in the ASB config is higher than the available balance to trade it will return the max available balance discounting the locking fees for monero, in the case the balance is lower than the min_buy_btc config it will return 0 to the CLI. If the ASB returns a quote of 0 the CLI will not allow you continue with a trade.
- Reduce required confirmations for Bitcoin transactions from 2 to 1
## [0.10.2] - 2021-12-25 ## [0.10.2] - 2021-12-25
@ -305,7 +313,7 @@ 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. - 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. 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.10.2...HEAD [unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...HEAD
[0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2 [0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2
[0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1 [0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1
[0.10.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.9.0...0.10.0 [0.10.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.9.0...0.10.0

447
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -47,6 +47,11 @@ It is not recommended to bump fees when swapping because it can have unpredictab
We are encourage community contributions whether it be a bug fix or an improvement to the documentation. We are encourage community contributions whether it be a bug fix or an improvement to the documentation.
Please have a look at the [contribution guidelines](./CONTRIBUTING.md). 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.58 _should_ work.
## Contact ## Contact
Feel free to reach out to us in the [COMIT-Monero Matrix channel](https://matrix.to/#/#comit-monero:matrix.org). Feel free to reach out to us in the [COMIT-Monero Matrix channel](https://matrix.to/#/#comit-monero:matrix.org).

@ -12,7 +12,7 @@ This quickstart guide assumes that you are running the software on testnet (i.e.
3. Run the ASB in terminal: `./asb --testnet start` 3. Run the ASB in terminal: `./asb --testnet start`
4. Follow the setup wizard in the terminal 4. Follow the setup wizard in the terminal
Public Monero nodes for running the Monero Wallet RPC can be found [here](https://melo.tools/nodes.html). Public Monero nodes for running the Monero Wallet RPC can be found [here](https://community.rino.io/nodes.html).
Run `./asb --help` for more information. Run `./asb --help` for more information.

@ -18,8 +18,8 @@
"includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"], "includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"],
"excludes": [ "target/" ], "excludes": [ "target/" ],
"plugins": [ "plugins": [
"https://plugins.dprint.dev/markdown-0.6.1.wasm", "https://plugins.dprint.dev/markdown-0.13.1.wasm",
"https://github.com/thomaseizinger/dprint-plugin-cargo-toml/releases/download/0.1.0/cargo-toml-0.1.0.wasm", "https://github.com/thomaseizinger/dprint-plugin-cargo-toml/releases/download/0.1.0/cargo-toml-0.1.0.wasm",
"https://plugins.dprint.dev/rustfmt-0.4.0.exe-plugin@c6bb223ef6e5e87580177f6461a0ab0554ac9ea6b54f78ea7ae8bf63b14f5bc2" "https://plugins.dprint.dev/rustfmt-0.6.1.exe-plugin@99b89a0599fd3a63e597e03436862157901f3facae2f0c2fbd0b9f656cdbc2a5"
] ]
} }

@ -2,7 +2,7 @@
name = "monero-harness" name = "monero-harness"
version = "0.1.0" version = "0.1.0"
authors = [ "CoBloX Team <team@coblox.tech>" ] authors = [ "CoBloX Team <team@coblox.tech>" ]
edition = "2018" edition = "2021"
publish = false publish = false
[dependencies] [dependencies]

@ -13,7 +13,7 @@ pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
/// this doesn't matter. /// this doesn't matter.
pub const RPC_PORT: u16 = 18081; pub const RPC_PORT: u16 = 18081;
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Monerod { pub struct Monerod {
args: MonerodArgs, args: MonerodArgs,
} }
@ -25,7 +25,7 @@ impl Image for Monerod {
type EntryPoint = str; type EntryPoint = str;
fn descriptor(&self) -> String { fn descriptor(&self) -> String {
"xmrto/monero:v0.17.2.0".to_owned() "rinocommunity/monero:v0.17.2.0".to_owned()
} }
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) { fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
@ -58,15 +58,7 @@ impl Image for Monerod {
} }
} }
impl Default for Monerod { #[derive(Debug, Default)]
fn default() -> Self {
Self {
args: MonerodArgs::default(),
}
}
}
#[derive(Debug)]
pub struct MoneroWalletRpc { pub struct MoneroWalletRpc {
args: MoneroWalletRpcArgs, args: MoneroWalletRpcArgs,
} }
@ -78,7 +70,7 @@ impl Image for MoneroWalletRpc {
type EntryPoint = str; type EntryPoint = str;
fn descriptor(&self) -> String { fn descriptor(&self) -> String {
"xmrto/monero:v0.17.2.0".to_owned() "rinocommunity/monero:v0.17.2.0".to_owned()
} }
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) { fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
@ -111,14 +103,6 @@ impl Image for MoneroWalletRpc {
} }
} }
impl Default for MoneroWalletRpc {
fn default() -> Self {
Self {
args: MoneroWalletRpcArgs::default(),
}
}
}
impl MoneroWalletRpc { impl MoneroWalletRpc {
pub fn new(name: &str, daemon_address: String) -> Self { pub fn new(name: &str, daemon_address: String) -> Self {
Self { Self {

@ -71,7 +71,7 @@ impl<'c> Monero {
let miner = "miner"; let miner = "miner";
tracing::info!("Starting miner wallet: {}", miner); tracing::info!("Starting miner wallet: {}", miner);
let (miner_wallet, miner_container) = let (miner_wallet, miner_container) =
MoneroWalletRpc::new(cli, &miner, &monerod, prefix.clone()).await?; MoneroWalletRpc::new(cli, miner, &monerod, prefix.clone()).await?;
wallets.push(miner_wallet); wallets.push(miner_wallet);
containers.push(miner_container); containers.push(miner_container);
@ -83,7 +83,7 @@ impl<'c> Monero {
// trying for 5 minutes // trying for 5 minutes
let (wallet, container) = tokio::time::timeout(Duration::from_secs(300), async { let (wallet, container) = tokio::time::timeout(Duration::from_secs(300), async {
loop { loop {
let result = MoneroWalletRpc::new(cli, &wallet, &monerod, prefix.clone()).await; let result = MoneroWalletRpc::new(cli, wallet, &monerod, prefix.clone()).await;
match result { match result {
Ok(tuple) => { return tuple; } Ok(tuple) => { return tuple; }
@ -188,7 +188,6 @@ fn random_prefix() -> String {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Monerod { pub struct Monerod {
rpc_port: u16,
name: String, name: String,
network: String, network: String,
client: monerod::Client, client: monerod::Client,
@ -196,9 +195,7 @@ pub struct Monerod {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MoneroWalletRpc { pub struct MoneroWalletRpc {
rpc_port: u16,
name: String, name: String,
network: String,
client: wallet::Client, client: wallet::Client,
} }
@ -220,7 +217,6 @@ impl<'c> Monerod {
Ok(( Ok((
Self { Self {
rpc_port: monerod_rpc_port,
name, name,
network, network,
client: monerod::Client::localhost(monerod_rpc_port)?, client: monerod::Client::localhost(monerod_rpc_port)?,
@ -252,7 +248,7 @@ impl<'c> MoneroWalletRpc {
prefix: String, prefix: String,
) -> Result<(Self, Container<'c, Cli, image::MoneroWalletRpc>)> { ) -> Result<(Self, Container<'c, Cli, image::MoneroWalletRpc>)> {
let daemon_address = format!("{}:{}", monerod.name, RPC_PORT); let daemon_address = format!("{}:{}", monerod.name, RPC_PORT);
let image = image::MoneroWalletRpc::new(&name, daemon_address); let image = image::MoneroWalletRpc::new(name, daemon_address);
let network = monerod.network.clone(); let network = monerod.network.clone();
let run_args = RunArgs::default() let run_args = RunArgs::default()
@ -272,9 +268,7 @@ impl<'c> MoneroWalletRpc {
Ok(( Ok((
Self { Self {
rpc_port: wallet_rpc_port,
name: name.to_string(), name: name.to_string(),
network,
client, client,
}, },
container, container,

@ -2,7 +2,7 @@
name = "monero-rpc" name = "monero-rpc"
version = "0.1.0" version = "0.1.0"
authors = [ "CoBloX Team <team@coblox.tech>" ] authors = [ "CoBloX Team <team@coblox.tech>" ]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"

@ -184,12 +184,7 @@ pub struct Refreshed {
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct SweepAll { pub struct SweepAll {
amount_list: Vec<u64>,
fee_list: Vec<u64>,
multisig_txset: String,
pub tx_hash_list: Vec<String>, pub tx_hash_list: Vec<String>,
unsigned_txset: String,
weight_list: Vec<u32>,
} }
#[derive(Debug, Copy, Clone, Deserialize)] #[derive(Debug, Copy, Clone, Deserialize)]
@ -244,7 +239,7 @@ mod tests {
} }
}"#; }"#;
let _: Response<SweepAll> = serde_json::from_str(&response).unwrap(); let _: Response<SweepAll> = serde_json::from_str(response).unwrap();
} }
#[test] #[test]
@ -256,6 +251,6 @@ mod tests {
} }
}"#; }"#;
let _: Response<WalletCreated> = serde_json::from_str(&response).unwrap(); let _: Response<WalletCreated> = serde_json::from_str(response).unwrap();
} }
} }

@ -2,7 +2,7 @@
name = "monero-wallet" name = "monero-wallet"
version = "0.1.0" version = "0.1.0"
authors = [ "CoBloX Team <team@coblox.tech>" ] authors = [ "CoBloX Team <team@coblox.tech>" ]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"

@ -77,8 +77,8 @@ mod tests {
let result = rpc_client let result = rpc_client
.get_outs( .get_outs(
key_offsets key_offsets
.to_vec() .iter()
.into_iter() .cloned()
.map(|varint| GetOutputsOut { .map(|varint| GetOutputsOut {
amount: 0, amount: 0,
index: varint.0, index: varint.0,

@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "1.53" channel = "1.59"
components = ["clippy"] components = ["clippy"]
targets = ["armv7-unknown-linux-gnueabihf"] targets = ["armv7-unknown-linux-gnueabihf"]

@ -2,7 +2,7 @@
name = "swap" name = "swap"
version = "0.10.2" version = "0.10.2"
authors = [ "The COMIT guys <hello@comit.network>" ] authors = [ "The COMIT guys <hello@comit.network>" ]
edition = "2018" edition = "2021"
description = "XMR/BTC trustless atomic swaps." description = "XMR/BTC trustless atomic swaps."
[lib] [lib]
@ -13,18 +13,18 @@ anyhow = "1"
async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] } async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
async-trait = "0.1" async-trait = "0.1"
atty = "0.2" atty = "0.2"
backoff = { version = "0.3", features = [ "tokio" ] } backoff = { version = "0.4", features = [ "tokio" ] }
base64 = "0.13" base64 = "0.13"
bdk = "0.12" bdk = "0.16"
big-bytes = "1" big-bytes = "1"
bitcoin = { version = "0.27", features = [ "rand", "use-serde" ] } bitcoin = { version = "0.27", features = [ "rand", "use-serde" ] }
bmrng = "0.5" bmrng = "0.5"
comfy-table = "4.1.1" comfy-table = "5.0"
config = { version = "0.11", default-features = false, features = [ "toml" ] } config = { version = "0.11", default-features = false, features = [ "toml" ] }
conquer-once = "0.3" conquer-once = "0.3"
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" } curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
data-encoding = "2.3" data-encoding = "2.3"
dialoguer = "0.8" dialoguer = "0.10"
directories-next = "2" directories-next = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] } ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] }
ed25519-dalek = "1" ed25519-dalek = "1"
@ -50,13 +50,13 @@ sha2 = "0.9"
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] } sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] }
sqlx = { version = "0.5", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] } sqlx = { version = "0.5", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
structopt = "0.3" structopt = "0.3"
strum = { version = "0.23", features = [ "derive" ] } strum = { version = "0.24", features = [ "derive" ] }
thiserror = "1" thiserror = "1"
time = "0.3" time = "0.3"
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] } tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
tokio-socks = "0.5" tokio-socks = "0.5"
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] } tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
tokio-util = { version = "0.6", features = [ "io" ] } tokio-util = { version = "0.7", features = [ "io", "codec" ] }
toml = "0.5" toml = "0.5"
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] } torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] }
tracing = { version = "0.1", features = [ "attributes" ] } tracing = { version = "0.1", features = [ "attributes" ] }

@ -276,7 +276,7 @@ pub enum RawCommand {
WithdrawBtc { WithdrawBtc {
#[structopt( #[structopt(
long = "amount", long = "amount",
help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained." help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained. Amount must be specified in quotes with denomination, e.g `--amount '0.1 BTC'`"
)] )]
amount: Option<Amount>, amount: Option<Amount>,
#[structopt(long = "address", help = "The address to receive the Bitcoin.")] #[structopt(long = "address", help = "The address to receive the Bitcoin.")]

@ -319,13 +319,46 @@ where
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
) -> Result<BidQuote> { ) -> Result<BidQuote> {
let rate = self let ask_price = self
.latest_rate .latest_rate
.latest_rate() .latest_rate()
.context("Failed to get latest rate")?; .context("Failed to get latest rate")?
.ask()
.context("Failed to compute asking price")?;
let max_bitcoin_for_monero = self
.monero_wallet
.get_balance()
.await?
.max_bitcoin_for_price(ask_price);
if min_buy > max_bitcoin_for_monero {
tracing::warn!(
"Your Monero balance is too low to initiate a swap, as your minimum swap amount is {}. You could at most swap {}",
min_buy, max_bitcoin_for_monero
);
return Ok(BidQuote {
price: ask_price,
min_quantity: bitcoin::Amount::ZERO,
max_quantity: bitcoin::Amount::ZERO,
});
}
if max_buy > max_bitcoin_for_monero {
tracing::warn!(
"Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}",
max_buy, max_bitcoin_for_monero
);
return Ok(BidQuote {
price: ask_price,
min_quantity: min_buy,
max_quantity: max_bitcoin_for_monero,
});
}
Ok(BidQuote { Ok(BidQuote {
price: rate.ask().context("Failed to compute asking price")?, price: ask_price,
min_quantity: min_buy, min_quantity: min_buy,
max_quantity: max_buy, max_quantity: max_buy,
}) })

@ -29,7 +29,7 @@ use swap::cli::{list_sellers, EventLoop, SellerStatus};
use swap::database::open_db; use swap::database::open_db;
use swap::env::Config; use swap::env::Config;
use swap::libp2p_ext::MultiAddrExt; use swap::libp2p_ext::MultiAddrExt;
use swap::network::quote::BidQuote; use swap::network::quote::{BidQuote, ZeroQuoteReceived};
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::bob; use swap::protocol::bob;
use swap::protocol::bob::{BobState, Swap}; use swap::protocol::bob::{BobState, Swap};
@ -47,7 +47,7 @@ async fn main() -> Result<()> {
json, json,
cmd, cmd,
} = match parse_args_and_apply_defaults(env::args_os())? { } = match parse_args_and_apply_defaults(env::args_os())? {
ParseResult::Arguments(args) => args, ParseResult::Arguments(args) => *args,
ParseResult::PrintAndExitZero { message } => { ParseResult::PrintAndExitZero { message } => {
println!("{}", message); println!("{}", message);
std::process::exit(0); std::process::exit(0);
@ -95,11 +95,11 @@ async fn main() -> Result<()> {
tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
let (event_loop, mut event_loop_handle) = let (event_loop, mut event_loop_handle) =
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; EventLoop::new(swap_id, swarm, seller_peer_id)?;
let event_loop = tokio::spawn(event_loop.run()); let event_loop = tokio::spawn(event_loop.run());
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
let (amount, fees) = determine_btc_to_swap( let (amount, fees) = match determine_btc_to_swap(
json, json,
event_loop_handle.request_quote(), event_loop_handle.request_quote(),
bitcoin_wallet.new_address(), bitcoin_wallet.new_address(),
@ -107,7 +107,16 @@ async fn main() -> Result<()> {
max_givable, max_givable,
|| bitcoin_wallet.sync(), || bitcoin_wallet.sync(),
) )
.await?; .await
{
Ok(val) => val,
Err(error) => match error.downcast::<ZeroQuoteReceived>() {
Ok(_) => {
bail!("Seller's XMR balance is currently too low to initiate a swap, please try again later")
}
Err(other) => bail!(other),
},
};
tracing::info!(%amount, %fees, "Determined swap amount"); tracing::info!(%amount, %fees, "Determined swap amount");
@ -267,8 +276,7 @@ async fn main() -> Result<()> {
.add_address(seller_peer_id, seller_address); .add_address(seller_peer_id, seller_address);
} }
let (event_loop, event_loop_handle) = let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?;
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
let handle = tokio::spawn(event_loop.run()); let handle = tokio::spawn(event_loop.run());
let monero_receive_address = db.get_monero_address(swap_id).await?; let monero_receive_address = db.get_monero_address(swap_id).await?;
@ -556,6 +564,11 @@ where
{ {
tracing::debug!("Requesting quote"); tracing::debug!("Requesting quote");
let bid_quote = bid_quote.await?; let bid_quote = bid_quote.await?;
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
bail!(ZeroQuoteReceived)
}
tracing::info!( tracing::info!(
price = %bid_quote.price, price = %bid_quote.price,
minimum_amount = %bid_quote.min_quantity, minimum_amount = %bid_quote.min_quantity,
@ -915,6 +928,32 @@ mod tests {
); );
} }
#[tokio::test]
async fn given_bid_quote_max_amount_0_return_errorq() {
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.0001).unwrap(),
Amount::from_btc(0.01).unwrap(),
])));
let determination_error = determine_btc_to_swap(
true,
async { Ok(quote_with_max(0.00)) },
get_dummy_address(),
|| async { Ok(Amount::from_btc(0.0101)?) },
|| async {
let mut result = givable.lock().unwrap();
result.give()
},
|| async { Ok(()) },
)
.await
.err()
.unwrap()
.to_string();
assert_eq!("Received quote of 0", determination_error);
}
struct MaxGiveable { struct MaxGiveable {
amounts: Vec<Amount>, amounts: Vec<Amount>,
call_counter: usize, call_counter: usize,

@ -171,7 +171,7 @@ pub fn verify_sig(
) -> Result<()> { ) -> Result<()> {
let ecdsa = ECDSA::verify_only(); let ecdsa = ECDSA::verify_only();
if ecdsa.verify(&verification_key.0, &transaction_sighash.into_inner(), &sig) { if ecdsa.verify(&verification_key.0, &transaction_sighash.into_inner(), sig) {
Ok(()) Ok(())
} else { } else {
bail!(InvalidSignature) bail!(InvalidSignature)
@ -194,7 +194,7 @@ pub fn verify_encsig(
&verification_key.0, &verification_key.0,
&encryption_key.0, &encryption_key.0,
&digest.into_inner(), &digest.into_inner(),
&encsig, encsig,
) { ) {
Ok(()) Ok(())
} else { } else {
@ -213,7 +213,7 @@ pub fn build_shared_output_descriptor(A: Point, B: Point) -> Descriptor<bitcoin:
let A = ToHex::to_hex(&secp256k1::PublicKey::from(A)); let A = ToHex::to_hex(&secp256k1::PublicKey::from(A));
let B = ToHex::to_hex(&secp256k1::PublicKey::from(B)); let B = ToHex::to_hex(&secp256k1::PublicKey::from(B));
let miniscript = MINISCRIPT_TEMPLATE.replace("A", &A).replace("B", &B); let miniscript = MINISCRIPT_TEMPLATE.replace('A', &A).replace('B', &B);
let miniscript = let miniscript =
bdk::miniscript::Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str(&miniscript) bdk::miniscript::Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str(&miniscript)

@ -200,9 +200,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn bob_can_fund_without_a_change_output() { async fn bob_can_fund_without_a_change_output() {
let (A, B) = alice_and_bob(); let (A, B) = alice_and_bob();
let fees = 610; let fees = 300;
let agreed_amount = Amount::from_sat(10000); let agreed_amount = Amount::from_sat(10000);
let wallet = WalletBuilder::new(agreed_amount.as_sat() + fees).build(); let amount = agreed_amount.as_sat() + fees;
let wallet = WalletBuilder::new(amount).build();
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await; let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
assert_eq!( assert_eq!(
@ -262,7 +263,7 @@ mod tests {
amount: Amount, amount: Amount,
) -> PartiallySignedTransaction { ) -> PartiallySignedTransaction {
let change = wallet.new_address().await.unwrap(); let change = wallet.new_address().await.unwrap();
TxLock::new(&wallet, amount, A, B, change) TxLock::new(wallet, amount, A, B, change)
.await .await
.unwrap() .unwrap()
.into() .into()

@ -128,7 +128,7 @@ impl TxRedeem {
let sig = sigs let sig = sigs
.into_iter() .into_iter()
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok()) .find(|sig| verify_sig(&B, &self.digest(), sig).is_ok())
.context("Neither signature on witness stack verifies against B")?; .context("Neither signature on witness stack verifies against B")?;
Ok(sig) Ok(sig)

@ -132,7 +132,7 @@ impl TxRefund {
let sig = sigs let sig = sigs
.into_iter() .into_iter()
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok()) .find(|sig| verify_sig(&B, &self.digest(), sig).is_ok())
.context("Neither signature on witness stack verifies against B")?; .context("Neither signature on witness stack verifies against B")?;
Ok(sig) Ok(sig)

@ -102,7 +102,7 @@ impl Wallet {
self.wallet self.wallet
.lock() .lock()
.await .await
.broadcast(transaction) .broadcast(&transaction)
.with_context(|| { .with_context(|| {
format!("Failed to broadcast Bitcoin {} transaction {}", kind, txid) format!("Failed to broadcast Bitcoin {} transaction {}", kind, txid)
})?; })?;
@ -152,13 +152,13 @@ impl Wallet {
ScriptStatus::Retrying ScriptStatus::Retrying
} }
}; };
if new_status != ScriptStatus::Retrying if new_status != ScriptStatus::Retrying
{ {
last_status = Some(print_status_change(txid, last_status, new_status)); last_status = Some(print_status_change(txid, last_status, new_status));
let all_receivers_gone = sender.send(new_status).is_err(); let all_receivers_gone = sender.send(new_status).is_err();
if all_receivers_gone { if all_receivers_gone {
tracing::debug!(%txid, "All receivers gone, removing subscription"); tracing::debug!(%txid, "All receivers gone, removing subscription");
client.lock().await.subscriptions.remove(&(txid, script)); client.lock().await.subscriptions.remove(&(txid, script));

@ -15,8 +15,8 @@ use url::Url;
use uuid::Uuid; use uuid::Uuid;
// See: https://moneroworld.com/ // See: https://moneroworld.com/
pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.melo.tools:18081"; pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081";
pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.melo.tools:38081"; pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081";
// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc // See: https://1209k.com/bitcoin-eye/ele.php?chain=btc
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700"; const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700";
@ -41,7 +41,7 @@ pub struct Arguments {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ParseResult { pub enum ParseResult {
/// The arguments we were invoked in. /// The arguments we were invoked in.
Arguments(Arguments), Arguments(Box<Arguments>),
/// A flag or command was given that does not need further processing other /// A flag or command was given that does not need further processing other
/// than printing the provided message. /// than printing the provided message.
/// ///
@ -260,7 +260,7 @@ where
}, },
}; };
Ok(ParseResult::Arguments(arguments)) Ok(ParseResult::Arguments(Box::new(arguments)))
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -693,7 +693,8 @@ mod tests {
MULTI_ADDRESS, MULTI_ADDRESS,
]; ];
let expected_args = ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults()); let expected_args =
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().into_boxed());
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!(expected_args, args); assert_eq!(expected_args, args);
@ -717,7 +718,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults()) ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().into_boxed())
); );
} }
@ -778,7 +779,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_mainnet_defaults()) ParseResult::Arguments(Arguments::resume_mainnet_defaults().into_boxed())
); );
} }
@ -790,7 +791,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_testnet_defaults()) ParseResult::Arguments(Arguments::resume_testnet_defaults().into_boxed())
); );
} }
@ -802,7 +803,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::cancel_mainnet_defaults()) ParseResult::Arguments(Arguments::cancel_mainnet_defaults().into_boxed())
); );
} }
@ -814,7 +815,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::cancel_testnet_defaults()) ParseResult::Arguments(Arguments::cancel_testnet_defaults().into_boxed())
); );
} }
@ -826,7 +827,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::refund_mainnet_defaults()) ParseResult::Arguments(Arguments::refund_mainnet_defaults().into_boxed())
); );
} }
@ -838,7 +839,7 @@ mod tests {
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::refund_testnet_defaults()) ParseResult::Arguments(Arguments::refund_testnet_defaults().into_boxed())
); );
} }
@ -866,6 +867,7 @@ mod tests {
ParseResult::Arguments( ParseResult::Arguments(
Arguments::buy_xmr_mainnet_defaults() Arguments::buy_xmr_mainnet_defaults()
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet")) .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
.into_boxed()
) )
); );
@ -890,6 +892,7 @@ mod tests {
ParseResult::Arguments( ParseResult::Arguments(
Arguments::buy_xmr_testnet_defaults() Arguments::buy_xmr_testnet_defaults()
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet")) .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
.into_boxed()
) )
); );
@ -909,6 +912,7 @@ mod tests {
ParseResult::Arguments( ParseResult::Arguments(
Arguments::resume_mainnet_defaults() Arguments::resume_mainnet_defaults()
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet")) .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
.into_boxed()
) )
); );
@ -929,6 +933,7 @@ mod tests {
ParseResult::Arguments( ParseResult::Arguments(
Arguments::resume_testnet_defaults() Arguments::resume_testnet_defaults()
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet")) .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
.into_boxed()
) )
); );
} }
@ -950,7 +955,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().with_debug()) ParseResult::Arguments(
Arguments::buy_xmr_mainnet_defaults()
.with_debug()
.into_boxed()
)
); );
let raw_ars = vec![ let raw_ars = vec![
@ -969,7 +978,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_debug()) ParseResult::Arguments(
Arguments::buy_xmr_testnet_defaults()
.with_debug()
.into_boxed()
)
); );
let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID]; let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID];
@ -977,7 +990,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_mainnet_defaults().with_debug()) ParseResult::Arguments(
Arguments::resume_mainnet_defaults()
.with_debug()
.into_boxed()
)
); );
let raw_ars = vec![ let raw_ars = vec![
@ -992,7 +1009,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_testnet_defaults().with_debug()) ParseResult::Arguments(
Arguments::resume_testnet_defaults()
.with_debug()
.into_boxed()
)
); );
} }
@ -1013,7 +1034,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().with_json()) ParseResult::Arguments(
Arguments::buy_xmr_mainnet_defaults()
.with_json()
.into_boxed()
)
); );
let raw_ars = vec![ let raw_ars = vec![
@ -1032,7 +1057,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_json()) ParseResult::Arguments(
Arguments::buy_xmr_testnet_defaults()
.with_json()
.into_boxed()
)
); );
let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID]; let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID];
@ -1040,7 +1069,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_mainnet_defaults().with_json()) ParseResult::Arguments(
Arguments::resume_mainnet_defaults()
.with_json()
.into_boxed()
)
); );
let raw_ars = vec![ let raw_ars = vec![
@ -1055,7 +1088,11 @@ mod tests {
let args = parse_args_and_apply_defaults(raw_ars).unwrap(); let args = parse_args_and_apply_defaults(raw_ars).unwrap();
assert_eq!( assert_eq!(
args, args,
ParseResult::Arguments(Arguments::resume_testnet_defaults().with_json()) ParseResult::Arguments(
Arguments::resume_testnet_defaults()
.with_json()
.into_boxed()
)
); );
} }
@ -1303,6 +1340,10 @@ mod tests {
self.json = true; self.json = true;
self self
} }
pub fn into_boxed(self) -> Box<Self> {
Box::new(self)
}
} }
fn data_dir_path_cli() -> PathBuf { fn data_dir_path_cli() -> PathBuf {

@ -1,10 +1,10 @@
use crate::bitcoin::EncryptedSignature; use crate::bitcoin::EncryptedSignature;
use crate::cli::behaviour::{Behaviour, OutEvent}; use crate::cli::behaviour::{Behaviour, OutEvent};
use crate::monero;
use crate::network::encrypted_signature; use crate::network::encrypted_signature;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::swap_setup::bob::NewSwap; use crate::network::swap_setup::bob::NewSwap;
use crate::protocol::bob::State2; use crate::protocol::bob::State2;
use crate::{env, monero};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::future::{BoxFuture, OptionFuture}; use futures::future::{BoxFuture, OptionFuture};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
@ -50,7 +50,6 @@ impl EventLoop {
swap_id: Uuid, swap_id: Uuid,
swarm: Swarm<Behaviour>, swarm: Swarm<Behaviour>,
alice_peer_id: PeerId, alice_peer_id: PeerId,
env_config: env::Config,
) -> Result<(Self, EventLoopHandle)> { ) -> Result<(Self, EventLoopHandle)> {
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60));
@ -76,7 +75,6 @@ impl EventLoop {
transfer_proof: transfer_proof.1, transfer_proof: transfer_proof.1,
encrypted_signature: encrypted_signature.0, encrypted_signature: encrypted_signature.0,
quote: quote.0, quote: quote.0,
env_config,
}; };
Ok((event_loop, handle)) Ok((event_loop, handle))
@ -220,7 +218,6 @@ pub struct EventLoopHandle {
transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>, transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>,
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>, encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
quote: bmrng::RequestSender<(), BidQuote>, quote: bmrng::RequestSender<(), BidQuote>,
env_config: env::Config,
} }
impl EventLoopHandle { impl EventLoopHandle {

@ -271,7 +271,7 @@ impl EventLoop {
QuoteStatus::Received(Status::Online(quote)) => { QuoteStatus::Received(Status::Online(quote)) => {
let address = self let address = self
.reachable_asb_address .reachable_asb_address
.get(&peer_id) .get(peer_id)
.expect("if we got a quote we must have stored an address"); .expect("if we got a quote we must have stored an address");
Ok(Seller { Ok(Seller {
@ -282,7 +282,7 @@ impl EventLoop {
QuoteStatus::Received(Status::Unreachable) => { QuoteStatus::Received(Status::Unreachable) => {
let address = self let address = self
.unreachable_asb_address .unreachable_asb_address
.get(&peer_id) .get(peer_id)
.expect("if we got a quote we must have stored an address"); .expect("if we got a quote we must have stored an address");
Ok(Seller { Ok(Seller {

@ -20,27 +20,22 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
std::mem::forget(guard); std::mem::forget(guard);
let file_logger = fmt::layer() let file_logger = registry.with(
.with_ansi(false) fmt::layer()
.with_target(false) .with_ansi(false)
.with_writer(appender); .with_target(false)
.json()
.with_writer(appender),
);
if json && debug { if json && debug {
set_global_default( set_global_default(file_logger.with(debug_json_terminal_printer()))?;
registry
.with(file_logger.json())
.with(debug_json_terminal_printer()),
)?;
} else if json && !debug { } else if json && !debug {
set_global_default( set_global_default(file_logger.with(info_json_terminal_printer()))?;
registry
.with(file_logger.json())
.with(info_json_terminal_printer()),
)?;
} else if !json && debug { } else if !json && debug {
set_global_default(registry.with(file_logger).with(debug_terminal_printer()))?; set_global_default(file_logger.with(debug_terminal_printer()))?;
} else { } else {
set_global_default(registry.with(file_logger).with(info_terminal_printer()))?; set_global_default(file_logger.with(info_terminal_printer()))?;
} }
Ok(()) Ok(())

@ -48,7 +48,7 @@ impl GetConfig for Mainnet {
Config { Config {
bitcoin_lock_mempool_timeout: 3.std_minutes(), bitcoin_lock_mempool_timeout: 3.std_minutes(),
bitcoin_lock_confirmed_timeout: 2.std_hours(), bitcoin_lock_confirmed_timeout: 2.std_hours(),
bitcoin_finality_confirmations: 2, bitcoin_finality_confirmations: 1,
bitcoin_avg_block_time: 10.std_minutes(), bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(72), bitcoin_cancel_timelock: CancelTimelock::new(72),
bitcoin_punish_timelock: PunishTimelock::new(72), bitcoin_punish_timelock: PunishTimelock::new(72),
@ -65,7 +65,7 @@ impl GetConfig for Testnet {
Config { Config {
bitcoin_lock_mempool_timeout: 3.std_minutes(), bitcoin_lock_mempool_timeout: 3.std_minutes(),
bitcoin_lock_confirmed_timeout: 1.std_hours(), bitcoin_lock_confirmed_timeout: 1.std_hours(),
bitcoin_finality_confirmations: 2, bitcoin_finality_confirmations: 1,
bitcoin_avg_block_time: 10.std_minutes(), bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(12), bitcoin_cancel_timelock: CancelTimelock::new(12),
bitcoin_punish_timelock: PunishTimelock::new(6), bitcoin_punish_timelock: PunishTimelock::new(6),
@ -138,7 +138,7 @@ mod monero_network {
Network::Stagenet => "stagenet", Network::Stagenet => "stagenet",
Network::Testnet => "testnet", Network::Testnet => "testnet",
}; };
s.serialize_str(&str) s.serialize_str(str)
} }
} }

@ -45,7 +45,7 @@ pub fn connect(price_ticker_ws_url: Url) -> Result<PriceUpdates> {
} }
} }
Err(backoff::Error::Transient(anyhow!("stream ended"))) Err(backoff::Error::transient(anyhow!("stream ended")))
} }
}, },
|error, next: Duration| { |error, next: Duration| {
@ -108,8 +108,8 @@ fn to_backoff(e: connection::Error) -> backoff::Error<anyhow::Error> {
match e { match e {
// Connection closures and websocket errors will be retried // Connection closures and websocket errors will be retried
connection::Error::ConnectionClosed => Transient(anyhow::Error::from(e)), connection::Error::ConnectionClosed => backoff::Error::transient(anyhow::Error::from(e)),
connection::Error::WebSocket(_) => Transient(anyhow::Error::from(e)), connection::Error::WebSocket(_) => backoff::Error::transient(anyhow::Error::from(e)),
// Failures while parsing a message are permanent because they most likely present a // Failures while parsing a message are permanent because they most likely present a
// programmer error // programmer error
@ -275,8 +275,6 @@ mod wire {
pub struct TickerData { pub struct TickerData {
#[serde(rename = "a")] #[serde(rename = "a")]
ask: Vec<RateElement>, ask: Vec<RateElement>,
#[serde(rename = "b")]
bid: Vec<RateElement>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

@ -99,6 +99,20 @@ impl Amount {
self.0 self.0
} }
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> bitcoin::Amount {
let piconero_minus_fee = self.as_piconero().saturating_sub(MONERO_FEE.as_piconero());
if piconero_minus_fee == 0 {
return bitcoin::Amount::ZERO;
}
// There needs to be an offset for difference in zeroes beetween Piconeros and
// Satoshis
let piconero_calc = (piconero_minus_fee * ask_price.as_sat()) / PICONERO_OFFSET;
bitcoin::Amount::from_sat(piconero_calc)
}
pub fn from_monero(amount: f64) -> Result<Self> { pub fn from_monero(amount: f64) -> Result<Self> {
let decimal = Decimal::try_from(amount)?; let decimal = Decimal::try_from(amount)?;
Self::from_decimal(decimal) Self::from_decimal(decimal)
@ -360,6 +374,30 @@ mod tests {
); );
} }
#[test]
fn geting_max_bitcoin_to_trade() {
let amount = Amount::parse_monero("10").unwrap();
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
assert_eq!(
bitcoin::Amount::from_sat(3_828_988),
monero_max_from_bitcoin
);
}
#[test]
fn geting_max_bitcoin_to_trade_with_balance_smaller_than_locking_fee() {
let monero = "0.00001";
let amount = Amount::parse_monero(monero).unwrap();
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
assert_eq!(bitcoin::Amount::ZERO, monero_max_from_bitcoin);
}
use rand::rngs::OsRng; use rand::rngs::OsRng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

@ -37,6 +37,10 @@ pub struct BidQuote {
pub max_quantity: bitcoin::Amount, pub max_quantity: bitcoin::Amount,
} }
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("Received quote of 0")]
pub struct ZeroQuoteReceived;
/// Constructs a new instance of the `quote` behaviour to be used by the ASB. /// Constructs a new instance of the `quote` behaviour to be used by the ASB.
/// ///
/// The ASB is always listening and only supports inbound connections, i.e. /// The ASB is always listening and only supports inbound connections, i.e.

@ -6,7 +6,7 @@ pub mod ecdsa_fun {
use ::ecdsa_fun::fun::{Point, Scalar, G}; use ::ecdsa_fun::fun::{Point, Scalar, G};
pub fn point() -> impl Strategy<Value = Point> { pub fn point() -> impl Strategy<Value = Point> {
scalar().prop_map(|mut scalar| Point::from_scalar_mul(&G, &mut scalar).mark::<Normal>()) scalar().prop_map(|mut scalar| Point::from_scalar_mul(G, &mut scalar).mark::<Normal>())
} }
pub fn scalar() -> impl Strategy<Value = Scalar> { pub fn scalar() -> impl Strategy<Value = Scalar> {

@ -659,8 +659,7 @@ impl State6 {
pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> {
let signed_tx_refund = self.signed_refund_transaction()?; let signed_tx_refund = self.signed_refund_transaction()?;
let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
subscription.wait_until_final().await?;
Ok(()) Ok(())
} }

@ -278,9 +278,12 @@ async fn next_state(
state.publish_refund_btc(bitcoin_wallet).await?; state.publish_refund_btc(bitcoin_wallet).await?;
BobState::BtcRefunded(state) BobState::BtcRefunded(state)
} }
ExpiredTimelocks::Punish => BobState::BtcPunished { ExpiredTimelocks::Punish => {
tx_lock_id: state.tx_lock_id(), tracing::info!("You have been punished for not refunding in time");
}, BobState::BtcPunished {
tx_lock_id: state.tx_lock_id(),
}
}
} }
} }
BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4), BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4),

@ -29,7 +29,7 @@ impl Image for Bitcoind {
container container
.logs() .logs()
.stdout .stdout
.wait_for_message(&"init message: Done loading") .wait_for_message("init message: Done loading")
.unwrap(); .unwrap();
} }

@ -14,7 +14,6 @@ pub struct Electrs {
entrypoint: Option<String>, entrypoint: Option<String>,
wait_for_message: String, wait_for_message: String,
volume: String, volume: String,
bitcoind_container_name: String,
} }
impl Image for Electrs { impl Image for Electrs {
@ -73,7 +72,6 @@ impl Default for Electrs {
entrypoint: Some("/build/electrs".into()), entrypoint: Some("/build/electrs".into()),
wait_for_message: "Running accept thread".to_string(), wait_for_message: "Running accept thread".to_string(),
volume: uuid::Uuid::new_v4().to_string(), volume: uuid::Uuid::new_v4().to_string(),
bitcoind_container_name: uuid::Uuid::new_v4().to_string(),
} }
} }
} }

@ -146,14 +146,14 @@ async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
let prefix = random_prefix(); let prefix = random_prefix();
let bitcoind_name = format!("{}_{}", prefix, "bitcoind"); let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
let (bitcoind, bitcoind_url) = let (bitcoind, bitcoind_url) =
init_bitcoind_container(&cli, prefix.clone(), bitcoind_name.clone(), prefix.clone()) init_bitcoind_container(cli, prefix.clone(), bitcoind_name.clone(), prefix.clone())
.await .await
.expect("could not init bitcoind"); .expect("could not init bitcoind");
let electrs = init_electrs_container(&cli, prefix.clone(), bitcoind_name, prefix) let electrs = init_electrs_container(cli, prefix.clone(), bitcoind_name, prefix)
.await .await
.expect("could not init electrs"); .expect("could not init electrs");
let (monero, monerod_container, monero_wallet_rpc_containers) = let (monero, monerod_container, monero_wallet_rpc_containers) =
Monero::new(&cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB]) Monero::new(cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB])
.await .await
.unwrap(); .unwrap();
@ -237,7 +237,7 @@ async fn start_alice(
let resume_only = false; let resume_only = false;
let mut swarm = swarm::asb( let mut swarm = swarm::asb(
&seed, seed,
min_buy, min_buy,
max_buy, max_buy,
latest_rate, latest_rate,
@ -485,7 +485,7 @@ impl BobParams {
.behaviour_mut() .behaviour_mut()
.add_address(self.alice_peer_id, self.alice_address.clone()); .add_address(self.alice_peer_id, self.alice_address.clone());
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config) cli::EventLoop::new(swap_id, swarm, self.alice_peer_id)
} }
} }

Loading…
Cancel
Save