mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-09 06:52:53 -04:00
Check error to determine if tx_cancel has already been published
If the error code suggests that the tx_cancel is already on the blockchain, continue with the protocol. This change removed the need to check for tx_cancel before attemting to publish it. Removed the tx_cancel field from the Cancelled state as it can be computed from state3. Co-authored-by: rishflab <rishflab@hotmail.com>
This commit is contained in:
parent
672377b216
commit
4903169f4c
9 changed files with 105 additions and 133 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3320,6 +3320,7 @@ dependencies = [
|
||||||
"genawaiter",
|
"genawaiter",
|
||||||
"get-port",
|
"get-port",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"jsonrpc_client",
|
||||||
"libp2p",
|
"libp2p",
|
||||||
"libp2p-tokio-socks5",
|
"libp2p-tokio-socks5",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -19,6 +19,7 @@ derivative = "2"
|
||||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "cdfbc766045ea678a41780919d6228dd5acee3be", features = ["libsecp_compat", "serde"] }
|
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "cdfbc766045ea678a41780919d6228dd5acee3be", features = ["libsecp_compat", "serde"] }
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
genawaiter = "0.99.1"
|
genawaiter = "0.99.1"
|
||||||
|
jsonrpc_client = { git = "https://github.com/thomaseizinger/rust-jsonrpc-client", rev = "c7010817e0f86ab24b3dc10d6bb0463faa0aace4", features = ["reqwest"] }
|
||||||
libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] }
|
libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] }
|
||||||
libp2p-tokio-socks5 = "0.4"
|
libp2p-tokio-socks5 = "0.4"
|
||||||
log = { version = "0.4", features = ["serde"] }
|
log = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
@ -20,8 +20,8 @@ use xmr_btc::{
|
||||||
alice::State3,
|
alice::State3,
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction,
|
poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction,
|
||||||
EncryptedSignature, GetRawTransaction, TransactionBlockHeight, TxCancel, TxLock, TxRefund,
|
EncryptedSignature, TxCancel, TxLock, TxRefund, WaitForTransactionFinality,
|
||||||
WaitForTransactionFinality, WatchForRawTransaction,
|
WatchForRawTransaction,
|
||||||
},
|
},
|
||||||
config::Config,
|
config::Config,
|
||||||
cross_curve_dleq,
|
cross_curve_dleq,
|
||||||
|
@ -203,54 +203,6 @@ where
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish_cancel_transaction<W>(
|
|
||||||
tx_lock: TxLock,
|
|
||||||
a: bitcoin::SecretKey,
|
|
||||||
B: bitcoin::PublicKey,
|
|
||||||
refund_timelock: u32,
|
|
||||||
tx_cancel_sig_bob: bitcoin::Signature,
|
|
||||||
bitcoin_wallet: Arc<W>,
|
|
||||||
) -> Result<bitcoin::TxCancel>
|
|
||||||
where
|
|
||||||
W: GetRawTransaction + TransactionBlockHeight + BlockHeight + BroadcastSignedTransaction,
|
|
||||||
{
|
|
||||||
// First wait for t1 to expire
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), tx_lock_height + refund_timelock).await;
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B);
|
|
||||||
|
|
||||||
// If Bob hasn't yet broadcasted the tx cancel, we do it
|
|
||||||
if bitcoin_wallet
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
// TODO(Franck): Maybe the cancel transaction is already mined, in this case,
|
|
||||||
// the broadcast will error out.
|
|
||||||
|
|
||||||
let sig_a = a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO(Franck): Error handling is delicate, why can't we broadcast?
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// TODO(Franck): Wait until transaction is mined and returned mined
|
|
||||||
// block height
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tx_cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn wait_for_bitcoin_refund<W>(
|
pub async fn wait_for_bitcoin_refund<W>(
|
||||||
tx_cancel: &TxCancel,
|
tx_cancel: &TxCancel,
|
||||||
cancel_tx_height: u32,
|
cancel_tx_height: u32,
|
||||||
|
|
|
@ -6,8 +6,8 @@ use crate::{
|
||||||
steps::{
|
steps::{
|
||||||
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
||||||
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
||||||
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
publish_bitcoin_redeem_transaction, wait_for_bitcoin_encrypted_signature,
|
||||||
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bitcoin,
|
bitcoin,
|
||||||
|
@ -16,9 +16,9 @@ use crate::{
|
||||||
state,
|
state,
|
||||||
state::{Alice, Swap},
|
state::{Alice, Swap},
|
||||||
storage::Database,
|
storage::Database,
|
||||||
SwapAmounts,
|
SwapAmounts, TRANSACTION_ALREADY_IN_BLOCKCHAIN_ERROR_CODE,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{select, Either},
|
future::{select, Either},
|
||||||
|
@ -31,7 +31,7 @@ use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::{
|
use xmr_btc::{
|
||||||
alice::{State0, State3},
|
alice::{State0, State3},
|
||||||
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
bitcoin::{TransactionBlockHeight, TxRefund, WatchForRawTransaction},
|
||||||
config::Config,
|
config::Config,
|
||||||
monero::CreateWalletForOutput,
|
monero::CreateWalletForOutput,
|
||||||
Epoch,
|
Epoch,
|
||||||
|
@ -67,7 +67,6 @@ pub enum AliceState {
|
||||||
BtcRedeemed,
|
BtcRedeemed,
|
||||||
BtcCancelled {
|
BtcCancelled {
|
||||||
state3: State3,
|
state3: State3,
|
||||||
tx_cancel: TxCancel,
|
|
||||||
},
|
},
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
spend_key: monero::PrivateKey,
|
spend_key: monero::PrivateKey,
|
||||||
|
@ -167,19 +166,7 @@ impl TryFrom<state::Swap> for AliceState {
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
},
|
},
|
||||||
Alice::T1Expired(state3) => AliceState::T1Expired { state3 },
|
Alice::T1Expired(state3) => AliceState::T1Expired { state3 },
|
||||||
Alice::BtcCancelled(state) => {
|
Alice::BtcCancelled(state3) => BtcCancelled { state3 },
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
|
|
||||||
BtcCancelled {
|
|
||||||
state3: state,
|
|
||||||
tx_cancel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alice::BtcPunishable(state) => {
|
Alice::BtcPunishable(state) => {
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
&state.tx_lock,
|
&state.tx_lock,
|
||||||
|
@ -486,20 +473,22 @@ pub async fn run_until(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
AliceState::T1Expired { state3 } => {
|
AliceState::T1Expired { state3 } => {
|
||||||
let tx_cancel = publish_cancel_transaction(
|
if let Err(error) = state3.submit_tx_cancel(bitcoin_wallet.as_ref()).await {
|
||||||
state3.tx_lock.clone(),
|
let json_rpc_err = error
|
||||||
state3.a.clone(),
|
.downcast_ref::<jsonrpc_client::JsonRpcError>()
|
||||||
state3.B,
|
.ok_or_else(|| anyhow!("Failed to downcast JsonRpcError"))?;
|
||||||
state3.refund_timelock,
|
if json_rpc_err.code == TRANSACTION_ALREADY_IN_BLOCKCHAIN_ERROR_CODE {
|
||||||
state3.tx_cancel_sig_bob.clone(),
|
info!("Failed to send cancel transaction, assuming that is was already published by the other party...");
|
||||||
bitcoin_wallet.clone(),
|
} else {
|
||||||
)
|
return Err(error);
|
||||||
.await?;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
let state = AliceState::BtcCancelled { state3 };
|
||||||
let db_state = (&state).into();
|
let db_state = (&state).into();
|
||||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
run_until(
|
run_until(
|
||||||
state,
|
state,
|
||||||
is_target_state,
|
is_target_state,
|
||||||
|
@ -512,7 +501,8 @@ pub async fn run_until(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
AliceState::BtcCancelled { state3, tx_cancel } => {
|
AliceState::BtcCancelled { state3 } => {
|
||||||
|
let tx_cancel = state3.tx_cancel();
|
||||||
let tx_cancel_height = bitcoin_wallet
|
let tx_cancel_height = bitcoin_wallet
|
||||||
.transaction_block_height(tx_cancel.txid())
|
.transaction_block_height(tx_cancel.txid())
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -3,9 +3,9 @@ use crate::{
|
||||||
state,
|
state,
|
||||||
state::{Bob, Swap},
|
state::{Bob, Swap},
|
||||||
storage::Database,
|
storage::Database,
|
||||||
SwapAmounts,
|
SwapAmounts, TRANSACTION_ALREADY_IN_BLOCKCHAIN_ERROR_CODE,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::{convert::TryFrom, fmt, sync::Arc};
|
use std::{convert::TryFrom, fmt, sync::Arc};
|
||||||
|
@ -343,13 +343,17 @@ where
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
BobState::T1Expired(state4) => {
|
BobState::T1Expired(state4) => {
|
||||||
if state4
|
let result = state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await;
|
||||||
.check_for_tx_cancel(bitcoin_wallet.as_ref())
|
if let Err(error) = result {
|
||||||
.await
|
let json_rpc_err = error
|
||||||
.is_err()
|
.downcast_ref::<jsonrpc_client::JsonRpcError>()
|
||||||
{
|
.ok_or_else(|| anyhow!("Failed to downcast JsonRpcError"))?;
|
||||||
state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
if json_rpc_err.code == TRANSACTION_ALREADY_IN_BLOCKCHAIN_ERROR_CODE {
|
||||||
}
|
info!("Failed to send cancel transaction, assuming that is was already included by the other party...");
|
||||||
|
} else {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let state = BobState::Cancelled(state4);
|
let state = BobState::Cancelled(state4);
|
||||||
db.insert_latest_state(swap_id, state::Swap::Bob(state.clone().into()))
|
db.insert_latest_state(swap_id, state::Swap::Bob(state.clone().into()))
|
||||||
|
|
|
@ -15,6 +15,8 @@ pub mod trace;
|
||||||
|
|
||||||
pub type Never = std::convert::Infallible;
|
pub type Never = std::convert::Infallible;
|
||||||
|
|
||||||
|
const TRANSACTION_ALREADY_IN_BLOCKCHAIN_ERROR_CODE: i64 = -27;
|
||||||
|
|
||||||
/// Commands sent from Bob to the main task.
|
/// Commands sent from Bob to the main task.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
|
|
|
@ -28,7 +28,8 @@ use std::{
|
||||||
use tokio::{sync::Mutex, time::timeout};
|
use tokio::{sync::Mutex, time::timeout};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
pub mod message;
|
pub mod message;
|
||||||
use crate::bitcoin::{BlockHeight, TransactionBlockHeight};
|
use crate::bitcoin::{BlockHeight, TransactionBlockHeight, TxCancel};
|
||||||
|
use ::bitcoin::Txid;
|
||||||
pub use message::{Message, Message0, Message1, Message2};
|
pub use message::{Message, Message0, Message1, Message2};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -710,6 +711,25 @@ impl State3 {
|
||||||
(false, false) => Ok(Epoch::T2),
|
(false, false) => Ok(Epoch::T2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tx_cancel(&self) -> TxCancel {
|
||||||
|
crate::bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.a.public(), self.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn submit_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Txid>
|
||||||
|
where
|
||||||
|
W: BroadcastSignedTransaction,
|
||||||
|
{
|
||||||
|
crate::bitcoin::publish_cancel_transaction(
|
||||||
|
self.tx_lock.clone(),
|
||||||
|
self.a.clone(),
|
||||||
|
self.B,
|
||||||
|
self.refund_timelock,
|
||||||
|
self.tx_punish_sig_bob.clone(),
|
||||||
|
bitcoin_wallet,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
|
|
@ -243,3 +243,37 @@ where
|
||||||
tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
|
tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn publish_cancel_transaction<W>(
|
||||||
|
tx_lock: TxLock,
|
||||||
|
secret_key: crate::bitcoin::SecretKey,
|
||||||
|
public_key: crate::bitcoin::PublicKey,
|
||||||
|
refund_timelock: u32,
|
||||||
|
tx_cancel_sig: crate::bitcoin::Signature,
|
||||||
|
bitcoin_wallet: &W,
|
||||||
|
) -> Result<Txid>
|
||||||
|
where
|
||||||
|
W: BroadcastSignedTransaction,
|
||||||
|
{
|
||||||
|
let tx_cancel =
|
||||||
|
crate::bitcoin::TxCancel::new(&tx_lock, refund_timelock, public_key, secret_key.public());
|
||||||
|
|
||||||
|
let sig_theirs = tx_cancel_sig.clone();
|
||||||
|
let sig_ours = secret_key.sign(tx_cancel.digest());
|
||||||
|
|
||||||
|
let tx_cancel_txn = tx_cancel
|
||||||
|
.clone()
|
||||||
|
.add_signatures(
|
||||||
|
&tx_lock,
|
||||||
|
(public_key, sig_theirs),
|
||||||
|
(secret_key.public(), sig_ours),
|
||||||
|
)
|
||||||
|
.expect(
|
||||||
|
"sig_{a,b} to be valid signatures for
|
||||||
|
tx_cancel",
|
||||||
|
);
|
||||||
|
|
||||||
|
bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(tx_cancel_txn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
|
@ -34,10 +34,10 @@ use tracing::error;
|
||||||
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight},
|
bitcoin::{BlockHeight, Network, TransactionBlockHeight},
|
||||||
monero::{CreateWalletForOutput, WatchForTransfer},
|
monero::{CreateWalletForOutput, WatchForTransfer},
|
||||||
};
|
};
|
||||||
use ::bitcoin::{Transaction, Txid};
|
use ::bitcoin::Txid;
|
||||||
pub use message::{Message, Message0, Message1, Message2, Message3};
|
pub use message::{Message, Message0, Message1, Message2, Message3};
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
@ -660,51 +660,19 @@ impl State4 {
|
||||||
self.b.encsign(self.S_a_bitcoin, tx_redeem.digest())
|
self.b.encsign(self.S_a_bitcoin, tx_redeem.digest())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_for_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Transaction>
|
|
||||||
where
|
|
||||||
W: GetRawTransaction,
|
|
||||||
{
|
|
||||||
let tx_cancel =
|
|
||||||
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public());
|
|
||||||
|
|
||||||
let sig_a = self.tx_cancel_sig_a.clone();
|
|
||||||
let sig_b = self.b.sign(tx_cancel.digest());
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b))
|
|
||||||
.expect(
|
|
||||||
"sig_{a,b} to be valid signatures for
|
|
||||||
tx_cancel",
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?;
|
|
||||||
|
|
||||||
Ok(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn submit_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Txid>
|
pub async fn submit_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Txid>
|
||||||
where
|
where
|
||||||
W: BroadcastSignedTransaction,
|
W: BroadcastSignedTransaction,
|
||||||
{
|
{
|
||||||
let tx_cancel =
|
crate::bitcoin::publish_cancel_transaction(
|
||||||
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public());
|
self.tx_lock.clone(),
|
||||||
|
self.b.clone(),
|
||||||
let sig_a = self.tx_cancel_sig_a.clone();
|
self.A,
|
||||||
let sig_b = self.b.sign(tx_cancel.digest());
|
self.refund_timelock,
|
||||||
|
self.tx_cancel_sig_a.clone(),
|
||||||
let tx_cancel = tx_cancel
|
bitcoin_wallet,
|
||||||
.clone()
|
)
|
||||||
.add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b))
|
.await
|
||||||
.expect(
|
|
||||||
"sig_{a,b} to be valid signatures for
|
|
||||||
tx_cancel",
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx_id = bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
Ok(tx_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_for_redeem_btc<W>(&self, bitcoin_wallet: &W) -> Result<State5>
|
pub async fn watch_for_redeem_btc<W>(&self, bitcoin_wallet: &W) -> Result<State5>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue