fix(asb): Would silently fail if Monero refund transaction publish failed (#254)

This commit is contained in:
binarybaron 2025-01-21 14:01:57 +01:00 committed by GitHub
parent 3e6b25a0c8
commit 29da23ea60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 54 deletions

View file

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- ASB: Fixed an issue where the ASB would silently fail if the publication of the Monero refund transaction failed.
## [1.0.0-rc.12] - 2025-01-14 ## [1.0.0-rc.12] - 2025-01-14
## [1.0.0-rc.11] - 2024-12-22 ## [1.0.0-rc.11] - 2024-12-22

View file

@ -32,6 +32,7 @@ Consider joining the designated [Matrix chat](https://matrix.to/#/%23unstoppable
### Using Docker ### Using Docker
Running the ASB and its required services (Bitcoin node, Monero node, wallet RPC) can be complex to set up manually. We provide a Docker Compose solution that handles all of this automatically. See our [docker-compose repository](https://github.com/UnstoppableSwap/asb-docker-compose) for setup instructions and configuration details. Running the ASB and its required services (Bitcoin node, Monero node, wallet RPC) can be complex to set up manually. We provide a Docker Compose solution that handles all of this automatically. See our [docker-compose repository](https://github.com/UnstoppableSwap/asb-docker-compose) for setup instructions and configuration details.
## ASB Details ## ASB Details
The ASB is a long running daemon that acts as the trading partner to the swap CLI. The ASB is a long running daemon that acts as the trading partner to the swap CLI.
@ -212,6 +213,7 @@ Sparrow wallet import works as follows:
![image](transactions-tab.png) ![image](transactions-tab.png)
If the bitcoin amount in your wallet doesn't match "asb balance" output and you don't see (all) the transactions you need to increase the gap limit: If the bitcoin amount in your wallet doesn't match "asb balance" output and you don't see (all) the transactions you need to increase the gap limit:
- go to Settings > Advanced... > Gap limit - go to Settings > Advanced... > Gap limit
![image](gap-limit.png) ![image](gap-limit.png)

View file

@ -116,47 +116,37 @@ impl Wallet {
/// keys. The generated wallet will be opened, all funds sweeped to the /// keys. The generated wallet will be opened, all funds sweeped to the
/// main_address and then the wallet will be re-loaded using the internally /// main_address and then the wallet will be re-loaded using the internally
/// stored name. /// stored name.
pub async fn create_from( pub async fn create_from_keys_and_sweep(
&self, &self,
file_name: String, file_name: String,
private_spend_key: PrivateKey, private_spend_key: PrivateKey,
private_view_key: PrivateViewKey, private_view_key: PrivateViewKey,
restore_height: BlockHeight, restore_height: BlockHeight,
) -> Result<()> { ) -> Result<()> {
let public_spend_key = PublicKey::from_private_key(&private_spend_key); // Close the default wallet, generate the new wallet from the keys and load it
let public_view_key = PublicKey::from_private_key(&private_view_key.into()); self.create_from_and_load(
let temp_wallet_address =
Address::standard(self.network, public_spend_key, public_view_key);
// Close the default wallet before generating the other wallet to ensure that
// it saves its state correctly
let _ = self.inner.lock().await.close_wallet().await?;
let _ = self
.inner
.lock()
.await
.generate_from_keys(
file_name, file_name,
temp_wallet_address.to_string(), private_spend_key,
private_spend_key.to_string(), private_view_key,
PrivateKey::from(private_view_key).to_string(), restore_height,
restore_height.height,
String::from(""),
true,
) )
.await?; .await?;
// Try to send all the funds from the generated wallet to the default wallet // Refresh the generated wallet
match self.refresh(3).await { if let Err(error) = self.refresh(20).await {
Ok(_) => match self return Err(anyhow::anyhow!(error)
.context("Failed to refresh generated wallet for sweeping to default wallet"));
}
// Sweep all the funds from the generated wallet to the default wallet
let sweep_result = self
.inner .inner
.lock() .lock()
.await .await
.sweep_all(self.main_address.to_string()) .sweep_all(self.main_address.to_string())
.await .await;
{
match sweep_result {
Ok(sweep_all) => { Ok(sweep_all) => {
for tx in sweep_all.tx_hash_list { for tx in sweep_all.tx_hash_list {
tracing::info!( tracing::info!(
@ -166,15 +156,10 @@ impl Wallet {
} }
} }
Err(error) => { Err(error) => {
tracing::warn!( return Err(
address = %self.main_address, anyhow::anyhow!(error).context("Failed to transfer Monero to default wallet")
"Failed to transfer Monero to default wallet: {:#}", error
); );
} }
},
Err(error) => {
tracing::warn!("Failed to refresh generated wallet: {:#}", error);
}
} }
let _ = self let _ = self

View file

@ -522,7 +522,7 @@ impl State3 {
.await?; .await?;
monero_wallet monero_wallet
.create_from( .create_from_keys_and_sweep(
file_name, file_name,
spend_key, spend_key,
view_key, view_key,

View file

@ -427,15 +427,37 @@ where
spend_key, spend_key,
state3, state3,
} => { } => {
// We retry indefinitely to refund the Monero funds, until the refund transaction is confirmed
let backoff = backoff::ExponentialBackoffBuilder::new()
.with_max_elapsed_time(None)
.with_max_interval(Duration::from_secs(60))
.build();
backoff::future::retry_notify(
backoff,
|| async {
state3 state3
.refund_xmr( .refund_xmr(
monero_wallet, monero_wallet,
monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
swap_id.to_string(), swap_id.to_string(),
spend_key, spend_key,
transfer_proof, transfer_proof.clone(),
) )
.await?; .await
.map_err(backoff::Error::transient)
},
|e, wait_time: Duration| {
tracing::warn!(
swap_id = %swap_id,
error = ?e,
"Failed to refund Monero. We will retry in {} seconds",
wait_time.as_secs()
)
},
)
.await
.expect("We should never run out of retries while refunding Monero");
AliceState::XmrRefunded AliceState::XmrRefunded
} }