diff --git a/contrib/devtools/check-doc.py b/contrib/devtools/check-doc.py index 7048661f38e..def08d98076 100755 --- a/contrib/devtools/check-doc.py +++ b/contrib/devtools/check-doc.py @@ -25,6 +25,8 @@ SET_DOC_OPTIONAL.update(['-con_fpowallowmindifficultyblocks', '-con_fpownoretargeting', '-con_nsubsidyhalvinginterval', '-con_bip34height', '-con_bip65height', '-con_bip66height', '-con_npowtargettimespan', '-con_npowtargetspacing', '-con_nrulechangeactivationthreshold', '-con_nminerconfirmationwindow', '-con_powlimit', '-con_parentpowlimit', '-con_bip34hash', '-con_nminimumchainwork', '-con_defaultassumevalid', '-parentgenesisblockhash', '-ndefaultport', '-npruneafterheight', '-fdefaultconsistencychecks', '-frequirestandard', '-fmineblocksondemand', '-mainchainrpccookiefile', '-testnet', '-ct_bits', '-ct_exponent', '-anyonecanspendaremine', '-fminingrequirespeers', '-fmineblocksondemand', '-con_mandatorycoinbase', '-con_has_parent_chain']) +SET_DOC_OPTIONAL.update(['-testemergencymode', '-ignoreemergencymode']) + def main(): used = check_output(CMD_GREP_ARGS, shell=True) docd = check_output(CMD_GREP_DOCS, shell=True) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 1b2393933e7..b40b0e392ba 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -107,6 +107,7 @@ 'feature_fedpeg.py', 'default_asset_name.py', 'assetdir.py', + 'emergency.py', # Elements' specially adapted tests second 'blockchain.py', diff --git a/qa/rpc-tests/emergency.py b/qa/rpc-tests/emergency.py new file mode 100755 index 00000000000..9c2fdeec666 --- /dev/null +++ b/qa/rpc-tests/emergency.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018-2018 The Elements Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + bitcoind_processes, + start_nodes, + start_node, + stop_nodes, +) + +import os +import tempfile +import time + +class EmergencyModeTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + # Both -testemergencymode and -ignoreemergencymode shouldn't do anything + self.extra_args = [['-debug', '-testemergencymode', '-ignoreemergencymode']] + + def setup_network(self): + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + + def run_test(self): + + print("Ensure that node is not already in emergency mode.") + emergency_file_path = self.options.tmpdir + "/node0/elementsregtest/ERROR_elementsregtest_HAS_SUFFERED_A_CRITICAL_FAILURE_AND_MAY_BE_UNSAFE_CORRECT_ERROR_BEFORE_REMOVING_THIS_FILE" + assert(not os.path.isfile(emergency_file_path)) + + self.nodes[0].generate(1) # Just to double-check the node works + stop_nodes(self.nodes) + + self.extra_args = [['-debug', '-testemergencymode']] + log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) + try: + self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0], stderr=log_stderr) + raise Exception("Node shouldn't start correctly with -testemergencymode") + except Exception as e: + return_code = bitcoind_processes[0].wait() + assert(return_code == 1) + assert(str(e) == "bitcoind exited with status 1 during initialization") + assert(os.path.isfile(emergency_file_path)) + log_stderr.seek(0) + stderr_out = log_stderr.read().decode('utf-8') + assert(stderr_out == 'Error: Using -testemergencymode\n' + 'Error: Error: A fatal internal error occurred, see debug.log for details\n') + log_stderr.close() + + os.remove(emergency_file_path) + # Once the file is removed it starts normally again + self.extra_args = [['-debug']] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + + print("Success!") + +if __name__ == '__main__': + EmergencyModeTest().main() diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 1fb66666842..f80d56fb541 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -333,7 +333,7 @@ def _rpchost_to_args(rpchost): rv += ['-rpcport=' + rpcport] return rv -def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, chain='elementsregtest'): +def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, chain='elementsregtest', stderr=sys.stderr): """ Start a bitcoind and return RPC connection to it """ @@ -342,7 +342,7 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= binary = os.getenv("ELEMENTSD", "elementsd") args = [ binary, '-chain='+chain, "-datadir="+datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-mocktime="+str(get_mocktime()) ] if extra_args is not None: args.extend(extra_args) - bitcoind_processes[i] = subprocess.Popen(args) + bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr) if os.getenv("PYTHON_DEBUG", ""): print("start_node: bitcoind started, waiting for RPC to come up") url = rpc_url(i, rpchost) diff --git a/src/init.cpp b/src/init.cpp index aa44ada33f7..40ce4494889 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -904,6 +904,11 @@ bool AppInitParameterInteraction() { const CChainParams& chainparams = Params(); // ********************************************************* Step 2: parameter interactions + CValidationState state; + if (GetBoolArg("-testemergencymode", false)) { + SetEmergencyMode(state, chainparams, "Testing emergency mode, the node should abort.", "Using -testemergencymode"); + } + InitEmergencyMode(chainparams); // also see: InitParameterInteraction() diff --git a/src/validation.cpp b/src/validation.cpp index f368785d2c2..dd94f72ee5d 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2053,6 +2053,45 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std } // anon namespace +static const std::string EMERGENCY_MODE_FILE_NAME = "_HAS_SUFFERED_A_CRITICAL_FAILURE_AND_MAY_BE_UNSAFE_CORRECT_ERROR_BEFORE_REMOVING_THIS_FILE"; + +void SetEmergencyMode(CValidationState& state, const CChainParams& chainparams, const std::string& strMessage, const std::string& userMessage) +{ + if (GetBoolArg("-ignoreemergencymode", false)) { + return; + } + const std::string filename = "ERROR_" + chainparams.NetworkIDString() + EMERGENCY_MODE_FILE_NAME; + boost::filesystem::path path = GetDataDir() / filename; + std::ofstream out(path.string().c_str()); + if (out) { + AbortNode(state, strMessage, userMessage); + } else { + AbortNode(state, "WITHOUT WRITTING EMERGENCY FILE:" + strMessage, userMessage); + } +} + +void InitEmergencyMode(const CChainParams& chainparams) +{ + if (GetBoolArg("-ignoreemergencymode", false)) { + return; + } + const std::string filename = "ERROR_" + chainparams.NetworkIDString() + EMERGENCY_MODE_FILE_NAME; + CValidationState state; + const std::string& strMessage = strprintf("%s(): %s", __func__, filename); + if (boost::filesystem::exists(GetDataDir() / filename)) { + AbortNode(state, strMessage); + } +} + +static void CheckInvalidBlockSignatures(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams) +{ + const CScript defaultRegtestScript(CScript() << OP_TRUE); + if (defaultRegtestScript != block.proof.challenge && CheckProof(block, chainparams.GetConsensus())) { + SetEmergencyMode(state, chainparams, "Block signers are signing invalid blocks", + "Block signers are signing invalid blocks: " + state.GetRejectReason() + " " + state.GetDebugMessage()); + } +} + /** * Apply the undo operation of a CTxInUndo to the given chain state. * @param undo The undo object. @@ -3215,6 +3254,12 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, vinvalidBlocks.push_back(blockConnecting.GetHash()); pblocktree->WriteInvalidBlockQueue(vinvalidBlocks); } + } else { + // If validation fails due to REJECT_PEGIN, It could simply be the case that the parent daemon + // is not in sync and therefore claimpegin transactions can not be validated. + // This check can not occur later in the validation path (f.e. after ActivateBestChain) + // because the validation state is reset hereafter. + CheckInvalidBlockSignatures(blockConnecting, state, chainparams); } } @@ -3777,7 +3822,7 @@ bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigne bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW) { // Check proof of work matches claimed amount - if (fCheckPOW && !CheckProof(block, Params().GetConsensus())) + if (fCheckPOW && !CheckProof(block, consensusParams)) return state.DoS(50, error("CheckBlockHeader(): block proof invalid"), REJECT_INVALID, "block-proof-invalid", true); @@ -4211,10 +4256,13 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptrnHeight + 1; // NOTE: CheckBlockHeader is called by CheckBlock - if (!ContextualCheckBlockHeader(block, state, chainparams.GetConsensus(), pindexPrev, GetAdjustedTime())) + if (!ContextualCheckBlockHeader(block, state, chainparams.GetConsensus(), pindexPrev, GetAdjustedTime())) { + CheckInvalidBlockSignatures(block, state, chainparams); return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, FormatStateMessage(state)); - if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) + } + if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) { + CheckInvalidBlockSignatures(block, state, chainparams); return error("%s: Consensus::CheckBlock: %s", __func__, FormatStateMessage(state)); - if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) + } + if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) { + CheckInvalidBlockSignatures(block, state, chainparams); return error("%s: Consensus::ContextualCheckBlock: %s", __func__, FormatStateMessage(state)); - if (!ConnectBlock(block, state, &indexDummy, viewNew, chainparams, NULL, true)) - return false; + } + if (!ConnectBlock(block, state, &indexDummy, viewNew, chainparams, NULL, true)) { + CheckInvalidBlockSignatures(block, state, chainparams); + return error("%s: ConnectBlock: %s", __func__, FormatStateMessage(state)); + } assert(state.IsValid()); return true; @@ -4617,9 +4673,11 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); // check level 1: verify block validity - if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus())) + if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus())) { + CheckInvalidBlockSignatures(block, state, chainparams); return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__, pindex->nHeight, pindex->GetBlockHash().ToString(), FormatStateMessage(state)); + } // check level 2: verify undo validity if (nCheckLevel >= 2 && pindex) { CBlockUndo undo; @@ -4657,8 +4715,10 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); - if (!ConnectBlock(block, state, pindex, coins, chainparams)) + if (!ConnectBlock(block, state, pindex, coins, chainparams)) { + CheckInvalidBlockSignatures(block, state, chainparams); return error("VerifyDB(): *** found unconnectable block at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); + } } } diff --git a/src/validation.h b/src/validation.h index c3c4d58607e..646a402c2d4 100644 --- a/src/validation.h +++ b/src/validation.h @@ -213,6 +213,16 @@ static const unsigned int DEFAULT_CHECKLEVEL = 3; // Setting the target to > than 550MB will make it likely we can respect the target. static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; +/** + * Create the emergency file and aborts the node. + */ +void SetEmergencyMode(CValidationState& state, const CChainParams& chainparams, const std::string& strMessage, const std::string& userMessage=""); +/** + * Checks if the emergency file exists and if so aborts the node. This + * is intended to be used on initialization. + */ +void InitEmergencyMode(const CChainParams& chainparams); + /** * Process an incoming block. This only returns after the best known valid * block is made active. Note that it does not, however, guarantee that the