Introduce dedicated bob::State6 for cancelling

This commit is contained in:
Thomas Eizinger 2021-03-18 14:23:55 +11:00
parent c32ef92cf5
commit 338f4b82e5
No known key found for this signature in database
GPG Key ID: 651AC83A6C6C8B96
5 changed files with 127 additions and 87 deletions

View File

@ -29,8 +29,8 @@ pub enum Bob {
state4: bob::State4, state4: bob::State4,
}, },
BtcRedeemed(bob::State5), BtcRedeemed(bob::State5),
CancelTimelockExpired(bob::State4), CancelTimelockExpired(bob::State6),
BtcCancelled(bob::State4), BtcCancelled(bob::State6),
Done(BobEndState), Done(BobEndState),
} }
@ -38,7 +38,7 @@ pub enum Bob {
pub enum BobEndState { pub enum BobEndState {
SafelyAborted, SafelyAborted,
XmrRedeemed { tx_lock_id: bitcoin::Txid }, XmrRedeemed { tx_lock_id: bitcoin::Txid },
BtcRefunded(Box<bob::State4>), BtcRefunded(Box<bob::State6>),
BtcPunished { tx_lock_id: bitcoin::Txid }, BtcPunished { tx_lock_id: bitcoin::Txid },
} }
@ -60,9 +60,9 @@ impl From<BobState> for Bob {
BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, BobState::XmrLocked(state4) => Bob::XmrLocked { state4 },
BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, BobState::EncSigSent(state4) => Bob::EncSigSent { state4 },
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4), BobState::CancelTimelockExpired(state6) => Bob::CancelTimelockExpired(state6),
BobState::BtcCancelled(state4) => Bob::BtcCancelled(state4), BobState::BtcCancelled(state6) => Bob::BtcCancelled(state6),
BobState::BtcRefunded(state4) => Bob::Done(BobEndState::BtcRefunded(Box::new(state4))), BobState::BtcRefunded(state6) => Bob::Done(BobEndState::BtcRefunded(Box::new(state6))),
BobState::XmrRedeemed { tx_lock_id } => { BobState::XmrRedeemed { tx_lock_id } => {
Bob::Done(BobEndState::XmrRedeemed { tx_lock_id }) Bob::Done(BobEndState::XmrRedeemed { tx_lock_id })
} }
@ -92,12 +92,12 @@ impl From<Bob> for BobState {
Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), Bob::XmrLocked { state4 } => BobState::XmrLocked(state4),
Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), Bob::EncSigSent { state4 } => BobState::EncSigSent(state4),
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4), Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6),
Bob::BtcCancelled(state4) => BobState::BtcCancelled(state4), Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6),
Bob::Done(end_state) => match end_state { Bob::Done(end_state) => match end_state {
BobEndState::SafelyAborted => BobState::SafelyAborted, BobEndState::SafelyAborted => BobState::SafelyAborted,
BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
BobEndState::BtcRefunded(state4) => BobState::BtcRefunded(*state4), BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6),
BobEndState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id }, BobEndState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id },
}, },
} }

View File

