From 9167b228998e353585d2d5e45826d57dfddf534e Mon Sep 17 00:00:00 2001 From: MASM fan Date: Sat, 2 Aug 2014 22:37:00 +0400 Subject: [PATCH] RPC: Add mergecoins function This method allows you to merge inputs according to specified criteria --- src/bitcoinrpc.cpp | 4 ++ src/bitcoinrpc.h | 1 + src/rpcwallet.cpp | 47 +++++++++++++++++++ src/wallet.cpp | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/wallet.h | 1 + 5 files changed, 181 insertions(+), 0 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index d93cab8..80e6865 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -253,6 +253,7 @@ static const CRPCCommand vRPCCommands[] = { "getaccount", &getaccount, false, false }, { "getaddressesbyaccount", &getaddressesbyaccount, true, false }, { "sendtoaddress", &sendtoaddress, false, false }, + { "mergecoins", &mergecoins, false, false }, { "getreceivedbyaddress", &getreceivedbyaddress, false, false }, { "getreceivedbyaccount", &getreceivedbyaccount, false, false }, { "listreceivedbyaddress", &listreceivedbyaddress, false, false }, @@ -1184,6 +1185,9 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 0) ConvertTo(params[0]); if (strMethod == "sendtoaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "mergecoins" && n > 0) ConvertTo(params[0]); + if (strMethod == "mergecoins" && n > 1) ConvertTo(params[1]); + if (strMethod == "mergecoins" && n > 2) ConvertTo(params[2]); if (strMethod == "settxfee" && n > 0) ConvertTo(params[0]); if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo(params[1]); if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo(params[1]); diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 102ea4b..0a0a788 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -197,6 +197,7 @@ extern json_spirit::Value checkwallet(const json_spirit::Array& params, bool fHe extern json_spirit::Value repairwallet(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value resendtx(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value makekeypair(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value mergecoins(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 8558036..ed024a5 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -255,6 +255,53 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) return ret; } +Value mergecoins(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 3) + throw runtime_error( + "mergecoins \n" + " is resulting inputs sum\n" + " is resulting value of inputs which will be created\n" + " is maximum value of inputs which are used in join process\n" + "All values are real and and rounded to the nearest " + FormatMoney(MIN_TXOUT_AMOUNT) + + HelpRequiringPassphrase()); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + // Amount + int64 nAmount = AmountFromValue(params[0]); + + // Amount + int64 nOutputValue = AmountFromValue(params[1]); + + // Amount + int64 nMaxValue = AmountFromValue(params[2]); + + if (nAmount < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Send amount too small"); + + if (nOutputValue < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Output value too small"); + + if (nMaxValue < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Max value too small"); + + if (nOutputValue < nMaxValue) + throw JSONRPCError(-101, "Output value is lower than max value"); + + + list listMerged; + if (!pwalletMain->MergeCoins(nAmount, nMaxValue, nOutputValue, listMerged)) + return Value::null; + + Array mergedHashes; + BOOST_FOREACH(const uint256 txHash, listMerged) + mergedHashes.push_back(txHash.GetHex()); + + return mergedHashes; +} + Value sendtoaddress(const Array& params, bool fHelp) { if (fHelp || params.size() < 2 || params.size() > 4) diff --git a/src/wallet.cpp b/src/wallet.cpp index e1dc9eb..851bcbe 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1693,6 +1693,134 @@ bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint return true; } +bool CWallet::MergeCoins(const int64& nAmount, const int64& nMaxValue, const int64& nOutputValue, list& listMerged) +{ + int64 nBalance = GetBalance(); + + if (nAmount > nBalance) + return false; + + listMerged.clear(); + int64 nValueIn = 0; + set > setCoins; + + // Simple coins selection - no randomization + if (!SelectCoinsSimple(nAmount, GetTime(), 1, setCoins, nValueIn)) + return false; + + if (setCoins.empty()) + return false; + + CWalletTx wtxNew; + vector vwtxPrev; + + // Reserve a new key pair from key pool + CReserveKey reservekey(this); + CPubKey vchPubKey = reservekey.GetReservedKey(); + + // Output script + CScript scriptOutput; + scriptOutput.SetDestination(vchPubKey.GetID()); + + // Insert output + wtxNew.vout.push_back(CTxOut(0, scriptOutput)); + + double dWeight = 0; + + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + int64 nCredit = pcoin.first->vout[pcoin.second].nValue; + + // Ignore coin if credit is too high + if (nCredit >= nMaxValue) + continue; + + // Ignore immature coins + if (pcoin.first->GetBlocksToMaturity() > 0) + continue; + + // Add current coin to inputs list and add its credit to transaction output + wtxNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + wtxNew.vout[0].nValue += nCredit; + vwtxPrev.push_back(pcoin.first); + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign scripts to get actual transaction size for fee calculation + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; + } + + int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); + dWeight += (double)nCredit * pcoin.first->GetDepthInMainChain(); + + double dFinalPriority = dWeight /= nBytes; + bool fAllowFree = CTransaction::AllowFree(dFinalPriority); + + // Get actual transaction fee according to its size and priority + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); + + // Prepare transaction for commit if sum is enough ot its size is too big + if (nBytes >= MAX_BLOCK_SIZE_GEN/6 || (wtxNew.vout[0].nValue >= nOutputValue && wtxNew.vout.size() > 1)) + { + wtxNew.vout[0].nValue -= nMinFee; // Set actual fee + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign all scripts again + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; + } + + // Try to commit, return false on failure + if (!CommitTransaction(wtxNew, reservekey)) + return false; + + listMerged.push_back(wtxNew.GetHash()); // Add to hashes list + + dWeight = 0; // Reset all temporary values + vwtxPrev.clear(); + wtxNew.SetNull(); + wtxNew.vout.push_back(CTxOut(0, scriptOutput)); + } + } + + // Create transactions if there are some unhandled coins left + if (wtxNew.vout[0].nValue > 0) { + int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); + + double dFinalPriority = dWeight /= nBytes; + bool fAllowFree = CTransaction::AllowFree(dFinalPriority); + + // Get actual transaction fee according to its size and priority + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); + + wtxNew.vout[0].nValue -= nMinFee; // Set actual fee + + if (wtxNew.vout[0].nValue <= 0) + return false; + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign all scripts again + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; + } + + // Try to commit, return false on failure + if (!CommitTransaction(wtxNew, reservekey)) + return false; + + listMerged.push_back(wtxNew.GetHash()); // Add to hashes list + } + + return true; +} + + bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew, CKey& key) { // The following combine threshold is important to security diff --git a/src/wallet.h b/src/wallet.h index 96fe239..26c49ea 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -200,6 +200,7 @@ public: bool GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint64& nMaxWeight, uint64& nWeight); void GetStakeWeightFromValue(const int64& nTime, const int64& nValue, uint64& nWeight); bool CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew, CKey& key); + bool MergeCoins(const int64& nAmount, const int64& nMaxValue, const int64& nOutputValue, list& listMerged); std::string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); std::string SendMoneyToDestination(const CTxDestination &address, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); -- 1.7.1