mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Currently, if -validatepegin is given, and block validation can't proceed because the parent chain is not synced, we mark the block invalid and put it in a queue to be "revalidated" later. Unfortunately, marking a block invalid has downstream consequences, in particular causing descendant blocks to be marked invalid, which are not currently fixed by the queue. Instead, we'll use a different strategy: if the mainchain daemon isn't sufficiently synced to validate a block, we will "stall" connecting that block to the chain, and have ActivateBestChain simply keep the tip at the previous block until we're ready. We can still download and validate (partly) blocks past this point while we're waiting. They will be connected once the parent chain daemon catches up.
195 lines
7.3 KiB
C++
195 lines
7.3 KiB
C++
#include <mainchainrpc.h>
|
|
|
|
#include <chainparamsbase.h>
|
|
#include <util/system.h>
|
|
#include <util/strencodings.h>
|
|
#include <util/translation.h>
|
|
#include <rpc/request.h>
|
|
|
|
#include <support/events.h>
|
|
|
|
#include <rpc/client.h>
|
|
|
|
#include <event2/buffer.h>
|
|
#include <event2/keyvalq_struct.h>
|
|
|
|
/** Reply structure for request_done to fill in */
|
|
struct HTTPReply
|
|
{
|
|
HTTPReply(): status(0), error(-1) {}
|
|
|
|
int status;
|
|
int error;
|
|
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<HTTPReply*>(ctx);
|
|
|
|
if (req == NULL) {
|
|
/* If req is NULL, 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<HTTPReply*>(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.GetArg("-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.GetArg("-mainchainrpctimeout", DEFAULT_HTTP_CLIENT_TIMEOUT));
|
|
|
|
HTTPReply response;
|
|
raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
|
|
if (req == NULL)
|
|
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)").translated,
|
|
GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string().c_str()));
|
|
}
|
|
} 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);
|
|
if (!find_value(reply, "error").isNull()) {
|
|
LogPrintf("ERROR: Got error reply from bitcoind getblockheader.\n");
|
|
return false;
|
|
}
|
|
UniValue result = find_value(reply, "result");
|
|
if (!result.isObject()) {
|
|
LogPrintf("ERROR: bitcoind getblockheader result was malformed (not object).\n");
|
|
return false;
|
|
}
|
|
|
|
UniValue confirmations = find_value(result.get_obj(), "confirmations");
|
|
if (!confirmations.isNum() || confirmations.get_int64() < nMinConfirmationDepth) {
|
|
LogPrintf("Insufficient confirmations (got %s).\n", confirmations.write());
|
|
return false;
|
|
}
|
|
|
|
// Only perform extra test if nbTxs has been provided (non-zero).
|
|
if (nbTxs != 0) {
|
|
UniValue nTx = find_value(result.get_obj(), "nTx");
|
|
if (!nTx.isNum() || nTx.get_int64() != nbTxs) {
|
|
LogPrintf("ERROR: Invalid number of transactions in merkle block for %s\n",
|
|
hash.GetHex());
|
|
return false;
|
|
}
|
|
}
|
|
} catch (CConnectionFailed& e) {
|
|
LogPrintf("ERROR: Lost connection to mainchain daemon RPC, you will want to restart after fixing this!\n");
|
|
return false;
|
|
} catch (...) {
|
|
LogPrintf("ERROR: Failure connecting to mainchain daemon RPC, you will want to restart after fixing this!\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|