Gavin Andresen: json-rpc return standard error objects with error code numbers,
authors_nakamoto <s_nakamoto@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
Mon, 6 Sep 2010 21:03:04 +0000 (21:03 +0000)
committers_nakamoto <s_nakamoto@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
Mon, 6 Sep 2010 21:03:04 +0000 (21:03 +0000)
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
db.h
rpc.cpp
serialize.h

diff --git a/db.cpp b/db.cpp
index 6717c04..56ec9ee 100644 (file)
--- 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 83eff35..9b1f5e5 100644 (file)
--- 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 1708728..0970c87 100644 (file)
--- a/rpc.cpp
+++ b/rpc.cpp
@@ -22,6 +22,14 @@ typedef Value(*rpcfn_type)(const Array& params, bool fHelp);
 extern map<string, rpcfn_type> 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 <destination>\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<string, rpcfn_type> pCallTable[] =
     make_pair("getreceivedbylabel",    &getreceivedbylabel),
     make_pair("listreceivedbyaddress", &listreceivedbyaddress),
     make_pair("listreceivedbylabel",   &listreceivedbylabel),
+    make_pair("backupwallet",          &backupwallet),
 };
 map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0]));
 
@@ -685,6 +708,7 @@ string pAllowInSafeMode[] =
     "setlabel",
     "getlabel",
     "getaddressesbylabel",
+    "backupwallet",
 };
 set<string> setAllowInSafeMode(pAllowInSafeMode, pAllowInSafeMode + sizeof(pAllowInSafeMode)/sizeof(pAllowInSafeMode[0]));
 
@@ -707,14 +731,14 @@ string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeader
       << "Content-Type: application/json\r\n"
       << "Content-Length: " << strMsg.size() << "\r\n"
       << "Accept: application/json\r\n";
-    for (map<string,string>::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)
             "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
             "</HTML>\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<string, string>& mapHeadersRet, string&
 
     // Read header
     int nLen = ReadHTTPHeader(stream, mapHeadersRet);
-    if (nLen <= 0)
+    if (nLen < 0 || nLen > MAX_SIZE)
         return 500;
 
     // Read message
-    vector<char> vch(nLen);
-    stream.read(&vch[0], nLen);
-    strMessageRet = string(vch.begin(), vch.end());
+    if (nLen > 0)
+    {
+        vector<char> vch(nLen);
+        stream.read(&vch[0], nLen);
+        strMessageRet = string(vch.begin(), vch.end());
+    }
 
     return nStatus;
 }
@@ -859,9 +888,12 @@ bool HTTPAuthorized(map<string, string>& 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<string, rpcfn_type>::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<string, rpcfn_type>::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<bool>(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;
 }
 
 
index 3797159..e1404b7 100644 (file)
@@ -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 <string>
 #include <vector>
 #include <map>
+#include <set>
 #include <boost/type_traits/is_fundamental.hpp>
 #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";