diff options
Diffstat (limited to 'src/bitcoinrpc.cpp')
-rw-r--r-- | src/bitcoinrpc.cpp | 736 |
1 files changed, 517 insertions, 219 deletions
diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 15bcf1da3d..24fa97588c 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2010 Satoshi Nakamoto // 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 "main.h" #include "wallet.h" @@ -44,6 +44,10 @@ static CCriticalSection cs_nWalletUnlockTime; extern Value dumpprivkey(const Array& params, bool fHelp); extern Value importprivkey(const Array& params, bool fHelp); +const Object emptyobj; + +void ThreadRPCServer3(void* parg); + Object JSONRPCError(int code, const string& message) { Object error; @@ -111,6 +115,48 @@ HexBits(unsigned int nBits) return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); } +static std::string +HelpRequiringPassphrase() +{ + return pwalletMain->IsCrypted() + ? "\nrequires wallet passphrase to be set with walletpassphrase first" + : ""; +} + +static inline void +EnsureWalletIsUnlocked() +{ + if (pwalletMain->IsLocked()) + throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); +} + +enum DecomposeMode { + DM_NONE = 0, + DM_HASH, + DM_HEX, + DM_ASM, + DM_OBJ, +}; + +enum DecomposeMode +FindDecompose(const Object& decompositions, const char* pcType, const char* pcDefault) +{ + Value val = find_value(decompositions, pcType); + std::string strDecompose = (val.type() == null_type) ? pcDefault : val.get_str(); + + if (strDecompose == "no") + return DM_NONE; + if (strDecompose == "hash") + return DM_HASH; + if (strDecompose == "hex") + return DM_HEX; + if (strDecompose == "asm") + return DM_ASM; + if (strDecompose == "obj") + return DM_OBJ; + throw JSONRPCError(-18, "Invalid decomposition"); +} + void WalletTxToJSON(const CWalletTx& wtx, Object& entry) { int confirms = wtx.GetDepthInMainChain(); @@ -126,6 +172,141 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry) entry.push_back(Pair(item.first, item.second)); } +void +ScriptSigToJSON(const CTxIn& txin, Object& out) +{ + out.push_back(Pair("asm", txin.scriptSig.ToString())); + out.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + + CTransaction txprev; + uint256 hashTxprevBlock; + if (!GetTransaction(txin.prevout.hash, txprev, hashTxprevBlock)) + return; + + txnouttype type; + vector<CBitcoinAddress> addresses; + int nRequired; + + if (!ExtractAddresses(txprev.vout[txin.prevout.n].scriptPubKey, type, + addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("type", GetTxnOutputType(type))); + if (type == TX_MULTISIG) + { + // TODO: Need to handle this specially since not all input addresses are required... + return; + } + + Array a; + BOOST_FOREACH(const CBitcoinAddress& addr, addresses) + a.push_back(addr.ToString()); + out.push_back(Pair("addresses", a)); +} + +void +ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out) +{ + txnouttype type; + vector<CBitcoinAddress> addresses; + int nRequired; + + out.push_back(Pair("asm", scriptPubKey.ToString())); + out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); + + if (!ExtractAddresses(scriptPubKey, type, addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("reqSigs", nRequired)); + out.push_back(Pair("type", GetTxnOutputType(type))); + + Array a; + BOOST_FOREACH(const CBitcoinAddress& addr, addresses) + a.push_back(addr.ToString()); + out.push_back(Pair("addresses", a)); +} + +void TxToJSON(const CTransaction &tx, Object& entry, const Object& decompositions) +{ + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime)); + entry.push_back(Pair("size", (boost::int64_t)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); + + enum DecomposeMode decomposeScript = FindDecompose(decompositions, "script", "asm"); + + Array vin; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + Object in; + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else + { + Object prevout; + prevout.push_back(Pair("hash", txin.prevout.hash.GetHex())); + prevout.push_back(Pair("n", (boost::int64_t)txin.prevout.n)); + in.push_back(Pair("prevout", prevout)); + switch (decomposeScript) { + case DM_NONE: + break; + case DM_HEX: + in.push_back(Pair("scriptSig", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + break; + case DM_ASM: + in.push_back(Pair("scriptSig", txin.scriptSig.ToString())); + break; + case DM_OBJ: + { + Object o; + ScriptSigToJSON(txin, o); + in.push_back(Pair("scriptSig", o)); + break; + } + default: + throw JSONRPCError(-18, "Invalid script decomposition"); + } + } + in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + Array vout; + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + Object out; + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + switch (decomposeScript) { + case DM_NONE: + break; + case DM_HEX: + out.push_back(Pair("scriptPubKey", HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end()))); + break; + case DM_ASM: + out.push_back(Pair("scriptPubKey", txout.scriptPubKey.ToString())); + break; + case DM_OBJ: + { + Object o; + ScriptPubKeyToJSON(txout.scriptPubKey, o); + out.push_back(Pair("scriptPubKey", o)); + break; + } + default: + throw JSONRPCError(-18, "Invalid script decomposition"); + } + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); +} + +void AnyTxToJSON(const uint256 hash, const CTransaction* ptx, Object& entry, const Object& decompositions); + string AccountFromValue(const Value& value) { string strAccount = value.get_str(); @@ -134,10 +315,13 @@ string AccountFromValue(const Value& value) return strAccount; } -Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) +Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, const Object& decompositions) { 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)); @@ -146,10 +330,38 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) 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))); - Array txhashes; - BOOST_FOREACH (const CTransaction&tx, block.vtx) - txhashes.push_back(tx.GetHash().GetHex()); - result.push_back(Pair("tx", txhashes)); + + enum DecomposeMode decomposeTxn = FindDecompose(decompositions, "tx", "hash"); + if (decomposeTxn) + { + Array txs; + switch (decomposeTxn) { + case DM_OBJ: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + { + Object entry; + AnyTxToJSON(tx.GetHash(), &tx, entry, decompositions); + txs.push_back(entry); + } + break; + case DM_HEX: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + { + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + + txs.push_back(HexStr(ssTx.begin(), ssTx.end())); + } + break; + case DM_HASH: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + txs.push_back(tx.GetHash().GetHex()); + break; + default: + throw JSONRPCError(-18, "Invalid transaction decomposition"); + } + result.push_back(Pair("tx", txs)); + } if (blockindex->pprev) result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); @@ -160,6 +372,7 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) + /// /// Note: This interface may still be subject to change. /// @@ -173,10 +386,7 @@ string CRPCTable::help(string strCommand) const 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 == "getblocknumber" || // deprecated - (strMethod.find("label") != string::npos)) + if (strMethod.find("label") != string::npos) continue; if (strCommand != "" && strMethod != strCommand) continue; @@ -223,10 +433,10 @@ Value stop(const Array& params, bool fHelp) if (fHelp || params.size() != 0) throw runtime_error( "stop\n" - "Stop bitcoin server."); + "Stop Bitcoin server."); // Shutdown will take long enough that the response should get back - QueueShutdown(); - return "bitcoin server stopping"; + uiInterface.QueueShutdown(); + return "Bitcoin server stopping"; } @@ -241,18 +451,6 @@ Value getblockcount(const Array& params, bool fHelp) } -// deprecated -Value getblocknumber(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getblocknumber\n" - "Deprecated. Use getblockcount."); - - return nBestHeight; -} - - Value getconnectioncount(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -379,7 +577,7 @@ 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]."); @@ -446,7 +644,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]); @@ -469,7 +667,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; @@ -499,7 +697,7 @@ 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); @@ -548,19 +746,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]); @@ -589,8 +783,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(); @@ -658,7 +851,7 @@ 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"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); scriptPubKey.SetBitcoinAddress(address); if (!IsMine(*pwalletMain,scriptPubKey)) return (double)0.0; @@ -839,7 +1032,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(); @@ -861,7 +1055,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; } @@ -869,20 +1064,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)) + 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\n" - "requires wallet passphrase to be set with walletpassphrase first"); - 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"); + "<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) @@ -895,8 +1086,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); @@ -914,15 +1104,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(); @@ -943,7 +1129,7 @@ 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_); @@ -957,8 +1143,7 @@ Value sendmany(const Array& params, bool fHelp) 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); @@ -987,7 +1172,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) { 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" + "each key is a Bitcoin address or hex-encoded public key\n" "If [account] is specified, assign address to [account]."; throw runtime_error(msg); } @@ -999,17 +1184,19 @@ Value addmultisigaddress(const Array& params, bool fHelp) strAccount = AccountFromValue(params[2]); // Gather public keys - if ((nRequired < 1) || ((int)keys.size() < nRequired)) + 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("wrong number of keys" - "(got %d, need at least %d)", keys.size(), nRequired)); + 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: + // Case 1: Bitcoin address and we have full public key: CBitcoinAddress address(ks); if (address.IsValid()) { @@ -1327,7 +1514,7 @@ Value listtransactions(const Array& params, bool fHelp) if (pacentry != 0) AcentryToJSON(*pacentry, strAccount, ret); - if (ret.size() >= (nCount+nFrom)) break; + if ((int)ret.size() >= (nCount+nFrom)) break; } // ret is newest to oldest @@ -1462,11 +1649,69 @@ Value listsinceblock(const Array& params, bool fHelp) return ret; } +void +AnyTxToJSON(const uint256 hash, const CTransaction* ptx, Object& entry, const Object& decompositions) +{ + if (pwalletMain->mapWallet.count(hash)) + { + const CWalletTx& wtx = pwalletMain->mapWallet[hash]; + + TxToJSON(wtx, entry, decompositions); + + int64 nCredit = wtx.GetCredit(); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); + + entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); + if (wtx.IsFromMe()) + entry.push_back(Pair("fee", ValueFromAmount(nFee))); + + WalletTxToJSON(wtx, entry); + + Array details; + ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); + entry.push_back(Pair("details", details)); + } + else + { + CTransaction tx; + uint256 hashBlock = 0; + if ((!ptx) && GetTransaction(hash, tx, hashBlock)) + ptx = &tx; + if (ptx) + { + entry.push_back(Pair("txid", hash.GetHex())); + TxToJSON(*ptx, entry, decompositions); + if (hashBlock == 0) + entry.push_back(Pair("confirmations", 0)); + else + { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + { + CBlockIndex* pindex = (*mi).second; + if (pindex->IsInMainChain()) + { + entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); + } + else + entry.push_back(Pair("confirmations", 0)); + } + } + } + else + throw JSONRPCError(-5, "No information available about transaction"); + } +} + Value gettransaction(const Array& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "gettransaction <txid>\n" + "gettransaction <txid> [decompositions]\n" "Get detailed information about <txid>"); uint256 hash; @@ -1474,24 +1719,8 @@ Value gettransaction(const Array& params, bool fHelp) Object entry; - if (!pwalletMain->mapWallet.count(hash)) - throw JSONRPCError(-5, "Invalid or non-wallet transaction id"); - const CWalletTx& wtx = pwalletMain->mapWallet[hash]; - - int64 nCredit = wtx.GetCredit(); - int64 nDebit = wtx.GetDebit(); - int64 nNet = nCredit - nDebit; - int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); - - entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); - if (wtx.IsFromMe()) - entry.push_back(Pair("fee", ValueFromAmount(nFee))); - - WalletTxToJSON(pwalletMain->mapWallet[hash], entry); - - Array details; - ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); - entry.push_back(Pair("details", details)); + AnyTxToJSON(hash, NULL, entry, + (params.size() > 1) ? params[1].get_obj() : emptyobj); return entry; } @@ -1513,17 +1742,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(); @@ -1703,8 +1928,8 @@ Value encryptwallet(const Array& params, bool fHelp) // 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: - QueueShutdown(); - return "wallet encrypted; bitcoin server stopping, restart to run with encrypted wallet"; + uiInterface.QueueShutdown(); + return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet"; } @@ -1781,7 +2006,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); @@ -1973,9 +2198,9 @@ Value getblockhash(const Array& params, bool fHelp) Value getblock(const Array& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "getblock <hash>\n" + "getblock <hash> [decompositions]\n" "Returns details of a block with given block-hash."); std::string strHash = params[0].get_str(); @@ -1988,7 +2213,8 @@ Value getblock(const Array& params, bool fHelp) CBlockIndex* pblockindex = mapBlockIndex[hash]; block.ReadFromDisk(pblockindex, true); - return blockToJSON(block, pblockindex); + return blockToJSON(block, pblockindex, + (params.size() > 1) ? params[1].get_obj() : emptyobj); } @@ -2012,7 +2238,6 @@ static const CRPCCommand vRPCCommands[] = { "help", &help, true }, { "stop", &stop, true }, { "getblockcount", &getblockcount, true }, - { "getblocknumber", &getblocknumber, true }, { "getconnectioncount", &getconnectioncount, true }, { "getdifficulty", &getdifficulty, true }, { "getgenerate", &getgenerate, true }, @@ -2114,7 +2339,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" @@ -2143,7 +2368,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" @@ -2152,12 +2377,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); @@ -2165,6 +2391,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()); } @@ -2199,7 +2429,8 @@ 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); @@ -2214,6 +2445,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; } @@ -2266,7 +2507,7 @@ 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) @@ -2332,23 +2573,37 @@ private: SSLStream& stream; }; +class AcceptedConnection +{ + public: + SSLStream sslStream; + SSLIOStreamDevice d; + iostreams::stream<SSLIOStreamDevice> stream; + + ip::tcp::endpoint peer; + + AcceptedConnection(asio::io_service &io_service, ssl::context &context, + bool fUseSSL) : sslStream(io_service, context), d(sslStream, fUseSSL), + stream(d) { ; } +}; + void ThreadRPCServer(void* parg) { IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer(parg)); try { - vnThreadsRunning[THREAD_RPCSERVER]++; + vnThreadsRunning[THREAD_RPCLISTENER]++; ThreadRPCServer2(parg); - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; } catch (std::exception& e) { - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(&e, "ThreadRPCServer()"); } catch (...) { - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(NULL, "ThreadRPCServer()"); } - printf("ThreadRPCServer exiting\n"); + printf("ThreadRPCServer exited\n"); } void ThreadRPCServer2(void* parg) @@ -2365,7 +2620,7 @@ void ThreadRPCServer2(void* parg) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); - ThreadSafeMessageBox(strprintf( + 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" @@ -2375,8 +2630,8 @@ void ThreadRPCServer2(void* parg) strWhatAmI.c_str(), GetConfigFile().string().c_str(), EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return; } @@ -2395,9 +2650,9 @@ void ThreadRPCServer2(void* parg) } catch(boost::system::system_error &e) { - ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + 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); + uiInterface.QueueShutdown(); return; } @@ -2423,55 +2678,78 @@ void ThreadRPCServer2(void* parg) loop { // Accept connection - SSLStream sslStream(io_service, context); - SSLIOStreamDevice d(sslStream, fUseSSL); - iostreams::stream<SSLIOStreamDevice> stream(d); - - ip::tcp::endpoint peer; - vnThreadsRunning[THREAD_RPCSERVER]--; - acceptor.accept(sslStream.lowest_layer(), peer); - vnThreadsRunning[4]++; + AcceptedConnection *conn = + new AcceptedConnection(io_service, context, fUseSSL); + + vnThreadsRunning[THREAD_RPCLISTENER]--; + acceptor.accept(conn->sslStream.lowest_layer(), conn->peer); + vnThreadsRunning[THREAD_RPCLISTENER]++; + if (fShutdown) + { + delete conn; return; + } - // Restrict callers by IP - if (!ClientAllowed(peer.address().to_string())) + // Restrict callers by IP. It is important to + // do this before starting client thread, to filter out + // certain DoS and misbehaving clients. + if (!ClientAllowed(conn->peer.address().to_string())) { // 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; + 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; } + } +} + +void ThreadRPCServer3(void* parg) +{ + IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer3(parg)); + vnThreadsRunning[THREAD_RPCHANDLER]++; + AcceptedConnection *conn = (AcceptedConnection *) parg; + bool fRun = true; + loop { + if (fShutdown || !fRun) + { + conn->stream.close(); + delete conn; + --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)) { - printf("ThreadRPCServer incorrect password attempt from %s\n",peer.address().to_string().c_str()); + 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); - stream << HTTPReply(401, "") << std::flush; - continue; + conn->stream << HTTPReply(401, "", false) << std::flush; + break; } + if (mapHeaders["connection"] == "close") + fRun = false; Value id = Value::null; try @@ -2505,47 +2783,56 @@ void ThreadRPCServer2(void* parg) else throw JSONRPCError(-32600, "Params must be an array"); - // 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); - } + Value result = tableRPC.execute(strMethod, 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); - } + // Send reply + string strReply = JSONRPCReply(result, Value::null, id); + conn->stream << HTTPReply(200, strReply, fRun) << std::flush; } catch (Object& objError) { - ErrorReply(stream, objError, id); + ErrorReply(conn->stream, objError, id); + break; } catch (std::exception& e) { - ErrorReply(stream, JSONRPCError(-32700, e.what()), id); + ErrorReply(conn->stream, JSONRPCError(-32700, e.what()), id); + break; } } + + delete conn; + 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) @@ -2619,6 +2906,62 @@ 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 == "getblock" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "gettransaction" && n > 1) ConvertTo<Object>(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]); + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) + { + string s = params[1].get_str(); + Value v; + if (!read_string(s, v) || v.type() != array_type) + throw runtime_error("type mismatch "+s); + params[1] = v.get_array(); + } + return params; +} + int CommandLineRPC(int argc, char *argv[]) { string strPrint; @@ -2638,53 +2981,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 == "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) - { - 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]); - if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "addmultisigaddress" && n > 1) - { - string s = params[1].get_str(); - Value v; - if (!read_string(s, v) || v.type() != array_type) - throw runtime_error("type mismatch "+s); - params[1] = v.get_array(); - } + std::vector<std::string> strParams(&argv[2], &argv[argc]); + Array params = RPCConvertValues(strMethod, strParams); // Execute Object reply = CallRPC(strMethod, params); |