From 853b17f459f2ffdc0191cc0afacebbd19eba435d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 2 Jan 2021 19:17:25 -0500 Subject: [PATCH 1/6] Improvements to consolidateunspent This commit implements two new features for consolidateunspent: 1. The ability to consolidate from all addresses to the specified address, and 2. The ability to sweep up change in the consolidation. --- src/rpc/client.cpp | 2 + src/rpc/rawtransaction.cpp | 155 ++++++++++++++++++++++++++++++++----- 2 files changed, 136 insertions(+), 21 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 21c12bb1dc..b6063d9209 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -112,6 +112,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "consolidatemsunspent" , 4 }, { "consolidatemsunspent" , 5 }, { "consolidatemsunspent" , 6 }, + { "consolidateunspent" , 3 }, + { "consolidateunspent" , 4 }, { "getbalance" , 1 }, { "getbalance" , 2 }, { "getbalancedetail" , 0 }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 347648fc03..530f885ee6 100755 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -21,6 +21,8 @@ #include "wallet/coincontrol.h" #include "wallet/wallet.h" +#include + using namespace GRC; using namespace std; @@ -488,19 +490,38 @@ UniValue listunspent(const UniValue& params, bool fHelp) UniValue consolidateunspent(const UniValue& params, bool fHelp) { - if (fHelp || params.size() < 1 || params.size() > 3) + if (fHelp || params.size() < 1 || params.size() > 5) throw runtime_error( - "consolidateunspent
[UTXO size [maximum number of inputs]]\n" + "consolidateunspent
[UTXO size] [maximum number of inputs] [sweep all addresses] [sweep change]\n" + "\n" + "
: The Gridcoin address target for consolidation.\n" + "\n" + "[UTXO size]: Optional parameter for target consolidation output size.\n" + "\n" + "[maximum number of inputs]: Defaults to 50, clamped to 200 maximum to prevent transaction failures.\n" + "\n" + "[sweep all addresses]: Boolean to indicate whether all addresses should be used for inputs to the\n" + " consolidation. If true, the source of the consolidation is all addresses and\n" + " the output will be to the specified address, otherwise inputs will only be\n" + " sourced from the same address.\n" "\n" - "Performs a single transaction to consolidate UTXOs on\n" - "a given address. The optional parameter of UTXO size will result\n" - "in consolidating UTXOs to generate an output less than that size or\n" - "the total value of the specified maximum number of smallest inputs,\n" - "whichever is less.\n" + "[sweep change]: Boolean to indicate whether change associated with the address should be\n" + " consolidated. If [sweep all addresses] is true then this is also forced true.\n" "\n" - "The script is designed to be run repeatedly and will become a no-op\n" - "if the UTXO's are consolidated such that no more meet the specified\n" - "criteria. This is ideal for automated periodic scripting.\n"); + "consolidateunspent performs a single transaction to consolidate UTXOs to/on a given address. The optional\n" + "parameter of UTXO size will result in consolidating UTXOs to generate the largest output possible less\n" + "than that size or the total value of the specified maximum number of smallest inputs, whichever is less.\n" + "\n" + "The script is designed to be run repeatedly and will become a no-op if the UTXO's are consolidated such\n" + "that no more meet the specified criteria. This is ideal for automated periodic scripting.\n" + "\n" + "To consolidate the entire wallet to one address do something like:\n" + "\n" + "consolidate unspent
200 true repeatedly until there are\n" + "no more UTXOs to consolidate.\n" + "\n" + "In all cases the address MUST exist in your wallet beforehand. If doing this for the purpose of creating\n" + "a new smaller wallet, create a new address beforehand to serve as the target of the consolidation.\n"); UniValue result(UniValue::VOBJ); @@ -514,16 +535,28 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) // about 3x that. The GUI will not be responsive during the transaction due to locking. unsigned int nInputNumberLimit = 50; + bool sweep_all_addresses = false; + + // Note this value is ignored if sweep_all_addresses is set to true. + bool sweep_change = false; + if (params.size() > 1) nConsolidateLimit = AmountFromValue(params[1]); + if (params.size() > 2) nInputNumberLimit = params[2].get_int(); + if (params.size() > 3) sweep_all_addresses = params[3].get_bool(); + + if (params.size() > 4 && !sweep_all_addresses) sweep_change = params[4].get_bool(); + // Clamp InputNumberLimit to 200. Above 200 risks an invalid transaction due to the size. - nInputNumberLimit = std::min(nInputNumberLimit, (unsigned int) 200); + nInputNumberLimit = std::min(nInputNumberLimit, 200); if (!OptimizeAddress.IsValid()) + { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Gridcoin address: ") + sAddress); + } - // Set the consolidation transaction address to the same as the inputs to consolidate. + // Set the consolidation transaction address to the specified output address. CScript scriptDestPubKey; scriptDestPubKey.SetDestination(OptimizeAddress.Get()); @@ -534,23 +567,99 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) // more UTXO's will have the same nValue. std::multimap mInputs; - // Have to lock both main and wallet to prevent deadlocks. + // Have to lock both main and wallet. LOCK2(cs_main, pwalletMain->cs_wallet); // Get the current UTXO's. pwalletMain->AvailableCoins(vecInputs, false, NULL, false); - // Filter outputs by matching address and insert into sorted multimap. + // Filter outputs in the wallet that are the candidate inputs by matching address and insert into sorted multimap. for (auto const& out : vecInputs) { - CTxDestination outaddress; + CTxDestination out_address; + int64_t nOutValue = out.tx->vout[out.i].nValue; - if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, outaddress)) continue; + // If we can't resolve the address, then move on. + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, out_address)) continue; - if (CBitcoinAddress(outaddress) == OptimizeAddress) + // If the UTXO matches the consolidation address or all sweep_all_addresses is true then add to the inputs + // map for consolidation. Note that the value of sweep_change is ignored and all change will be swept. + if (CBitcoinAddress(out_address) == OptimizeAddress || sweep_all_addresses) + { mInputs.insert(std::make_pair(nOutValue, out)); - } + } + // If sweep_change is true... and ... + // If the UTXO is change (we already know it "ISMINE") then iterate through the inputs to the transaction + // that created the change. This loop has the effect of matching the change to the address of the first + // input's address of the transaction that created the change. It is possible that the change could have been + // created from a transaction whose inputs can from multiple addresses within one's wallet. In this case, + // a choice has to be made. This is similar to listaddressgroupings and is a decent approach... + else if (sweep_change && pwalletMain->IsChange(out.tx->vout[out.i])) + { + // Iterate through the inputs of the transaction that created the change. Note that this has to be implemented + // as a work queue, because change can stake, and so the input here could still be change, etc. You have to + // continue walking up the tree of transactions until you get to a non-change source address. If the non-change + // source address matches the consolidation address, the UTXO is included. + + // The work queue. + std::queue> vin_queue; + + // The inital population of the queue is the input vector of the transaction that created the change UTXO. + vin_queue.push(out.tx->vin); + + + // Keep track of the vin vectors processed on the queue and limit to a reasonable value of 25 to prevent + // excessively long execution times. This introduces the possibility of failing to resolve a change parent + // address that has been through many stakes, but I am concerned about the processing time. + unsigned int vins_emplaced = 0; + + while (!vin_queue.empty() && vins_emplaced <= 25) + { + // Pull the first unit of work. + std::vector v_change_input = vin_queue.front(); + vin_queue.pop(); + + for (const auto& change_input : v_change_input) + { + // Get the COutPoint of this transaction input. + COutPoint prevout = change_input.prevout; + + CTxDestination change_input_address; + + // Get the transaction that generated this output, which was the input to + // the transaction that created the change. + CWalletTx tx_change_input = pwalletMain->mapWallet[prevout.hash]; + + // Get the corresponding output of that transaction (this is the same as the input to the + // referenced transaction). We need this to resolve the address. + CTxOut prev_ctxout = tx_change_input.vout[prevout.n]; + + // If this is still change then place the input vector of this transaction onto the queue. + if (pwalletMain->IsChange(prev_ctxout)) + { + vin_queue.emplace(tx_change_input.vin); + ++vins_emplaced; + } + + // If not change, then get the address of that output and if it matches the OptimizeAddress add the UTXO + // to the inputs map for consolidation. + if (ExtractDestination(prev_ctxout.scriptPubKey, change_input_address)) + { + if (CBitcoinAddress(change_input_address) == OptimizeAddress) + { + // Insert the ORIGINAL change UTXO into the input map for the consolidation. + mInputs.insert(std::make_pair(nOutValue, out)); + + // We do not need/want to add it more than once, and we do not need to continue processing the + // queue if a linkage is found. + break; + } + } + } // for (const auto& change_input : v_change_input) + } // while (!vin_queue.empty()) + } // else if (pwalletMain->IsChange(out.tx->vout[out.i])) + } // for (auto const& out : vecInputs) CWalletTx wtxNew; @@ -591,7 +700,8 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) nValue += nUTXOValue; - LogPrint(BCLog::LogFlags::VERBOSE, "INFO: consolidateunspent: input value = %f, confirmations = %" PRId64, ((double) out.first) / (double) COIN, out.second.nDepth); + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: consolidateunspent: input value = %f, confirmations = %" PRId64, + ((double) out.first) / (double) COIN, out.second.nDepth); setCoins.insert(make_pair(out.second.tx, out.second.i)); @@ -654,7 +764,8 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) { string strError; if (nValue + nFeeRequired > pwalletMain->GetBalance()) - strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired)); + strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount," + " complexity, or use of recently received funds "), FormatMoney(nFeeRequired)); else strError = _("Error: Transaction creation failed "); LogPrintf("consolidateunspent: %s", strError); @@ -662,7 +773,9 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) } if (!pwalletMain->CommitTransaction(wtxNew, reservekey)) - return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); + return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already" + " spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent" + " here."); result.pushKV("result", true); result.pushKV("UTXOs consolidated", (uint64_t) iInputCount); From 3ed7e31b56ad4b6632bfed557bc980ba8bfe8427 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 2 Jan 2021 23:37:23 -0500 Subject: [PATCH 2/6] Corrections to fee calculations for consolidateunspent --- src/rpc/rawtransaction.cpp | 45 +++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 530f885ee6..57c8733a25 100755 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -663,13 +663,11 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) CWalletTx wtxNew; - // For min fee calculation. - CTransaction txDummy; - set> setCoins; unsigned int iInputCount = 0; - int64_t nValue = 0; + int64_t nAmount = 0; + unsigned int nBytesInputs = 0; // Construct the inputs to the consolidation transaction. Either all of the inputs from above, or 200, // or when the total reaches/exceeds nConsolidateLimit, whichever is more limiting. The map allows us @@ -678,7 +676,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) { int64_t nUTXOValue = out.second.tx->vout[out.second.i].nValue; - if (iInputCount == nInputNumberLimit || ((nValue + nUTXOValue) > nConsolidateLimit && nConsolidateLimit != 0)) break; + if (iInputCount == nInputNumberLimit || ((nAmount + nUTXOValue) > nConsolidateLimit && nConsolidateLimit != 0)) break; // This has been moved after the break to change the behavior so that the // consolidation is limited to the set of UTXO's SMALLER than the nConsolidateLimit @@ -698,7 +696,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) // to solve both is to include a "change" UTXO to true up the mismatch. This is // overly complex and not worth the implementation time. - nValue += nUTXOValue; + nAmount += nUTXOValue; LogPrint(BCLog::LogFlags::VERBOSE, "INFO: consolidateunspent: input value = %f, confirmations = %" PRId64, ((double) out.first) / (double) COIN, out.second.nDepth); @@ -706,6 +704,25 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) setCoins.insert(make_pair(out.second.tx, out.second.i)); ++iInputCount; + + // For fee calculation. This is similar to the calculation in coincontroldialog.cpp. + CTxDestination address; + + if(ExtractDestination(out.second.tx->vout[out.second.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get(&address); + if (keyid && pwalletMain->GetPubKey(*keyid, pubkey)) + { + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + } + // in all error cases, simply assume 148 here + else + { + nBytesInputs += 148; + } + } + else nBytesInputs += 148; } // If number of inputs that meet criteria is less than two, then do nothing. @@ -722,16 +739,18 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) // Fee calculation to avoid change. + CTransaction txDummy; + // Bytes - // --------- The inputs to the tx - The one output. - int64_t nBytes = iInputCount * 148 + 34 + 10; + // ----- The inputs to the tx - The one output. + int64_t nBytes = nBytesInputs + 2 * 34 + 10; // Min Fee int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes); - int64_t nFee = nTransactionFee * (1 + nBytes / 1000); + int64_t nFee = nTransactionFee * (1 + (int64_t) nBytes / 1000); - int64_t nFeeRequired = max(nMinFee, nFee); + int64_t nFeeRequired = std::max(nMinFee, nFee); if (pwalletMain->IsLocked()) @@ -753,7 +772,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) // Reduce the out value for the transaction by nFeeRequired from the total of the inputs to provide a fee // to the staker. The fee has been calculated so that no change should be produced from the CreateTransaction // call. Just in case, the input address is specified as the return address via coincontrol. - vecSend.push_back(std::make_pair(scriptDestPubKey, nValue - nFeeRequired)); + vecSend.push_back(std::make_pair(scriptDestPubKey, nAmount - nFeeRequired)); CCoinControl coinControl; @@ -763,7 +782,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) if (!pwalletMain->CreateTransaction(vecSend, setCoins, wtxNew, reservekey, nFeeRequired, &coinControl)) { string strError; - if (nValue + nFeeRequired > pwalletMain->GetBalance()) + if (nAmount + nFeeRequired > pwalletMain->GetBalance()) strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount," " complexity, or use of recently received funds "), FormatMoney(nFeeRequired)); else @@ -779,7 +798,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) result.pushKV("result", true); result.pushKV("UTXOs consolidated", (uint64_t) iInputCount); - result.pushKV("Output UTXO value", (double)(nValue - nFeeRequired) / COIN); + result.pushKV("Output UTXO value", (double)(nAmount - nFeeRequired) / COIN); return result; } From 4c8dbfa0cfcb39e7857b89b7c03484f7988d7a31 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Mon, 4 Jan 2021 17:44:45 -0500 Subject: [PATCH 3/6] Add reset button to clear coincontrol entries When advanced coin control is enabled, and via the inputs button inputs are selected, then those inputs are spent via an underlying transaction or staking, they become "stuck". This is because the coinControl object still has the stale COutPuts in it. A simple reset button has been added that calls SetNull() to nullify the coinControl object and then updates the coin control labels. This gets rid of the stuck selected inputs. --- src/qt/forms/sendcoinsdialog.ui | 17 ++++++++++++----- src/qt/sendcoinsdialog.cpp | 7 +++++++ src/qt/sendcoinsdialog.h | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 5b1451479e..3478b8fb4f 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -6,8 +6,8 @@ 0 0 - 850 - 400 + 899 + 456 @@ -85,7 +85,7 @@ - + 8 @@ -122,6 +122,13 @@ + + + + Reset + + + @@ -547,8 +554,8 @@ 0 0 - 830 - 167 + 869 + 112 diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 7b9188f589..76d1b71e35 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -36,6 +36,7 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : // Coin Control ui->coinControlChangeEdit->setFont(GUIUtil::bitcoinAddressFont()); connect(ui->coinControlPushButton, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); + connect(ui->coinControlResetPushButton, SIGNAL(clicked()), this, SLOT(coinControlResetButtonClicked())); connect(ui->coinControlChangeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); connect(ui->coinControlChangeEdit, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); @@ -436,6 +437,12 @@ void SendCoinsDialog::coinControlButtonClicked() coinControlUpdateLabels(); } +void SendCoinsDialog::coinControlResetButtonClicked() +{ + CoinControlDialog::coinControl->SetNull(); + coinControlUpdateLabels(); +} + // Coin Control: checkbox custom change address void SendCoinsDialog::coinControlChangeChecked(int state) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index a40b65d1ad..f6bf390225 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -52,6 +52,7 @@ private slots: void updateDisplayUnit(); void coinControlFeatureChanged(bool); void coinControlButtonClicked(); + void coinControlResetButtonClicked(); void coinControlChangeChecked(int); void coinControlChangeEdited(const QString &); void coinControlUpdateLabels(); From 4a50634def2705f622a65fefad57cb9ac278503d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 8 Jan 2021 17:21:56 -0500 Subject: [PATCH 4/6] Fix typo on consolidateunspent Co-authored-by: div72 <60045611+div72@users.noreply.github.com> --- src/rpc/rawtransaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 57c8733a25..31debf4fd9 100755 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -517,7 +517,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) "\n" "To consolidate the entire wallet to one address do something like:\n" "\n" - "consolidate unspent
200 true repeatedly until there are\n" + "consolidateunspent
200 true repeatedly until there are\n" "no more UTXOs to consolidate.\n" "\n" "In all cases the address MUST exist in your wallet beforehand. If doing this for the purpose of creating\n" From 022c2211058bf95c2fc6d201b83cd70f65a703e8 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 8 Jan 2021 17:22:31 -0500 Subject: [PATCH 5/6] Change int64_t to CAmount to follow new Bitcoin types. Co-authored-by: div72 <60045611+div72@users.noreply.github.com> --- src/rpc/rawtransaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 31debf4fd9..c087e884cb 100755 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -666,7 +666,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) set> setCoins; unsigned int iInputCount = 0; - int64_t nAmount = 0; + CAmount nAmount = 0; unsigned int nBytesInputs = 0; // Construct the inputs to the consolidation transaction. Either all of the inputs from above, or 200, From 58772a1c17ba9c0c131de554e75889c69685a358 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Fri, 8 Jan 2021 18:16:03 -0500 Subject: [PATCH 6/6] Add time based expiry for wallet model balance cache This adds a time based expiration to the wallet balance cache to avoid repeated calls to GetBalance() during periods of rapid updateTransaction calls due to operations like consolidatunspent or syncing from zero with a busy wallet. --- src/qt/walletmodel.cpp | 37 +++++++++++++++++++++++++++---------- src/qt/walletmodel.h | 2 ++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d7854bbe8d..d9787a24fe 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -102,18 +102,35 @@ void WalletModel::pollBalanceChanged() void WalletModel::checkBalanceChanged() { - qint64 newBalance = getBalance(); - qint64 newStake = getStake(); - qint64 newUnconfirmedBalance = getUnconfirmedBalance(); - qint64 newImmatureBalance = getImmatureBalance(); + // These are INCREDIBLY expensive calls for wallets with a large transaction map size. Use a timed expire (stale) + // pattern to avoid calling these repeatedly for rapid fire updates which occur during a blockchain resync or + // rescan of a busy wallet, or a transaction that changes lots of UTXO's statuses, such as consolidateunspent. - if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance) + // We don't have to worry about the last call to this being lost (absorbed) because it doesn't pass the stale + // test, because the balance will be updated anyway by the timer poll in MODEL_UPDATE_DELAY seconds period. + int64_t current_time = GetAdjustedTime(); + + if (current_time - last_balance_update_time > MODEL_UPDATE_DELAY / 1000) { - cachedBalance = newBalance; - cachedStake = newStake; - cachedUnconfirmedBalance = newUnconfirmedBalance; - cachedImmatureBalance = newImmatureBalance; - emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance); + qint64 newBalance = getBalance(); + qint64 newStake = getStake(); + qint64 newUnconfirmedBalance = getUnconfirmedBalance(); + qint64 newImmatureBalance = getImmatureBalance(); + + if (cachedBalance != newBalance + || cachedStake != newStake + || cachedUnconfirmedBalance != newUnconfirmedBalance + || cachedImmatureBalance != newImmatureBalance) + { + cachedBalance = newBalance; + cachedStake = newStake; + cachedUnconfirmedBalance = newUnconfirmedBalance; + cachedImmatureBalance = newImmatureBalance; + + last_balance_update_time = current_time; + + emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance); + } } } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index f6c28fe937..2c2bfaba46 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -146,6 +146,8 @@ class WalletModel : public QObject EncryptionStatus cachedEncryptionStatus; int cachedNumBlocks; + int64_t last_balance_update_time = 0; + QTimer *pollTimer; void subscribeToCoreSignals();