@ -20,12 +20,12 @@ pub async fn cancel(
db: Database, db: Database,
force: bool, force: bool,
) -> Result<Result<(Txid, BobState), Error>> { ) -> Result<Result<(Txid, BobState), Error>> {
let state4 = match state { let state6 = match state {
BobState::BtcLocked(state3) => state3.cancel(), BobState::BtcLocked(state3) => state3.cancel(),
BobState::XmrLockProofReceived { state, .. } => state.cancel(), BobState::XmrLockProofReceived { state, .. } => state.cancel(),
BobState::XmrLocked(state4) => state4, BobState::XmrLocked(state4) => state4.cancel(),
BobState::EncSigSent(state4) => state4, BobState::EncSigSent(state4) => state4.cancel(),
BobState::CancelTimelockExpired(state4) => state4, BobState::CancelTimelockExpired(state6) => state6,
_ => bail!( _ => bail!(
"Cannot cancel swap {} because it is in state {} which is not refundable.", "Cannot cancel swap {} because it is in state {} which is not refundable.",
swap_id, swap_id,
@ -34,16 +34,16 @@ pub async fn cancel(
}; };
if !force { if !force {
if let ExpiredTimelocks::None = state4.expired_timelock(bitcoin_wallet.as_ref()).await? { if let ExpiredTimelocks::None = state6.expired_timelock(bitcoin_wallet.as_ref()).await? {
return Ok(Err(Error::CancelTimelockNotExpiredYet)); return Ok(Err(Error::CancelTimelockNotExpiredYet));
} }
if state4 if state6
.check_for_tx_cancel(bitcoin_wallet.as_ref()) .check_for_tx_cancel(bitcoin_wallet.as_ref())
.await .await
.is_ok() .is_ok()
{ {
let state = BobState::BtcCancelled(state4); let state = BobState::BtcCancelled(state6);
let db_state = state.into(); let db_state = state.into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
@ -51,9 +51,9 @@ pub async fn cancel(
} }
} }
let txid = state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; let txid = state6.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
let state = BobState::BtcCancelled(state4); let state = BobState::BtcCancelled(state6);
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;

View File

@ -16,14 +16,14 @@ pub async fn refund(
db: Database, db: Database,
force: bool, force: bool,
) -> Result<Result<BobState, SwapNotCancelledYet>> { ) -> Result<Result<BobState, SwapNotCancelledYet>> {
let state4 = if force { let state6 = if force {
match state { match state {
BobState::BtcLocked(state3) => state3.cancel(), BobState::BtcLocked(state3) => state3.cancel(),
BobState::XmrLockProofReceived { state, .. } => state.cancel(), BobState::XmrLockProofReceived { state, .. } => state.cancel(),
BobState::XmrLocked(state4) => state4, BobState::XmrLocked(state4) => state4.cancel(),
BobState::EncSigSent(state4) => state4, BobState::EncSigSent(state4) => state4.cancel(),
BobState::CancelTimelockExpired(state4) => state4, BobState::CancelTimelockExpired(state6) => state6,
BobState::BtcCancelled(state4) => state4, BobState::BtcCancelled(state6) => state6,
_ => bail!( _ => bail!(
"Cannot refund swap {} because it is in state {} which is not refundable.", "Cannot refund swap {} because it is in state {} which is not refundable.",
swap_id, swap_id,
@ -32,16 +32,16 @@ pub async fn refund(
} }
} else { } else {
match state { match state {
BobState::BtcCancelled(state4) => state4, BobState::BtcCancelled(state6) => state6,
_ => { _ => {
return Ok(Err(SwapNotCancelledYet(swap_id))); return Ok(Err(SwapNotCancelledYet(swap_id)));
} }
} }
}; };
state4.refund_btc(bitcoin_wallet.as_ref()).await?; state6.refund_btc(bitcoin_wallet.as_ref()).await?;
let state = BobState::BtcRefunded(state4); let state = BobState::BtcRefunded(state6);
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;

View File

@ -34,9 +34,9 @@ pub enum BobState {
XmrLocked(State4), XmrLocked(State4),
EncSigSent(State4), EncSigSent(State4),
BtcRedeemed(State5), BtcRedeemed(State5),
CancelTimelockExpired(State4), CancelTimelockExpired(State6),
BtcCancelled(State4), BtcCancelled(State6),
BtcRefunded(State4), BtcRefunded(State6),
XmrRedeemed { XmrRedeemed {
tx_lock_id: bitcoin::Txid, tx_lock_id: bitcoin::Txid,
}, },
@ -357,23 +357,17 @@ impl State3 {
Ok(()) Ok(())
} }
pub fn cancel(&self) -> State4 { pub fn cancel(&self) -> State6 {
State4 { State6 {
A: self.A, A: self.A,
b: self.b.clone(), b: self.b.clone(),
s_b: self.s_b, s_b: self.s_b,
S_a_bitcoin: self.S_a_bitcoin,
v: self.v,
cancel_timelock: self.cancel_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address.clone(), refund_address: self.refund_address.clone(),
redeem_address: self.redeem_address.clone(),
tx_lock: self.tx_lock.clone(), tx_lock: self.tx_lock.clone(),
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
tx_refund_encsig: self.tx_refund_encsig.clone(), tx_refund_encsig: self.tx_refund_encsig.clone(),
// For cancel scenarios the monero wallet rescan blockchain height is irrelevant for
// Bob, because Bob's cancel can only lead to refunding on Bitcoin
monero_wallet_restore_blockheight: BlockHeight { height: 0 },
} }
} }
@ -428,29 +422,6 @@ 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(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Transaction> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?;
Ok(tx)
}
pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<Txid> {
let transaction =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public())
.complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone())
.context("Failed to complete Bitcoin cancel transaction")?;
let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?;
Ok(tx_id)
}
pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<State5> { pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<State5> {
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address); let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest()); let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest());
@ -505,29 +476,18 @@ impl State4 {
)) ))
} }
pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { pub fn cancel(self) -> State6 {
let tx_cancel = State6 {
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); A: self.A,
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); b: self.b,
s_b: self.s_b,
let adaptor = Adaptor::<HashTranscript<Sha256>, Deterministic<Sha256>>::default(); cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock,
let sig_b = self.b.sign(tx_refund.digest()); refund_address: self.refund_address,
let sig_a = tx_lock: self.tx_lock,
adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone()); tx_cancel_sig_a: self.tx_cancel_sig_a,
tx_refund_encsig: self.tx_refund_encsig,
let signed_tx_refund = }
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
let (_, finality) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
finality.await?;
Ok(())
}
pub fn tx_lock_id(&self) -> bitcoin::Txid {
self.tx_lock.txid()
} }
} }
@ -559,3 +519,83 @@ impl State5 {
self.tx_lock.txid() self.tx_lock.txid()
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct State6 {
A: bitcoin::PublicKey,
b: bitcoin::SecretKey,
s_b: monero::Scalar,
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
refund_address: bitcoin::Address,
tx_lock: bitcoin::TxLock,
tx_cancel_sig_a: Signature,
tx_refund_encsig: bitcoin::EncryptedSignature,
}
impl State6 {
pub async fn expired_timelock(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<ExpiredTimelocks> {
let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?;
let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?;
Ok(current_epoch(
self.cancel_timelock,
self.punish_timelock,
tx_lock_status,
tx_cancel_status,
))
}
pub async fn check_for_tx_cancel(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Transaction> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?;
Ok(tx)
}
pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<Txid> {
let transaction =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public())
.complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone())
.context("Failed to complete Bitcoin cancel transaction")?;
let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?;
Ok(tx_id)
}
pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
let adaptor = Adaptor::<HashTranscript<Sha256>, Deterministic<Sha256>>::default();
let sig_b = self.b.sign(tx_refund.digest());
let sig_a =
adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone());
let signed_tx_refund =
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
let (_, finality) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
finality.await?;
Ok(())
}
pub fn tx_lock_id(&self) -> bitcoin::Txid {
self.tx_lock.txid()
}
}

View File

@ -184,11 +184,11 @@ async fn run_until_internal(
BobState::EncSigSent(state) BobState::EncSigSent(state)
}, },
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
BobState::CancelTimelockExpired(state) BobState::CancelTimelockExpired(state.cancel())
} }
} }
} else { } else {
BobState::CancelTimelockExpired(state) BobState::CancelTimelockExpired(state.cancel())
} }
} }
BobState::EncSigSent(state) => { BobState::EncSigSent(state) => {
@ -198,11 +198,11 @@ async fn run_until_internal(
BobState::BtcRedeemed(state5?) BobState::BtcRedeemed(state5?)
}, },
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { _ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
BobState::CancelTimelockExpired(state) BobState::CancelTimelockExpired(state.cancel())
} }
} }
} else { } else {
BobState::CancelTimelockExpired(state) BobState::CancelTimelockExpired(state.cancel())
} }
} }
BobState::BtcRedeemed(state) => { BobState::BtcRedeemed(state) => {