mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-01-13 00:19:31 -05:00
Introduce Watchable
abstraction for Bitcoin wallet
We have a repeated pattern where we construct one of our Tx{Cancel,Redeem,Punish,Refund,Lock} transactions and wait until the status of this transaction changes. We can make this more ergonomic by creating and implementing a `Watchable` trait that gives access to the TxId and relevant script for this transaction. This allows us to remove a parameter from the `watch_until_status` function. Additionally, there is a 2nd pattern: "Completing" one of these transaction and waiting until they are confirmed with the configured number of blocks for finality. We can make this more ergonomic by returning a future from `broadcast` that callers can await in case they want to wait for the broadcasted transaction to reach finality.
This commit is contained in:
parent
a0830f099f
commit
273cf15631
@ -1,3 +1,4 @@
|
|||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, Address, Amount, BlockHeight, PublicKey, Transaction, TxLock,
|
build_shared_output_descriptor, Address, Amount, BlockHeight, PublicKey, Transaction, TxLock,
|
||||||
TX_FEE,
|
TX_FEE,
|
||||||
@ -81,7 +82,7 @@ impl PartialEq<PunishTimelock> for u32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct TxCancel {
|
pub struct TxCancel {
|
||||||
inner: Transaction,
|
inner: Transaction,
|
||||||
digest: SigHash,
|
digest: SigHash,
|
||||||
@ -148,16 +149,6 @@ impl TxCancel {
|
|||||||
OutPoint::new(self.inner.txid(), 0)
|
OutPoint::new(self.inner.txid(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the relevant script_pubkey of this transaction.
|
|
||||||
///
|
|
||||||
/// Even though a transaction can have multiple outputs, the nature of our
|
|
||||||
/// protocol is that there is only one relevant output within this one.
|
|
||||||
/// As such, subscribing or inquiring the status of this script allows us to
|
|
||||||
/// check the status of the whole transaction.
|
|
||||||
pub fn script_pubkey(&self) -> Script {
|
|
||||||
self.output_descriptor.script_pubkey()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_signatures(
|
pub fn add_signatures(
|
||||||
self,
|
self,
|
||||||
(A, sig_a): (PublicKey, Signature),
|
(A, sig_a): (PublicKey, Signature),
|
||||||
@ -216,3 +207,13 @@ impl TxCancel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxCancel {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.output_descriptor.script_pubkey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet, TX_FEE,
|
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet, TX_FEE,
|
||||||
};
|
};
|
||||||
@ -105,3 +106,13 @@ impl From<TxLock> for PartiallySignedTransaction {
|
|||||||
from.inner
|
from.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxLock {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.output_descriptor.script_pubkey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
use crate::bitcoin::{self, Address, PunishTimelock, Transaction, TxCancel};
|
use crate::bitcoin::wallet::Watchable;
|
||||||
|
use crate::bitcoin::{self, Address, PunishTimelock, Transaction, TxCancel, Txid};
|
||||||
use ::bitcoin::util::bip143::SigHashCache;
|
use ::bitcoin::util::bip143::SigHashCache;
|
||||||
use ::bitcoin::{SigHash, SigHashType};
|
use ::bitcoin::{SigHash, SigHashType};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use bdk::bitcoin::Script;
|
||||||
use miniscript::{Descriptor, DescriptorTrait};
|
use miniscript::{Descriptor, DescriptorTrait};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TxPunish {
|
pub struct TxPunish {
|
||||||
inner: Transaction,
|
inner: Transaction,
|
||||||
digest: SigHash,
|
digest: SigHash,
|
||||||
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
watch_script: Script,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxPunish {
|
impl TxPunish {
|
||||||
@ -31,6 +34,7 @@ impl TxPunish {
|
|||||||
inner: tx_punish,
|
inner: tx_punish,
|
||||||
digest,
|
digest,
|
||||||
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
|
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
|
||||||
|
watch_script: punish_address.script_pubkey(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,3 +72,13 @@ impl TxPunish {
|
|||||||
Ok(tx_punish)
|
Ok(tx_punish)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxPunish {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.inner.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.watch_script.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
verify_encsig, verify_sig, Address, EmptyWitnessStack, EncryptedSignature, NoInputs,
|
verify_encsig, verify_sig, Address, EmptyWitnessStack, EncryptedSignature, NoInputs,
|
||||||
NotThreeWitnesses, PublicKey, SecretKey, TooManyInputs, Transaction, TxLock,
|
NotThreeWitnesses, PublicKey, SecretKey, TooManyInputs, Transaction, TxLock,
|
||||||
@ -5,6 +6,7 @@ use crate::bitcoin::{
|
|||||||
use ::bitcoin::util::bip143::SigHashCache;
|
use ::bitcoin::util::bip143::SigHashCache;
|
||||||
use ::bitcoin::{SigHash, SigHashType, Txid};
|
use ::bitcoin::{SigHash, SigHashType, Txid};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use bitcoin::Script;
|
||||||
use ecdsa_fun::adaptor::{Adaptor, HashTranscript};
|
use ecdsa_fun::adaptor::{Adaptor, HashTranscript};
|
||||||
use ecdsa_fun::fun::Scalar;
|
use ecdsa_fun::fun::Scalar;
|
||||||
use ecdsa_fun::nonce::Deterministic;
|
use ecdsa_fun::nonce::Deterministic;
|
||||||
@ -13,11 +15,12 @@ use miniscript::{Descriptor, DescriptorTrait};
|
|||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct TxRedeem {
|
pub struct TxRedeem {
|
||||||
inner: Transaction,
|
inner: Transaction,
|
||||||
digest: SigHash,
|
digest: SigHash,
|
||||||
lock_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
lock_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
watch_script: Script,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxRedeem {
|
impl TxRedeem {
|
||||||
@ -37,6 +40,7 @@ impl TxRedeem {
|
|||||||
inner: tx_redeem,
|
inner: tx_redeem,
|
||||||
digest,
|
digest,
|
||||||
lock_output_descriptor: tx_lock.output_descriptor.clone(),
|
lock_output_descriptor: tx_lock.output_descriptor.clone(),
|
||||||
|
watch_script: redeem_address.script_pubkey(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,3 +134,13 @@ impl TxRedeem {
|
|||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxRedeem {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.watch_script.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::bitcoin::wallet::Watchable;
|
||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
verify_sig, Address, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs,
|
verify_sig, Address, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs,
|
||||||
Transaction, TxCancel,
|
Transaction, TxCancel,
|
||||||
@ -5,6 +6,7 @@ use crate::bitcoin::{
|
|||||||
use ::bitcoin::util::bip143::SigHashCache;
|
use ::bitcoin::util::bip143::SigHashCache;
|
||||||
use ::bitcoin::{SigHash, SigHashType, Txid};
|
use ::bitcoin::{SigHash, SigHashType, Txid};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use bitcoin::Script;
|
||||||
use ecdsa_fun::Signature;
|
use ecdsa_fun::Signature;
|
||||||
use miniscript::{Descriptor, DescriptorTrait};
|
use miniscript::{Descriptor, DescriptorTrait};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -14,6 +16,7 @@ pub struct TxRefund {
|
|||||||
inner: Transaction,
|
inner: Transaction,
|
||||||
digest: SigHash,
|
digest: SigHash,
|
||||||
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
|
||||||
|
watch_script: Script,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxRefund {
|
impl TxRefund {
|
||||||
@ -31,6 +34,7 @@ impl TxRefund {
|
|||||||
inner: tx_punish,
|
inner: tx_punish,
|
||||||
digest,
|
digest,
|
||||||
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
|
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
|
||||||
|
watch_script: refund_address.script_pubkey(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,3 +114,13 @@ impl TxRefund {
|
|||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Watchable for TxRefund {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.txid()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.watch_script.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ use reqwest::Url;
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -159,9 +160,22 @@ impl Wallet {
|
|||||||
|
|
||||||
/// Broadcast the given transaction to the network and emit a log statement
|
/// Broadcast the given transaction to the network and emit a log statement
|
||||||
/// if done so successfully.
|
/// if done so successfully.
|
||||||
pub async fn broadcast(&self, transaction: Transaction, kind: &str) -> Result<Txid> {
|
///
|
||||||
|
/// Returns the transaction ID and a future for when the transaction meets
|
||||||
|
/// the configured finality confirmations.
|
||||||
|
pub async fn broadcast(
|
||||||
|
&self,
|
||||||
|
transaction: Transaction,
|
||||||
|
kind: &str,
|
||||||
|
) -> Result<(Txid, impl Future<Output = Result<()>> + '_)> {
|
||||||
let txid = transaction.txid();
|
let txid = transaction.txid();
|
||||||
|
|
||||||
|
// to watch for confirmations, watching a single output is enough
|
||||||
|
let watcher = self.wait_for_transaction_finality(
|
||||||
|
(txid, transaction.output[0].script_pubkey.clone()),
|
||||||
|
kind.to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
self.wallet
|
self.wallet
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
@ -172,7 +186,7 @@ impl Wallet {
|
|||||||
|
|
||||||
tracing::info!(%txid, "Published Bitcoin {} transaction", kind);
|
tracing::info!(%txid, "Published Bitcoin {} transaction", kind);
|
||||||
|
|
||||||
Ok(txid)
|
Ok((txid, watcher))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result<Transaction> {
|
pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result<Transaction> {
|
||||||
@ -193,20 +207,27 @@ impl Wallet {
|
|||||||
.ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid))
|
.ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn status_of_script(&self, script: &Script, txid: &Txid) -> Result<ScriptStatus> {
|
pub async fn status_of_script<T>(&self, tx: &T) -> Result<ScriptStatus>
|
||||||
self.client.lock().await.status_of_script(script, txid)
|
where
|
||||||
|
T: Watchable,
|
||||||
|
{
|
||||||
|
self.client.lock().await.status_of_script(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_until_status(
|
pub async fn watch_until_status<T>(
|
||||||
&self,
|
&self,
|
||||||
txid: Txid,
|
tx: &T,
|
||||||
script: Script,
|
|
||||||
mut status_fn: impl FnMut(ScriptStatus) -> bool,
|
mut status_fn: impl FnMut(ScriptStatus) -> bool,
|
||||||
) -> Result<()> {
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
T: Watchable,
|
||||||
|
{
|
||||||
|
let txid = tx.id();
|
||||||
|
|
||||||
let mut last_status = None;
|
let mut last_status = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let new_status = self.client.lock().await.status_of_script(&script, &txid)?;
|
let new_status = self.client.lock().await.status_of_script(tx)?;
|
||||||
|
|
||||||
if Some(new_status) != last_status {
|
if Some(new_status) != last_status {
|
||||||
tracing::debug!(%txid, "Transaction is {}", new_status);
|
tracing::debug!(%txid, "Transaction is {}", new_status);
|
||||||
@ -223,23 +244,23 @@ impl Wallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_transaction_finality(
|
async fn wait_for_transaction_finality<T>(&self, tx: T, kind: String) -> Result<()>
|
||||||
&self,
|
where
|
||||||
txid: Txid,
|
T: Watchable,
|
||||||
script_to_watch: Script,
|
{
|
||||||
) -> Result<()> {
|
|
||||||
let conf_target = self.bitcoin_finality_confirmations;
|
let conf_target = self.bitcoin_finality_confirmations;
|
||||||
|
let txid = tx.id();
|
||||||
|
|
||||||
tracing::info!(%txid, "Waiting for {} confirmation{} of Bitcoin transaction", conf_target, if conf_target > 1 { "s" } else { "" });
|
tracing::info!(%txid, "Waiting for {} confirmation{} of Bitcoin {} transaction", conf_target, if conf_target > 1 { "s" } else { "" }, kind);
|
||||||
|
|
||||||
let mut seen_confirmations = 0;
|
let mut seen_confirmations = 0;
|
||||||
|
|
||||||
self.watch_until_status(txid, script_to_watch, |status| match status {
|
self.watch_until_status(&tx, |status| match status {
|
||||||
ScriptStatus::Confirmed(inner) => {
|
ScriptStatus::Confirmed(inner) => {
|
||||||
let confirmations = inner.confirmations();
|
let confirmations = inner.confirmations();
|
||||||
|
|
||||||
if confirmations > seen_confirmations {
|
if confirmations > seen_confirmations {
|
||||||
tracing::info!(%txid, "Bitcoin tx has {} out of {} confirmation{}", confirmations, conf_target, if conf_target > 1 { "s" } else { "" });
|
tracing::info!(%txid, "Bitcoin {} tx has {} out of {} confirmation{}", kind, confirmations, conf_target, if conf_target > 1 { "s" } else { "" });
|
||||||
seen_confirmations = confirmations;
|
seen_confirmations = confirmations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +281,27 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines a watchable transaction.
|
||||||
|
///
|
||||||
|
/// For a transaction to be watchable, we need to know two things: Its
|
||||||
|
/// transaction ID and the specific output script that is going to change.
|
||||||
|
/// A transaction can obviously have multiple outputs but our protocol purposes,
|
||||||
|
/// we are usually interested in a specific one.
|
||||||
|
pub trait Watchable {
|
||||||
|
fn id(&self) -> Txid;
|
||||||
|
fn script(&self) -> Script;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watchable for (Txid, Script) {
|
||||||
|
fn id(&self) -> Txid {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script(&self) -> Script {
|
||||||
|
self.1.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Client {
|
struct Client {
|
||||||
electrum: bdk::electrum_client::Client,
|
electrum: bdk::electrum_client::Client,
|
||||||
latest_block: BlockHeight,
|
latest_block: BlockHeight,
|
||||||
@ -321,18 +363,24 @@ impl Client {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_of_script(&mut self, script: &Script, txid: &Txid) -> Result<ScriptStatus> {
|
fn status_of_script<T>(&mut self, tx: &T) -> Result<ScriptStatus>
|
||||||
if !self.script_history.contains_key(script) {
|
where
|
||||||
|
T: Watchable,
|
||||||
|
{
|
||||||
|
let txid = tx.id();
|
||||||
|
let script = tx.script();
|
||||||
|
|
||||||
|
if !self.script_history.contains_key(&script) {
|
||||||
self.script_history.insert(script.clone(), vec![]);
|
self.script_history.insert(script.clone(), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drain_notifications()?;
|
self.drain_notifications()?;
|
||||||
|
|
||||||
let history = self.script_history.entry(script.clone()).or_default();
|
let history = self.script_history.entry(script).or_default();
|
||||||
|
|
||||||
let history_of_tx = history
|
let history_of_tx = history
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|entry| &entry.tx_hash == txid)
|
.filter(|entry| entry.tx_hash == txid)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
match history_of_tx.as_slice() {
|
match history_of_tx.as_slice() {
|
||||||
|
@ -321,11 +321,9 @@ impl State3 {
|
|||||||
bitcoin_wallet: &bitcoin::Wallet,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(
|
.watch_until_status(&self.tx_lock, |status| {
|
||||||
self.tx_lock.txid(),
|
status.is_confirmed_with(self.cancel_timelock)
|
||||||
self.tx_lock.script_pubkey(),
|
})
|
||||||
|status| status.is_confirmed_with(self.cancel_timelock),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -337,12 +335,8 @@ impl State3 {
|
|||||||
) -> Result<ExpiredTimelocks> {
|
) -> Result<ExpiredTimelocks> {
|
||||||
let tx_cancel = self.tx_cancel();
|
let tx_cancel = self.tx_cancel();
|
||||||
|
|
||||||
let tx_lock_status = bitcoin_wallet
|
let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?;
|
||||||
.status_of_script(&self.tx_lock.script_pubkey(), &self.tx_lock.txid())
|
let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?;
|
||||||
.await?;
|
|
||||||
let tx_cancel_status = bitcoin_wallet
|
|
||||||
.status_of_script(&tx_cancel.script_pubkey(), &tx_cancel.txid())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(current_epoch(
|
Ok(current_epoch(
|
||||||
self.cancel_timelock,
|
self.cancel_timelock,
|
||||||
|
@ -6,7 +6,6 @@ use crate::protocol::alice::event_loop::EventLoopHandle;
|
|||||||
use crate::protocol::alice::TransferProof;
|
use crate::protocol::alice::TransferProof;
|
||||||
use crate::{bitcoin, monero};
|
use crate::{bitcoin, monero};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use futures::future::{select, Either};
|
|
||||||
use futures::pin_mut;
|
use futures::pin_mut;
|
||||||
use libp2p::PeerId;
|
use libp2p::PeerId;
|
||||||
|
|
||||||
@ -61,9 +60,7 @@ pub async fn publish_cancel_transaction(
|
|||||||
bitcoin_wallet: &bitcoin::Wallet,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(tx_lock.txid(), tx_lock.script_pubkey(), |status| {
|
.watch_until_status(&tx_lock, |status| status.is_confirmed_with(cancel_timelock))
|
||||||
status.is_confirmed_with(cancel_timelock)
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
|
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
|
||||||
@ -85,7 +82,7 @@ pub async fn publish_cancel_transaction(
|
|||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
.expect("sig_{a,b} to be valid signatures for tx_cancel");
|
||||||
|
|
||||||
// TODO(Franck): Error handling is delicate, why can't we broadcast?
|
// TODO(Franck): Error handling is delicate, why can't we broadcast?
|
||||||
bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
|
let (..) = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
|
||||||
|
|
||||||
// TODO(Franck): Wait until transaction is mined and returned mined
|
// TODO(Franck): Wait until transaction is mined and returned mined
|
||||||
// block height
|
// block height
|
||||||
@ -96,35 +93,36 @@ pub async fn publish_cancel_transaction(
|
|||||||
|
|
||||||
pub async fn wait_for_bitcoin_refund(
|
pub async fn wait_for_bitcoin_refund(
|
||||||
tx_cancel: &TxCancel,
|
tx_cancel: &TxCancel,
|
||||||
|
tx_refund: &TxRefund,
|
||||||
punish_timelock: PunishTimelock,
|
punish_timelock: PunishTimelock,
|
||||||
refund_address: &bitcoin::Address,
|
|
||||||
bitcoin_wallet: &bitcoin::Wallet,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)> {
|
) -> Result<Option<bitcoin::Transaction>> {
|
||||||
let tx_refund = bitcoin::TxRefund::new(tx_cancel, refund_address);
|
let refund_tx_id = tx_refund.txid();
|
||||||
|
let seen_refund_tx =
|
||||||
|
bitcoin_wallet.watch_until_status(tx_refund, |status| status.has_been_seen());
|
||||||
|
|
||||||
let seen_refund_tx = bitcoin_wallet.watch_until_status(
|
let punish_timelock_expired = bitcoin_wallet.watch_until_status(tx_cancel, |status| {
|
||||||
tx_refund.txid(),
|
status.is_confirmed_with(punish_timelock)
|
||||||
refund_address.script_pubkey(),
|
});
|
||||||
|status| status.has_been_seen(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let punish_timelock_expired =
|
|
||||||
bitcoin_wallet.watch_until_status(tx_cancel.txid(), tx_cancel.script_pubkey(), |status| {
|
|
||||||
status.is_confirmed_with(punish_timelock)
|
|
||||||
});
|
|
||||||
|
|
||||||
pin_mut!(punish_timelock_expired);
|
pin_mut!(punish_timelock_expired);
|
||||||
pin_mut!(seen_refund_tx);
|
pin_mut!(seen_refund_tx);
|
||||||
|
|
||||||
match select(punish_timelock_expired, seen_refund_tx).await {
|
tokio::select! {
|
||||||
Either::Left(_) => Ok((tx_refund, None)),
|
seen_refund = seen_refund_tx => {
|
||||||
Either::Right((Ok(()), _)) => {
|
match seen_refund {
|
||||||
let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?;
|
Ok(()) => {
|
||||||
|
let published_refund_tx = bitcoin_wallet.get_raw_transaction(refund_tx_id).await?;
|
||||||
|
|
||||||
Ok((tx_refund, Some(published_refund_tx)))
|
Ok(Some(published_refund_tx))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
bail!(e.context("Failed to monitor refund transaction"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Either::Right((Err(e), _)) => {
|
_ = punish_timelock_expired => {
|
||||||
bail!(e.context("Failed to monitor refund transaction"))
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,20 +82,16 @@ async fn run_until_internal(
|
|||||||
} => {
|
} => {
|
||||||
timeout(
|
timeout(
|
||||||
execution_params.bob_time_to_act,
|
execution_params.bob_time_to_act,
|
||||||
bitcoin_wallet.watch_until_status(
|
bitcoin_wallet
|
||||||
state3.tx_lock.txid(),
|
.watch_until_status(&state3.tx_lock, |status| status.has_been_seen()),
|
||||||
state3.tx_lock.script_pubkey(),
|
|
||||||
|status| status.has_been_seen(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to find lock Bitcoin tx")??;
|
.context("Failed to find lock Bitcoin tx")??;
|
||||||
|
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.wait_for_transaction_finality(
|
.watch_until_status(&state3.tx_lock, |status| {
|
||||||
state3.tx_lock.txid(),
|
status.is_confirmed_with(execution_params.bitcoin_finality_confirmations)
|
||||||
state3.tx_lock.script_pubkey(),
|
})
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = AliceState::BtcLocked {
|
let state = AliceState::BtcLocked {
|
||||||
@ -209,30 +205,19 @@ async fn run_until_internal(
|
|||||||
} => {
|
} => {
|
||||||
let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
|
let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
|
||||||
ExpiredTimelocks::None => {
|
ExpiredTimelocks::None => {
|
||||||
let tx_redeem = TxRedeem::new(&state3.tx_lock, &state3.redeem_address);
|
match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete(
|
||||||
|
|
||||||
match tx_redeem.complete(
|
|
||||||
*encrypted_signature,
|
*encrypted_signature,
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
state3.s_a.to_secpfun_scalar(),
|
state3.s_a.to_secpfun_scalar(),
|
||||||
state3.B,
|
state3.B,
|
||||||
) {
|
) {
|
||||||
Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await {
|
Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await {
|
||||||
Ok(txid) => {
|
Ok((_, finality)) => match finality.await {
|
||||||
let publishded_redeem_tx = bitcoin_wallet
|
Ok(_) => AliceState::BtcRedeemed,
|
||||||
.wait_for_transaction_finality(
|
Err(e) => {
|
||||||
txid,
|
bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e)
|
||||||
state3.redeem_address.script_pubkey(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match publishded_redeem_tx {
|
|
||||||
Ok(_) => AliceState::BtcRedeemed,
|
|
||||||
Err(e) => {
|
|
||||||
bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e);
|
error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e);
|
||||||
state3
|
state3
|
||||||
@ -316,10 +301,10 @@ async fn run_until_internal(
|
|||||||
state3,
|
state3,
|
||||||
monero_wallet_restore_blockheight,
|
monero_wallet_restore_blockheight,
|
||||||
} => {
|
} => {
|
||||||
let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund(
|
let published_refund_tx = wait_for_bitcoin_refund(
|
||||||
&state3.tx_cancel(),
|
&state3.tx_cancel(),
|
||||||
|
&state3.tx_refund(),
|
||||||
state3.punish_timelock,
|
state3.punish_timelock,
|
||||||
&state3.refund_address,
|
|
||||||
&bitcoin_wallet,
|
&bitcoin_wallet,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -350,7 +335,7 @@ async fn run_until_internal(
|
|||||||
Some(published_refund_tx) => {
|
Some(published_refund_tx) => {
|
||||||
let spend_key = extract_monero_private_key(
|
let spend_key = extract_monero_private_key(
|
||||||
published_refund_tx,
|
published_refund_tx,
|
||||||
&tx_refund,
|
&state3.tx_refund(),
|
||||||
state3.s_a,
|
state3.s_a,
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
state3.S_b_bitcoin,
|
state3.S_b_bitcoin,
|
||||||
@ -405,36 +390,30 @@ async fn run_until_internal(
|
|||||||
state3.B,
|
state3.B,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let punish_script_pubkey = state3.punish_address.script_pubkey();
|
|
||||||
|
|
||||||
let punish_tx_finalised = async {
|
let punish_tx_finalised = async {
|
||||||
let txid = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?;
|
let (txid, finality) =
|
||||||
|
bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?;
|
||||||
|
|
||||||
bitcoin_wallet
|
finality.await?;
|
||||||
.wait_for_transaction_finality(txid, punish_script_pubkey)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Result::<_, anyhow::Error>::Ok(txid)
|
Result::<_, anyhow::Error>::Ok(txid)
|
||||||
};
|
};
|
||||||
|
|
||||||
let refund_tx_seen = bitcoin_wallet.watch_until_status(
|
let tx_refund = state3.tx_refund();
|
||||||
state3.tx_refund().txid(),
|
let refund_tx_seen =
|
||||||
state3.refund_address.script_pubkey(),
|
bitcoin_wallet.watch_until_status(&tx_refund, |status| status.has_been_seen());
|
||||||
|status| status.has_been_seen(),
|
|
||||||
);
|
|
||||||
|
|
||||||
pin_mut!(punish_tx_finalised);
|
pin_mut!(punish_tx_finalised);
|
||||||
pin_mut!(refund_tx_seen);
|
pin_mut!(refund_tx_seen);
|
||||||
|
|
||||||
match select(refund_tx_seen, punish_tx_finalised).await {
|
match select(refund_tx_seen, punish_tx_finalised).await {
|
||||||
Either::Left((Ok(()), _)) => {
|
Either::Left((Ok(()), _)) => {
|
||||||
let published_refund_tx = bitcoin_wallet
|
let published_refund_tx =
|
||||||
.get_raw_transaction(state3.tx_refund().txid())
|
bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let spend_key = extract_monero_private_key(
|
let spend_key = extract_monero_private_key(
|
||||||
published_refund_tx,
|
published_refund_tx,
|
||||||
&state3.tx_refund(),
|
&tx_refund,
|
||||||
state3.s_a,
|
state3.s_a,
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
state3.S_b_bitcoin,
|
state3.S_b_bitcoin,
|
||||||
|
@ -350,11 +350,9 @@ impl State3 {
|
|||||||
bitcoin_wallet: &bitcoin::Wallet,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(
|
.watch_until_status(&self.tx_lock, |status| {
|
||||||
self.tx_lock.txid(),
|
status.is_confirmed_with(self.cancel_timelock)
|
||||||
self.tx_lock.script_pubkey(),
|
})
|
||||||
|status| status.is_confirmed_with(self.cancel_timelock),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -389,12 +387,8 @@ impl State3 {
|
|||||||
) -> Result<ExpiredTimelocks> {
|
) -> Result<ExpiredTimelocks> {
|
||||||
let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
|
let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
|
||||||
|
|
||||||
let tx_lock_status = bitcoin_wallet
|
let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?;
|
||||||
.status_of_script(&self.tx_lock.script_pubkey(), &self.tx_lock.txid())
|
let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?;
|
||||||
.await?;
|
|
||||||
let tx_cancel_status = bitcoin_wallet
|
|
||||||
.status_of_script(&tx_cancel.script_pubkey(), &tx_cancel.txid())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(current_epoch(
|
Ok(current_epoch(
|
||||||
self.cancel_timelock,
|
self.cancel_timelock,
|
||||||
@ -454,14 +448,13 @@ impl State4 {
|
|||||||
let sig_b = self.b.sign(tx_cancel.digest());
|
let sig_b = self.b.sign(tx_cancel.digest());
|
||||||
|
|
||||||
let tx_cancel = tx_cancel
|
let tx_cancel = tx_cancel
|
||||||
.clone()
|
|
||||||
.add_signatures((self.A, sig_a), (self.b.public(), sig_b))
|
.add_signatures((self.A, sig_a), (self.b.public(), sig_b))
|
||||||
.expect(
|
.expect(
|
||||||
"sig_{a,b} to be valid signatures for
|
"sig_{a,b} to be valid signatures for
|
||||||
tx_cancel",
|
tx_cancel",
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx_id = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
|
let (tx_id, _) = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
|
||||||
|
|
||||||
Ok(tx_id)
|
Ok(tx_id)
|
||||||
}
|
}
|
||||||
@ -471,11 +464,7 @@ impl State4 {
|
|||||||
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());
|
||||||
|
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(
|
.watch_until_status(&tx_redeem, |status| status.has_been_seen())
|
||||||
tx_redeem.txid(),
|
|
||||||
self.redeem_address.script_pubkey(),
|
|
||||||
|status| status.has_been_seen(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let tx_redeem_candidate = bitcoin_wallet.get_raw_transaction(tx_redeem.txid()).await?;
|
let tx_redeem_candidate = bitcoin_wallet.get_raw_transaction(tx_redeem.txid()).await?;
|
||||||
@ -499,11 +488,9 @@ impl State4 {
|
|||||||
bitcoin_wallet: &bitcoin::Wallet,
|
bitcoin_wallet: &bitcoin::Wallet,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(
|
.watch_until_status(&self.tx_lock, |status| {
|
||||||
self.tx_lock.txid(),
|
status.is_confirmed_with(self.cancel_timelock)
|
||||||
self.tx_lock.script_pubkey(),
|
})
|
||||||
|status| status.is_confirmed_with(self.cancel_timelock),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -515,12 +502,8 @@ impl State4 {
|
|||||||
) -> Result<ExpiredTimelocks> {
|
) -> Result<ExpiredTimelocks> {
|
||||||
let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
|
let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
|
||||||
|
|
||||||
let tx_lock_status = bitcoin_wallet
|
let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?;
|
||||||
.status_of_script(&self.tx_lock.script_pubkey(), &self.tx_lock.txid())
|
let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?;
|
||||||
.await?;
|
|
||||||
let tx_cancel_status = bitcoin_wallet
|
|
||||||
.status_of_script(&tx_cancel.script_pubkey(), &tx_cancel.txid())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(current_epoch(
|
Ok(current_epoch(
|
||||||
self.cancel_timelock,
|
self.cancel_timelock,
|
||||||
@ -544,11 +527,9 @@ impl State4 {
|
|||||||
let signed_tx_refund =
|
let signed_tx_refund =
|
||||||
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
|
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
|
||||||
|
|
||||||
let txid = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
|
let (_, finality) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
|
||||||
|
|
||||||
bitcoin_wallet
|
finality.await?;
|
||||||
.wait_for_transaction_finality(txid, self.refund_address.script_pubkey())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -104,12 +104,10 @@ async fn run_until_internal(
|
|||||||
.sign_and_finalize(tx_lock.clone().into())
|
.sign_and_finalize(tx_lock.clone().into())
|
||||||
.await
|
.await
|
||||||
.context("Failed to sign Bitcoin lock transaction")?;
|
.context("Failed to sign Bitcoin lock transaction")?;
|
||||||
let tx_lock_id = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
|
let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
|
||||||
|
|
||||||
bitcoin_wallet
|
bitcoin_wallet
|
||||||
.watch_until_status(tx_lock_id, tx_lock.script_pubkey(), |status| {
|
.watch_until_status(&tx_lock, |status| status.is_confirmed())
|
||||||
status.is_confirmed()
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = BobState::BtcLocked(state3);
|
let state = BobState::BtcLocked(state3);
|
||||||
|
Loading…
Reference in New Issue
Block a user