mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Fix happy path test
This commit is contained in:
parent
7092af457a
commit
4b00141c29
@ -136,7 +136,7 @@ impl From<&AliceState> for state::Alice {
|
|||||||
AliceState::XmrRefunded => Alice::SwapComplete,
|
AliceState::XmrRefunded => Alice::SwapComplete,
|
||||||
// TODO(Franck): it may be more efficient to store the fact that we already want to
|
// TODO(Franck): it may be more efficient to store the fact that we already want to
|
||||||
// abort
|
// abort
|
||||||
AliceState::Cancelling { state3 } => Alice::XmrLocked(state3.clone()),
|
AliceState::Cancelling { state3 } => Alice::Cancelling(state3.clone()),
|
||||||
AliceState::Punished => Alice::SwapComplete,
|
AliceState::Punished => Alice::SwapComplete,
|
||||||
AliceState::SafelyAborted => Alice::SwapComplete,
|
AliceState::SafelyAborted => Alice::SwapComplete,
|
||||||
}
|
}
|
||||||
@ -176,6 +176,7 @@ impl TryFrom<state::Swap> for AliceState {
|
|||||||
state3: state,
|
state3: state,
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
},
|
},
|
||||||
|
Alice::Cancelling(state3) => AliceState::Cancelling { state3 },
|
||||||
Alice::BtcCancelled(state) => {
|
Alice::BtcCancelled(state) => {
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
let tx_cancel = bitcoin::TxCancel::new(
|
||||||
&state.tx_lock,
|
&state.tx_lock,
|
||||||
|
@ -19,8 +19,7 @@ use std::sync::Arc;
|
|||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use swap::{
|
use swap::{
|
||||||
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, cli::Options, monero,
|
alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, cli::Options, monero,
|
||||||
network::transport::build, recover::recover, storage::Database, trace::init_tracing,
|
network::transport::build, storage::Database, trace::init_tracing, SwapAmounts,
|
||||||
SwapAmounts,
|
|
||||||
};
|
};
|
||||||
use tracing::{info, log::LevelFilter};
|
use tracing::{info, log::LevelFilter};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -226,24 +225,7 @@ async fn main() -> Result<()> {
|
|||||||
// Print the table to stdout
|
// Print the table to stdout
|
||||||
table.printstd();
|
table.printstd();
|
||||||
}
|
}
|
||||||
Options::Recover {
|
Options::Recover { .. } => todo!("implement this"),
|
||||||
swap_id,
|
|
||||||
bitcoind_url,
|
|
||||||
monerod_url,
|
|
||||||
bitcoin_wallet_name,
|
|
||||||
} => {
|
|
||||||
let state = db.get_state(swap_id)?;
|
|
||||||
let bitcoin_wallet = bitcoin::Wallet::new(
|
|
||||||
bitcoin_wallet_name.as_ref(),
|
|
||||||
bitcoind_url,
|
|
||||||
config.bitcoin_network,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("failed to create bitcoin wallet");
|
|
||||||
let monero_wallet = monero::Wallet::new(monerod_url);
|
|
||||||
|
|
||||||
recover(bitcoin_wallet, monero_wallet, state).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -9,7 +9,6 @@ pub mod bob;
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod recover;
|
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
@ -1,505 +0,0 @@
|
|||||||
//! This module is used to attempt to recover an unfinished swap.
|
|
||||||
//!
|
|
||||||
//! Recovery is only supported for certain states and the strategy followed is
|
|
||||||
//! to perform the simplest steps that require no further action from the
|
|
||||||
//! counterparty.
|
|
||||||
//!
|
|
||||||
//! The quality of this module is bad because there is a lot of code
|
|
||||||
//! duplication, both within the module and with respect to
|
|
||||||
//! `xmr_btc/src/{alice,bob}.rs`. In my opinion, a better approach to support
|
|
||||||
//! swap recovery would be through the `action_generator`s themselves, but this
|
|
||||||
//! was deemed too complicated for the time being.
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
bitcoin, monero,
|
|
||||||
monero::CreateWalletForOutput,
|
|
||||||
state::{Alice, Bob, Swap},
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
|
||||||
use bitcoin_harness::BitcoindRpcApi;
|
|
||||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
|
||||||
use futures::{
|
|
||||||
future::{select, Either},
|
|
||||||
pin_mut,
|
|
||||||
};
|
|
||||||
use sha2::Sha256;
|
|
||||||
use tracing::info;
|
|
||||||
use xmr_btc::{
|
|
||||||
bitcoin::{
|
|
||||||
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight,
|
|
||||||
WatchForRawTransaction,
|
|
||||||
},
|
|
||||||
bob::{State3, State4},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn recover(
|
|
||||||
bitcoin_wallet: bitcoin::Wallet,
|
|
||||||
monero_wallet: monero::Wallet,
|
|
||||||
state: Swap,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Swap::Alice(state) => alice_recover(bitcoin_wallet, monero_wallet, state).await,
|
|
||||||
Swap::Bob(state) => bob_recover(bitcoin_wallet, monero_wallet, state).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn alice_recover(
|
|
||||||
bitcoin_wallet: bitcoin::Wallet,
|
|
||||||
monero_wallet: monero::Wallet,
|
|
||||||
state: Alice,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Alice::Started { .. }
|
|
||||||
| Alice::Negotiated(_)
|
|
||||||
| Alice::BtcLocked(_)
|
|
||||||
| Alice::SwapComplete => {
|
|
||||||
info!("Nothing to do");
|
|
||||||
}
|
|
||||||
Alice::XmrLocked(state) => {
|
|
||||||
info!("Monero still locked up");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
info!("Bitcoin cancel transaction not yet published");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_lock_height + state.refund_timelock,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = state.tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&state.tx_lock, (state.a.public(), sig_a), (state.B, sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_cancel_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_cancel.txid())
|
|
||||||
.await;
|
|
||||||
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_cancel_height + state.punish_timelock,
|
|
||||||
);
|
|
||||||
pin_mut!(poll_until_bob_can_be_punished);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Waiting for either Bitcoin refund or punish timelock");
|
|
||||||
match select(
|
|
||||||
bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()),
|
|
||||||
poll_until_bob_can_be_punished,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((tx_refund_published, ..)) => {
|
|
||||||
info!("Found Bitcoin refund transaction");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig = state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Either::Right(_) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Alice::EncSignLearned { .. } => unimplemented!("recover method is deprecated"),
|
|
||||||
Alice::BtcCancelled { .. } => unimplemented!("recover method is deprecated"),
|
|
||||||
Alice::BtcRedeemable { redeem_tx, state } => {
|
|
||||||
info!("Have the means to redeem the Bitcoin");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(state.tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let block_height = bitcoin_wallet.inner.client.getblockcount().await?;
|
|
||||||
let refund_absolute_expiry = tx_lock_height + state.refund_timelock;
|
|
||||||
|
|
||||||
info!("Checking refund timelock");
|
|
||||||
if block_height < refund_absolute_expiry {
|
|
||||||
info!("Safe to redeem");
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(redeem_tx)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully redeemed bitcoin");
|
|
||||||
} else {
|
|
||||||
info!("Refund timelock reached");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
let sig_a = state.a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = state.tx_cancel_sig_bob.clone();
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&state.tx_lock, (state.a.public(), sig_a), (state.B, sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_cancel_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_cancel.txid())
|
|
||||||
.await;
|
|
||||||
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
|
||||||
&bitcoin_wallet,
|
|
||||||
tx_cancel_height + state.punish_timelock,
|
|
||||||
);
|
|
||||||
pin_mut!(poll_until_bob_can_be_punished);
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Waiting for either Bitcoin refund or punish timelock");
|
|
||||||
match select(
|
|
||||||
bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()),
|
|
||||||
poll_until_bob_can_be_punished,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((tx_refund_published, ..)) => {
|
|
||||||
info!("Found Bitcoin refund transaction");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig =
|
|
||||||
state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b =
|
|
||||||
bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Either::Right(_) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alice::BtcPunishable(state) => {
|
|
||||||
info!("Punish timelock reached, attempting to punish Bob");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(
|
|
||||||
&state.tx_lock,
|
|
||||||
state.refund_timelock,
|
|
||||||
state.a.public(),
|
|
||||||
state.B,
|
|
||||||
);
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
||||||
|
|
||||||
info!("Checking if Bitcoin has already been refunded");
|
|
||||||
|
|
||||||
// TODO: Protect against transient errors so that we can correctly decide if the
|
|
||||||
// bitcoin has been refunded
|
|
||||||
match bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_refund.txid())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(tx_refund_published) => {
|
|
||||||
info!("Bitcoin already refunded");
|
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
|
||||||
scalar: state.s_a.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund_sig = tx_refund
|
|
||||||
.extract_signature_by_key(tx_refund_published, state.a.public())?;
|
|
||||||
let tx_refund_encsig = state.a.encsign(state.S_b_bitcoin, tx_refund.digest());
|
|
||||||
|
|
||||||
let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?;
|
|
||||||
let s_b = monero::PrivateKey::from_scalar(
|
|
||||||
monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
|
||||||
);
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
info!("Bitcoin not yet refunded");
|
|
||||||
|
|
||||||
let tx_punish = bitcoin::TxPunish::new(
|
|
||||||
&tx_cancel,
|
|
||||||
&state.punish_address,
|
|
||||||
state.punish_timelock,
|
|
||||||
);
|
|
||||||
|
|
||||||
let sig_a = state.a.sign(tx_punish.digest());
|
|
||||||
let sig_b = state.tx_punish_sig_bob.clone();
|
|
||||||
|
|
||||||
let sig_tx_punish = tx_punish.add_signatures(
|
|
||||||
&tx_cancel,
|
|
||||||
(state.a.public(), sig_a),
|
|
||||||
(state.B, sig_b),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(sig_tx_punish)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully punished Bob's inactivity by taking bitcoin");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alice::BtcRefunded {
|
|
||||||
view_key,
|
|
||||||
spend_key,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
info!("Bitcoin was refunded, attempting to refund monero");
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded monero");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn bob_recover(
|
|
||||||
bitcoin_wallet: crate::bitcoin::Wallet,
|
|
||||||
monero_wallet: crate::monero::Wallet,
|
|
||||||
state: Bob,
|
|
||||||
) -> Result<()> {
|
|
||||||
match state {
|
|
||||||
Bob::Negotiated { .. } | Bob::SwapComplete => {
|
|
||||||
info!("Nothing to do");
|
|
||||||
}
|
|
||||||
Bob::BtcLocked {
|
|
||||||
state3:
|
|
||||||
State3 {
|
|
||||||
A,
|
|
||||||
b,
|
|
||||||
s_b,
|
|
||||||
refund_timelock,
|
|
||||||
refund_address,
|
|
||||||
tx_lock,
|
|
||||||
tx_cancel_sig_a,
|
|
||||||
tx_refund_encsig,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Bob::XmrLocked {
|
|
||||||
state4:
|
|
||||||
State4 {
|
|
||||||
A,
|
|
||||||
b,
|
|
||||||
s_b,
|
|
||||||
refund_timelock,
|
|
||||||
refund_address,
|
|
||||||
tx_lock,
|
|
||||||
tx_cancel_sig_a,
|
|
||||||
tx_refund_encsig,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Bob::BtcCancelled(State4 {
|
|
||||||
A,
|
|
||||||
b,
|
|
||||||
s_b,
|
|
||||||
refund_timelock,
|
|
||||||
refund_address,
|
|
||||||
tx_lock,
|
|
||||||
tx_cancel_sig_a,
|
|
||||||
tx_refund_encsig,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
info!("Bitcoin may still be locked up, attempting to refund");
|
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, A, b.public());
|
|
||||||
|
|
||||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
|
||||||
if bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_cancel.txid())
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
info!("Bitcoin cancel transaction not yet published");
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_wallet
|
|
||||||
.transaction_block_height(tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
poll_until_block_height_is_gte(&bitcoin_wallet, tx_lock_height + refund_timelock)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let sig_a = tx_cancel_sig_a.clone();
|
|
||||||
let sig_b = b.sign(tx_cancel.digest());
|
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&tx_lock, (A, sig_a), (b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
|
||||||
|
|
||||||
// TODO: We should not fail if the transaction is already on the blockchain
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(tx_cancel)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Confirmed that Bitcoin cancel transaction is on the blockchain");
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address);
|
|
||||||
let signed_tx_refund = {
|
|
||||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
|
||||||
let sig_a =
|
|
||||||
adaptor.decrypt_signature(&s_b.into_secp256k1(), tx_refund_encsig.clone());
|
|
||||||
let sig_b = b.sign(tx_refund.digest());
|
|
||||||
|
|
||||||
tx_refund
|
|
||||||
.add_signatures(&tx_cancel, (A, sig_a), (b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_refund")
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Check if Bitcoin has already been punished and provide a useful error
|
|
||||||
// message/log to the user if so
|
|
||||||
bitcoin_wallet
|
|
||||||
.broadcast_signed_transaction(signed_tx_refund)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully refunded bitcoin");
|
|
||||||
}
|
|
||||||
Bob::BtcRedeemed(state) => {
|
|
||||||
info!("Bitcoin was redeemed, attempting to redeem monero");
|
|
||||||
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
|
||||||
let tx_redeem_published = bitcoin_wallet
|
|
||||||
.inner
|
|
||||||
.get_raw_transaction(tx_redeem.txid())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let tx_redeem_encsig = state.b.encsign(state.S_a_bitcoin, tx_redeem.digest());
|
|
||||||
let tx_redeem_sig =
|
|
||||||
tx_redeem.extract_signature_by_key(tx_redeem_published, state.b.public())?;
|
|
||||||
|
|
||||||
let s_a = bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?;
|
|
||||||
let s_a = monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order(
|
|
||||||
s_a.to_bytes(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let s_b = monero::PrivateKey {
|
|
||||||
scalar: state.s_b.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
monero_wallet
|
|
||||||
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
|
||||||
.await?;
|
|
||||||
info!("Successfully redeemed monero")
|
|
||||||
}
|
|
||||||
Bob::Started { .. } => todo!(),
|
|
||||||
Bob::EncSigSent { .. } => todo!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -34,6 +34,7 @@ pub enum Alice {
|
|||||||
state: alice::State3,
|
state: alice::State3,
|
||||||
encrypted_signature: EncryptedSignature,
|
encrypted_signature: EncryptedSignature,
|
||||||
},
|
},
|
||||||
|
Cancelling(alice::State3),
|
||||||
BtcCancelled(alice::State3),
|
BtcCancelled(alice::State3),
|
||||||
BtcPunishable(alice::State3),
|
BtcPunishable(alice::State3),
|
||||||
BtcRefunded {
|
BtcRefunded {
|
||||||
@ -106,6 +107,7 @@ impl Display for Alice {
|
|||||||
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
|
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
|
||||||
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
Alice::XmrLocked(_) => f.write_str("Monero locked"),
|
||||||
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
|
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
|
||||||
|
Alice::Cancelling(_) => f.write_str("Submitting TxCancel"),
|
||||||
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"),
|
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"),
|
||||||
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
|
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
|
||||||
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
|
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
|
||||||
|
@ -109,10 +109,13 @@ async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
|||||||
|
|
||||||
assert!(matches!(alice_state, AliceState::EncSignLearned {..}));
|
assert!(matches!(alice_state, AliceState::EncSignLearned {..}));
|
||||||
|
|
||||||
// todo: add db code here
|
|
||||||
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
|
||||||
let alice_state = alice_db.get_state(alice_swap_id).unwrap();
|
let alice_state = alice_db.get_state(alice_swap_id).unwrap();
|
||||||
|
|
||||||
|
if let swap::state::Swap::Alice(state) = alice_state.clone() {
|
||||||
|
assert!(matches!(state, swap::state::Alice::EncSignLearned {..}));
|
||||||
|
}
|
||||||
|
|
||||||
let (alice_state, _) = alice::swap::swap(
|
let (alice_state, _) = alice::swap::swap(
|
||||||
AliceState::try_from(alice_state).unwrap(),
|
AliceState::try_from(alice_state).unwrap(),
|
||||||
alice_event_loop_handle,
|
alice_event_loop_handle,
|
||||||
|
Loading…
Reference in New Issue
Block a user