* Lowering MWEB feerate

* Transaction display & amount calculation fixes for pegouts
* Crash fix during CreateTx
* Bump version to 0.21.2rc4
This commit is contained in:
David Burkett 2022-03-07 01:15:29 -05:00 committed by Loshan T
parent d3de60d46f
commit df12de6bf0
15 changed files with 369 additions and 161 deletions

View File

@ -3,7 +3,7 @@ define(_CLIENT_VERSION_MAJOR, 0)
define(_CLIENT_VERSION_MINOR, 21)
define(_CLIENT_VERSION_REVISION, 2)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_RC, 3)
define(_CLIENT_VERSION_RC, 4)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2022)
define(_COPYRIGHT_HOLDERS,[The %s developers])

View File

@ -251,13 +251,13 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
UniValue kern(UniValue::VOBJ);
kern.pushKV("kernel_id", kernel.GetKernelID().ToHex());
kern.pushKV("fee", kernel.GetFee());
kern.pushKV("pegin", kernel.GetPegIn());
kern.pushKV("fee", ValueFromAmount(kernel.GetFee()));
kern.pushKV("pegin", ValueFromAmount(kernel.GetPegIn()));
UniValue pegouts(UniValue::VARR);
for (const PegOutCoin& pegout : kernel.GetPegOuts()) {
UniValue uni_pegout(UniValue::VOBJ);
uni_pegout.pushKV("value", pegout.GetAmount());
uni_pegout.pushKV("value", ValueFromAmount(pegout.GetAmount()));
UniValue p(UniValue::VOBJ);
ScriptPubKeyToUniv(pegout.GetScriptPubKey(), p, true);

View File

@ -97,6 +97,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.time = wtx.GetTxTime();
result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase();
result.is_hogex = wtx.IsHogEx();
result.wtx_hash = wtx.GetHash();
//
@ -134,6 +135,11 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.outputs.push_back(MakeWalletTxOut(wallet, wtx, txout));
}
result.pegouts = wtx.tx->mweb_tx.GetPegOuts();
for (const PegOutCoin& pegout : result.pegouts) {
result.pegout_is_mine.emplace_back(wallet.IsMine(DestinationAddr(pegout.GetScriptPubKey())));
}
return result;
}

View File

@ -403,6 +403,7 @@ struct WalletTx
std::vector<isminetype> txin_is_mine;
std::vector<isminetype> txout_is_mine;
std::vector<isminetype> txout_address_is_mine;
std::vector<isminetype> pegout_is_mine;
CAmount credit;
CAmount debit;
CAmount change;
@ -410,9 +411,11 @@ struct WalletTx
int64_t time;
std::map<std::string, std::string> value_map;
bool is_coinbase;
bool is_hogex;
uint256 wtx_hash;
std::vector<CTxInput> inputs;
std::vector<WalletTxOut> outputs;
std::vector<PegOutCoin> pegouts;
};
//! Updated transaction status.

View File

