Bitcoin Core 31.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 <policy/policy.h>
18#include <util/log.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;
101 bool inMempool;
102 std::vector<std::string> messages;
103 std::vector<std::string> payment_requests;
104 interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, messages, payment_requests, inMempool, numBlocks);
105
106 QString strHTML;
107
108 strHTML.reserve(4000);
109 strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
110
111 int64_t nTime = wtx.time;
112 CAmount nCredit = wtx.credit;
113 CAmount nDebit = wtx.debit;
114 CAmount nNet = nCredit - nDebit;
115
116 strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool);
117 strHTML += "<br>";
118
119 strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
120
121 //
122 // From
123 //
124 if (wtx.is_coinbase)
125 {
126 strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
127 } else if (wtx.from) {
128 // Online transaction
129 strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(*wtx.from) + "<br>";
130 }
131 else
132 {
133 // Offline transaction
134 if (nNet > 0)
135 {
136 // Credit
138 if (IsValidDestination(address)) {
139 std::string name;
140 if (wallet.getAddress(address, &name, /*purpose=*/nullptr))
141 {
142 strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
143 strHTML += "<b>" + tr("To") + ":</b> ";
144 strHTML += GUIUtil::HtmlEscape(rec->address);
145 QString addressOwned = tr("own address");
146 if (!name.empty())
147 strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
148 else
149 strHTML += " (" + addressOwned + ")";
150 strHTML += "<br>";
151 }
152 }
153 }
154 }
155
156 //
157 // To
158 //
159 if (wtx.comment_to) {
160 // Online transaction
161 std::string strAddress = *wtx.comment_to;
162 strHTML += "<b>" + tr("To") + ":</b> ";
163 CTxDestination dest = DecodeDestination(strAddress);
164 std::string name;
165 if (wallet.getAddress(
166 dest, &name, /*purpose=*/nullptr) && !name.empty())
167 strHTML += GUIUtil::HtmlEscape(name) + " ";
168 strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
169 }
170
171 //
172 // Amount
173 //
174 if (wtx.is_coinbase && nCredit == 0)
175 {
176 //
177 // Coinbase
178 //
179 CAmount nUnmatured = 0;
180 for (const CTxOut& txout : wtx.tx->vout)
181 nUnmatured += wallet.getCredit(txout);
182 strHTML += "<b>" + tr("Credit") + ":</b> ";
183 if (status.is_in_main_chain)
184 strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
185 else
186 strHTML += "(" + tr("not accepted") + ")";
187 strHTML += "<br>";
188 }
189 else if (nNet > 0)
190 {
191 //
192 // Credit
193 //
194 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
195 }
196 else
197 {
198 bool all_from_me = std::all_of(wtx.txin_is_mine.begin(), wtx.txin_is_mine.end(), [](bool mine) { return mine; });
199 bool all_to_me = std::all_of(wtx.txout_is_mine.begin(), wtx.txout_is_mine.end(), [](bool mine) { return mine; });
200
201 if (all_from_me)
202 {
203 // Debit
204 //
205 auto mine = wtx.txout_is_mine.begin();
206 for (const CTxOut& txout : wtx.tx->vout)
207 {
208 // Ignore change
209 bool toSelf = *(mine++);
210 if (toSelf && all_from_me)
211 continue;
212
213 if (!wtx.comment_to) {
214 // Offline transaction
215 CTxDestination address;
216 if (ExtractDestination(txout.scriptPubKey, address))
217 {
218 strHTML += "<b>" + tr("To") + ":</b> ";
219 std::string name;
220 if (wallet.getAddress(
221 address, &name, /*purpose=*/nullptr) && !name.empty())
222 strHTML += GUIUtil::HtmlEscape(name) + " ";
223 strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
224 if(toSelf)
225 strHTML += " (" + tr("own address") + ")";
226 strHTML += "<br>";
227 }
228 }
229
230 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
231 if(toSelf)
232 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
233 }
234
235 if (all_to_me)
236 {
237 // Payment to self
238 CAmount nChange = wtx.change;
239 CAmount nValue = nCredit - nChange;
240 strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
241 strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
242 }
243
244 CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
245 if (nTxFee > 0)
246 strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
247 }
248 else
249 {
250 //
251 // Mixed debit transaction
252 //
253 auto mine = wtx.txin_is_mine.begin();
254 for (const CTxIn& txin : wtx.tx->vin) {
255 if (*(mine++)) {
256 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
257 }
258 }
259 mine = wtx.txout_is_mine.begin();
260 for (const CTxOut& txout : wtx.tx->vout) {
261 if (*(mine++)) {
262 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
263 }
264 }
265 }
266 }
267
268 strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
269
270 //
271 // Message
272 //
273 if (wtx.message) {
274 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.message, true) + "<br>";
275 }
276 if (wtx.comment) {
277 strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(*wtx.comment, true) + "<br>";
278 }
279
280 strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
281 strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->ComputeTotalSize()) + " bytes<br>";
282 strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
283 strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
284
285 // Message from normal bitcoin:URI (bitcoin:123...?message=example)
286 for (const std::string& msg : messages) {
287 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(msg, true) + "<br>";
288 }
289 // BIP 70 Payment Requests
290 for (const std::string& req : payment_requests) {
291 QString merchant;
292 if (!GetPaymentRequestMerchant(req, merchant)) {
293 merchant.clear();
294 } else {
295 merchant = tr("%1 (Certificate was not verified)").arg(merchant);
296 }
297 if (!merchant.isNull()) {
298 strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
299 }
300 }
301
302 if (wtx.is_coinbase)
303 {
304 quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
305 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>";
306 }
307
308 //
309 // Debug view
310 //
311 if (node.getLogCategories() != BCLog::NONE)
312 {
313 strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
314 for (const CTxIn& txin : wtx.tx->vin)
315 if(wallet.txinIsMine(txin))
316 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin)) + "<br>";
317 for (const CTxOut& txout : wtx.tx->vout)
318 if(wallet.txoutIsMine(txout))
319 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout)) + "<br>";
320
321 strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
322 strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
323
324 strHTML += "<br><b>" + tr("Inputs") + ":</b>";
325 strHTML += "<ul>";
326
327 for (const CTxIn& txin : wtx.tx->vin)
328 {
329 COutPoint prevout = txin.prevout;
330
331 if (auto prev{node.getUnspentOutput(prevout)}) {
332 {
333 strHTML += "<li>";
334 const CTxOut& vout = prev->out;
335 CTxDestination address;
336 if (ExtractDestination(vout.scriptPubKey, address))
337 {
338 std::string name;
339 if (wallet.getAddress(address, &name, /*purpose=*/nullptr) && !name.empty())
340 strHTML += GUIUtil::HtmlEscape(name) + " ";
341 strHTML += QString::fromStdString(EncodeDestination(address));
342 }
343 strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
344 strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) ? tr("true") : tr("false")) + "</li>";
345 }
346 }
347 }
348
349 strHTML += "</ul>";
350 }
351
352 strHTML += "</font></html>";
353 return strHTML;
354}
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:62
COutPoint prevout
Definition: transaction.h:64
An output of a transaction.
Definition: transaction.h:140
CScript scriptPubKey
Definition: transaction.h:143
CAmount nValue
Definition: transaction.h:142
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:64
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:300
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:295
@ NONE
Definition: categories.h:15
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:90
Definition: messages.h:21
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:395
const char * name
Definition: rest.cpp:50
std::vector< bool > txin_is_mine
Definition: wallet.h:382
std::optional< std::string > comment
Definition: wallet.h:393
CTransactionRef tx
Definition: wallet.h:381
std::optional< std::string > comment_to
Definition: wallet.h:394
std::vector< bool > txout_is_mine
Definition: wallet.h:383
std::optional< std::string > message
Definition: wallet.h:392
std::optional< std::string > from
Definition: wallet.h:391
Updated transaction status.
Definition: wallet.h:402
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)