LCOV - code coverage report
Current view: top level - src/qt - psbtoperationsdialog.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 0 178 0.0 %
Date: 2020-09-26 01:30:44 Functions: 0 15 0.0 %

          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 : }

Generated by: LCOV version 1.15