From 9b0a8d3152b43b63c99878d0223a1681993ad608 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 19 Nov 2014 09:39:42 +0100 Subject: [PATCH 1/4] Add 'invalidateblock' and 'reconsiderblock' RPC commands. These can be used for testing reorganizations or for manual intervention in case of chain forks. --- src/main.cpp | 73 +++++++++++++++++++++++++++++++++++++++++ src/main.h | 6 ++++ src/rpcblockchain.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ src/rpcserver.cpp | 2 ++ src/rpcserver.h | 2 ++ 5 files changed, 159 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 0a81d0d7..eeef5135 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2135,6 +2135,79 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) { return true; } +bool InvalidateBlock(CValidationState& state, CBlockIndex *pindex) { + AssertLockHeld(cs_main); + + // Mark the block itself as invalid. + pindex->nStatus |= BLOCK_FAILED_VALID; + if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { + return state.Abort("Failed to update block index"); + } + setBlockIndexCandidates.erase(pindex); + + while (chainActive.Contains(pindex)) { + CBlockIndex *pindexWalk = chainActive.Tip(); + pindexWalk->nStatus |= BLOCK_FAILED_CHILD; + if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexWalk))) { + return state.Abort("Failed to update block index"); + } + setBlockIndexCandidates.erase(pindexWalk); + // ActivateBestChain considers blocks already in chainActive + // unconditionally valid already, so force disconnect away from it. + if (!DisconnectTip(state)) { + return false; + } + } + + // The resulting new best tip may not be in setBlockIndexCandidates anymore, so + // add them again. + BlockMap::iterator it = mapBlockIndex.begin(); + while (it != mapBlockIndex.end()) { + if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { + setBlockIndexCandidates.insert(pindex); + } + it++; + } + + InvalidChainFound(pindex); + return true; +} + +bool ReconsiderBlock(CValidationState& state, CBlockIndex *pindex) { + AssertLockHeld(cs_main); + + int nHeight = pindex->nHeight; + + // Remove the invalidity flag from this block and all its descendants. + BlockMap::iterator it = mapBlockIndex.begin(); + while (it != mapBlockIndex.end()) { + if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { + it->second->nStatus &= ~BLOCK_FAILED_MASK; + if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { + return state.Abort("Failed to update block index"); + } + if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { + setBlockIndexCandidates.insert(it->second); + } + if (it->second == pindexBestInvalid) { + // Reset invalid block marker if it was pointing to one of those. + pindexBestInvalid = NULL; + } + } + it++; + } + + // Remove the invalidity flag from all ancestors too. + while (pindex != NULL) { + pindex->nStatus &= ~BLOCK_FAILED_MASK; + if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { + return state.Abort("Failed to update block index"); + } + pindex = pindex->pprev; + } + return true; +} + CBlockIndex* AddToBlockIndex(const CBlockHeader& block) { // Check for duplicate diff --git a/src/main.h b/src/main.h index c0d64125..aee8d923 100644 --- a/src/main.h +++ b/src/main.h @@ -609,6 +609,12 @@ public: /** Find the last common block between the parameter chain and a locator. */ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator); +/** Mark a block as invalid. */ +bool InvalidateBlock(CValidationState& state, CBlockIndex *pindex); + +/** Remove invalidity status from a block and its descendants. */ +bool ReconsiderBlock(CValidationState& state, CBlockIndex *pindex); + /** The currently-connected chain of blocks. */ extern CChain chainActive; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index e8b0f62a..0ce18e41 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -561,3 +561,79 @@ Value getmempoolinfo(const Array& params, bool fHelp) return ret; } +Value invalidateblock(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "invalidateblock \"hash\"\n" + "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n" + "\nArguments:\n" + "1. hash (string, required) the hash of the block to mark as invalid\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("invalidateblock", "\"blockhash\"") + + HelpExampleRpc("invalidateblock", "\"blockhash\"") + ); + + std::string strHash = params[0].get_str(); + uint256 hash(strHash); + CValidationState state; + + { + LOCK(cs_main); + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlockIndex* pblockindex = mapBlockIndex[hash]; + InvalidateBlock(state, pblockindex); + } + + if (state.IsValid()) { + ActivateBestChain(state); + } + + if (!state.IsValid()) { + throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); + } + + return Value::null; +} + +Value reconsiderblock(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "reconsiderblock \"hash\"\n" + "\nRemoves invalidity status of a block and its descendants, reconsider them for activation.\n" + "This can be used to undo the effects of invalidateblock.\n" + "\nArguments:\n" + "1. hash (string, required) the hash of the block to reconsider\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("reconsiderblock", "\"blockhash\"") + + HelpExampleRpc("reconsiderblock", "\"blockhash\"") + ); + + std::string strHash = params[0].get_str(); + uint256 hash(strHash); + CValidationState state; + + { + LOCK(cs_main); + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlockIndex* pblockindex = mapBlockIndex[hash]; + ReconsiderBlock(state, pblockindex); + } + + if (state.IsValid()) { + ActivateBestChain(state); + } + + if (!state.IsValid()) { + throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); + } + + return Value::null; +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index b03016a5..c53dbe22 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -270,6 +270,8 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "gettxout", &gettxout, true, false, false }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, false, false }, { "blockchain", "verifychain", &verifychain, true, false, false }, + { "blockchain", "invalidateblock", &invalidateblock, true, true, false }, + { "blockchain", "reconsiderblock", &reconsiderblock, true, true, false }, /* Mining */ { "mining", "getblocktemplate", &getblocktemplate, true, false, false }, diff --git a/src/rpcserver.h b/src/rpcserver.h index b0e43705..2b242844 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -222,6 +222,8 @@ extern json_spirit::Value gettxoutsetinfo(const json_spirit::Array& params, bool extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp); // in rest.cpp extern bool HTTPReq_REST(AcceptedConnection *conn, From 0dd06b2515cd4d1f0be4dcf9060e34f3b9abde76 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 25 Nov 2014 12:33:43 +0100 Subject: [PATCH 2/4] Delay writing block indexes in invalidate/reconsider --- src/main.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index eeef5135..5695c8ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2140,17 +2140,13 @@ bool InvalidateBlock(CValidationState& state, CBlockIndex *pindex) { // Mark the block itself as invalid. pindex->nStatus |= BLOCK_FAILED_VALID; - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { - return state.Abort("Failed to update block index"); - } + setDirtyBlockIndex.insert(pindex); setBlockIndexCandidates.erase(pindex); while (chainActive.Contains(pindex)) { CBlockIndex *pindexWalk = chainActive.Tip(); pindexWalk->nStatus |= BLOCK_FAILED_CHILD; - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexWalk))) { - return state.Abort("Failed to update block index"); - } + setDirtyBlockIndex.insert(pindexWalk); setBlockIndexCandidates.erase(pindexWalk); // ActivateBestChain considers blocks already in chainActive // unconditionally valid already, so force disconnect away from it. @@ -2183,9 +2179,7 @@ bool ReconsiderBlock(CValidationState& state, CBlockIndex *pindex) { while (it != mapBlockIndex.end()) { if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { it->second->nStatus &= ~BLOCK_FAILED_MASK; - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { - return state.Abort("Failed to update block index"); - } + setDirtyBlockIndex.insert(it->second); if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { setBlockIndexCandidates.insert(it->second); } @@ -2199,9 +2193,9 @@ bool ReconsiderBlock(CValidationState& state, CBlockIndex *pindex) { // Remove the invalidity flag from all ancestors too. while (pindex != NULL) { - pindex->nStatus &= ~BLOCK_FAILED_MASK; - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { - return state.Abort("Failed to update block index"); + if (pindex->nStatus & BLOCK_FAILED_MASK) { + pindex->nStatus &= ~BLOCK_FAILED_MASK; + setDirtyBlockIndex.insert(pindex); } pindex = pindex->pprev; } From bd9aebf19d6714552fe9f23bce97159d90d63e5b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 26 Nov 2014 16:33:18 +0100 Subject: [PATCH 3/4] Introduce a hidden category --- src/rpcserver.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index c53dbe22..ac0df5fa 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -160,7 +160,7 @@ string CRPCTable::help(string strCommand) const // We already filter duplicates, but these deprecated screw up the sort order if (strMethod.find("label") != string::npos) continue; - if (strCommand != "" && strMethod != strCommand) + if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) continue; #ifdef ENABLE_WALLET if (pcmd->reqWallet && !pwalletMain) @@ -270,8 +270,6 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "gettxout", &gettxout, true, false, false }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, false, false }, { "blockchain", "verifychain", &verifychain, true, false, false }, - { "blockchain", "invalidateblock", &invalidateblock, true, true, false }, - { "blockchain", "reconsiderblock", &reconsiderblock, true, true, false }, /* Mining */ { "mining", "getblocktemplate", &getblocktemplate, true, false, false }, @@ -302,6 +300,10 @@ static const CRPCCommand vRPCCommands[] = { "util", "estimatefee", &estimatefee, true, true, false }, { "util", "estimatepriority", &estimatepriority, true, true, false }, + /* Not shown in help */ + { "hidden", "invalidateblock", &invalidateblock, true, true, false }, + { "hidden", "reconsiderblock", &reconsiderblock, true, true, false }, + #ifdef ENABLE_WALLET /* Wallet */ { "wallet", "addmultisigaddress", &addmultisigaddress, true, false, true }, From f86a24b368cf4e133add769966c7652ec3b63a75 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 28 Nov 2014 10:56:11 +0100 Subject: [PATCH 4/4] Move `setmocktime` to hidden category Another testing-only potential footgun command. --- src/rpcserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index ac0df5fa..85122121 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -246,7 +246,6 @@ static const CRPCCommand vRPCCommands[] = { "control", "getinfo", &getinfo, true, false, false }, /* uses wallet if enabled */ { "control", "help", &help, true, true, false }, { "control", "stop", &stop, true, true, false }, - { "control", "setmocktime", &setmocktime, true, false, false }, /* P2P networking */ { "network", "getnetworkinfo", &getnetworkinfo, true, false, false }, @@ -303,6 +302,7 @@ static const CRPCCommand vRPCCommands[] = /* Not shown in help */ { "hidden", "invalidateblock", &invalidateblock, true, true, false }, { "hidden", "reconsiderblock", &reconsiderblock, true, true, false }, + { "hidden", "setmocktime", &setmocktime, true, false, false }, #ifdef ENABLE_WALLET /* Wallet */