Merge branch 'master' into update-monero-17.3.0

This commit is contained in:
Byron Hambly 2022-04-16 14:08:11 +02:00 committed by GitHub
commit cf248ae1e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 572 additions and 331 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

461
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")]

View File

@ -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,
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(())
} }

View File

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

View File

@ -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();
} }

View File

@ -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(),
} }
} }
} }

View File

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