diff --git a/src/blockstatus.h b/src/blockstatus.h --- a/src/blockstatus.h +++ b/src/blockstatus.h @@ -14,7 +14,7 @@ private: uint32_t status; - explicit BlockStatus(uint32_t nStatusIn) : status(nStatusIn) {} + explicit constexpr BlockStatus(uint32_t nStatusIn) : status(nStatusIn) {} static const uint32_t VALIDITY_MASK = 0x07; @@ -41,7 +41,7 @@ static const uint32_t PARKED_MASK = PARKED_FLAG | PARKED_PARENT_FLAG; public: - explicit BlockStatus() : status(0) {} + explicit constexpr BlockStatus() : status(0) {} BlockValidity getValidity() const { return BlockValidity(status & VALIDITY_MASK); @@ -109,16 +109,20 @@ return BlockStatus(status & ~PARKED_MASK); } - BlockStatus withReconsideredFlags() const { - return withClearedFailureFlags().withClearedParkedFlags(); - } - ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(VARINT(status)); } + + friend constexpr bool operator==(const BlockStatus a, const BlockStatus b) { + return a.status == b.status; + } + + friend constexpr bool operator!=(const BlockStatus a, const BlockStatus b) { + return !(a == b); + } }; #endif // BITCOIN_BLOCKSTATUS_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1524,6 +1524,44 @@ return NullUniValue; } +UniValue parkblock(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error("parkblock \"blockhash\"\n" + "\nMarks a block as parked.\n" + "\nArguments:\n" + "1. \"blockhash\" (string, required) the " + "hash of the block to park\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("parkblock", "\"blockhash\"") + + HelpExampleRpc("parkblock", "\"blockhash\"")); + } + + std::string strHash = request.params[0].get_str(); + uint256 hash(uint256S(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]; + ParkBlock(config, state, pblockindex); + } + + if (state.IsValid()) { + ActivateBestChain(config, state); + } + + if (!state.IsValid()) { + throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); + } + + return NullUniValue; +} + UniValue reconsiderblock(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( @@ -1563,6 +1601,45 @@ return NullUniValue; } +UniValue unparkblock(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "unparkblock \"blockhash\"\n" + "\nRemoves parked status of a block and its descendants, " + "reconsider them for activation.\n" + "This can be used to undo the effects of parkblock.\n" + "\nArguments:\n" + "1. \"blockhash\" (string, required) the hash of the block to " + "unpark\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("unparkblock", "\"blockhash\"") + + HelpExampleRpc("unparkblock", "\"blockhash\"")); + } + + std::string strHash = request.params[0].get_str(); + uint256 hash(uint256S(strHash)); + + { + LOCK(cs_main); + if (mapBlockIndex.count(hash) == 0) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + + CBlockIndex *pblockindex = mapBlockIndex[hash]; + UnparkBlock(pblockindex); + } + + CValidationState state; + ActivateBestChain(config, state); + + if (!state.IsValid()) { + throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); + } + + return NullUniValue; +} + UniValue getchaintxstats(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( @@ -1689,7 +1766,9 @@ /* Not shown in help */ { "hidden", "invalidateblock", invalidateblock, {"blockhash"} }, + { "hidden", "parkblock", parkblock, {"blockhash"} }, { "hidden", "reconsiderblock", reconsiderblock, {"blockhash"} }, + { "hidden", "unparkblock", unparkblock, {"blockhash"} }, { "hidden", "waitfornewblock", waitfornewblock, {"timeout"} }, { "hidden", "waitforblock", waitforblock, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", waitforblockheight, {"height","timeout"} }, diff --git a/src/test/blockstatus_tests.cpp b/src/test/blockstatus_tests.cpp --- a/src/test/blockstatus_tests.cpp +++ b/src/test/blockstatus_tests.cpp @@ -47,8 +47,6 @@ hasUndo, false, false, isParked, hasParkedParent); CheckBlockStatus(s.withClearedParkedFlags(), validity, hasData, hasUndo, hasFailed, hasFailedParent, false, false); - CheckBlockStatus(s.withReconsideredFlags(), validity, hasData, hasUndo, - false, false, false, false); // Also check all possible alterations. CheckBlockStatus(s.withData(true), validity, true, hasUndo, hasFailed, diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -629,9 +629,16 @@ bool InvalidateBlock(const Config &config, CValidationState &state, CBlockIndex *pindex); +/** Park a block. */ +bool ParkBlock(const Config &config, CValidationState &state, + CBlockIndex *pindex); + /** Remove invalidity status from a block and its descendants. */ bool ResetBlockFailureFlags(CBlockIndex *pindex); +/** Remove parked status from a block and its descendants. */ +bool UnparkBlock(CBlockIndex *pindex); + /** The currently-connected chain of blocks (protected by cs_main). */ extern CChain chainActive; diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -136,7 +136,7 @@ arith_uint256 nLastPreciousChainwork = 0; /** Dirty block index entries. */ -std::set setDirtyBlockIndex; +std::set setDirtyBlockIndex; /** Dirty block file entries. */ std::set setDirtyFileInfo; @@ -2459,8 +2459,7 @@ static void PruneBlockIndexCandidates() { // Note that we can't delete the current block itself, as we may need to // return to it later in case a reorganization to a better block fails. - std::set::iterator it = - setBlockIndexCandidates.begin(); + auto it = setBlockIndexCandidates.begin(); while (it != setBlockIndexCandidates.end() && setBlockIndexCandidates.value_comp()(*it, chainActive.Tip())) { setBlockIndexCandidates.erase(it++); @@ -2723,19 +2722,21 @@ return ActivateBestChain(config, state); } -bool InvalidateBlock(const Config &config, CValidationState &state, - CBlockIndex *pindex) { +static bool UnwindBlock(const Config &config, CValidationState &state, + CBlockIndex *pindex, bool invalid) { AssertLockHeld(cs_main); // Mark the block itself as invalid. - pindex->nStatus = pindex->nStatus.withFailed(); + pindex->nStatus = pindex->nStatus.withFailed(invalid).withParked(!invalid); setDirtyBlockIndex.insert(pindex); DisconnectedBlockTransactions disconnectpool; while (chainActive.Contains(pindex)) { CBlockIndex *pindexWalk = chainActive.Tip(); - pindexWalk->nStatus = pindexWalk->nStatus.withFailedParent(); + pindexWalk->nStatus = + pindexWalk->nStatus.withFailed(invalid).withParked(!invalid); setDirtyBlockIndex.insert(pindexWalk); + // ActivateBestChain considers blocks already in chainActive // unconditionally valid already, so force disconnect away from it. if (!DisconnectTip(config, state, &disconnectpool)) { @@ -2760,44 +2761,53 @@ } } - InvalidChainFound(pindex); + if (invalid) { + InvalidChainFound(pindex); + } uiInterface.NotifyBlockTip(IsInitialBlockDownload(), pindex->pprev); return true; } -bool ResetBlockFailureFlags(CBlockIndex *pindex) { +bool InvalidateBlock(const Config &config, CValidationState &state, + CBlockIndex *pindex) { + return UnwindBlock(config, state, pindex, true); +} + +bool ParkBlock(const Config &config, CValidationState &state, + CBlockIndex *pindex) { + return UnwindBlock(config, state, pindex, false); +} + +template bool UpdateFlags(CBlockIndex *pindex, F f) { AssertLockHeld(cs_main); int nHeight = pindex->nHeight; - // Remove the invalidity flag from this block and all its descendants. + // Update the flags from this block and all its descendants. BlockMap::iterator it = mapBlockIndex.begin(); while (it != mapBlockIndex.end()) { - if (!it->second->IsValid() && + BlockStatus newSatus = f(pindex->nStatus); + if (it->second->nStatus != newSatus && it->second->GetAncestor(nHeight) == pindex) { - it->second->nStatus = it->second->nStatus.withClearedFailureFlags(); + it->second->nStatus = newSatus; setDirtyBlockIndex.insert(it->second); + if (it->second->IsValid(BlockValidity::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 = nullptr; - } } it++; } - // Remove the invalidity flag from all ancestors too. + // Update the flags from all ancestors too. while (pindex != nullptr) { - if (pindex->nStatus.isInvalid()) { - pindex->nStatus = pindex->nStatus.withClearedFailureFlags(); + BlockStatus newSatus = f(pindex->nStatus); + if (pindex->nStatus != newSatus) { + pindex->nStatus = newSatus; setDirtyBlockIndex.insert(pindex); } pindex = pindex->pprev; @@ -2806,6 +2816,37 @@ return true; } +bool ResetBlockFailureFlags(CBlockIndex *pindex) { + AssertLockHeld(cs_main); + + if (pindexBestInvalid && + (pindexBestInvalid->GetAncestor(pindex->nHeight) == pindex || + pindex->GetAncestor(pindexBestInvalid->nHeight) == + pindexBestInvalid)) { + // Reset the invalid block marker if it is about to be cleared. + pindexBestInvalid = nullptr; + } + + return UpdateFlags(pindex, [](const BlockStatus status) { + return status.withClearedFailureFlags(); + }); +} + +bool UnparkBlock(CBlockIndex *pindex) { + AssertLockHeld(cs_main); + + if (pindexBestParked && + (pindexBestParked->GetAncestor(pindex->nHeight) == pindex || + pindex->GetAncestor(pindexBestParked->nHeight) == pindexBestParked)) { + // Reset the parked block marker if it is about to be cleared. + pindexBestParked = nullptr; + } + + return UpdateFlags(pindex, [](const BlockStatus status) { + return status.withClearedParkedFlags(); + }); +} + static CBlockIndex *AddToBlockIndex(const CBlockHeader &block) { // Check for duplicate uint256 hash = block.GetHash(); diff --git a/test/functional/abc-parkedchain.py b/test/functional/abc-parkedchain.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-parkedchain.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the parckblock and unparkblock RPC calls.""" +import os + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class ParckedChainTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + node = self.nodes[0] + + self.log.info("test chain parking") + node.generate(10) + tip = node.getbestblockhash() + node.generate(1) + block_to_park = node.getbestblockhash() + node.generate(10) + parked_tip = node.getbestblockhash() + + # Let's park the chain. + assert(parked_tip != tip) + node.parkblock(block_to_park) + assert_equal(node.getbestblockhash(), tip) + + # When the chain is unparked, the node reorg into its original chain. + node.unparkblock(parked_tip) + assert_equal(node.getbestblockhash(), parked_tip) + + +if __name__ == '__main__': + ParckedChainTest().main() diff --git a/test/functional/timing.json b/test/functional/timing.json --- a/test/functional/timing.json +++ b/test/functional/timing.json @@ -1,23 +1,23 @@ [ { "name": "abandonconflict.py", - "time": 25 + "time": 13 }, { "name": "abc-checkdatasig-activation.py", - "time": 5 + "time": 4 }, { "name": "abc-cmdline.py", - "time": 11 + "time": 9 }, { "name": "abc-high_priority_transaction.py", - "time": 10 + "time": 12 }, { "name": "abc-magnetic-anomaly-activation.py", - "time": 14 + "time": 7 }, { "name": "abc-mempool-accept-txn.py", @@ -25,11 +25,15 @@ }, { "name": "abc-p2p-compactblocks.py", - "time": 170 + "time": 167 }, { "name": "abc-p2p-fullblocktest.py", - "time": 65 + "time": 50 + }, + { + "name": "abc-parkedchain.py", + "time": 3 }, { "name": "abc-replay-protection.py", @@ -37,27 +41,27 @@ }, { "name": "abc-rpc.py", - "time": 2 + "time": 3 }, { "name": "abc-transaction-ordering.py", - "time": 13 + "time": 15 }, { "name": "assumevalid.py", - "time": 12 + "time": 13 }, { "name": "bip65-cltv-p2p.py", - "time": 6 + "time": 19 }, { "name": "bip68-112-113-p2p.py", - "time": 24 + "time": 34 }, { "name": "bip68-sequence.py", - "time": 28 + "time": 26 }, { "name": "bipdersig-p2p.py", @@ -65,23 +69,23 @@ }, { "name": "bitcoin_cli.py", - "time": 16 + "time": 13 }, { "name": "blockchain.py", - "time": 30 + "time": 10 }, { "name": "dbcrash.py", - "time": 1109 + "time": 1257 }, { "name": "decodescript.py", - "time": 3 + "time": 2 }, { "name": "deprecated_rpc.py", - "time": 3 + "time": 18 }, { "name": "disablewallet.py", @@ -89,43 +93,43 @@ }, { "name": "disconnect_ban.py", - "time": 10 + "time": 9 }, { "name": "example_test.py", - "time": 16 + "time": 5 }, { "name": "fundrawtransaction.py", - "time": 56 + "time": 50 }, { "name": "getblocktemplate_longpoll.py", - "time": 69 + "time": 68 }, { "name": "getchaintips.py", - "time": 19 + "time": 5 }, { "name": "httpbasics.py", - "time": 25 + "time": 4 }, { "name": "import-rescan.py", - "time": 43 + "time": 16 }, { "name": "importmulti.py", - "time": 23 + "time": 24 }, { "name": "importprunedfunds.py", - "time": 31 + "time": 8 }, { "name": "invalidateblock.py", - "time": 9 + "time": 10 }, { "name": "invalidblockrequest.py", @@ -133,51 +137,51 @@ }, { "name": "invalidtxrequest.py", - "time": 16 + "time": 5 }, { "name": "keypool-topup.py", - "time": 17 + "time": 45 }, { "name": "keypool.py", - "time": 10 + "time": 9 }, { "name": "listsinceblock.py", - "time": 5 + "time": 4 }, { "name": "listtransactions.py", - "time": 11 + "time": 10 }, { "name": "maxuploadtarget.py", - "time": 58 + "time": 49 }, { "name": "mempool_limit.py", - "time": 18 + "time": 7 }, { "name": "mempool_packages.py", - "time": 36 + "time": 34 }, { "name": "mempool_persist.py", - "time": 40 + "time": 21 }, { "name": "mempool_reorg.py", - "time": 17 + "time": 28 }, { "name": "mempool_resurrect_test.py", - "time": 3 + "time": 18 }, { "name": "mempool_spendcoinbase.py", - "time": 3 + "time": 17 }, { "name": "merkle_blocks.py", @@ -185,99 +189,99 @@ }, { "name": "minchainwork.py", - "time": 6 + "time": 16 }, { "name": "mining.py", - "time": 6 + "time": 18 }, { "name": "multi_rpc.py", - "time": 6 + "time": 5 }, { "name": "multiwallet.py", - "time": 20 + "time": 8 }, { "name": "net.py", - "time": 3 + "time": 17 }, { "name": "notifications.py", - "time": 12 + "time": 7 }, { "name": "nulldummy.py", - "time": 3 + "time": 17 }, { "name": "p2p-acceptblock.py", - "time": 6 + "time": 8 }, { "name": "p2p-compactblocks.py", - "time": 24 + "time": 42 }, { "name": "p2p-feefilter.py", - "time": 65 + "time": 25 }, { "name": "p2p-fullblocktest.py", - "time": 155 + "time": 154 }, { "name": "p2p-leaktests.py", - "time": 8 + "time": 24 }, { "name": "p2p-mempool.py", - "time": 3 + "time": 16 }, { "name": "p2p-timeouts.py", - "time": 64 + "time": 65 }, { "name": "preciousblock.py", - "time": 6 + "time": 5 }, { "name": "prioritise_transaction.py", - "time": 12 + "time": 10 }, { "name": "proxy_test.py", - "time": 6 + "time": 5 }, { "name": "pruning.py", - "time": 1248 + "time": 1491 }, { "name": "rawtransactions.py", - "time": 10 + "time": 37 }, { "name": "receivedby.py", - "time": 8 + "time": 13 }, { "name": "reindex.py", - "time": 15 + "time": 26 }, { "name": "resendwallettransactions.py", - "time": 7 + "time": 5 }, { "name": "rest.py", - "time": 20 + "time": 9 }, { "name": "rpcbind_test.py", - "time": 39 + "time": 29 }, { "name": "rpcnamedargs.py", @@ -285,7 +289,7 @@ }, { "name": "sendheaders.py", - "time": 26 + "time": 30 }, { "name": "signmessages.py", @@ -297,27 +301,27 @@ }, { "name": "txn_clone.py", - "time": 6 + "time": 7 }, { "name": "txn_clone.py --mineblock", - "time": 22 + "time": 8 }, { "name": "txn_doublespend.py", - "time": 21 + "time": 6 }, { "name": "txn_doublespend.py --mineblock", - "time": 7 + "time": 5 }, { "name": "uptime.py", - "time": 2 + "time": 3 }, { "name": "wallet-accounts.py", - "time": 10 + "time": 40 }, { "name": "wallet-dump.py", @@ -325,26 +329,26 @@ }, { "name": "wallet-encryption.py", - "time": 18 + "time": 20 }, { "name": "wallet-hd.py", - "time": 66 + "time": 126 }, { "name": "wallet.py", - "time": 41 + "time": 44 }, { "name": "walletbackup.py", - "time": 84 + "time": 101 }, { "name": "zapwallettxes.py", - "time": 13 + "time": 15 }, { "name": "zmq_test.py", - "time": 6 + "time": 9 } ] \ No newline at end of file