107: Ensure that Bob can cancel correctly if T1 expired and Alice did not … r=da-kami a=da-kami

Bob has to check for the possibility to cancel in every state after he locked the BTC.
Otherwise Bob will try to perform actions that don't have any point and it might be impossible to use the `resume` command because it will always fail in trying to go on with Alice even though that might not be possible.

Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
bors[bot] 2020-12-23 01:16:42 +00:00 committed by GitHub
commit 9c83ca52ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 51 deletions

View File

@ -429,6 +429,10 @@ pub async fn run_until(
state3,
encrypted_signature,
} => {
// TODO: Evaluate if it is correct for Alice to Redeem no matter what.
// If T1 expired she should potentially not try redeem. (The implementation
// gives her an advantage.)
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
encrypted_signature,
&state3.tx_lock,

View File

@ -223,16 +223,40 @@ where
// Bob has locked Btc
// Watch for Alice to Lock Xmr or for t1 to elapse
BobState::BtcLocked(state3) => {
// TODO(Franck): Refund if cannot connect to Alice.
let state = if let Epoch::T0 = state3.current_epoch(bitcoin_wallet.as_ref()).await?
{
event_loop_handle.dial().await?;
// todo: watch until t1, not indefinitely
let msg2 = event_loop_handle.recv_message2().await?;
let state4 = state3
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2)
.await?;
let msg2_watcher = event_loop_handle.recv_message2();
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref());
let state = BobState::XmrLocked(state4);
select! {
msg2 = msg2_watcher => {
let xmr_lock_watcher = state3.clone()
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2?);
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref());
select! {
state4 = xmr_lock_watcher => {
BobState::XmrLocked(state4?)
},
_ = t1_timeout => {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
}
}
},
_ = t1_timeout => {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
}
}
} else {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
};
let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
.await?;
@ -249,10 +273,8 @@ where
.await
}
BobState::XmrLocked(state) => {
// TODO(Franck): Refund if cannot connect to Alice.
event_loop_handle.dial().await?;
let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? {
event_loop_handle.dial().await?;
// Alice has locked Xmr
// Bob sends Alice his key
let tx_redeem_encsig = state.tx_redeem_encsig();

View File

@ -28,7 +28,7 @@ use std::{
use tokio::{sync::Mutex, time::timeout};
use tracing::{error, info};
pub mod message;
use crate::bitcoin::{BlockHeight, TransactionBlockHeight};
use crate::bitcoin::{current_epoch, wait_for_t1, BlockHeight, TransactionBlockHeight};
pub use message::{Message, Message0, Message1, Message2};
#[derive(Debug)]
@ -684,31 +684,20 @@ impl State3 {
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let tx_id = self.tx_lock.txid();
let tx_lock_height = bitcoin_wallet.transaction_block_height(tx_id).await;
let t1_timeout =
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + self.refund_timelock);
t1_timeout.await;
Ok(())
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await
}
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let current_block_height = bitcoin_wallet.block_height().await;
let t0 = bitcoin_wallet
.transaction_block_height(self.tx_lock.txid())
.await;
let t1 = t0 + self.refund_timelock;
let t2 = t1 + self.punish_timelock;
match (current_block_height < t1, current_block_height < t2) {
(true, _) => Ok(Epoch::T0),
(false, true) => Ok(Epoch::T1),
(false, false) => Ok(Epoch::T2),
}
current_epoch(
bitcoin_wallet,
self.refund_timelock,
self.punish_timelock,
self.tx_lock.txid(),
)
.await
}
}

View File

@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::str::FromStr;
use crate::Epoch;
pub use bitcoin::{util::psbt::PartiallySignedTransaction, *};
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
@ -243,3 +244,40 @@ where
tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
}
}
pub async fn current_epoch<W>(
bitcoin_wallet: &W,
refund_timelock: u32,
punish_timelock: u32,
lock_tx_id: ::bitcoin::Txid,
) -> anyhow::Result<Epoch>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let current_block_height = bitcoin_wallet.block_height().await;
let t0 = bitcoin_wallet.transaction_block_height(lock_tx_id).await;
let t1 = t0 + refund_timelock;
let t2 = t1 + punish_timelock;
match (current_block_height < t1, current_block_height < t2) {
(true, _) => Ok(Epoch::T0),
(false, true) => Ok(Epoch::T1),
(false, false) => Ok(Epoch::T2),
}
}
pub async fn wait_for_t1<W>(
bitcoin_wallet: &W,
refund_timelock: u32,
lock_tx_id: ::bitcoin::Txid,
) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await;
let t1_timeout =
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + refund_timelock);
t1_timeout.await;
Ok(())
}

View File

@ -34,7 +34,9 @@ use tracing::error;
pub mod message;
use crate::{
bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight},
bitcoin::{
current_epoch, wait_for_t1, BlockHeight, GetRawTransaction, Network, TransactionBlockHeight,
},
monero::{CreateWalletForOutput, WatchForTransfer},
};
use ::bitcoin::{Transaction, Txid};
@ -621,9 +623,50 @@ impl State3 {
})
}
pub async fn wait_for_t1<W>(&self, bitcoin_wallet: &W) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await
}
pub fn t1_expired(&self) -> State4 {
State4 {
A: self.A,
b: self.b.clone(),
s_b: self.s_b,
S_a_monero: self.S_a_monero,
S_a_bitcoin: self.S_a_bitcoin,
v: self.v,
btc: self.btc,
xmr: self.xmr,
refund_timelock: self.refund_timelock,
punish_timelock: self.punish_timelock,
refund_address: self.refund_address.clone(),
redeem_address: self.redeem_address.clone(),
punish_address: self.punish_address.clone(),
tx_lock: self.tx_lock.clone(),
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
tx_refund_encsig: self.tx_refund_encsig.clone(),
}
}
pub fn tx_lock_id(&self) -> bitcoin::Txid {
self.tx_lock.txid()
}
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
current_epoch(
bitcoin_wallet,
self.refund_timelock,
self.punish_timelock,
self.tx_lock.txid(),
)
.await
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
@ -748,31 +791,20 @@ impl State4 {
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let tx_id = self.tx_lock.txid();
let tx_lock_height = bitcoin_wallet.transaction_block_height(tx_id).await;
let t1_timeout =
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + self.refund_timelock);
t1_timeout.await;
Ok(())
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await
}
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch>
where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{
let current_block_height = bitcoin_wallet.block_height().await;
let t0 = bitcoin_wallet
.transaction_block_height(self.tx_lock.txid())
.await;
let t1 = t0 + self.refund_timelock;
let t2 = t1 + self.punish_timelock;
match (current_block_height < t1, current_block_height < t2) {
(true, _) => Ok(Epoch::T0),
(false, true) => Ok(Epoch::T1),
(false, false) => Ok(Epoch::T2),
}
current_epoch(
bitcoin_wallet,
self.refund_timelock,
self.punish_timelock,
self.tx_lock.txid(),
)
.await
}
pub async fn refund_btc<W: bitcoin::BroadcastSignedTransaction>(