Skip to content

Commit

Permalink
Merge #944: gui: duplicate labels for rbf psbt
Browse files Browse the repository at this point in the history
6dd1d87 gui: reuse labels for rbf psbt (edouardparis)
dc2fb2a gui: move rbf in iced command (edouardparis)

Pull request description:

  close #861

ACKs for top commit:
  edouardparis:
    Self-ACK 6dd1d87

Tree-SHA512: 62433ec28f4924c3d8fbd342a91432a51f8e7e05e1d9e6da74f3053bba6337dc24db92d44598325763c7b517bb07eaa2b3dc673c666c99ed0c56057f2ef74251
  • Loading branch information
edouardparis committed Jan 31, 2024
2 parents 05bbeff + 6dd1d87 commit bc7f2e3
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 51 deletions.
3 changes: 2 additions & 1 deletion gui/src/app/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use liana::{
miniscript::bitcoin::{
bip32::{ChildNumber, Fingerprint},
psbt::Psbt,
Address,
Address, Txid,
},
};

Expand All @@ -30,6 +30,7 @@ pub enum Message {
Labels(Result<HashMap<String, String>, Error>),
SpendTxs(Result<Vec<SpendTx>, Error>),
Psbt(Result<Psbt, Error>),
RbfPsbt(Result<Txid, Error>),
Recovery(Result<SpendTx, Error>),
Signed(Fingerprint, Result<Psbt, Error>),
WalletRegistered(Result<Fingerprint, Error>),
Expand Down
157 changes: 107 additions & 50 deletions gui/src/app/state/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
use std::{
collections::HashMap,
convert::TryInto,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};

use iced::Command;
use liana::{
miniscript::bitcoin::Txid,
miniscript::bitcoin::{OutPoint, Txid},
spend::{SpendCreationError, MAX_FEERATE},
};
use liana_ui::{
component::{form, modal::Modal},
widget::*,
};

use crate::app::{
cache::Cache,
error::Error,
message::Message,
state::{label::LabelsEdited, State},
view,
use crate::{
app::{
cache::Cache,
error::Error,
message::Message,
state::{label::LabelsEdited, State},
view,
},
daemon::model,
};

use crate::daemon::{
model::{CreateSpendResult, HistoryTransaction, Labelled},
model::{CreateSpendResult, HistoryTransaction, LabelItem, Labelled},
Daemon,
};

Expand Down Expand Up @@ -65,11 +69,7 @@ impl State for TransactionsPanel {
self.warning.as_ref(),
);
if let Some(modal) = &self.create_rbf_modal {
Modal::new(content, modal.view())
.on_blur(Some(view::Message::CreateRbf(
view::CreateRbfMessage::Cancel,
)))
.into()
modal.view(content)
} else {
content
}
Expand Down Expand Up @@ -129,8 +129,7 @@ impl State for TransactionsPanel {
.to_sat()
.checked_div(tx.tx.vsize().try_into().unwrap())
.unwrap();
let modal =
CreateRbfModal::new(tx.tx.txid(), is_cancel, prev_feerate_vb);
let modal = CreateRbfModal::new(tx.clone(), is_cancel, prev_feerate_vb);
self.create_rbf_modal = Some(modal);
}
}
Expand Down Expand Up @@ -243,7 +242,7 @@ impl From<TransactionsPanel> for Box<dyn State> {

pub struct CreateRbfModal {
/// Transaction to replace.
txid: Txid,
tx: model::HistoryTransaction,
/// Whether to cancel or bump fee.
is_cancel: bool,
/// Min feerate required for RBF.
Expand All @@ -252,16 +251,18 @@ pub struct CreateRbfModal {
feerate_val: form::Value<String>,
/// Parsed feerate.
feerate_vb: Option<u64>,
warning: Option<Error>,
/// Replacement transaction ID.
replacement_txid: Option<Txid>,

processing: bool,
warning: Option<Error>,
}

impl CreateRbfModal {
fn new(txid: Txid, is_cancel: bool, prev_feerate_vb: u64) -> Self {
fn new(tx: model::HistoryTransaction, is_cancel: bool, prev_feerate_vb: u64) -> Self {
let min_feerate_vb = prev_feerate_vb.checked_add(1).unwrap();
Self {
txid,
tx,
is_cancel,
min_feerate_vb,
feerate_val: form::Value {
Expand All @@ -274,8 +275,9 @@ impl CreateRbfModal {
} else {
Some(min_feerate_vb)
},
warning: None,
replacement_txid: None,
warning: None,
processing: false,
}
}

Expand All @@ -301,43 +303,98 @@ impl CreateRbfModal {
self.feerate_vb = None;
}
}
Message::RbfPsbt(res) => {
self.processing = false;
match res {
Ok(txid) => {
self.replacement_txid = Some(txid);
}
Err(e) => self.warning = Some(e),
}
}
Message::View(view::Message::CreateRbf(view::CreateRbfMessage::Confirm)) => {
self.warning = None;
self.processing = true;
return Command::perform(
rbf(daemon, self.tx.clone(), self.is_cancel, self.feerate_vb),
Message::RbfPsbt,
);
}
_ => {}
}
Command::none()
}
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<view::Message> {
let modal = Modal::new(
content,
view::transactions::create_rbf_modal(
self.is_cancel,
&self.feerate_val,
self.replacement_txid,
self.warning.as_ref(),
),
);
if self.processing {
modal
} else {
modal.on_blur(Some(view::Message::CreateRbf(
view::CreateRbfMessage::Cancel,
)))
}
.into()
}
}

async fn rbf(
daemon: Arc<dyn Daemon + Sync + Send>,
previous_tx: model::HistoryTransaction,
is_cancel: bool,
feerate_vb: Option<u64>,
) -> Result<Txid, Error> {
let previous_txid = previous_tx.tx.txid();
let psbt = match daemon.rbf_psbt(&previous_txid, is_cancel, feerate_vb)? {
CreateSpendResult::Success { psbt, .. } => psbt,
CreateSpendResult::InsufficientFunds { missing } => {
return Err(
SpendCreationError::CoinSelection(liana::spend::InsufficientFunds { missing })
.into(),
);
}
};

let psbt = match daemon.rbf_psbt(&self.txid, self.is_cancel, self.feerate_vb) {
Ok(res) => match res {
CreateSpendResult::Success { psbt, .. } => psbt,
CreateSpendResult::InsufficientFunds { missing } => {
self.warning = Some(
SpendCreationError::CoinSelection(
liana::spend::InsufficientFunds { missing },
)
.into(),
);
return Command::none();
if !is_cancel {
let mut labels = HashMap::<LabelItem, Option<String>>::new();
let new_txid = psbt.unsigned_tx.txid();
for item in previous_tx.labelled() {
if let Some(label) = previous_tx.labels.get(&item.to_string()) {
match item {
LabelItem::Txid(_) => {
labels.insert(new_txid.into(), Some(label.to_string()));
}
LabelItem::OutPoint(o) => {
if let Some(previous_output) = previous_tx.tx.output.get(o.vout as usize) {
for (vout, output) in psbt.unsigned_tx.output.iter().enumerate() {
if output.script_pubkey == previous_output.script_pubkey {
labels.insert(
LabelItem::OutPoint(OutPoint {
txid: new_txid,
vout: vout as u32,
}),
Some(label.to_string()),
);
}
}
}
},
Err(e) => {
self.warning = Some(e.into());
return Command::none();
}
};
if let Err(e) = daemon.update_spend_tx(&psbt) {
self.warning = Some(e.into());
return Command::none();
// Address label is already in database
LabelItem::Address(_) => {}
}
self.replacement_txid = Some(psbt.unsigned_tx.txid());
}
_ => {}
}
Command::none()
}
fn view(&self) -> Element<view::Message> {
view::transactions::create_rbf_modal(
self.is_cancel,
&self.feerate_val,
self.replacement_txid,
self.warning.as_ref(),
)

daemon.update_labels(&labels)?;
}

daemon.update_spend_tx(&psbt)?;
Ok(psbt.unsigned_tx.txid())
}

0 comments on commit bc7f2e3

Please sign in to comment.