diff options
Diffstat (limited to 'src/bitcoinrpc.cpp')
-rw-r--r-- | src/bitcoinrpc.cpp | 1709 |
1 files changed, 1125 insertions, 584 deletions
diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index d8d65e7f42..3af298a58b 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1,31 +1,34 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2011 The Bitcoin developers +// Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying -// file license.txt or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "headers.h" +#include "main.h" +#include "wallet.h" #include "db.h" +#include "walletdb.h" #include "net.h" #include "init.h" +#include "ui_interface.h" +#include "base58.h" +#include "bitcoinrpc.h" + #undef printf #include <boost/asio.hpp> +#include <boost/asio/ip/v6_only.hpp> +#include <boost/bind.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> #include <boost/iostreams/concepts.hpp> #include <boost/iostreams/stream.hpp> #include <boost/algorithm/string.hpp> -#ifdef USE_SSL -#include <boost/asio/ssl.hpp> -#include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/asio/ssl.hpp> #include <boost/filesystem/fstream.hpp> -typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SSLStream; -#endif -#include "json/json_spirit_reader_template.h" -#include "json/json_spirit_writer_template.h" -#include "json/json_spirit_utils.h" +#include <boost/shared_ptr.hpp> +#include <list> + #define printf OutputDebugStringF -// MinGW 3.4.5 gets "fatal error: had to relocate PCH" if the json headers are -// precompiled in headers.h. The problem might be when the pch file goes over -// a certain size around 145MB. If we need access to json_spirit outside this -// file, we could use the compiled json_spirit option. using namespace std; using namespace boost; @@ -33,12 +36,26 @@ using namespace boost::asio; using namespace json_spirit; void ThreadRPCServer2(void* parg); -typedef Value(*rpcfn_type)(const Array& params, bool fHelp); -extern map<string, rpcfn_type> mapCallTable; + +static std::string strRPCUserColonPass; static int64 nWalletUnlockTime; static CCriticalSection cs_nWalletUnlockTime; +extern Value getconnectioncount(const Array& params, bool fHelp); // in rpcnet.cpp +extern Value getpeerinfo(const Array& params, bool fHelp); +extern Value dumpprivkey(const Array& params, bool fHelp); // in rpcdump.cpp +extern Value importprivkey(const Array& params, bool fHelp); +extern Value getrawtransaction(const Array& params, bool fHelp); // in rcprawtransaction.cpp +extern Value listunspent(const Array& params, bool fHelp); +extern Value createrawtransaction(const Array& params, bool fHelp); +extern Value decoderawtransaction(const Array& params, bool fHelp); +extern Value signrawtransaction(const Array& params, bool fHelp); +extern Value sendrawtransaction(const Array& params, bool fHelp); + +const Object emptyobj; + +void ThreadRPCServer3(void* parg); Object JSONRPCError(int code, const string& message) { @@ -48,22 +65,72 @@ Object JSONRPCError(int code, const string& message) return error; } +void RPCTypeCheck(const Array& params, + const list<Value_type>& typesExpected) +{ + unsigned int i = 0; + BOOST_FOREACH(Value_type t, typesExpected) + { + if (params.size() <= i) + break; + + const Value& v = params[i]; + if (v.type() != t) + { + string err = strprintf("Expected type %s, got %s", + Value_type_name[t], Value_type_name[v.type()]); + throw JSONRPCError(-3, err); + } + i++; + } +} -void PrintConsole(const std::string &format, ...) +void RPCTypeCheck(const Object& o, + const map<string, Value_type>& typesExpected) { - char buffer[50000]; - int limit = sizeof(buffer); - va_list arg_ptr; - va_start(arg_ptr, format); - int ret = _vsnprintf(buffer, limit, format.c_str(), arg_ptr); - va_end(arg_ptr); - if (ret < 0 || ret >= limit) + BOOST_FOREACH(const PAIRTYPE(string, Value_type)& t, typesExpected) { - ret = limit - 1; - buffer[limit-1] = 0; + const Value& v = find_value(o, t.first); + if (v.type() == null_type) + throw JSONRPCError(-3, strprintf("Missing %s", t.first.c_str())); + if (v.type() != t.second) + { + string err = strprintf("Expected type %s for %s, got %s", + Value_type_name[t.second], t.first.c_str(), Value_type_name[v.type()]); + throw JSONRPCError(-3, err); + } } - printf("%s", buffer); - fprintf(stdout, "%s", buffer); +} + +double GetDifficulty(const CBlockIndex* blockindex = NULL) +{ + // Floating point number that is a multiple of the minimum difficulty, + // minimum difficulty = 1.0. + if (blockindex == NULL) + { + if (pindexBest == NULL) + return 1.0; + else + blockindex = pindexBest; + } + + int nShift = (blockindex->nBits >> 24) & 0xff; + + double dDiff = + (double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff); + + while (nShift < 29) + { + dDiff *= 256.0; + nShift++; + } + while (nShift > 29) + { + dDiff /= 256.0; + nShift--; + } + + return dDiff; } @@ -83,9 +150,41 @@ Value ValueFromAmount(int64 amount) return (double)amount / (double)COIN; } +std::string +HexBits(unsigned int nBits) +{ + union { + int32_t nBits; + char cBits[4]; + } uBits; + uBits.nBits = htonl((int32_t)nBits); + return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); +} + +std::string +HelpRequiringPassphrase() +{ + return pwalletMain->IsCrypted() + ? "\nrequires wallet passphrase to be set with walletpassphrase first" + : ""; +} + +void +EnsureWalletIsUnlocked() +{ + if (pwalletMain->IsLocked()) + throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); +} + void WalletTxToJSON(const CWalletTx& wtx, Object& entry) { - entry.push_back(Pair("confirmations", wtx.GetDepthInMainChain())); + int confirms = wtx.GetDepthInMainChain(); + entry.push_back(Pair("confirmations", confirms)); + if (confirms) + { + entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); + entry.push_back(Pair("blockindex", wtx.nIndex)); + } entry.push_back(Pair("txid", wtx.GetHash().GetHex())); entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime())); BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) @@ -100,40 +199,57 @@ string AccountFromValue(const Value& value) return strAccount; } +Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) +{ + Object result; + result.push_back(Pair("hash", block.GetHash().GetHex())); + CMerkleTx txGen(block.vtx[0]); + txGen.SetMerkleBranch(&block); + result.push_back(Pair("confirmations", (int)txGen.GetDepthInMainChain())); + result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); + result.push_back(Pair("height", blockindex->nHeight)); + result.push_back(Pair("version", block.nVersion)); + result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); + Array txs; + BOOST_FOREACH(const CTransaction&tx, block.vtx) + txs.push_back(tx.GetHash().GetHex()); + result.push_back(Pair("tx", txs)); + result.push_back(Pair("time", (boost::int64_t)block.GetBlockTime())); + result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce)); + result.push_back(Pair("bits", HexBits(block.nBits))); + result.push_back(Pair("difficulty", GetDifficulty(blockindex))); + + if (blockindex->pprev) + result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); + if (blockindex->pnext) + result.push_back(Pair("nextblockhash", blockindex->pnext->GetBlockHash().GetHex())); + return result; +} + + /// /// Note: This interface may still be subject to change. /// - -Value help(const Array& params, bool fHelp) +string CRPCTable::help(string strCommand) const { - if (fHelp || params.size() > 1) - throw runtime_error( - "help [command]\n" - "List commands, or get help for a command."); - - string strCommand; - if (params.size() > 0) - strCommand = params[0].get_str(); - string strRet; set<rpcfn_type> setDone; - for (map<string, rpcfn_type>::iterator mi = mapCallTable.begin(); mi != mapCallTable.end(); ++mi) + for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi) { - string strMethod = (*mi).first; + const CRPCCommand *pcmd = mi->second; + string strMethod = mi->first; // We already filter duplicates, but these deprecated screw up the sort order - if (strMethod == "getamountreceived" || - strMethod == "getallreceived" || - (strMethod.find("label") != string::npos)) + if (strMethod.find("label") != string::npos) continue; if (strCommand != "" && strMethod != strCommand) continue; try { Array params; - rpcfn_type pfn = (*mi).second; + rpcfn_type pfn = pcmd->actor; if (setDone.insert(pfn).second) (*pfn)(params, true); } @@ -142,7 +258,7 @@ Value help(const Array& params, bool fHelp) // Help text is returned in an exception string strHelp = string(e.what()); if (strCommand == "") - if (strHelp.find('\n') != -1) + if (strHelp.find('\n') != string::npos) strHelp = strHelp.substr(0, strHelp.find('\n')); strRet += strHelp + "\n"; } @@ -153,20 +269,30 @@ Value help(const Array& params, bool fHelp) return strRet; } +Value help(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "help [command]\n" + "List commands, or get help for a command."); + + string strCommand; + if (params.size() > 0) + strCommand = params[0].get_str(); + + return tableRPC.help(strCommand); +} + Value stop(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( "stop\n" - "Stop bitcoin server."); -#ifndef QT_GUI + "Stop Bitcoin server."); // Shutdown will take long enough that the response should get back - CreateThread(Shutdown, NULL); - return "bitcoin server stopping"; -#else - throw runtime_error("NYI: cannot shut down GUI with RPC command"); -#endif + StartShutdown(); + return "Bitcoin server stopping"; } @@ -181,54 +307,6 @@ Value getblockcount(const Array& params, bool fHelp) } -Value getblocknumber(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getblocknumber\n" - "Returns the block number of the latest block in the longest block chain."); - - return nBestHeight; -} - - -Value getconnectioncount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getconnectioncount\n" - "Returns the number of connections to other nodes."); - - return (int)vNodes.size(); -} - - -double GetDifficulty() -{ - // Floating point number that is a multiple of the minimum difficulty, - // minimum difficulty = 1.0. - - if (pindexBest == NULL) - return 1.0; - int nShift = (pindexBest->nBits >> 24) & 0xff; - - double dDiff = - (double)0x0000ffff / (double)(pindexBest->nBits & 0x00ffffff); - - while (nShift < 29) - { - dDiff *= 256.0; - nShift++; - } - while (nShift > 29) - { - dDiff /= 256.0; - nShift--; - } - - return dDiff; -} - Value getdifficulty(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -247,7 +325,7 @@ Value getgenerate(const Array& params, bool fHelp) "getgenerate\n" "Returns true or false."); - return (bool)fGenerateBitcoins; + return GetBoolArg("-gen"); } @@ -266,13 +344,11 @@ Value setgenerate(const Array& params, bool fHelp) if (params.size() > 1) { int nGenProcLimit = params[1].get_int(); - fLimitProcessors = (nGenProcLimit != -1); - WriteSetting("fLimitProcessors", fLimitProcessors); - if (nGenProcLimit != -1) - WriteSetting("nLimitProcessors", nLimitProcessors = nGenProcLimit); + mapArgs["-genproclimit"] = itostr(nGenProcLimit); if (nGenProcLimit == 0) fGenerate = false; } + mapArgs["-gen"] = (fGenerate ? "1" : "0"); GenerateBitcoins(fGenerate, pwalletMain); return Value::null; @@ -299,33 +375,57 @@ Value getinfo(const Array& params, bool fHelp) "getinfo\n" "Returns an object containing various state info."); + CService addrProxy; + GetProxy(NET_IPV4, addrProxy); + Object obj; - obj.push_back(Pair("version", (int)VERSION)); + obj.push_back(Pair("version", (int)CLIENT_VERSION)); + obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION)); + obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); obj.push_back(Pair("blocks", (int)nBestHeight)); obj.push_back(Pair("connections", (int)vNodes.size())); - obj.push_back(Pair("proxy", (fUseProxy ? addrProxy.ToStringIPPort() : string()))); - obj.push_back(Pair("generate", (bool)fGenerateBitcoins)); - obj.push_back(Pair("genproclimit", (int)(fLimitProcessors ? nLimitProcessors : -1))); + obj.push_back(Pair("proxy", (addrProxy.IsValid() ? addrProxy.ToStringIPPort() : string()))); obj.push_back(Pair("difficulty", (double)GetDifficulty())); - obj.push_back(Pair("hashespersec", gethashespersec(params, false))); obj.push_back(Pair("testnet", fTestNet)); obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime())); obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); if (pwalletMain->IsCrypted()) - obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime)); + obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000)); obj.push_back(Pair("errors", GetWarnings("statusbar"))); return obj; } +Value getmininginfo(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getmininginfo\n" + "Returns an object containing mining-related information."); + + Object obj; + obj.push_back(Pair("blocks", (int)nBestHeight)); + obj.push_back(Pair("currentblocksize",(uint64_t)nLastBlockSize)); + obj.push_back(Pair("currentblocktx",(uint64_t)nLastBlockTx)); + obj.push_back(Pair("difficulty", (double)GetDifficulty())); + obj.push_back(Pair("errors", GetWarnings("statusbar"))); + obj.push_back(Pair("generate", GetBoolArg("-gen"))); + obj.push_back(Pair("genproclimit", (int)GetArg("-genproclimit", -1))); + obj.push_back(Pair("hashespersec", gethashespersec(params, false))); + obj.push_back(Pair("pooledtx", (uint64_t)mempool.size())); + obj.push_back(Pair("testnet", fTestNet)); + return obj; +} + + Value getnewaddress(const Array& params, bool fHelp) { if (fHelp || params.size() > 1) throw runtime_error( "getnewaddress [account]\n" - "Returns a new bitcoin address for receiving payments. " + "Returns a new Bitcoin address for receiving payments. " "If [account] is specified (recommended), it is added to the address book " "so payments received with the address will be credited to [account]."); @@ -338,14 +438,14 @@ Value getnewaddress(const Array& params, bool fHelp) pwalletMain->TopUpKeyPool(); // Generate a new key that is added to wallet - std::vector<unsigned char> newKey; + CPubKey newKey; if (!pwalletMain->GetKeyFromPool(newKey, false)) throw JSONRPCError(-12, "Error: Keypool ran out, please call keypoolrefill first"); - CBitcoinAddress address(newKey); + CKeyID keyID = newKey.GetID(); - pwalletMain->SetAddressBookName(address, strAccount); + pwalletMain->SetAddressBookName(keyID, strAccount); - return address.ToString(); + return CBitcoinAddress(keyID).ToString(); } @@ -359,12 +459,12 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) bool bKeyUsed = false; // Check if the current key has been used - if (!account.vchPubKey.empty()) + if (account.vchPubKey.IsValid()) { CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(account.vchPubKey); + scriptPubKey.SetDestination(account.vchPubKey.GetID()); for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); - it != pwalletMain->mapWallet.end() && !account.vchPubKey.empty(); + it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); ++it) { const CWalletTx& wtx = (*it).second; @@ -375,16 +475,16 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) } // Generate a new key - if (account.vchPubKey.empty() || bForceNew || bKeyUsed) + if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) { if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) throw JSONRPCError(-12, "Error: Keypool ran out, please call keypoolrefill first"); - pwalletMain->SetAddressBookName(CBitcoinAddress(account.vchPubKey), strAccount); + pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), strAccount); walletdb.WriteAccount(strAccount, account); } - return CBitcoinAddress(account.vchPubKey); + return CBitcoinAddress(account.vchPubKey.GetID()); } Value getaccountaddress(const Array& params, bool fHelp) @@ -392,7 +492,7 @@ Value getaccountaddress(const Array& params, bool fHelp) if (fHelp || params.size() != 1) throw runtime_error( "getaccountaddress <account>\n" - "Returns the current bitcoin address for receiving payments to this account."); + "Returns the current Bitcoin address for receiving payments to this account."); // Parse the account first so we don't generate a key if there's an error string strAccount = AccountFromValue(params[0]); @@ -415,7 +515,7 @@ Value setaccount(const Array& params, bool fHelp) CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); string strAccount; @@ -423,14 +523,14 @@ Value setaccount(const Array& params, bool fHelp) strAccount = AccountFromValue(params[1]); // Detect when changing the account of an address that is the 'unused current key' of another account: - if (pwalletMain->mapAddressBook.count(address)) + if (pwalletMain->mapAddressBook.count(address.Get())) { - string strOldAccount = pwalletMain->mapAddressBook[address]; + string strOldAccount = pwalletMain->mapAddressBook[address.Get()]; if (address == GetAccountAddress(strOldAccount)) GetAccountAddress(strOldAccount, true); } - pwalletMain->SetAddressBookName(address, strAccount); + pwalletMain->SetAddressBookName(address.Get(), strAccount); return Value::null; } @@ -445,10 +545,10 @@ Value getaccount(const Array& params, bool fHelp) CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); string strAccount; - map<CBitcoinAddress, string>::iterator mi = pwalletMain->mapAddressBook.find(address); + map<CTxDestination, string>::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.empty()) strAccount = (*mi).second; return strAccount; @@ -494,19 +594,15 @@ Value settxfee(const Array& params, bool fHelp) Value sendtoaddress(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + if (fHelp || params.size() < 2 || params.size() > 4) throw runtime_error( "sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001"); + "<amount> is a real and is rounded to the nearest 0.00000001" + + HelpRequiringPassphrase()); CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); // Amount int64 nAmount = AmountFromValue(params[1]); @@ -521,15 +617,13 @@ Value sendtoaddress(const Array& params, bool fHelp) if (pwalletMain->IsLocked()) throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - string strError = pwalletMain->SendMoneyToBitcoinAddress(address, nAmount, wtx); + string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); if (strError != "") throw JSONRPCError(-4, strError); return wtx.GetHash().GetHex(); } -static const string strMessageMagic = "Bitcoin Signed Message:\n"; - Value signmessage(const Array& params, bool fHelp) { if (fHelp || params.size() != 2) @@ -537,8 +631,7 @@ Value signmessage(const Array& params, bool fHelp) "signmessage <bitcoinaddress> <message>\n" "Sign a message with the private key of an address"); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); string strAddress = params[0].get_str(); string strMessage = params[1].get_str(); @@ -547,11 +640,15 @@ Value signmessage(const Array& params, bool fHelp) if (!addr.IsValid()) throw JSONRPCError(-3, "Invalid address"); + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + throw JSONRPCError(-3, "Address does not refer to key"); + CKey key; - if (!pwalletMain->GetKey(addr, key)) + if (!pwalletMain->GetKey(keyID, key)) throw JSONRPCError(-4, "Private key not available"); - CDataStream ss(SER_GETHASH); + CDataStream ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; @@ -577,13 +674,17 @@ Value verifymessage(const Array& params, bool fHelp) if (!addr.IsValid()) throw JSONRPCError(-3, "Invalid address"); + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + throw JSONRPCError(-3, "Address does not refer to key"); + bool fInvalid = false; vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid); if (fInvalid) throw JSONRPCError(-5, "Malformed base64 encoding"); - CDataStream ss(SER_GETHASH); + CDataStream ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; @@ -591,7 +692,7 @@ Value verifymessage(const Array& params, bool fHelp) if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig)) return false; - return (key.GetAddress() == addr); + return (key.GetPubKey().GetID() == keyID); } @@ -606,8 +707,8 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); CScript scriptPubKey; if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); - scriptPubKey.SetBitcoinAddress(address); + throw JSONRPCError(-5, "Invalid Bitcoin address"); + scriptPubKey.SetDestination(address.Get()); if (!IsMine(*pwalletMain,scriptPubKey)) return (double)0.0; @@ -634,18 +735,17 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) } -void GetAccountAddresses(string strAccount, set<CBitcoinAddress>& setAddress) +void GetAccountAddresses(string strAccount, set<CTxDestination>& setAddress) { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) + BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& item, pwalletMain->mapAddressBook) { - const CBitcoinAddress& address = item.first; + const CTxDestination& address = item.first; const string& strName = item.second; if (strName == strAccount) setAddress.insert(address); } } - Value getreceivedbyaccount(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) @@ -658,9 +758,9 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) if (params.size() > 1) nMinDepth = params[1].get_int(); - // Get the set of pub keys that have the label + // Get the set of pub keys assigned to account string strAccount = AccountFromValue(params[0]); - set<CBitcoinAddress> setAddress; + set<CTxDestination> setAddress; GetAccountAddresses(strAccount, setAddress); // Tally @@ -673,8 +773,8 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) BOOST_FOREACH(const CTxOut& txout, wtx.vout) { - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, pwalletMain, address) && setAddress.count(address)) + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; } @@ -745,13 +845,15 @@ Value getbalance(const Array& params, bool fHelp) int64 allGeneratedImmature, allGeneratedMature, allFee; allGeneratedImmature = allGeneratedMature = allFee = 0; string strSentAccount; - list<pair<CBitcoinAddress, int64> > listReceived; - list<pair<CBitcoinAddress, int64> > listSent; + list<pair<CTxDestination, int64> > listReceived; + list<pair<CTxDestination, int64> > listSent; wtx.GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); if (wtx.GetDepthInMainChain() >= nMinDepth) - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived) + { + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived) nBalance += r.second; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listSent) + } + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listSent) nBalance -= r.second; nBalance -= allFee; nBalance += allGeneratedMature; @@ -785,7 +887,8 @@ Value movecmd(const Array& params, bool fHelp) strComment = params[4].get_str(); CWalletDB walletdb(pwalletMain->strWalletFile); - walletdb.TxnBegin(); + if (!walletdb.TxnBegin()) + throw JSONRPCError(-20, "database error"); int64 nNow = GetAdjustedTime(); @@ -807,7 +910,8 @@ Value movecmd(const Array& params, bool fHelp) credit.strComment = strComment; walletdb.WriteAccountingEntry(credit); - walletdb.TxnCommit(); + if (!walletdb.TxnCommit()) + throw JSONRPCError(-20, "database error"); return true; } @@ -815,20 +919,16 @@ Value movecmd(const Array& params, bool fHelp) Value sendfrom(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) - throw runtime_error( - "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) + if (fHelp || params.size() < 3 || params.size() > 6) throw runtime_error( "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001"); + "<amount> is a real and is rounded to the nearest 0.00000001" + + HelpRequiringPassphrase()); string strAccount = AccountFromValue(params[0]); CBitcoinAddress address(params[1].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); int64 nAmount = AmountFromValue(params[2]); int nMinDepth = 1; if (params.size() > 3) @@ -841,8 +941,7 @@ Value sendfrom(const Array& params, bool fHelp) if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) wtx.mapValue["to"] = params[5].get_str(); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -850,7 +949,7 @@ Value sendfrom(const Array& params, bool fHelp) throw JSONRPCError(-6, "Account has insufficient funds"); // Send - string strError = pwalletMain->SendMoneyToBitcoinAddress(address, nAmount, wtx); + string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); if (strError != "") throw JSONRPCError(-4, strError); @@ -860,15 +959,11 @@ Value sendfrom(const Array& params, bool fHelp) Value sendmany(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + if (fHelp || params.size() < 2 || params.size() > 4) throw runtime_error( "sendmany <fromaccount> {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendmany <fromaccount> {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers"); + "amounts are double-precision floating point numbers" + + HelpRequiringPassphrase()); string strAccount = AccountFromValue(params[0]); Object sendTo = params[1].get_obj(); @@ -889,22 +984,21 @@ Value sendmany(const Array& params, bool fHelp) { CBitcoinAddress address(s.name_); if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid bitcoin address:")+s.name_); + throw JSONRPCError(-5, string("Invalid Bitcoin address:")+s.name_); if (setAddress.count(address)) throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); setAddress.insert(address); CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(address); - int64 nAmount = AmountFromValue(s.value_); + scriptPubKey.SetDestination(address.Get()); + int64 nAmount = AmountFromValue(s.value_); totalAmount += nAmount; vecSend.push_back(make_pair(scriptPubKey, nAmount)); } - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -927,6 +1021,75 @@ Value sendmany(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } +Value addmultisigaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 3) + { + string msg = "addmultisigaddress <nrequired> <'[\"key\",\"key\"]'> [account]\n" + "Add a nrequired-to-sign multisignature address to the wallet\"\n" + "each key is a Bitcoin address or hex-encoded public key\n" + "If [account] is specified, assign address to [account]."; + throw runtime_error(msg); + } + + int nRequired = params[0].get_int(); + const Array& keys = params[1].get_array(); + string strAccount; + if (params.size() > 2) + strAccount = AccountFromValue(params[2]); + + // Gather public keys + if (nRequired < 1) + throw runtime_error("a multisignature address must require at least one key to redeem"); + if ((int)keys.size() < nRequired) + throw runtime_error( + strprintf("not enough keys supplied " + "(got %d keys, but need at least %d to redeem)", keys.size(), nRequired)); + std::vector<CKey> pubkeys; + pubkeys.resize(keys.size()); + for (unsigned int i = 0; i < keys.size(); i++) + { + const std::string& ks = keys[i].get_str(); + + // Case 1: Bitcoin address and we have full public key: + CBitcoinAddress address(ks); + if (address.IsValid()) + { + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw runtime_error( + strprintf("%s does not refer to a key",ks.c_str())); + CPubKey vchPubKey; + if (!pwalletMain->GetPubKey(keyID, vchPubKey)) + throw runtime_error( + strprintf("no full public key for address %s",ks.c_str())); + if (!vchPubKey.IsValid() || !pubkeys[i].SetPubKey(vchPubKey)) + throw runtime_error(" Invalid public key: "+ks); + } + + // Case 2: hex public key + else if (IsHex(ks)) + { + CPubKey vchPubKey(ParseHex(ks)); + if (!vchPubKey.IsValid() || !pubkeys[i].SetPubKey(vchPubKey)) + throw runtime_error(" Invalid public key: "+ks); + } + else + { + throw runtime_error(" Invalid public key: "+ks); + } + } + + // Construct using pay-to-script-hash: + CScript inner; + inner.SetMultisig(nRequired, pubkeys); + CScriptID innerID = inner.GetID(); + pwalletMain->AddCScript(inner); + + pwalletMain->SetAddressBookName(innerID, strAccount); + return CBitcoinAddress(innerID).ToString(); +} + struct tallyitem { @@ -935,7 +1098,7 @@ struct tallyitem tallyitem() { nAmount = 0; - nConf = INT_MAX; + nConf = std::numeric_limits<int>::max(); } }; @@ -956,6 +1119,7 @@ Value ListReceived(const Array& params, bool fByAccounts) for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; + if (wtx.IsCoinBase() || !wtx.IsFinal()) continue; @@ -965,8 +1129,8 @@ Value ListReceived(const Array& params, bool fByAccounts) BOOST_FOREACH(const CTxOut& txout, wtx.vout) { - CBitcoinAddress address; - if (!ExtractAddress(txout.scriptPubKey, pwalletMain, address) || !address.IsValid()) + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address) || !IsMine(*pwalletMain, address)) continue; tallyitem& item = mapTally[address]; @@ -987,7 +1151,7 @@ Value ListReceived(const Array& params, bool fByAccounts) continue; int64 nAmount = 0; - int nConf = INT_MAX; + int nConf = std::numeric_limits<int>::max(); if (it != mapTally.end()) { nAmount = (*it).second.nAmount; @@ -1006,7 +1170,7 @@ Value ListReceived(const Array& params, bool fByAccounts) obj.push_back(Pair("address", address.ToString())); obj.push_back(Pair("account", strAccount)); obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == INT_MAX ? 0 : nConf))); + obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); ret.push_back(obj); } } @@ -1020,7 +1184,7 @@ Value ListReceived(const Array& params, bool fByAccounts) Object obj; obj.push_back(Pair("account", (*it).first)); obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == INT_MAX ? 0 : nConf))); + obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); ret.push_back(obj); } } @@ -1063,8 +1227,9 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe { int64 nGeneratedImmature, nGeneratedMature, nFee; string strSentAccount; - list<pair<CBitcoinAddress, int64> > listReceived; - list<pair<CBitcoinAddress, int64> > listSent; + list<pair<CTxDestination, int64> > listReceived; + list<pair<CTxDestination, int64> > listSent; + wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); bool fAllAccounts = (strAccount == string("*")); @@ -1092,11 +1257,11 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe // Sent if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& s, listSent) + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& s, listSent) { Object entry; entry.push_back(Pair("account", strSentAccount)); - entry.push_back(Pair("address", s.first.ToString())); + entry.push_back(Pair("address", CBitcoinAddress(s.first).ToString())); entry.push_back(Pair("category", "send")); entry.push_back(Pair("amount", ValueFromAmount(-s.second))); entry.push_back(Pair("fee", ValueFromAmount(-nFee))); @@ -1108,7 +1273,8 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe // Received if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& r, listReceived) + { + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived) { string account; if (pwalletMain->mapAddressBook.count(r.first)) @@ -1117,7 +1283,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe { Object entry; entry.push_back(Pair("account", account)); - entry.push_back(Pair("address", r.first.ToString())); + entry.push_back(Pair("address", CBitcoinAddress(r.first).ToString())); entry.push_back(Pair("category", "receive")); entry.push_back(Pair("amount", ValueFromAmount(r.second))); if (fLong) @@ -1125,6 +1291,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe ret.push_back(entry); } } + } } void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Array& ret) @@ -1161,14 +1328,21 @@ Value listtransactions(const Array& params, bool fHelp) if (params.size() > 2) nFrom = params[2].get_int(); + if (nCount < 0) + throw JSONRPCError(-8, "Negative count"); + if (nFrom < 0) + throw JSONRPCError(-8, "Negative from"); + Array ret; CWalletDB walletdb(pwalletMain->strWalletFile); - // Firs: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap: + // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap. typedef pair<CWalletTx*, CAccountingEntry*> TxPair; typedef multimap<int64, TxPair > TxItems; TxItems txByTime; + // Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry + // would make this much faster for applications that do this a lot. for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { CWalletTx* wtx = &((*it).second); @@ -1181,10 +1355,8 @@ Value listtransactions(const Array& params, bool fHelp) txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry))); } - // Now: iterate backwards until we have nCount items to return: - TxItems::reverse_iterator it = txByTime.rbegin(); - if (txByTime.size() > nFrom) std::advance(it, nFrom); - for (; it != txByTime.rend(); ++it) + // iterate backwards until we have nCount items to return: + for (TxItems::reverse_iterator it = txByTime.rbegin(); it != txByTime.rend(); ++it) { CWalletTx *const pwtx = (*it).second.first; if (pwtx != 0) @@ -1193,18 +1365,23 @@ Value listtransactions(const Array& params, bool fHelp) if (pacentry != 0) AcentryToJSON(*pacentry, strAccount, ret); - if (ret.size() >= nCount) break; - } - // ret is now newest to oldest - - // Make sure we return only last nCount items (sends-to-self might give us an extra): - if (ret.size() > nCount) - { - Array::iterator last = ret.begin(); - std::advance(last, nCount); - ret.erase(last, ret.end()); + if ((int)ret.size() >= (nCount+nFrom)) break; } - std::reverse(ret.begin(), ret.end()); // oldest to newest + // ret is newest to oldest + + if (nFrom > (int)ret.size()) + nFrom = ret.size(); + if ((nFrom + nCount) > (int)ret.size()) + nCount = ret.size() - nFrom; + Array::iterator first = ret.begin(); + std::advance(first, nFrom); + Array::iterator last = ret.begin(); + std::advance(last, nFrom+nCount); + + if (last != ret.end()) ret.erase(last, ret.end()); + if (first != ret.begin()) ret.erase(ret.begin(), first); + + std::reverse(ret.begin(), ret.end()); // Return oldest to newest return ret; } @@ -1221,8 +1398,8 @@ Value listaccounts(const Array& params, bool fHelp) nMinDepth = params[0].get_int(); map<string, int64> mapAccountBalances; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& entry, pwalletMain->mapAddressBook) { - if (pwalletMain->HaveKey(entry.first)) // This address belongs to me + BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& entry, pwalletMain->mapAddressBook) { + if (IsMine(*pwalletMain, entry.first)) // This address belongs to me mapAccountBalances[entry.second] = 0; } @@ -1231,16 +1408,16 @@ Value listaccounts(const Array& params, bool fHelp) const CWalletTx& wtx = (*it).second; int64 nGeneratedImmature, nGeneratedMature, nFee; string strSentAccount; - list<pair<CBitcoinAddress, int64> > listReceived; - list<pair<CBitcoinAddress, int64> > listSent; + list<pair<CTxDestination, int64> > listReceived; + list<pair<CTxDestination, int64> > listSent; wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); mapAccountBalances[strSentAccount] -= nFee; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& s, listSent) + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& s, listSent) mapAccountBalances[strSentAccount] -= s.second; if (wtx.GetDepthInMainChain() >= nMinDepth) { mapAccountBalances[""] += nGeneratedMature; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& r, listReceived) + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived) if (pwalletMain->mapAddressBook.count(r.first)) mapAccountBalances[pwalletMain->mapAddressBook[r.first]] += r.second; else @@ -1264,8 +1441,8 @@ Value listsinceblock(const Array& params, bool fHelp) { if (fHelp) throw runtime_error( - "listsinceblock [blockid] [target-confirmations]\n" - "Get all transactions in blocks since block [blockid], or all transactions if omitted"); + "listsinceblock [blockhash] [target-confirmations]\n" + "Get all transactions in blocks since block [blockhash], or all transactions if omitted"); CBlockIndex *pindex = NULL; int target_confirms = 1; @@ -1302,7 +1479,6 @@ Value listsinceblock(const Array& params, bool fHelp) if (target_confirms == 1) { - printf("oops!\n"); lastblock = hashBestChain; } else @@ -1312,7 +1488,7 @@ Value listsinceblock(const Array& params, bool fHelp) CBlockIndex *block; for (block = pindexBest; block && block->nHeight > target_height; - block = block->pprev); + block = block->pprev) { } lastblock = block ? block->GetBlockHash() : 0; } @@ -1329,13 +1505,12 @@ Value gettransaction(const Array& params, bool fHelp) if (fHelp || params.size() != 1) throw runtime_error( "gettransaction <txid>\n" - "Get detailed information about <txid>"); + "Get detailed information about in-wallet transaction <txid>"); uint256 hash; hash.SetHex(params[0].get_str()); Object entry; - if (!pwalletMain->mapWallet.count(hash)) throw JSONRPCError(-5, "Invalid or non-wallet transaction id"); const CWalletTx& wtx = pwalletMain->mapWallet[hash]; @@ -1349,10 +1524,10 @@ Value gettransaction(const Array& params, bool fHelp) if (wtx.IsFromMe()) entry.push_back(Pair("fee", ValueFromAmount(nFee))); - WalletTxToJSON(pwalletMain->mapWallet[hash], entry); + WalletTxToJSON(wtx, entry); Array details; - ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); + ListTransactions(wtx, "*", 0, false, details); entry.push_back(Pair("details", details)); return entry; @@ -1375,17 +1550,13 @@ Value backupwallet(const Array& params, bool fHelp) Value keypoolrefill(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) - throw runtime_error( - "keypoolrefill\n" - "Fills the keypool, requires wallet passphrase to be set."); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) + if (fHelp || params.size() > 0) throw runtime_error( "keypoolrefill\n" - "Fills the keypool."); + "Fills the keypool." + + HelpRequiringPassphrase()); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); pwalletMain->TopUpKeyPool(); @@ -1398,42 +1569,54 @@ Value keypoolrefill(const Array& params, bool fHelp) void ThreadTopUpKeyPool(void* parg) { + // Make this thread recognisable as the key-topping-up thread + RenameThread("bitcoin-key-top"); + pwalletMain->TopUpKeyPool(); } void ThreadCleanWalletPassphrase(void* parg) { - int64 nMyWakeTime = GetTime() + *((int*)parg); + // Make this thread recognisable as the wallet relocking thread + RenameThread("bitcoin-lock-wa"); + + int64 nMyWakeTime = GetTimeMillis() + *((int64*)parg) * 1000; + + ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); if (nWalletUnlockTime == 0) { - CRITICAL_BLOCK(cs_nWalletUnlockTime) + nWalletUnlockTime = nMyWakeTime; + + do { - nWalletUnlockTime = nMyWakeTime; - } + if (nWalletUnlockTime==0) + break; + int64 nToSleep = nWalletUnlockTime - GetTimeMillis(); + if (nToSleep <= 0) + break; - while (GetTime() < nWalletUnlockTime) - Sleep(GetTime() - nWalletUnlockTime); + LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); + Sleep(nToSleep); + ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); - CRITICAL_BLOCK(cs_nWalletUnlockTime) + } while(1); + + if (nWalletUnlockTime) { nWalletUnlockTime = 0; + pwalletMain->Lock(); } } else { - CRITICAL_BLOCK(cs_nWalletUnlockTime) - { - if (nWalletUnlockTime < nMyWakeTime) - nWalletUnlockTime = nMyWakeTime; - } - free(parg); - return; + if (nWalletUnlockTime < nMyWakeTime) + nWalletUnlockTime = nMyWakeTime; } - pwalletMain->Lock(); + LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); - delete (int*)parg; + delete (int64*)parg; } Value walletpassphrase(const Array& params, bool fHelp) @@ -1451,21 +1634,16 @@ Value walletpassphrase(const Array& params, bool fHelp) throw JSONRPCError(-17, "Error: Wallet is already unlocked."); // Note that the walletpassphrase is stored in params[0] which is not mlock()ed - string strWalletPass; + SecureString strWalletPass; strWalletPass.reserve(100); - mlock(&strWalletPass[0], strWalletPass.capacity()); - strWalletPass = params[0].get_str(); + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + strWalletPass = params[0].get_str().c_str(); if (strWalletPass.length() > 0) { if (!pwalletMain->Unlock(strWalletPass)) - { - fill(strWalletPass.begin(), strWalletPass.end(), '\0'); - munlock(&strWalletPass[0], strWalletPass.capacity()); throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); - } - fill(strWalletPass.begin(), strWalletPass.end(), '\0'); - munlock(&strWalletPass[0], strWalletPass.capacity()); } else throw runtime_error( @@ -1473,7 +1651,7 @@ Value walletpassphrase(const Array& params, bool fHelp) "Stores the wallet decryption key in memory for <timeout> seconds."); CreateThread(ThreadTopUpKeyPool, NULL); - int* pnSleepTime = new int(params[1].get_int()); + int64* pnSleepTime = new int64(params[1].get_int64()); CreateThread(ThreadCleanWalletPassphrase, pnSleepTime); return Value::null; @@ -1491,15 +1669,15 @@ Value walletpassphrasechange(const Array& params, bool fHelp) if (!pwalletMain->IsCrypted()) throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); - string strOldWalletPass; + // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + SecureString strOldWalletPass; strOldWalletPass.reserve(100); - mlock(&strOldWalletPass[0], strOldWalletPass.capacity()); - strOldWalletPass = params[0].get_str(); + strOldWalletPass = params[0].get_str().c_str(); - string strNewWalletPass; + SecureString strNewWalletPass; strNewWalletPass.reserve(100); - mlock(&strNewWalletPass[0], strNewWalletPass.capacity()); - strNewWalletPass = params[1].get_str(); + strNewWalletPass = params[1].get_str().c_str(); if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) throw runtime_error( @@ -1507,17 +1685,7 @@ Value walletpassphrasechange(const Array& params, bool fHelp) "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) - { - fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); - fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); - munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); - munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); - } - fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); - fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); - munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); - munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); return Value::null; } @@ -1536,9 +1704,9 @@ Value walletlock(const Array& params, bool fHelp) if (!pwalletMain->IsCrypted()) throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletlock was called."); - pwalletMain->Lock(); - CRITICAL_BLOCK(cs_nWalletUnlockTime) { + LOCK(cs_nWalletUnlockTime); + pwalletMain->Lock(); nWalletUnlockTime = 0; } @@ -1557,15 +1725,11 @@ Value encryptwallet(const Array& params, bool fHelp) if (pwalletMain->IsCrypted()) throw JSONRPCError(-15, "Error: running with an encrypted wallet, but encryptwallet was called."); -#ifdef QT_GUI - // shutting down via RPC while the GUI is running does not work (yet): - throw runtime_error("Not Yet Implemented: use GUI to encrypt wallet, not RPC command"); -#endif - - string strWalletPass; + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + SecureString strWalletPass; strWalletPass.reserve(100); - mlock(&strWalletPass[0], strWalletPass.capacity()); - strWalletPass = params[0].get_str(); + strWalletPass = params[0].get_str().c_str(); if (strWalletPass.length() < 1) throw runtime_error( @@ -1573,21 +1737,49 @@ Value encryptwallet(const Array& params, bool fHelp) "Encrypts the wallet with <passphrase>."); if (!pwalletMain->EncryptWallet(strWalletPass)) - { - fill(strWalletPass.begin(), strWalletPass.end(), '\0'); - munlock(&strWalletPass[0], strWalletPass.capacity()); throw JSONRPCError(-16, "Error: Failed to encrypt the wallet."); - } - fill(strWalletPass.begin(), strWalletPass.end(), '\0'); - munlock(&strWalletPass[0], strWalletPass.capacity()); // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: - CreateThread(Shutdown, NULL); - return "wallet encrypted; bitcoin server stopping, restart to run with encrypted wallet"; + StartShutdown(); + return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet"; } +class DescribeAddressVisitor : public boost::static_visitor<Object> +{ +public: + Object operator()(const CNoDestination &dest) const { return Object(); } + + Object operator()(const CKeyID &keyID) const { + Object obj; + CPubKey vchPubKey; + pwalletMain->GetPubKey(keyID, vchPubKey); + obj.push_back(Pair("isscript", false)); + obj.push_back(Pair("pubkey", HexStr(vchPubKey.Raw()))); + obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); + return obj; + } + + Object operator()(const CScriptID &scriptID) const { + Object obj; + obj.push_back(Pair("isscript", true)); + CScript subscript; + pwalletMain->GetCScript(scriptID, subscript); + std::vector<CTxDestination> addresses; + txnouttype whichType; + int nRequired; + ExtractDestinations(subscript, whichType, addresses, nRequired); + obj.push_back(Pair("script", GetTxnOutputType(whichType))); + Array a; + BOOST_FOREACH(const CTxDestination& addr, addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + obj.push_back(Pair("addresses", a)); + if (whichType == TX_MULTISIG) + obj.push_back(Pair("sigsrequired", nRequired)); + return obj; + } +}; Value validateaddress(const Array& params, bool fHelp) { @@ -1603,18 +1795,21 @@ Value validateaddress(const Array& params, bool fHelp) ret.push_back(Pair("isvalid", isValid)); if (isValid) { - // Call Hash160ToAddress() so we always return current ADDRESSVERSION - // version of the address: + CTxDestination dest = address.Get(); string currentAddress = address.ToString(); ret.push_back(Pair("address", currentAddress)); - ret.push_back(Pair("ismine", (pwalletMain->HaveKey(address) > 0))); - if (pwalletMain->mapAddressBook.count(address)) - ret.push_back(Pair("account", pwalletMain->mapAddressBook[address])); + bool fMine = IsMine(*pwalletMain, dest); + ret.push_back(Pair("ismine", fMine)); + if (fMine) { + Object detail = boost::apply_visitor(DescribeAddressVisitor(), dest); + ret.insert(ret.end(), detail.begin(), detail.end()); + } + if (pwalletMain->mapAddressBook.count(dest)) + ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest])); } return ret; } - Value getwork(const Array& params, bool fHelp) { if (fHelp || params.size() > 1) @@ -1634,7 +1829,7 @@ Value getwork(const Array& params, bool fHelp) throw JSONRPCError(-10, "Bitcoin is downloading blocks..."); typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t; - static mapNewBlock_t mapNewBlock; + static mapNewBlock_t mapNewBlock; // FIXME: thread safety static vector<CBlock*> vNewBlock; static CReserveKey reservekey(pwalletMain); @@ -1676,7 +1871,7 @@ Value getwork(const Array& params, bool fHelp) } // Update nTime - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + pblock->UpdateTime(pindexPrev); pblock->nNonce = 0; // Update nExtraNonce @@ -1738,7 +1933,10 @@ Value getmemorypool(const Array& params, bool fHelp) " \"previousblockhash\" : hash of current highest block\n" " \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n" " \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n" + " \"coinbaseflags\" : data that should be included in coinbase so support for new features can be judged\n" " \"time\" : timestamp appropriate for next block\n" + " \"mintime\" : minimum timestamp appropriate for next block\n" + " \"curtime\" : current timestamp\n" " \"bits\" : compressed target of next block\n" "If [data] is specified, tries to solve the block and returns true if it was successful."); @@ -1783,7 +1981,7 @@ Value getmemorypool(const Array& params, bool fHelp) } // Update nTime - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + pblock->UpdateTime(pindexPrev); pblock->nNonce = 0; Array transactions; @@ -1791,7 +1989,7 @@ Value getmemorypool(const Array& params, bool fHelp) if(tx.IsCoinBase()) continue; - CDataStream ssTx; + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << tx; transactions.push_back(HexStr(ssTx.begin(), ssTx.end())); @@ -1802,21 +2000,18 @@ Value getmemorypool(const Array& params, bool fHelp) result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); result.push_back(Pair("transactions", transactions)); result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue)); + result.push_back(Pair("coinbaseflags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end()))); result.push_back(Pair("time", (int64_t)pblock->nTime)); - - union { - int32_t nBits; - char cBits[4]; - } uBits; - uBits.nBits = htonl((int32_t)pblock->nBits); - result.push_back(Pair("bits", HexStr(BEGIN(uBits.cBits), END(uBits.cBits)))); + result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1)); + result.push_back(Pair("curtime", (int64_t)GetAdjustedTime())); + result.push_back(Pair("bits", HexBits(pblock->nBits))); return result; } else { // Parse parameters - CDataStream ssBlock(ParseHex(params[0].get_str())); + CDataStream ssBlock(ParseHex(params[0].get_str()), SER_NETWORK, PROTOCOL_VERSION); CBlock pblock; ssBlock >> pblock; @@ -1824,9 +2019,60 @@ Value getmemorypool(const Array& params, bool fHelp) } } +Value getrawmempool(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getrawmempool\n" + "Returns all transaction ids in memory pool."); + vector<uint256> vtxid; + mempool.queryHashes(vtxid); + Array a; + BOOST_FOREACH(const uint256& hash, vtxid) + a.push_back(hash.ToString()); + return a; +} + +Value getblockhash(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getblockhash <index>\n" + "Returns hash of block in best-block-chain at <index>."); + + int nHeight = params[0].get_int(); + if (nHeight < 0 || nHeight > nBestHeight) + throw runtime_error("Block number out of range."); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hashBestChain]; + while (pblockindex->nHeight > nHeight) + pblockindex = pblockindex->pprev; + return pblockindex->phashBlock->GetHex(); +} + +Value getblock(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getblock <hash>\n" + "Returns details of a block with given block-hash."); + + std::string strHash = params[0].get_str(); + uint256 hash(strHash); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(-5, "Block not found"); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hash]; + block.ReadFromDisk(pblockindex, true); + + return blockToJSON(block, pblockindex); +} @@ -1838,79 +2084,84 @@ Value getmemorypool(const Array& params, bool fHelp) // Call Table // -pair<string, rpcfn_type> pCallTable[] = -{ - make_pair("help", &help), - make_pair("stop", &stop), - make_pair("getblockcount", &getblockcount), - make_pair("getblocknumber", &getblocknumber), - make_pair("getconnectioncount", &getconnectioncount), - make_pair("getdifficulty", &getdifficulty), - make_pair("getgenerate", &getgenerate), - make_pair("setgenerate", &setgenerate), - make_pair("gethashespersec", &gethashespersec), - make_pair("getinfo", &getinfo), - make_pair("getnewaddress", &getnewaddress), - make_pair("getaccountaddress", &getaccountaddress), - make_pair("setaccount", &setaccount), - make_pair("getaccount", &getaccount), - make_pair("getaddressesbyaccount", &getaddressesbyaccount), - make_pair("sendtoaddress", &sendtoaddress), - make_pair("getreceivedbyaddress", &getreceivedbyaddress), - make_pair("getreceivedbyaccount", &getreceivedbyaccount), - make_pair("listreceivedbyaddress", &listreceivedbyaddress), - make_pair("listreceivedbyaccount", &listreceivedbyaccount), - make_pair("backupwallet", &backupwallet), - make_pair("keypoolrefill", &keypoolrefill), - make_pair("walletpassphrase", &walletpassphrase), - make_pair("walletpassphrasechange", &walletpassphrasechange), - make_pair("walletlock", &walletlock), - make_pair("encryptwallet", &encryptwallet), - make_pair("validateaddress", &validateaddress), - 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("signmessage", &signmessage), - make_pair("verifymessage", &verifymessage), - make_pair("getwork", &getwork), - make_pair("listaccounts", &listaccounts), - make_pair("settxfee", &settxfee), - make_pair("getmemorypool", &getmemorypool), - make_pair("listsinceblock", &listsinceblock), -}; -map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); - -string pAllowInSafeMode[] = -{ - "help", - "stop", - "getblockcount", - "getblocknumber", - "getconnectioncount", - "getdifficulty", - "getgenerate", - "setgenerate", - "gethashespersec", - "getinfo", - "getnewaddress", - "getaccountaddress", - "getaccount", - "getaddressesbyaccount", - "backupwallet", - "keypoolrefill", - "walletpassphrase", - "walletlock", - "validateaddress", - "getwork", - "getmemorypool", + +static const CRPCCommand vRPCCommands[] = +{ // name function safe mode? + // ------------------------ ----------------------- ---------- + { "help", &help, true }, + { "stop", &stop, true }, + { "getblockcount", &getblockcount, true }, + { "getconnectioncount", &getconnectioncount, true }, + { "getpeerinfo", &getpeerinfo, true }, + { "getdifficulty", &getdifficulty, true }, + { "getgenerate", &getgenerate, true }, + { "setgenerate", &setgenerate, true }, + { "gethashespersec", &gethashespersec, true }, + { "getinfo", &getinfo, true }, + { "getmininginfo", &getmininginfo, true }, + { "getnewaddress", &getnewaddress, true }, + { "getaccountaddress", &getaccountaddress, true }, + { "setaccount", &setaccount, true }, + { "getaccount", &getaccount, false }, + { "getaddressesbyaccount", &getaddressesbyaccount, true }, + { "sendtoaddress", &sendtoaddress, false }, + { "getreceivedbyaddress", &getreceivedbyaddress, false }, + { "getreceivedbyaccount", &getreceivedbyaccount, false }, + { "listreceivedbyaddress", &listreceivedbyaddress, false }, + { "listreceivedbyaccount", &listreceivedbyaccount, false }, + { "backupwallet", &backupwallet, true }, + { "keypoolrefill", &keypoolrefill, true }, + { "walletpassphrase", &walletpassphrase, true }, + { "walletpassphrasechange", &walletpassphrasechange, false }, + { "walletlock", &walletlock, true }, + { "encryptwallet", &encryptwallet, false }, + { "validateaddress", &validateaddress, true }, + { "getbalance", &getbalance, false }, + { "move", &movecmd, false }, + { "sendfrom", &sendfrom, false }, + { "sendmany", &sendmany, false }, + { "addmultisigaddress", &addmultisigaddress, false }, + { "getrawmempool", &getrawmempool, true }, + { "getblock", &getblock, false }, + { "getblockhash", &getblockhash, false }, + { "gettransaction", &gettransaction, false }, + { "listtransactions", &listtransactions, false }, + { "signmessage", &signmessage, false }, + { "verifymessage", &verifymessage, false }, + { "getwork", &getwork, true }, + { "listaccounts", &listaccounts, false }, + { "settxfee", &settxfee, false }, + { "getmemorypool", &getmemorypool, true }, + { "listsinceblock", &listsinceblock, false }, + { "dumpprivkey", &dumpprivkey, false }, + { "importprivkey", &importprivkey, false }, + { "listunspent", &listunspent, false }, + { "getrawtransaction", &getrawtransaction, false }, + { "createrawtransaction", &createrawtransaction, false }, + { "decoderawtransaction", &decoderawtransaction, false }, + { "signrawtransaction", &signrawtransaction, false }, + { "sendrawtransaction", &sendrawtransaction, false }, }; -set<string> setAllowInSafeMode(pAllowInSafeMode, pAllowInSafeMode + sizeof(pAllowInSafeMode)/sizeof(pAllowInSafeMode[0])); +CRPCTable::CRPCTable() +{ + unsigned int vcidx; + for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) + { + const CRPCCommand *pcmd; + pcmd = &vRPCCommands[vcidx]; + mapCommands[pcmd->name] = pcmd; + } +} +const CRPCCommand *CRPCTable::operator[](string name) const +{ + map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); + if (it == mapCommands.end()) + return NULL; + return (*it).second; +} // // HTTP protocol @@ -1949,7 +2200,7 @@ string rfc1123Time() return string(buffer); } -static string HTTPReply(int nStatus, const string& strMsg) +static string HTTPReply(int nStatus, const string& strMsg, bool keepalive) { if (nStatus == 401) return strprintf("HTTP/1.0 401 Authorization Required\r\n" @@ -1978,7 +2229,7 @@ static string HTTPReply(int nStatus, const string& strMsg) return strprintf( "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" - "Connection: close\r\n" + "Connection: %s\r\n" "Content-Length: %d\r\n" "Content-Type: application/json\r\n" "Server: bitcoin-json-rpc/%s\r\n" @@ -1987,12 +2238,13 @@ static string HTTPReply(int nStatus, const string& strMsg) nStatus, cStatus, rfc1123Time().c_str(), + keepalive ? "keep-alive" : "close", strMsg.size(), FormatFullVersion().c_str(), strMsg.c_str()); } -int ReadHTTPStatus(std::basic_istream<char>& stream) +int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) { string str; getline(stream, str); @@ -2000,6 +2252,10 @@ int ReadHTTPStatus(std::basic_istream<char>& stream) boost::split(vWords, str, boost::is_any_of(" ")); if (vWords.size() < 2) return 500; + proto = 0; + const char *ver = strstr(str.c_str(), "HTTP/1."); + if (ver != NULL) + proto = atoi(ver+7); return atoi(vWords[1].c_str()); } @@ -2034,11 +2290,12 @@ int ReadHTTP(std::basic_istream<char>& stream, map<string, string>& mapHeadersRe strMessageRet = ""; // Read status - int nStatus = ReadHTTPStatus(stream); + int nProto = 0; + int nStatus = ReadHTTPStatus(stream, nProto); // Read header int nLen = ReadHTTPHeader(stream, mapHeadersRet); - if (nLen < 0 || nLen > MAX_SIZE) + if (nLen < 0 || nLen > (int)MAX_SIZE) return 500; // Read message @@ -2049,6 +2306,16 @@ int ReadHTTP(std::basic_istream<char>& stream, map<string, string>& mapHeadersRe strMessageRet = string(vch.begin(), vch.end()); } + string sConHdr = mapHeadersRet["connection"]; + + if ((sConHdr != "close") && (sConHdr != "keep-alive")) + { + if (nProto >= 1) + mapHeadersRet["connection"] = "keep-alive"; + else + mapHeadersRet["connection"] = "close"; + } + return nStatus; } @@ -2059,12 +2326,7 @@ bool HTTPAuthorized(map<string, string>& mapHeaders) return false; string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); string strUserPass = DecodeBase64(strUserPass64); - string::size_type nColon = strUserPass.find(":"); - if (nColon == string::npos) - return false; - string strUser = strUserPass.substr(0, nColon); - string strPassword = strUserPass.substr(nColon+1); - return (strUser == mapArgs["-rpcuser"] && strPassword == mapArgs["-rpcpassword"]); + return strUserPass == strRPCUserColonPass; } // @@ -2086,7 +2348,7 @@ string JSONRPCRequest(const string& strMethod, const Array& params, const Value& return write_string(Value(request), false) + "\n"; } -string JSONRPCReply(const Value& result, const Value& error, const Value& id) +Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) { Object reply; if (error.type() != null_type) @@ -2095,6 +2357,12 @@ string JSONRPCReply(const Value& result, const Value& error, const Value& id) reply.push_back(Pair("result", result)); reply.push_back(Pair("error", error)); reply.push_back(Pair("id", id)); + return reply; +} + +string JSONRPCReply(const Value& result, const Value& error, const Value& id) +{ + Object reply = JSONRPCReplyObj(result, error, id); return write_string(Value(reply), false) + "\n"; } @@ -2106,13 +2374,25 @@ void ErrorReply(std::ostream& stream, const Object& objError, const Value& id) if (code == -32600) nStatus = 400; else if (code == -32601) nStatus = 404; string strReply = JSONRPCReply(Value::null, objError, id); - stream << HTTPReply(nStatus, strReply) << std::flush; + stream << HTTPReply(nStatus, strReply, false) << std::flush; } -bool ClientAllowed(const string& strAddress) +bool ClientAllowed(const boost::asio::ip::address& address) { - if (strAddress == asio::ip::address_v4::loopback().to_string()) + // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses + if (address.is_v6() + && (address.to_v6().is_v4_compatible() + || address.to_v6().is_v4_mapped())) + return ClientAllowed(address.to_v6().to_v4()); + + if (address == asio::ip::address_v4::loopback() + || address == asio::ip::address_v6::loopback() + || (address.is_v4() + // Chech whether IPv4 addresses match 127.0.0.0/8 (loopback subnet) + && (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000)) return true; + + const string strAddress = address.to_string(); const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; BOOST_FOREACH(string strAllow, vAllow) if (WildcardMatch(strAddress, strAllow)) @@ -2120,13 +2400,13 @@ bool ClientAllowed(const string& strAddress) return false; } -#ifdef USE_SSL // // IOStream device that speaks SSL but can also speak non-SSL // +template <typename Protocol> class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> { public: - SSLIOStreamDevice(SSLStream &streamIn, bool fUseSSLIn) : stream(streamIn) + SSLIOStreamDevice(asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) { fUseSSL = fUseSSLIn; fNeedHandshake = fUseSSLIn; @@ -2170,213 +2450,468 @@ public: private: bool fNeedHandshake; bool fUseSSL; - SSLStream& stream; + asio::ssl::stream<typename Protocol::socket>& stream; +}; + +class AcceptedConnection +{ +public: + virtual ~AcceptedConnection() {} + + virtual std::iostream& stream() = 0; + virtual std::string peer_address_to_string() const = 0; + virtual void close() = 0; +}; + +template <typename Protocol> +class AcceptedConnectionImpl : public AcceptedConnection +{ +public: + AcceptedConnectionImpl( + asio::io_service& io_service, + ssl::context &context, + bool fUseSSL) : + sslStream(io_service, context), + _d(sslStream, fUseSSL), + _stream(_d) + { + } + + virtual std::iostream& stream() + { + return _stream; + } + + virtual std::string peer_address_to_string() const + { + return peer.address().to_string(); + } + + virtual void close() + { + _stream.close(); + } + + typename Protocol::endpoint peer; + asio::ssl::stream<typename Protocol::socket> sslStream; + +private: + SSLIOStreamDevice<Protocol> _d; + iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; }; -#endif void ThreadRPCServer(void* parg) { IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer(parg)); + + // Make this thread recognisable as the RPC listener + RenameThread("bitcoin-rpclist"); + try { - vnThreadsRunning[4]++; + vnThreadsRunning[THREAD_RPCLISTENER]++; ThreadRPCServer2(parg); - vnThreadsRunning[4]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; } catch (std::exception& e) { - vnThreadsRunning[4]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(&e, "ThreadRPCServer()"); } catch (...) { - vnThreadsRunning[4]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(NULL, "ThreadRPCServer()"); } - printf("ThreadRPCServer exiting\n"); + printf("ThreadRPCServer exited\n"); +} + +// Forward declaration required for RPCListen +template <typename Protocol, typename SocketAcceptorService> +static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, + ssl::context& context, + bool fUseSSL, + AcceptedConnection* conn, + const boost::system::error_code& error); + +/** + * Sets up I/O resources to accept and handle a new connection. + */ +template <typename Protocol, typename SocketAcceptorService> +static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, + ssl::context& context, + const bool fUseSSL) +{ + // Accept connection + AcceptedConnectionImpl<Protocol>* conn = new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL); + + acceptor->async_accept( + conn->sslStream.lowest_layer(), + conn->peer, + boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, + acceptor, + boost::ref(context), + fUseSSL, + conn, + boost::asio::placeholders::error)); +} + +/** + * Accept and handle incoming connection. + */ +template <typename Protocol, typename SocketAcceptorService> +static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, + ssl::context& context, + const bool fUseSSL, + AcceptedConnection* conn, + const boost::system::error_code& error) +{ + vnThreadsRunning[THREAD_RPCLISTENER]++; + + // Immediately start accepting new connections, except when we're canceled or our socket is closed. + if (error != asio::error::operation_aborted + && acceptor->is_open()) + RPCListen(acceptor, context, fUseSSL); + + AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn); + + // TODO: Actually handle errors + if (error) + { + delete conn; + } + + // Restrict callers by IP. It is important to + // do this before starting client thread, to filter out + // certain DoS and misbehaving clients. + else if (tcp_conn + && !ClientAllowed(tcp_conn->peer.address())) + { + // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. + if (!fUseSSL) + conn->stream() << HTTPReply(403, "", false) << std::flush; + delete conn; + } + + // start HTTP client thread + else if (!CreateThread(ThreadRPCServer3, conn)) { + printf("Failed to create RPC server client thread\n"); + delete conn; + } + + vnThreadsRunning[THREAD_RPCLISTENER]--; } void ThreadRPCServer2(void* parg) { printf("ThreadRPCServer started\n"); - if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") + strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; + if (mapArgs["-rpcpassword"] == "") { + unsigned char rand_pwd[32]; + RAND_bytes(rand_pwd, 32); string strWhatAmI = "To use bitcoind"; if (mapArgs.count("-server")) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); - PrintConsole( - _("Error: %s, you must set rpcpassword=<password>\nin the configuration file: %s\n" + uiInterface.ThreadSafeMessageBox(strprintf( + _("%s, you must set a rpcpassword in the configuration file:\n %s\n" + "It is recommended you use the following random password:\n" + "rpcuser=bitcoinrpc\n" + "rpcpassword=%s\n" + "(you do not need to remember this password)\n" "If the file does not exist, create it with owner-readable-only file permissions.\n"), strWhatAmI.c_str(), - GetConfigFile().c_str()); -#ifndef QT_GUI - CreateThread(Shutdown, NULL); -#endif + GetConfigFile().string().c_str(), + EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + StartShutdown(); return; } - bool fUseSSL = GetBoolArg("-rpcssl"); - asio::ip::address bindAddress = mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback(); + const bool fUseSSL = GetBoolArg("-rpcssl"); asio::io_service io_service; - ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332)); - ip::tcp::acceptor acceptor(io_service, endpoint); - - acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); -#ifdef USE_SSL ssl::context context(io_service, ssl::context::sslv23); if (fUseSSL) { context.set_options(ssl::context::no_sslv2); - filesystem::path certfile = GetArg("-rpcsslcertificatechainfile", "server.cert"); - if (!certfile.is_complete()) certfile = filesystem::path(GetDataDir()) / certfile; - if (filesystem::exists(certfile)) context.use_certificate_chain_file(certfile.string().c_str()); - else printf("ThreadRPCServer ERROR: missing server certificate file %s\n", certfile.string().c_str()); - filesystem::path pkfile = GetArg("-rpcsslprivatekeyfile", "server.pem"); - if (!pkfile.is_complete()) pkfile = filesystem::path(GetDataDir()) / pkfile; - if (filesystem::exists(pkfile)) context.use_private_key_file(pkfile.string().c_str(), ssl::context::pem); - else printf("ThreadRPCServer ERROR: missing server private key file %s\n", pkfile.string().c_str()); - - string ciphers = GetArg("-rpcsslciphers", - "TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH"); - SSL_CTX_set_cipher_list(context.impl(), ciphers.c_str()); - } -#else - if (fUseSSL) - throw runtime_error("-rpcssl=1, but bitcoin compiled without full openssl libraries."); -#endif - loop + filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); + if (!pathCertFile.is_complete()) pathCertFile = filesystem::path(GetDataDir()) / pathCertFile; + if (filesystem::exists(pathCertFile)) context.use_certificate_chain_file(pathCertFile.string()); + else printf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string().c_str()); + + filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); + if (!pathPKFile.is_complete()) pathPKFile = filesystem::path(GetDataDir()) / pathPKFile; + if (filesystem::exists(pathPKFile)) context.use_private_key_file(pathPKFile.string(), ssl::context::pem); + else printf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string().c_str()); + + string strCiphers = GetArg("-rpcsslciphers", "TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH"); + SSL_CTX_set_cipher_list(context.impl(), strCiphers.c_str()); + } + + // Try a dual IPv6/IPv4 socket, falling back to separate IPv4 and IPv6 sockets + const bool loopback = !mapArgs.count("-rpcallowip"); + asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any(); + ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332)); + + boost::signals2::signal<void ()> StopRequests; + + try { - // Accept connection -#ifdef USE_SSL - SSLStream sslStream(io_service, context); - SSLIOStreamDevice d(sslStream, fUseSSL); - iostreams::stream<SSLIOStreamDevice> stream(d); -#else - ip::tcp::iostream stream; -#endif + boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service)); + acceptor->open(endpoint.protocol()); + acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - ip::tcp::endpoint peer; - vnThreadsRunning[4]--; -#ifdef USE_SSL - acceptor.accept(sslStream.lowest_layer(), peer); -#else - acceptor.accept(*stream.rdbuf(), peer); -#endif - vnThreadsRunning[4]++; - if (fShutdown) - return; + // Try making the socket dual IPv6/IPv4 (if listening on the "any" address) + boost::system::error_code v6_only_error; + acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error); - // Restrict callers by IP - if (!ClientAllowed(peer.address().to_string())) + acceptor->bind(endpoint); + acceptor->listen(socket_base::max_connections); + + RPCListen(acceptor, context, fUseSSL); + // Cancel outstanding listen-requests for this acceptor when shutting down + StopRequests.connect(signals2::slot<void ()>( + static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) + .track(acceptor)); + + // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately + if (loopback || v6_only_error) { - // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. - if (!fUseSSL) - stream << HTTPReply(403, "") << std::flush; - continue; + bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any(); + endpoint.address(bindAddress); + + acceptor.reset(new ip::tcp::acceptor(io_service)); + acceptor->open(endpoint.protocol()); + acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor->bind(endpoint); + acceptor->listen(socket_base::max_connections); + + RPCListen(acceptor, context, fUseSSL); + // Cancel outstanding listen-requests for this acceptor when shutting down + StopRequests.connect(signals2::slot<void ()>( + static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get()) + .track(acceptor)); } + } + catch(boost::system::system_error &e) + { + uiInterface.ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + StartShutdown(); + return; + } + + vnThreadsRunning[THREAD_RPCLISTENER]--; + while (!fShutdown) + io_service.run_one(); + vnThreadsRunning[THREAD_RPCLISTENER]++; + StopRequests(); +} + +class JSONRequest +{ +public: + Value id; + string strMethod; + Array params; + + JSONRequest() { id = Value::null; } + void parse(const Value& valRequest); +}; + +void JSONRequest::parse(const Value& valRequest) +{ + // Parse request + if (valRequest.type() != obj_type) + throw JSONRPCError(-32600, "Invalid Request object"); + const Object& request = valRequest.get_obj(); + + // Parse id now so errors from here on will have the id + id = find_value(request, "id"); + + // Parse method + Value valMethod = find_value(request, "method"); + if (valMethod.type() == null_type) + throw JSONRPCError(-32600, "Missing method"); + if (valMethod.type() != str_type) + throw JSONRPCError(-32600, "Method must be a string"); + strMethod = valMethod.get_str(); + if (strMethod != "getwork" && strMethod != "getmemorypool") + printf("ThreadRPCServer method=%s\n", strMethod.c_str()); + + // Parse params + Value valParams = find_value(request, "params"); + if (valParams.type() == array_type) + params = valParams.get_array(); + else if (valParams.type() == null_type) + params = Array(); + else + throw JSONRPCError(-32600, "Params must be an array"); +} + +static Object JSONRPCExecOne(const Value& req) +{ + Object rpc_result; + + JSONRequest jreq; + try { + jreq.parse(req); + + Value result = tableRPC.execute(jreq.strMethod, jreq.params); + rpc_result = JSONRPCReplyObj(result, Value::null, jreq.id); + } + catch (Object& objError) + { + rpc_result = JSONRPCReplyObj(Value::null, objError, jreq.id); + } + catch (std::exception& e) + { + rpc_result = JSONRPCReplyObj(Value::null, + JSONRPCError(-32700, e.what()), jreq.id); + } + + return rpc_result; +} + +static string JSONRPCExecBatch(const Array& vReq) +{ + Array ret; + for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) + ret.push_back(JSONRPCExecOne(vReq[reqIdx])); + + return write_string(Value(ret), false) + "\n"; +} + +static CCriticalSection cs_THREAD_RPCHANDLER; + +void ThreadRPCServer3(void* parg) +{ + IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer3(parg)); + + // Make this thread recognisable as the RPC handler + RenameThread("bitcoin-rpchand"); + { + LOCK(cs_THREAD_RPCHANDLER); + vnThreadsRunning[THREAD_RPCHANDLER]++; + } + AcceptedConnection *conn = (AcceptedConnection *) parg; + + bool fRun = true; + loop { + if (fShutdown || !fRun) + { + conn->close(); + delete conn; + { + LOCK(cs_THREAD_RPCHANDLER); + --vnThreadsRunning[THREAD_RPCHANDLER]; + } + return; + } map<string, string> mapHeaders; string strRequest; - boost::thread api_caller(ReadHTTP, boost::ref(stream), boost::ref(mapHeaders), boost::ref(strRequest)); - if (!api_caller.timed_join(boost::posix_time::seconds(GetArg("-rpctimeout", 30)))) - { // Timed out: - acceptor.cancel(); - printf("ThreadRPCServer ReadHTTP timeout\n"); - continue; - } + ReadHTTP(conn->stream(), mapHeaders, strRequest); // Check authorization if (mapHeaders.count("authorization") == 0) { - stream << HTTPReply(401, "") << std::flush; - continue; + conn->stream() << HTTPReply(401, "", false) << std::flush; + break; } if (!HTTPAuthorized(mapHeaders)) { - // Deter brute-forcing short passwords - if (mapArgs["-rpcpassword"].size() < 15) - Sleep(50); - - stream << HTTPReply(401, "") << std::flush; - printf("ThreadRPCServer incorrect password attempt\n"); - continue; + printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string().c_str()); + /* Deter brute-forcing short passwords. + If this results in a DOS the user really + shouldn't have their RPC port exposed.*/ + if (mapArgs["-rpcpassword"].size() < 20) + Sleep(250); + + conn->stream() << HTTPReply(401, "", false) << std::flush; + break; } + if (mapHeaders["connection"] == "close") + fRun = false; - Value id = Value::null; + JSONRequest jreq; try { // Parse request Value valRequest; - if (!read_string(strRequest, valRequest) || valRequest.type() != obj_type) + if (!read_string(strRequest, valRequest)) throw JSONRPCError(-32700, "Parse error"); - const Object& request = valRequest.get_obj(); - - // Parse id now so errors from here on will have the id - id = find_value(request, "id"); - - // Parse method - Value valMethod = find_value(request, "method"); - if (valMethod.type() == null_type) - throw JSONRPCError(-32600, "Missing method"); - if (valMethod.type() != str_type) - throw JSONRPCError(-32600, "Method must be a string"); - string strMethod = valMethod.get_str(); - if (strMethod != "getwork" && strMethod != "getmemorypool") - printf("ThreadRPCServer method=%s\n", strMethod.c_str()); - - // Parse params - Value valParams = find_value(request, "params"); - Array params; - if (valParams.type() == array_type) - params = valParams.get_array(); - else if (valParams.type() == null_type) - params = Array(); - else - throw JSONRPCError(-32600, "Params must be an array"); - // Find method - map<string, rpcfn_type>::iterator mi = mapCallTable.find(strMethod); - if (mi == mapCallTable.end()) - throw JSONRPCError(-32601, "Method not found"); + string strReply; - // Observe safe mode - string strWarning = GetWarnings("rpc"); - if (strWarning != "" && !GetBoolArg("-disablesafemode") && !setAllowInSafeMode.count(strMethod)) - throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + // singleton request + if (valRequest.type() == obj_type) { + jreq.parse(valRequest); - try - { - // Execute - Value result; - CRITICAL_BLOCK(cs_main) - CRITICAL_BLOCK(pwalletMain->cs_wallet) - result = (*(*mi).second)(params, false); + Value result = tableRPC.execute(jreq.strMethod, jreq.params); // Send reply - string strReply = JSONRPCReply(result, Value::null, id); - stream << HTTPReply(200, strReply) << std::flush; - } - catch (std::exception& e) - { - ErrorReply(stream, JSONRPCError(-1, e.what()), id); - } + strReply = JSONRPCReply(result, Value::null, jreq.id); + + // array of requests + } else if (valRequest.type() == array_type) + strReply = JSONRPCExecBatch(valRequest.get_array()); + else + throw JSONRPCError(-32700, "Top-level object parse error"); + + conn->stream() << HTTPReply(200, strReply, fRun) << std::flush; } catch (Object& objError) { - ErrorReply(stream, objError, id); + ErrorReply(conn->stream(), objError, jreq.id); + break; } catch (std::exception& e) { - ErrorReply(stream, JSONRPCError(-32700, e.what()), id); + ErrorReply(conn->stream(), JSONRPCError(-32700, e.what()), jreq.id); + break; } } + + delete conn; + { + LOCK(cs_THREAD_RPCHANDLER); + vnThreadsRunning[THREAD_RPCHANDLER]--; + } } +json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const +{ + // Find method + const CRPCCommand *pcmd = tableRPC[strMethod]; + if (!pcmd) + throw JSONRPCError(-32601, "Method not found"); + // Observe safe mode + string strWarning = GetWarnings("rpc"); + if (strWarning != "" && !GetBoolArg("-disablesafemode") && + !pcmd->okSafeMode) + throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + + try + { + // Execute + Value result; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + result = pcmd->actor(params, false); + } + return result; + } + catch (std::exception& e) + { + throw JSONRPCError(-1, e.what()); + } +} Object CallRPC(const string& strMethod, const Array& params) @@ -2385,28 +2920,18 @@ Object CallRPC(const string& strMethod, const Array& params) throw runtime_error(strprintf( _("You must set rpcpassword=<password> in the configuration file:\n%s\n" "If the file does not exist, create it with owner-readable-only file permissions."), - GetConfigFile().c_str())); + GetConfigFile().string().c_str())); // Connect to localhost bool fUseSSL = GetBoolArg("-rpcssl"); -#ifdef USE_SSL asio::io_service io_service; ssl::context context(io_service, ssl::context::sslv23); context.set_options(ssl::context::no_sslv2); - SSLStream sslStream(io_service, context); - SSLIOStreamDevice d(sslStream, fUseSSL); - iostreams::stream<SSLIOStreamDevice> stream(d); + asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context); + SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL); + iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d); if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332"))) throw runtime_error("couldn't connect to server"); -#else - if (fUseSSL) - throw runtime_error("-rpcssl=1, but bitcoin compiled without full openssl libraries."); - - ip::tcp::iostream stream(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332")); - if (stream.fail()) - throw runtime_error("couldn't connect to server"); -#endif - // HTTP basic authentication string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); @@ -2450,8 +2975,9 @@ void ConvertTo(Value& value) { // reinterpret string as unquoted json value Value value2; - if (!read_string(value.get_str(), value2)) - throw runtime_error("type mismatch"); + string strJSON = value.get_str(); + if (!read_string(strJSON, value2)) + throw runtime_error(string("Error parsing JSON:")+strJSON); value = value2.get_value<T>(); } else @@ -2460,6 +2986,54 @@ void ConvertTo(Value& value) } } +// Convert strings to command-specific RPC representation +Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) +{ + Array params; + BOOST_FOREACH(const std::string ¶m, strParams) + params.push_back(param); + + int n = params.size(); + + // + // Special case non-string parameter types + // + if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); + if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); + if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]); + if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]); + if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1]); + if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2]); + + return params; +} + int CommandLineRPC(int argc, char *argv[]) { string strPrint; @@ -2479,43 +3053,8 @@ int CommandLineRPC(int argc, char *argv[]) string strMethod = argv[1]; // Parameters default to strings - Array params; - for (int i = 2; i < argc; i++) - params.push_back(argv[i]); - int n = params.size(); - - // - // Special case non-string parameter types - // - if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); - if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); - if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); - if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); - 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<boost::int64_t>(params[2]); + std::vector<std::string> strParams(&argv[2], &argv[argc]); + Array params = RPCConvertValues(strMethod, strParams); // Execute Object reply = CallRPC(strMethod, params); @@ -2594,3 +3133,5 @@ int main(int argc, char *argv[]) return 0; } #endif + +const CRPCTable tableRPC; |