diff --git a/src/Makefile.am b/src/Makefile.am index 16d31b8efc..60a3d37f5e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -131,6 +131,7 @@ BITCOIN_CORE_H = \ dbwrapper.h \ limitedmap.h \ logging.h \ + mainchainrpc.h \ memusage.h \ merkleblock.h \ miner.h \ @@ -232,6 +233,7 @@ libbitcoin_server_a_SOURCES = \ index/txindex.cpp \ init.cpp \ dbwrapper.cpp \ + mainchainrpc.cpp \ merkleblock.cpp \ miner.cpp \ net.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index dbef7d5c51..e1855ddd11 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -547,6 +547,7 @@ class CCustomParams : public CRegTestParams { const bool parent_genesis_is_null = parentGenesisBlockHash == uint256(); assert(consensus.has_parent_chain != parent_genesis_is_null); consensus.parentChainPowLimit = uint256S(args.GetArg("-con_parentpowlimit", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + consensus.pegin_min_depth = args.GetArg("-peginconfirmationdepth", DEFAULT_PEGIN_CONFIRMATION_DEPTH); const CScript default_script(CScript() << OP_TRUE); consensus.fedpegScript = StrHexToScriptWithDefault(args.GetArg("-fedpegscript", ""), default_script); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index e06d02ec9c..d9338b3ff0 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -50,13 +50,14 @@ const CBaseChainParams& BaseParams() std::unique_ptr CreateBaseChainParams(const std::string& chain) { if (chain == CBaseChainParams::MAIN) - return MakeUnique("", 8332); + return MakeUnique("", 8332, 18332); else if (chain == CBaseChainParams::TESTNET) - return MakeUnique("testnet3", 18332); + return MakeUnique("testnet3", 18332, 8332); else if (chain == CBaseChainParams::REGTEST) - return MakeUnique("regtest", 18443); + return MakeUnique("regtest", 18443, 18332); - return MakeUnique(chain, 18553); + // ELEMENTS: + return MakeUnique(chain, 7041, 18332); } void SelectBaseParams(const std::string& chain) diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 355df043d3..4d3617eb6e 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -23,12 +23,14 @@ public: const std::string& DataDir() const { return strDataDir; } int RPCPort() const { return nRPCPort; } + int MainchainRPCPort() const { return nMainchainRPCPort; } CBaseChainParams() = delete; - CBaseChainParams(const std::string& data_dir, int rpc_port) : nRPCPort(rpc_port), strDataDir(data_dir) {} + CBaseChainParams(const std::string& data_dir, int rpc_port, int mainchain_rpc_port) : nRPCPort(rpc_port), nMainchainRPCPort(mainchain_rpc_port), strDataDir(data_dir) {} private: int nRPCPort; + int nMainchainRPCPort; std::string strDataDir; }; diff --git a/src/init.cpp b/src/init.cpp index f11545d9f6..d4d5821422 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -538,6 +539,13 @@ void SetupServerArgs() gArgs.AddArg("-initialfreecoins", strprintf("The amount of OP_TRUE coins created in the genesis block. Primarily for testing. (default: %d)", 0), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-validatepegin", strprintf("Validate peg-in claims. An RPC connection will be attempted to the trusted bitcoind using the `mainchain*` settings below. All functionaries must run this enabled. (default: %u)", DEFAULT_VALIDATE_PEGIN), false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpchost=", "The address which the daemon will try to connect to the trusted bitcoind to validate peg-ins, if enabled. (default: 127.0.0.1)", false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpcport=", strprintf("The port which the daemon will try to connect to the trusted bitcoind to validate peg-ins, if enabled. (default: %u)", defaultBaseParams->MainchainRPCPort()), false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpcuser=", "The rpc username that the daemon will use to connect to the trusted bitcoind to validate peg-ins, if enabled. (default: cookie auth)", false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpcpassword=", "The rpc password which the daemon will use to connect to the trusted bitcoind to validate peg-ins, if enabled. (default: cookie auth)", false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpccookiefile=", "The bitcoind cookie auth path which the daemon will use to connect to the trusted bitcoind to validate peg-ins. (default: `/regtest/.cookie`)", false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-mainchainrpctimeout=", strprintf("Timeout in seconds during mainchain RPC requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-peginconfirmationdepth=", strprintf("Pegin claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), false, OptionsCategory::ELEMENTS); gArgs.AddArg("-parentpubkeyprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)", 111), false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-parentscriptprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)", 196), false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-parent_bech32_hrp", strprintf("The human-readable part of the parent chain's bech32 encoding. (default: %s)", "bc"), false, OptionsCategory::CHAINPARAMS); diff --git a/src/mainchainrpc.cpp b/src/mainchainrpc.cpp new file mode 100644 index 0000000000..cf4685497b --- /dev/null +++ b/src/mainchainrpc.cpp @@ -0,0 +1,188 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +/** 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(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(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)"), + 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) +{ + try { + UniValue params(UniValue::VARR); + params.push_back(hash.GetHex()); + UniValue reply = CallMainChainRPC("getblockheader", params); + if (!find_value(reply, "error").isNull()) + return false; + UniValue result = find_value(reply, "result"); + if (!result.isObject()) + return false; + + UniValue confirmations = find_value(result.get_obj(), "confirmations"); + if (!confirmations.isNum() || confirmations.get_int64() < nMinConfirmationDepth) { + 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 bitcoind RPC, you will want to restart after fixing this!\n"); + return false; + } catch (...) { + LogPrintf("ERROR: Failure connecting to bitcoind RPC, you will want to restart after fixing this!\n"); + return false; + } + return true; +} + diff --git a/src/mainchainrpc.h b/src/mainchainrpc.h new file mode 100644 index 0000000000..5528d4985e --- /dev/null +++ b/src/mainchainrpc.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_MAINCHAINRPC_H +#define BITCOIN_MAINCHAINRPC_H + +#include +#include +#include + +#include +#include + +#include + +static const bool DEFAULT_NAMED=false; +static const char DEFAULT_RPCCONNECT[] = "127.0.0.1"; +static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; + +// +// Exception thrown on connection error. This error is used to determine +// when to wait if -rpcwait is given. +// +class CConnectionFailed : public std::runtime_error +{ +public: + + explicit inline CConnectionFailed(const std::string& msg) : + std::runtime_error(msg) + {} + +}; + +UniValue CallMainChainRPC(const std::string& strMethod, const UniValue& params); + +// Verify if the block with given hash has at least the specified minimum number +// of confirmations. +// For validating merkle blocks, you can provide the nbTxs parameter to verify if +// it equals the number of transactions in the block. +bool IsConfirmedBitcoinBlock(const uint256& hash, const int nMinConfirmationDepth, const int nbTxs); + +#endif // BITCOIN_MAINCHAINRPC_H + diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp index dab63ab263..9115938ff1 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/protocol.cpp @@ -123,6 +123,39 @@ bool GetAuthCookie(std::string *cookie_out) return true; } +// +// ELEMENTS: + +/** Default name for mainchain auth cookie file */ +static const std::string MAINCHAIN_COOKIEAUTH_FILE = "regtest/.cookie"; +/** Get name mainchain RPC authentication cookie file */ +static fs::path GetMainchainAuthCookieFile() +{ + boost::filesystem::path path(gArgs.GetArg("-mainchainrpccookiefile", MAINCHAIN_COOKIEAUTH_FILE)); + if (!path.is_complete()) path = GetDataDir(false) / path; + return path; +} + +bool GetMainchainAuthCookie(std::string *cookie_out) +{ + std::ifstream file; + std::string cookie; + + boost::filesystem::path filepath = GetMainchainAuthCookieFile(); + file.open(filepath.string().c_str()); + if (!file.is_open()) + return false; + std::getline(file, cookie); + file.close(); + + if (cookie_out) + *cookie_out = cookie; + return true; +} + +// END ELEMENTS +// + void DeleteAuthCookie() { try { diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 6bcbccbd4f..49caff561c 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -106,4 +106,8 @@ void DeleteAuthCookie(); /** Parse JSON-RPC batch reply into a vector */ std::vector JSONRPCProcessBatchReply(const UniValue &in, size_t num); +// ELEMENTS: +/** Needs to know cookiedir path info -cli doesn't require */ +bool GetMainchainAuthCookie(std::string *cookie_out); + #endif // BITCOIN_RPC_PROTOCOL_H diff --git a/src/script/script.h b/src/script/script.h index a116ee9954..9c88b7e498 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -41,6 +41,8 @@ static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 // ELEMENTS: // Validate pegin proof by checking Bitcoin transaction inclusion in mainchain. static const bool DEFAULT_VALIDATE_PEGIN = false; +// Number of confirms on parent chain required to confirm on sidechain. +static const unsigned int DEFAULT_PEGIN_CONFIRMATION_DEPTH = 8; template std::vector ToByteVector(const T& in)