Line data Source code
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 :
5 : #include <qt/psbtoperationsdialog.h>
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 :
21 0 : PSBTOperationsDialog::PSBTOperationsDialog(
22 0 : QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent),
23 0 : m_ui(new Ui::PSBTOperationsDialog),
24 0 : m_wallet_model(wallet_model),
25 0 : m_client_model(client_model)
26 0 : {
27 0 : m_ui->setupUi(this);
28 0 : setWindowTitle("PSBT Operations");
29 :
30 0 : connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);
31 0 : connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);
32 0 : connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);
33 0 : connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);
34 :
35 0 : connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);
36 :
37 0 : m_ui->signTransactionButton->setEnabled(false);
38 0 : m_ui->broadcastTransactionButton->setEnabled(false);
39 0 : }
40 :
41 0 : PSBTOperationsDialog::~PSBTOperationsDialog()
42 0 : {
43 0 : delete m_ui;
44 0 : }
45 :
46 0 : void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
47 : {
48 0 : m_transaction_data = psbtx;
49 :
50 0 : bool complete;
51 0 : size_t n_could_sign;
52 0 : FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
53 0 : TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_could_sign);
54 0 : if (err != TransactionError::OK) {
55 0 : showStatus(tr("Failed to load transaction: %1")
56 0 : .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
57 0 : return;
58 : }
59 :
60 0 : m_ui->broadcastTransactionButton->setEnabled(complete);
61 0 : m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
62 :
63 0 : updateTransactionDisplay();
64 0 : }
65 :
66 0 : void PSBTOperationsDialog::signTransaction()
67 : {
68 0 : bool complete;
69 0 : size_t n_signed;
70 0 : TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_signed);
71 :
72 0 : if (err != TransactionError::OK) {
73 0 : showStatus(tr("Failed to sign transaction: %1")
74 0 : .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
75 0 : return;
76 : }
77 :
78 0 : updateTransactionDisplay();
79 :
80 0 : if (!complete && n_signed < 1) {
81 0 : showStatus(tr("Could not sign any more inputs."), StatusLevel::WARN);
82 0 : } else if (!complete) {
83 0 : showStatus(tr("Signed %1 inputs, but more signatures are still required.").arg(n_signed),
84 : StatusLevel::INFO);
85 0 : } else {
86 0 : showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),
87 : StatusLevel::INFO);
88 0 : m_ui->broadcastTransactionButton->setEnabled(true);
89 : }
90 0 : }
91 :
92 0 : void PSBTOperationsDialog::broadcastTransaction()
93 : {
94 0 : CMutableTransaction mtx;
95 0 : if (!FinalizeAndExtractPSBT(m_transaction_data, mtx)) {
96 : // This is never expected to fail unless we were given a malformed PSBT
97 : // (e.g. with an invalid signature.)
98 0 : showStatus(tr("Unknown error processing transaction."), StatusLevel::ERR);
99 0 : return;
100 : }
101 :
102 0 : CTransactionRef tx = MakeTransactionRef(mtx);
103 0 : std::string err_string;
104 0 : TransactionError error = BroadcastTransaction(
105 0 : *m_client_model->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* await_callback */ false);
106 :
107 0 : if (error == TransactionError::OK) {
108 0 : showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")
109 0 : .arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::INFO);
110 0 : } else {
111 0 : showStatus(tr("Transaction broadcast failed: %1")
112 0 : .arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::ERR);
113 : }
114 0 : }
115 :
116 0 : void PSBTOperationsDialog::copyToClipboard() {
117 0 : CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
118 0 : ssTx << m_transaction_data;
119 0 : GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
120 0 : showStatus(tr("PSBT copied to clipboard."), StatusLevel::INFO);
121 0 : }
122 :
123 0 : void PSBTOperationsDialog::saveTransaction() {
124 0 : CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
125 0 : ssTx << m_transaction_data;
126 :
127 0 : QString selected_filter;
128 0 : QString filename_suggestion = "";
129 : bool first = true;
130 0 : for (const CTxOut& out : m_transaction_data.tx->vout) {
131 0 : if (!first) {
132 0 : filename_suggestion.append("-");
133 : }
134 0 : CTxDestination address;
135 0 : ExtractDestination(out.scriptPubKey, address);
136 0 : QString amount = BitcoinUnits::format(m_wallet_model->getOptionsModel()->getDisplayUnit(), out.nValue);
137 0 : QString address_str = QString::fromStdString(EncodeDestination(address));
138 0 : filename_suggestion.append(address_str + "-" + amount);
139 : first = false;
140 0 : }
141 0 : filename_suggestion.append(".psbt");
142 0 : QString filename = GUIUtil::getSaveFileName(this,
143 0 : tr("Save Transaction Data"), filename_suggestion,
144 0 : tr("Partially Signed Transaction (Binary) (*.psbt)"), &selected_filter);
145 0 : if (filename.isEmpty()) {
146 0 : return;
147 : }
148 0 : std::ofstream out(filename.toLocal8Bit().data());
149 0 : out << ssTx.str();
150 0 : out.close();
151 0 : showStatus(tr("PSBT saved to disk."), StatusLevel::INFO);
152 0 : }
153 :
154 0 : void PSBTOperationsDialog::updateTransactionDisplay() {
155 0 : m_ui->transactionDescription->setText(QString::fromStdString(renderTransaction(m_transaction_data)));
156 0 : showTransactionStatus(m_transaction_data);
157 0 : }
158 :
159 0 : std::string PSBTOperationsDialog::renderTransaction(const PartiallySignedTransaction &psbtx)
160 : {
161 0 : QString tx_description = "";
162 0 : CAmount totalAmount = 0;
163 0 : for (const CTxOut& out : psbtx.tx->vout) {
164 0 : CTxDestination address;
165 0 : ExtractDestination(out.scriptPubKey, address);
166 0 : totalAmount += out.nValue;
167 0 : tx_description.append(tr(" * Sends %1 to %2")
168 0 : .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, out.nValue))
169 0 : .arg(QString::fromStdString(EncodeDestination(address))));
170 0 : tx_description.append("<br>");
171 0 : }
172 :
173 0 : PSBTAnalysis analysis = AnalyzePSBT(psbtx);
174 0 : tx_description.append(" * ");
175 0 : if (!*analysis.fee) {
176 : // This happens if the transaction is missing input UTXO information.
177 0 : tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));
178 0 : } else {
179 0 : tx_description.append(tr("Pays transaction fee: "));
180 0 : tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, *analysis.fee));
181 :
182 : // add total amount in all subdivision units
183 0 : tx_description.append("<hr />");
184 0 : QStringList alternativeUnits;
185 0 : for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
186 : {
187 0 : if(u != m_client_model->getOptionsModel()->getDisplayUnit()) {
188 0 : alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
189 0 : }
190 0 : }
191 0 : tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
192 0 : .arg(BitcoinUnits::formatHtmlWithUnit(m_client_model->getOptionsModel()->getDisplayUnit(), totalAmount)));
193 0 : tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
194 0 : .arg(alternativeUnits.join(" " + tr("or") + " ")));
195 0 : }
196 :
197 0 : size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);
198 0 : if (num_unsigned > 0) {
199 0 : tx_description.append("<br><br>");
200 0 : tx_description.append(tr("Transaction has %1 unsigned inputs.").arg(QString::number(num_unsigned)));
201 0 : }
202 :
203 0 : return tx_description.toStdString();
204 0 : }
205 :
206 0 : void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {
207 0 : m_ui->statusBar->setText(msg);
208 0 : switch (level) {
209 : case StatusLevel::INFO: {
210 0 : m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");
211 0 : break;
212 : }
213 : case StatusLevel::WARN: {
214 0 : m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");
215 0 : break;
216 : }
217 : case StatusLevel::ERR: {
218 0 : m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");
219 0 : break;
220 : }
221 : }
222 0 : m_ui->statusBar->show();
223 0 : }
224 :
225 0 : size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) {
226 0 : size_t n_signed;
227 0 : bool complete;
228 0 : TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, m_transaction_data, complete, &n_signed);
229 :
230 0 : if (err != TransactionError::OK) {
231 0 : return 0;
232 : }
233 0 : return n_signed;
234 0 : }
235 :
236 0 : void PSBTOperationsDialog::showTransactionStatus(const PartiallySignedTransaction &psbtx) {
237 0 : PSBTAnalysis analysis = AnalyzePSBT(psbtx);
238 0 : size_t n_could_sign = couldSignInputs(psbtx);
239 :
240 0 : switch (analysis.next) {
241 : case PSBTRole::UPDATER: {
242 0 : showStatus(tr("Transaction is missing some information about inputs."), StatusLevel::WARN);
243 0 : break;
244 : }
245 : case PSBTRole::SIGNER: {
246 0 : QString need_sig_text = tr("Transaction still needs signature(s).");
247 : StatusLevel level = StatusLevel::INFO;
248 0 : if (m_wallet_model->wallet().privateKeysDisabled()) {
249 0 : need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
250 : level = StatusLevel::WARN;
251 0 : } else if (n_could_sign < 1) {
252 0 : need_sig_text += " " + tr("(But this wallet does not have the right keys.)"); // XXX wording
253 : level = StatusLevel::WARN;
254 0 : }
255 0 : showStatus(need_sig_text, level);
256 : break;
257 0 : }
258 : case PSBTRole::FINALIZER:
259 : case PSBTRole::EXTRACTOR: {
260 0 : showStatus(tr("Transaction is fully signed and ready for broadcast."), StatusLevel::INFO);
261 0 : break;
262 : }
263 : default: {
264 0 : showStatus(tr("Transaction status is unknown."), StatusLevel::ERR);
265 0 : break;
266 : }
267 : }
268 0 : }
|