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