Bitcoin Core 22.99.0
P2P Digital Currency
transactiondesc.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2019 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
7#endif
8
10
11#include <qt/bitcoinunits.h>
12#include <qt/guiutil.h>
13#include <qt/paymentserver.h>
15
16#include <consensus/consensus.h>
17#include <interfaces/node.h>
18#include <interfaces/wallet.h>
19#include <key_io.h>
20#include <policy/policy.h>
21#include <script/script.h>
22#include <util/system.h>
23#include <validation.h>
24#include <wallet/ismine.h>
25
26#include <stdint.h>
27#include <string>
28
29#include <QLatin1String>
30
31QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks)
32{
33 if (!status.is_final)
34 {
35 if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD)
36 return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - numBlocks);
37 else
38 return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime));
39 }
40 else
41 {
42 int nDepth = status.depth_in_main_chain;
43 if (nDepth < 0) {
44 return tr("conflicted with a transaction with %1 confirmations").arg(-nDepth);
45 } else if (nDepth == 0) {
46 const QString abandoned{status.is_abandoned ? QLatin1String(", ") + tr("abandoned") : QString()};
47 return tr("0/unconfirmed, %1").arg(inMempool ? tr("in memory pool") : tr("not in memory pool")) + abandoned;
48 } else if (nDepth < 6) {
49 return tr("%1/unconfirmed").arg(nDepth);
50 } else {
51 return tr("%1 confirmations").arg(nDepth);
52 }
53 }
54}
55
56// Takes an encoded PaymentRequest as a string and tries to find the Common Name of the X.509 certificate
57// used to sign the PaymentRequest.
58bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant)
59{
60 // Search for the supported pki type strings
61 if (pr.find(std::string({0x12, 0x0b}) + "x509+sha256") != std::string::npos || pr.find(std::string({0x12, 0x09}) + "x509+sha1") != std::string::npos) {
62 // We want the common name of the Subject of the cert. This should be the second occurrence
63 // of the bytes 0x0603550403. The first occurrence of those is the common name of the issuer.
64 // After those bytes will be either 0x13 or 0x0C, then length, then either the ascii or utf8
65 // string with the common name which is the merchant name
66 size_t cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03});
67 if (cn_pos != std::string::npos) {
68 cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03}, cn_pos + 5);
69 if (cn_pos != std::string::npos) {
70 cn_pos += 5;
71 if (pr[cn_pos] == 0x13 || pr[cn_pos] == 0x0c) {
72 cn_pos++; // Consume the type
73 int str_len = pr[cn_pos];
74 cn_pos++; // Consume the string length
75 merchant = QString::fromUtf8(pr.data() + cn_pos, str_len);
76 return true;
77 }
78 }
79 }
80 }
81 return false;
82}
83
85{
86 int numBlocks;
89 bool inMempool;
90 interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, 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 CAmount nCredit = wtx.credit;
99 CAmount nDebit = wtx.debit;
100 CAmount nNet = nCredit - nDebit;
101
102 strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks);
103 strHTML += "<br>";
104
105 strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
106
107 //
108 // From
109 //
110 if (wtx.is_coinbase)
111 {
112 strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
113 }
114 else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty())
115 {
116 // Online transaction
117 strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
118 }
119 else
120 {
121 // Offline transaction
122 if (nNet > 0)
123 {
124 // Credit
126 if (IsValidDestination(address)) {
127 std::string name;
128 isminetype ismine;
129 if (wallet.getAddress(address, &name, &ismine, /* purpose= */ nullptr))
130 {
131 strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
132 strHTML += "<b>" + tr("To") + ":</b> ";
133 strHTML += GUIUtil::HtmlEscape(rec->address);
134 QString addressOwned = ismine == ISMINE_SPENDABLE ? tr("own address") : tr("watch-only");
135 if (!name.empty())
136 strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
137 else
138 strHTML += " (" + addressOwned + ")";
139 strHTML += "<br>";
140 }
141 }
142 }
143 }
144
145 //
146 // To
147 //
148 if (wtx.value_map.count("to") && !wtx.value_map["to"].empty())
149 {
150 // Online transaction
151 std::string strAddress = wtx.value_map["to"];
152 strHTML += "<b>" + tr("To") + ":</b> ";
153 CTxDestination dest = DecodeDestination(strAddress);
154 std::string name;
155 if (wallet.getAddress(
156 dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
157 strHTML += GUIUtil::HtmlEscape(name) + " ";
158 strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
159 }
160
161 //
162 // Amount
163 //
164 if (wtx.is_coinbase && nCredit == 0)
165 {
166 //
167 // Coinbase
168 //
169 CAmount nUnmatured = 0;
170 for (const CTxOut& txout : wtx.tx->vout)
171 nUnmatured += wallet.getCredit(txout, ISMINE_ALL);
172 strHTML += "<b>" + tr("Credit") + ":</b> ";
173 if (status.is_in_main_chain)
174 strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
175 else
176 strHTML += "(" + tr("not accepted") + ")";
177 strHTML += "<br>";
178 }
179 else if (nNet > 0)
180 {
181 //
182 // Credit
183 //
184 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
185 }
186 else
187 {
188 isminetype fAllFromMe = ISMINE_SPENDABLE;
189 for (const isminetype mine : wtx.txin_is_mine)
190 {
191 if(fAllFromMe > mine) fAllFromMe = mine;
192 }
193
194 isminetype fAllToMe = ISMINE_SPENDABLE;
195 for (const isminetype mine : wtx.txout_is_mine)
196 {
197 if(fAllToMe > mine) fAllToMe = mine;
198 }
199
200 if (fAllFromMe)
201 {
202 if(fAllFromMe & ISMINE_WATCH_ONLY)
203 strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
204
205 //
206 // Debit
207 //
208 auto mine = wtx.txout_is_mine.begin();
209 for (const CTxOut& txout : wtx.tx->vout)
210 {
211 // Ignore change
212 isminetype toSelf = *(mine++);
213 if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE))
214 continue;
215
216 if (!wtx.value_map.count("to") || wtx.value_map["to"].empty())
217 {
218 // Offline transaction
219 CTxDestination address;
220 if (ExtractDestination(txout.scriptPubKey, address))
221 {
222 strHTML += "<b>" + tr("To") + ":</b> ";
223 std::string name;
224 if (wallet.getAddress(
225 address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
226 strHTML += GUIUtil::HtmlEscape(name) + " ";
227 strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
228 if(toSelf == ISMINE_SPENDABLE)
229 strHTML += " (own address)";
230 else if(toSelf & ISMINE_WATCH_ONLY)
231 strHTML += " (watch-only)";
232 strHTML += "<br>";
233 }
234 }
235
236 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
237 if(toSelf)
238 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
239 }
240
241 if (fAllToMe)
242 {
243 // Payment to self
244 CAmount nChange = wtx.change;
245 CAmount nValue = nCredit - nChange;
246 strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
247 strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
248 }
249
250 CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
251 if (nTxFee > 0)
252 strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
253 }
254 else
255 {
256 //
257 // Mixed debit transaction
258 //
259 auto mine = wtx.txin_is_mine.begin();
260 for (const CTxIn& txin : wtx.tx->vin) {
261 if (*(mine++)) {
262 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
263 }
264 }
265 mine = wtx.txout_is_mine.begin();
266 for (const CTxOut& txout : wtx.tx->vout) {
267 if (*(mine++)) {
268 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
269 }
270 }
271 }
272 }
273
274 strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
275
276 //
277 // Message
278 //
279 if (wtx.value_map.count("message") && !wtx.value_map["message"].empty())
280 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
281 if (wtx.value_map.count("comment") && !wtx.value_map["comment"].empty())
282 strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
283
284 strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
285 strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->GetTotalSize()) + " bytes<br>";
286 strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
287 strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
288
289 // Message from normal bitcoin:URI (bitcoin:123...?message=example)
290 for (const std::pair<std::string, std::string>& r : orderForm) {
291 if (r.first == "Message")
292 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
293
294 //
295 // PaymentRequest info:
296 //
297 if (r.first == "PaymentRequest")
298 {
299 QString merchant;
300 if (!GetPaymentRequestMerchant(r.second, merchant)) {
301 merchant.clear();
302 } else {
303 merchant += tr(" (Certificate was not verified)");
304 }
305 if (!merchant.isNull()) {
306 strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
307 }
308 }
309 }
310
311 if (wtx.is_coinbase)
312 {
313 quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
314 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>";
315 }
316
317 //
318 // Debug view
319 //
320 if (node.getLogCategories() != BCLog::NONE)
321 {
322 strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
323 for (const CTxIn& txin : wtx.tx->vin)
324 if(wallet.txinIsMine(txin))
325 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
326 for (const CTxOut& txout : wtx.tx->vout)
327 if(wallet.txoutIsMine(txout))
328 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
329
330 strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
331 strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
332
333 strHTML += "<br><b>" + tr("Inputs") + ":</b>";
334 strHTML += "<ul>";
335
336 for (const CTxIn& txin : wtx.tx->vin)
337 {
338 COutPoint prevout = txin.prevout;
339
340 Coin prev;
341 if(node.getUnspentOutput(prevout, prev))
342 {
343 {
344 strHTML += "<li>";
345 const CTxOut &vout = prev.out;
346 CTxDestination address;
347 if (ExtractDestination(vout.scriptPubKey, address))
348 {
349 std::string name;
350 if (wallet.getAddress(address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
351 strHTML += GUIUtil::HtmlEscape(name) + " ";
352 strHTML += QString::fromStdString(EncodeDestination(address));
353 }
354 strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
355 strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
356 strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
357 }
358 }
359 }
360
361 strHTML += "</ul>";
362 }
363
364 strHTML += "</font></html>";
365 return strHTML;
366}
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:27
An input of a transaction.
Definition: transaction.h:66
COutPoint prevout
Definition: transaction.h:68
An output of a transaction.
Definition: transaction.h:129
CScript scriptPubKey
Definition: transaction.h:132
CAmount nValue
Definition: transaction.h:131
A UTXO entry.
Definition: coins.h:31
CTxOut out
unspent transaction output
Definition: coins.h:34
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 getTxHash() const
Return the unique identifier for this transaction (part)
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:55
Interface for accessing a wallet.
Definition: wallet.h:53
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule)
Definition: consensus.h:19
isminetype
IsMine() return codes, which depend on ScriptPubKeyMan implementation.
Definition: ismine.h:39
@ ISMINE_ALL
Definition: ismine.h:44
@ ISMINE_SPENDABLE
Definition: ismine.h:42
@ ISMINE_WATCH_ONLY
Definition: ismine.h:41
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:256
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg)
Definition: key_io.cpp:261
@ NONE
Definition: logging.h:37
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:233
QString dateTimeStr(const QDateTime &date)
Definition: guiutil.cpp:78
std::vector< std::pair< std::string, std::string > > WalletOrderForm
Definition: wallet.h:48
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:285
const char * name
Definition: rest.cpp:43
static const unsigned int LOCKTIME_THRESHOLD
Definition: script.h:40
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:213
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:332
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:157
std::vector< isminetype > txout_is_mine
Definition: wallet.h:379
CTransactionRef tx
Definition: wallet.h:377
std::map< std::string, std::string > value_map
Definition: wallet.h:386
std::vector< isminetype > txin_is_mine
Definition: wallet.h:378
Updated transaction status.
Definition: wallet.h:392
bool GetPaymentRequestMerchant(const std::string &pr, QString &merchant)