/ src / qt / transactiondesc.cpp
transactiondesc.cpp
  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 <logging.h>
 18  #include <policy/policy.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      interfaces::WalletOrderForm orderForm;
102      bool inMempool;
103      interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks);
104  
105      QString strHTML;
106  
107      strHTML.reserve(4000);
108      strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
109  
110      int64_t nTime = wtx.time;
111      CAmount nCredit = wtx.credit;
112      CAmount nDebit = wtx.debit;
113      CAmount nNet = nCredit - nDebit;
114  
115      strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool);
116      strHTML += "<br>";
117  
118      strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
119  
120      //
121      // From
122      //
123      if (wtx.is_coinbase)
124      {
125          strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
126      }
127      else if (wtx.value_map.contains("from") && !wtx.value_map["from"].empty())
128      {
129          // Online transaction
130          strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
131      }
132      else
133      {
134          // Offline transaction
135          if (nNet > 0)
136          {
137              // Credit
138              CTxDestination address = DecodeDestination(rec->address);
139              if (IsValidDestination(address)) {
140                  std::string name;
141                  if (wallet.getAddress(address, &name, /*purpose=*/nullptr))
142                  {
143                      strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
144                      strHTML += "<b>" + tr("To") + ":</b> ";
145                      strHTML += GUIUtil::HtmlEscape(rec->address);
146                      QString addressOwned = tr("own address");
147                      if (!name.empty())
148                          strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
149                      else
150                          strHTML += " (" + addressOwned + ")";
151                      strHTML += "<br>";
152                  }
153              }
154          }
155      }
156  
157      //
158      // To
159      //
160      if (wtx.value_map.contains("to") && !wtx.value_map["to"].empty())
161      {
162          // Online transaction
163          std::string strAddress = wtx.value_map["to"];
164          strHTML += "<b>" + tr("To") + ":</b> ";
165          CTxDestination dest = DecodeDestination(strAddress);
166          std::string name;
167          if (wallet.getAddress(
168                  dest, &name, /*purpose=*/nullptr) && !name.empty())
169              strHTML += GUIUtil::HtmlEscape(name) + " ";
170          strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
171      }
172  
173      //
174      // Amount
175      //
176      if (wtx.is_coinbase && nCredit == 0)
177      {
178          //
179          // Coinbase
180          //
181          CAmount nUnmatured = 0;
182          for (const CTxOut& txout : wtx.tx->vout)
183              nUnmatured += wallet.getCredit(txout);
184          strHTML += "<b>" + tr("Credit") + ":</b> ";
185          if (status.is_in_main_chain)
186              strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
187          else
188              strHTML += "(" + tr("not accepted") + ")";
189          strHTML += "<br>";
190      }
191      else if (nNet > 0)
192      {
193          //
194          // Credit
195          //
196          strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
197      }
198      else
199      {
200          bool all_from_me = std::all_of(wtx.txin_is_mine.begin(), wtx.txin_is_mine.end(), [](bool mine) { return mine; });
201          bool all_to_me = std::all_of(wtx.txout_is_mine.begin(), wtx.txout_is_mine.end(), [](bool mine) { return mine; });
202  
203          if (all_from_me)
204          {
205              // Debit
206              //
207              auto mine = wtx.txout_is_mine.begin();
208              for (const CTxOut& txout : wtx.tx->vout)
209              {
210                  // Ignore change
211                  bool toSelf = *(mine++);
212                  if (toSelf && all_from_me)
213                      continue;
214  
215                  if (!wtx.value_map.contains("to") || wtx.value_map["to"].empty())
216                  {
217                      // Offline transaction
218                      CTxDestination address;
219                      if (ExtractDestination(txout.scriptPubKey, address))
220                      {
221                          strHTML += "<b>" + tr("To") + ":</b> ";
222                          std::string name;
223                          if (wallet.getAddress(
224                                  address, &name, /*purpose=*/nullptr) && !name.empty())
225                              strHTML += GUIUtil::HtmlEscape(name) + " ";
226                          strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
227                          if(toSelf)
228                              strHTML += " (" + tr("own address") + ")";
229                          strHTML += "<br>";
230                      }
231                  }
232  
233                  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
234                  if(toSelf)
235                      strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
236              }
237  
238              if (all_to_me)
239              {
240                  // Payment to self
241                  CAmount nChange = wtx.change;
242                  CAmount nValue = nCredit - nChange;
243                  strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
244                  strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
245              }
246  
247              CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
248              if (nTxFee > 0)
249                  strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
250          }
251          else
252          {
253              //
254              // Mixed debit transaction
255              //
256              auto mine = wtx.txin_is_mine.begin();
257              for (const CTxIn& txin : wtx.tx->vin) {
258                  if (*(mine++)) {
259                      strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
260                  }
261              }
262              mine = wtx.txout_is_mine.begin();
263              for (const CTxOut& txout : wtx.tx->vout) {
264                  if (*(mine++)) {
265                      strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
266                  }
267              }
268          }
269      }
270  
271      strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
272  
273      //
274      // Message
275      //
276      if (wtx.value_map.contains("message") && !wtx.value_map["message"].empty())
277          strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
278      if (wtx.value_map.contains("comment") && !wtx.value_map["comment"].empty())
279          strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
280  
281      strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
282      strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->ComputeTotalSize()) + " bytes<br>";
283      strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
284      strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
285  
286      // Message from normal bitcoin:URI (bitcoin:123...?message=example)
287      for (const std::pair<std::string, std::string>& r : orderForm) {
288          if (r.first == "Message")
289              strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
290  
291          //
292          // PaymentRequest info:
293          //
294          if (r.first == "PaymentRequest")
295          {
296              QString merchant;
297              if (!GetPaymentRequestMerchant(r.second, merchant)) {
298                  merchant.clear();
299              } else {
300                  merchant = tr("%1 (Certificate was not verified)").arg(merchant);
301              }
302              if (!merchant.isNull()) {
303                  strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
304              }
305          }
306      }
307  
308      if (wtx.is_coinbase)
309      {
310          quint32 numBlocksToMaturity = COINBASE_MATURITY +  1;
311          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>";
312      }
313  
314      //
315      // Debug view
316      //
317      if (node.getLogCategories() != BCLog::NONE)
318      {
319          strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
320          for (const CTxIn& txin : wtx.tx->vin)
321              if(wallet.txinIsMine(txin))
322                  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
323          for (const CTxOut& txout : wtx.tx->vout)
324              if(wallet.txoutIsMine(txout))
325                  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
326  
327          strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
328          strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
329  
330          strHTML += "<br><b>" + tr("Inputs") + ":</b>";
331          strHTML += "<ul>";
332  
333          for (const CTxIn& txin : wtx.tx->vin)
334          {
335              COutPoint prevout = txin.prevout;
336  
337              if (auto prev{node.getUnspentOutput(prevout)}) {
338                  {
339                      strHTML += "<li>";
340                      const CTxOut& vout = prev->out;
341                      CTxDestination address;
342                      if (ExtractDestination(vout.scriptPubKey, address))
343                      {
344                          std::string name;
345                          if (wallet.getAddress(address, &name, /*purpose=*/nullptr) && !name.empty())
346                              strHTML += GUIUtil::HtmlEscape(name) + " ";
347                          strHTML += QString::fromStdString(EncodeDestination(address));
348                      }
349                      strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
350                      strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) ? tr("true") : tr("false")) + "</li>";
351                  }
352              }
353          }
354  
355          strHTML += "</ul>";
356      }
357  
358      strHTML += "</font></html>";
359      return strHTML;
360  }