Skip to content

Commit

Permalink
gui(psbt): check for conflicting txs before broadcast
Browse files Browse the repository at this point in the history
  • Loading branch information
jp1ac4 committed Feb 1, 2024
1 parent beabf08 commit 881d9a7
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
3 changes: 2 additions & 1 deletion gui/src/app/message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;

use liana::{
Expand Down Expand Up @@ -42,4 +42,5 @@ pub enum Message {
HistoryTransactions(Result<Vec<HistoryTransaction>, Error>),
PendingTransactions(Result<Vec<HistoryTransaction>, Error>),
LabelsUpdated(Result<HashMap<String, Option<String>>, Error>),
BroadcastModal(Result<HashSet<Txid>, Error>),
}
45 changes: 42 additions & 3 deletions gui/src/app/state/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use iced::Subscription;
use iced::Command;
use liana::{
descriptors::LianaPolicy,
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network},
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network, Txid},
};

use liana_ui::component::toast;
Expand Down Expand Up @@ -167,7 +167,29 @@ impl PsbtState {
return cmd;
}
Message::View(view::Message::Spend(view::SpendTxMessage::Broadcast)) => {
self.action = Some(PsbtAction::Broadcast(BroadcastAction::default()));
let outpoints: HashSet<_> = self.tx.coins.keys().cloned().collect();
return Command::perform(
async move {
daemon
// TODO: filter for the outpoints in `tx.coins` when this is possible:
// https://github.com/wizardsardine/liana/issues/677
.list_coins()
.map(|res| {
res.coins
.iter()
.filter_map(|c| {
if outpoints.contains(&c.outpoint) {
c.spend_info.map(|info| info.txid)
} else {
None
}
})
.collect()
})
.map_err(|e| e.into())
},
Message::BroadcastModal,
);
}
Message::View(view::Message::Spend(view::SpendTxMessage::Save)) => {
self.action = Some(PsbtAction::Save(SaveAction::default()));
Expand All @@ -194,6 +216,17 @@ impl PsbtState {
.update(daemon.clone(), message, &mut self.tx);
}
}
Message::BroadcastModal(res) => match res {
Ok(conflicting_txids) => {
self.action = Some(PsbtAction::Broadcast(BroadcastAction {
conflicting_txids,
..Default::default()
}));
}
Err(e) => {
self.warning = Some(e);
}
},
_ => {
if let Some(action) = self.action.as_mut() {
return action
Expand Down Expand Up @@ -277,6 +310,8 @@ impl Action for SaveAction {
pub struct BroadcastAction {
broadcast: bool,
error: Option<Error>,
/// IDs of any directly conflicting transactions.
conflicting_txids: HashSet<Txid>,
}

impl Action for BroadcastAction {
Expand Down Expand Up @@ -314,7 +349,11 @@ impl Action for BroadcastAction {
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> {
modal::Modal::new(
content,
view::psbt::broadcast_action(self.error.as_ref(), self.broadcast),
view::psbt::broadcast_action(
&self.conflicting_txids,
self.error.as_ref(),
self.broadcast,
),
)
.on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel)))
.into()
Expand Down
70 changes: 67 additions & 3 deletions gui/src/app/view/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,15 @@ pub fn save_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Mess
}
}

pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Message> {
/// Return the modal view to broadcast a transaction.
///
/// `conflicting_txids` contains the IDs of any directly conflicting transactions
/// of the transaction to be broadcast.
pub fn broadcast_action<'a>(
conflicting_txids: &HashSet<Txid>,
warning: Option<&Error>,
saved: bool,
) -> Element<'a, Message> {
if saved {
card::simple(text("Transaction is broadcast"))
.width(Length::Fixed(400.0))
Expand All @@ -151,15 +159,71 @@ pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a,
Column::new()
.spacing(10)
.push_maybe(warning.map(|w| warn(Some(w))))
.push(text("Broadcast the transaction"))
.push(Container::new(h4_bold("Broadcast the transaction")).width(Length::Fill))
.push_maybe(if conflicting_txids.is_empty() {
None
} else {
Some(
conflicting_txids.iter().fold(
Column::new()
.spacing(5)
.push(Row::new().spacing(10).push(icon::warning_icon()).push(text(
if conflicting_txids.len() > 1 {
"WARNING: Broadcasting this transaction \
will invalidate some pending payments."
} else {
"WARNING: Broadcasting this transaction \
will invalidate a pending payment."
},
)))
.push(Row::new().padding([0, 30]).push(text(
if conflicting_txids.len() > 1 {
"The following transactions are \
spending one or more inputs \
from the transaction to be \
broadcast and will be \
dropped, along with any other \
transactions that depend on them:"
} else {
"The following transaction is \
spending one or more inputs \
from the transaction to be \
broadcast and will be \
dropped, along with any other \
transactions that depend on it:"
},
))),
|col, txid| {
col.push(
Row::new()
.padding([0, 30])
.spacing(5)
.align_items(Alignment::Center)
.push(text(txid.to_string()))
.push(
Button::new(
icon::clipboard_icon().style(color::GREY_3),
)
.on_press(Message::Clipboard(txid.to_string()))
.style(theme::Button::TransparentBorder),
),
)
},
),
)
})
.push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Broadcast")
.on_press(Message::Spend(SpendTxMessage::Confirm)),
),
),
)
.width(Length::Fixed(400.0))
.width(Length::Fixed(if conflicting_txids.is_empty() {
400.0
} else {
800.0
}))
.into()
}
}
Expand Down

0 comments on commit 881d9a7

Please sign in to comment.