transactiondesc.cpp
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 5 #include <qt/transactiondesc.h> 6 7 #include <qt/bitcoinunits.h> 8 #include <qt/guiutil.h> 9 #include <qt/paymentserver.h> 10 #include <qt/transactionrecord.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 26 QString 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. 71 bool 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 97 QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit) 98 { 99 int numBlocks; 100 interfaces::WalletTxStatus status; 101 interfaces::WalletOrderForm orderForm; 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.contains("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 138 CTxDestination address = DecodeDestination(rec->address); 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.contains("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.contains("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.contains("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.contains("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->ComputeTotalSize()) + " 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 }