Bitcoin Core  0.20.99
P2P Digital Currency
psbtoperationsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2020 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>
13 #include <qt/forms/ui_psbtoperationsdialog.h>
14 #include <qt/guiutil.h>
15 #include <qt/optionsmodel.h>
16 #include <util/strencodings.h>
17 
18 #include <iostream>
19 
20 
22  QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent),
23  m_ui(new Ui::PSBTOperationsDialog),
24  m_wallet_model(wallet_model),
25  m_client_model(client_model)
26 {
27  m_ui->setupUi(this);
28  setWindowTitle("PSBT Operations");
29 
30  connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);
31  connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);
32  connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);
33  connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);
34 
35  connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);
36 
37  m_ui->signTransactionButton->setEnabled(false);
38  m_ui->broadcastTransactionButton->setEnabled(false);
39 }
40 
42 {
43  delete m_ui;
44 }
45 
47 {
48  m_transaction_data = psbtx;
49 
50  bool complete;
51  size_t n_could_sign;
52  FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
53  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_could_sign);
54  if (err != TransactionError::OK) {
55  showStatus(tr("Failed to load transaction: %1")
56  .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
57  return;
58  }
59 
60  m_ui->broadcastTransactionButton->setEnabled(complete);
61  m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
62 
64 }
65 
67 {
68  bool complete;
69  size_t n_signed;
70  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_signed);
71 
72  if (err != TransactionError::OK) {
73  showStatus(tr("Failed to sign transaction: %1")
74  .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
75  return;
76  }
77 
79 
80  if (!complete && n_signed < 1) {
81  showStatus(tr("Could not sign any more inputs."), StatusLevel::WARN);
82  } else if (!complete) {
83  showStatus(tr("Signed %1 inputs, but more signatures are still required.").arg(n_signed),
85  } else {
86  showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),
88  m_ui->broadcastTransactionButton->setEnabled(true);
89  }
90 }
91 
93 {
96  // This is never expected to fail unless we were given a malformed PSBT
97  // (e.g. with an invalid signature.)
98  showStatus(tr("Unknown error processing transaction."), StatusLevel::ERR);
99  return;
100  }
101 
103  std::string err_string;
105  *m_client_model->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* await_callback */ false);
106 
107  if (error == TransactionError::OK) {
108  showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")
109  .arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::INFO);
110  } else {
111  showStatus(tr("Transaction broadcast failed: %1")
112  .arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::ERR);
113  }
114 }
115 
118  ssTx << m_transaction_data;
119  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
120  showStatus(tr("PSBT copied to clipboard."), StatusLevel::INFO);
121 }
122 
125  ssTx << m_transaction_data;
126 
127  QString selected_filter;
128  QString filename_suggestion = "";
129  bool first = true;
130  for (const CTxOut& out : m_transaction_data.tx->vout) {
131  if (!first) {
132  filename_suggestion.append("-");
133  }
134  CTxDestination address;
135  ExtractDestination(out.scriptPubKey, address);
137  QString address_str = QString::fromStdString(EncodeDestination(address));
138  filename_suggestion.append(address_str + "-" + amount);
139  first = false;
140  }
141  filename_suggestion.append(".psbt");
142  QString filename = GUIUtil::getSaveFileName(this,
143  tr("Save Transaction Data"), filename_suggestion,
144  tr("Partially Signed Transaction (Binary) (*.psbt)"), &selected_filter);
145  if (filename.isEmpty()) {
146  return;
147  }
148  std::ofstream out(filename.toLocal8Bit().data());
149  out << ssTx.str();
150  out.close();
151  showStatus(tr("PSBT saved to disk."), StatusLevel::INFO);
152 }
153 
155  m_ui->transactionDescription->setText(QString::fromStdString(renderTransaction(m_transaction_data)));
157 }
158 
160 {
161  QString tx_description = "";
162  CAmount totalAmount = 0;
163  for (const CTxOut& out : psbtx.tx->vout) {
164  CTxDestination address;
165  ExtractDestination(out.scriptPubKey, address);
166  totalAmount += out.nValue;
167  tx_description.append(tr(" * Sends %1 to %2")
169  .arg(QString::fromStdString(EncodeDestination(address))));
170  tx_description.append("<br>");
171  }
172 
173  PSBTAnalysis analysis = AnalyzePSBT(psbtx);
174  tx_description.append(" * ");
175  if (!*analysis.fee) {
176  // This happens if the transaction is missing input UTXO information.
177  tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));
178  } else {
179  tx_description.append(tr("Pays transaction fee: "));
180  tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, *analysis.fee));
181 
182  // add total amount in all subdivision units
183  tx_description.append("<hr />");
184  QStringList alternativeUnits;
186  {
188  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
189  }
190  }
191  tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
193  tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
194  .arg(alternativeUnits.join(" " + tr("or") + " ")));
195  }
196 
197  size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);
198  if (num_unsigned > 0) {
199  tx_description.append("<br><br>");
200  tx_description.append(tr("Transaction has %1 unsigned inputs.").arg(QString::number(num_unsigned)));
201  }
202 
203  return tx_description.toStdString();
204 }
205 
206 void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {
207  m_ui->statusBar->setText(msg);
208  switch (level) {
209  case StatusLevel::INFO: {
210  m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");
211  break;
212  }
213  case StatusLevel::WARN: {
214  m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");
215  break;
216  }
217  case StatusLevel::ERR: {
218  m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");
219  break;
220  }
221  }
222  m_ui->statusBar->show();
223 }
224 
226  size_t n_signed;
227  bool complete;
228  TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, m_transaction_data, complete, &n_signed);
229 
230  if (err != TransactionError::OK) {
231  return 0;
232  }
233  return n_signed;
234 }
235 
237  PSBTAnalysis analysis = AnalyzePSBT(psbtx);
238  size_t n_could_sign = couldSignInputs(psbtx);
239 
240  switch (analysis.next) {
241  case PSBTRole::UPDATER: {
242  showStatus(tr("Transaction is missing some information about inputs."), StatusLevel::WARN);
243  break;
244  }
245  case PSBTRole::SIGNER: {
246  QString need_sig_text = tr("Transaction still needs signature(s).");
249  need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
250  level = StatusLevel::WARN;
251  } else if (n_could_sign < 1) {
252  need_sig_text += " " + tr("(But this wallet does not have the right keys.)"); // XXX wording
253  level = StatusLevel::WARN;
254  }
255  showStatus(need_sig_text, level);
256  break;
257  }
258  case PSBTRole::FINALIZER:
259  case PSBTRole::EXTRACTOR: {
260  showStatus(tr("Transaction is fully signed and ready for broadcast."), StatusLevel::INFO);
261  break;
262  }
263  default: {
264  showStatus(tr("Transaction status is unknown."), StatusLevel::ERR);
265  break;
266  }
267  }
268 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:395
virtual bool privateKeysDisabled()=0
CAmount nValue
Definition: transaction.h:131
Unit
Bitcoin units.
Definition: bitcoinunits.h:41
interfaces::Wallet & wallet() const
Definition: walletmodel.h:146
std::string renderTransaction(const PartiallySignedTransaction &psbtx)
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:180
CScript scriptPubKey
Definition: transaction.h:132
Dialog showing transaction details.
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
Definition: psbt.cpp:15
Optional< CMutableTransaction > tx
Definition: psbt.h:392
std::string str() const
Definition: streams.h:280
static QString format(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
bool FinalizePSBT(PartiallySignedTransaction &psbtx)
Finalizes a PSBT if possible, combining partial signatures.
Definition: psbt.cpp:295
static CTransactionRef MakeTransactionRef()
Definition: transaction.h:396
PSBTRole next
Which of the BIP 174 roles needs to handle the transaction next.
Definition: psbt.h:32
Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
Definition: psbt.h:27
OptionsModel * getOptionsModel()
bilingual_str TransactionErrorString(const TransactionError err)
Definition: error.cpp:11
A version of CTransaction with the PSBT format.
Definition: psbt.h:390
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:202
fs::ofstream ofstream
Definition: fs.h:91
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:309
int getDisplayUnit() const
Definition: optionsmodel.h:84
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
size_t couldSignInputs(const PartiallySignedTransaction &psbtx)
Optional< CAmount > fee
Amount of fee being paid by the transaction.
Definition: psbt.h:30
std::string EncodeBase64(Span< const unsigned char > input)
void setClipboard(const QString &str)
Definition: guiutil.cpp:735
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE
Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
Definition: transaction.h:20
PartiallySignedTransaction m_transaction_data
interfaces::Node & node() const
Definition: clientmodel.h:54
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
void showTransactionStatus(const PartiallySignedTransaction &psbtx)
An output of a transaction.
Definition: transaction.h:128
Model for Bitcoin network client.
Definition: clientmodel.h:46
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete, size_t *n_signed)=0
Fill PSBT.
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction &psbt)
Counts the unsigned inputs of a PSBT.
Definition: psbt.cpp:197
PSBTOperationsDialog(QWidget *parent, WalletModel *walletModel, ClientModel *clientModel)
TransactionError BroadcastTransaction(NodeContext &node, const CTransactionRef tx, std::string &err_string, const CAmount &max_tx_fee, bool relay, bool wait_callback)
Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
Definition: transaction.cpp:29
void showStatus(const QString &msg, StatusLevel level)
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:262
virtual NodeContext * context()
Get and set internal node context.
Definition: node.h:226
TransactionError
Definition: error.h:22
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:210
A mutable version of CTransaction.
Definition: transaction.h:353
boost::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:214
bool error(const char *fmt, const Args &... args)
Definition: system.h:52
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:51
OptionsModel * getOptionsModel()