#include #include #include #include #include #include #include #include #include #include #include /** Reply structure for request_done to fill in */ struct HTTPReply { int status{0}; int error{-1}; std::string body; }; const char *http_errorstring(int code) { switch(code) { #if LIBEVENT_VERSION_NUMBER >= 0x02010300 case EVREQ_HTTP_TIMEOUT: return "timeout reached"; case EVREQ_HTTP_EOF: return "EOF reached"; case EVREQ_HTTP_INVALID_HEADER: return "error while reading header, or invalid header"; case EVREQ_HTTP_BUFFER_ERROR: return "error encountered while reading or writing"; case EVREQ_HTTP_REQUEST_CANCEL: return "request was canceled"; case EVREQ_HTTP_DATA_TOO_LONG: return "response body is larger than allowed"; #endif default: return "unknown"; } } static void http_request_done(struct evhttp_request *req, void *ctx) { HTTPReply *reply = static_cast(ctx); if (req == nullptr) { /* If req is nullptr, it means an error occurred while connecting: the * error code will have been passed to http_error_cb. */ reply->status = 0; return; } reply->status = evhttp_request_get_response_code(req); struct evbuffer *buf = evhttp_request_get_input_buffer(req); if (buf) { size_t size = evbuffer_get_length(buf); const char *data = (const char*)evbuffer_pullup(buf, size); if (data) reply->body = std::string(data, size); evbuffer_drain(buf, size); } } #if LIBEVENT_VERSION_NUMBER >= 0x02010300 static void http_error_cb(enum evhttp_request_error err, void *ctx) { HTTPReply *reply = static_cast(ctx); reply->error = err; } #endif UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params) { std::string host = gArgs.GetArg("-mainchainrpchost", DEFAULT_RPCCONNECT); int port = gArgs.GetIntArg("-mainchainrpcport", BaseParams().MainchainRPCPort()); // Obtain event base raii_event_base base = obtain_event_base(); // Synchronously look up hostname raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); evhttp_connection_set_timeout(evcon.get(), gArgs.GetIntArg("-mainchainrpctimeout", DEFAULT_HTTP_CLIENT_TIMEOUT)); HTTPReply response; raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response); if (req == nullptr) throw std::runtime_error("create http request failed"); #if LIBEVENT_VERSION_NUMBER >= 0x02010300 evhttp_request_set_error_cb(req.get(), http_error_cb); #endif // Get credentials std::string strRPCUserColonPass; if (gArgs.GetArg("-mainchainrpcpassword", "") == "") { // Try fall back to cookie-based authentication if no password is provided if (!GetMainchainAuthCookie(&strRPCUserColonPass)) { throw std::runtime_error(strprintf( _("Could not locate mainchain RPC credentials. No authentication cookie could be found, and no mainchainrpcpassword is set in the configuration file (%s)"), gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME).c_str()).translated); } } else { strRPCUserColonPass = gArgs.GetArg("-mainchainrpcuser", "") + ":" + gArgs.GetArg("-mainchainrpcpassword", ""); } struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get()); assert(output_headers); evhttp_add_header(output_headers, "Host", host.c_str()); evhttp_add_header(output_headers, "Connection", "close"); evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); // Attach request data std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n"; struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get()); assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); req.release(); // ownership moved to evcon in above call if (r != 0) { throw CConnectionFailed("send http request failed"); } event_base_dispatch(base.get()); if (response.status == 0) throw CConnectionFailed(strprintf("couldn't connect to server: %s (code %d)\n(make sure server is running and you are connecting to the correct RPC port)", http_errorstring(response.error), response.error)); else if (response.status == HTTP_UNAUTHORIZED) throw std::runtime_error("incorrect mainchainrpcuser or mainchainrpcpassword (authorization failed)"); else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); else if (response.body.empty()) throw std::runtime_error("no response from server"); // Parse reply UniValue valReply(UniValue::VSTR); if (!valReply.read(response.body)) throw std::runtime_error("couldn't parse reply from server"); const UniValue& reply = valReply.get_obj(); if (reply.empty()) throw std::runtime_error("expected reply to have result, error and id properties"); return reply; } bool IsConfirmedBitcoinBlock(const uint256& hash, const int nMinConfirmationDepth, const int nbTxs) { LogPrintf("Checking for confirmed bitcoin block with hash %s, mindepth %d, nbtxs %d\n", hash.ToString().c_str(), nMinConfirmationDepth, nbTxs); try { UniValue params(UniValue::VARR); params.push_back(hash.GetHex()); UniValue reply = CallMainChainRPC("getblockheader", params); const UniValue& errval = reply.find_value("error"); if (!errval.isNull()) { LogPrintf("WARNING: Got error reply from bitcoind getblockheader: %s\n", errval.write()); return false; } const UniValue& result = reply.find_value("result"); if (!result.isObject()) { LogPrintf("ERROR: bitcoind getblockheader result was malformed (not object): %s\n", result.write()); return false; } UniValue confirmations = result.get_obj().find_value("confirmations"); if (!confirmations.isNum() || confirmations.getInt() < nMinConfirmationDepth) { LogPrintf("Insufficient confirmations (got %s, need at least %d).\n", confirmations.write(), nMinConfirmationDepth); return false; } // Only perform extra test if nbTxs has been provided (non-zero). if (nbTxs != 0) { UniValue nTx = result.get_obj().find_value("nTx"); if (!nTx.isNum() || nTx.getInt() != nbTxs) { LogPrintf("ERROR: Invalid number of transactions in merkle block for %s (got %s, need exactly %d)\n", hash.GetHex(), nTx.write(), nbTxs); return false; } } } catch (CConnectionFailed&) { LogPrintf("WARNING: Lost connection to mainchain daemon RPC; will retry.\n"); return false; } catch (...) { LogPrintf("WARNING: Failure connecting to mainchain daemon RPC; will retry.\n"); return false; } return true; }