Skip to content

Commit c514184

Browse files
committed
Initial roughout of consolidateutxos
Includes a new overloaded CreateTransaction function. This function works by rpc call... consolidateutxos <address> [utxo size limit [max num of inputs]] The address is an address in your wallet for which you want to combine UTXOs. The optional utxo size limit means to try and make an output of that size. The function consolidates UTXO's starting from the smallest ascending order on that address, stopping when either the total output size exceeds the utxo size limit (if specified) OR max num of inputs (selected UTXO's) have been reached OR all UTXO's on that address have been selected, whichever occurs first. The max num of inputs is clamped to 200. The fees are calculated and netted to produce no change, but coincontrol is used to route the change back to the same address just in case.
1 parent 9468e0f commit c514184

File tree

6 files changed

+196
-12
lines changed

6 files changed

+196
-12
lines changed

src/rpcclient.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
132132
{ "listunspent" , 0 },
133133
{ "listunspent" , 1 },
134134
{ "listunspent" , 2 },
135+
{ "consolidateutxos" , 1 },
136+
{ "consolidateutxos" , 2 },
135137
{ "move" , 2 },
136138
{ "move" , 3 },
137139
{ "rainbymagnitude" , 0 },

src/rpcrawtransaction.cpp

100644100755
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "main.h"
1414
#include "net.h"
1515
#include "wallet.h"
16+
#include "coincontrol.h"
1617

1718
using namespace std;
1819
using namespace boost;
@@ -604,6 +605,159 @@ UniValue listunspent(const UniValue& params, bool fHelp)
604605
}
605606

606607

