From 5a3f43bea9b5294d4404ff528c12b6a6cffe50bb Mon Sep 17 00:00:00 2001 From: Zshan0 Date: Sat, 2 Jul 2022 18:29:38 +0900 Subject: [PATCH 1/2] Manual CPFP. --- src/app/message.rs | 3 ++ src/app/state/cmd.rs | 8 +++ src/app/state/spend_transaction.rs | 55 ++++++++++++++++++- src/app/view/spend_transaction.rs | 84 +++++++++++++++++++++++++++++- src/daemon/client/mod.rs | 6 +++ src/daemon/embedded.rs | 12 +++++ src/daemon/mod.rs | 1 + 7 files changed, 165 insertions(+), 4 deletions(-) diff --git a/src/app/message.rs b/src/app/message.rs index 0835ed0a..46c066d8 100644 --- a/src/app/message.rs +++ b/src/app/message.rs @@ -99,6 +99,9 @@ pub enum SpendTxMessage { Update, Updated(Result<(), RevaultDError>), WithPriority(bool), + // [ZEE] Addition for CPFP + CPFP, + CPFPed(Result<(), RevaultDError>), } #[derive(Debug, Clone)] diff --git a/src/app/state/cmd.rs b/src/app/state/cmd.rs index 3a786a81..48a9b736 100644 --- a/src/app/state/cmd.rs +++ b/src/app/state/cmd.rs @@ -89,6 +89,14 @@ pub async fn broadcast_spend_tx( revaultd.broadcast_spend_tx(&txid, with_priority) } +pub async fn cpfp( + revaultd: Arc, + txids: Vec, + fee_rate: f64, +) -> Result<(), RevaultDError> { + revaultd.cpfp(&txids, fee_rate) +} + pub async fn emergency(revaultd: Arc) -> Result<(), RevaultDError> { revaultd.emergency() } diff --git a/src/app/state/spend_transaction.rs b/src/app/state/spend_transaction.rs index 0445b37c..56bfdce1 100644 --- a/src/app/state/spend_transaction.rs +++ b/src/app/state/spend_transaction.rs @@ -11,13 +11,13 @@ use crate::{ error::Error, message::{Message, SpendTxMessage}, state::{ - cmd::{broadcast_spend_tx, delete_spend_tx, list_vaults, update_spend_tx}, + cmd::{broadcast_spend_tx, cpfp, delete_spend_tx, list_vaults, update_spend_tx}, sign::{Signer, SpendTransactionTarget}, State, }, view::spend_transaction::{ spend_tx_confirmed, spend_tx_deprecated, spend_tx_processing, - SpendTransactionBroadcastView, SpendTransactionDeleteView, + SpendTransactionBroadcastView, SpendTransactionCPFPView, SpendTransactionDeleteView, SpendTransactionListItemView, SpendTransactionSharePsbtView, SpendTransactionSignView, SpendTransactionView, }, @@ -154,6 +154,13 @@ pub enum SpendTransactionAction { warning: Option, view: SpendTransactionDeleteView, }, + CPFP { + view: SpendTransactionCPFPView, + processing: bool, + success: bool, + warning: Option, + feerate: form::Value, + }, } impl SpendTransactionAction { @@ -311,6 +318,43 @@ impl SpendTransactionAction { *with_priority = priority; } } + // [ZEE] Receiver for the new type of message. + SpendTxMessage::CPFP => { + if let Self::CPFP { + processing, + feerate, + .. + } = self + { + *processing = true; + // [ZEE] Placeholder for setting feerate. + let fee_f64: f64 = 0.0; + return Command::perform( + cpfp( + ctx.revaultd.clone(), + [psbt.global.unsigned_tx.txid()].to_vec(), + fee_f64, + ), + SpendTxMessage::CPFPed, + ); + } + } + SpendTxMessage::CPFPed(res) => { + if let Self::CPFP { + success, + processing, + warning, + .. + } = self + { + *processing = false; + match res { + Ok(()) => *success = true, + Err(e) => *warning = Error::from(e).into(), + }; + } + } + // [ZEE] handling the Broadcast request. SpendTxMessage::Broadcast => { if let Self::Broadcast { processing, @@ -501,6 +545,13 @@ impl SpendTransactionAction { success, warning, } => view.view(&processing, &success, warning.as_ref()), + Self::CPFP { + view, + warning, + feerate, + processing, + success, + } => view.view(*processing, *success, feerate, warning.as_ref()), } } } diff --git a/src/app/view/spend_transaction.rs b/src/app/view/spend_transaction.rs index 4f91e4e4..be070bda 100644 --- a/src/app/view/spend_transaction.rs +++ b/src/app/view/spend_transaction.rs @@ -1,8 +1,8 @@ use bitcoin::{util::psbt::PartiallySignedTransaction as Psbt, Amount}; use iced::{ - alignment::Horizontal, scrollable, tooltip, Alignment, Checkbox, Column, Container, Element, - Length, Row, Tooltip, + alignment::Horizontal, scrollable, text_input, tooltip, Alignment, Checkbox, Column, Container, + Element, Length, Row, Tooltip, }; use revaultd::revault_tx::transactions::RevaultTransaction; @@ -541,6 +541,7 @@ impl SpendTransactionBroadcastView { ) .spacing(5), ) + // [ZEE] The button for broadcasting the transaction. .push( button::important( &mut self.confirm_button, @@ -691,3 +692,82 @@ impl SpendTransactionListItemView { .into() } } + +#[derive(Debug)] +pub struct SpendTransactionCPFPView { + cpfp_input: text_input::State, + confirm_button: iced::button::State, +} + +impl SpendTransactionCPFPView { + pub fn new() -> Self { + Self { + cpfp_input: text_input::State::new(), + confirm_button: iced::button::State::new(), + } + } + + pub fn view( + &mut self, + processing: bool, + success: bool, + feerate: &form::Value, + warning: Option<&Error>, + ) -> Element { + let mut col_action = Column::new(); + if let Some(error) = warning { + col_action = col_action.push(card::alert_warning(Container::new( + Text::new(&error.to_string()).small(), + ))); + } + + if processing { + col_action = col_action.push(button::important( + &mut self.confirm_button, + button::button_content(None, "CPFPing"), + )); + } else if success { + col_action = col_action.push( + card::success(Text::new("Transaction has been CPFPed")) + .padding(20) + .width(Length::Fill) + .align_x(Horizontal::Center), + ); + } else { + // [ZEE] Check if the transaction has been mined or + // needs to be CPFPed above. + col_action = col_action + .push(Text::new("Transaction has not been mined")) + .push( + Row::new() + .push(Text::new("CPFP amount in sat/vbyte:").bold()) + .push( + form::Form::new(&mut self.cpfp_input, "CPFP", feerate, |msg| { + Message::SpendTx(SpendTxMessage::CPFP) + }) + .warning("Feerate must be a number.") + .size(20) + .padding(10) + .render(), + ), + ) + // [ZEE] The button for broadcasting the transaction. + .push( + button::important( + &mut self.confirm_button, + button::button_content(None, "CPFP"), + ) + .width(Length::Units(200)) + .on_press(Message::SpendTx(SpendTxMessage::CPFP)), + ); + } + + card::white(Container::new( + col_action.align_items(Alignment::Center).spacing(20), + )) + .width(Length::Fill) + .align_x(Horizontal::Center) + .padding(20) + .into() + } +} diff --git a/src/daemon/client/mod.rs b/src/daemon/client/mod.rs index a0c5f0eb..48e48b92 100644 --- a/src/daemon/client/mod.rs +++ b/src/daemon/client/mod.rs @@ -221,6 +221,12 @@ impl Daemon for RevaultD { )?; Ok(resp.events) } + + fn cpfp(&self, txids: &[Txid], feerate: f64) -> Result<(), RevaultDError> { + let _res: serde_json::value::Value = + self.call("cpfp", Some(vec![json!(txids), json!(feerate)]))?; + Ok(()) + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/daemon/embedded.rs b/src/daemon/embedded.rs index 02d5f2b8..db8f19d6 100644 --- a/src/daemon/embedded.rs +++ b/src/daemon/embedded.rs @@ -319,4 +319,16 @@ impl Daemon for EmbeddedDaemon { .get_history(start, end, limit, kind) .map_err(|e| e.into()) } + fn cpfp(&self, txids: &[Txid], feerate: f64) -> Result<(), RevaultDError> { + Ok(()) + // Here we implement the call to the backend. + // self.handle + // .as_ref() + // .ok_or(RevaultDError::NoAnswer)? + // .lock() + // .unwrap() + // .control + // .cpfp(txids, feerate) + // .map_err(|e| e.into()) + } } diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index da8c5bd8..b0de6aec 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -114,4 +114,5 @@ pub trait Daemon: Debug { end: u32, limit: u64, ) -> Result, RevaultDError>; + fn cpfp(&self, txids: &[Txid], feerate: f64) -> Result<(), RevaultDError>; } From a20b7be15a306efe2f8187008e8e62f825953e9a Mon Sep 17 00:00:00 2001 From: Zshan0 Date: Tue, 2 Aug 2022 14:27:08 +0530 Subject: [PATCH 2/2] Separation of `CPFP` from `SpendTransactionAction` --- src/app/message.rs | 12 ++- src/app/state/spend_transaction.rs | 148 +++++++++++++++++++---------- src/app/view/spend_transaction.rs | 35 ++++--- 3 files changed, 127 insertions(+), 68 deletions(-) diff --git a/src/app/message.rs b/src/app/message.rs index 46c066d8..4e15af59 100644 --- a/src/app/message.rs +++ b/src/app/message.rs @@ -67,6 +67,15 @@ pub enum Message { AddWatchtower, LoadDaemonConfig(DaemonConfig), DaemonConfigLoaded(Result<(), Error>), + CPFP(CPFPMessage), +} + +// [ZEE] Addition for CPFP +#[derive(Debug, Clone)] +pub enum CPFPMessage { + CPFP(String), + ConfirmCPFP, + CPFPed(Result<(), RevaultDError>), } #[derive(Debug, Clone)] @@ -99,9 +108,6 @@ pub enum SpendTxMessage { Update, Updated(Result<(), RevaultDError>), WithPriority(bool), - // [ZEE] Addition for CPFP - CPFP, - CPFPed(Result<(), RevaultDError>), } #[derive(Debug, Clone)] diff --git a/src/app/state/spend_transaction.rs b/src/app/state/spend_transaction.rs index 56bfdce1..1936560b 100644 --- a/src/app/state/spend_transaction.rs +++ b/src/app/state/spend_transaction.rs @@ -9,7 +9,7 @@ use crate::{ app::{ context::Context, error::Error, - message::{Message, SpendTxMessage}, + message::{CPFPMessage, Message, SpendTxMessage}, state::{ cmd::{broadcast_spend_tx, cpfp, delete_spend_tx, list_vaults, update_spend_tx}, sign::{Signer, SpendTransactionTarget}, @@ -154,13 +154,102 @@ pub enum SpendTransactionAction { warning: Option, view: SpendTransactionDeleteView, }, - CPFP { - view: SpendTransactionCPFPView, - processing: bool, - success: bool, - warning: Option, - feerate: form::Value, - }, +} + +#[derive(Debug)] +pub struct SpendTransactionCPFP { + tx: model::SpendTx, + view: SpendTransactionCPFPView, + processing: bool, + success: bool, + warning: Option, + feerate: form::Value, +} +impl SpendTransactionCPFP { + pub fn new(tx: model::SpendTx) -> Self { + Self { + tx, + view: SpendTransactionCPFPView::new(), + processing: false, + success: false, + warning: None, + feerate: form::Value::default(), + } + } + + fn feerate(&self) -> Result { + if self.feerate.value.is_empty() { + return Err(Error::Unexpected("Amount should be non-zero".to_string())); + } + + let feerate: f64 = self + .feerate + .value + .to_string() + .parse() + .unwrap_or_else(|_str| 0.0); + + if feerate == 0.0 { + return Err(Error::Unexpected("Invalid feerate".to_string())); + } + + Ok(feerate) + } + + fn valid(&self) -> bool { + !self.feerate.value.is_empty() && self.feerate.valid + } + + fn update( + &mut self, + ctx: &Context, + psbt: &mut Psbt, + message: CPFPMessage, + ) -> Command { + match message { + CPFPMessage::CPFP(feerate) => { + self.feerate.value = feerate; + if let Ok(parsed_feerate) = self.feerate() { + self.feerate.valid = true; + } else { + self.feerate.valid = false; + } + } + CPFPMessage::ConfirmCPFP => { + if self.feerate.valid { + self.processing = true; + let fee_rate: f64 = self.feerate().unwrap(); + return Command::perform( + cpfp( + ctx.revaultd.clone(), + [psbt.global.unsigned_tx.txid()].to_vec(), + fee_rate, + ), + CPFPMessage::CPFPed, + ); + } + } + CPFPMessage::CPFPed(res) => { + self.processing = false; + match res { + Ok(()) => self.success = true, + Err(e) => self.warning = Error::from(e).into(), + }; + } + _ => {} + }; + Command::none() + } + + fn view(&mut self) -> Element { + self.view.view( + self.processing, + self.success, + &self.tx, + &self.feerate, + self.warning.as_ref(), + ) + } } impl SpendTransactionAction { @@ -318,42 +407,6 @@ impl SpendTransactionAction { *with_priority = priority; } } - // [ZEE] Receiver for the new type of message. - SpendTxMessage::CPFP => { - if let Self::CPFP { - processing, - feerate, - .. - } = self - { - *processing = true; - // [ZEE] Placeholder for setting feerate. - let fee_f64: f64 = 0.0; - return Command::perform( - cpfp( - ctx.revaultd.clone(), - [psbt.global.unsigned_tx.txid()].to_vec(), - fee_f64, - ), - SpendTxMessage::CPFPed, - ); - } - } - SpendTxMessage::CPFPed(res) => { - if let Self::CPFP { - success, - processing, - warning, - .. - } = self - { - *processing = false; - match res { - Ok(()) => *success = true, - Err(e) => *warning = Error::from(e).into(), - }; - } - } // [ZEE] handling the Broadcast request. SpendTxMessage::Broadcast => { if let Self::Broadcast { @@ -545,13 +598,6 @@ impl SpendTransactionAction { success, warning, } => view.view(&processing, &success, warning.as_ref()), - Self::CPFP { - view, - warning, - feerate, - processing, - success, - } => view.view(*processing, *success, feerate, warning.as_ref()), } } } diff --git a/src/app/view/spend_transaction.rs b/src/app/view/spend_transaction.rs index be070bda..3a10879e 100644 --- a/src/app/view/spend_transaction.rs +++ b/src/app/view/spend_transaction.rs @@ -20,7 +20,7 @@ use crate::{ app::{ context::Context, error::Error, - message::{Message, SpendTxMessage}, + message::{CPFPMessage, Message, SpendTxMessage}, view::{manager::spend_tx_with_feerate_view, warning::warn}, }, daemon::model, @@ -695,14 +695,14 @@ impl SpendTransactionListItemView { #[derive(Debug)] pub struct SpendTransactionCPFPView { - cpfp_input: text_input::State, + fee_input: text_input::State, confirm_button: iced::button::State, } impl SpendTransactionCPFPView { pub fn new() -> Self { Self { - cpfp_input: text_input::State::new(), + fee_input: text_input::State::new(), confirm_button: iced::button::State::new(), } } @@ -711,6 +711,7 @@ impl SpendTransactionCPFPView { &mut self, processing: bool, success: bool, + tx: &model::SpendTx, feerate: &form::Value, warning: Option<&Error>, ) -> Element { @@ -734,32 +735,38 @@ impl SpendTransactionCPFPView { .align_x(Horizontal::Center), ); } else { - // [ZEE] Check if the transaction has been mined or - // needs to be CPFPed above. col_action = col_action .push(Text::new("Transaction has not been mined")) - .push( - Row::new() + .push(match tx.status { + // Only `Broadcasted` can be CPFPed. + model::ListSpendStatus::Broadcasted => Row::new() .push(Text::new("CPFP amount in sat/vbyte:").bold()) .push( - form::Form::new(&mut self.cpfp_input, "CPFP", feerate, |msg| { - Message::SpendTx(SpendTxMessage::CPFP) + form::Form::new(&mut self.fee_input, "CPFP", feerate, |msg| { + Message::CPFP(CPFPMessage::CPFP(msg)) }) .warning("Feerate must be a number.") .size(20) .padding(10) .render(), ), - ) + _ => Row::new().push(Text::new("Transaction can't be CPFPed.")), + }) // [ZEE] The button for broadcasting the transaction. - .push( - button::important( + .push(match tx.status { + model::ListSpendStatus::Broadcasted => button::important( &mut self.confirm_button, button::button_content(None, "CPFP"), ) .width(Length::Units(200)) - .on_press(Message::SpendTx(SpendTxMessage::CPFP)), - ); + .on_press(Message::CPFP(CPFPMessage::ConfirmCPFP)), + + _ => button::transparent( + &mut self.confirm_button, + button::button_content(None, "Can't CPFP"), + ) + .width(Length::Units(200)), + }); } card::white(Container::new(