mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Introduce dedicated bob::State6 for cancelling
This commit is contained in:
parent
c32ef92cf5
commit
338f4b82e5
@ -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 },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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?;
|
||||||
|
|
||||||
|
@ -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?;
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user