Remove Refund timelock and T0/T1/T2

There are no refund timelock, only a cancellation timelock and punish
timelock.

Refund can be done as soon as the cancellation transaction is published.
This commit is contained in:
Franck Royer 2020-12-22 15:47:09 +11:00
parent 405e377f79
commit f0736d0906
No known key found for this signature in database
GPG key ID: A82ED75A8DFC50A4
11 changed files with 194 additions and 158 deletions

View file

@ -207,20 +207,20 @@ pub async fn publish_cancel_transaction<W>(
tx_lock: TxLock,
a: bitcoin::SecretKey,
B: bitcoin::PublicKey,
refund_timelock: u32,
cancel_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
// First wait for cancel timelock 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;
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), tx_lock_height + cancel_timelock).await;
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B);
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
// If Bob hasn't yet broadcasted the tx cancel, we do it
if bitcoin_wallet
@ -306,14 +306,14 @@ pub fn extract_monero_private_key(
pub fn build_bitcoin_punish_transaction(
tx_lock: &TxLock,
refund_timelock: u32,
cancel_timelock: u32,
punish_address: &bitcoin::Address,
punish_timelock: u32,
tx_punish_sig_bob: bitcoin::Signature,
a: bitcoin::SecretKey,
B: bitcoin::PublicKey,
) -> Result<bitcoin::Transaction> {
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B);
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
let tx_punish = bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
let sig_a = a.sign(tx_punish.digest());

View file

@ -34,7 +34,7 @@ use xmr_btc::{
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
config::Config,
monero::CreateWalletForOutput,
Epoch,
ExpiredTimelocks,
};
trait Rng: RngCore + CryptoRng + Send {}
@ -78,7 +78,7 @@ pub enum AliceState {
state3: State3,
},
XmrRefunded,
T1Expired {
CancelTimelockExpired {
state3: State3,
},
Punished,
@ -100,7 +100,7 @@ impl fmt::Display for AliceState {
AliceState::SafelyAborted => write!(f, "safely_aborted"),
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"),
AliceState::XmrRefunded => write!(f, "xmr_refunded"),
AliceState::T1Expired { .. } => write!(f, "t1 is expired"),
AliceState::CancelTimelockExpired { .. } => write!(f, "cancel timelock is expired"),
}
}
}
@ -113,7 +113,7 @@ impl From<&AliceState> for state::Alice {
AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.clone()),
AliceState::EncSigLearned {
state3,
encrypted_signature,,
encrypted_signature,
} => Alice::EncSigLearned {
state: state3.clone(),
encrypted_signature: encrypted_signature.clone(),
@ -123,7 +123,9 @@ impl From<&AliceState> for state::Alice {
AliceState::BtcRefunded { .. } => Alice::SwapComplete,
AliceState::BtcPunishable { state3, .. } => Alice::BtcPunishable(state3.clone()),
AliceState::XmrRefunded => Alice::SwapComplete,
AliceState::T1Expired { state3 } => Alice::T1Expired(state3.clone()),
AliceState::CancelTimelockExpired { state3 } => {
Alice::CancelTimelockExpired(state3.clone())
}
AliceState::Punished => Alice::SwapComplete,
AliceState::SafelyAborted => Alice::SwapComplete,
// TODO: Potentially add support to resume swaps that are not Negotiated
@ -166,11 +168,13 @@ impl TryFrom<state::Swap> for AliceState {
state3: state,
encrypted_signature,
},
Alice::T1Expired(state3) => AliceState::T1Expired { state3 },
Alice::CancelTimelockExpired(state3) => {
AliceState::CancelTimelockExpired { state3 }
}
Alice::BtcCancelled(state) => {
let tx_cancel = bitcoin::TxCancel::new(
&state.tx_lock,
state.refund_timelock,
state.cancel_timelock,
state.a.public(),
state.B,
);
@ -183,7 +187,7 @@ impl TryFrom<state::Swap> for AliceState {
Alice::BtcPunishable(state) => {
let tx_cancel = bitcoin::TxCancel::new(
&state.tx_lock,
state.refund_timelock,
state.cancel_timelock,
state.a.public(),
state.B,
);
@ -386,28 +390,30 @@ pub async fn run_until(
.await
}
AliceState::XmrLocked { state3 } => {
// todo: match statement and wait for t1 can probably be expressed more cleanly
let state = match state3.current_epoch(bitcoin_wallet.as_ref()).await? {
Epoch::T0 => {
// todo: match statement and wait for cancel timelock to expire can probably be
// expressed more cleanly
let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
ExpiredTimelocks::None => {
let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature(
&mut event_loop_handle,
config.monero_max_finality_time,
);
let state3_clone = state3.clone();
let t1_timeout = state3_clone.wait_for_t1(bitcoin_wallet.as_ref());
let cancel_timelock_expires = state3_clone
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
pin_mut!(wait_for_enc_sig);
pin_mut!(t1_timeout);
pin_mut!(cancel_timelock_expires);
match select(t1_timeout, wait_for_enc_sig).await {
Either::Left(_) => AliceState::T1Expired { state3 },
match select(cancel_timelock_expires, wait_for_enc_sig).await {
Either::Left(_) => AliceState::CancelTimelockExpired { state3 },
Either::Right((enc_sig, _)) => AliceState::EncSigLearned {
state3,
encrypted_signature: enc_sig?,
},
}
}
_ => AliceState::T1Expired { state3 },
_ => AliceState::CancelTimelockExpired { state3 },
};
let db_state = (&state).into();
@ -430,8 +436,8 @@ pub async fn run_until(
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.)
// If cancel timelock expired she should potentially not try redeem. (The
// implementation gives her an advantage.)
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
encrypted_signature,
@ -443,9 +449,11 @@ pub async fn run_until(
) {
Ok(tx) => tx,
Err(_) => {
state3.wait_for_t1(bitcoin_wallet.as_ref()).await?;
state3
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref())
.await?;
let state = AliceState::T1Expired { state3 };
let state = AliceState::CancelTimelockExpired { state3 };
let db_state = (&state).into();
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?;
@ -489,12 +497,12 @@ pub async fn run_until(
)
.await
}
AliceState::T1Expired { state3 } => {
AliceState::CancelTimelockExpired { state3 } => {
let tx_cancel = publish_cancel_transaction(
state3.tx_lock.clone(),
state3.a.clone(),
state3.B,
state3.refund_timelock,
state3.cancel_timelock,
state3.tx_cancel_sig_bob.clone(),
bitcoin_wallet.clone(),
)
@ -591,7 +599,7 @@ pub async fn run_until(
AliceState::BtcPunishable { tx_refund, state3 } => {
let signed_tx_punish = build_bitcoin_punish_transaction(
&state3.tx_lock,
state3.refund_timelock,
state3.cancel_timelock,
&state3.punish_address,
state3.punish_timelock,
state3.tx_punish_sig_bob.clone(),

View file

@ -84,7 +84,7 @@ async fn main() -> Result<()> {
v_a,
amounts.btc,
amounts.xmr,
config.bitcoin_refund_timelock,
config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock,
redeem_address,
punish_address,
@ -132,7 +132,7 @@ async fn main() -> Result<()> {
&mut OsRng,
send_bitcoin,
receive_monero,
config.bitcoin_refund_timelock,
config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock,
refund_address,
);

View file

@ -14,7 +14,7 @@ use tracing::info;
use uuid::Uuid;
use xmr_btc::{
bob::{self, State2},
Epoch,
ExpiredTimelocks,
};
#[derive(Debug, Clone)]
@ -28,7 +28,7 @@ pub enum BobState {
XmrLocked(bob::State4),
EncSigSent(bob::State4),
BtcRedeemed(bob::State5),
T1Expired(bob::State4),
CancelTimelockExpired(bob::State4),
Cancelled(bob::State4),
BtcRefunded(bob::State4),
XmrRedeemed,
@ -45,7 +45,7 @@ impl fmt::Display for BobState {
BobState::XmrLocked(..) => write!(f, "xmr_locked"),
BobState::EncSigSent(..) => write!(f, "encsig_sent"),
BobState::BtcRedeemed(..) => write!(f, "btc_redeemed"),
BobState::T1Expired(..) => write!(f, "t1_expired"),
BobState::CancelTimelockExpired(..) => write!(f, "cancel_timelock_expired"),
BobState::Cancelled(..) => write!(f, "cancelled"),
BobState::BtcRefunded(..) => write!(f, "btc_refunded"),
BobState::XmrRedeemed => write!(f, "xmr_redeemed"),
@ -67,7 +67,7 @@ impl From<BobState> for state::Bob {
BobState::XmrLocked(state4) => Bob::XmrLocked { state4 },
BobState::EncSigSent(state4) => Bob::EncSigSent { state4 },
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
BobState::T1Expired(state4) => Bob::T1Expired(state4),
BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4),
BobState::Cancelled(state4) => Bob::BtcCancelled(state4),
BobState::BtcRefunded(_)
| BobState::XmrRedeemed
@ -88,7 +88,7 @@ impl TryFrom<state::Swap> for BobState {
Bob::XmrLocked { state4 } => BobState::XmrLocked(state4),
Bob::EncSigSent { state4 } => BobState::EncSigSent(state4),
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
Bob::T1Expired(state4) => BobState::T1Expired(state4),
Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4),
Bob::BtcCancelled(state4) => BobState::Cancelled(state4),
Bob::SwapComplete => BobState::SafelyAborted,
};
@ -221,41 +221,43 @@ where
.await
}
// Bob has locked Btc
// Watch for Alice to Lock Xmr or for t1 to elapse
// Watch for Alice to Lock Xmr or for cancel timelock to elapse
BobState::BtcLocked(state3) => {
let state = if let Epoch::T0 = state3.current_epoch(bitcoin_wallet.as_ref()).await?
let state = if let ExpiredTimelocks::None =
state3.current_epoch(bitcoin_wallet.as_ref()).await?
{
event_loop_handle.dial().await?;
let msg2_watcher = event_loop_handle.recv_message2();
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref());
let cancel_timelock_expires =
state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
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());
let cancel_timelock_expires = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! {
state4 = xmr_lock_watcher => {
BobState::XmrLocked(state4?)
},
_ = t1_timeout => {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
_ = cancel_timelock_expires => {
let state4 = state3.state4();
BobState::CancelTimelockExpired(state4)
}
}
},
_ = t1_timeout => {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
_ = cancel_timelock_expires => {
let state4 = state3.state4();
BobState::CancelTimelockExpired(state4)
}
}
} else {
let state4 = state3.t1_expired();
BobState::T1Expired(state4)
let state4 = state3.state4();
BobState::CancelTimelockExpired(state4)
};
let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
@ -273,7 +275,9 @@ where
.await
}
BobState::XmrLocked(state) => {
let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? {
let state = if let ExpiredTimelocks::None =
state.expired_timelock(bitcoin_wallet.as_ref()).await?
{
event_loop_handle.dial().await?;
// Alice has locked Xmr
// Bob sends Alice his key
@ -283,18 +287,19 @@ where
// TODO(Franck): Refund if message cannot be sent.
let enc_sig_sent_watcher = event_loop_handle.send_message3(tx_redeem_encsig);
let bitcoin_wallet = bitcoin_wallet.clone();
let t1_timeout = state4_clone.wait_for_t1(bitcoin_wallet.as_ref());
let cancel_timelock_expires =
state4_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! {
_ = enc_sig_sent_watcher => {
BobState::EncSigSent(state)
},
_ = t1_timeout => {
BobState::T1Expired(state)
_ = cancel_timelock_expires => {
BobState::CancelTimelockExpired(state)
}
}
} else {
BobState::T1Expired(state)
BobState::CancelTimelockExpired(state)
};
let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
@ -312,21 +317,24 @@ where
.await
}
BobState::EncSigSent(state) => {
let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? {
let state = if let ExpiredTimelocks::None =
state.expired_timelock(bitcoin_wallet.as_ref()).await?
{
let state_clone = state.clone();
let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref());
let t1_timeout = state_clone.wait_for_t1(bitcoin_wallet.as_ref());
let cancel_timelock_expires =
state_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! {
state5 = redeem_watcher => {
BobState::BtcRedeemed(state5?)
},
_ = t1_timeout => {
BobState::T1Expired(state)
_ = cancel_timelock_expires => {
BobState::CancelTimelockExpired(state)
}
}
} else {
BobState::T1Expired(state)
BobState::CancelTimelockExpired(state)
};
let db_state = state.clone().into();
@ -364,7 +372,7 @@ where
)
.await
}
BobState::T1Expired(state4) => {
BobState::CancelTimelockExpired(state4) => {
if state4
.check_for_tx_cancel(bitcoin_wallet.as_ref())
.await
@ -390,15 +398,16 @@ where
.await
}
BobState::Cancelled(state) => {
// TODO
// Bob has cancelled the swap
let state = match state.current_epoch(bitcoin_wallet.as_ref()).await? {
Epoch::T0 => panic!("Cancelled before t1??? Something is really wrong"),
Epoch::T1 => {
let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? {
ExpiredTimelocks::None => {
bail!("Internal error: canceled state reached before cancel timelock was expired");
}
ExpiredTimelocks::Cancel => {
state.refund_btc(bitcoin_wallet.as_ref()).await?;
BobState::BtcRefunded(state)
}
Epoch::T2 => BobState::Punished,
ExpiredTimelocks::Punish => BobState::Punished,
};
let db_state = state.clone().into();

View file

@ -24,7 +24,7 @@ pub enum Alice {
state: alice::State3,
encrypted_signature: EncryptedSignature,
},
T1Expired(alice::State3),
CancelTimelockExpired(alice::State3),
BtcCancelled(alice::State3),
BtcPunishable(alice::State3),
BtcRefunded {
@ -43,7 +43,7 @@ pub enum Bob {
XmrLocked { state4: bob::State4 },
EncSigSent { state4: bob::State4 },
BtcRedeemed(bob::State5),
T1Expired(bob::State4),
CancelTimelockExpired(bob::State4),
BtcCancelled(bob::State4),
SwapComplete,
}
@ -76,7 +76,7 @@ impl Display for Alice {
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
Alice::XmrLocked(_) => f.write_str("Monero locked"),
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
Alice::T1Expired(_) => f.write_str("Timelock T1 expired"),
Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"),
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
@ -92,7 +92,7 @@ impl Display for Bob {
Bob::Negotiated { .. } => f.write_str("Handshake complete"),
Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"),
Bob::XmrLocked { .. } => f.write_str("Monero locked"),
Bob::T1Expired(_) => f.write_str("Timelock T1 expired"),
Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"),
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
Bob::SwapComplete => f.write_str("Swap complete"),

View file

@ -88,7 +88,7 @@ pub async fn init_alice_state(
v_a,
amounts.btc,
amounts.xmr,
config.bitcoin_refund_timelock,
config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock,
redeem_address,
punish_address,
@ -170,7 +170,7 @@ pub async fn init_bob_state(
&mut OsRng,
btc_to_swap,
xmr_to_swap,
config.bitcoin_refund_timelock,
config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock,
refund_address,
);