From b931ed8563eff9021ce3b1a05d8e6bc21117dc71 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 16 Dec 2010 15:48:04 -0500 Subject: [PATCH] sendmany RPC command, to send to multiple recipients in one transaction. --- main.cpp | 48 ++++++++++++++++++++++++------------- main.h | 1 + rpc.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 16 deletions(-) diff --git a/main.cpp b/main.cpp index c65cd72d..50562b76 100644 --- a/main.cpp +++ b/main.cpp @@ -678,7 +678,11 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi // Safety limits unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK); - if (GetSigOpCount() > 2 || nSize < 100) + // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service + // attacks disallow transactions with more than one SigOp per 34 bytes. + // 34 bytes because a TxOut is: + // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length + if (GetSigOpCount() > nSize / 34 || nSize < 100) return error("AcceptToMemoryPool() : nonstandard transaction"); // Rather not work on nonstandard transactions @@ -3846,8 +3850,18 @@ bool SelectCoins(int64 nTargetValue, set& setCoinsRet) -bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) { + int64 nValue = 0; + foreach (const PAIRTYPE(CScript, int64)& s, vecSend) + { + if (nValue < 0) + return false; + nValue += s.second; + } + if (vecSend.empty() || nValue < 0) + return false; + CRITICAL_BLOCK(cs_main) { // txdb must be opened before the mapWallet lock @@ -3860,11 +3874,12 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CR wtxNew.vin.clear(); wtxNew.vout.clear(); wtxNew.fFromMe = true; - if (nValue < 0) - return false; - int64 nValueOut = nValue; + int64 nTotalValue = nValue + nFeeRet; double dPriority = 0; + // vouts to the payees + foreach (const PAIRTYPE(CScript, int64)& s, vecSend) + wtxNew.vout.push_back(CTxOut(s.second, s.first)); // Choose coins to use set setCoins; @@ -3878,11 +3893,6 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CR dPriority += (double)nCredit * pcoin->GetDepthInMainChain(); } - // Fill a vout to the payee - bool fChangeFirst = GetRand(2); - if (!fChangeFirst) - wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); - // Fill a vout back to self with any change int64 nChange = nValueIn - nTotalValue; if (nChange >= CENT) @@ -3900,19 +3910,18 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CR // Fill a vout to ourself, using same address type as the payment CScript scriptChange; - if (scriptPubKey.GetBitcoinAddressHash160() != 0) + if (vecSend[0].first.GetBitcoinAddressHash160() != 0) scriptChange.SetBitcoinAddress(vchPubKey); else scriptChange << vchPubKey << OP_CHECKSIG; - wtxNew.vout.push_back(CTxOut(nChange, scriptChange)); + + // Insert change txn at random position: + vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); + wtxNew.vout.insert(position, CTxOut(nChange, scriptChange)); } else reservekey.ReturnKey(); - // Fill a vout to the payee - if (fChangeFirst) - wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); - // Fill vin foreach(CWalletTx* pcoin, setCoins) for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) @@ -3954,6 +3963,13 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CR return true; } +bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +{ + vector< pair > vecSend; + vecSend.push_back(make_pair(scriptPubKey, nValue)); + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet); +} + // Call after CreateTransaction unless you want to abort bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { diff --git a/main.h b/main.h index 668d7f98..e9d0c003 100644 --- a/main.h +++ b/main.h @@ -77,6 +77,7 @@ bool ProcessMessages(CNode* pfrom); bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv); bool SendMessages(CNode* pto, bool fSendTrickle); int64 GetBalance(); +bool CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool BroadcastTransaction(CWalletTx& wtxNew); diff --git a/rpc.cpp b/rpc.cpp index dd94acc0..97710ff6 100644 --- a/rpc.cpp +++ b/rpc.cpp @@ -768,6 +768,69 @@ Value sendfrom(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } +Value sendmany(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "sendmany {address:amount,...} [minconf=1] [comment]\n" + "amounts are double-precision floating point numbers"); + + string strAccount = AccountFromValue(params[0]); + Object sendTo = params[1].get_obj(); + int nMinDepth = 1; + if (params.size() > 2) + nMinDepth = params[2].get_int(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["comment"] = params[3].get_str(); + + set setAddress; + vector > vecSend; + + int64 totalAmount = 0; + foreach(const Pair& s, sendTo) + { + uint160 hash160; + string strAddress = s.name_; + + if (setAddress.count(strAddress)) + throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+strAddress); + setAddress.insert(strAddress); + + CScript scriptPubKey; + if (!scriptPubKey.SetBitcoinAddress(strAddress)) + throw JSONRPCError(-5, string("Invalid bitcoin address:")+strAddress); + int64 nAmount = AmountFromValue(s.value_); + totalAmount += nAmount; + + vecSend.push_back(make_pair(scriptPubKey, nAmount)); + } + + CRITICAL_BLOCK(cs_mapWallet) + { + // Check funds + int64 nBalance = GetAccountBalance(strAccount, nMinDepth); + if (totalAmount > nBalance) + throw JSONRPCError(-6, "Account has insufficient funds"); + + // Send + CReserveKey keyChange; + int64 nFeeRequired = 0; + bool fCreated = CreateTransaction(vecSend, wtx, keyChange, nFeeRequired); + if (!fCreated) + { + if (totalAmount + nFeeRequired > GetBalance()) + throw JSONRPCError(-6, "Insufficient funds"); + throw JSONRPCError(-4, "Transaction creation failed"); + } + if (!CommitTransaction(wtx, keyChange)) + throw JSONRPCError(-4, "Transaction commit failed"); + } + + return wtx.GetHash().GetHex(); +} struct tallyitem @@ -1344,6 +1407,7 @@ pair pCallTable[] = make_pair("getbalance", &getbalance), make_pair("move", &movecmd), make_pair("sendfrom", &sendfrom), + make_pair("sendmany", &sendmany), make_pair("gettransaction", &gettransaction), make_pair("listtransactions", &listtransactions), make_pair("getwork", &getwork), @@ -1995,6 +2059,15 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "sendfrom" && n > 3) ConvertTo(params[3]); if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); if (strMethod == "listaccounts" && n > 0) ConvertTo(params[0]); + if (strMethod == "sendmany" && n > 1) + { + string s = params[1].get_str(); + Value v; + if (!read_string(s, v) || v.type() != obj_type) + throw runtime_error("type mismatch"); + params[1] = v.get_obj(); + } + if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); // Execute Object reply = CallRPC(strMethod, params);