Skip to content

Commit 2317f22

Browse files
authored
Merge pull request #1472 from jamescowens/consolidateunspentoutputs
consolidateunspent rpc function
2 parents 7aee79d + 8ded881 commit 2317f22

File tree

6 files changed

+204
-12
lines changed

6 files changed

+204
-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+
{ "consolidateunspent" , 1 },
136+
{ "consolidateunspent" , 2 },
135137
{ "move" , 2 },
136138
{ "move" , 3 },
137139
{ "rainbymagnitude" , 0 },

src/rpcrawtransaction.cpp

100644100755
Lines changed: 162 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,167 @@ UniValue listunspent(const UniValue& params, bool fHelp)
604605
}
605606

606607

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

608770
UniValue createrawtransaction(const UniValue& params, bool fHelp)
609771
{

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+
{ "consolidateunspent", &consolidateunspent, 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 consolidateunspent(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)