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