608+
UniValue consolidateutxos(const UniValue& params, bool fHelp)
609+
{
610+
if (fHelp || params.size() < 1 || params.size() > 3)
611+
throw runtime_error(
612+
"consolidateutxos <address> [UTXO size [maximum number of inputs]]\n"
613+
"\n"
614+
"Performs a single transaction to consolidate UTXOs on\n"
615+
"a given address. The optional parameter of UTXO size will result\n"
616+
"in consolidating UTXOs to generate an output of that size or\n"
617+
"the output for the total value of the specified maximum,\n"
618+
"maximum number of smallest inputs, whichever is less.\n");
619+
620+
UniValue result(UniValue::VOBJ);
621+
622+
std::string sAddress = params[0].get_str();
623+
CBitcoinAddress OptimizeAddress(sAddress);
624+
625+
int64_t nConsolidateLimit = 0;
626+
// Set default maximum consolidation to 200 inputs if it is not specified.
627+
unsigned int nInputNumberLimit = 200;
628+
629+
if (params.size() > 1) nConsolidateLimit = AmountFromValue(params[1]);
630+
if (params.size() > 2) nInputNumberLimit = params[2].get_int();
631+
632+
// Clamp InputNumberLimit to 200.
633+
nInputNumberLimit = std::min(nInputNumberLimit, (unsigned int) 200);
634+
635+
if (!OptimizeAddress.IsValid())
636+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Gridcoin address: ") + sAddress);
637+
638+
// Set the consolidation transaction address to the same as the inputs to consolidate.
639+
CScript scriptDestPubKey;
640+
scriptDestPubKey.SetDestination(OptimizeAddress.Get());
641+
642+
std::vector<COutput> vecInputs;
643+
644+
std::multimap<int64_t, COutput> mInputs;
645+
646+
LOCK(pwalletMain->cs_wallet);
647+
648+
pwalletMain->AvailableCoins(vecInputs, false, NULL, false);
649+
650+
// Filter outputs by matching address and insert into sorted multimap.
651+
for (auto const& out : vecInputs)
652+
{
653+
CTxDestination outaddress;
654+
int64_t nOutValue = out.tx->vout[out.i].nValue;
655+
656+
if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, outaddress)) continue;
657+
658+
if (CBitcoinAddress(outaddress) == OptimizeAddress)
659+
mInputs.insert(std::make_pair(nOutValue, out));
660+
}
661+
662+
CWalletTx wtxNew;
663+
664+
// For min fee
665+
CTransaction txDummy;
666+
667+
set<pair<const CWalletTx*,unsigned int>> setCoins;
668+
669+
unsigned int iInputCount = 0;
670+
int64_t nValue = 0;
671+
672+
// Construct the inputs to the consolidation transaction. Either all of the inputs from above, or 200,
673+
// or when the total reaches/exceeds nConsolidateLimit, whichever is more limiting. The map allows us
674+
// to elegantly select the UTXO's from the smallest upwards.
675+
for (auto const& out : mInputs)
676+
{
677+
// Increment first so the count is 1 based.
678+
++iInputCount;
679+
680+
if (fDebug) LogPrintf("INFO consolidateutxos: input value = %f, confirmations = %" PRId64, ((double) out.first) / (double) COIN, out.second.nDepth);
681+
682+
setCoins.insert(make_pair(out.second.tx, out.second.i));
683+
nValue += out.second.tx->vout[out.second.i].nValue;
684+
685+
if (iInputCount == nInputNumberLimit || (nValue >= nConsolidateLimit && nConsolidateLimit != 0)) break;
686+
}
687+
688+
// If number of inputs that meet criteria is less than two, then do nothing.
689+
if (iInputCount < 2)
690+
{
691+
result.pushKV("result", true);
692+
result.pushKV("UTXOs consolidated", (uint64_t) 0);
693+
694+
return result;
695+
}
696+
697+
CReserveKey reservekey(pwalletMain);
698+
699+
700+
// Fee calculation to avoid change.
701+
702+
// Bytes - Assume two outputs (because there might be change for the time being).
703+
// ----------The inputs to the tx - The one output.
704+
int64_t nBytes = iInputCount * 148 + 34 + 10;
705+
706+
// Min Fee
707+
int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes);
708+
709+
int64_t nFee = nTransactionFee * (1 + nBytes / 1000);
710+
711+
int64_t nFeeRequired = max(nMinFee, nFee);
712+
713+
714+
if (pwalletMain->IsLocked())
715+
{
716+
string strError = _("Error: Wallet locked, unable to create transaction.");
717+
LogPrintf("consolidateutxos: %s", strError);
718+
return strError;
719+
}
720+
721+
if (fWalletUnlockStakingOnly)
722+
{
723+
string strError = _("Error: Wallet unlocked for staking only, unable to create transaction.");
724+
LogPrintf("consolidateutxos: %s", strError);
725+
return strError;
726+
}
727+
728+
vector<pair<CScript, int64_t> > vecSend;
729+
730+
// Reduce the out value for the transaction by nFeeRequired from the total of the inputs to provide a fee
731+
// to the staker. The fee has been calculated so that no change should be produced from the CreateTransaction
732+
// call. Just in case, the input address is specified as the return address via coincontrol.
733+
vecSend.push_back(std::make_pair(scriptDestPubKey, nValue - nFeeRequired));
734+
735+
CCoinControl* coinControl = new CCoinControl();
736+
737+
// Send the change back to the same address.
738+
coinControl->destChange = OptimizeAddress.Get();
739+
740+
if (!pwalletMain->CreateTransaction(vecSend, setCoins, wtxNew, reservekey, nFeeRequired, coinControl))
741+
{
742+
string strError;
743+
if (nValue + nFeeRequired > pwalletMain->GetBalance())
744+
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));
745+
else
746+
strError = _("Error: Transaction creation failed ");
747+
LogPrintf("consolidateutxos: %s", strError);
748+
return strError;
749+
}
750+
751+
if (!pwalletMain->CommitTransaction(wtxNew, reservekey))
752+
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.");
753+
754+
result.pushKV("result", true);
755+
result.pushKV("UTXOs consolidated", (uint64_t) iInputCount);
756+
result.pushKV("Output UTXO value", (double)(nValue - nFeeRequired) / COIN);
757+
758+
return result;
759+
}
760+
607761

