From 787827e50f3e7b3d395f949899244d86b22212c2 Mon Sep 17 00:00:00 2001 From: binarybaron Date: Thu, 1 Aug 2024 18:33:50 +0200 Subject: [PATCH] feat: Add RPC endpoint for requesting raw cancel and refund transactions --- Cargo.lock | 20 +++++++++--------- swap/src/api/request.rs | 37 +++++++++++++++++++++++++--------- swap/src/bitcoin/cancel.rs | 5 +++++ swap/src/database.rs | 19 ++++++++++++++++- swap/src/database/sqlite.rs | 1 + swap/src/protocol/bob/state.rs | 10 ++++----- swap/src/rpc/methods.rs | 16 +++++++++++++++ 7 files changed, 83 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78413bfc..bb01de1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -743,7 +743,7 @@ dependencies = [ "nom", "pathdiff", "serde", - "toml 0.8.17", + "toml 0.8.16", ] [[package]] @@ -4580,7 +4580,7 @@ dependencies = [ "tokio-tar", "tokio-tungstenite", "tokio-util", - "toml 0.8.17", + "toml 0.8.16", "torut", "tracing", "tracing-appender", @@ -4920,9 +4920,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.17" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a44eede9b727419af8095cb2d72fab15487a541f54647ad4414b34096ee4631" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", @@ -4932,18 +4932,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.18" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1490595c74d930da779e944f5ba2ecdf538af67df1a9848cbd156af43c1b7cf0" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap 2.1.0", "serde", @@ -5767,9 +5767,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] diff --git a/swap/src/api/request.rs b/swap/src/api/request.rs index d47e9a0d..f961cf74 100644 --- a/swap/src/api/request.rs +++ b/swap/src/api/request.rs @@ -7,9 +7,12 @@ use crate::network::swarm; use crate::protocol::bob::{BobState, Swap}; use crate::protocol::{bob, State}; use crate::{bitcoin, cli, monero, rpc}; +use crate::database::SwapStateVecExt; use anyhow::{bail, Context as AnyContext, Result}; +use ::bitcoin::consensus::encode::serialize_hex; use comfy_table::Table; use libp2p::core::Multiaddr; +use monero_rpc::wallet::BlockHeight; use qrcode::render::unicode; use qrcode::QrCode; use rust_decimal::prelude::FromPrimitive; @@ -69,6 +72,9 @@ pub enum Method { swap_id: Uuid, }, GetRawStates, + GetRawTransactions { + swap_id: Uuid, + }, } impl Method { @@ -164,6 +170,14 @@ impl Method { method_name = "WithdrawBtc", log_reference_id = field::Empty ) + }, + Method::GetRawTransactions { swap_id } => { + debug_span!( + "method", + method_name = "GetRawTransactions", + swap_id=%swap_id, + log_reference_id = field::Empty + ) } }; if let Some(log_reference_id) = log_reference_id { @@ -660,15 +674,7 @@ impl Request { let latest_state: BobState = state.try_into()?; let all_states = context.db.get_states(swap_id).await?; let state3 = all_states - .iter() - .find_map(|s| { - if let State::Bob(BobState::BtcLocked { state3, .. }) = s { - Some(state3) - } else { - None - } - }) - .context("Failed to get \"BtcLocked\" state")?; + .find_state3()?; let swap_start_date = context.db.get_swap_start_date(swap_id).await?; let peer_id = context.db.get_peer_id(swap_id).await?; @@ -915,6 +921,19 @@ impl Request { Method::GetCurrentSwap => Ok(json!({ "swap_id": context.swap_lock.get_current_swap_id().await })), + Method::GetRawTransactions { swap_id } => { + let all_states = context.db.get_states(swap_id).await?; + let state3 = all_states.find_state3()?; + + let cancelledState = state3.cancel(BlockHeight { height: 0 }); + let txCancel = cancelledState.construct_tx_cancel()?; + let txRefund = cancelledState.signed_refund_transaction()?; + + Ok(json!({ + "tx_cancel": txCancel.serialize_hex(), + "tx_refund": serialize_hex(&txRefund), + })) + } } } diff --git a/swap/src/bitcoin/cancel.rs b/swap/src/bitcoin/cancel.rs index 34612148..648a6145 100644 --- a/swap/src/bitcoin/cancel.rs +++ b/swap/src/bitcoin/cancel.rs @@ -16,6 +16,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; use std::ops::Add; +use bdk::bitcoin::consensus::encode::{serialize_hex, serialize}; /// Represent a timelock, expressed in relative block height as defined in /// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki). @@ -160,6 +161,10 @@ impl TxCancel { pub fn txid(&self) -> Txid { self.inner.txid() } + + pub fn serialize_hex(&self) -> String { + serialize_hex(&self.inner) + } pub fn digest(&self) -> Sighash { self.digest diff --git a/swap/src/database.rs b/swap/src/database.rs index 7c6185f7..33cb6f50 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -1,10 +1,12 @@ pub use alice::Alice; pub use bob::Bob; +use monero_rpc::wallet::BlockHeight; pub use sqlite::SqliteDatabase; use crate::fs::ensure_directory_exists; +use crate::protocol::bob::{BobState, State2, State3}; use crate::protocol::{Database, State}; -use anyhow::{bail, Result}; +use anyhow::{bail, Result, anyhow}; use serde::{Deserialize, Serialize}; use std::fmt::Display; use std::path::Path; @@ -105,3 +107,18 @@ pub async fn open_db( Ok(Arc::new(sqlite)) } } + +pub trait SwapStateVecExt { + fn find_state3(&self) -> Result; +} + +impl SwapStateVecExt for Vec { + fn find_state3(&self) -> Result { + self.iter() + .find_map(|state| match state { + State::Bob(BobState::BtcLocked {state3, ..}) => Some(state3.clone()), + _ => None + }) + .ok_or_else(|| anyhow!("No BobState::BtcLocked found")) + } +} \ No newline at end of file diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs index eb013f66..2c3f356b 100644 --- a/swap/src/database/sqlite.rs +++ b/swap/src/database/sqlite.rs @@ -1,5 +1,6 @@ use crate::database::Swap; use crate::monero::{Address, TransferProof}; +use crate::protocol::bob::{BobState, State3}; use crate::protocol::{Database, State}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 04e7778e..f3cf04f5 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -727,11 +727,7 @@ impl State6 { &self, bitcoin_wallet: &bitcoin::Wallet, ) -> Result<(Txid, Subscription)> { - let transaction = self - .construct_tx_cancel()? - .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) - .context("Failed to complete Bitcoin cancel transaction")?; - + let transaction = self.signed_cancel_transaction()?; let (tx_id, subscription) = bitcoin_wallet.broadcast(transaction, "cancel").await?; Ok((tx_id, subscription)) @@ -760,6 +756,10 @@ impl State6 { Ok(signed_tx_refund) } + pub fn signed_cancel_transaction(&self) -> Result { + self.construct_tx_cancel()?.complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) + } + pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } diff --git a/swap/src/rpc/methods.rs b/swap/src/rpc/methods.rs index f804e085..0a2d427a 100644 --- a/swap/src/rpc/methods.rs +++ b/swap/src/rpc/methods.rs @@ -213,6 +213,22 @@ pub fn register_modules(context: Arc) -> Result> execute_request(params, Method::GetCurrentSwap, &context).await })?; + module.register_async_method("get_raw_transactions", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let swap_id = params.get("swap_id").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string()) + })?; + + let swap_id = as_uuid(swap_id).ok_or_else(|| { + jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()) + })?; + + execute_request(params_raw, Method::GetRawTransactions { + swap_id + }, &context).await + })?; + Ok(module) }