gui(psbt): check for conflicting txs before broadcast
This commit is contained in:
parent
beabf08cbf
commit
881d9a74a2
@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use liana::{
|
||||
@ -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>),
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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()));
|
||||
@ -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
|
||||
@ -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 {
|
||||
@ -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()
|
||||
|
||||
@ -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))
|
||||
@ -151,7 +159,59 @@ 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")
|
||||
@ -159,7 +219,11 @@ pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a,
|
||||
),
|
||||
),
|
||||
)
|
||||
.width(Length::Fixed(400.0))
|
||||
.width(Length::Fixed(if conflicting_txids.is_empty() {
|
||||
400.0
|
||||
} else {
|
||||
800.0
|
||||
}))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user