Bitcoin Core  26.1.0
P2P Digital Currency
psbtoperationsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2022 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 <core_io.h>
8 #include <interfaces/node.h>
9 #include <key_io.h>
10 #include <node/psbt.h>
11 #include <policy/policy.h>
12 #include <qt/bitcoinunits.h>
14 #include <qt/guiutil.h>
15 #include <qt/optionsmodel.h>
16 #include <util/fs.h>
17 #include <util/strencodings.h>
18 
19 #include <fstream>
20 #include <iostream>
21 #include <string>
22 
23 using node::AnalyzePSBT;
25 using node::PSBTAnalysis;
26 
28  QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent, GUIUtil::dialog_flags),
29  m_ui(new Ui::PSBTOperationsDialog),
30  m_wallet_model(wallet_model),
31  m_client_model(client_model)
32 {
33  m_ui->setupUi(this);
34 
35  connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);
36  connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);
37  connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);
38  connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);
39 
40  connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);
41 
42  m_ui->signTransactionButton->setEnabled(false);
43  m_ui->broadcastTransactionButton->setEnabled(false);
44 }
45 
47 {
48  delete m_ui;
49 }
50 
52 {
53  m_transaction_data = psbtx;
54 
55  bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
56  if (m_wallet_model) {
57  size_t n_could_sign;
58  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete);
59  if (err != TransactionError::OK) {
60  showStatus(tr("Failed to load transaction: %1")
61  .arg(QString::fromStdString(TransactionErrorString(err).translated)),
63  return;
64  }
65  m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
66  } else {
67  m_ui->signTransactionButton->setEnabled(false);
68  }
69 
70  m_ui->broadcastTransactionButton->setEnabled(complete);
71 
73 }
74 
76 {
77  bool complete;
78  size_t n_signed;
79 
81 
82  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete);
83 
84  if (err != TransactionError::OK) {
85  showStatus(tr("Failed to sign transaction: %1")
86  .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
87  return;
88  }
89 
91 
92  if (!complete && !ctx.isValid()) {
93  showStatus(tr("Cannot sign inputs while wallet is locked."), StatusLevel::WARN);
94  } else if (!complete && n_signed < 1) {
95  showStatus(tr("Could not sign any more inputs."), StatusLevel::WARN);
96  } else if (!complete) {
97  showStatus(tr("Signed %1 inputs, but more signatures are still required.").arg(n_signed),
99  } else {
100  showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),
102  m_ui->broadcastTransactionButton->setEnabled(true);
103  }
104 }
105 
107 {
110  // This is never expected to fail unless we were given a malformed PSBT
111  // (e.g. with an invalid signature.)
112  showStatus(tr("Unknown error processing transaction."), StatusLevel::ERR);
113  return;
114  }
115 
117  std::string err_string;
120 
121  if (error == TransactionError::OK) {
122  showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")
123  .arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::INFO);
124  } else {
125  showStatus(tr("Transaction broadcast failed: %1")
126  .arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::ERR);
127  }
128 }
129 
132  ssTx << m_transaction_data;
133  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
134  showStatus(tr("PSBT copied to clipboard."), StatusLevel::INFO);
135 }
136 
139  ssTx << m_transaction_data;
140 
141  QString selected_filter;
142  QString filename_suggestion = "";
143  bool first = true;
144  for (const CTxOut& out : m_transaction_data.tx->vout) {
145  if (!first) {
146  filename_suggestion.append("-");
147  }
148  CTxDestination address;
149  ExtractDestination(out.scriptPubKey, address);
150  QString amount = BitcoinUnits::format(m_client_model->getOptionsModel()->getDisplayUnit(), out.nValue);
151  QString address_str = QString::fromStdString(EncodeDestination(address));
152  filename_suggestion.append(address_str + "-" + amount);
153  first = false;
154  }
155  filename_suggestion.append(".psbt");
156  QString filename = GUIUtil::getSaveFileName(this,
157  tr("Save Transaction Data"), filename_suggestion,
158  //: Expanded name of the binary PSBT file format. See: BIP 174.
159  tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selected_filter);
160  if (filename.isEmpty()) {
161  return;
162  }
163  std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
164  out << ssTx.str();
165  out.close();
166  showStatus(tr("PSBT saved to disk."), StatusLevel::INFO);
167 }
168 
170  m_ui->transactionDescription->setText(QString::fromStdString(renderTransaction(m_transaction_data)));
172 }
173 
175 {
176  QString tx_description = "";
177  CAmount totalAmount = 0;
178  for (const CTxOut& out : psbtx.tx->vout) {
179  CTxDestination address;
180  ExtractDestination(out.scriptPubKey, address);
181  totalAmount += out.nValue;
182  tx_description.append(tr(" * Sends %1 to %2")
184  .arg(QString::fromStdString(EncodeDestination(address))));
185  // Check if the address is one of ours
186  if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(out)) tx_description.append(" (" + tr("own address") + ")");
187  tx_description.append("<br>");
188  }
189 
190  PSBTAnalysis analysis = AnalyzePSBT(psbtx);
191  tx_description.append(" * ");
192  if (!*analysis.fee) {
193  // This happens if the transaction is missing input UTXO information.
194  tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));
195  } else {
196  tx_description.append(tr("Pays transaction fee: "));
197  tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *analysis.fee));
198 
199  // add total amount in all subdivision units
200  tx_description.append("<hr />");
201  QStringList alternativeUnits;
203  {
205  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
206  }
207  }
208  tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
210  tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
211  .arg(alternativeUnits.join(" " + tr("or") + " ")));
212  }
213 
214  size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);
215  if (num_unsigned > 0) {
216  tx_description.append("<br><br>");
217  tx_description.append(tr("Transaction has %1 unsigned inputs.").arg(QString::number(num_unsigned)));
218  }
219 
220  return tx_description.toStdString();
221 }
222 
223 void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {
224  m_ui->statusBar->setText(msg);
225  switch (level) {
226  case StatusLevel::INFO: {
227  m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");
228  break;
229  }
230  case StatusLevel::WARN: {
231  m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");
232  break;
233  }
234  case StatusLevel::ERR: {
235  m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");
236  break;
237  }
238  }
239  m_ui->statusBar->show();
240 }
241 
243  if (!m_wallet_model) {
244  return 0;
245  }
246 
247  size_t n_signed;
248  bool complete;
249  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete);
250 
251  if (err != TransactionError::OK) {
252  return 0;
253  }
254  return n_signed;
255 }
256 
258  PSBTAnalysis analysis = AnalyzePSBT(psbtx);
259  size_t n_could_sign = couldSignInputs(psbtx);
260 
261  switch (analysis.next) {
262  case PSBTRole::UPDATER: {
263  showStatus(tr("Transaction is missing some information about inputs."), StatusLevel::WARN);
264  break;
265  }
266  case PSBTRole::SIGNER: {
267  QString need_sig_text = tr("Transaction still needs signature(s).");
269  if (!m_wallet_model) {
270  need_sig_text += " " + tr("(But no wallet is loaded.)");
271  level = StatusLevel::WARN;
272  } else if (m_wallet_model->wallet().privateKeysDisabled()) {
273  need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
274  level = StatusLevel::WARN;
275  } else if (n_could_sign < 1) {
276  need_sig_text += " " + tr("(But this wallet does not have the right keys.)"); // XXX wording
277  level = StatusLevel::WARN;
278  }
279  showStatus(need_sig_text, level);
280  break;
281  }
282  case PSBTRole::FINALIZER:
283  case PSBTRole::EXTRACTOR: {
284  showStatus(tr("Transaction is fully signed and ready for broadcast."), StatusLevel::INFO);
285  break;
286  }
287  default: {
288  showStatus(tr("Transaction status is unknown."), StatusLevel::ERR);
289  break;
290  }
291  }
292 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:421
virtual bool privateKeysDisabled()=0
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
interfaces::Wallet & wallet() const
Definition: walletmodel.h:142
std::string renderTransaction(const PartiallySignedTransaction &psbtx)
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:60
void setupUi(QDialog *PSBTOperationsDialog)
Dialog showing transaction details.
virtual TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string &err_string)=0
Broadcast transaction.
UnlockContext requestUnlock()
bool FinalizePSBT(PartiallySignedTransaction &psbtx)
Finalizes a PSBT if possible, combining partial signatures.
Definition: psbt.cpp:480
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:94
std::string EncodeBase64(Span< const unsigned char > input)
OptionsModel * getOptionsModel()
bilingual_str TransactionErrorString(const TransactionError err)
Definition: error.cpp:13
A version of CTransaction with the PSBT format.
Definition: psbt.h:946
Ui::PSBTOperationsDialog * m_ui
void openWithPSBT(PartiallySignedTransaction psbtx)
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:495
std::optional< CAmount > fee
Amount of fee being paid by the transaction.
Definition: psbt.h:33
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
constexpr auto dialog_flags
Definition: guiutil.h:60
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
virtual wallet::isminetype txoutIsMine(const CTxOut &txout)=0
Return whether transaction output belongs to wallet.
size_t couldSignInputs(const PartiallySignedTransaction &psbtx)
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a scriptPubKey for the destination.
Definition: addresstype.cpp:49
static QString format(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
void setClipboard(const QString &str)
Definition: guiutil.cpp:656
PartiallySignedTransaction m_transaction_data
interfaces::Node & node() const
Definition: clientmodel.h:61
void showTransactionStatus(const PartiallySignedTransaction &psbtx)
An output of a transaction.
Definition: transaction.h:157
Model for Bitcoin network client.
Definition: clientmodel.h:53
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:422
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:129
std::string str() const
Definition: streams.h:214
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction &psbt)
Counts the unsigned inputs of a PSBT.
Definition: psbt.cpp:327
PSBTOperationsDialog(QWidget *parent, WalletModel *walletModel, ClientModel *clientModel)
void showStatus(const QString &msg, StatusLevel level)
bool error(const char *fmt, const Args &... args)
Definition: logging.h:262
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:51
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:12
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:308
Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
Definition: psbt.h:30
TransactionError
Definition: error.h:22
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:287
A mutable version of CTransaction.
Definition: transaction.h:379
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
std::optional< CMutableTransaction > tx
Definition: psbt.h:948
static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE
Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
Definition: transaction.h:27
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
Definition: psbt.cpp:16
PSBTRole next
Which of the BIP 174 roles needs to handle the transaction next.
Definition: psbt.h:35
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:65