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