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 }}
steps:
- name: Checkout tagged commit
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3.0.1
with:
ref: ${{ github.event.release.target_commitish }}
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
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
@ -67,9 +67,9 @@ jobs:
run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help
# Remove once python 3 is the default
- uses: actions/setup-python@v2.3.1
- uses: actions/setup-python@v3
with:
python-version: '3.x'
python-version: "3.x"
- id: create-archive-name
shell: python # Use python to have a prettier name for the archive on Windows.

@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- 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
uses: dprint/check@v2.0
@ -42,9 +42,9 @@ jobs:
steps:
- 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
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
@ -75,9 +75,9 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- 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
run: cargo build --tests --workspace --all-features
@ -111,9 +111,9 @@ jobs:
steps:
- 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 }}
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/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3.0.1
- name: Extract version from branch name
id: extract-version

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

@ -10,7 +10,7 @@ jobs:
name: Create preview release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3.0.1
- name: Delete 'preview' release
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
- Change Monero nodes to [Rino tool nodes](https://community.rino.io/nodes.html)
- Revert logs to use rfc3339 local time formatting.
- 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
@ -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.
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.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

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

@ -18,8 +18,8 @@
"includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"],
"excludes": [ "target/" ],
"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://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"
version = "0.1.0"
authors = [ "CoBloX Team <team@coblox.tech>" ]
edition = "2018"
edition = "2021"
publish = false
[dependencies]

