diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 4f466ada..662e3a5e 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -4,9 +4,6 @@ on: release: types: [created] -env: - DOCKER_IMAGE_NAME: unstoppableswap/asb - jobs: build_binaries: name: Build @@ -85,7 +82,7 @@ jobs: run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help - id: create-archive-name - shell: python + shell: python # Use python to have a prettier name for the archive on Windows. run: | import platform os_info = platform.uname() @@ -125,51 +122,3 @@ jobs: asset_path: ./${{ steps.create-archive-name.outputs.archive }} asset_name: ${{ steps.create-archive-name.outputs.archive }} asset_content_type: application/gzip - - build_and_push_docker: - name: Build and Push Docker Image - runs-on: ubuntu-latest - needs: build_binaries - steps: - - name: Checkout code - uses: actions/checkout@v4.1.7 - with: - ref: ${{ github.event.release.target_commitish }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set Docker tags - id: docker_tags - run: | - if [[ ${{ github.event.release.tag_name }} == "preview" ]]; then - echo "::set-output name=preview::true" - else - echo "::set-output name=preview::false" - fi - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - push: true - tags: | - ${{ env.DOCKER_IMAGE_NAME }}:${{ github.event.release.tag_name }} - ${{ env.DOCKER_IMAGE_NAME }}:latest - if: steps.docker_tags.outputs.preview == 'false' - - - name: Build and push Docker image without latest tag (preview release) - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ env.DOCKER_IMAGE_NAME }}:${{ github.event.release.tag_name }} - if: steps.docker_tags.outputs.preview == 'true' diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa3ad2a..bb8e53e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- ASB: The `history` command can now be used while the asb is running. +## [0.13.4] - 2024-07-25 -## [0.13.3] - 2024-07-22 +- ASB: The `history` command can now be used while the asb is running. +- ASB: Retry locking of Monero if it fails on first attempt + +## [0.13.3] - 2024-07-15 - Introduced a cooperative Monero redeem feature for Bob to request from Alice if Bob is punished for not refunding in time. Alice can choose to cooperate but is not obligated to do so. This change is backwards compatible. To attempt recovery, resume a swap in the "Bitcoin punished" state. Success depends on Alice being active and still having a record of the swap. Note that Alice's cooperation is voluntary and recovery is not guaranteed - CLI: `--change-address` can now be omitted. In that case, any change is refunded to the internal bitcoin wallet. @@ -372,10 +375,8 @@ 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/UnstoppableSwap/xmr-btc-swap/compare/0.13.3...HEAD -[0.13.3]: https://github.com/UnstoppableSwap/xmr-btc-swap/compare/0.13.3...0.13.3 -[0.13.3]: https://github.com/UnstoppableSwap/xmr-btc-swap/compare/0.13.3...0.13.3 -[0.13.3]: https://github.com/UnstoppableSwap/xmr-btc-swap/compare/0.13.3...0.13.3 +[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.4...HEAD +[0.13.4]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.3...0.13.4 [0.13.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.2...0.13.3 [0.13.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.1...0.13.2 [0.13.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.0...0.13.1 diff --git a/Cargo.lock b/Cargo.lock index 4c2826b8..6ef8e322 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4515,7 +4515,7 @@ checksum = "8049cf85f0e715d6af38dde439cb0ccb91f67fb9f5f63c80f8b43e48356e1a3f" [[package]] name = "swap" -version = "0.13.3" +version = "0.13.4" dependencies = [ "anyhow", "async-compression", diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2dbf5397..00000000 --- a/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM rust:1.59-slim AS builder - -WORKDIR /build - -RUN apt-get update -RUN apt-get install -y git clang cmake libsnappy-dev - -COPY . . - -RUN cargo build --release --bin=asb - -FROM debian:bullseye-slim - -WORKDIR /data - -COPY --from=builder /build/target/release/asb /bin/asb - -ENTRYPOINT ["asb"] diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 6af34a86..a75c29a7 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swap" -version = "0.13.3" +version = "0.13.4" authors = [ "The COMIT guys " ] edition = "2021" description = "XMR/BTC trustless atomic swaps." diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 79236563..3848a2e1 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -1,11 +1,14 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. +use std::time::Duration; + use crate::asb::{EventLoopHandle, LatestRate}; use crate::bitcoin::ExpiredTimelocks; use crate::env::Config; use crate::protocol::alice::{AliceState, Swap}; use crate::{bitcoin, monero}; use anyhow::{bail, Context, Result}; +use backoff::ExponentialBackoffBuilder; use tokio::select; use tokio::time::timeout; use uuid::Uuid; @@ -111,23 +114,63 @@ where } } AliceState::BtcLocked { state3 } => { - match state3.expired_timelocks(bitcoin_wallet).await? { - ExpiredTimelocks::None { .. } => { - // Record the current monero wallet block height so we don't have to scan from - // block 0 for scenarios where we create a refund wallet. - let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; + // We retry to lock the Monero wallet until we succeed or until the cancel timelock expires. + // + // This is necessary because the monero-wallet-rpc can sometimes error out due to various reasons, such as + // - no connection to the daemon + // - "failed to get output distribution" + // See https://github.com/comit-network/xmr-btc-swap/issues/1726 + let backoff = ExponentialBackoffBuilder::new() + .with_initial_interval(Duration::from_secs(5)) + .with_max_interval(Duration::from_secs(60 * 3)) + .with_max_elapsed_time(None) + .build(); - let transfer_proof = monero_wallet - .transfer(state3.lock_xmr_transfer_request()) - .await?; + let result = backoff::future::retry_notify( + backoff, + || async { + match state3.expired_timelocks(bitcoin_wallet).await { + Ok(ExpiredTimelocks::None { .. }) => { + // Record the current monero wallet block height so we don't have to scan from + // block 0 for scenarios where we create a refund wallet. + let monero_wallet_restore_blockheight = monero_wallet + .block_height() + .await + .map_err(backoff::Error::transient)?; + let transfer_proof = monero_wallet + .transfer(state3.lock_xmr_transfer_request()) + .await + .map_err(backoff::Error::transient)?; + + Ok(Some((monero_wallet_restore_blockheight, transfer_proof))) + } + Ok(_) => Ok(None), + Err(e) => Err(backoff::Error::transient(e)), + } + }, + |err, delay: Duration| { + tracing::warn!( + %err, + delay_secs = delay.as_secs(), + "Failed to lock XMR. We will retry after a delay" + ); + }, + ) + .await; + + match result { + Ok(Some((monero_wallet_restore_blockheight, transfer_proof))) => { AliceState::XmrLockTransactionSent { monero_wallet_restore_blockheight, transfer_proof, state3, } } - _ => AliceState::SafelyAborted, + Ok(None) => AliceState::SafelyAborted, + Err(e) => { + unreachable!("We should retry forever until the cancel timelock expires. But we got an error: {:#}", e); + } } } AliceState::XmrLockTransactionSent {