diff --git a/src/init.cpp b/src/init.cpp index d4d5821422..ac01d3af03 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -546,6 +546,7 @@ void SetupServerArgs() 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("-recheckpeginblockinterval=", strprintf("The interval in seconds at which a peg-in witness failing block is re-evaluated in case of intermittant peg-in witness failure. 0 means never. (default: %u)", 120), 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); @@ -1762,6 +1763,18 @@ bool AppInitMain() // ********************************************************* Step 13: finished SetRPCWarmupFinished(); + + // ELEMENTS: + CScheduler::Function f2 = boost::bind(&BitcoindRPCCheck, false); + unsigned int check_rpc_every = gArgs.GetArg("-recheckpeginblockinterval", 120); + if (check_rpc_every) { + scheduler.scheduleEvery(f2, check_rpc_every); + } + uiInterface.InitMessage(_("Awaiting bitcoind RPC warmup")); + if (!BitcoindRPCCheck(true)) { //Initial check, fail immediately + return InitError(_("ERROR: elementsd is set to verify pegins but cannot get valid response from bitcoind. Please check debug.log for more information.")); + } + uiInterface.InitMessage(_("Done loading")); g_wallet_init_interface.Start(scheduler); diff --git a/src/validation.cpp b/src/validation.cpp index e46f198d24..78e331d94c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -4979,3 +4980,127 @@ public: mapBlockIndex.clear(); } } instance_of_cmaincleanup; + +// ELEMENTS: +/* This function has two major purposes: + * 1) Checks that the RPC connection to the parent chain node + * can be attained, and is returning back reasonable answers. + * 2) Re-evaluates a list of blocks that have been deemed "bad" + * from the perspective of peg-in witness validation. Blocks are + * added to this queue in ConnectTip based on the error code returned. + */ +bool BitcoindRPCCheck(const bool init) +{ + // First, we can clear out any blocks thatsomehow are now deemed valid + // eg reconsiderblock rpc call manually + std::vector vblocksToReconsider; + pblocktree->ReadInvalidBlockQueue(vblocksToReconsider); + std::vector vblocksToReconsiderAgain; + for(uint256& blockhash : vblocksToReconsider) { + LOCK(cs_main); + if (mapBlockIndex.count(blockhash)) { + CBlockIndex* pblockindex = mapBlockIndex[blockhash]; + if ((pblockindex->nStatus & BLOCK_FAILED_MASK)) { + vblocksToReconsiderAgain.push_back(blockhash); + } + } + } + vblocksToReconsider = vblocksToReconsiderAgain; + vblocksToReconsiderAgain.clear(); + pblocktree->WriteInvalidBlockQueue(vblocksToReconsider); + + // Next, check for working and valid rpc + if (gArgs.GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) { + // During init try until a non-RPC_IN_WARMUP result + while (true) { + try { + // The first thing we have to check is the version of the node. + UniValue params(UniValue::VARR); + UniValue reply = CallMainChainRPC("getnetworkinfo", params); + UniValue error = reply["error"]; + if (!error.isNull()) { + // On the first call, it's possible to node is still in + // warmup; in that case, just wait and retry. + if (error["code"].get_int() == RPC_IN_WARMUP) { + MilliSleep(1000); + continue; + } + else { + LogPrintf("ERROR: Bitcoind RPC check returned 'error' response.\n"); + return false; + } + } + UniValue result = reply["result"]; + if (!result.isObject() || !result.get_obj()["version"].isNum() || + result.get_obj()["version"].get_int() < MIN_MAINCHAIN_NODE_VERSION) { + LogPrintf("ERROR: Parent chain daemon too old; need Bitcoin Core version 0.16.3 or newer.\n"); + return false; + } + + // Then check the genesis block to correspond to parent chain. + params.push_back(UniValue(0)); + reply = CallMainChainRPC("getblockhash", params); + error = reply["error"]; + if (!error.isNull()) { + LogPrintf("ERROR: Bitcoind RPC check returned 'error' response.\n"); + return false; + } + result = reply["result"]; + if (!result.isStr() || result.get_str() != Params().ParentGenesisBlockHash().GetHex()) { + LogPrintf("ERROR: Invalid parent genesis block hash response via RPC. Contacting wrong parent daemon?\n"); + return false; + } + } catch (const std::runtime_error& re) { + LogPrintf("ERROR: Failure connecting to bitcoind RPC: %s\n", std::string(re.what())); + return false; + } + // Success + break; + } + } + + //Sanity startup check won't reconsider queued blocks + if (init) { + return true; + } + + // Getting this far means we either aren't validating pegins(so let's make sure that's why + // it failed previously) or we successfully connected to bitcoind + // Time to reconsider blocks + if (vblocksToReconsider.size() > 0) { + CValidationState state; + for(const uint256& blockhash : vblocksToReconsider) { + { + LOCK(cs_main); + if (mapBlockIndex.count(blockhash) == 0) + continue; + CBlockIndex* pblockindex = mapBlockIndex[blockhash]; + ResetBlockFailureFlags(pblockindex); + } + } + + //All blocks are now being reconsidered + ActivateBestChain(state, Params()); + //This simply checks for DB errors + if (!state.IsValid()) { + //Something scary? + } + + //Now to clear out now-valid blocks + for(const uint256& blockhash : vblocksToReconsider) { + LOCK(cs_main); + if (mapBlockIndex.count(blockhash)) { + CBlockIndex* pblockindex = mapBlockIndex[blockhash]; + + //Marked as invalid still, put back into queue + if((pblockindex->nStatus & BLOCK_FAILED_MASK)) { + vblocksToReconsiderAgain.push_back(blockhash); + } + } + } + + //Write back remaining blocks + pblocktree->WriteInvalidBlockQueue(vblocksToReconsiderAgain); + } + return true; +} diff --git a/src/validation.h b/src/validation.h index 25f9692fa0..98c4f28a55 100644 --- a/src/validation.h +++ b/src/validation.h @@ -137,6 +137,13 @@ static const bool DEFAULT_PEERBLOOMFILTERS = true; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; +// ELEMENTS constants: +/** The minimum version for the mainchain node. + * We need v0.16.3 to get the nTx field in getblockheader and inflation fix. + * Note that Elements-based parent chains may not have fixes based on this + * version check! */ +static const int MIN_MAINCHAIN_NODE_VERSION = 160300; // 0.16.3 + struct BlockHasher { size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); } @@ -502,4 +509,8 @@ inline bool IsBlockPruned(const CBlockIndex* pblockindex) return (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0); } +// ELEMENTS: +/** Check if bitcoind connection via RPC is correctly working*/ +bool BitcoindRPCCheck(bool init); + #endif // BITCOIN_VALIDATION_H