From d743f0355269e2e03fe3f982e250d07999d72b98 Mon Sep 17 00:00:00 2001 From: s_nakamoto Date: Mon, 6 Sep 2010 21:03:04 +0000 Subject: [PATCH] Gavin Andresen: json-rpc return standard error objects with error code numbers, json-rpc command line client return exit code, added rpc backupwallet command git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@147 1a98c847-1fd6-4fd8-948a-caf3550aa51b --- db.cpp | 33 +++++++- db.h | 1 + rpc.cpp | 230 ++++++++++++++++++++++++++++++++++------------------ serialize.h | 5 +- 4 files changed, 186 insertions(+), 83 deletions(-) diff --git a/db.cpp b/db.cpp index 6717c04b..56ec9ee6 100644 --- a/db.cpp +++ b/db.cpp @@ -81,7 +81,7 @@ CDB::CDB(const char* pszFile, const char* pszMode) : pdb(NULL) DB_RECOVER, S_IRUSR | S_IWUSR); if (ret > 0) - throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); + throw runtime_error(strprintf("CDB() : error %d opening database environment", ret)); fDbEnvInit = true; } @@ -106,7 +106,7 @@ CDB::CDB(const char* pszFile, const char* pszMode) : pdb(NULL) CRITICAL_BLOCK(cs_db) --mapFileUseCount[strFile]; strFile = ""; - throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret)); + throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret)); } if (fCreate && !Exists(string("version"))) @@ -803,3 +803,32 @@ void ThreadFlushWalletDB(void* parg) } } } + +void BackupWallet(const string& strDest) +{ + while (!fShutdown) + { + CRITICAL_BLOCK(cs_db) + { + const string strFile = "wallet.dat"; + if (!mapFileUseCount.count(strFile) || mapFileUseCount[strFile] == 0) + { + // Flush log data to the dat file + CloseDb(strFile); + dbenv.txn_checkpoint(0, 0, 0); + dbenv.lsn_reset(strFile.c_str(), 0); + mapFileUseCount.erase(strFile); + + // Copy wallet.dat + filesystem::path pathDest(strDest); + if (filesystem::is_directory(pathDest)) + pathDest = pathDest / strFile; + filesystem::copy_file(filesystem::path(GetDataDir() + "/" + strFile), pathDest, filesystem::copy_option::overwrite_if_exists); + printf("copied wallet.dat to %s\n", pathDest.string().c_str()); + + return; + } + } + Sleep(100); + } +} diff --git a/db.h b/db.h index 83eff354..9b1f5e58 100644 --- a/db.h +++ b/db.h @@ -399,6 +399,7 @@ public: }; bool LoadWallet(bool& fFirstRunRet); +void BackupWallet(const string& strDest); inline bool SetAddressBookName(const string& strAddress, const string& strName) { diff --git a/rpc.cpp b/rpc.cpp index 17087287..0970c87b 100644 --- a/rpc.cpp +++ b/rpc.cpp @@ -22,6 +22,14 @@ typedef Value(*rpcfn_type)(const Array& params, bool fHelp); extern map mapCallTable; +Object JSONRPCError(int code, const string& message) +{ + Object error; + error.push_back(Pair("code", code)); + error.push_back(Pair("message", message)); + return error; +} + void PrintConsole(const char* format, ...) { @@ -352,7 +360,7 @@ Value sendtoaddress(const Array& params, bool fHelp) // Amount if (params[1].get_real() <= 0.0 || params[1].get_real() > 21000000.0) - throw runtime_error("Invalid amount"); + throw JSONRPCError(-3, "Invalid amount"); int64 nAmount = roundint64(params[1].get_real() * 100.00) * CENT; // Wallet comments @@ -364,7 +372,7 @@ Value sendtoaddress(const Array& params, bool fHelp) string strError = SendMoneyToBitcoinAddress(strAddress, nAmount, wtx); if (strError != "") - throw runtime_error(strError); + throw JSONRPCError(-4, strError); return "sent"; } @@ -401,7 +409,7 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) string strAddress = params[0].get_str(); CScript scriptPubKey; if (!scriptPubKey.SetBitcoinAddress(strAddress)) - throw runtime_error("Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid bitcoin address"); if (!IsMine(scriptPubKey)) return (double)0.0; @@ -627,6 +635,20 @@ Value listreceivedbylabel(const Array& params, bool fHelp) } +Value backupwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "backupwallet \n" + "Safely copies wallet.dat to destination, which can be a directory or a path with filename."); + + string strDest = params[0].get_str(); + BackupWallet(strDest); + + return Value::null; +} + + @@ -666,6 +688,7 @@ pair pCallTable[] = make_pair("getreceivedbylabel", &getreceivedbylabel), make_pair("listreceivedbyaddress", &listreceivedbyaddress), make_pair("listreceivedbylabel", &listreceivedbylabel), + make_pair("backupwallet", &backupwallet), }; map mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); @@ -685,6 +708,7 @@ string pAllowInSafeMode[] = "setlabel", "getlabel", "getaddressesbylabel", + "backupwallet", }; set setAllowInSafeMode(pAllowInSafeMode, pAllowInSafeMode + sizeof(pAllowInSafeMode)/sizeof(pAllowInSafeMode[0])); @@ -707,14 +731,14 @@ string HTTPPost(const string& strMsg, const map& mapRequestHeader << "Content-Type: application/json\r\n" << "Content-Length: " << strMsg.size() << "\r\n" << "Accept: application/json\r\n"; - for (map::const_iterator it = mapRequestHeaders.begin(); it != mapRequestHeaders.end(); ++it) - s << it->first << ": " << it->second << "\r\n"; + foreach(const PAIRTYPE(string, string)& item, mapRequestHeaders) + s << item.first << ": " << item.second << "\r\n"; s << "\r\n" << strMsg; return s.str(); } -string HTTPReply(const string& strMsg, int nStatus=200) +string HTTPReply(int nStatus, const string& strMsg) { if (nStatus == 401) return "HTTP/1.0 401 Authorization Required\r\n" @@ -734,8 +758,10 @@ string HTTPReply(const string& strMsg, int nStatus=200) "

401 Unauthorized.

\r\n" "\r\n"; string strStatus; - if (nStatus == 200) strStatus = "OK"; - if (nStatus == 500) strStatus = "Internal Server Error"; + if (nStatus == 200) strStatus = "OK"; + else if (nStatus == 400) strStatus = "Bad Request"; + else if (nStatus == 404) strStatus = "Not Found"; + else if (nStatus == 500) strStatus = "Internal Server Error"; return strprintf( "HTTP/1.1 %d %s\r\n" "Connection: close\r\n" @@ -795,13 +821,16 @@ int ReadHTTP(tcp::iostream& stream, map& mapHeadersRet, string& // Read header int nLen = ReadHTTPHeader(stream, mapHeadersRet); - if (nLen <= 0) + if (nLen < 0 || nLen > MAX_SIZE) return 500; // Read message - vector vch(nLen); - stream.read(&vch[0], nLen); - strMessageRet = string(vch.begin(), vch.end()); + if (nLen > 0) + { + vector vch(nLen); + stream.read(&vch[0], nLen); + strMessageRet = string(vch.begin(), vch.end()); + } return nStatus; } @@ -859,9 +888,12 @@ bool HTTPAuthorized(map& mapHeaders) } // -// JSON-RPC protocol +// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, +// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were +// unspecified (HTTP errors and contents of 'error'). // -// http://json-rpc.org/wiki/specification +// 1.0 spec: http://json-rpc.org/wiki/specification +// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http // http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx // @@ -956,7 +988,7 @@ void ThreadRPCServer2(void* parg) // Check authorization if (mapHeaders.count("Authorization") == 0) { - stream << HTTPReply("", 401) << std::flush; + stream << HTTPReply(401, "") << std::flush; continue; } if (!HTTPAuthorized(mapHeaders)) @@ -965,57 +997,83 @@ void ThreadRPCServer2(void* parg) if (mapArgs["-rpcpassword"].size() < 15) Sleep(50); - stream << HTTPReply("", 401) << std::flush; + stream << HTTPReply(401, "") << std::flush; printf("ThreadRPCServer incorrect password attempt\n"); continue; } - // Handle multiple invocations per request - string::iterator begin = strRequest.begin(); - while (skipspaces(begin), begin != strRequest.end()) + Value id = Value::null; + try { - string::iterator prev = begin; - Value id; + // Parse request + Value valRequest; + if (!read_string(strRequest, valRequest) || valRequest.type() != obj_type) + 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(); + 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::iterator mi = mapCallTable.find(strMethod); + if (mi == mapCallTable.end()) + throw JSONRPCError(-32601, "Method not found"); + + // Observe safe mode + string strWarning = GetWarnings("rpc"); + if (strWarning != "" && !mapArgs.count("-disablesafemode") && !setAllowInSafeMode.count(strMethod)) + throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + try { - // Parse request - Value valRequest; - if (!read_range(begin, strRequest.end(), valRequest)) - throw runtime_error("Parse error."); - const Object& request = valRequest.get_obj(); - if (find_value(request, "method").type() != str_type || - find_value(request, "params").type() != array_type) - throw runtime_error("Invalid request."); - - string strMethod = find_value(request, "method").get_str(); - const Array& params = find_value(request, "params").get_array(); - id = find_value(request, "id"); - - printf("ThreadRPCServer method=%s\n", strMethod.c_str()); - - // Observe safe mode - string strWarning = GetWarnings("rpc"); - if (strWarning != "" && !mapArgs.count("-disablesafemode") && !setAllowInSafeMode.count(strMethod)) - throw runtime_error(string("Safe mode: ") + strWarning); - // Execute - map::iterator mi = mapCallTable.find(strMethod); - if (mi == mapCallTable.end()) - throw runtime_error("Method not found."); Value result = (*(*mi).second)(params, false); // Send reply string strReply = JSONRPCReply(result, Value::null, id); - stream << HTTPReply(strReply, 200) << std::flush; + stream << HTTPReply(200, strReply) << std::flush; } catch (std::exception& e) { - // Send error reply - string strReply = JSONRPCReply(Value::null, e.what(), id); - stream << HTTPReply(strReply, 500) << std::flush; + // Send error reply from method + string strReply = JSONRPCReply(Value::null, JSONRPCError(-1, e.what()), id); + stream << HTTPReply(500, strReply) << std::flush; } - if (begin == prev) - break; + } + catch (Object& objError) + { + // Send error reply from json-rpc error object + int nStatus = 500; + int code = find_value(objError, "code").get_int(); + if (code == -32600) nStatus = 400; + else if (code == -32601) nStatus = 404; + string strReply = JSONRPCReply(Value::null, objError, id); + stream << HTTPReply(nStatus, strReply) << std::flush; + } + catch (std::exception& e) + { + // Send error reply from other json-rpc parsing errors + string strReply = JSONRPCReply(Value::null, JSONRPCError(-32700, e.what()), id); + stream << HTTPReply(500, strReply) << std::flush; } } } @@ -1023,7 +1081,7 @@ void ThreadRPCServer2(void* parg) -Value CallRPC(const string& strMethod, const Array& params) +Object CallRPC(const string& strMethod, const Array& params) { if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") throw runtime_error(strprintf( @@ -1052,7 +1110,7 @@ Value CallRPC(const string& strMethod, const Array& params) int nStatus = ReadHTTP(stream, mapHeaders, strReply); if (nStatus == 401) throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); - else if (nStatus >= 400 && nStatus != 500) + else if (nStatus >= 400 && nStatus != 400 && nStatus != 404 && nStatus != 500) throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); else if (strReply.empty()) throw runtime_error("no response from server"); @@ -1065,15 +1123,7 @@ Value CallRPC(const string& strMethod, const Array& params) if (reply.empty()) throw runtime_error("expected reply to have result, error and id properties"); - const Value& result = find_value(reply, "result"); - const Value& error = find_value(reply, "error"); - const Value& id = find_value(reply, "id"); - - if (error.type() == str_type) - throw runtime_error(error.get_str()); - else if (error.type() != null_type) - throw runtime_error(write_string(error, false)); - return result; + return reply; } @@ -1098,6 +1148,8 @@ void ConvertTo(Value& value) int CommandLineRPC(int argc, char *argv[]) { + string strPrint; + int nRet = 0; try { // Skip switches @@ -1137,32 +1189,52 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "listreceivedbylabel" && n > 1) ConvertTo(params[1]); // Execute - Value result = CallRPC(strMethod, params); + Object reply = CallRPC(strMethod, params); + + // Parse reply + const Value& result = find_value(reply, "result"); + const Value& error = find_value(reply, "error"); + const Value& id = find_value(reply, "id"); - // Print result - string strResult = (result.type() == str_type ? result.get_str() : write_string(result, true)); - if (result.type() != null_type) + if (error.type() != null_type) { -#if defined(__WXMSW__) && defined(GUI) - // Windows GUI apps can't print to command line, - // so settle for a message box yuck - MyMessageBox(strResult.c_str(), "Bitcoin", wxOK); -#else - fprintf(stdout, "%s\n", strResult.c_str()); -#endif + // Error + strPrint = "error: " + write_string(error, false); + int code = find_value(error.get_obj(), "code").get_int(); + nRet = abs(code); + } + else + { + // Result + if (result.type() == null_type) + strPrint = ""; + else if (result.type() == str_type) + strPrint = result.get_str(); + else + strPrint = write_string(result, true); } - return 0; } - catch (std::exception& e) { + catch (std::exception& e) + { + strPrint = string("error: ") + e.what(); + nRet = 87; + } + catch (...) + { + PrintException(NULL, "CommandLineRPC()"); + } + + if (strPrint != "") + { #if defined(__WXMSW__) && defined(GUI) - MyMessageBox(strprintf("error: %s\n", e.what()).c_str(), "Bitcoin", wxOK); + // Windows GUI apps can't print to command line, + // so settle for a message box yuck + MyMessageBox(strPrint, "Bitcoin", wxOK); #else - fprintf(stderr, "error: %s\n", e.what()); + fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); #endif - } catch (...) { - PrintException(NULL, "CommandLineRPC()"); } - return 1; + return nRet; } diff --git a/serialize.h b/serialize.h index 37971597..e1404b70 100644 --- a/serialize.h +++ b/serialize.h @@ -2,8 +2,10 @@ // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. +#include #include #include +#include #include #if defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 int64; @@ -18,10 +20,9 @@ typedef unsigned long long uint64; class CScript; class CDataStream; class CAutoFile; - static const unsigned int MAX_SIZE = 0x02000000; -static const int VERSION = 311; +static const int VERSION = 312; static const char* pszSubVer = ".0";