mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-09-29 21:06:21 +00:00
Implement swap recover function for Alice
This introduces a lot of duplication between the binary and the library, but it's okay because this module should only be a temporary measure until we allow recovery to be handled by the original state machine. Also, fix a bug in `xmr_btc::alice::action_generator` caused by the incorrect assumption that Alice's ability to punish Bob could be determined before the cancel transaction hits the blockchain.
This commit is contained in:
parent
b989e94322
commit
28225f8643
@ -14,6 +14,7 @@ base64 = "0.12"
|
|||||||
bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version.
|
bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version.
|
||||||
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" }
|
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" }
|
||||||
derivative = "2"
|
derivative = "2"
|
||||||
|
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "510d48ef6a2b19805f7f5c70c598e5b03f668e7a", features = ["libsecp_compat", "serde", "serialization"] }
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
genawaiter = "0.99.1"
|
genawaiter = "0.99.1"
|
||||||
libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] }
|
libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] }
|
||||||
|
@ -6,6 +6,7 @@ pub mod bitcoin;
|
|||||||
pub mod bob;
|
pub mod bob;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
pub mod recover;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
|
@ -16,7 +16,6 @@ use anyhow::Result;
|
|||||||
use futures::{channel::mpsc, StreamExt};
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use libp2p::Multiaddr;
|
use libp2p::Multiaddr;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use prettytable::{row, Table};
|
|
||||||
use std::{io, io::Write, process, sync::Arc};
|
use std::{io, io::Write, process, sync::Arc};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use swap::{
|
use swap::{
|
||||||
@ -31,13 +30,11 @@ use swap::{
|
|||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate prettytable;
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod trace;
|
mod trace;
|
||||||
|
|
||||||
use cli::Options;
|
use cli::Options;
|
||||||
|
use swap::storage::Database;
|
||||||
|
|
||||||
// TODO: Add root seed file instead of generating new seed each run.
|
// TODO: Add root seed file instead of generating new seed each run.
|
||||||
|
|
||||||
@ -47,6 +44,9 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
trace::init_tracing(LevelFilter::Debug)?;
|
trace::init_tracing(LevelFilter::Debug)?;
|
||||||
|
|
||||||
|
let db_dir = tempdir()?;
|
||||||
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
|
|
||||||
match opt {
|
match opt {
|
||||||
Options::Alice {
|
Options::Alice {
|
||||||
bitcoind_url,
|
bitcoind_url,
|
||||||
@ -85,7 +85,11 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
||||||
|
|
||||||
swap_as_alice(bitcoin_wallet, monero_wallet, dblisten_addr,
|
swap_as_alice(
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
db,
|
||||||
|
listen_addr,
|
||||||
transport,
|
transport,
|
||||||
behaviour,
|
behaviour,
|
||||||
)
|
)
|
||||||
@ -115,12 +119,10 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
||||||
|
|
||||||
let db = Database::open(db_dir.path()).unwrap();
|
|
||||||
|
|
||||||
swap_as_bob(
|
swap_as_bob(
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
db
|
db,
|
||||||
satoshis,
|
satoshis,
|
||||||
alice_addr,
|
alice_addr,
|
||||||
transport,
|
transport,
|
||||||
@ -159,7 +161,15 @@ async fn swap_as_alice(
|
|||||||
transport: SwapTransport,
|
transport: SwapTransport,
|
||||||
behaviour: Alice,
|
behaviour: Alice,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
alice::swap(bitcoin_wallet, monero_wallet, addr, transport, behaviour).await
|
alice::swap(
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
db,
|
||||||
|
addr,
|
||||||
|
transport,
|
||||||
|
behaviour,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn swap_as_bob(
|
async fn swap_as_bob(
|
||||||
|
187
swap/src/recover.rs
Normal file
187
swap/src/recover.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
use crate::{
|
||||||
|
monero::CreateWalletForOutput,
|
||||||
|
state::{Alice, Bob, Swap},
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures::{
|
||||||
|
future::{select, Either},
|
||||||
|
pin_mut,
|
||||||
|
};
|
||||||
|
use xmr_btc::bitcoin::{
|
||||||
|
poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel,
|
||||||
|
TxPunish, TxRefund, WatchForRawTransaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn recover(
|
||||||
|
bitcoin_wallet: crate::bitcoin::Wallet,
|
||||||
|
monero_wallet: crate::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: crate::bitcoin::Wallet,
|
||||||
|
monero_wallet: crate::monero::Wallet,
|
||||||
|
state: Alice,
|
||||||
|
) -> Result<()> {
|
||||||
|
match state {
|
||||||
|
Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => Ok(()),
|
||||||
|
Alice::XmrLocked(state) => {
|
||||||
|
let tx_cancel = TxCancel::new(
|
||||||
|
&state.tx_lock,
|
||||||
|
state.refund_timelock,
|
||||||
|
state.a.public(),
|
||||||
|
state.B.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure that TxCancel is on the blockchain
|
||||||
|
if bitcoin_wallet
|
||||||
|
.0
|
||||||
|
.get_raw_transaction(tx_cancel.txid())
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
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.clone(), 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?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = TxRefund::new(&tx_cancel, &state.refund_address);
|
||||||
|
|
||||||
|
match select(
|
||||||
|
bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()),
|
||||||
|
poll_until_bob_can_be_punished,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Either::Left((tx_refund_published, ..)) => {
|
||||||
|
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.clone(), tx_refund.digest());
|
||||||
|
|
||||||
|
let s_b = xmr_btc::bitcoin::recover(
|
||||||
|
state.S_b_bitcoin,
|
||||||
|
tx_refund_sig,
|
||||||
|
tx_refund_encsig,
|
||||||
|
)?;
|
||||||
|
let s_b = monero::PrivateKey::from_scalar(
|
||||||
|
xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let s_a = monero::PrivateKey {
|
||||||
|
scalar: state.s_a.into_ed25519(),
|
||||||
|
};
|
||||||
|
|
||||||
|
monero_wallet
|
||||||
|
.create_and_load_wallet_for_output(s_a + s_b, state.v)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Either::Right(_) => {
|
||||||
|
let tx_punish =
|
||||||
|
TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock);
|
||||||
|
|
||||||
|
let sig_a = state.a.sign(tx_punish.digest());
|
||||||
|
let sig_b = state.tx_cancel_sig_bob.clone();
|
||||||
|
|
||||||
|
let sig_tx_punish = tx_punish.add_signatures(
|
||||||
|
&tx_cancel,
|
||||||
|
(state.a.public(), sig_a),
|
||||||
|
(state.B.clone(), sig_b),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(sig_tx_punish)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Alice::BtcRedeemable { redeem_tx, .. } => {
|
||||||
|
bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(redeem_tx)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Alice::BtcPunishable(state) => {
|
||||||
|
let tx_cancel = TxCancel::new(
|
||||||
|
&state.tx_lock,
|
||||||
|
state.refund_timelock,
|
||||||
|
state.a.public(),
|
||||||
|
state.B.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock);
|
||||||
|
|
||||||
|
let sig_a = state.a.sign(tx_punish.digest());
|
||||||
|
let sig_b = state.tx_cancel_sig_bob.clone();
|
||||||
|
|
||||||
|
let sig_tx_punish = tx_punish.add_signatures(
|
||||||
|
&tx_cancel,
|
||||||
|
(state.a.public(), sig_a),
|
||||||
|
(state.B.clone(), sig_b),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
bitcoin_wallet
|
||||||
|
.broadcast_signed_transaction(sig_tx_punish)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Alice::BtcRefunded {
|
||||||
|
view_key,
|
||||||
|
spend_key,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
monero_wallet
|
||||||
|
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bob_recover(
|
||||||
|
_bitcoin_wallet: crate::bitcoin::Wallet,
|
||||||
|
_monero_wallet: crate::monero::Wallet,
|
||||||
|
_state: Bob,
|
||||||
|
) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
@ -33,7 +33,7 @@ impl Database {
|
|||||||
.context("Could not flush db")
|
.context("Could not flush db")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_state(&self, swap_id: Uuid) -> anyhow::Result<Swap> {
|
pub fn get_state(&self, swap_id: Uuid) -> anyhow::Result<Swap> {
|
||||||
let key = serialize(&swap_id)?;
|
let key = serialize(&swap_id)?;
|
||||||
|
|
||||||
let encoded = self
|
let encoded = self
|
||||||
@ -103,11 +103,11 @@ mod tests {
|
|||||||
.expect("Failed to save first state");
|
.expect("Failed to save first state");
|
||||||
|
|
||||||
let recovered_1 = db
|
let recovered_1 = db
|
||||||
.get_latest_state(swap_id_1)
|
.get_state(swap_id_1)
|
||||||
.expect("Failed to recover first state");
|
.expect("Failed to recover first state");
|
||||||
|
|
||||||
let recovered_2 = db
|
let recovered_2 = db
|
||||||
.get_latest_state(swap_id_2)
|
.get_state(swap_id_2)
|
||||||
.expect("Failed to recover second state");
|
.expect("Failed to recover second state");
|
||||||
|
|
||||||
assert_eq!(recovered_1, state_1);
|
assert_eq!(recovered_1, state_1);
|
||||||
@ -126,7 +126,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to save state the first time");
|
.expect("Failed to save state the first time");
|
||||||
let recovered = db
|
let recovered = db
|
||||||
.get_latest_state(swap_id)
|
.get_state(swap_id)
|
||||||
.expect("Failed to recover state the first time");
|
.expect("Failed to recover state the first time");
|
||||||
|
|
||||||
// We insert and recover twice to ensure database implementation allows the
|
// We insert and recover twice to ensure database implementation allows the
|
||||||
@ -135,7 +135,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to save state the second time");
|
.expect("Failed to save state the second time");
|
||||||
let recovered = db
|
let recovered = db
|
||||||
.get_latest_state(swap_id)
|
.get_state(swap_id)
|
||||||
.expect("Failed to recover state the second time");
|
.expect("Failed to recover state the second time");
|
||||||
|
|
||||||
assert_eq!(recovered, state);
|
assert_eq!(recovered, state);
|
||||||
|
@ -5,7 +5,8 @@ mod e2e_test {
|
|||||||
use libp2p::Multiaddr;
|
use libp2p::Multiaddr;
|
||||||
use monero_harness::Monero;
|
use monero_harness::Monero;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use swap::{alice, bob, network::transport::build};
|
use swap::{alice, bob, network::transport::build, storage::Database};
|
||||||
|
use tempfile::tempdir;
|
||||||
use testcontainers::clients::Cli;
|
use testcontainers::clients::Cli;
|
||||||
|
|
||||||
// NOTE: For some reason running these tests overflows the stack. In order to
|
// NOTE: For some reason running these tests overflows the stack. In order to
|
||||||
@ -48,12 +49,10 @@ mod e2e_test {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (monero, _container) = Monero::new(&cli, Some("swap_".to_string()), vec![
|
let (monero, _container) =
|
||||||
"alice".to_string(),
|
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
||||||
"bob".to_string(),
|
.await
|
||||||
])
|
.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
monero
|
monero
|
||||||
.init(vec![("alice", xmr_alice), ("bob", xmr_bob)])
|
.init(vec![("alice", xmr_alice), ("bob", xmr_bob)])
|
||||||
.await
|
.await
|
||||||
@ -68,7 +67,7 @@ mod e2e_test {
|
|||||||
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
||||||
|
|
||||||
let db_dir = tempdir().unwrap();
|
let db_dir = tempdir().unwrap();
|
||||||
let db = Database::open(std::path::Path::new("/home/luckysori/test/xmr_btc_swap")).unwrap();
|
let db = Database::open(db_dir.path()).unwrap();
|
||||||
let alice_swap = alice::swap(
|
let alice_swap = alice::swap(
|
||||||
alice_btc_wallet.clone(),
|
alice_btc_wallet.clone(),
|
||||||
alice_xmr_wallet.clone(),
|
alice_xmr_wallet.clone(),
|
||||||
|
@ -97,7 +97,7 @@ where
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SwapFailed {
|
enum SwapFailed {
|
||||||
BeforeBtcLock(Reason),
|
BeforeBtcLock(Reason),
|
||||||
AfterXmrLock { tx_lock_height: u32, reason: Reason },
|
AfterXmrLock(Reason),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reason why the swap has failed.
|
/// Reason why the swap has failed.
|
||||||
@ -114,9 +114,7 @@ where
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum RefundFailed {
|
enum RefundFailed {
|
||||||
BtcPunishable {
|
BtcPunishable,
|
||||||
tx_cancel_was_published: bool,
|
|
||||||
},
|
|
||||||
/// Could not find Alice's signature on the refund transaction witness
|
/// Could not find Alice's signature on the refund transaction witness
|
||||||
/// stack.
|
/// stack.
|
||||||
BtcRefundSignature,
|
BtcRefundSignature,
|
||||||
@ -167,12 +165,7 @@ where
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Either::Left((encsig, _)) => encsig,
|
Either::Left((encsig, _)) => encsig,
|
||||||
Either::Right(_) => {
|
Either::Right(_) => return Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)),
|
||||||
return Err(SwapFailed::AfterXmrLock {
|
|
||||||
reason: Reason::BtcExpired,
|
|
||||||
tx_lock_height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::debug!("select returned redeem encsig from message");
|
tracing::debug!("select returned redeem encsig from message");
|
||||||
@ -191,10 +184,7 @@ where
|
|||||||
&tx_redeem.digest(),
|
&tx_redeem.digest(),
|
||||||
&tx_redeem_encsig,
|
&tx_redeem_encsig,
|
||||||
)
|
)
|
||||||
.map_err(|_| SwapFailed::AfterXmrLock {
|
.map_err(|_| SwapFailed::AfterXmrLock(Reason::InvalidEncryptedSignature))?;
|
||||||
reason: Reason::InvalidEncryptedSignature,
|
|
||||||
tx_lock_height,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let sig_a = a.sign(tx_redeem.digest());
|
let sig_a = a.sign(tx_redeem.digest());
|
||||||
let sig_b =
|
let sig_b =
|
||||||
@ -217,12 +207,7 @@ where
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Either::Left(_) => {}
|
Either::Left(_) => {}
|
||||||
Either::Right(_) => {
|
Either::Right(_) => return Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)),
|
||||||
return Err(SwapFailed::AfterXmrLock {
|
|
||||||
reason: Reason::BtcExpired,
|
|
||||||
tx_lock_height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -233,19 +218,8 @@ where
|
|||||||
error!("swap failed: {:?}", err);
|
error!("swap failed: {:?}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(SwapFailed::AfterXmrLock {
|
if let Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)) = swap_result {
|
||||||
reason: Reason::BtcExpired,
|
|
||||||
tx_lock_height,
|
|
||||||
}) = swap_result
|
|
||||||
{
|
|
||||||
let refund_result: Result<(), RefundFailed> = async {
|
let refund_result: Result<(), RefundFailed> = async {
|
||||||
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
|
||||||
bitcoin_client.as_ref(),
|
|
||||||
tx_lock_height + refund_timelock + punish_timelock,
|
|
||||||
)
|
|
||||||
.shared();
|
|
||||||
pin_mut!(poll_until_bob_can_be_punished);
|
|
||||||
|
|
||||||
let tx_cancel =
|
let tx_cancel =
|
||||||
bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone());
|
bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone());
|
||||||
let signed_tx_cancel = {
|
let signed_tx_cancel = {
|
||||||
@ -260,19 +234,19 @@ where
|
|||||||
|
|
||||||
co.yield_(Action::CancelBtc(signed_tx_cancel)).await;
|
co.yield_(Action::CancelBtc(signed_tx_cancel)).await;
|
||||||
|
|
||||||
match select(
|
bitcoin_client
|
||||||
bitcoin_client.watch_for_raw_transaction(tx_cancel.txid()),
|
.watch_for_raw_transaction(tx_cancel.txid())
|
||||||
poll_until_bob_can_be_punished.clone(),
|
.await;
|
||||||
|
|
||||||
|
let tx_cancel_height = bitcoin_client
|
||||||
|
.transaction_block_height(tx_cancel.txid())
|
||||||
|
.await;
|
||||||
|
let poll_until_bob_can_be_punished = poll_until_block_height_is_gte(
|
||||||
|
bitcoin_client.as_ref(),
|
||||||
|
tx_cancel_height + punish_timelock,
|
||||||
)
|
)
|
||||||
.await
|
.shared();
|
||||||
{
|
pin_mut!(poll_until_bob_can_be_punished);
|
||||||
Either::Left(_) => {}
|
|
||||||
Either::Right(_) => {
|
|
||||||
return Err(RefundFailed::BtcPunishable {
|
|
||||||
tx_cancel_was_published: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address);
|
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address);
|
||||||
let tx_refund_published = match select(
|
let tx_refund_published = match select(
|
||||||
@ -282,11 +256,7 @@ where
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Either::Left((tx, _)) => tx,
|
Either::Left((tx, _)) => tx,
|
||||||
Either::Right(_) => {
|
Either::Right(_) => return Err(RefundFailed::BtcPunishable),
|
||||||
return Err(RefundFailed::BtcPunishable {
|
|
||||||
tx_cancel_was_published: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let s_a = monero::PrivateKey {
|
let s_a = monero::PrivateKey {
|
||||||
@ -321,32 +291,9 @@ where
|
|||||||
// transaction with his refund transaction. Alice would then need to carry on
|
// transaction with his refund transaction. Alice would then need to carry on
|
||||||
// with the refund on Monero. Doing so may be too verbose with the current,
|
// with the refund on Monero. Doing so may be too verbose with the current,
|
||||||
// linear approach. A different design may be required
|
// linear approach. A different design may be required
|
||||||
if let Err(RefundFailed::BtcPunishable {
|
if let Err(RefundFailed::BtcPunishable) = refund_result {
|
||||||
tx_cancel_was_published,
|
|
||||||
}) = refund_result
|
|
||||||
{
|
|
||||||
let tx_cancel =
|
let tx_cancel =
|
||||||
bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone());
|
bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone());
|
||||||
|
|
||||||
if !tx_cancel_was_published {
|
|
||||||
let tx_cancel_txid = tx_cancel.txid();
|
|
||||||
let signed_tx_cancel = {
|
|
||||||
let sig_a = a.sign(tx_cancel.digest());
|
|
||||||
let sig_b = tx_cancel_sig_bob;
|
|
||||||
|
|
||||||
tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&tx_lock, (a.public(), sig_a), (B.clone(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel")
|
|
||||||
};
|
|
||||||
|
|
||||||
co.yield_(Action::CancelBtc(signed_tx_cancel)).await;
|
|
||||||
|
|
||||||
let _ = bitcoin_client
|
|
||||||
.watch_for_raw_transaction(tx_cancel_txid)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tx_punish =
|
let tx_punish =
|
||||||
bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
|
bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
|
||||||
let tx_punish_txid = tx_punish.txid();
|
let tx_punish_txid = tx_punish.txid();
|
||||||
|
@ -507,10 +507,10 @@ pub struct State2 {
|
|||||||
btc: bitcoin::Amount,
|
btc: bitcoin::Amount,
|
||||||
pub xmr: monero::Amount,
|
pub xmr: monero::Amount,
|
||||||
pub refund_timelock: u32,
|
pub refund_timelock: u32,
|
||||||
punish_timelock: u32,
|
pub punish_timelock: u32,
|
||||||
pub refund_address: bitcoin::Address,
|
pub refund_address: bitcoin::Address,
|
||||||
pub redeem_address: bitcoin::Address,
|
pub redeem_address: bitcoin::Address,
|
||||||
punish_address: bitcoin::Address,
|
pub punish_address: bitcoin::Address,
|
||||||
pub tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
pub tx_cancel_sig_a: Signature,
|
pub tx_cancel_sig_a: Signature,
|
||||||
pub tx_refund_encsig: EncryptedSignature,
|
pub tx_refund_encsig: EncryptedSignature,
|
||||||
|
Loading…
Reference in New Issue
Block a user