mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-10 15:46:48 -04:00

0e5dedbc9e
qt/wallettests: sort includes (William Casarin)0554251d66
qt: Skip displayUnitChanged signal if unit is not actually changed (Hennadii Stepanov)ffbc2fe459
qt, refactor: Remove default cases for scoped enum (Hennadii Stepanov)152d5bad50
qt, refactor: Remove BitcoinUnits::valid function (Hennadii Stepanov)aa23960fdf
qt, refactor: Make BitcoinUnits::Unit a scoped enum (Hennadii Stepanov)75832fdc37
qt: Use QVariant instead of int for BitcoinUnit in QSettings (Hennadii Stepanov) Pull request description: This is a rebased version of #60 Since Qt 5.5 there are [means](https://doc.qt.io/qt-5/qobject.html#Q_ENUM) to register an enum type with the meta-object system (such enum still lacks an ability to interact with [QSettings::setValue()](https://doc.qt.io/qt-5/qsettings.html#setValue) and [QSettings::value()](https://doc.qt.io/qt-5/qsettings.html#value) without defined stream operators). In order to reduce global namespace polluting and to force strong type checking, this PR makes BitcoinUnits::Unit a scoped enum (typedef BitcoinUnits::Unit BitcoinUnit;). No behavior change. ACKs for top commit: jonatack: ACK0e5dedbc9e
, review and debug build of each commit after rebase on current master, lightly tested running the GUI, changing units a few times, and verifying persistence after restarting promag: Code review ACK0e5dedbc9e
Tree-SHA512: 39ec0d7e4f0b9b25be287888121a8db6b282339674e37ec3a3554da63a9e22d6fe079e8310ca289b2a0356a19b3c7e55afa17d09dd34e0f222177f603bb053a3
360 lines
14 KiB
C++
360 lines
14 KiB
C++
// Copyright (c) 2011-2021 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config/bitcoin-config.h>
|
|
#endif
|
|
|
|
#include <qt/transactiondesc.h>
|
|
|
|
#include <qt/bitcoinunits.h>
|
|
#include <qt/guiutil.h>
|
|
#include <qt/paymentserver.h>
|
|
#include <qt/transactionrecord.h>
|
|
|
|
#include <consensus/consensus.h>
|
|
#include <interfaces/node.h>
|
|
#include <interfaces/wallet.h>
|
|
#include <key_io.h>
|
|
#include <policy/policy.h>
|
|
#include <util/system.h>
|
|
#include <validation.h>
|
|
#include <wallet/ismine.h>
|
|
|
|
#include <stdint.h>
|
|
#include <string>
|
|
|
|
#include <QLatin1String>
|
|
|
|
using wallet::ISMINE_ALL;
|
|
using wallet::ISMINE_SPENDABLE;
|
|
using wallet::ISMINE_WATCH_ONLY;
|
|
using wallet::isminetype;
|
|
|
|
QString TransactionDesc::FormatTxStatus(const interfaces::WalletTxStatus& status, bool inMempool)
|
|
{
|
|
int depth = status.depth_in_main_chain;
|
|
if (depth < 0) {
|
|
return tr("conflicted with a transaction with %1 confirmations").arg(-depth);
|
|
} else if (depth == 0) {
|
|
const QString abandoned{status.is_abandoned ? QLatin1String(", ") + tr("abandoned") : QString()};
|
|
return tr("0/unconfirmed, %1").arg(inMempool ? tr("in memory pool") : tr("not in memory pool")) + abandoned;
|
|
} else if (depth < 6) {
|
|
return tr("%1/unconfirmed").arg(depth);
|
|
} else {
|
|
return tr("%1 confirmations").arg(depth);
|
|
}
|
|
}
|
|
|
|
// Takes an encoded PaymentRequest as a string and tries to find the Common Name of the X.509 certificate
|
|
// used to sign the PaymentRequest.
|
|
bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant)
|
|
{
|
|
// Search for the supported pki type strings
|
|
if (pr.find(std::string({0x12, 0x0b}) + "x509+sha256") != std::string::npos || pr.find(std::string({0x12, 0x09}) + "x509+sha1") != std::string::npos) {
|
|
// We want the common name of the Subject of the cert. This should be the second occurrence
|
|
// of the bytes 0x0603550403. The first occurrence of those is the common name of the issuer.
|
|
// After those bytes will be either 0x13 or 0x0C, then length, then either the ascii or utf8
|
|
// string with the common name which is the merchant name
|
|
size_t cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03});
|
|
if (cn_pos != std::string::npos) {
|
|
cn_pos = pr.find({0x06, 0x03, 0x55, 0x04, 0x03}, cn_pos + 5);
|
|
if (cn_pos != std::string::npos) {
|
|
cn_pos += 5;
|
|
if (pr[cn_pos] == 0x13 || pr[cn_pos] == 0x0c) {
|
|
cn_pos++; // Consume the type
|
|
int str_len = pr[cn_pos];
|
|
cn_pos++; // Consume the string length
|
|
merchant = QString::fromUtf8(pr.data() + cn_pos, str_len);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit)
|
|
{
|
|
int numBlocks;
|
|
interfaces::WalletTxStatus status;
|
|
interfaces::WalletOrderForm orderForm;
|
|
bool inMempool;
|
|
interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks);
|
|
|
|
QString strHTML;
|
|
|
|
strHTML.reserve(4000);
|
|
strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
|
|
|
|
int64_t nTime = wtx.time;
|
|
CAmount nCredit = wtx.credit;
|
|
CAmount nDebit = wtx.debit;
|
|
CAmount nNet = nCredit - nDebit;
|
|
|
|
strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool);
|
|
strHTML += "<br>";
|
|
|
|
strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
|
|
|
|
//
|
|
// From
|
|
//
|
|
if (wtx.is_coinbase)
|
|
{
|
|
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
|
|
}
|
|
else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty())
|
|
{
|
|
// Online transaction
|
|
strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
|
|
}
|
|
else
|
|
{
|
|
// Offline transaction
|
|
if (nNet > 0)
|
|
{
|
|
// Credit
|
|
CTxDestination address = DecodeDestination(rec->address);
|
|
if (IsValidDestination(address)) {
|
|
std::string name;
|
|
isminetype ismine;
|
|
if (wallet.getAddress(address, &name, &ismine, /* purpose= */ nullptr))
|
|
{
|
|
strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
|
|
strHTML += "<b>" + tr("To") + ":</b> ";
|
|
strHTML += GUIUtil::HtmlEscape(rec->address);
|
|
QString addressOwned = ismine == ISMINE_SPENDABLE ? tr("own address") : tr("watch-only");
|
|
if (!name.empty())
|
|
strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(name) + ")";
|
|
else
|
|
strHTML += " (" + addressOwned + ")";
|
|
strHTML += "<br>";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// To
|
|
//
|
|
if (wtx.value_map.count("to") && !wtx.value_map["to"].empty())
|
|
{
|
|
// Online transaction
|
|
std::string strAddress = wtx.value_map["to"];
|
|
strHTML += "<b>" + tr("To") + ":</b> ";
|
|
CTxDestination dest = DecodeDestination(strAddress);
|
|
std::string name;
|
|
if (wallet.getAddress(
|
|
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
|
|
strHTML += GUIUtil::HtmlEscape(name) + " ";
|
|
strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
|
|
}
|
|
|
|
//
|
|
// Amount
|
|
//
|
|
if (wtx.is_coinbase && nCredit == 0)
|
|
{
|
|
//
|
|
// Coinbase
|
|
//
|
|
CAmount nUnmatured = 0;
|
|
for (const CTxOut& txout : wtx.tx->vout)
|
|
nUnmatured += wallet.getCredit(txout, ISMINE_ALL);
|
|
strHTML += "<b>" + tr("Credit") + ":</b> ";
|
|
if (status.is_in_main_chain)
|
|
strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
|
|
else
|
|
strHTML += "(" + tr("not accepted") + ")";
|
|
strHTML += "<br>";
|
|
}
|
|
else if (nNet > 0)
|
|
{
|
|
//
|
|
// Credit
|
|
//
|
|
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
|
|
}
|
|
else
|
|
{
|
|
isminetype fAllFromMe = ISMINE_SPENDABLE;
|
|
for (const isminetype mine : wtx.txin_is_mine)
|
|
{
|
|
if(fAllFromMe > mine) fAllFromMe = mine;
|
|
}
|
|
|
|
isminetype fAllToMe = ISMINE_SPENDABLE;
|
|
for (const isminetype mine : wtx.txout_is_mine)
|
|
{
|
|
if(fAllToMe > mine) fAllToMe = mine;
|
|
}
|
|
|
|
if (fAllFromMe)
|
|
{
|
|
if(fAllFromMe & ISMINE_WATCH_ONLY)
|
|
strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
|
|
|
|
//
|
|
// Debit
|
|
//
|
|
auto mine = wtx.txout_is_mine.begin();
|
|
for (const CTxOut& txout : wtx.tx->vout)
|
|
{
|
|
// Ignore change
|
|
isminetype toSelf = *(mine++);
|
|
if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE))
|
|
continue;
|
|
|
|
if (!wtx.value_map.count("to") || wtx.value_map["to"].empty())
|
|
{
|
|
// Offline transaction
|
|
CTxDestination address;
|
|
if (ExtractDestination(txout.scriptPubKey, address))
|
|
{
|
|
strHTML += "<b>" + tr("To") + ":</b> ";
|
|
std::string name;
|
|
if (wallet.getAddress(
|
|
address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
|
|
strHTML += GUIUtil::HtmlEscape(name) + " ";
|
|
strHTML += GUIUtil::HtmlEscape(EncodeDestination(address));
|
|
if(toSelf == ISMINE_SPENDABLE)
|
|
strHTML += " (own address)";
|
|
else if(toSelf & ISMINE_WATCH_ONLY)
|
|
strHTML += " (watch-only)";
|
|
strHTML += "<br>";
|
|
}
|
|
}
|
|
|
|
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
|
|
if(toSelf)
|
|
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
|
|
}
|
|
|
|
if (fAllToMe)
|
|
{
|
|
// Payment to self
|
|
CAmount nChange = wtx.change;
|
|
CAmount nValue = nCredit - nChange;
|
|
strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
|
|
strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
|
|
}
|
|
|
|
CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
|
|
if (nTxFee > 0)
|
|
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Mixed debit transaction
|
|
//
|
|
auto mine = wtx.txin_is_mine.begin();
|
|
for (const CTxIn& txin : wtx.tx->vin) {
|
|
if (*(mine++)) {
|
|
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
|
|
}
|
|
}
|
|
mine = wtx.txout_is_mine.begin();
|
|
for (const CTxOut& txout : wtx.tx->vout) {
|
|
if (*(mine++)) {
|
|
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
|
|
|
|
//
|
|
// Message
|
|
//
|
|
if (wtx.value_map.count("message") && !wtx.value_map["message"].empty())
|
|
strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["message"], true) + "<br>";
|
|
if (wtx.value_map.count("comment") && !wtx.value_map["comment"].empty())
|
|
strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.value_map["comment"], true) + "<br>";
|
|
|
|
strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxHash() + "<br>";
|
|
strHTML += "<b>" + tr("Transaction total size") + ":</b> " + QString::number(wtx.tx->GetTotalSize()) + " bytes<br>";
|
|
strHTML += "<b>" + tr("Transaction virtual size") + ":</b> " + QString::number(GetVirtualTransactionSize(*wtx.tx)) + " bytes<br>";
|
|
strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
|
|
|
|
// Message from normal bitcoin:URI (bitcoin:123...?message=example)
|
|
for (const std::pair<std::string, std::string>& r : orderForm) {
|
|
if (r.first == "Message")
|
|
strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
|
|
|
|
//
|
|
// PaymentRequest info:
|
|
//
|
|
if (r.first == "PaymentRequest")
|
|
{
|
|
QString merchant;
|
|
if (!GetPaymentRequestMerchant(r.second, merchant)) {
|
|
merchant.clear();
|
|
} else {
|
|
merchant += tr(" (Certificate was not verified)");
|
|
}
|
|
if (!merchant.isNull()) {
|
|
strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wtx.is_coinbase)
|
|
{
|
|
quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
|
|
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>";
|
|
}
|
|
|
|
//
|
|
// Debug view
|
|
//
|
|
if (node.getLogCategories() != BCLog::NONE)
|
|
{
|
|
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
|
|
for (const CTxIn& txin : wtx.tx->vin)
|
|
if(wallet.txinIsMine(txin))
|
|
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
|
|
for (const CTxOut& txout : wtx.tx->vout)
|
|
if(wallet.txoutIsMine(txout))
|
|
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
|
|
|
|
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
|
|
strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
|
|
|
|
strHTML += "<br><b>" + tr("Inputs") + ":</b>";
|
|
strHTML += "<ul>";
|
|
|
|
for (const CTxIn& txin : wtx.tx->vin)
|
|
{
|
|
COutPoint prevout = txin.prevout;
|
|
|
|
Coin prev;
|
|
if(node.getUnspentOutput(prevout, prev))
|
|
{
|
|
{
|
|
strHTML += "<li>";
|
|
const CTxOut &vout = prev.out;
|
|
CTxDestination address;
|
|
if (ExtractDestination(vout.scriptPubKey, address))
|
|
{
|
|
std::string name;
|
|
if (wallet.getAddress(address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
|
|
strHTML += GUIUtil::HtmlEscape(name) + " ";
|
|
strHTML += QString::fromStdString(EncodeDestination(address));
|
|
}
|
|
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
|
|
strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
|
|
strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
|
|
}
|
|
}
|
|
}
|
|
|
|
strHTML += "</ul>";
|
|
}
|
|
|
|
strHTML += "</font></html>";
|
|
return strHTML;
|
|
}
|