@ -13,7 +13,7 @@ pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
/// this doesn't matter.
pub const RPC_PORT: u16 = 18081;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Monerod {
args: MonerodArgs,
}
@ -25,7 +25,7 @@ impl Image for Monerod {
type EntryPoint = str;
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>) {
@ -58,15 +58,7 @@ impl Image for Monerod {
}
}
impl Default for Monerod {
fn default() -> Self {
Self {
args: MonerodArgs::default(),
}
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct MoneroWalletRpc {
args: MoneroWalletRpcArgs,
}
@ -78,7 +70,7 @@ impl Image for MoneroWalletRpc {
type EntryPoint = str;
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>) {
@ -111,14 +103,6 @@ impl Image for MoneroWalletRpc {
}
}
impl Default for MoneroWalletRpc {
fn default() -> Self {
Self {
args: MoneroWalletRpcArgs::default(),
}
}
}
impl MoneroWalletRpc {
pub fn new(name: &str, daemon_address: String) -> Self {
Self {

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

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

@ -184,12 +184,7 @@ pub struct Refreshed {
#[derive(Debug, Clone, Deserialize)]
pub struct SweepAll {
amount_list: Vec<u64>,
fee_list: Vec<u64>,
multisig_txset: String,
pub tx_hash_list: Vec<String>,
unsigned_txset: String,
weight_list: Vec<u32>,
}
#[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]
@ -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"
version = "0.1.0"
authors = [ "CoBloX Team <team@coblox.tech>" ]
edition = "2018"
edition = "2021"
[dependencies]
anyhow = "1"

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

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

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

@ -276,7 +276,7 @@ pub enum RawCommand {
WithdrawBtc {
#[structopt(
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>,
#[structopt(long = "address", help = "The address to receive the Bitcoin.")]

@ -319,13 +319,46 @@ where
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
) -> Result<BidQuote> {
let rate = self
let ask_price = self
.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 {
price: rate.ask().context("Failed to compute asking price")?,
price: ask_price,
min_quantity: min_buy,
max_quantity: max_buy,
})

@ -29,7 +29,7 @@ use swap::cli::{list_sellers, EventLoop, SellerStatus};
use swap::database::open_db;
use swap::env::Config;
use swap::libp2p_ext::MultiAddrExt;
use swap::network::quote::BidQuote;
use swap::network::quote::{BidQuote, ZeroQuoteReceived};
use swap::network::swarm;
use swap::protocol::bob;
use swap::protocol::bob::{BobState, Swap};
@ -47,7 +47,7 @@ async fn main() -> Result<()> {
json,
cmd,
} = match parse_args_and_apply_defaults(env::args_os())? {
ParseResult::Arguments(args) => args,
ParseResult::Arguments(args) => *args,
ParseResult::PrintAndExitZero { message } => {
println!("{}", message);
std::process::exit(0);
@ -95,11 +95,11 @@ async fn main() -> Result<()> {
tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
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 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,
event_loop_handle.request_quote(),
bitcoin_wallet.new_address(),
@ -107,7 +107,16 @@ async fn main() -> Result<()> {
max_givable,
|| 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");
@ -267,8 +276,7 @@ async fn main() -> Result<()> {
.add_address(seller_peer_id, seller_address);
}
let (event_loop, event_loop_handle) =
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?;
let handle = tokio::spawn(event_loop.run());
let monero_receive_address = db.get_monero_address(swap_id).await?;
@ -556,6 +564,11 @@ where
{
tracing::debug!("Requesting quote");
let bid_quote = bid_quote.await?;
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
bail!(ZeroQuoteReceived)
}
tracing::info!(
price = %bid_quote.price,
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 {
amounts: Vec<Amount>,
call_counter: usize,

@ -171,7 +171,7 @@ pub fn verify_sig(
) -> Result<()> {
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(())
} else {
bail!(InvalidSignature)
@ -194,7 +194,7 @@ pub fn verify_encsig(
&verification_key.0,
&encryption_key.0,
&digest.into_inner(),
&encsig,
encsig,
) {
Ok(())
} 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 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 =
bdk::miniscript::Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str(&miniscript)

@ -200,9 +200,10 @@ mod tests {
#[tokio::test]
async fn bob_can_fund_without_a_change_output() {
let (A, B) = alice_and_bob();
let fees = 610;
let fees = 300;
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;
assert_eq!(
@ -262,7 +263,7 @@ mod tests {
amount: Amount,
) -> PartiallySignedTransaction {
let change = wallet.new_address().await.unwrap();
TxLock::new(&wallet, amount, A, B, change)
TxLock::new(wallet, amount, A, B, change)
.await
.unwrap()
.into()

@ -128,7 +128,7 @@ impl TxRedeem {
let sig = sigs
.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")?;
Ok(sig)

@ -132,7 +132,7 @@ impl TxRefund {
let sig = sigs
.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")?;
Ok(sig)

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

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

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

@ -271,7 +271,7 @@ impl EventLoop {
QuoteStatus::Received(Status::Online(quote)) => {
let address = self
.reachable_asb_address
.get(&peer_id)
.get(peer_id)
.expect("if we got a quote we must have stored an address");
Ok(Seller {
@ -282,7 +282,7 @@ impl EventLoop {
QuoteStatus::Received(Status::Unreachable) => {
let address = self
.unreachable_asb_address
.get(&peer_id)
.get(peer_id)
.expect("if we got a quote we must have stored an address");
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);
let file_logger = fmt::layer()
.with_ansi(false)
.with_target(false)
.with_writer(appender);
let file_logger = registry.with(
fmt::layer()
.with_ansi(false)
.with_target(false)
.json()
.with_writer(appender),
);
if json && debug {
set_global_default(
registry
.with(file_logger.json())
.with(debug_json_terminal_printer()),
)?;
set_global_default(file_logger.with(debug_json_terminal_printer()))?;
} else if json && !debug {
set_global_default(
registry
.with(file_logger.json())
.with(info_json_terminal_printer()),
)?;
set_global_default(file_logger.with(info_json_terminal_printer()))?;
} 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 {
set_global_default(registry.with(file_logger).with(info_terminal_printer()))?;
set_global_default(file_logger.with(info_terminal_printer()))?;
}
Ok(())

@ -48,7 +48,7 @@ impl GetConfig for Mainnet {
Config {
bitcoin_lock_mempool_timeout: 3.std_minutes(),
bitcoin_lock_confirmed_timeout: 2.std_hours(),
bitcoin_finality_confirmations: 2,
bitcoin_finality_confirmations: 1,
bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(72),
bitcoin_punish_timelock: PunishTimelock::new(72),
@ -65,7 +65,7 @@ impl GetConfig for Testnet {
Config {
bitcoin_lock_mempool_timeout: 3.std_minutes(),
bitcoin_lock_confirmed_timeout: 1.std_hours(),
bitcoin_finality_confirmations: 2,
bitcoin_finality_confirmations: 1,
bitcoin_avg_block_time: 10.std_minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(12),
bitcoin_punish_timelock: PunishTimelock::new(6),
@ -138,7 +138,7 @@ mod monero_network {
Network::Stagenet => "stagenet",
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| {
@ -108,8 +108,8 @@ fn to_backoff(e: connection::Error) -> backoff::Error<anyhow::Error> {
match e {
// Connection closures and websocket errors will be retried
connection::Error::ConnectionClosed => Transient(anyhow::Error::from(e)),
connection::Error::WebSocket(_) => Transient(anyhow::Error::from(e)),
connection::Error::ConnectionClosed => backoff::Error::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
// programmer error
@ -275,8 +275,6 @@ mod wire {
pub struct TickerData {
#[serde(rename = "a")]
ask: Vec<RateElement>,
#[serde(rename = "b")]
bid: Vec<RateElement>,
}
#[derive(Debug, Deserialize)]

@ -99,6 +99,20 @@ impl Amount {
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> {
let decimal = Decimal::try_from(amount)?;
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 serde::{Deserialize, Serialize};

@ -37,6 +37,10 @@ pub struct BidQuote {
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.
///
/// 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};
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> {

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

@ -278,9 +278,12 @@ async fn next_state(
state.publish_refund_btc(bitcoin_wallet).await?;
BobState::BtcRefunded(state)
}
ExpiredTimelocks::Punish => BobState::BtcPunished {
tx_lock_id: state.tx_lock_id(),
},
ExpiredTimelocks::Punish => {
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),

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

@ -14,7 +14,6 @@ pub struct Electrs {
entrypoint: Option<String>,
wait_for_message: String,
volume: String,
bitcoind_container_name: String,
}
impl Image for Electrs {
@ -73,7 +72,6 @@ impl Default for Electrs {
entrypoint: Some("/build/electrs".into()),
wait_for_message: "Running accept thread".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 bitcoind_name = format!("{}_{}", prefix, "bitcoind");
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
.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
.expect("could not init electrs");
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
.unwrap();
@ -237,7 +237,7 @@ async fn start_alice(
let resume_only = false;
let mut swarm = swarm::asb(
&seed,
seed,
min_buy,
max_buy,
latest_rate,
@ -485,7 +485,7 @@ impl BobParams {
.behaviour_mut()
.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