Bitcoin Core 29.99.0
P2P Digital Currency
transactiondesc.cpp
Go to the documentation of this file.
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
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
21#include <cstdint>
22#include <string>
23
24#include <QLatin1String>
25
26QString 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.
71bool 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
98{
99 int numBlocks;
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.count("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
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.count("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.count("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.count("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.count("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->GetTotalSize()) + " 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}
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:143
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:65
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:65
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:60
Definition: messages.h:20
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:376
const char * name
Definition: rest.cpp:50
std::vector< bool > txin_is_mine
Definition: wallet.h:384
CTransactionRef tx
Definition: wallet.h:383
std::map< std::string, std::string > value_map
Definition: wallet.h:393
std::vector< bool > txout_is_mine
Definition: wallet.h:385
Updated transaction status.
Definition: wallet.h:401
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)