transactiondesc.cpp raw

   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  
   5  #include <qt/transactiondesc.h>
   6  
   7  #include <qt/bitcoinunits.h>
   8  #include <qt/guiutil.h>
   9  #include <qt/paymentserver.h>
  10  #include <qt/transactionrecord.h>
  11  
  12  #include <common/system.h>
  13  #include <consensus/consensus.h>
  14  #include <interfaces/node.h>
  15  #include <interfaces/wallet.h>
  16  #include <key_io.h>
  17  #include <policy/policy.h>
  18  #include <util/log.h>
  19  #include <validation.h>
  20  
  21  #include <cstdint>
  22  #include <string>
  23  
  24  #include <QLatin1String>
  25  
  26  QString TransactionDesc::FormatTxStatus(const interfaces::WalletTxStatus& status, bool inMempool)
  27  {
  28      int depth = status.depth_in_main_chain;
  29      if (depth < 0) {
  30          /*: Text explaining the current status of a transaction, shown in the
  31              status field of the details window for this transaction. This status
  32              represents an unconfirmed transaction that conflicts with a confirmed
  33              transaction. */
  34          return tr("conflicted with a transaction with %1 confirmations").arg(-depth);
  35      } else if (depth == 0) {
  36          QString s;
  37          if (inMempool) {
  38              /*: Text explaining the current status of a transaction, shown in the
  39                  status field of the details window for this transaction. This status
  40                  represents an unconfirmed transaction that is in the memory pool. */
  41              s = tr("0/unconfirmed, in memory pool");
  42          } else {
  43              /*: Text explaining the current status of a transaction, shown in the
  44                  status field of the details window for this transaction. This status
  45                  represents an unconfirmed transaction that is not in the memory pool. */
  46              s = tr("0/unconfirmed, not in memory pool");
  47          }
  48          if (status.is_abandoned) {
  49              /*: Text explaining the current status of a transaction, shown in the
  50                  status field of the details window for this transaction. This
  51                  status represents an abandoned transaction. */
  52              s += QLatin1String(", ") + tr("abandoned");
  53          }
  54          return s;
  55      } else if (depth < 6) {
  56          /*: Text explaining the current status of a transaction, shown in the
  57              status field of the details window for this transaction. This
  58              status represents a transaction confirmed in at least one block,
  59              but less than 6 blocks. */
  60          return tr("%1/unconfirmed").arg(depth);
  61      } else {
  62          /*: Text explaining the current status of a transaction, shown in the
  63              status field of the details window for this transaction. This status
  64              represents a transaction confirmed in 6 or more blocks. */
  65          return tr("%1 confirmations").arg(depth);
  66      }
  67  }
  68  
  69  // Takes an encoded PaymentRequest as a string and tries to find the Common Name of the X.509 certificate
  70  // used to sign the PaymentRequest.
  71  bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant)
  72  {
  73      // Search for the supported pki type strings
  74      if (pr.find(std::string({0x12, 0x0b}) + "x509+sha256") != std::string::npos || pr.find(std::string({0x12, 0x09}) + "x509+sha1") != std::string::npos) {
  75          // We want the common name of the Subject of the cert. This should be the second occurrence
  76          // of the bytes 0x0603550403. The first occurrence of those is the common name of the issuer.
  77          // After those bytes will be either 0x13 or 0x0C, then length, then either the ascii or utf8
  78          // string with the common name which is the merchant name
  79          size_t cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03});
  80          if (cn_pos != std::string::npos) {
  81              cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03}, cn_pos + 5);
  82              if (cn_pos != std::string::npos) {
  83                  cn_pos += 5;
  84                  if (pr[cn_pos] == 0x13 || pr[cn_pos] == 0x0c) {
  85                      cn_pos++; // Consume the type
  86                      int str_len = pr[cn_pos];
  87                      cn_pos++; // Consume the string length
  88                      merchant = QString::fromUtf8(pr.data() + cn_pos, str_len);
  89                      return true;
  90                  }
  91              }
  92          }
  93      }
  94      return false;
  95  }
  96  
  97  QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit)
  98  {
  99      int numBlocks;
 100      interfaces::WalletTxStatus status;
 101      bool inMempool;
 102      std::vector<std::string> messages;
 103      std::vector<std::string> payment_requests;
 104      interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, messages, payment_requests, inMempool, numBlocks);
 105  
 106      QString strHTML;
 107  
 108      strHTML.reserve(4000);
 109      strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
 110  
 111      int64_t nTime = wtx.time;
 112      CAmount nCredit = wtx.credit;
 113      CAmount nDebit = wtx.debit;
 114      CAmount nNet = nCredit - nDebit;
 115  
 116      strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool);
 117      strHTML += "<br>";
 118  
 119      strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
 120  
 121      //
 122      // From
 123      //
 124      if (wtx.is_coinbase)
 125      {
 126          strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
 127      } else if (wtx.from) {
 128          // Online transaction
 129          strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(*wtx.from) + "<br>";
 130      }
 131      else
 132      {
 133          // Offline transaction
 134          if (nNet > 0)
 135          {
 136              // Credit
 137              CTxDestination address = DecodeDestination(rec->address);
 138              if (IsValidDestination(address)) {
 139                  std::string name;
 140                  if (wallet.getAddress(address, &name, /*purpose=*/nullptr))
 141                  {
 142                      strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
 143                      strHTML += "<b>" + tr("To") + ":</b> ";
 144                      strHTML += GUIUtil::HtmlEscape(rec->address);
 145                      QString addressOwned = tr("own address");
 146                      if (!name.empty())
 147                          strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
 148                      else
 149                          strHTML += " (" + addressOwned + ")";
 150                      strHTML += "<br>";
 151                  }
 152              }
 153          }
 154      }
 155  
 156      //
 157      // To
 158      //
 159      if (wtx.comment_to) {
 160          // Online transaction
 161          std::string strAddress = *wtx.comment_to;
 162          strHTML += "<b>" + tr("To") + ":</b> ";
 163          CTxDestination dest = DecodeDestination(strAddress);
 164          std::string name;
 165          if (wallet.getAddress(
 166                  dest, &name, /*purpose=*/nullptr) && !name.empty())
 167              strHTML += GUIUtil::HtmlEscape(name) + " ";
 168          strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
 169      }
 170  
 171      //
 172      // Amount
 173      //
 174      if (wtx.is_coinbase && nCredit == 0)
 175      {
 176          //
 177          // Coinbase
 178          //
 179          CAmount nUnmatured = 0;
 180          for (const CTxOut& txout : wtx.tx->vout)
 181              nUnmatured += wallet.getCredit(txout);
 182          strHTML += "<b>" + tr("Credit") + ":</b> ";
 183          if (status.is_in_main_chain)
 184              strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
 185          else
 186              strHTML += "(" + tr("not accepted") + ")";
 187          strHTML += "<br>";
 188      }
 189      else if (nNet > 0)
 190      {
 191          //
 192          // Credit
 193          //
 194          strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
 195      }
 196      else
 197      {
 198          bool all_from_me = std::all_of(wtx.txin_is_mine.begin(), wtx.txin_is_mine.end(), [](bool mine) { return mine; });
 199          bool all_to_me = std::all_of(wtx.txout_is_mine.begin(), wtx.txout_is_mine.end(), [](bool mine) { return mine; });
 200  
 201          if (all_from_me)
 202          {
 203              // Debit
 204              //
 205              auto mine = wtx.txout_is_mine.begin();
 206              for (const CTxOut& txout : wtx.tx->vout)
 207              {
 208                  // Ignore change
 209                  bool toSelf = *(mine++);
 210                  if (toSelf && all_from_me)
 211                      continue;
 212  
 213                  if (!wtx.comment_to) {
 214                      // Offline transaction
 215                      CTxDestination address;
 216                      if (ExtractDestination(txout.scriptPubKey, address))
 217                      {
 218                          strHTML += "<b>" + tr("To") + ":</b> ";
 219                          std::string name;
 220                          if (wallet.getAddress(
 221                                  address, &name, /*purpose=*/nullptr) && !name.empty())
 222                              strHTML += GUIUtil::HtmlEscape(name) + " ";
 223                          strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
 224                          if(toSelf)
 225                              strHTML += " (" + tr("own address") + ")";
 226                          strHTML += "<br>";
 227                      }
 228                  }
 229  
 230                  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
 231                  if(toSelf)
 232                      strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
 233              }
 234  
 235              if (all_to_me)
 236              {
 237                  // Payment to self
 238                  CAmount nChange = wtx.change;
 239                  CAmount nValue = nCredit - nChange;
 240                  strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
 241                  strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
 242              }
 243  
 244              CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
 245              if (nTxFee > 0)
 246                  strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
 247          }
 248          else
 249          {
 250              //
 251              // Mixed debit transaction
 252              //
 253              auto mine = wtx.txin_is_mine.begin();
 254              for (const CTxIn& txin : wtx.tx->vin) {
 255                  if (*(mine++)) {
 256                      strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
 257                  }
 258              }
 259              mine = wtx.txout_is_mine.begin();
 260              for (const CTxOut& txout : wtx.tx->vout) {
 261                  if (*(mine++)) {
 262                      strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
 263                  }
 264              }
 265          }
 266      }
 267  
 268      strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
 269  
 270      //
 271      // Message
 272      //
 273      if (wtx.message) {
 274          strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.message, true) + "<br>";
 275      }
 276      if (wtx.comment) {
 277          strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.comment, true) + "<br>";
 278      }
 279  
 280      strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
 281      strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->ComputeTotalSize()) + " bytes<br>";
 282      strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
 283      strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
 284  
 285      // Message from normal bitcoin:URI (bitcoin:123...?message=example)
 286      for (const std::string& msg : messages) {
 287          strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(msg, true) + "<br>";
 288      }
 289      // BIP 70 Payment Requests
 290      for (const std::string& req : payment_requests) {
 291          QString merchant;
 292          if (!GetPaymentRequestMerchant(req, merchant)) {
 293              merchant.clear();
 294          } else {
 295              merchant = tr("%1 (Certificate was not verified)").arg(merchant);
 296          }
 297          if (!merchant.isNull()) {
 298              strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
 299          }
 300      }
 301  
 302      if (wtx.is_coinbase)
 303      {
 304          quint32 numBlocksToMaturity = COINBASE_MATURITY +  1;
 305          strHTML += "<br>" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "<br>";
 306      }
 307  
 308      //
 309      // Debug view
 310      //
 311      if (node.getLogCategories() != BCLog::NONE)
 312      {
 313          strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
 314          for (const CTxIn& txin : wtx.tx->vin)
 315              if(wallet.txinIsMine(txin))
 316                  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
 317          for (const CTxOut& txout : wtx.tx->vout)
 318              if(wallet.txoutIsMine(txout))
 319                  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
 320  
 321          strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
 322          strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
 323  
 324          strHTML += "<br><b>" + tr("Inputs") + ":</b>";
 325          strHTML += "<ul>";
 326  
 327          for (const CTxIn& txin : wtx.tx->vin)
 328          {
 329              COutPoint prevout = txin.prevout;
 330  
 331              if (auto prev{node.getUnspentOutput(prevout)}) {
 332                  {
 333                      strHTML += "<li>";
 334                      const CTxOut& vout = prev->out;
 335                      CTxDestination address;
 336                      if (ExtractDestination(vout.scriptPubKey, address))
 337                      {
 338                          std::string name;
 339                          if (wallet.getAddress(address, &name, /*purpose=*/nullptr) && !name.empty())
 340                              strHTML += GUIUtil::HtmlEscape(name) + " ";
 341                          strHTML += QString::fromStdString(EncodeDestination(address));
 342                      }
 343                      strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
 344                      strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) ? tr("true") : tr("false")) + "</li>";
 345                  }
 346              }
 347          }
 348  
 349          strHTML += "</ul>";
 350      }
 351  
 352      strHTML += "</font></html>";
 353      return strHTML;
 354  }
 355