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();
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();
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..c087e884cb 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"
+ "[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"
+ "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"
- "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"
+ "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"
- "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");
+ "To consolidate the entire wallet to one address do something like:\n"
+ "\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"
+ "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,33 +567,107 @@ 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.
- CWalletTx wtxNew;
+ // The work queue.
+ std::queue> vin_queue;
- // For min fee calculation.
- CTransaction txDummy;
+ // 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;
set> setCoins;
unsigned int iInputCount = 0;
- int64_t nValue = 0;
+ CAmount 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
@@ -569,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
@@ -589,13 +696,33 @@ 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);
+ 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));
++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.
@@ -612,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())
@@ -643,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;
@@ -653,8 +782,9 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp)
if (!pwalletMain->CreateTransaction(vecSend, setCoins, wtxNew, reservekey, nFeeRequired, &coinControl))
{
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));
+ 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
strError = _("Error: Transaction creation failed ");
LogPrintf("consolidateunspent: %s", strError);
@@ -662,11 +792,13 @@ 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);
- result.pushKV("Output UTXO value", (double)(nValue - nFeeRequired) / COIN);
+ result.pushKV("Output UTXO value", (double)(nAmount - nFeeRequired) / COIN);
return result;
}