@ -25,7 +25,7 @@ mw::Transaction::CPtr TxBuilder::BuildTx(
// Get input coins
LOG_INFO_F(
"Creating Txs: Inputs({}), pegins({}), pegouts({}), recipient_total({}), fee({})",
"Creating Tx: Inputs({}), pegins({}), pegouts({}), recipient_total({}), fee({})",
TotalAmount(input_coins),
pegin_amount.value_or(0),
pegout_total,

View File

@ -99,14 +99,19 @@ bool Transact::CreateTx(
// Create transaction
std::vector<mw::Coin> input_coins = GetInputCoins(selected_coins);
std::vector<mw::Coin> output_coins;
transaction.mweb_tx = TxBuilder::BuildTx(
input_coins,
receivers,
pegouts,
pegin_amount,
mweb_fee,
output_coins
);
try {
transaction.mweb_tx = TxBuilder::BuildTx(
input_coins,
receivers,
pegouts,
pegin_amount,
mweb_fee,
output_coins
);
} catch (std::exception&) {
return false;
}
if (!output_coins.empty()) {
mweb_wallet->SaveToWallet(output_coins);

View File

@ -7,7 +7,7 @@
#include <tinyformat.h>
static const CAmount BASE_MWEB_FEE = 100'000;
static const CAmount BASE_MWEB_FEE = 1'000;
CFeeRate::CFeeRate(const CAmount& nFeePaid, size_t nBytes_, uint64_t mweb_weight)
: m_nFeePaid(nFeePaid), m_nBytes(nBytes_), m_weight(mweb_weight)

View File

@ -79,10 +79,10 @@ bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant)
QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit)
{
int numBlocks;
interfaces::WalletTxStatus status;
interfaces::WalletOrderForm orderForm;
bool inMempool;
int numBlocks;
interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks);
QString strHTML;
@ -91,39 +91,68 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
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(wtx, status, inMempool, numBlocks);
strHTML += "<br>";
strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks) + "<br>";
strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
strHTML += toHTML_Addresses(wallet, wtx, rec);
strHTML += toHTML_Amounts(wallet, wtx, status, unit);
//
// 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>";
strHTML += toHTML_OrderForm(orderForm);
if (wtx.is_coinbase) // MW: TODO - Include pegout maturity
{
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 += toHTML_Debug(node, wallet, wtx, rec, unit);
}
strHTML += "</font></html>";
return strHTML;
}
QString TransactionDesc::toHTML_Addresses(interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, TransactionRecord* rec)
{
QString strHTML;
//
// From
//
if (wtx.is_coinbase)
{
if (wtx.is_coinbase) {
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
}
else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty())
{
} else if (wtx.value_map.count("from") && !wtx.value_map.at("from").empty()) {
// Online transaction
strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map["from"]) + "<br>";
}
else
{
strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.value_map.at("from")) + "<br>";
} else {
// Offline transaction
if (nNet > 0)
{
CAmount nNet = wtx.credit - wtx.debit;
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))
{
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);
@ -141,104 +170,103 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
//
// To
//
if (wtx.value_map.count("to") && !wtx.value_map["to"].empty())
{
if (wtx.value_map.count("to") && !wtx.value_map.at("to").empty()) {
// Online transaction
std::string strAddress = wtx.value_map["to"];
std::string strAddress = wtx.value_map.at("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())
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) &&
!name.empty())
strHTML += GUIUtil::HtmlEscape(name) + " ";
strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
}
return strHTML;
}
QString TransactionDesc::toHTML_Amounts(interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, int unit)
{
QString strHTML;
CAmount nNet = wtx.credit - wtx.debit;
//
// Amount
//
if (wtx.is_coinbase && nCredit == 0)
if (status.blocks_to_maturity > 0 && wtx.credit == 0) // Blocks to maturity covers coinbase and pegouts (HogEx)
{
//
// Coinbase
// Coinbase & Pegouts (HogEx)
//
CAmount nUnmatured = 0;
for (const interfaces::WalletTxOut& out : wtx.outputs)
nUnmatured += wallet.getCredit(out.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) + ")";
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)
{
} else if (nNet > 0) {
//
// Credit
//
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
}
else
{
} else {
isminetype fAllFromMe = ISMINE_SPENDABLE;
for (const isminetype mine : wtx.txin_is_mine)
{
if(fAllFromMe > mine) fAllFromMe = mine;
for (const isminetype mine : wtx.txin_is_mine) {
if (fAllFromMe > mine) fAllFromMe = mine;
}
// MW: TODO - Pegouts?
isminetype fAllToMe = ISMINE_SPENDABLE;
for (const isminetype mine : wtx.txout_is_mine)
{
if(fAllToMe > mine) fAllToMe = mine;
for (const isminetype mine : wtx.txout_is_mine) {
if (fAllToMe > mine) fAllToMe = mine;
}
if (fAllFromMe)
{
if(fAllFromMe & ISMINE_WATCH_ONLY)
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 interfaces::WalletTxOut& txout : wtx.outputs)
{
for (const interfaces::WalletTxOut& txout : wtx.outputs) {
// Ignore change
isminetype toSelf = *(mine++);
if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE))
continue;
if (!wtx.value_map.count("to") || wtx.value_map["to"].empty())
{
if (!wtx.value_map.count("to") || wtx.value_map.at("to").empty()) {
// Offline transaction
CTxDestination dest;
if (txout.address.ExtractDestination(dest))
{
if (txout.address.ExtractDestination(dest)) {
strHTML += "<b>" + tr("To") + ":</b> ";
std::string name;
if (wallet.getAddress(
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty())
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) &&
!name.empty())
strHTML += GUIUtil::HtmlEscape(name) + " ";
strHTML += GUIUtil::HtmlEscape(txout.address.Encode());
if(toSelf == ISMINE_SPENDABLE)
if (toSelf == ISMINE_SPENDABLE)
strHTML += " (own address)";
else if(toSelf & ISMINE_WATCH_ONLY)
else if (toSelf & ISMINE_WATCH_ONLY)
strHTML += " (watch-only)";
strHTML += "<br>";
}
}
// MW: TODO - Pegouts?
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
if(toSelf)
if (toSelf)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
}
if (fAllToMe)
{
if (fAllToMe) {
// Payment to self
CAmount nChange = wtx.change;
CAmount nValue = nCredit - nChange;
CAmount nValue = wtx.credit - wtx.change;
strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
}
@ -246,9 +274,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
CAmount nTxFee = wtx.fee;
if (nTxFee > 0)
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
}
else
{
} else {
//
// Mixed debit transaction
//
@ -264,23 +290,18 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(out.txout, ISMINE_ALL)) + "<br>";
}
}
// MW: TODO - Pegouts?
}
}
strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
return strHTML;
}
//
// 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>";
QString TransactionDesc::toHTML_OrderForm(const interfaces::WalletOrderForm& orderForm)
{
QString strHTML;
// Message from normal bitcoin:URI (bitcoin:123...?message=example)
for (const std::pair<std::string, std::string>& r : orderForm) {
@ -290,8 +311,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
//
// PaymentRequest info:
//
if (r.first == "PaymentRequest")
{
if (r.first == "PaymentRequest") {
QString merchant;
if (!GetPaymentRequestMerchant(r.second, merchant)) {
merchant.clear();
@ -304,54 +324,59 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
}
}
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>";
return strHTML;
}
QString TransactionDesc::toHTML_Debug(interfaces::Node& node, interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, TransactionRecord* rec, int unit)
{
QString strHTML = "<hr><br>" + tr("Debug information") + "<br><br>";
for (const CTxInput& txin : wtx.inputs) {
if (wallet.txinIsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
}
for (const interfaces::WalletTxOut& out : wtx.outputs)
if (wallet.txoutIsMine(out.txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(out.txout, ISMINE_ALL)) + "<br>";
//
// Debug view
//
if (node.getLogCategories() != BCLog::NONE)
{
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
for (const CTxInput& txin : wtx.inputs)
if(wallet.txinIsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
for (const interfaces::WalletTxOut& out : wtx.outputs)
if(wallet.txoutIsMine(out.txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(out.txout, ISMINE_ALL)) + "<br>";
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
strHTML += "<br><b>" + tr("Inputs") + ":</b>";
strHTML += "<ul>";
strHTML += "<br><b>" + tr("Inputs") + ":</b>";
strHTML += "<ul>";
for (const CTxInput& txin : wtx.inputs)
{
CTxOutput prevout;
if (node.getUnspentOutput(txin.GetIndex(), prevout))
{
strHTML += "<li>";
for (const CTxInput& txin : wtx.inputs) {
CTxOutput prevout;
if (node.getUnspentOutput(txin.GetIndex(), prevout)) {
strHTML += "<li>";
CTxDestination address;
if (wallet.extractOutputDestination(prevout, 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, wallet.getValue(prevout));
strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(prevout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(prevout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
} else {
strHTML += "<li>";
if (txin.IsMWEB()) {
CTxOutput prevout(txin.ToMWEB());
CTxDestination address;
if (wallet.extractOutputDestination(prevout, address))
{
if (wallet.extractOutputDestination(prevout, 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, wallet.getValue(prevout));
strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(prevout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(prevout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
}
}
strHTML += "</ul>";
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getValue(prevout));
}
strHTML = strHTML + " IsMine=" + (wallet.txinIsMine(txin) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet.txinIsMine(txin) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
}
}
strHTML += "</font></html>";
strHTML += "</ul>";
return strHTML;
}
}

View File

@ -15,6 +15,7 @@ class Node;
class Wallet;
struct WalletTx;
struct WalletTxStatus;
using WalletOrderForm = std::vector<std::pair<std::string, std::string>>;
}
/** Provide a human-readable extended HTML description of a transaction.
@ -30,6 +31,11 @@ private:
TransactionDesc() {}
static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks);
static QString toHTML_Addresses(interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, TransactionRecord* rec);
static QString toHTML_Amounts(interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, int unit);
static QString toHTML_OrderForm(const interfaces::WalletOrderForm& orderForm);
static QString toHTML_Debug(interfaces::Node& node, interfaces::Wallet& wallet, const interfaces::WalletTx& wtx, TransactionRecord* rec, int unit);
};
#endif // BITCOIN_QT_TRANSACTIONDESC_H

View File

@ -33,7 +33,7 @@ protected:
{
Q_UNUSED(event);
QSizePolicy sp = sizePolicy();
sp.setHorizontalPolicy(QSizePolicy::Minimum);
sp.setHorizontalPolicy(QSizePolicy::Preferred);
setSizePolicy(sp);
}
};

View File

@ -35,7 +35,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
uint256 hash = wtx.wtx_hash;
std::map<std::string, std::string> mapValue = wtx.value_map;
if (nNet > 0 || wtx.is_coinbase)
if (nNet > 0 || wtx.is_coinbase || wtx.is_hogex)
{
//
// Credit
@ -89,15 +89,26 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
if(fAllToMe > mine) fAllToMe = mine;
}
// MWEB: Check pegouts for fAllToMe
for (const isminetype mine : wtx.pegout_is_mine) {
if (mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if (fAllToMe > mine) fAllToMe = mine;
}
if (fAllFromMe && fAllToMe)
{
// Payment to self
std::string address;
for (auto it = wtx.outputs.begin(); it != wtx.outputs.end(); ++it) {
if (it != wtx.outputs.begin()) address += ", ";
if (!address.empty()) address += ", ";
address += it->address.Encode();
}
for (auto it = wtx.pegouts.begin(); it != wtx.pegouts.end(); ++it) {
if (!address.empty()) address += ", ";
address += DestinationAddr(it->GetScriptPubKey()).Encode();
}
CAmount nChange = wtx.change;
parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, address, -(nDebit - nChange), nCredit - nChange));
parts.last().involvesWatchAddress = involvesWatchAddress; // maybe pass to TransactionRecord as constructor argument
@ -147,6 +158,33 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
parts.append(sub);
}
for (unsigned int nPegout = 0; nPegout < wtx.pegouts.size(); nPegout++) {
const PegOutCoin& pegout = wtx.pegouts[nPegout];
TransactionRecord sub(hash, nTime);
sub.idx = wtx.outputs.size() + nPegout;
sub.involvesWatchAddress = involvesWatchAddress;
if (wtx.pegout_is_mine[nPegout]) {
// Ignore parts sent to self, as this is usually the change
// from a transaction sent back to our own address.
continue;
}
// Sent to Bitcoin Address
sub.type = TransactionRecord::SendToAddress;
sub.address = DestinationAddr(pegout.GetScriptPubKey()).Encode();
CAmount nValue = pegout.GetAmount();
/* Add fee to first output */
if (nTxFee > 0) {
nValue += nTxFee;
nTxFee = 0;
}
sub.debit = -nValue;
parts.append(sub);
}
}
else
{
@ -175,6 +213,10 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
status.depth = wtx.depth_in_main_chain;
status.m_cur_block_hash = block_hash;
if (wtx.is_in_main_chain) {
status.matures_in = wtx.blocks_to_maturity;
}
const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP);
if (up_to_date && !wtx.is_final) {
if (wtx.lock_time < LOCKTIME_THRESHOLD) {
@ -194,11 +236,7 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
{
status.status = TransactionStatus::Immature;
if (wtx.is_in_main_chain)
{
status.matures_in = wtx.blocks_to_maturity;
}
else
if (!wtx.is_in_main_chain)
{
status.status = TransactionStatus::NotAccepted;
}

View File

@ -1038,7 +1038,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
if (fExisted || IsMine(tx) || IsFromMe(tx, boost::none))
{
/* Check if any keys in the wallet keypool that were supposed to be unused
* have appeared in a new transaction. If so, remove those keys from the keypool.
@ -1056,6 +1056,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
}
}
// Loop through pegout scripts
for (const PegOutCoin& pegout : tx.mweb_tx.GetPegOuts()) {
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->MarkUnusedAddresses(DestinationAddr{pegout.GetScriptPubKey()});
}
}
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
return AddToWallet(MakeTransactionRef(tx), /* mweb_wtx_info */ boost::none, confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false);
@ -1483,15 +1490,22 @@ bool CWallet::IsMine(const CTransaction& tx) const
for (const CTxOutput& txout : tx.GetOutputs())
if (IsMine(txout))
return true;
for (const PegOutCoin& pegout : tx.mweb_tx.GetPegOuts()) {
if (IsMine(DestinationAddr{pegout.GetScriptPubKey()})) {
return true;
}
}
return false;
}
bool CWallet::IsFromMe(const CTransaction& tx) const
bool CWallet::IsFromMe(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const
{
return (GetDebit(tx, ISMINE_ALL) > 0);
return (GetDebit(tx, mweb_wtx_info, ISMINE_ALL) > 0);
}
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
CAmount CWallet::GetDebit(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, const isminefilter& filter) const
{
CAmount nDebit = 0;
for (const CTxInput& txin : tx.GetInputs())
@ -1500,6 +1514,13 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co
if (!MoneyRange(nDebit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
if (mweb_wtx_info && mweb_wtx_info->spent_input) {
nDebit += GetDebit(CTxInput{*mweb_wtx_info->spent_input}, filter);
if (!MoneyRange(nDebit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nDebit;
}
@ -1515,7 +1536,7 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co
return true;
}
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
CAmount CWallet::GetCredit(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, const isminefilter& filter) const
{
CAmount nCredit = 0;
for (const CTxOutput& txout : tx.GetOutputs())
@ -1524,10 +1545,37 @@ CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) c
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
if (mweb_wtx_info && mweb_wtx_info->received_coin) {
nCredit += GetCredit(CTxOutput{mweb_wtx_info->received_coin->output_id}, filter);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
bool has_my_inputs = false;
for (const CTxInput& txin : tx.GetInputs()) {
LOCK(cs_wallet);
if (IsMine(txin)) {
has_my_inputs = true;
break;
}
}
if (!has_my_inputs) {
for (const PegOutCoin& pegout : tx.mweb_tx.GetPegOuts()) {
LOCK(cs_wallet);
if (IsMine(DestinationAddr(pegout.GetScriptPubKey()))) {
nCredit += pegout.GetAmount();
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
}
return nCredit;
}
CAmount CWallet::GetChange(const CTransaction& tx) const
CAmount CWallet::GetChange(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const
{
LOCK(cs_wallet);
CAmount nChange = 0;
@ -1537,6 +1585,23 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
if (mweb_wtx_info && mweb_wtx_info->received_coin) {
nChange += GetChange(CTxOutput{mweb_wtx_info->received_coin->output_id});
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
for (const PegOutCoin& pegout : tx.mweb_tx.GetPegOuts()) {
LOCK(cs_wallet);
if (IsChange(DestinationAddr{pegout.GetScriptPubKey()})) {
nChange += pegout.GetAmount();
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
return nChange;
}
@ -2061,7 +2126,7 @@ CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter
{
auto& amount = m_amounts[type];
if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); // MW: TODO - Need to support partial mweb info
amount.Set(filter, (type == DEBIT) ? pwallet->GetDebit(*tx, mweb_wtx_info, filter) : pwallet->GetCredit(*tx, mweb_wtx_info, filter));
m_is_cache_empty = false;
}
return amount.m_value[filter];
@ -2069,7 +2134,7 @@ CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter
CAmount CWalletTx::GetDebit(const isminefilter& filter) const
{
if (tx->GetInputs().empty())
if (GetInputs().empty())
return 0;
CAmount debit = 0;
@ -2156,7 +2221,7 @@ CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
nChangeCached = pwallet->GetChange(*tx);
nChangeCached = pwallet->GetChange(*tx, mweb_wtx_info);
fChangeCached = true;
return nChangeCached;
}
@ -2175,6 +2240,10 @@ CAmount CWalletTx::GetFee(const isminefilter& filter) const
}
}
for (const PegOutCoin& pegout : tx->mweb_tx.GetPegOuts()) {
nValueOut += pegout.GetAmount();
}
nFee = nDebit - nValueOut;
}
@ -3911,8 +3980,7 @@ int CWalletTx::GetBlocksToMaturity() const
{
if (!IsCoinBase() && !IsHogEx())
return 0;
int chain_depth = GetDepthInMainChain();
assert(chain_depth >= 0); // coinbase tx should not be conflicted
int chain_depth = std::max(0, GetDepthInMainChain());
if (IsCoinBase()) {
return std::max(0, (COINBASE_MATURITY + 1) - chain_depth);
} else {

View File

@ -1164,12 +1164,12 @@ public:
CAmount GetChange(const CTxOutput& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
bool IsFromMe(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const;
CAmount GetDebit(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, const isminefilter& filter) const;
/** Returns whether all of the inputs match the filter */
bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
CAmount GetCredit(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const;
void chainStateFlushed(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet);

View File

@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Verify that we can pegout all coins in the MWEB"""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.ltc_util import get_hog_addr_txout, setup_mweb_chain

View File

@ -11,8 +11,8 @@ from test_framework.util import assert_equal
class MWEBWalletBasicTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [['-whitelist=noban@127.0.0.1'],[]] # immediate tx relay
self.num_nodes = 3
self.extra_args = [['-whitelist=noban@127.0.0.1'],['-whitelist=noban@127.0.0.1'],[]] # immediate tx relay
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@ -20,14 +20,15 @@ class MWEBWalletBasicTest(BitcoinTestFramework):
def run_test(self):
node0 = self.nodes[0]
node1 = self.nodes[1]
node2 = self.nodes[2]
self.log.info("Setting up MWEB chain")
setup_mweb_chain(node0)
self.sync_all()
self.log.info("Send to node1 mweb address")
n1_addr0 = node1.getnewaddress(address_type='mweb')
tx1_id = node0.sendtoaddress(n1_addr0, 25)
n1_addr = node1.getnewaddress(address_type='mweb')
tx1_id = node0.sendtoaddress(n1_addr, 25)
self.sync_mempools()
self.log.info("Verify node0's wallet lists the transaction as spent")
@ -37,10 +38,10 @@ class MWEBWalletBasicTest(BitcoinTestFramework):
assert n0_tx1['fee'] < 0 and n0_tx1['fee'] > -0.1
self.log.info("Verify node1's wallet receives the unconfirmed transaction")
n1_addr0_coins = node1.listreceivedbyaddress(minconf=0, address_filter=n1_addr0)
assert_equal(len(n1_addr0_coins), 1)
assert n1_addr0_coins[0]['amount'] == 25
assert n1_addr0_coins[0]['confirmations'] == 0
n1_addr_coins = node1.listreceivedbyaddress(minconf=0, address_filter=n1_addr)
assert_equal(len(n1_addr_coins), 1)
assert n1_addr_coins[0]['amount'] == 25
assert n1_addr_coins[0]['confirmations'] == 0
assert node1.getbalances()['mine']['untrusted_pending'] == 25
assert node1.getbalances()['mine']['trusted'] == 0
@ -49,10 +50,10 @@ class MWEBWalletBasicTest(BitcoinTestFramework):
self.sync_blocks()
self.log.info("Verify node1's wallet lists the transaction as confirmed")
n1_addr0_coins = node1.listunspent(addresses=[n1_addr0])
assert_equal(len(n1_addr0_coins), 1)
assert n1_addr0_coins[0]['amount'] == 25
assert n1_addr0_coins[0]['confirmations'] == 1
n1_addr_coins = node1.listunspent(addresses=[n1_addr])
assert_equal(len(n1_addr_coins), 1)
assert n1_addr_coins[0]['amount'] == 25
assert n1_addr_coins[0]['confirmations'] == 1
assert node1.getbalances()['mine']['untrusted_pending'] == 0
assert node1.getbalances()['mine']['trusted'] == 25
@ -62,6 +63,63 @@ class MWEBWalletBasicTest(BitcoinTestFramework):
assert n0_tx1['amount'] == -25
assert n0_tx1['fee'] < 0 and n0_tx1['fee'] > -0.1
# Pegout to node2
self.log.info("Send (pegout) to node2 bech32 address")
n2_addr = node2.getnewaddress(address_type='bech32')
self.log.info("Address: {}".format(n2_addr))
tx2_id = node1.sendtoaddress(n2_addr, 15)
self.sync_mempools()
self.log.info("Verify node1's wallet lists the transaction as spent")
n1_tx2 = node1.gettransaction(txid=tx2_id)
self.log.info(n1_tx2)
assert_equal(n1_tx2['confirmations'], 0)
assert_equal(n1_tx2['amount'], -15)
assert n1_tx2['fee'] < 0 and n1_tx2['fee'] > -0.1
self.log.info("Verify node2's wallet receives the first pegout transaction")
n2_tx2 = node2.gettransaction(txid=tx2_id)
self.log.info(n2_tx2)
#n2_addr_coins = node2.listreceivedbyaddress(minconf=0, address_filter=n2_addr)
assert_equal(n2_tx2['amount'], 15)
assert_equal(n2_tx2['confirmations'], 0)
# Pegout to node2 using subtract fee from amount
self.log.info("Send (pegout) to node2 bech32 address")
n2_addr2 = node2.getnewaddress(address_type='bech32')
tx3_id = node1.sendtoaddress(address=n2_addr2, amount=5, subtractfeefromamount=True)
self.sync_mempools()
self.log.info("Verify node1's wallet lists the transaction as spent")
n1_tx3 = node1.gettransaction(txid=tx3_id)
assert_equal(n1_tx3['confirmations'], 0)
assert n1_tx3['amount'] > -5 and n1_tx3['amount'] < -4.9
assert n1_tx3['fee'] < 0 and n1_tx3['fee'] > -0.1
assert tx2_id in node1.getrawmempool()
assert tx3_id in node1.getrawmempool()
self.log.info("Mine next block so node2 sees the transactions")
node0.generate(1)
self.sync_all()
self.log.info("Verify node2's wallet receives the first pegout transaction")
n2_addr_coins = node2.listreceivedbyaddress(minconf=0, address_filter=n2_addr)
assert_equal(len(n2_addr_coins), 1)
assert_equal(n2_addr_coins[0]['amount'], 15)
assert_equal(n2_addr_coins[0]['confirmations'], 1)
self.log.info("Verify node2's wallet receives the second pegout transaction")
n2_addr2_coins = node2.listreceivedbyaddress(minconf=0)
self.log.info(n2_addr2_coins)
assert_equal(len(n2_addr2_coins), 1)
assert n2_addr2_coins['amount'] < 5 and n2_addr2_coins['amount'] > 4.9
assert_equal(n2_addr2_coins[0]['confirmations'], 1)
n2_balances = node2.getbalances()['mine']
assert n2_balances['immature'] > 19.9 and n2_balances['immature'] < 20
assert_equal(n2_balances['untrusted_pending'], 0)
assert_equal(n2_balances['trusted'], 0)
# TODO: Conflicting txs
# TODO: Duplicate hash