aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/server.cpp')
-rw-r--r--src/rpc/server.cpp539
1 files changed, 539 insertions, 0 deletions
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
new file mode 100644
index 0000000000..b2d4559ccd
--- /dev/null
+++ b/src/rpc/server.cpp
@@ -0,0 +1,539 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "rpc/server.h"
+
+#include "base58.h"
+#include "init.h"
+#include "random.h"
+#include "sync.h"
+#include "ui_interface.h"
+#include "util.h"
+#include "utilstrencodings.h"
+
+#include <univalue.h>
+
+#include <boost/bind.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/foreach.hpp>
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2/signal.hpp>
+#include <boost/thread.hpp>
+#include <boost/algorithm/string/case_conv.hpp> // for to_upper()
+
+using namespace RPCServer;
+using namespace std;
+
+static bool fRPCRunning = false;
+static bool fRPCInWarmup = true;
+static std::string rpcWarmupStatus("RPC server started");
+static CCriticalSection cs_rpcWarmup;
+/* Timer-creating functions */
+static RPCTimerInterface* timerInterface = NULL;
+/* Map of name to timer.
+ * @note Can be changed to std::unique_ptr when C++11 */
+static std::map<std::string, boost::shared_ptr<RPCTimerBase> > deadlineTimers;
+
+static struct CRPCSignals
+{
+ boost::signals2::signal<void ()> Started;
+ boost::signals2::signal<void ()> Stopped;
+ boost::signals2::signal<void (const CRPCCommand&)> PreCommand;
+ boost::signals2::signal<void (const CRPCCommand&)> PostCommand;
+} g_rpcSignals;
+
+void RPCServer::OnStarted(boost::function<void ()> slot)
+{
+ g_rpcSignals.Started.connect(slot);
+}
+
+void RPCServer::OnStopped(boost::function<void ()> slot)
+{
+ g_rpcSignals.Stopped.connect(slot);
+}
+
+void RPCServer::OnPreCommand(boost::function<void (const CRPCCommand&)> slot)
+{
+ g_rpcSignals.PreCommand.connect(boost::bind(slot, _1));
+}
+
+void RPCServer::OnPostCommand(boost::function<void (const CRPCCommand&)> slot)
+{
+ g_rpcSignals.PostCommand.connect(boost::bind(slot, _1));
+}
+
+void RPCTypeCheck(const UniValue& params,
+ const list<UniValue::VType>& typesExpected,
+ bool fAllowNull)
+{
+ unsigned int i = 0;
+ BOOST_FOREACH(UniValue::VType t, typesExpected)
+ {
+ if (params.size() <= i)
+ break;
+
+ const UniValue& v = params[i];
+ if (!((v.type() == t) || (fAllowNull && (v.isNull()))))
+ {
+ string err = strprintf("Expected type %s, got %s",
+ uvTypeName(t), uvTypeName(v.type()));
+ throw JSONRPCError(RPC_TYPE_ERROR, err);
+ }
+ i++;
+ }
+}
+
+void RPCTypeCheckObj(const UniValue& o,
+ const map<string, UniValue::VType>& typesExpected,
+ bool fAllowNull)
+{
+ BOOST_FOREACH(const PAIRTYPE(string, UniValue::VType)& t, typesExpected)
+ {
+ const UniValue& v = find_value(o, t.first);
+ if (!fAllowNull && v.isNull())
+ throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first));
+
+ if (!((v.type() == t.second) || (fAllowNull && (v.isNull()))))
+ {
+ string err = strprintf("Expected type %s for %s, got %s",
+ uvTypeName(t.second), t.first, uvTypeName(v.type()));
+ throw JSONRPCError(RPC_TYPE_ERROR, err);
+ }
+ }
+}
+
+CAmount AmountFromValue(const UniValue& value)
+{
+ if (!value.isNum() && !value.isStr())
+ throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string");
+ CAmount amount;
+ if (!ParseFixedPoint(value.getValStr(), 8, &amount))
+ throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
+ if (!MoneyRange(amount))
+ throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range");
+ return amount;
+}
+
+UniValue ValueFromAmount(const CAmount& amount)
+{
+ bool sign = amount < 0;
+ int64_t n_abs = (sign ? -amount : amount);
+ int64_t quotient = n_abs / COIN;
+ int64_t remainder = n_abs % COIN;
+ return UniValue(UniValue::VNUM,
+ strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder));
+}
+
+uint256 ParseHashV(const UniValue& v, string strName)
+{
+ string strHex;
+ if (v.isStr())
+ strHex = v.get_str();
+ if (!IsHex(strHex)) // Note: IsHex("") is false
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')");
+ uint256 result;
+ result.SetHex(strHex);
+ return result;
+}
+uint256 ParseHashO(const UniValue& o, string strKey)
+{
+ return ParseHashV(find_value(o, strKey), strKey);
+}
+vector<unsigned char> ParseHexV(const UniValue& v, string strName)
+{
+ string strHex;
+ if (v.isStr())
+ strHex = v.get_str();
+ if (!IsHex(strHex))
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be hexadecimal string (not '"+strHex+"')");
+ return ParseHex(strHex);
+}
+vector<unsigned char> ParseHexO(const UniValue& o, string strKey)
+{
+ return ParseHexV(find_value(o, strKey), strKey);
+}
+
+/**
+ * Note: This interface may still be subject to change.
+ */
+
+std::string CRPCTable::help(const std::string& strCommand) const
+{
+ string strRet;
+ string category;
+ set<rpcfn_type> setDone;
+ vector<pair<string, const CRPCCommand*> > vCommands;
+
+ for (map<string, const CRPCCommand*>::const_iterator mi = mapCommands.begin(); mi != mapCommands.end(); ++mi)
+ vCommands.push_back(make_pair(mi->second->category + mi->first, mi->second));
+ sort(vCommands.begin(), vCommands.end());
+
+ BOOST_FOREACH(const PAIRTYPE(string, const CRPCCommand*)& command, vCommands)
+ {
+ const CRPCCommand *pcmd = command.second;
+ string strMethod = pcmd->name;
+ // We already filter duplicates, but these deprecated screw up the sort order
+ if (strMethod.find("label") != string::npos)
+ continue;
+ if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand)
+ continue;
+ try
+ {
+ UniValue params;
+ rpcfn_type pfn = pcmd->actor;
+ if (setDone.insert(pfn).second)
+ (*pfn)(params, true);
+ }
+ catch (const std::exception& e)
+ {
+ // Help text is returned in an exception
+ string strHelp = string(e.what());
+ if (strCommand == "")
+ {
+ if (strHelp.find('\n') != string::npos)
+ strHelp = strHelp.substr(0, strHelp.find('\n'));
+
+ if (category != pcmd->category)
+ {
+ if (!category.empty())
+ strRet += "\n";
+ category = pcmd->category;
+ string firstLetter = category.substr(0,1);
+ boost::to_upper(firstLetter);
+ strRet += "== " + firstLetter + category.substr(1) + " ==\n";
+ }
+ }
+ strRet += strHelp + "\n";
+ }
+ }
+ if (strRet == "")
+ strRet = strprintf("help: unknown command: %s\n", strCommand);
+ strRet = strRet.substr(0,strRet.size()-1);
+ return strRet;
+}
+
+UniValue help(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() > 1)
+ throw runtime_error(
+ "help ( \"command\" )\n"
+ "\nList all commands, or get help for a specified command.\n"
+ "\nArguments:\n"
+ "1. \"command\" (string, optional) The command to get help on\n"
+ "\nResult:\n"
+ "\"text\" (string) The help text\n"
+ );
+
+ string strCommand;
+ if (params.size() > 0)
+ strCommand = params[0].get_str();
+
+ return tableRPC.help(strCommand);
+}
+
+
+UniValue stop(const UniValue& params, bool fHelp)
+{
+ // Accept the deprecated and ignored 'detach' boolean argument
+ if (fHelp || params.size() > 1)
+ throw runtime_error(
+ "stop\n"
+ "\nStop Bitcoin server.");
+ // Event loop will exit after current HTTP requests have been handled, so
+ // this reply will get back to the client.
+ StartShutdown();
+ return "Bitcoin server stopping";
+}
+
+/**
+ * Call Table
+ */
+static const CRPCCommand vRPCCommands[] =
+{ // category name actor (function) okSafeMode
+ // --------------------- ------------------------ ----------------------- ----------
+ /* Overall control/query calls */
+ { "control", "getinfo", &getinfo, true }, /* uses wallet if enabled */
+ { "control", "help", &help, true },
+ { "control", "stop", &stop, true },
+
+ /* P2P networking */
+ { "network", "getnetworkinfo", &getnetworkinfo, true },
+ { "network", "addnode", &addnode, true },
+ { "network", "disconnectnode", &disconnectnode, true },
+ { "network", "getaddednodeinfo", &getaddednodeinfo, true },
+ { "network", "getconnectioncount", &getconnectioncount, true },
+ { "network", "getnettotals", &getnettotals, true },
+ { "network", "getpeerinfo", &getpeerinfo, true },
+ { "network", "ping", &ping, true },
+ { "network", "setban", &setban, true },
+ { "network", "listbanned", &listbanned, true },
+ { "network", "clearbanned", &clearbanned, true },
+
+ /* Block chain and UTXO */
+ { "blockchain", "getblockchaininfo", &getblockchaininfo, true },
+ { "blockchain", "getbestblockhash", &getbestblockhash, true },
+ { "blockchain", "getblockcount", &getblockcount, true },
+ { "blockchain", "getblock", &getblock, true },
+ { "blockchain", "getblockhash", &getblockhash, true },
+ { "blockchain", "getblockheader", &getblockheader, true },
+ { "blockchain", "getchaintips", &getchaintips, true },
+ { "blockchain", "getdifficulty", &getdifficulty, true },
+ { "blockchain", "getmempoolinfo", &getmempoolinfo, true },
+ { "blockchain", "getrawmempool", &getrawmempool, true },
+ { "blockchain", "gettxout", &gettxout, true },
+ { "blockchain", "gettxoutproof", &gettxoutproof, true },
+ { "blockchain", "verifytxoutproof", &verifytxoutproof, true },
+ { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
+ { "blockchain", "verifychain", &verifychain, true },
+
+ /* Mining */
+ { "mining", "getblocktemplate", &getblocktemplate, true },
+ { "mining", "getmininginfo", &getmininginfo, true },
+ { "mining", "getnetworkhashps", &getnetworkhashps, true },
+ { "mining", "prioritisetransaction", &prioritisetransaction, true },
+ { "mining", "submitblock", &submitblock, true },
+
+ /* Coin generation */
+ { "generating", "getgenerate", &getgenerate, true },
+ { "generating", "setgenerate", &setgenerate, true },
+ { "generating", "generate", &generate, true },
+
+ /* Raw transactions */
+ { "rawtransactions", "createrawtransaction", &createrawtransaction, true },
+ { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true },
+ { "rawtransactions", "decodescript", &decodescript, true },
+ { "rawtransactions", "getrawtransaction", &getrawtransaction, true },
+ { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false },
+ { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */
+
+ /* Utility functions */
+ { "util", "createmultisig", &createmultisig, true },
+ { "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */
+ { "util", "verifymessage", &verifymessage, true },
+ { "util", "estimatefee", &estimatefee, true },
+ { "util", "estimatepriority", &estimatepriority, true },
+ { "util", "estimatesmartfee", &estimatesmartfee, true },
+ { "util", "estimatesmartpriority", &estimatesmartpriority, true },
+
+ /* Not shown in help */
+ { "hidden", "invalidateblock", &invalidateblock, true },
+ { "hidden", "reconsiderblock", &reconsiderblock, true },
+ { "hidden", "setmocktime", &setmocktime, true },
+};
+
+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[](const std::string &name) const
+{
+ map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name);
+ if (it == mapCommands.end())
+ return NULL;
+ return (*it).second;
+}
+
+bool CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd)
+{
+ if (IsRPCRunning())
+ return false;
+
+ // don't allow overwriting for now
+ map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name);
+ if (it != mapCommands.end())
+ return false;
+
+ mapCommands[name] = pcmd;
+ return true;
+}
+
+bool StartRPC()
+{
+ LogPrint("rpc", "Starting RPC\n");
+ fRPCRunning = true;
+ g_rpcSignals.Started();
+ return true;
+}
+
+void InterruptRPC()
+{
+ LogPrint("rpc", "Interrupting RPC\n");
+ // Interrupt e.g. running longpolls
+ fRPCRunning = false;
+}
+
+void StopRPC()
+{
+ LogPrint("rpc", "Stopping RPC\n");
+ deadlineTimers.clear();
+ g_rpcSignals.Stopped();
+}
+
+bool IsRPCRunning()
+{
+ return fRPCRunning;
+}
+
+void SetRPCWarmupStatus(const std::string& newStatus)
+{
+ LOCK(cs_rpcWarmup);
+ rpcWarmupStatus = newStatus;
+}
+
+void SetRPCWarmupFinished()
+{
+ LOCK(cs_rpcWarmup);
+ assert(fRPCInWarmup);
+ fRPCInWarmup = false;
+}
+
+bool RPCIsInWarmup(std::string *outStatus)
+{
+ LOCK(cs_rpcWarmup);
+ if (outStatus)
+ *outStatus = rpcWarmupStatus;
+ return fRPCInWarmup;
+}
+
+void JSONRequest::parse(const UniValue& valRequest)
+{
+ // Parse request
+ if (!valRequest.isObject())
+ throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
+ const UniValue& request = valRequest.get_obj();
+
+ // Parse id now so errors from here on will have the id
+ id = find_value(request, "id");
+
+ // Parse method
+ UniValue valMethod = find_value(request, "method");
+ if (valMethod.isNull())
+ throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method");
+ if (!valMethod.isStr())
+ throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
+ strMethod = valMethod.get_str();
+ if (strMethod != "getblocktemplate")
+ LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
+
+ // Parse params
+ UniValue valParams = find_value(request, "params");
+ if (valParams.isArray())
+ params = valParams.get_array();
+ else if (valParams.isNull())
+ params = UniValue(UniValue::VARR);
+ else
+ throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
+}
+
+static UniValue JSONRPCExecOne(const UniValue& req)
+{
+ UniValue rpc_result(UniValue::VOBJ);
+
+ JSONRequest jreq;
+ try {
+ jreq.parse(req);
+
+ UniValue result = tableRPC.execute(jreq.strMethod, jreq.params);
+ rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id);
+ }
+ catch (const UniValue& objError)
+ {
+ rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id);
+ }
+ catch (const std::exception& e)
+ {
+ rpc_result = JSONRPCReplyObj(NullUniValue,
+ JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
+ }
+
+ return rpc_result;
+}
+
+std::string JSONRPCExecBatch(const UniValue& vReq)
+{
+ UniValue ret(UniValue::VARR);
+ for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++)
+ ret.push_back(JSONRPCExecOne(vReq[reqIdx]));
+
+ return ret.write() + "\n";
+}
+
+UniValue CRPCTable::execute(const std::string &strMethod, const UniValue &params) const
+{
+ // Return immediately if in warmup
+ {
+ LOCK(cs_rpcWarmup);
+ if (fRPCInWarmup)
+ throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus);
+ }
+
+ // Find method
+ const CRPCCommand *pcmd = tableRPC[strMethod];
+ if (!pcmd)
+ throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
+
+ g_rpcSignals.PreCommand(*pcmd);
+
+ try
+ {
+ // Execute
+ return pcmd->actor(params, false);
+ }
+ catch (const std::exception& e)
+ {
+ throw JSONRPCError(RPC_MISC_ERROR, e.what());
+ }
+
+ g_rpcSignals.PostCommand(*pcmd);
+}
+
+std::string HelpExampleCli(const std::string& methodname, const std::string& args)
+{
+ return "> bitcoin-cli " + methodname + " " + args + "\n";
+}
+
+std::string HelpExampleRpc(const std::string& methodname, const std::string& args)
+{
+ return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", "
+ "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n";
+}
+
+void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
+{
+ if (!timerInterface)
+ timerInterface = iface;
+}
+
+void RPCSetTimerInterface(RPCTimerInterface *iface)
+{
+ timerInterface = iface;
+}
+
+void RPCUnsetTimerInterface(RPCTimerInterface *iface)
+{
+ if (timerInterface == iface)
+ timerInterface = NULL;
+}
+
+void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds)
+{
+ if (!timerInterface)
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
+ deadlineTimers.erase(name);
+ LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
+ deadlineTimers.insert(std::make_pair(name, boost::shared_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000))));
+}
+
+CRPCTable tableRPC;