diff --git a/src/httpserver.cpp b/src/httpserver.cpp index a72527333..e60a8b2a2 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -1,684 +1,698 @@ // Copyright (c) 2015-2016 The Bitcoin Core developers // Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include +#include #include #include // For HTTP status codes #include #include #include #include #include #include #include #include #include #include #ifdef EVENT__HAVE_NETINET_IN_H #include #ifdef _XOPEN_SOURCE_EXTENDED #include #endif #endif #include #include #include #include #include #include #include /** Maximum size of http request (request line + headers) */ static const size_t MAX_HEADERS_SIZE = 8192; /** * Maximum HTTP post body size. Twice the maximum block size is added to this * value in practice. */ static const size_t MIN_SUPPORTED_BODY_SIZE = 0x02000000; /** HTTP request work item */ class HTTPWorkItem final : public HTTPClosure { public: HTTPWorkItem(Config &_config, std::unique_ptr _req, const std::string &_path, const HTTPRequestHandler &_func) : req(std::move(_req)), path(_path), func(_func), config(&_config) {} void operator()() override { func(*config, req.get(), path); } std::unique_ptr req; private: std::string path; HTTPRequestHandler func; Config *config; }; /** * Simple work queue for distributing work over multiple threads. * Work items are simply callable objects. */ template class WorkQueue { private: /** Mutex protects entire object */ Mutex cs; std::condition_variable cond; std::deque> queue; bool running; size_t maxDepth; public: explicit WorkQueue(size_t _maxDepth) : running(true), maxDepth(_maxDepth) {} /** * Precondition: worker threads have all stopped (they have all been joined) */ ~WorkQueue() {} /** Enqueue a work item */ bool Enqueue(WorkItem *item) { LOCK(cs); if (queue.size() >= maxDepth) { return false; } queue.emplace_back(std::unique_ptr(item)); cond.notify_one(); return true; } /** Thread function */ void Run() { while (true) { std::unique_ptr i; { WAIT_LOCK(cs, lock); while (running && queue.empty()) { cond.wait(lock); } if (!running) { break; } i = std::move(queue.front()); queue.pop_front(); } (*i)(); } } /** Interrupt and exit loops */ void Interrupt() { LOCK(cs); running = false; cond.notify_all(); } }; struct HTTPPathHandler { HTTPPathHandler(std::string _prefix, bool _exactMatch, HTTPRequestHandler _handler) : prefix(_prefix), exactMatch(_exactMatch), handler(_handler) {} std::string prefix; bool exactMatch; HTTPRequestHandler handler; }; /** HTTP module state */ //! libevent event loop static struct event_base *eventBase = nullptr; //! HTTP server struct evhttp *eventHTTP = nullptr; //! List of subnets to allow RPC connections from static std::vector rpc_allow_subnets; //! Work queue for handling longer requests off the event loop thread static WorkQueue *workQueue = nullptr; //! Handlers for (sub)paths std::vector pathHandlers; //! Bound listening sockets std::vector boundSockets; /** Check if a network address is allowed to access the HTTP server */ static bool ClientAllowed(const CNetAddr &netaddr) { if (!netaddr.IsValid()) { return false; } for (const CSubNet &subnet : rpc_allow_subnets) { if (subnet.Match(netaddr)) { return true; } } return false; } /** Initialize ACL list for HTTP server */ static bool InitHTTPAllowList() { rpc_allow_subnets.clear(); CNetAddr localv4; CNetAddr localv6; LookupHost("127.0.0.1", localv4, false); LookupHost("::1", localv6, false); // always allow IPv4 local subnet. rpc_allow_subnets.push_back(CSubNet(localv4, 8)); // always allow IPv6 localhost. rpc_allow_subnets.push_back(CSubNet(localv6)); for (const std::string &strAllow : gArgs.GetArgs("-rpcallowip")) { CSubNet subnet; LookupSubNet(strAllow.c_str(), subnet); if (!subnet.IsValid()) { uiInterface.ThreadSafeMessageBox( strprintf("Invalid -rpcallowip subnet specification: %s. " "Valid are a single IP (e.g. 1.2.3.4), a " "network/netmask (e.g. 1.2.3.4/255.255.255.0) or a " "network/CIDR (e.g. 1.2.3.4/24).", strAllow), "", CClientUIInterface::MSG_ERROR); return false; } rpc_allow_subnets.push_back(subnet); } std::string strAllowed; for (const CSubNet &subnet : rpc_allow_subnets) { strAllowed += subnet.ToString() + " "; } LogPrint(BCLog::HTTP, "Allowing HTTP connections from: %s\n", strAllowed); return true; } /** HTTP request method as string - use for logging only */ static std::string RequestMethodString(HTTPRequest::RequestMethod m) { switch (m) { case HTTPRequest::GET: return "GET"; case HTTPRequest::POST: return "POST"; case HTTPRequest::HEAD: return "HEAD"; case HTTPRequest::PUT: return "PUT"; case HTTPRequest::OPTIONS: return "OPTIONS"; default: return "unknown"; } } /** HTTP request callback */ static void http_request_cb(struct evhttp_request *req, void *arg) { Config &config = *reinterpret_cast(arg); // Disable reading to work around a libevent bug, fixed in 2.2.0. if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02020001) { evhttp_connection *conn = evhttp_request_get_connection(req); if (conn) { bufferevent *bev = evhttp_connection_get_bufferevent(conn); if (bev) { bufferevent_disable(bev, EV_READ); } } } auto hreq = std::make_unique(req); LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n", RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString()); // Early address-based allow check if (!ClientAllowed(hreq->GetPeer())) { hreq->WriteReply(HTTP_FORBIDDEN); return; } // Early reject unknown HTTP methods if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { hreq->WriteReply(HTTP_BADMETHOD); return; } // Find registered handler for prefix std::string strURI = hreq->GetURI(); std::string path; std::vector::const_iterator i = pathHandlers.begin(); std::vector::const_iterator iend = pathHandlers.end(); for (; i != iend; ++i) { bool match = false; if (i->exactMatch) { match = (strURI == i->prefix); } else { match = (strURI.substr(0, i->prefix.size()) == i->prefix); } if (match) { path = strURI.substr(i->prefix.size()); break; } } // Dispatch to worker thread. if (i != iend) { std::unique_ptr item( new HTTPWorkItem(config, std::move(hreq), path, i->handler)); assert(workQueue); if (workQueue->Enqueue(item.get())) { /* if true, queue took ownership */ item.release(); } else { LogPrintf("WARNING: request rejected because http work queue depth " "exceeded, it can be increased with the -rpcworkqueue= " "setting\n"); item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); } } else { hreq->WriteReply(HTTP_NOTFOUND); } } /** Callback to reject HTTP requests after shutdown. */ static void http_reject_request_cb(struct evhttp_request *req, void *) { LogPrint(BCLog::HTTP, "Rejecting request while shutting down\n"); evhttp_send_error(req, HTTP_SERVUNAVAIL, nullptr); } /** Event dispatcher thread */ static bool ThreadHTTP(struct event_base *base) { RenameThread("bitcoin-http"); LogPrint(BCLog::HTTP, "Entering http event loop\n"); event_base_dispatch(base); // Event loop will be interrupted by InterruptHTTPServer() LogPrint(BCLog::HTTP, "Exited http event loop\n"); return event_base_got_break(base) == 0; } /** Bind HTTP server to specified addresses */ static bool HTTPBindAddresses(struct evhttp *http) { int defaultPort = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); std::vector> endpoints; // Determine what addresses to bind to if (!gArgs.IsArgSet("-rpcallowip")) { // Default to loopback if not allowing external IPs. endpoints.push_back(std::make_pair("::1", defaultPort)); endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); if (gArgs.IsArgSet("-rpcbind")) { LogPrintf("WARNING: option -rpcbind was ignored because " "-rpcallowip was not specified, refusing to allow " "everyone to connect\n"); } } else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address. for (const std::string &strRPCBind : gArgs.GetArgs("-rpcbind")) { int port = defaultPort; std::string host; SplitHostPort(strRPCBind, port, host); endpoints.push_back(std::make_pair(host, port)); } } else { // No specific bind address specified, bind to any. endpoints.push_back(std::make_pair("::", defaultPort)); endpoints.push_back(std::make_pair("0.0.0.0", defaultPort)); } // Bind addresses for (std::vector>::iterator i = endpoints.begin(); i != endpoints.end(); ++i) { LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, i->second); evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle( http, i->first.empty() ? nullptr : i->first.c_str(), i->second); if (bind_handle) { boundSockets.push_back(bind_handle); } else { LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second); } } return !boundSockets.empty(); } /** Simple wrapper to set thread name and run work queue */ static void HTTPWorkQueueRun(WorkQueue *queue) { RenameThread("bitcoin-httpworker"); queue->Run(); } /** libevent event log callback */ static void libevent_log_cb(int severity, const char *msg) { #ifndef EVENT_LOG_WARN // EVENT_LOG_WARN was added in 2.0.19; but before then _EVENT_LOG_WARN existed. #define EVENT_LOG_WARN _EVENT_LOG_WARN #endif // Log warn messages and higher without debug category. if (severity >= EVENT_LOG_WARN) { LogPrintf("libevent: %s\n", msg); } else { LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg); } } bool InitHTTPServer(Config &config) { if (!InitHTTPAllowList()) { return false; } if (gArgs.GetBoolArg("-rpcssl", false)) { uiInterface.ThreadSafeMessageBox( "SSL mode for RPC (-rpcssl) is no longer supported.", "", CClientUIInterface::MSG_ERROR); return false; } // Redirect libevent's logging to our own log event_set_log_callback(&libevent_log_cb); -#if LIBEVENT_VERSION_NUMBER >= 0x02010100 - // If -debug=libevent, set full libevent debugging. - // Otherwise, disable all libevent debugging. - if (LogAcceptCategory(BCLog::LIBEVENT)) { - event_enable_debug_logging(EVENT_DBG_ALL); - } else { - event_enable_debug_logging(EVENT_DBG_NONE); + // Update libevent's log handling. Returns false if our version of + // libevent doesn't support debug logging, in which case we should + // clear the BCLog::LIBEVENT flag. + if (!UpdateHTTPServerLogging( + GetLogger().WillLogCategory(BCLog::LIBEVENT))) { + GetLogger().DisableCategory(BCLog::LIBEVENT); } -#endif + #ifdef WIN32 evthread_use_windows_threads(); #else evthread_use_pthreads(); #endif raii_event_base base_ctr = obtain_event_base(); /* Create a new evhttp object to handle requests. */ raii_evhttp http_ctr = obtain_evhttp(base_ctr.get()); struct evhttp *http = http_ctr.get(); if (!http) { LogPrintf("couldn't create evhttp. Exiting.\n"); return false; } evhttp_set_timeout( http, gArgs.GetArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT)); evhttp_set_max_headers_size(http, MAX_HEADERS_SIZE); evhttp_set_max_body_size(http, MIN_SUPPORTED_BODY_SIZE + 2 * config.GetMaxBlockSize()); evhttp_set_gencb(http, http_request_cb, &config); // Only POST and OPTIONS are supported, but we return HTTP 405 for the // others evhttp_set_allowed_methods( http, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_OPTIONS); if (!HTTPBindAddresses(http)) { LogPrintf("Unable to bind any endpoint for RPC server\n"); return false; } LogPrint(BCLog::HTTP, "Initialized HTTP server\n"); int workQueueDepth = std::max( (long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); workQueue = new WorkQueue(workQueueDepth); // transfer ownership to eventBase/HTTP via .release() eventBase = base_ctr.release(); eventHTTP = http_ctr.release(); return true; } +bool UpdateHTTPServerLogging(bool enable) { +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + if (enable) { + event_enable_debug_logging(EVENT_DBG_ALL); + } else { + event_enable_debug_logging(EVENT_DBG_NONE); + } + return true; +#else + // Can't update libevent logging if version < 02010100 + return false; +#endif +} + std::thread threadHTTP; std::future threadResult; static std::vector g_thread_http_workers; void StartHTTPServer() { LogPrint(BCLog::HTTP, "Starting HTTP server\n"); int rpcThreads = std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); std::packaged_task task(ThreadHTTP); threadResult = task.get_future(); threadHTTP = std::thread(std::move(task), eventBase); for (int i = 0; i < rpcThreads; i++) { g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue); } } void InterruptHTTPServer() { LogPrint(BCLog::HTTP, "Interrupting HTTP server\n"); if (eventHTTP) { // Unlisten sockets for (evhttp_bound_socket *socket : boundSockets) { evhttp_del_accept_socket(eventHTTP, socket); } // Reject requests on current connections evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr); } if (workQueue) { workQueue->Interrupt(); } } void StopHTTPServer() { LogPrint(BCLog::HTTP, "Stopping HTTP server\n"); if (workQueue) { LogPrint(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n"); for (auto &thread : g_thread_http_workers) { thread.join(); } g_thread_http_workers.clear(); delete workQueue; workQueue = nullptr; } if (eventBase) { LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); // Exit the event loop as soon as there are no active events. event_base_loopexit(eventBase, nullptr); // Give event loop a few seconds to exit (to send back last RPC // responses), then break it. Before this was solved with // event_base_loopexit, but that didn't work as expected in at least // libevent 2.0.21 and always introduced a delay. In libevent master // that appears to be solved, so in the future that solution could be // used again (if desirable). // (see discussion in https://github.com/bitcoin/bitcoin/pull/6990) if (threadResult.valid() && threadResult.wait_for(std::chrono::milliseconds(2000)) == std::future_status::timeout) { LogPrintf("HTTP event loop did not exit within allotted time, " "sending loopbreak\n"); event_base_loopbreak(eventBase); } threadHTTP.join(); } if (eventHTTP) { evhttp_free(eventHTTP); eventHTTP = nullptr; } if (eventBase) { event_base_free(eventBase); eventBase = nullptr; } LogPrint(BCLog::HTTP, "Stopped HTTP server\n"); } struct event_base *EventBase() { return eventBase; } static void httpevent_callback_fn(evutil_socket_t, short, void *data) { // Static handler: simply call inner handler HTTPEvent *self = static_cast(data); self->handler(); if (self->deleteWhenTriggered) { delete self; } } HTTPEvent::HTTPEvent(struct event_base *base, bool _deleteWhenTriggered, const std::function &_handler) : deleteWhenTriggered(_deleteWhenTriggered), handler(_handler) { ev = event_new(base, -1, 0, httpevent_callback_fn, this); assert(ev); } HTTPEvent::~HTTPEvent() { event_free(ev); } void HTTPEvent::trigger(struct timeval *tv) { if (tv == nullptr) { // Immediately trigger event in main thread. event_active(ev, 0, 0); } else { // Trigger after timeval passed. evtimer_add(ev, tv); } } HTTPRequest::HTTPRequest(struct evhttp_request *_req) : req(_req), replySent(false) {} HTTPRequest::~HTTPRequest() { if (!replySent) { // Keep track of whether reply was sent to avoid request leaks LogPrintf("%s: Unhandled request\n", __func__); WriteReply(HTTP_INTERNAL, "Unhandled request"); } // evhttpd cleans up the request, as long as a reply was sent. } std::pair HTTPRequest::GetHeader(const std::string &hdr) { const struct evkeyvalq *headers = evhttp_request_get_input_headers(req); assert(headers); const char *val = evhttp_find_header(headers, hdr.c_str()); if (val) { return std::make_pair(true, val); } else { return std::make_pair(false, ""); } } std::string HTTPRequest::ReadBody() { struct evbuffer *buf = evhttp_request_get_input_buffer(req); if (!buf) { return ""; } size_t size = evbuffer_get_length(buf); /** * Trivial implementation: if this is ever a performance bottleneck, * internal copying can be avoided in multi-segment buffers by using * evbuffer_peek and an awkward loop. Though in that case, it'd be even * better to not copy into an intermediate string but use a stream * abstraction to consume the evbuffer on the fly in the parsing algorithm. */ const char *data = (const char *)evbuffer_pullup(buf, size); // returns nullptr in case of empty buffer. if (!data) { return ""; } std::string rv(data, size); evbuffer_drain(buf, size); return rv; } void HTTPRequest::WriteHeader(const std::string &hdr, const std::string &value) { struct evkeyvalq *headers = evhttp_request_get_output_headers(req); assert(headers); evhttp_add_header(headers, hdr.c_str(), value.c_str()); } /** * Closure sent to main thread to request a reply to be sent to a HTTP request. * Replies must be sent in the main loop in the main http thread, this cannot be * done from worker threads. */ void HTTPRequest::WriteReply(int nStatus, const std::string &strReply) { assert(!replySent && req); // Send event to main http thread to send reply message struct evbuffer *evb = evhttp_request_get_output_buffer(req); assert(evb); evbuffer_add(evb, strReply.data(), strReply.size()); auto req_copy = req; HTTPEvent *ev = new HTTPEvent(eventBase, true, [req_copy, nStatus] { evhttp_send_reply(req_copy, nStatus, nullptr, nullptr); // Re-enable reading from the socket. This is the second part of the // libevent workaround above. if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02020001) { evhttp_connection *conn = evhttp_request_get_connection(req_copy); if (conn) { bufferevent *bev = evhttp_connection_get_bufferevent(conn); if (bev) { bufferevent_enable(bev, EV_READ | EV_WRITE); } } } }); ev->trigger(nullptr); replySent = true; // transferred back to main thread. req = nullptr; } CService HTTPRequest::GetPeer() { evhttp_connection *con = evhttp_request_get_connection(req); CService peer; if (con) { // evhttp retains ownership over returned address string const char *address = ""; uint16_t port = 0; evhttp_connection_get_peer(con, (char **)&address, &port); peer = LookupNumeric(address, port); } return peer; } std::string HTTPRequest::GetURI() { return evhttp_request_get_uri(req); } HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() { switch (evhttp_request_get_command(req)) { case EVHTTP_REQ_GET: return GET; case EVHTTP_REQ_POST: return POST; case EVHTTP_REQ_HEAD: return HEAD; case EVHTTP_REQ_PUT: return PUT; case EVHTTP_REQ_OPTIONS: return OPTIONS; default: return UNKNOWN; } } void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) { LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler)); } void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) { std::vector::iterator i = pathHandlers.begin(); std::vector::iterator iend = pathHandlers.end(); for (; i != iend; ++i) { if (i->prefix == prefix && i->exactMatch == exactMatch) { break; } } if (i != iend) { LogPrint(BCLog::HTTP, "Unregistering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); pathHandlers.erase(i); } } diff --git a/src/httpserver.h b/src/httpserver.h index 719250fb9..081c03771 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -1,159 +1,165 @@ // Copyright (c) 2015-2016 The Bitcoin Core developers // Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_HTTPSERVER_H #define BITCOIN_HTTPSERVER_H #include #include #include static const int DEFAULT_HTTP_THREADS = 4; static const int DEFAULT_HTTP_WORKQUEUE = 16; static const int DEFAULT_HTTP_SERVER_TIMEOUT = 30; struct evhttp_request; struct event_base; class Config; class CService; class HTTPRequest; /** * Initialize HTTP server. * Call this before RegisterHTTPHandler or EventBase(). */ bool InitHTTPServer(Config &config); /** * Start HTTP server. * This is separate from InitHTTPServer to give users race-condition-free time * to register their handlers between InitHTTPServer and StartHTTPServer. */ void StartHTTPServer(); /** Interrupt HTTP server threads */ void InterruptHTTPServer(); /** Stop HTTP server */ void StopHTTPServer(); +/** + * Change logging level for libevent. Removes BCLog::LIBEVENT from + * logCategories if libevent doesn't support debug logging. + */ +bool UpdateHTTPServerLogging(bool enable); + /** Handler for requests to a certain HTTP path */ typedef std::function HTTPRequestHandler; /** * Register handler for prefix. * If multiple handlers match a prefix, the first-registered one will * be invoked. */ void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler); /** Unregister handler for prefix */ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch); /** * Return evhttp event base. This can be used by submodules to * queue timers or custom events. */ struct event_base *EventBase(); /** * In-flight HTTP request. * Thin C++ wrapper around evhttp_request. */ class HTTPRequest { private: struct evhttp_request *req; bool replySent; public: explicit HTTPRequest(struct evhttp_request *req); ~HTTPRequest(); enum RequestMethod { UNKNOWN, GET, POST, HEAD, PUT, OPTIONS }; /** Get requested URI */ std::string GetURI(); /** Get CService (address:ip) for the origin of the http request */ CService GetPeer(); /** Get request method */ RequestMethod GetRequestMethod(); /** * Get the request header specified by hdr, or an empty string. * Return a pair (isPresent,string). */ std::pair GetHeader(const std::string &hdr); /** * Read request body. * * @note As this consumes the underlying buffer, call this only once. * Repeated calls will return an empty string. */ std::string ReadBody(); /** * Write output header. * * @note call this before calling WriteErrorReply or Reply. */ void WriteHeader(const std::string &hdr, const std::string &value); /** * Write HTTP reply. * nStatus is the HTTP status code to send. * strReply is the body of the reply. Keep it empty to send a standard * message. * * @note Can be called only once. As this will give the request back to the * main thread, do not call any other HTTPRequest methods after calling * this. */ void WriteReply(int nStatus, const std::string &strReply = ""); }; /** Event handler closure */ class HTTPClosure { public: virtual void operator()() = 0; virtual ~HTTPClosure() {} }; /** * Event class. This can be used either as a cross-thread trigger or as a * timer. */ class HTTPEvent { public: /** * Create a new event. * deleteWhenTriggered deletes this event object after the event is * triggered (and the handler called) * handler is the handler to call when the event is triggered. */ HTTPEvent(struct event_base *base, bool deleteWhenTriggered, const std::function &handler); ~HTTPEvent(); /** * Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger * it after the given time has elapsed. */ void trigger(struct timeval *tv); bool deleteWhenTriggered; std::function handler; private: struct event *ev; }; #endif // BITCOIN_HTTPSERVER_H diff --git a/src/logging.cpp b/src/logging.cpp index f7d027cb5..3b6a8f504 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -1,268 +1,283 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017-2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include bool fLogIPs = DEFAULT_LOGIPS; const char *const DEFAULT_DEBUGLOGFILE = "debug.log"; /** * NOTE: the logger instance is leaked on exit. This is ugly, but will be * cleaned up by the OS/libc. Defining a logger as a global object doesn't work * since the order of destruction of static/global objects is undefined. * Consider if the logger gets destroyed, and then some later destructor calls * LogPrintf, maybe indirectly, and you get a core dump at shutdown trying to * access the logger. When the shutdown sequence is fully audited and tested, * explicit destruction of these objects can be implemented by changing this * from a raw pointer to a std::unique_ptr. * * This method of initialization was originally introduced in * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c. */ BCLog::Logger &GetLogger() { static BCLog::Logger *const logger = new BCLog::Logger(); return *logger; } static int FileWriteStr(const std::string &str, FILE *fp) { return fwrite(str.data(), 1, str.size(), fp); } fs::path BCLog::Logger::GetDebugLogPath() { fs::path logfile(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)); return AbsPathForConfigVal(logfile); } bool BCLog::Logger::OpenDebugLog() { std::lock_guard scoped_lock(m_file_mutex); assert(m_fileout == nullptr); fs::path pathDebug = GetDebugLogPath(); m_fileout = fsbridge::fopen(pathDebug, "a"); if (!m_fileout) { return false; } // Unbuffered. setbuf(m_fileout, nullptr); // Dump buffered messages from before we opened the log. while (!m_msgs_before_open.empty()) { FileWriteStr(m_msgs_before_open.front(), m_fileout); m_msgs_before_open.pop_front(); } return true; } struct CLogCategoryDesc { BCLog::LogFlags flag; std::string category; }; const CLogCategoryDesc LogCategories[] = { {BCLog::NONE, "0"}, {BCLog::NONE, "none"}, {BCLog::NET, "net"}, {BCLog::TOR, "tor"}, {BCLog::MEMPOOL, "mempool"}, {BCLog::HTTP, "http"}, {BCLog::BENCH, "bench"}, {BCLog::ZMQ, "zmq"}, {BCLog::DB, "db"}, {BCLog::RPC, "rpc"}, {BCLog::ESTIMATEFEE, "estimatefee"}, {BCLog::ADDRMAN, "addrman"}, {BCLog::SELECTCOINS, "selectcoins"}, {BCLog::REINDEX, "reindex"}, {BCLog::CMPCTBLOCK, "cmpctblock"}, {BCLog::RAND, "rand"}, {BCLog::PRUNE, "prune"}, {BCLog::PROXY, "proxy"}, {BCLog::MEMPOOLREJ, "mempoolrej"}, {BCLog::LIBEVENT, "libevent"}, {BCLog::COINDB, "coindb"}, {BCLog::QT, "qt"}, {BCLog::LEVELDB, "leveldb"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str) { if (str == "") { flag = BCLog::ALL; return true; } for (const CLogCategoryDesc &category_desc : LogCategories) { if (category_desc.category == str) { flag = category_desc.flag; return true; } } return false; } std::string ListLogCategories() { std::string ret; int outcount = 0; for (const CLogCategoryDesc &category_desc : LogCategories) { // Omit the special cases. if (category_desc.flag != BCLog::NONE && category_desc.flag != BCLog::ALL) { if (outcount != 0) { ret += ", "; } ret += category_desc.category; outcount++; } } return ret; } +std::vector ListActiveLogCategories() { + std::vector ret; + for (const CLogCategoryDesc &category_desc : LogCategories) { + // Omit the special cases. + if (category_desc.flag != BCLog::NONE && + category_desc.flag != BCLog::ALL) { + CLogCategoryActive catActive; + catActive.category = category_desc.category; + catActive.active = LogAcceptCategory(category_desc.flag); + ret.push_back(catActive); + } + } + return ret; +} + BCLog::Logger::~Logger() { if (m_fileout) { fclose(m_fileout); } } std::string BCLog::Logger::LogTimestampStr(const std::string &str) { std::string strStamped; if (!m_log_timestamps) { return str; } if (m_started_new_line) { int64_t nTimeMicros = GetTimeMicros(); strStamped = FormatISO8601DateTime(nTimeMicros / 1000000); if (m_log_time_micros) { strStamped.pop_back(); strStamped += strprintf(".%06dZ", nTimeMicros % 1000000); } int64_t mocktime = GetMockTime(); if (mocktime) { strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")"; } strStamped += ' ' + str; } else { strStamped = str; } if (!str.empty() && str[str.size() - 1] == '\n') { m_started_new_line = true; } else { m_started_new_line = false; } return strStamped; } void BCLog::Logger::LogPrintStr(const std::string &str) { std::string strTimestamped = LogTimestampStr(str); if (m_print_to_console) { // Print to console. fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); fflush(stdout); } else if (m_print_to_file) { std::lock_guard scoped_lock(m_file_mutex); // Buffer if we haven't opened the log yet. if (m_fileout == nullptr) { m_msgs_before_open.push_back(strTimestamped); } else { // Reopen the log file, if requested. if (m_reopen_file) { m_reopen_file = false; fs::path pathDebug = GetDebugLogPath(); FILE *new_fileout = fsbridge::fopen(pathDebug, "a"); if (new_fileout) { // unbuffered. setbuf(m_fileout, nullptr); fclose(m_fileout); m_fileout = new_fileout; } } FileWriteStr(strTimestamped, m_fileout); } } } void BCLog::Logger::ShrinkDebugFile() { // Amount of debug.log to save at end when shrinking (must fit in memory) constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000; // Scroll debug.log if it's getting too big. fs::path pathLog = GetDebugLogPath(); FILE *file = fsbridge::fopen(pathLog, "r"); // If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE // trim it down by saving only the last RECENT_DEBUG_HISTORY_SIZE bytes. if (file && fs::file_size(pathLog) > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10)) { // Restart the file with some of the end. std::vector vch(RECENT_DEBUG_HISTORY_SIZE, 0); if (fseek(file, -((long)vch.size()), SEEK_END)) { LogPrintf("Failed to shrink debug log file: fseek(...) failed\n"); fclose(file); return; } int nBytes = fread(vch.data(), 1, vch.size(), file); fclose(file); file = fsbridge::fopen(pathLog, "w"); if (file) { fwrite(vch.data(), 1, nBytes, file); fclose(file); } } else if (file != nullptr) { fclose(file); } } void BCLog::Logger::EnableCategory(LogFlags category) { m_categories |= category; } bool BCLog::Logger::EnableCategory(const std::string &str) { BCLog::LogFlags flag; if (!GetLogCategory(flag, str)) { return false; } EnableCategory(flag); return true; } void BCLog::Logger::DisableCategory(LogFlags category) { m_categories &= ~category; } bool BCLog::Logger::DisableCategory(const std::string &str) { BCLog::LogFlags flag; if (!GetLogCategory(flag, str)) { return false; } DisableCategory(flag); return true; } bool BCLog::Logger::WillLogCategory(LogFlags category) const { // ALL is not meant to be used as a logging category, but only as a mask // representing all categories. if (category == BCLog::NONE || category == BCLog::ALL) { LogPrintf("Error trying to log using a category mask instead of an " "explicit category.\n"); return true; } return (m_categories.load(std::memory_order_relaxed) & category) != 0; } bool BCLog::Logger::DefaultShrinkDebugFile() const { return m_categories != BCLog::NONE; } diff --git a/src/logging.h b/src/logging.h index c4fa0b435..be6bc0cb7 100644 --- a/src/logging.h +++ b/src/logging.h @@ -1,161 +1,170 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017-2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LOGGING_H #define BITCOIN_LOGGING_H #include #include #include #include #include #include #include static const bool DEFAULT_LOGTIMEMICROS = false; static const bool DEFAULT_LOGIPS = false; static const bool DEFAULT_LOGTIMESTAMPS = true; extern bool fLogIPs; extern const char *const DEFAULT_DEBUGLOGFILE; +struct CLogCategoryActive { + std::string category; + bool active; +}; + namespace BCLog { enum LogFlags : uint32_t { NONE = 0, NET = (1 << 0), TOR = (1 << 1), MEMPOOL = (1 << 2), HTTP = (1 << 3), BENCH = (1 << 4), ZMQ = (1 << 5), DB = (1 << 6), RPC = (1 << 7), ESTIMATEFEE = (1 << 8), ADDRMAN = (1 << 9), SELECTCOINS = (1 << 10), REINDEX = (1 << 11), CMPCTBLOCK = (1 << 12), RAND = (1 << 13), PRUNE = (1 << 14), PROXY = (1 << 15), MEMPOOLREJ = (1 << 16), LIBEVENT = (1 << 17), COINDB = (1 << 18), QT = (1 << 19), LEVELDB = (1 << 20), ALL = ~uint32_t(0), }; class Logger { private: FILE *m_fileout = nullptr; std::mutex m_file_mutex; std::list m_msgs_before_open; /** * m_started_new_line is a state variable that will suppress printing of the * timestamp when multiple calls are made that don't end in a newline. */ std::atomic_bool m_started_new_line{true}; /** - * Log categories bitfield. Leveldb/libevent need special handling if their - * flags are changed at runtime. + * Log categories bitfield. */ std::atomic m_categories{0}; std::string LogTimestampStr(const std::string &str); public: bool m_print_to_console = false; bool m_print_to_file = true; bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS; bool m_log_time_micros = DEFAULT_LOGTIMEMICROS; std::atomic m_reopen_file{false}; ~Logger(); /** Send a string to the log output */ void LogPrintStr(const std::string &str); fs::path GetDebugLogPath(); bool OpenDebugLog(); void ShrinkDebugFile(); + uint32_t GetCategoryMask() const { return m_categories.load(); } + void EnableCategory(LogFlags category); bool EnableCategory(const std::string &str); void DisableCategory(LogFlags category); bool DisableCategory(const std::string &str); /** Return true if log accepts specified category */ bool WillLogCategory(LogFlags category) const; /** Default for whether ShrinkDebugFile should be run */ bool DefaultShrinkDebugFile() const; }; } // namespace BCLog BCLog::Logger &GetLogger(); /** Return true if log accepts specified category */ static inline bool LogAcceptCategory(BCLog::LogFlags category) { return GetLogger().WillLogCategory(category); } -/** Returns a string with the supported log categories */ +/** Returns a string with the log categories. */ std::string ListLogCategories(); +/** Returns a vector of the active log categories. */ +std::vector ListActiveLogCategories(); + /** Return true if str parses as a log category and set the flag */ bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str); // Be conservative when using LogPrintf/error or other things which // unconditionally log to debug.log! It should not be the case that an inbound // peer can fill up a user's disk with debug.log entries. static inline void MarkUsed() {} template static inline void MarkUsed(const T &t, const Args &... args) { (void)t; MarkUsed(args...); } #ifdef USE_COVERAGE #define LogPrintf(...) \ do { \ MarkUsed(__VA_ARGS__); \ } while (0) #define LogPrint(category, ...) \ do { \ MarkUsed(__VA_ARGS__); \ } while (0) #else #define LogPrint(category, ...) \ do { \ if (LogAcceptCategory((category))) { \ GetLogger().LogPrintStr(tfm::format(__VA_ARGS__)); \ } \ } while (0) #define LogPrintf(...) \ do { \ GetLogger().LogPrintStr(tfm::format(__VA_ARGS__)); \ } while (0) #endif /** * These are aliases used to explicitly state that the message should not end * with a newline character. It allows for detecting the missing newlines that * could make the logs hard to read. */ #define LogPrintfToBeContinued LogPrintf #define LogPrintToBeContinued LogPrint #endif // BITCOIN_LOGGING_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8f90b4a02..41091cb0d 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -1,231 +1,233 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 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 #include #include #include #include #include class CRPCConvertParam { public: std::string methodName; //!< method whose params want conversion int paramIdx; //!< 0-based idx of param to convert std::string paramName; //!< parameter name }; /** * Specify a (method, idx, name) here if the argument is a non-string RPC * argument and needs to be converted from JSON. * * @note Parameter indexes start from 0. */ static const CRPCConvertParam vRPCConvertParams[] = { {"setmocktime", 0, "timestamp"}, {"generate", 0, "nblocks"}, {"generate", 1, "maxtries"}, {"generatetoaddress", 0, "nblocks"}, {"generatetoaddress", 2, "maxtries"}, {"getnetworkhashps", 0, "nblocks"}, {"getnetworkhashps", 1, "height"}, {"sendtoaddress", 1, "amount"}, {"sendtoaddress", 4, "subtractfeefromamount"}, {"settxfee", 0, "amount"}, {"sethdseed", 0, "newkeypool"}, {"getreceivedbyaddress", 1, "minconf"}, {"getreceivedbyaccount", 1, "minconf"}, {"getreceivedbylabel", 1, "minconf"}, {"listreceivedbyaddress", 0, "minconf"}, {"listreceivedbyaddress", 1, "include_empty"}, {"listreceivedbyaddress", 2, "include_watchonly"}, {"listreceivedbyaddress", 3, "address_filter"}, {"listreceivedbyaccount", 0, "minconf"}, {"listreceivedbyaccount", 1, "include_empty"}, {"listreceivedbyaccount", 2, "include_watchonly"}, {"listreceivedbylabel", 0, "minconf"}, {"listreceivedbylabel", 1, "include_empty"}, {"listreceivedbylabel", 2, "include_watchonly"}, {"getbalance", 1, "minconf"}, {"getbalance", 2, "include_watchonly"}, {"getblockhash", 0, "height"}, {"waitforblockheight", 0, "height"}, {"waitforblockheight", 1, "timeout"}, {"waitforblock", 1, "timeout"}, {"waitfornewblock", 0, "timeout"}, {"move", 2, "amount"}, {"move", 3, "minconf"}, {"sendfrom", 2, "amount"}, {"sendfrom", 3, "minconf"}, {"listtransactions", 1, "count"}, {"listtransactions", 2, "skip"}, {"listtransactions", 3, "include_watchonly"}, {"listaccounts", 0, "minconf"}, {"listaccounts", 1, "include_watchonly"}, {"walletpassphrase", 1, "timeout"}, {"getblocktemplate", 0, "template_request"}, {"listsinceblock", 1, "target_confirmations"}, {"listsinceblock", 2, "include_watchonly"}, {"listsinceblock", 3, "include_removed"}, {"sendmany", 1, "amounts"}, {"sendmany", 2, "minconf"}, {"sendmany", 4, "subtractfeefrom"}, {"addmultisigaddress", 0, "nrequired"}, {"addmultisigaddress", 1, "keys"}, {"createmultisig", 0, "nrequired"}, {"createmultisig", 1, "keys"}, {"listunspent", 0, "minconf"}, {"listunspent", 1, "maxconf"}, {"listunspent", 2, "addresses"}, {"listunspent", 3, "include_unsafe"}, {"listunspent", 4, "query_options"}, {"getblock", 1, "verbosity"}, {"getblock", 1, "verbose"}, {"getblockheader", 1, "verbose"}, {"getchaintxstats", 0, "nblocks"}, {"gettransaction", 1, "include_watchonly"}, {"getrawtransaction", 1, "verbose"}, {"createrawtransaction", 0, "inputs"}, {"createrawtransaction", 1, "outputs"}, {"createrawtransaction", 2, "locktime"}, {"signrawtransactionwithkey", 1, "privkeys"}, {"signrawtransactionwithkey", 2, "prevtxs"}, {"signrawtransactionwithwallet", 1, "prevtxs"}, {"sendrawtransaction", 1, "allowhighfees"}, {"testmempoolaccept", 0, "rawtxs"}, {"testmempoolaccept", 1, "allowhighfees"}, {"combinerawtransaction", 0, "txs"}, {"fundrawtransaction", 1, "options"}, {"gettxout", 1, "n"}, {"gettxout", 2, "include_mempool"}, {"gettxoutproof", 0, "txids"}, {"lockunspent", 0, "unlock"}, {"lockunspent", 1, "transactions"}, {"importprivkey", 2, "rescan"}, {"importaddress", 2, "rescan"}, {"importaddress", 3, "p2sh"}, {"importpubkey", 2, "rescan"}, {"importmulti", 0, "requests"}, {"importmulti", 1, "options"}, {"verifychain", 0, "checklevel"}, {"verifychain", 1, "nblocks"}, {"getblockstats", 0, "hash_or_height"}, {"getblockstats", 1, "stats"}, {"pruneblockchain", 0, "height"}, {"keypoolrefill", 0, "newsize"}, {"getrawmempool", 0, "verbose"}, {"estimatefee", 0, "nblocks"}, {"prioritisetransaction", 1, "priority_delta"}, {"prioritisetransaction", 2, "fee_delta"}, {"setban", 2, "bantime"}, {"setban", 3, "absolute"}, {"setnetworkactive", 0, "state"}, {"getmempoolancestors", 1, "verbose"}, {"getmempooldescendants", 1, "verbose"}, {"disconnectnode", 1, "nodeid"}, + {"logging", 0, "include"}, + {"logging", 1, "exclude"}, // Echo with conversion (For testing only) {"echojson", 0, "arg0"}, {"echojson", 1, "arg1"}, {"echojson", 2, "arg2"}, {"echojson", 3, "arg3"}, {"echojson", 4, "arg4"}, {"echojson", 5, "arg5"}, {"echojson", 6, "arg6"}, {"echojson", 7, "arg7"}, {"echojson", 8, "arg8"}, {"echojson", 9, "arg9"}, {"rescanblockchain", 0, "start_height"}, {"rescanblockchain", 1, "stop_height"}, }; class CRPCConvertTable { private: std::set> members; std::set> membersByName; public: CRPCConvertTable(); bool convert(const std::string &method, int idx) { return (members.count(std::make_pair(method, idx)) > 0); } bool convert(const std::string &method, const std::string &name) { return (membersByName.count(std::make_pair(method, name)) > 0); } }; CRPCConvertTable::CRPCConvertTable() { const unsigned int n_elem = (sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0])); for (unsigned int i = 0; i < n_elem; i++) { members.insert(std::make_pair(vRPCConvertParams[i].methodName, vRPCConvertParams[i].paramIdx)); membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName, vRPCConvertParams[i].paramName)); } } static CRPCConvertTable rpcCvtTable; /** * Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, * false, null) as well as objects and arrays. */ UniValue ParseNonRFCJSONValue(const std::string &strVal) { UniValue jVal; if (!jVal.read(std::string("[") + strVal + std::string("]")) || !jVal.isArray() || jVal.size() != 1) throw std::runtime_error(std::string("Error parsing JSON:") + strVal); return jVal[0]; } UniValue RPCConvertValues(const std::string &strMethod, const std::vector &strParams) { UniValue params(UniValue::VARR); for (unsigned int idx = 0; idx < strParams.size(); idx++) { const std::string &strVal = strParams[idx]; if (!rpcCvtTable.convert(strMethod, idx)) { // insert string value directly params.push_back(strVal); } else { // parse string as JSON, insert bool/number/object/etc. value params.push_back(ParseNonRFCJSONValue(strVal)); } } return params; } UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector &strParams) { UniValue params(UniValue::VOBJ); for (const std::string &s : strParams) { size_t pos = s.find('='); if (pos == std::string::npos) { throw(std::runtime_error("No '=' in named argument '" + s + "', this needs to be present for every " "argument (even if it is empty)")); } std::string name = s.substr(0, pos); std::string value = s.substr(pos + 1); if (!rpcCvtTable.convert(strMethod, name)) { // insert string value directly params.pushKV(name, value); } else { // parse string as JSON, insert bool/number/object/etc. value params.pushKV(name, ParseNonRFCJSONValue(value)); } } return params; } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6bc39c88a..49b05cf47 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1,449 +1,539 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 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 #include #include #include +#include #include #include +#include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #include #endif #include #include #include #ifdef HAVE_MALLOC_INFO #include #endif static UniValue validateaddress(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "validateaddress \"address\"\n" "\nReturn information about the given bitcoin address.\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin " "address to validate\n" "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is " "valid or not. If not, this is the only property returned.\n" " \"address\" : \"address\", (string) The bitcoin address " "validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded " "scriptPubKey generated by the address\n" " \"isscript\" : true|false, (boolean) If the key is a " "script\n" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")); } CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); bool isValid = IsValidDestination(dest); UniValue ret(UniValue::VOBJ); ret.pushKV("isvalid", isValid); if (isValid) { if (ret["address"].isNull()) { std::string currentAddress = EncodeDestination(dest, config); ret.pushKV("address", currentAddress); CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); } } return ret; } // Needed even with !ENABLE_WALLET, to pass (ignored) pointers around class CWallet; static UniValue createmultisig(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) { std::string msg = "createmultisig nrequired [\"key\",...]\n" "\nCreates a multi-signature address with n signature of m keys " "required.\n" "It returns a json object with the address and redeemScript.\n" "\nArguments:\n" "1. nrequired (numeric, required) The number of required " "signatures out of the n keys.\n" "2. \"keys\" (string, required) A json array of hex-encoded " "public keys\n" " [\n" " \"key\" (string) The hex-encoded public key\n" " ,...\n" " ]\n" "\nResult:\n" "{\n" " \"address\":\"multisigaddress\", (string) The value of the new " "multisig address.\n" " \"redeemScript\":\"script\" (string) The string value of " "the hex-encoded redemption script.\n" "}\n" "\nExamples:\n" "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli("createmultisig", "2 " "\"[" "\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3" "42cf11ae157a7ace5fd\\\"," "\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1" "7e107ef3f6aa5a61626\\\"]\"") + "\nAs a json rpc call\n" + HelpExampleRpc("createmultisig", "2, " "\"[" "\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3" "42cf11ae157a7ace5fd\\\"," "\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1" "7e107ef3f6aa5a61626\\\"]\""); throw std::runtime_error(msg); } int required = request.params[0].get_int(); // Get the public keys const UniValue &keys = request.params[1].get_array(); std::vector pubkeys; for (size_t i = 0; i < keys.size(); ++i) { if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys[i].get_str())); } else { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n", keys[i].get_str())); } } // Construct using pay-to-script-hash: CScript inner = CreateMultisigRedeemscript(required, pubkeys); CScriptID innerID(inner); UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(innerID, config)); result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); return result; } static UniValue verifymessage(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 3) { throw std::runtime_error( "verifymessage \"address\" \"signature\" \"message\"\n" "\nVerify a signed message\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address to " "use for the signature.\n" "2. \"signature\" (string, required) The signature provided " "by the signer in base 64 encoding (see signmessage).\n" "3. \"message\" (string, required) The message that was " "signed.\n" "\nResult:\n" "true|false (boolean) If the signature is verified or not.\n" "\nExamples:\n" "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" + HelpExampleCli( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs json rpc\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\", \"signature\", \"my " "message\"")); } LOCK(cs_main); std::string strAddress = request.params[0].get_str(); std::string strSign = request.params[1].get_str(); std::string strMessage = request.params[2].get_str(); CTxDestination destination = DecodeDestination(strAddress, config.GetChainParams()); if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); } const CKeyID *keyID = boost::get(&destination); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } bool fInvalid = false; std::vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); if (fInvalid) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; CPubKey pubkey; if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) { return false; } return (pubkey.GetID() == *keyID); } static UniValue signmessagewithprivkey(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( "signmessagewithprivkey \"privkey\" \"message\"\n" "\nSign a message with the private key of an address\n" "\nArguments:\n" "1. \"privkey\" (string, required) The private key to sign " "the message with.\n" "2. \"message\" (string, required) The message to create a " "signature of.\n" "\nResult:\n" "\"signature\" (string) The signature of the message " "encoded in base 64\n" "\nExamples:\n" "\nCreate the signature\n" + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs json rpc\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")); } std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CKey key = DecodeSecret(strPrivkey); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; std::vector vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); } return EncodeBase64(vchSig.data(), vchSig.size()); } static UniValue setmocktime(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "setmocktime timestamp\n" "\nSet the local time to given timestamp (-regtest only)\n" "\nArguments:\n" "1. timestamp (integer, required) Unix seconds-since-epoch " "timestamp\n" " Pass 0 to go back to using the system time."); } if (!config.GetChainParams().MineBlocksOnDemand()) { throw std::runtime_error( "setmocktime for regression testing (-regtest mode) only"); } // For now, don't change mocktime if we're in the middle of validation, as // this could have an effect on mempool time-based eviction, as well as // IsInitialBlockDownload(). // TODO: figure out the right way to synchronize around mocktime, and // ensure all call sites of GetTime() are accessing this safely. LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VNUM}); SetMockTime(request.params[0].get_int64()); return NullUniValue; } static UniValue RPCLockedMemoryInfo() { LockedPool::Stats stats = LockedPoolManager::Instance().stats(); UniValue obj(UniValue::VOBJ); obj.pushKV("used", uint64_t(stats.used)); obj.pushKV("free", uint64_t(stats.free)); obj.pushKV("total", uint64_t(stats.total)); obj.pushKV("locked", uint64_t(stats.locked)); obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); return obj; } #ifdef HAVE_MALLOC_INFO static std::string RPCMallocInfo() { char *ptr = nullptr; size_t size = 0; FILE *f = open_memstream(&ptr, &size); if (f) { malloc_info(0, f); fclose(f); if (ptr) { std::string rv(ptr, size); free(ptr); return rv; } } return ""; } #endif static UniValue getmemoryinfo(const Config &config, const JSONRPCRequest &request) { /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" */ if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "getmemoryinfo (\"mode\")\n" "Returns an object containing information about memory usage.\n" "Arguments:\n" "1. \"mode\" determines what kind of information is returned. This " "argument is optional, the default mode is \"stats\".\n" " - \"stats\" returns general statistics about memory usage in " "the daemon.\n" " - \"mallocinfo\" returns an XML string describing low-level " "heap state (only available if compiled with glibc 2.10+).\n" "\nResult (mode \"stats\"):\n" "{\n" " \"locked\": { (json object) Information about " "locked memory manager\n" " \"used\": xxxxx, (numeric) Number of bytes used\n" " \"free\": xxxxx, (numeric) Number of bytes available " "in current arenas\n" " \"total\": xxxxxxx, (numeric) Total number of bytes " "managed\n" " \"locked\": xxxxxx, (numeric) Amount of bytes that " "succeeded locking. If this number is smaller than total, locking " "pages failed at some point and key data could be swapped to " "disk.\n" " \"chunks_used\": xxxxx, (numeric) Number allocated chunks\n" " \"chunks_free\": xxxxx, (numeric) Number unused chunks\n" " }\n" "}\n" "\nResult (mode \"mallocinfo\"):\n" "\"...\"\n" "\nExamples:\n" + HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "")); } std::string mode = (request.params.size() < 1 || request.params[0].isNull()) ? "stats" : request.params[0].get_str(); if (mode == "stats") { UniValue obj(UniValue::VOBJ); obj.pushKV("locked", RPCLockedMemoryInfo()); return obj; } else if (mode == "mallocinfo") { #ifdef HAVE_MALLOC_INFO return RPCMallocInfo(); #else throw JSONRPCError( RPC_INVALID_PARAMETER, "mallocinfo is only available when compiled with glibc 2.10+"); #endif } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); } } +static void EnableOrDisableLogCategories(UniValue cats, bool enable) { + cats = cats.get_array(); + for (size_t i = 0; i < cats.size(); ++i) { + std::string cat = cats[i].get_str(); + + bool success; + if (enable) { + success = GetLogger().EnableCategory(cat); + } else { + success = GetLogger().DisableCategory(cat); + } + + if (!success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "unknown logging category " + cat); + } + } +} + +static UniValue logging(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() > 2) { + throw std::runtime_error( + "logging [include,...] \n" + "Gets and sets the logging configuration.\n" + "When called without an argument, returns the list of categories " + "that are currently being debug logged.\n" + "When called with arguments, adds or removes categories from debug " + "logging.\n" + "The valid logging categories are: " + + ListLogCategories() + + "\n" + "libevent logging is configured on startup and cannot be modified " + "by this RPC during runtime.\n" + "Arguments:\n" + "1. \"include\" (array of strings) add debug logging for these " + "categories.\n" + "2. \"exclude\" (array of strings) remove debug logging for these " + "categories.\n" + "\nResult:\n" + " (string): a list of the logging categories that are " + "active.\n" + "\nExamples:\n" + + HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + + HelpExampleRpc("logging", "[\"all\"], \"[libevent]\"")); + } + + uint32_t original_log_categories = GetLogger().GetCategoryMask(); + if (request.params.size() > 0 && request.params[0].isArray()) { + EnableOrDisableLogCategories(request.params[0], true); + } + + if (request.params.size() > 1 && request.params[1].isArray()) { + EnableOrDisableLogCategories(request.params[1], false); + } + + uint32_t updated_log_categories = GetLogger().GetCategoryMask(); + uint32_t changed_log_categories = + original_log_categories ^ updated_log_categories; + + /** + * Update libevent logging if BCLog::LIBEVENT has changed. + * If the library version doesn't allow it, UpdateHTTPServerLogging() + * returns false, in which case we should clear the BCLog::LIBEVENT flag. + * Throw an error if the user has explicitly asked to change only the + * libevent flag and it failed. + */ + if (changed_log_categories & BCLog::LIBEVENT) { + if (!UpdateHTTPServerLogging( + GetLogger().WillLogCategory(BCLog::LIBEVENT))) { + GetLogger().DisableCategory(BCLog::LIBEVENT); + if (changed_log_categories == BCLog::LIBEVENT) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "libevent logging cannot be updated when " + "using libevent before v2.1.1."); + } + } + } + + UniValue result(UniValue::VOBJ); + std::vector vLogCatActive = ListActiveLogCategories(); + for (const auto &logCatActive : vLogCatActive) { + result.pushKV(logCatActive.category, logCatActive.active); + } + + return result; +} + static UniValue echo(const Config &config, const JSONRPCRequest &request) { if (request.fHelp) { throw std::runtime_error( "echo|echojson \"message\" ...\n" "\nSimply echo back the input arguments. This command is for " "testing.\n" "\nThe difference between echo and echojson is that echojson has " "argument conversion enabled in the client-side table in" "bitcoin-cli and the GUI. There is no server-side difference."); } return request.params; } static UniValue getinfo_deprecated(const Config &config, const JSONRPCRequest &request) { throw JSONRPCError(RPC_METHOD_NOT_FOUND, "getinfo\n" "\nThis call was removed in version 0.19.8. Use the " "appropriate fields from:\n" "- getblockchaininfo: blocks, difficulty, chain\n" "- getnetworkinfo: version, protocolversion, " "timeoffset, connections, proxy, relayfee, warnings\n" "- getwalletinfo: balance, keypoololdest, keypoolsize, " "paytxfee, unlocked_until, walletversion\n" "\nbitcoin-cli has the option -getinfo to collect and " "format these in the old format."); } // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "control", "getmemoryinfo", getmemoryinfo, {"mode"} }, { "util", "validateaddress", validateaddress, {"address"} }, /* uses wallet if enabled */ { "util", "createmultisig", createmultisig, {"nrequired","keys"} }, { "util", "verifymessage", verifymessage, {"address","signature","message"} }, { "util", "signmessagewithprivkey", signmessagewithprivkey, {"privkey","message"} }, /* Not shown in help */ { "hidden", "setmocktime", setmocktime, {"timestamp"}}, { "hidden", "echo", echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echojson", echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "getinfo", getinfo_deprecated, {}}, + { "hidden", "logging", logging, {"include", "exclude"}}, }; // clang-format on void RegisterMiscRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } }