Bitcoin ABC 0.34.2
P2P Digital Currency
transactiondesc.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2016 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
6#include <config/bitcoin-config.h>
7#endif
8
10
11#include <cashaddrenc.h>
12#include <chain.h>
13#include <common/args.h>
14#include <consensus/consensus.h>
15#include <interfaces/node.h>
16#include <interfaces/wallet.h>
17#include <key_io.h>
18#include <logging.h>
19#include <policy/policy.h>
20#include <qt/bitcoinunits.h>
21#include <qt/guiutil.h>
22#include <qt/paymentserver.h>
24#include <validation.h>
25#include <wallet/ismine.h>
26
27#include <cstdint>
28#include <string>
29
30QString
32 const interfaces::WalletTxStatus &status,
33 bool inMempool, int numBlocks) {
34 int nDepth = status.depth_in_main_chain;
35 if (nDepth < 0) {
36 return tr("conflicted with a transaction with %1 confirmations")
37 .arg(-nDepth);
38 } else if (nDepth == 0) {
39 return tr("0/unconfirmed, %1")
40 .arg((inMempool ? tr("in memory pool")
41 : tr("not in memory pool"))) +
42 (status.is_abandoned ? ", " + tr("abandoned") : "");
43 } else if (nDepth < 6) {
44 return tr("%1/unconfirmed").arg(nDepth);
45 } else {
46 return tr("%1 confirmations").arg(nDepth);
47 }
48}
49
50#ifndef ENABLE_BIP70
51// Takes an encoded PaymentRequest as a string and tries to find the Common Name
52// of the X.509 certificate used to sign the PaymentRequest.
53bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant) {
54 // Search for the supported pki type strings
55 if (pr.find(std::string({0x12, 0x0b}) + "x509+sha256") !=
56 std::string::npos ||
57 pr.find(std::string({0x12, 0x09}) + "x509+sha1") != std::string::npos) {
58 // We want the common name of the Subject of the cert. This should be
59 // the second occurrence of the bytes 0x0603550403. The first occurrence
60 // of those is the common name of the issuer. After those bytes will be
61 // either 0x13 or 0x0C, then length, then either the ascii or utf8
62 // string with the common name which is the merchant name
63 size_t cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03});
64 if (cn_pos != std::string::npos) {
65 cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03}, cn_pos + 5);
66 if (cn_pos != std::string::npos) {
67 cn_pos += 5;
68 if (pr[cn_pos] == 0x13 || pr[cn_pos] == 0x0c) {
69 cn_pos++; // Consume the type
70 int str_len = pr[cn_pos];
71 cn_pos++; // Consume the string length
72 merchant = QString::fromUtf8(pr.data() + cn_pos, str_len);
73 return true;
74 }
75 }
76 }
77 }
78 return false;
79}
80#endif
81
84 TransactionRecord *rec, int unit) {
85 int numBlocks;
88 bool inMempool;
89 interfaces::WalletTx wtx = wallet.getWalletTxDetails(
90 rec->txid, status, orderForm, inMempool, numBlocks);
91
92 QString strHTML;
93
94 strHTML.reserve(4000);
95 strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
96
97 int64_t nTime = wtx.time;
98 Amount nCredit = wtx.credit;
99 Amount nDebit = wtx.debit;
100 Amount nNet = nCredit - nDebit;
101
102 strHTML += "<b>" + tr("Status") + ":</b> " +
103 FormatTxStatus(wtx, status, inMempool, numBlocks);
104 strHTML += "<br>";
105
106 strHTML += "<b>" + tr("Date") + ":</b> " +
107 (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
108
109 //
110 // From
111 //
112 if (wtx.is_coinbase) {
113 strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
114 } else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty()) {
115 // Online transaction
116 strHTML += "<b>" + tr("From") + ":</b> " +
117 GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
118 } else {
119 // Offline transaction
120 if (nNet > Amount::zero()) {
121 // Credit
122 CTxDestination address =
123 DecodeDestination(rec->address, wallet.getChainParams());
124 if (IsValidDestination(address)) {
125 std::string name;
126 isminetype ismine;
127 if (wallet.getAddress(address, &name, &ismine,
128 /* purpose= */ nullptr)) {
129 strHTML +=
130 "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
131 strHTML += "<b>" + tr("To") + ":</b> ";
132 strHTML += GUIUtil::HtmlEscape(rec->address);
133 QString addressOwned = ismine == ISMINE_SPENDABLE
134 ? tr("own address")
135 : tr("watch-only");
136 if (!name.empty()) {
137 strHTML += " (" + addressOwned + ", " + tr("label") +
138 ": " + GUIUtil::HtmlEscape(name) + ")";
139 } else {
140 strHTML += " (" + addressOwned + ")";
141 }
142 strHTML += "<br>";
143 }
144 }
145 }
146 }
147
148 //
149 // To
150 //
151 if (wtx.value_map.count("to") && !wtx.value_map["to"].empty()) {
152 // Online transaction
153 std::string strAddress = wtx.value_map["to"];
154 strHTML += "<b>" + tr("To") + ":</b> ";
155 CTxDestination dest =
156 DecodeDestination(strAddress, wallet.getChainParams());
157 std::string name;
158 if (wallet.getAddress(dest, &name, /* is_mine= */ nullptr,
159 /* purpose= */ nullptr) &&
160 !name.empty()) {
161 strHTML += GUIUtil::HtmlEscape(name) + " ";
162 }
163 strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
164 }
165
166 //
167 // Amount
168 //
169 if (wtx.is_coinbase && nCredit == Amount::zero()) {
170 //
171 // Coinbase
172 //
173 Amount nUnmatured = Amount::zero();
174 for (const CTxOut &txout : wtx.tx->vout) {
175 nUnmatured += wallet.getCredit(txout, ISMINE_ALL);
176 }
177 strHTML += "<b>" + tr("Credit") + ":</b> ";
178 if (status.is_in_main_chain) {
179 strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured) +
180 " (" +
181 tr("matures in %n more block(s)", "",
182 status.blocks_to_maturity) +
183 ")";
184 } else {
185 strHTML += "(" + tr("not accepted") + ")";
186 }
187 strHTML += "<br>";
188 } else if (nNet > Amount::zero()) {
189 //
190 // Credit
191 //
192 strHTML += "<b>" + tr("Credit") + ":</b> " +
193 BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
194 } else {
195 isminetype fAllFromMe = ISMINE_SPENDABLE;
196 for (const isminetype mine : wtx.txin_is_mine) {
197 if (fAllFromMe > mine) {
198 fAllFromMe = mine;
199 }
200 }
201
202 isminetype fAllToMe = ISMINE_SPENDABLE;
203 for (const isminetype mine : wtx.txout_is_mine) {
204 if (fAllToMe > mine) {
205 fAllToMe = mine;
206 }
207 }
208
209 if (fAllFromMe) {
210 if (fAllFromMe & ISMINE_WATCH_ONLY) {
211 strHTML +=
212 "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
213 }
214
215 //
216 // Debit
217 //
218 auto mine = wtx.txout_is_mine.begin();
219 for (const CTxOut &txout : wtx.tx->vout) {
220 // Ignore change
221 isminetype toSelf = *(mine++);
222 if ((toSelf == ISMINE_SPENDABLE) &&
223 (fAllFromMe == ISMINE_SPENDABLE)) {
224 continue;
225 }
226
227 if (!wtx.value_map.count("to") || wtx.value_map["to"].empty()) {
228 // Offline transaction
229 CTxDestination address;
230 if (ExtractDestination(txout.scriptPubKey, address)) {
231 strHTML += "<b>" + tr("To") + ":</b> ";
232 std::string name;
233 if (wallet.getAddress(address, &name,
234 /* is_mine= */ nullptr,
235 /* purpose= */ nullptr) &&
236 !name.empty()) {
237 strHTML += GUIUtil::HtmlEscape(name) + " ";
238 }
239 strHTML += GUIUtil::HtmlEscape(
240 EncodeCashAddr(address, wallet.getChainParams()));
241 if (toSelf == ISMINE_SPENDABLE) {
242 strHTML += " (own address)";
243 } else if (toSelf & ISMINE_WATCH_ONLY) {
244 strHTML += " (watch-only)";
245 }
246 strHTML += "<br>";
247 }
248 }
249
250 strHTML +=
251 "<b>" + tr("Debit") + ":</b> " +
252 BitcoinUnits::formatHtmlWithUnit(unit, -1 * txout.nValue) +
253 "<br>";
254 if (toSelf) {
255 strHTML +=
256 "<b>" + tr("Credit") + ":</b> " +
258 "<br>";
259 }
260 }
261
262 if (fAllToMe) {
263 // Payment to self
264 Amount nChange = wtx.change;
265 Amount nValue = nCredit - nChange;
266 strHTML += "<b>" + tr("Total debit") + ":</b> " +
267 BitcoinUnits::formatHtmlWithUnit(unit, -1 * nValue) +
268 "<br>";
269 strHTML += "<b>" + tr("Total credit") + ":</b> " +
271 "<br>";
272 }
273
274 Amount nTxFee = nDebit - wtx.tx->GetValueOut();
275 if (nTxFee > Amount::zero()) {
276 strHTML += "<b>" + tr("Transaction fee") + ":</b> " +
277 BitcoinUnits::formatHtmlWithUnit(unit, -1 * nTxFee) +
278 "<br>";
279 }
280 } else {
281 //
282 // Mixed debit transaction
283 //
284 auto mine = wtx.txin_is_mine.begin();
285 for (const CTxIn &txin : wtx.tx->vin) {
286 if (*(mine++)) {
287 strHTML += "<b>" + tr("Debit") + ":</b> " +
289 unit, -wallet.getDebit(txin, ISMINE_ALL)) +
290 "<br>";
291 }
292 }
293 mine = wtx.txout_is_mine.begin();
294 for (const CTxOut &txout : wtx.tx->vout) {
295 if (*(mine++)) {
296 strHTML += "<b>" + tr("Credit") + ":</b> " +
298 unit, wallet.getCredit(txout, ISMINE_ALL)) +
299 "<br>";
300 }
301 }
302 }
303 }
304
305 strHTML += "<b>" + tr("Net amount") + ":</b> " +
306 BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
307
308 //
309 // Message
310 //
311 if (wtx.value_map.count("message") && !wtx.value_map["message"].empty()) {
312 strHTML += "<br><b>" + tr("Message") + ":</b><br>" +
313 GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
314 }
315 if (wtx.value_map.count("comment") && !wtx.value_map["comment"].empty()) {
316 strHTML += "<br><b>" + tr("Comment") + ":</b><br>" +
317 GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
318 }
319 strHTML +=
320 "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxID() + "<br>";
321 strHTML += "<b>" + tr("Transaction total size") + ":</b> " +
322 QString::number(wtx.tx->GetTotalSize()) + " bytes<br>";
323 strHTML += "<b>" + tr("Output index") + ":</b> " +
324 QString::number(rec->getOutputIndex()) + "<br>";
325
326 // Message from normal bitcoincash:URI (bitcoincash:123...?message=example)
327 for (const std::pair<std::string, std::string> &r : orderForm) {
328 if (r.first == "Message") {
329 strHTML += "<br><b>" + tr("Message") + ":</b><br>" +
330 GUIUtil::HtmlEscape(r.second, true) + "<br>";
331 }
332
333 //
334 // PaymentRequest info:
335 //
336 if (r.first == "PaymentRequest") {
337 QString merchant;
338#ifdef ENABLE_BIP70
340 req.parse(
341 QByteArray::fromRawData(r.second.data(), r.second.size()));
342 if (!req.getMerchant(PaymentServer::getCertStore(), merchant)) {
343 merchant.clear();
344 }
345#else
346 if (!GetPaymentRequestMerchant(r.second, merchant)) {
347 merchant.clear();
348 } else {
349 merchant += tr(" (Certificate was not verified)");
350 }
351#endif
352 if (!merchant.isNull()) {
353 strHTML += "<b>" + tr("Merchant") + ":</b> " +
354 GUIUtil::HtmlEscape(merchant) + "<br>";
355 }
356 }
357 }
358
359 if (wtx.is_coinbase) {
360 quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
361 strHTML +=
362 "<br>" +
363 tr("Generated coins must mature %1 blocks before they can be "
364 "spent. When you generated this block, it was broadcast to the "
365 "network to be added to the block chain. If it fails to get "
366 "into the chain, its state will change to \"not accepted\" and "
367 "it won't be spendable. This may occasionally happen if another "
368 "node generates a block within a few seconds of yours.")
369 .arg(QString::number(numBlocksToMaturity)) +
370 "<br>";
371 }
372
373 //
374 // Debug view
375 //
376 if (gArgs.GetBoolArg("-debug", false)) {
377 strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
378 for (const CTxIn &txin : wtx.tx->vin) {
379 if (wallet.txinIsMine(txin)) {
380 strHTML += "<b>" + tr("Debit") + ":</b> " +
382 unit, -wallet.getDebit(txin, ISMINE_ALL)) +
383 "<br>";
384 }
385 }
386 for (const CTxOut &txout : wtx.tx->vout) {
387 if (wallet.txoutIsMine(txout)) {
388 strHTML += "<b>" + tr("Credit") + ":</b> " +
390 unit, wallet.getCredit(txout, ISMINE_ALL)) +
391 "<br>";
392 }
393 }
394
395 strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
396 strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
397
398 strHTML += "<br><b>" + tr("Inputs") + ":</b>";
399 strHTML += "<ul>";
400
401 for (const CTxIn &txin : wtx.tx->vin) {
402 COutPoint prevout = txin.prevout;
403
404 if (auto prev{node.getUnspentOutput(prevout)}) {
405 strHTML += "<li>";
406 const CTxOut &vout = prev->GetTxOut();
407 CTxDestination address;
408 if (ExtractDestination(vout.scriptPubKey, address)) {
409 std::string name;
410 if (wallet.getAddress(address, &name,
411 /* is_mine= */ nullptr,
412 /* purpose= */ nullptr) &&
413 !name.empty()) {
414 strHTML += GUIUtil::HtmlEscape(name) + " ";
415 }
416 strHTML += QString::fromStdString(
417 EncodeCashAddr(address, wallet.getChainParams()));
418 }
419 strHTML = strHTML + " " + tr("Amount") + "=" +
421 strHTML = strHTML + " IsMine=" +
422 (wallet.txoutIsMine(vout) & ISMINE_SPENDABLE
423 ? tr("true")
424 : tr("false")) +
425 "</li>";
426 strHTML = strHTML + " IsWatchOnly=" +
427 (wallet.txoutIsMine(vout) & ISMINE_WATCH_ONLY
428 ? tr("true")
429 : tr("false")) +
430 "</li>";
431 }
432 }
433
434 strHTML += "</ul>";
435 }
436
437 strHTML += "</font></html>";
438 return strHTML;
439}
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
Definition: addresstype.cpp:33
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
Definition: addresstype.h:54
ArgsManager gArgs
Definition: args.cpp:39
std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams &params)
Definition: cashaddrenc.cpp:91
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: args.cpp:524
static QString formatHtmlWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
An output of a transaction.
Definition: transaction.h:128
CScript scriptPubKey
Definition: transaction.h:131
Amount nValue
Definition: transaction.h:130
bool getMerchant(X509_STORE *certStore, QString &merchant) const
bool parse(const QByteArray &data)
static QString toHTML(interfaces::Node &node, interfaces::Wallet &wallet, TransactionRecord *rec, int unit)
static QString FormatTxStatus(const interfaces::WalletTx &wtx, const interfaces::WalletTxStatus &status, bool inMempool, int numBlocks)
UI model for a transaction.
int getOutputIndex() const
Return the output index of the subtransaction
QString getTxID() const
Return the unique identifier for this transaction (part)
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:59
Interface for accessing a wallet.
Definition: wallet.h:63
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule).
Definition: consensus.h:32
isminetype
IsMine() return codes.
Definition: ismine.h:18
@ ISMINE_ALL
Definition: ismine.h:23
@ ISMINE_SPENDABLE
Definition: ismine.h:21
@ ISMINE_WATCH_ONLY
Definition: ismine.h:20
CTxDestination DecodeDestination(const std::string &addr, const CChainParams &params)
Definition: key_io.cpp:174
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:261
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:84
std::vector< std::pair< std::string, std::string > > WalletOrderForm
Definition: wallet.h:59
Definition: messages.h:12
const char * name
Definition: rest.cpp:46
Definition: amount.h:23
static constexpr Amount zero() noexcept
Definition: amount.h:36
std::vector< isminetype > txout_is_mine
Definition: wallet.h:380
CTransactionRef tx
Definition: wallet.h:378
std::map< std::string, std::string > value_map
Definition: wallet.h:387
std::vector< isminetype > txin_is_mine
Definition: wallet.h:379
Updated transaction status.
Definition: wallet.h:392
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)