Bitcoin Core 32.99.0
P2P Digital Currency
psbtoperationsdialog.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
7#include <common/messages.h>
8#include <core_io.h>
9#include <interfaces/node.h>
10#include <key_io.h>
11#include <node/psbt.h>
12#include <node/types.h>
13#include <policy/feerate.h>
14#include <policy/policy.h>
15#include <qt/bitcoinunits.h>
16#include <qt/forms/ui_psbtoperationsdialog.h>
17#include <qt/guiutil.h>
18#include <qt/optionsmodel.h>
19#include <util/fs.h>
20#include <util/strencodings.h>
21
22#include <fstream>
23#include <iostream>
24#include <string>
25
31
33 QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent, GUIUtil::dialog_flags),
34 m_ui(new Ui::PSBTOperationsDialog),
35 m_wallet_model(wallet_model),
36 m_client_model(client_model)
37{
38 m_ui->setupUi(this);
39
40 connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);
41 connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);
42 connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);
43 connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);
44
45 connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);
46
47 m_ui->signTransactionButton->setEnabled(false);
48 m_ui->broadcastTransactionButton->setEnabled(false);
49}
50
52{
53 delete m_ui;
54}
55
57{
58 m_transaction_data = psbtx;
59
60 bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
61 if (m_wallet_model) {
62 size_t n_could_sign;
63 const auto err{m_wallet_model->wallet().fillPSBT({.sign = false, .bip32_derivs= true}, &n_could_sign, *m_transaction_data, complete)};
64 if (err) {
65 showStatus(tr("Failed to load transaction: %1")
66 .arg(QString::fromStdString(PSBTErrorString(*err).translated)),
68 return;
69 }
70 m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
71 } else {
72 m_ui->signTransactionButton->setEnabled(false);
73 }
74
75 m_ui->broadcastTransactionButton->setEnabled(complete);
76
78}
79
81{
82 bool complete;
83 size_t n_signed;
84
86
87 const auto err{m_wallet_model->wallet().fillPSBT({.sign = true, .bip32_derivs = true}, &n_signed, *m_transaction_data, complete)};
88
89 if (err) {
90 showStatus(tr("Failed to sign transaction: %1")
91 .arg(QString::fromStdString(PSBTErrorString(*err).translated)), StatusLevel::Error);
92 return;
93 }
94
96
97 if (!complete && !ctx.isValid()) {
98 showStatus(tr("Cannot sign inputs while wallet is locked."), StatusLevel::Warn);
99 } else if (!complete && n_signed < 1) {
100 showStatus(tr("Could not sign any more inputs."), StatusLevel::Warn);
101 } else if (!complete) {
102 showStatus(tr("Signed %n input(s), but more signatures are still required.", "", n_signed),
104 } else {
105 showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),
107 m_ui->broadcastTransactionButton->setEnabled(true);
108 }
109}
110
112{
115 // This is never expected to fail unless we were given a malformed PSBT
116 // (e.g. with an invalid signature.)
117 showStatus(tr("Unknown error processing transaction."), StatusLevel::Error);
118 return;
119 }
120
122 std::string err_string;
123 const auto max_raw_tx_fee_rate{DEFAULT_MAX_RAW_TX_FEE_RATE};
124 // TODO: do not use default values for maxtxfee and maxfeerate.
125 TransactionError error =
126 m_client_model->node().broadcastTransaction(tx, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), max_raw_tx_fee_rate, err_string);
127
128 if (error == TransactionError::OK) {
129 showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")
130 .arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::Info);
131 } else {
132 showStatus(tr("Transaction broadcast failed: %1")
133 .arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::Error);
134 }
135}
136
138 DataStream ssTx{};
139 ssTx << *m_transaction_data;
140 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
141 showStatus(tr("PSBT copied to clipboard."), StatusLevel::Info);
142}
143
145 DataStream ssTx{};
146 ssTx << *m_transaction_data;
147
148 QString selected_filter;
149 QString filename_suggestion = "";
150 bool first = true;
151 for (const PSBTOutput& out : m_transaction_data->outputs) {
152 if (!first) {
153 filename_suggestion.append("-");
154 }
155 CTxDestination address;
156 ExtractDestination(out.script, address);
158 QString address_str = QString::fromStdString(EncodeDestination(address));
159 filename_suggestion.append(address_str + "-" + amount);
160 first = false;
161 }
162 filename_suggestion.append(".psbt");
163 QString filename = GUIUtil::getSaveFileName(this,
164 tr("Save Transaction Data"), filename_suggestion,
165 //: Expanded name of the binary PSBT file format. See: BIP 174.
166 tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selected_filter);
167 if (filename.isEmpty()) {
168 return;
169 }
170 std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
171 out << ssTx.str();
172 out.close();
173 showStatus(tr("PSBT saved to disk."), StatusLevel::Info);
174}
175
177 m_ui->transactionDescription->setText(renderTransaction(*m_transaction_data));
179}
180
182{
183 QString tx_description;
184 QLatin1String bullet_point(" * ");
185 CAmount totalAmount = 0;
186 for (const PSBTOutput& out : psbtx.outputs) {
187 CTxDestination address;
188 ExtractDestination(out.script, address);
189 totalAmount += out.amount;
190 tx_description.append(bullet_point).append(tr("Sends %1 to %2")
192 .arg(QString::fromStdString(EncodeDestination(address))));
193 // Check if the address is one of ours
194 if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(CTxOut(out.amount, out.script))) tx_description.append(" (" + tr("own address") + ")");
195 tx_description.append("<br>");
196 }
197
198 PSBTAnalysis analysis = AnalyzePSBT(psbtx);
199 tx_description.append(bullet_point);
200 if (!*analysis.fee) {
201 // This happens if the transaction is missing input UTXO information.
202 tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));
203 } else {
204 tx_description.append(tr("Pays transaction fee: "));
205 tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *analysis.fee));
206
207 // add total amount in all subdivision units
208 tx_description.append("<hr />");
209 QStringList alternativeUnits;
211 {
213 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
214 }
215 }
216 tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
218 tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
219 .arg(alternativeUnits.join(" " + tr("or") + " ")));
220 }
221
222 size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);
223 if (num_unsigned > 0) {
224 tx_description.append("<br><br>");
225 tx_description.append(tr("Transaction has %n unsigned input(s).", "", num_unsigned));
226 }
227
228 return tx_description;
229}
230
232 m_ui->statusBar->setText(msg);
233 switch (level) {
234 case StatusLevel::Info: {
235 m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");
236 break;
237 }
238 case StatusLevel::Warn: {
239 m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");
240 break;
241 }
242 case StatusLevel::Error: {
243 m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");
244 break;
245 }
246 }
247 m_ui->statusBar->show();
248}
249
251 if (!m_wallet_model) {
252 return 0;
253 }
254
255 size_t n_signed;
256 bool complete;
257 const auto err{m_wallet_model->wallet().fillPSBT({.sign = false, .bip32_derivs = false}, &n_signed, *m_transaction_data, complete)};
258
259 if (err) {
260 return 0;
261 }
262 return n_signed;
263}
264
266 PSBTAnalysis analysis = AnalyzePSBT(psbtx);
267 size_t n_could_sign = couldSignInputs(psbtx);
268
269 switch (analysis.next) {
270 case PSBTRole::UPDATER: {
271 showStatus(tr("Transaction is missing some information about inputs."), StatusLevel::Warn);
272 break;
273 }
274 case PSBTRole::SIGNER: {
275 QString need_sig_text = tr("Transaction still needs signature(s).");
277 if (!m_wallet_model) {
278 need_sig_text += " " + tr("(But no wallet is loaded.)");
279 level = StatusLevel::Warn;
280 } else if (m_wallet_model->wallet().privateKeysDisabled()) {
281 need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
282 level = StatusLevel::Warn;
283 } else if (n_could_sign < 1) {
284 need_sig_text += " " + tr("(But this wallet does not have the right keys.)"); // XXX wording
285 level = StatusLevel::Warn;
286 }
287 showStatus(need_sig_text, level);
288 break;
289 }
291 case PSBTRole::EXTRACTOR: {
292 showStatus(tr("Transaction is fully signed and ready for broadcast."), StatusLevel::Info);
293 break;
294 }
295 default: {
296 showStatus(tr("Transaction status is unknown."), StatusLevel::Error);
297 break;
298 }
299 }
300}
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a scriptPubKey for the destination.
Definition: addresstype.cpp:49
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:143
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QString format(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:71
An output of a transaction.
Definition: transaction.h:141
Model for Bitcoin network client.
Definition: clientmodel.h:57
OptionsModel * getOptionsModel()
interfaces::Node & node() const
Definition: clientmodel.h:66
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:165
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:104
Dialog showing transaction details.
PSBTOperationsDialog(QWidget *parent, WalletModel *walletModel, ClientModel *clientModel)
Ui::PSBTOperationsDialog * m_ui
void openWithPSBT(PartiallySignedTransaction psbtx)
size_t couldSignInputs(const PartiallySignedTransaction &psbtx)
void showTransactionStatus(const PartiallySignedTransaction &psbtx)
void showStatus(const QString &msg, StatusLevel level)
std::optional< PartiallySignedTransaction > m_transaction_data
QString renderTransaction(const PartiallySignedTransaction &psbtx)
A structure for PSBTs which contains per output information.
Definition: psbt.h:939
A version of CTransaction with the PSBT format.
Definition: psbt.h:1239
std::vector< PSBTOutput > outputs
Definition: psbt.h:1249
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:49
interfaces::Wallet & wallet() const
Definition: walletmodel.h:136
UnlockContext requestUnlock()
virtual node::TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, CFeeRate max_tx_fee_rate, std::string &err_string)=0
Broadcast transaction.
virtual bool txoutIsMine(const CTxOut &txout)=0
Return whether transaction output belongs to wallet.
virtual std::optional< common::PSBTError > fillPSBT(const common::PSBTFillOptions &options, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
virtual bool privateKeysDisabled()=0
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:292
is a home for simple string functions returning descriptive messages that are used in RPC and GUI int...
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:58
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition: guiutil.cpp:315
constexpr auto dialog_flags
Definition: guiutil.h:60
void setClipboard(const QString &str)
Definition: guiutil.cpp:663
bilingual_str PSBTErrorString(PSBTError err)
Definition: messages.cpp:108
bilingual_str TransactionErrorString(const TransactionError err)
Definition: messages.cpp:129
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
Definition: psbt.cpp:16
TransactionError
Definition: types.h:19
constexpr CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE
Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
Definition: transaction.h:28
is a home for public enum and struct type definitions that are used internally by node code,...
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:418
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:417
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction &psbt)
Counts the unsigned inputs of a PSBT.
Definition: psbt.cpp:586
bool FinalizeAndExtractPSBT(PartiallySignedTransaction &psbtx, CMutableTransaction &result)
Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized.
Definition: psbt.cpp:818
bool FinalizePSBT(PartiallySignedTransaction &psbtx)
Finalizes a PSBT if possible, combining partial signatures.
Definition: psbt.cpp:797
A mutable version of CTransaction.
Definition: transaction.h:372
std::string translated
Definition: translation.h:26
Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
Definition: psbt.h:30
std::optional< CAmount > fee
Amount of fee being paid by the transaction.
Definition: psbt.h:33
PSBTRole next
Which of the BIP 174 roles needs to handle the transaction next.
Definition: psbt.h:35
std::string EncodeBase64(std::span< const unsigned char > input)