608762
UniValue createrawtransaction(const UniValue& params, bool fHelp)
609763
{

src/rpcserver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ static const CRPCCommand vRPCCommands[] =
312312
{ "listsinceblock", &listsinceblock, cat_wallet },
313313
{ "listtransactions", &listtransactions, cat_wallet },
314314
{ "listunspent", &listunspent, cat_wallet },
315+
{ "consolidateutxos", &consolidateutxos, cat_wallet },
315316
{ "makekeypair", &makekeypair, cat_wallet },
316317
{ "move", &movecmd, cat_wallet },
317318
{ "rain", &rain, cat_wallet },

src/rpcserver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ extern UniValue listreceivedbyaddress(const UniValue& params, bool fHelp);
133133
extern UniValue listsinceblock(const UniValue& params, bool fHelp);
134134
extern UniValue listtransactions(const UniValue& params, bool fHelp);
135135
extern UniValue listunspent(const UniValue& params, bool fHelp);
136+
extern UniValue consolidateutxos(const UniValue& params, bool fHelp);
136137
extern UniValue makekeypair(const UniValue& params, bool fHelp);
137138
extern UniValue movecmd(const UniValue& params, bool fHelp);
138139
extern UniValue rain(const UniValue& params, bool fHelp);

src/wallet.cpp

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,19 +1530,19 @@ bool CWallet::SelectCoinsForStaking(int64_t nTargetValueIn, unsigned int nSpendT
15301530
return true;
15311531
}
15321532

1533-
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey,
1534-
int64_t& nFeeRet, const CCoinControl* coinControl)
1533+
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, set<pair<const CWalletTx*,unsigned int>>& setCoins,
1534+
CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl)
15351535
{
15361536

1537-
int64_t nValue = 0;
1537+
int64_t nValueOut = 0;
15381538

15391539
for (auto const& s : vecSend)
15401540
{
1541-
if (nValue < 0)
1541+
if (nValueOut < 0)
15421542
return false;
1543-
nValue += s.second;
1543+
nValueOut += s.second;
15441544
}
1545-
if (vecSend.empty() || nValue < 0)
1545+
if (vecSend.empty() || nValueOut < 0)
15461546
return false;
15471547

15481548
wtxNew.BindWallet(this);
@@ -1559,24 +1559,36 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
15591559
wtxNew.vout.clear();
15601560
wtxNew.fFromMe = true;
15611561

1562-
int64_t nTotalValue = nValue + nFeeRet;
1562+
int64_t nTotalValue = nValueOut + nFeeRet;
15631563
double dPriority = 0;
15641564
// vouts to the payees
15651565
for (auto const& s : vecSend)
15661566
wtxNew.vout.push_back(CTxOut(s.second, s.first));
15671567

1568-
// Choose coins to use
1569-
set<pair<const CWalletTx*,unsigned int> > setCoins;
15701568
int64_t nValueIn = 0;
1571-
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
1572-
return false;
1569+
1570+
// If provided coin set is empty, choose coins to use.
1571+
if (!setCoins.size())
1572+
{
1573+
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
1574+
return false;
1575+
}
1576+
else
1577+
{
1578+
// Add up input value for the provided set of coins.
1579+
for (auto const& input : setCoins)
1580+
{
1581+
nValueIn += input.first->vout[input.second].nValue;
1582+
}
1583+
}
1584+
15731585
for (auto const& pcoin : setCoins)
15741586
{
15751587
int64_t nCredit = pcoin.first->vout[pcoin.second].nValue;
15761588
dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain();
15771589
}
15781590

1579-
int64_t nChange = nValueIn - nValue - nFeeRet;
1591+
int64_t nChange = nValueIn - nValueOut - nFeeRet;
15801592
// if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE
15811593
// or until nChange becomes zero
15821594
// NOTE: this depends on the exact behaviour of GetMinFee
@@ -1659,6 +1671,18 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
16591671
return true;
16601672
}
16611673

1674+
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey,
1675+
int64_t& nFeeRet, const CCoinControl* coinControl)
1676+
{
1677+
// Initialize setCoins empty to let CreateTransaction choose via SelectCoins...
1678+
set<pair<const CWalletTx*,unsigned int>> setCoins;
1679+
1680+
return CreateTransaction(vecSend, setCoins, wtxNew, reservekey, nFeeRet, coinControl);
1681+
}
1682+
1683+
1684+
1685+
16621686
bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl)
16631687
{
16641688
vector< pair<CScript, int64_t> > vecSend;

src/wallet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <string>
99
#include <vector>
10+
#include <set>
1011
#include <stdlib.h>
1112
#include "main.h"
1213
#include "key.h"
@@ -199,6 +200,7 @@ class CWallet : public CCryptoKeyStore
199200
int64_t GetStake() const;
200201
int64_t GetNewMint() const;
201202
bool CreateTransaction(const std::vector<std::pair<CScript, int64_t> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
203+
bool CreateTransaction(const std::vector<std::pair<CScript, int64_t> >& vecSend, std::set<std::pair<const CWalletTx*,unsigned int>>& setCoins, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
202204
bool CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl *coinControl=NULL);
203205
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
204206

0 commit comments

Comments
 (0)