Bitcoin Core 28.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
6
7#include <qt/bitcoinunits.h>
8#include <qt/guiutil.h>
9#include <qt/paymentserver.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
31
32QString 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.
77bool 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;
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
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, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:140
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:71
Interface for accessing a wallet.
Definition: wallet.h:66
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:299
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:294
@ NONE
Definition: logging.h:42
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:90
std::vector< std::pair< std::string, std::string > > WalletOrderForm
Definition: wallet.h:61
Definition: messages.h:20
isminetype
IsMine() return codes, which depend on ScriptPubKeyMan implementation.
Definition: types.h:41
@ ISMINE_SPENDABLE
Definition: types.h:44
@ ISMINE_WATCH_ONLY
Definition: types.h:43
@ ISMINE_ALL
Definition: types.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:312
const char * name
Definition: rest.cpp:49
std::vector< wallet::isminetype > txin_is_mine
Definition: wallet.h:402
CTransactionRef tx
Definition: wallet.h:401
std::map< std::string, std::string > value_map
Definition: wallet.h:411
std::vector< wallet::isminetype > txout_is_mine
Definition: wallet.h:403
Updated transaction status.
Definition: wallet.h:419
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)
is a home for public enum and struct type definitions that are used by internally by wallet code,...