Trigger refund if the publication of Monero TxLock takes too long

This commit is contained in:
Lucas Soriano del Pino 2020-10-15 18:34:55 +11:00
parent 08be87747f
commit ba3011a9c9
3 changed files with 61 additions and 13 deletions

View File

@ -12,6 +12,7 @@ cross-curve-dleq = { git = "https://github.com/comit-network/cross-curve-dleq",
curve25519-dalek = "2" curve25519-dalek = "2"
ecdsa_fun = { version = "0.3.1", features = ["libsecp_compat"] } ecdsa_fun = { version = "0.3.1", features = ["libsecp_compat"] }
ed25519-dalek = "1.0.0-pre.4" # Cannot be 1 because they depend on curve25519-dalek version 3 ed25519-dalek = "1.0.0-pre.4" # Cannot be 1 because they depend on curve25519-dalek version 3
futures = "0.3"
genawaiter = "0.99.1" genawaiter = "0.99.1"
miniscript = "1" miniscript = "1"
monero = "0.9" monero = "0.9"

View File

@ -51,7 +51,9 @@ pub mod bob;
pub mod monero; pub mod monero;
pub mod transport; pub mod transport;
use async_trait::async_trait;
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic}; use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
use futures::future::Either;
use genawaiter::sync::{Gen, GenBoxed}; use genawaiter::sync::{Gen, GenBoxed};
use sha2::Sha256; use sha2::Sha256;
@ -75,6 +77,11 @@ pub trait ReceiveTransferProof {
fn receive_transfer_proof(&self) -> monero::TransferProof; fn receive_transfer_proof(&self) -> monero::TransferProof;
} }
#[async_trait]
pub trait MedianTime {
async fn median_time(&self) -> u32;
}
/// Perform the on-chain protocol to swap monero and bitcoin as Bob. /// Perform the on-chain protocol to swap monero and bitcoin as Bob.
/// ///
/// This is called post handshake, after all the keys, addresses and most of the /// This is called post handshake, after all the keys, addresses and most of the
@ -104,16 +111,31 @@ pub fn action_generator_bob<N, M, B>(
where where
N: ReceiveTransferProof + Send + Sync, N: ReceiveTransferProof + Send + Sync,
M: monero::WatchForTransfer + Send + Sync, M: monero::WatchForTransfer + Send + Sync,
B: bitcoin::WatchForRawTransaction + Send + Sync, B: MedianTime + bitcoin::WatchForRawTransaction + Send + Sync,
{ {
enum SwapFailedRefund { enum SwapFailed {
TimelockReached,
InsufficientXMR(monero::InsufficientFunds), InsufficientXMR(monero::InsufficientFunds),
} }
async fn poll_until_bitcoin_time<B>(bitcoin_client: &B, timestamp: u32)
where
B: MedianTime,
{
loop {
if bitcoin_client.median_time().await >= timestamp {
return;
}
}
}
Gen::new_boxed(|co| async move { Gen::new_boxed(|co| async move {
let swap_result: Result<(), SwapFailedRefund> = async { let swap_result: Result<(), SwapFailed> = async {
co.yield_(Action::LockBitcoin(tx_lock.clone())).await; co.yield_(Action::LockBitcoin(tx_lock.clone())).await;
let poll_until_expiry = poll_until_bitcoin_time(bitcoin_ledger, refund_timelock);
futures::pin_mut!(poll_until_expiry);
// the source of this could be the database, this layer doesn't care // the source of this could be the database, this layer doesn't care
let transfer_proof = network.receive_transfer_proof(); let transfer_proof = network.receive_transfer_proof();
@ -122,12 +144,22 @@ where
)); ));
let S = S_a_monero + S_b_monero; let S = S_a_monero + S_b_monero;
// TODO: We should require a specific number of confirmations on the lock match futures::future::select(
// transaction monero_ledger.watch_for_transfer(
monero_ledger S,
.watch_for_transfer(S, v.public(), transfer_proof, xmr, 10) v.public(),
transfer_proof,
xmr,
monero::MIN_CONFIRMATIONS,
),
poll_until_expiry,
)
.await .await
.map_err(|e| SwapFailedRefund::InsufficientXMR(e))?; {
Either::Left((Err(e), _)) => return Err(SwapFailed::InsufficientXMR(e)),
Either::Right(_) => return Err(SwapFailed::TimelockReached),
_ => {}
}
let tx_redeem = bitcoin::TxRedeem::new(&tx_lock, &redeem_address); let tx_redeem = bitcoin::TxRedeem::new(&tx_lock, &redeem_address);
let tx_redeem_encsig = b.encsign(S_a_bitcoin.clone(), tx_redeem.digest()); let tx_redeem_encsig = b.encsign(S_a_bitcoin.clone(), tx_redeem.digest());
@ -166,8 +198,6 @@ where
} }
.await; .await;
// NOTE: swap result should only be `Err` if we have reached the
// `refund_timelock`. Therefore, we should always yield the refund action
if swap_result.is_err() { if swap_result.is_err() {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&tx_lock, refund_timelock, A.clone(), b.public()); bitcoin::TxCancel::new(&tx_lock, refund_timelock, A.clone(), b.public());

View File

@ -1,12 +1,16 @@
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use backoff::{future::FutureOperation as _, ExponentialBackoff};
use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid}; use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid};
use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind}; use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind};
use reqwest::Url; use reqwest::Url;
use std::time::Duration; use std::time::Duration;
use tokio::time; use tokio::time;
use xmr_btc::bitcoin::{ use xmr_btc::{
bitcoin::{
BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, WatchForRawTransaction, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, WatchForRawTransaction,
},
MedianTime,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -117,3 +121,16 @@ impl WatchForRawTransaction for Wallet {
} }
} }
} }
#[async_trait]
impl MedianTime for Wallet {
async fn median_time(&self) -> u32 {
(|| async { Ok(self.0.median_time().await?) })
.retry(ExponentialBackoff {
max_elapsed_time: None,
..Default::default()
})
.await
.expect("transient errors to be retried")
}
}