Bitcoin Core  27.99.0
P2P Digital Currency
transactiondesc.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2022 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 #include <wallet/types.h>
21 
22 #include <stdint.h>
23 #include <string>
24 
25 #include <QLatin1String>
26 
27 using wallet::ISMINE_ALL;
30 using wallet::isminetype;
31 
32 QString TransactionDesc::FormatTxStatus(const interfaces::WalletTxStatus& status, bool inMempool)
33 {
34  int depth = status.depth_in_main_chain;
35  if (depth < 0) {
36  /*: Text explaining the current status of a transaction, shown in the
37  status field of the details window for this transaction. This status
38  represents an unconfirmed transaction that conflicts with a confirmed
39  transaction. */
40  return tr("conflicted with a transaction with %1 confirmations").arg(-depth);
41  } else if (depth == 0) {
42  QString s;
43  if (inMempool) {
44  /*: Text explaining the current status of a transaction, shown in the
45  status field of the details window for this transaction. This status
46  represents an unconfirmed transaction that is in the memory pool. */
47  s = tr("0/unconfirmed, in memory pool");
48  } else {
49  /*: Text explaining the current status of a transaction, shown in the
50  status field of the details window for this transaction. This status
51  represents an unconfirmed transaction that is not in the memory pool. */
52  s = tr("0/unconfirmed, not in memory pool");
53  }
54  if (status.is_abandoned) {
55  /*: Text explaining the current status of a transaction, shown in the
56  status field of the details window for this transaction. This
57  status represents an abandoned transaction. */
58  s += QLatin1String(", ") + tr("abandoned");
59  }
60  return s;
61  } else if (depth < 6) {
62  /*: Text explaining the current status of a transaction, shown in the
63  status field of the details window for this transaction. This
64  status represents a transaction confirmed in at least one block,
65  but less than 6 blocks. */
66  return tr("%1/unconfirmed").arg(depth);
67  } else {
68  /*: Text explaining the current status of a transaction, shown in the
69  status field of the details window for this transaction. This status
70  represents a transaction confirmed in 6 or more blocks. */
71  return tr("%1 confirmations").arg(depth);
72  }
73 }
74 
75 // Takes an encoded PaymentRequest as a string and tries to find the Common Name of the X.509 certificate
76 // used to sign the PaymentRequest.
77 bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant)
78 {
79  // Search for the supported pki type strings
80  if (pr.find(std::string({0x12, 0x0b}) + "x509+sha256") != std::string::npos || pr.find(std::string({0x12, 0x09}) + "x509+sha1") != std::string::npos) {
81  // We want the common name of the Subject of the cert. This should be the second occurrence
82  // of the bytes 0x0603550403. The first occurrence of those is the common name of the issuer.
83  // After those bytes will be either 0x13 or 0x0C, then length, then either the ascii or utf8
84  // string with the common name which is the merchant name
85  size_t cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03});
86  if (cn_pos != std::string::npos) {
87  cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03}, cn_pos + 5);
88  if (cn_pos != std::string::npos) {
89  cn_pos += 5;
90  if (pr[cn_pos] == 0x13 || pr[cn_pos] == 0x0c) {
91  cn_pos++; // Consume the type
92  int str_len = pr[cn_pos];
93  cn_pos++; // Consume the string length
94  merchant = QString::fromUtf8(pr.data() + cn_pos, str_len);
95  return true;
96  }
97  }
98  }
99  }
100  return false;
101 }
102 
104 {
105  int numBlocks;
107  interfaces::WalletOrderForm orderForm;
108  bool inMempool;
109  interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks);
110 
111  QString strHTML;
112 
113  strHTML.reserve(4000);
114  strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
115 
116  int64_t nTime = wtx.time;
117  CAmount nCredit = wtx.credit;
118  CAmount nDebit = wtx.debit;
119  CAmount nNet = nCredit - nDebit;
120 
121  strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool);
122  strHTML += "<br>";
123 
124  strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
125 
126  //
127  // From
128  //
129  if (wtx.is_coinbase)
130  {
131  strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
132  }
133  else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty())
134  {
135  // Online transaction
136  strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
137  }
138  else
139  {
140  // Offline transaction
141  if (nNet > 0)
142  {
143  // Credit
144  CTxDestination address = DecodeDestination(rec->address);
145  if (IsValidDestination(address)) {
146  std::string name;
147  isminetype ismine;
148  if (wallet.getAddress(address, &name, &ismine, /* purpose= */ nullptr))
149  {
150  strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
151  strHTML += "<b>" + tr("To") + ":</b> ";
152  strHTML += GUIUtil::HtmlEscape(rec->address);
153  QString addressOwned = ismine == ISMINE_SPENDABLE ? tr("own address") : tr("watch-only");
154  if (!name.empty())
155  strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
156  else
157  strHTML += " (" + addressOwned + ")";
158  strHTML += "<br>";
159  }
160  }
161  }
162  }
163 
164  //
165  // To
166  //
167  if (wtx.value_map.count("to") && !wtx.value_map["to"].empty())
168  {
169  // Online transaction
170  std::string strAddress = wtx.value_map["to"];
171  strHTML += "<b>" + tr("To") + ":</b> ";
172  CTxDestination dest = DecodeDestination(strAddress);
173  std::string name;
174  if (wallet.getAddress(
175  dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
176  strHTML += GUIUtil::HtmlEscape(name) + " ";
177  strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
178  }
179 
180  //
181  // Amount
182  //
183  if (wtx.is_coinbase && nCredit == 0)
184  {
185  //
186  // Coinbase
187  //
188  CAmount nUnmatured = 0;
189  for (const CTxOut& txout : wtx.tx->vout)
190  nUnmatured += wallet.getCredit(txout, ISMINE_ALL);
191  strHTML += "<b>" + tr("Credit") + ":</b> ";
192  if (status.is_in_main_chain)
193  strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
194  else
195  strHTML += "(" + tr("not accepted") + ")";
196  strHTML += "<br>";
197  }
198  else if (nNet > 0)
199  {
200  //
201  // Credit
202  //
203  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
204  }
205  else
206  {
207  isminetype fAllFromMe = ISMINE_SPENDABLE;
208  for (const isminetype mine : wtx.txin_is_mine)
209  {
210  if(fAllFromMe > mine) fAllFromMe = mine;
211  }
212 
213  isminetype fAllToMe = ISMINE_SPENDABLE;
214  for (const isminetype mine : wtx.txout_is_mine)
215  {
216  if(fAllToMe > mine) fAllToMe = mine;
217  }
218 
219  if (fAllFromMe)
220  {
221  if(fAllFromMe & ISMINE_WATCH_ONLY)
222  strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
223 
224  //
225  // Debit
226  //
227  auto mine = wtx.txout_is_mine.begin();
228  for (const CTxOut& txout : wtx.tx->vout)
229  {
230  // Ignore change
231  isminetype toSelf = *(mine++);
232  if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE))
233  continue;
234 
235  if (!wtx.value_map.count("to") || wtx.value_map["to"].empty())
236  {
237  // Offline transaction
238  CTxDestination address;
239  if (ExtractDestination(txout.scriptPubKey, address))
240  {
241  strHTML += "<b>" + tr("To") + ":</b> ";
242  std::string name;
243  if (wallet.getAddress(
244  address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
245  strHTML += GUIUtil::HtmlEscape(name) + " ";
246  strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
247  if(toSelf == ISMINE_SPENDABLE)
248  strHTML += " (" + tr("own address") + ")";
249  else if(toSelf & ISMINE_WATCH_ONLY)
250  strHTML += " (" + tr("watch-only") + ")";
251  strHTML += "<br>";
252  }
253  }
254 
255  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
256  if(toSelf)
257  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
258  }
259 
260  if (fAllToMe)
261  {
262  // Payment to self
263  CAmount nChange = wtx.change;
264  CAmount nValue = nCredit - nChange;
265  strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
266  strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
267  }
268 
269  CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
270  if (nTxFee > 0)
271  strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
272  }
273  else
274  {
275  //
276  // Mixed debit transaction
277  //
278  auto mine = wtx.txin_is_mine.begin();
279  for (const CTxIn& txin : wtx.tx->vin) {
280  if (*(mine++)) {
281  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
282  }
283  }
284  mine = wtx.txout_is_mine.begin();
285  for (const CTxOut& txout : wtx.tx->vout) {
286  if (*(mine++)) {
287  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
288  }
289  }
290  }
291  }
292 
293  strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
294 
295  //
296  // Message
297  //
298  if (wtx.value_map.count("message") && !wtx.value_map["message"].empty())
299  strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
300  if (wtx.value_map.count("comment") && !wtx.value_map["comment"].empty())
301  strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
302 
303  strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
304  strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->GetTotalSize()) + " bytes<br>";
305  strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
306  strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
307 
308  // Message from normal bitcoin:URI (bitcoin:123...?message=example)
309  for (const std::pair<std::string, std::string>& r : orderForm) {
310  if (r.first == "Message")
311  strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
312 
313  //
314  // PaymentRequest info:
315  //
316  if (r.first == "PaymentRequest")
317  {
318  QString merchant;
319  if (!GetPaymentRequestMerchant(r.second, merchant)) {
320  merchant.clear();
321  } else {
322  merchant = tr("%1 (Certificate was not verified)").arg(merchant);
323  }
324  if (!merchant.isNull()) {
325  strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
326  }
327  }
328  }
329 
330  if (wtx.is_coinbase)
331  {
332  quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
333  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>";
334  }
335 
336  //
337  // Debug view
338  //
339  if (node.getLogCategories() != BCLog::NONE)
340  {
341  strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
342  for (const CTxIn& txin : wtx.tx->vin)
343  if(wallet.txinIsMine(txin))
344  strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
345  for (const CTxOut& txout : wtx.tx->vout)
346  if(wallet.txoutIsMine(txout))
347  strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
348 
349  strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
350  strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
351 
352  strHTML += "<br><b>" + tr("Inputs") + ":</b>";
353  strHTML += "<ul>";
354 
355  for (const CTxIn& txin : wtx.tx->vin)
356  {
357  COutPoint prevout = txin.prevout;
358 
359  if (auto prev{node.getUnspentOutput(prevout)}) {
360  {
361  strHTML += "<li>";
362  const CTxOut& vout = prev->out;
363  CTxDestination address;
364  if (ExtractDestination(vout.scriptPubKey, address))
365  {
366  std::string name;
367  if (wallet.getAddress(address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
368  strHTML += GUIUtil::HtmlEscape(name) + " ";
369  strHTML += QString::fromStdString(EncodeDestination(address));
370  }
371  strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
372  strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
373  strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
374  }
375  }
376  }
377 
378  strHTML += "</ul>";
379  }
380 
381  strHTML += "</font></html>";
382  return strHTML;
383 }
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a scriptPubKey for the destination.
Definition: addresstype.cpp:49
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination corresponds to one with an address.
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:131
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
An input of a transaction.
Definition: transaction.h:67
COutPoint prevout
Definition: transaction.h:69
An output of a transaction.
Definition: transaction.h:150
CScript scriptPubKey
Definition: transaction.h:153
CAmount nValue
Definition: transaction.h:152
static QString FormatTxStatus(const interfaces::WalletTxStatus &status, bool inMempool)
static QString toHTML(interfaces::Node &node, interfaces::Wallet &wallet, TransactionRecord *rec, BitcoinUnit unit)
UI model for a transaction.
int getOutputIndex() const
Return the output index of the subtransaction
QString getTxHash() const
Return the unique identifier for this transaction (part)
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:70
Interface for accessing a wallet.
Definition: wallet.h:61
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule)
Definition: consensus.h:19
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:292
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:287
@ NONE
Definition: logging.h:40
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:248
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:94
std::vector< std::pair< std::string, std::string > > WalletOrderForm
Definition: wallet.h:56
Definition: init.h:25
isminetype
IsMine() return codes, which depend on ScriptPubKeyMan implementation.
Definition: types.h:40
@ ISMINE_SPENDABLE
Definition: types.h:43
@ ISMINE_WATCH_ONLY
Definition: types.h:42
@ ISMINE_ALL
Definition: types.h:45
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
Compute the virtual transaction size (weight reinterpreted as bytes).
Definition: policy.cpp:295
const char * name
Definition: rest.cpp:50
std::vector< wallet::isminetype > txin_is_mine
Definition: wallet.h:394
CTransactionRef tx
Definition: wallet.h:393
std::map< std::string, std::string > value_map
Definition: wallet.h:403
std::vector< wallet::isminetype > txout_is_mine
Definition: wallet.h:395
Updated transaction status.
Definition: wallet.h:411
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)