diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp
index 6c22206b26..00559b6dfe 100644
--- a/src/rpcclient.cpp
+++ b/src/rpcclient.cpp
@@ -132,6 +132,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listunspent" , 0 },
{ "listunspent" , 1 },
{ "listunspent" , 2 },
+ { "consolidateunspent" , 1 },
+ { "consolidateunspent" , 2 },
{ "move" , 2 },
{ "move" , 3 },
{ "rainbymagnitude" , 0 },
diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp
old mode 100644
new mode 100755
index 127e766735..aa82f937f4
--- a/src/rpcrawtransaction.cpp
+++ b/src/rpcrawtransaction.cpp
@@ -13,6 +13,7 @@
#include "main.h"
#include "net.h"
#include "wallet.h"
+#include "coincontrol.h"
using namespace std;
using namespace boost;
@@ -604,6 +605,167 @@ UniValue listunspent(const UniValue& params, bool fHelp)
}
+UniValue consolidateunspent(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() < 1 || params.size() > 3)
+ throw runtime_error(
+ "consolidateunspent
[UTXO size [maximum number of inputs]]\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 of that size or\n"
+ "the output for the total value of the specified maximum,\n"
+ "maximum number of smallest inputs, whichever is less.\n");
+
+ UniValue result(UniValue::VOBJ);
+
+ std::string sAddress = params[0].get_str();
+ CBitcoinAddress OptimizeAddress(sAddress);
+
+ int64_t nConsolidateLimit = 0;
+ // Set default maximum consolidation to 50 inputs if it is not specified. This is based
+ // on performance tests on the Pi to ensure the transaction returns within a reasonable time.
+ // The performance tests on the Pi show about 3 UTXO's/second. Intel machines should do
+ // about 3x that. The GUI will not be responsive during the transaction.
+ unsigned int nInputNumberLimit = 50;
+
+ if (params.size() > 1) nConsolidateLimit = AmountFromValue(params[1]);
+ if (params.size() > 2) nInputNumberLimit = params[2].get_int();
+
+ // Clamp InputNumberLimit to 200. Above 200 risks an invalid transaction due to the size.
+ nInputNumberLimit = std::min(nInputNumberLimit, (unsigned int) 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.
+ CScript scriptDestPubKey;
+ scriptDestPubKey.SetDestination(OptimizeAddress.Get());
+
+ std::vector vecInputs;
+
+ // A convenient way to do a sort without the bother of writing a comparison operator.
+ // The map does it for us! It must be a multimap, because it is highly likely one or
+ // more UTXO's will have the same nValue.
+ std::multimap mInputs;
+
+ // Have to lock both main and wallet to prevent deadlocks.
+ 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.
+ for (auto const& out : vecInputs)
+ {
+ CTxDestination outaddress;
+ int64_t nOutValue = out.tx->vout[out.i].nValue;
+
+ if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, outaddress)) continue;
+
+ if (CBitcoinAddress(outaddress) == OptimizeAddress)
+ mInputs.insert(std::make_pair(nOutValue, out));
+ }
+
+ CWalletTx wtxNew;
+
+ // For min fee calculation.
+ CTransaction txDummy;
+
+ set> setCoins;
+
+ unsigned int iInputCount = 0;
+ int64_t nValue = 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
+ // to elegantly select the UTXO's from the smallest upwards.
+ for (auto const& out : mInputs)
+ {
+ // Increment first so the count is 1 based.
+ ++iInputCount;
+
+ if (fDebug) LogPrintf("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));
+ nValue += out.second.tx->vout[out.second.i].nValue;
+
+ if (iInputCount == nInputNumberLimit || (nValue >= nConsolidateLimit && nConsolidateLimit != 0)) break;
+ }
+
+ // If number of inputs that meet criteria is less than two, then do nothing.
+ if (iInputCount < 2)
+ {
+ result.pushKV("result", true);
+ result.pushKV("UTXOs consolidated", (uint64_t) 0);
+
+ return result;
+ }
+
+ CReserveKey reservekey(pwalletMain);
+
+
+ // Fee calculation to avoid change.
+
+ // Bytes
+ // --------- The inputs to the tx - The one output.
+ int64_t nBytes = iInputCount * 148 + 34 + 10;
+
+ // Min Fee
+ int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes);
+
+ int64_t nFee = nTransactionFee * (1 + nBytes / 1000);
+
+ int64_t nFeeRequired = max(nMinFee, nFee);
+
+
+ if (pwalletMain->IsLocked())
+ {
+ string strError = _("Error: Wallet locked, unable to create transaction.");
+ LogPrintf("consolidateunspent: %s", strError);
+ return strError;
+ }
+
+ if (fWalletUnlockStakingOnly)
+ {
+ string strError = _("Error: Wallet unlocked for staking only, unable to create transaction.");
+ LogPrintf("consolidateunspent: %s", strError);
+ return strError;
+ }
+
+ vector > vecSend;
+
+ // 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));
+
+ CCoinControl coinControl;
+
+ // Send the change back to the same address.
+ coinControl.destChange = OptimizeAddress.Get();
+
+ 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));
+ else
+ strError = _("Error: Transaction creation failed ");
+ LogPrintf("consolidateunspent: %s", strError);
+ return strError;
+ }
+
+ 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.");
+
+ result.pushKV("result", true);
+ result.pushKV("UTXOs consolidated", (uint64_t) iInputCount);
+ result.pushKV("Output UTXO value", (double)(nValue - nFeeRequired) / COIN);
+
+ return result;
+}
+
UniValue createrawtransaction(const UniValue& params, bool fHelp)
{
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp
index b8b1484484..c8a8edec5f 100644
--- a/src/rpcserver.cpp
+++ b/src/rpcserver.cpp
@@ -312,6 +312,7 @@ static const CRPCCommand vRPCCommands[] =
{ "listsinceblock", &listsinceblock, cat_wallet },
{ "listtransactions", &listtransactions, cat_wallet },
{ "listunspent", &listunspent, cat_wallet },
+ { "consolidateunspent", &consolidateunspent, cat_wallet },
{ "makekeypair", &makekeypair, cat_wallet },
{ "move", &movecmd, cat_wallet },
{ "rain", &rain, cat_wallet },
diff --git a/src/rpcserver.h b/src/rpcserver.h
index a6f5abdb49..18c287cc8e 100644
--- a/src/rpcserver.h
+++ b/src/rpcserver.h
@@ -133,6 +133,7 @@ extern UniValue listreceivedbyaddress(const UniValue& params, bool fHelp);
extern UniValue listsinceblock(const UniValue& params, bool fHelp);
extern UniValue listtransactions(const UniValue& params, bool fHelp);
extern UniValue listunspent(const UniValue& params, bool fHelp);
+extern UniValue consolidateunspent(const UniValue& params, bool fHelp);
extern UniValue makekeypair(const UniValue& params, bool fHelp);
extern UniValue movecmd(const UniValue& params, bool fHelp);
extern UniValue rain(const UniValue& params, bool fHelp);
diff --git a/src/wallet.cpp b/src/wallet.cpp
index 8ef137cc7f..63a912d6f4 100644
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -1530,19 +1530,19 @@ bool CWallet::SelectCoinsForStaking(int64_t nTargetValueIn, unsigned int nSpendT
return true;
}
-bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey,
- int64_t& nFeeRet, const CCoinControl* coinControl)
+bool CWallet::CreateTransaction(const vector >& vecSend, set>& setCoins,
+ CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl)
{
- int64_t nValue = 0;
+ int64_t nValueOut = 0;
for (auto const& s : vecSend)
{
- if (nValue < 0)
+ if (nValueOut < 0)
return false;
- nValue += s.second;
+ nValueOut += s.second;
}
- if (vecSend.empty() || nValue < 0)
+ if (vecSend.empty() || nValueOut < 0)
return false;
wtxNew.BindWallet(this);
@@ -1559,24 +1559,36 @@ bool CWallet::CreateTransaction(const vector >& vecSend,
wtxNew.vout.clear();
wtxNew.fFromMe = true;
- int64_t nTotalValue = nValue + nFeeRet;
+ int64_t nTotalValue = nValueOut + nFeeRet;
double dPriority = 0;
// vouts to the payees
for (auto const& s : vecSend)
wtxNew.vout.push_back(CTxOut(s.second, s.first));
- // Choose coins to use
- set > setCoins;
int64_t nValueIn = 0;
- if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
- return false;
+
+ // If provided coin set is empty, choose coins to use.
+ if (!setCoins.size())
+ {
+ if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
+ return false;
+ }
+ else
+ {
+ // Add up input value for the provided set of coins.
+ for (auto const& input : setCoins)
+ {
+ nValueIn += input.first->vout[input.second].nValue;
+ }
+ }
+
for (auto const& pcoin : setCoins)
{
int64_t nCredit = pcoin.first->vout[pcoin.second].nValue;
dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain();
}
- int64_t nChange = nValueIn - nValue - nFeeRet;
+ int64_t nChange = nValueIn - nValueOut - nFeeRet;
// if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE
// or until nChange becomes zero
// NOTE: this depends on the exact behaviour of GetMinFee
@@ -1659,6 +1671,18 @@ bool CWallet::CreateTransaction(const vector >& vecSend,
return true;
}
+bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey,
+ int64_t& nFeeRet, const CCoinControl* coinControl)
+{
+ // Initialize setCoins empty to let CreateTransaction choose via SelectCoins...
+ set> setCoins;
+
+ return CreateTransaction(vecSend, setCoins, wtxNew, reservekey, nFeeRet, coinControl);
+}
+
+
+
+
bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl)
{
vector< pair > vecSend;
diff --git a/src/wallet.h b/src/wallet.h
index f6f73a60e6..129810270c 100644
--- a/src/wallet.h
+++ b/src/wallet.h
@@ -7,6 +7,7 @@
#include
#include
+#include
#include
#include "main.h"
#include "key.h"
@@ -199,6 +200,7 @@ class CWallet : public CCryptoKeyStore
int64_t GetStake() const;
int64_t GetNewMint() const;
bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
+ bool CreateTransaction(const std::vector >& vecSend, std::set>& setCoins, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
bool CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);