diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1532,7 +1532,7 @@ } CBlockIndex *pblockindex = mapBlockIndex[hash]; - FinalizeBlock(config, state, pblockindex); + FinalizeBlockAndInvalidate(config, state, pblockindex); } if (state.IsValid()) { diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -66,8 +66,9 @@ static const Amount DEFAULT_TRANSACTION_MAXFEE(COIN / 10); //! Discourage users to set fees higher than this amount (in satoshis) per kB static const Amount HIGH_TX_FEE_PER_KB(COIN / 100); -/** -maxtxfee will warn if called with a higher fee than this amount (in - * satoshis */ +/** + * -maxtxfee will warn if called with a higher fee than this amount (in satoshis + */ static const Amount HIGH_MAX_TX_FEE(100 * HIGH_TX_FEE_PER_KB); /** Default for -limitancestorcount, max number of in-mempool ancestors */ static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25; @@ -93,7 +94,8 @@ static const int MAX_SCRIPTCHECK_THREADS = 16; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; -/** Number of blocks that can be requested at any given time from a single peer. +/** + * Number of blocks that can be requested at any given time from a single peer. */ static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; /** @@ -191,6 +193,8 @@ /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; +/** Default for -maxreorgdepth */ +static const int DEFAULT_MAX_REORG_DEPTH = 10; extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; @@ -629,8 +633,8 @@ * Mark a block as finalized. * A finalized block can not be reorged in any way. */ -bool FinalizeBlock(const Config &config, CValidationState &state, - CBlockIndex *pindex); +bool FinalizeBlockAndInvalidate(const Config &config, CValidationState &state, + CBlockIndex *pindex); /** Mark a block as invalid. */ bool InvalidateBlock(const Config &config, CValidationState &state, diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2289,6 +2289,31 @@ } }; +static bool FinalizeBlockInternal(const Config &config, CValidationState &state, + CBlockIndex *pindex) { + AssertLockHeld(cs_main); + if (pindex->nStatus.isInvalid()) { + // We try to finalize an invalid block. + return state.DoS(100, + error("%s: Trying to finalize invalid block %s", + __func__, pindex->GetBlockHash().ToString()), + REJECT_INVALID, "finalize-invalid-block"); + } + + // Check that the request is consistent with current finalization. + if (pindexFinalized && !AreOnTheSameFork(pindex, pindexFinalized)) { + return state.DoS( + 20, error("%s: Trying to finalize block %s which conflicts " + "with already finalized block", + __func__, pindex->GetBlockHash().ToString()), + REJECT_AGAINST_FINALIZED, "bad-fork-prior-finalized"); + } + + // Our candidate is valid, finalize it. + pindexFinalized = pindex; + return true; +} + /** * Connect a new block to chainActive. pblock is either nullptr or a pointer to * a CBlock corresponding to pindexNew, to bypass loading it again from disk. @@ -2338,6 +2363,20 @@ FormatStateMessage(state)); } + // Update the finalized block. + int32_t nHeightToFinalize = + pindexNew->nHeight - + gArgs.GetArg("-maxreorgdepth", DEFAULT_MAX_REORG_DEPTH); + CBlockIndex *pindexToFinalize = + pindexNew->GetAncestor(nHeightToFinalize); + if (pindexToFinalize && + !FinalizeBlockInternal(config, state, pindexToFinalize)) { + state.SetCorruptionPossible(); + return error("ConnectTip(): FinalizeBlock %s failed (%s)", + pindexNew->GetBlockHash().ToString(), + FormatStateMessage(state)); + } + nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs]\n", @@ -2852,28 +2891,15 @@ return true; } -bool FinalizeBlock(const Config &config, CValidationState &state, - CBlockIndex *pindex) { +bool FinalizeBlockAndInvalidate(const Config &config, CValidationState &state, + CBlockIndex *pindex) { AssertLockHeld(cs_main); - if (pindex->nStatus.isInvalid()) { - // We try to finalize an invalid block. - return state.DoS(100, - error("%s: Trying to finalize invalid block %s", - __func__, pindex->GetBlockHash().ToString()), - REJECT_INVALID, "finalize-invalid-block"); - } - - // Check that the request is consistent with current finalization. - if (pindexFinalized && !AreOnTheSameFork(pindex, pindexFinalized)) { - return state.DoS( - 20, error("%s: Trying to finalize block %s which conflicts " - "with already finalized block", - __func__, pindex->GetBlockHash().ToString()), - REJECT_AGAINST_FINALIZED, "bad-fork-prior-finalized"); + if (!FinalizeBlockInternal(config, state, pindex)) { + // state is set by FinalizeBlockInternal. + return false; } // We have a valid candidate, make sure it is not parked. - pindexFinalized = pindex; if (pindex->nStatus.isOnParkedChain()) { UnparkBlock(pindex); } @@ -4669,6 +4695,7 @@ LOCK(cs_main); setBlockIndexCandidates.clear(); chainActive.SetTip(nullptr); + pindexFinalized = nullptr; pindexBestInvalid = nullptr; pindexBestParked = nullptr; pindexBestHeader = nullptr; diff --git a/test/functional/abc-parkedchain.py b/test/functional/abc-parkedchain.py --- a/test/functional/abc-parkedchain.py +++ b/test/functional/abc-parkedchain.py @@ -12,7 +12,7 @@ class ParkedChainTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [["-noparkdeepreorg"], []] + self.extra_args = [["-noparkdeepreorg"], ["-maxreorgdepth=-1"]] # There should only be one chaintip, which is expected_tip def only_valid_tip(self, expected_tip, other_tip_status=None): diff --git a/test/functional/bip68-sequence.py b/test/functional/bip68-sequence.py --- a/test/functional/bip68-sequence.py +++ b/test/functional/bip68-sequence.py @@ -26,8 +26,8 @@ class BIP68Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [["-blockprioritypercentage=0", "-noparkdeepreorg"], - ["-blockprioritypercentage=0", "-acceptnonstdtxn=0"]] + self.extra_args = [["-blockprioritypercentage=0", "-noparkdeepreorg", "-maxreorgdepth=-1"], + ["-blockprioritypercentage=0", "-acceptnonstdtxn=0", "-maxreorgdepth=-1"]] def run_test(self): self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"] diff --git a/test/functional/p2p-fullblocktest.py b/test/functional/p2p-fullblocktest.py --- a/test/functional/p2p-fullblocktest.py +++ b/test/functional/p2p-fullblocktest.py @@ -61,7 +61,8 @@ # Change the "outcome" variable from each TestInstance object to only do the comparison. def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-whitelist=127.0.0.1', '-noparkdeepreorg']] + self.extra_args = [['-whitelist=127.0.0.1', + '-noparkdeepreorg', '-maxreorgdepth=-1']] self.setup_clean_chain = True self.block_heights = {} self.coinbase_key = CECKey() diff --git a/test/functional/pruning.py b/test/functional/pruning.py --- a/test/functional/pruning.py +++ b/test/functional/pruning.py @@ -38,20 +38,20 @@ # Create nodes 0 and 1 to mine. # Create node 2 to test pruning. self.full_node_default_args = ["-maxreceivebuffer=20000", "-blockmaxsize=999000", - "-checkblocks=5", "-noparkdeepreorg", + "-checkblocks=5", "-noparkdeepreorg", "-maxreorgdepth=-1", "-limitdescendantcount=100", "-limitdescendantsize=5000", "-limitancestorcount=100", "-limitancestorsize=5000"] # Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later) # Create nodes 5 to test wallet in prune mode, but do not connect self.extra_args = [self.full_node_default_args, self.full_node_default_args, - ["-maxreceivebuffer=20000", - "-prune=550", "-noparkdeepreorg"], - ["-maxreceivebuffer=20000", - "-blockmaxsize=999000", "-noparkdeepreorg"], - ["-maxreceivebuffer=20000", - "-blockmaxsize=999000", "-noparkdeepreorg"], - ["-prune=550", "-noparkdeepreorg"]] + ["-maxreceivebuffer=20000", "-prune=550", + "-noparkdeepreorg", "-maxreorgdepth=-1"], + ["-maxreceivebuffer=20000", "-blockmaxsize=999000", + "-noparkdeepreorg", "-maxreorgdepth=-1"], + ["-maxreceivebuffer=20000", "-blockmaxsize=999000", + "-noparkdeepreorg", "-maxreorgdepth=-1"], + ["-prune=550"]] def setup_network(self): self.setup_nodes() @@ -152,7 +152,7 @@ self.stop_node(1) self.start_node(1, extra_args=[ "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", - "-disablesafemode", "-noparkdeepreorg"]) + "-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) height = self.nodes[1].getblockcount() self.log.info("Current block height: %d" % height) @@ -179,7 +179,7 @@ self.stop_node(1) self.start_node(1, extra_args=[ "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", - "-disablesafemode", "-noparkdeepreorg"]) + "-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) self.log.info("Generating new longer chain of 300 more blocks") self.nodes[1].generate(300) @@ -371,7 +371,8 @@ # check that the pruning node's wallet is still in good shape self.log.info("Stop and start pruning node to trigger wallet rescan") self.stop_node(2) - self.start_node(2, extra_args=["-prune=550", "-noparkdeepreorg"]) + self.start_node( + 2, extra_args=["-prune=550", "-noparkdeepreorg", "-maxreorgdepth=-1"]) self.log.info("Success") # check that wallet loads loads successfully when restarting a pruned node after IBD. @@ -381,7 +382,8 @@ nds = [self.nodes[0], self.nodes[5]] sync_blocks(nds, wait=5, timeout=300) self.stop_node(5) # stop and start to trigger rescan - self.start_node(5, extra_args=["-prune=550", "-noparkdeepreorg"]) + self.start_node( + 5, extra_args=["-prune=550", "-noparkdeepreorg", "-maxreorgdepth=-1"]) self.log.info("Success") def run_test(self): diff --git a/test/functional/timing.json b/test/functional/timing.json --- a/test/functional/timing.json +++ b/test/functional/timing.json @@ -1,7 +1,7 @@ [ { "name": "abandonconflict.py", - "time": 16 + "time": 17 }, { "name": "abc-checkdatasig-activation.py", @@ -11,37 +11,41 @@ "name": "abc-cmdline.py", "time": 8 }, + { + "name": "abc-finalize-block.py", + "time": 4 + }, { "name": "abc-high_priority_transaction.py", - "time": 10 + "time": 37 }, { "name": "abc-magnetic-anomaly-activation.py", - "time": 6 + "time": 14 }, { "name": "abc-magnetic-anomaly-mining.py", - "time": 15 + "time": 13 }, { "name": "abc-mempool-accept-txn.py", - "time": 5 + "time": 3 }, { "name": "abc-p2p-compactblocks.py", - "time": 104 + "time": 273 }, { "name": "abc-p2p-fullblocktest.py", - "time": 81 + "time": 62 }, { "name": "abc-parkedchain.py", - "time": 4 + "time": 17 }, { "name": "abc-replay-protection.py", - "time": 5 + "time": 4 }, { "name": "abc-rpc.py", @@ -53,23 +57,23 @@ }, { "name": "assumevalid.py", - "time": 20 + "time": 17 }, { "name": "bip65-cltv-p2p.py", - "time": 5 + "time": 6 }, { "name": "bip68-112-113-p2p.py", - "time": 18 + "time": 15 }, { "name": "bip68-sequence.py", - "time": 18 + "time": 32 }, { "name": "bipdersig-p2p.py", - "time": 7 + "time": 6 }, { "name": "bitcoin_cli.py", @@ -77,15 +81,15 @@ }, { "name": "blockchain.py", - "time": 11 + "time": 19 }, { "name": "dbcrash.py", - "time": 756 + "time": 1292 }, { "name": "decodescript.py", - "time": 3 + "time": 2 }, { "name": "disablewallet.py", @@ -93,99 +97,99 @@ }, { "name": "disconnect_ban.py", - "time": 8 + "time": 9 }, { "name": "example_test.py", - "time": 4 + "time": 16 }, { "name": "fundrawtransaction.py", - "time": 42 + "time": 40 }, { "name": "getblocktemplate_longpoll.py", - "time": 68 + "time": 69 }, { "name": "getchaintips.py", - "time": 4 + "time": 5 }, { "name": "httpbasics.py", - "time": 3 + "time": 17 }, { "name": "import-rescan.py", - "time": 16 + "time": 18 }, { "name": "importmulti.py", - "time": 12 + "time": 11 }, { "name": "importprunedfunds.py", - "time": 4 + "time": 5 }, { "name": "invalidateblock.py", - "time": 9 + "time": 8 }, { "name": "invalidblockrequest.py", - "time": 4 + "time": 5 }, { "name": "invalidtxrequest.py", - "time": 4 + "time": 15 }, { "name": "keypool-topup.py", - "time": 19 + "time": 34 }, { "name": "keypool.py", - "time": 8 + "time": 9 }, { "name": "listsinceblock.py", - "time": 5 + "time": 24 }, { "name": "listtransactions.py", - "time": 11 + "time": 23 }, { "name": "maxuploadtarget.py", - "time": 37 + "time": 28 }, { "name": "mempool_limit.py", - "time": 5 + "time": 8 }, { "name": "mempool_packages.py", - "time": 28 + "time": 55 }, { "name": "mempool_persist.py", - "time": 16 + "time": 19 }, { "name": "mempool_reorg.py", - "time": 4 + "time": 6 }, { "name": "mempool_resurrect_test.py", - "time": 3 + "time": 18 }, { "name": "mempool_spendcoinbase.py", - "time": 3 + "time": 5 }, { "name": "merkle_blocks.py", - "time": 5 + "time": 32 }, { "name": "minchainwork.py", @@ -193,19 +197,19 @@ }, { "name": "mining.py", - "time": 3 + "time": 5 }, { "name": "multi_rpc.py", - "time": 4 + "time": 18 }, { "name": "multiwallet.py", - "time": 9 + "time": 20 }, { "name": "net.py", - "time": 4 + "time": 13 }, { "name": "notifications.py", @@ -213,27 +217,27 @@ }, { "name": "nulldummy.py", - "time": 4 + "time": 5 }, { "name": "p2p-acceptblock.py", - "time": 7 + "time": 14 }, { "name": "p2p-compactblocks.py", - "time": 23 + "time": 22 }, { "name": "p2p-feefilter.py", - "time": 63 + "time": 51 }, { "name": "p2p-fullblocktest.py", - "time": 184 + "time": 148 }, { "name": "p2p-leaktests.py", - "time": 7 + "time": 8 }, { "name": "p2p-mempool.py", @@ -245,43 +249,43 @@ }, { "name": "preciousblock.py", - "time": 4 + "time": 5 }, { "name": "prioritise_transaction.py", - "time": 6 + "time": 34 }, { "name": "proxy_test.py", - "time": 4 + "time": 5 }, { "name": "pruning.py", - "time": 1040 + "time": 1511 }, { "name": "rawtransactions.py", - "time": 17 + "time": 16 }, { "name": "receivedby.py", - "time": 6 + "time": 37 }, { "name": "reindex.py", - "time": 14 + "time": 18 }, { "name": "resendwallettransactions.py", - "time": 6 + "time": 18 }, { "name": "rest.py", - "time": 11 + "time": 20 }, { "name": "rpcbind_test.py", - "time": 29 + "time": 28 }, { "name": "rpcnamedargs.py", @@ -289,7 +293,7 @@ }, { "name": "sendheaders.py", - "time": 22 + "time": 16 }, { "name": "signmessages.py", @@ -301,7 +305,7 @@ }, { "name": "txn_clone.py", - "time": 5 + "time": 8 }, { "name": "txn_clone.py --mineblock", @@ -309,46 +313,46 @@ }, { "name": "txn_doublespend.py", - "time": 5 + "time": 7 }, { "name": "txn_doublespend.py --mineblock", - "time": 6 + "time": 7 }, { "name": "uptime.py", - "time": 2 + "time": 3 }, { "name": "wallet-accounts.py", - "time": 13 + "time": 26 }, { "name": "wallet-dump.py", - "time": 8 + "time": 35 }, { "name": "wallet-encryption.py", - "time": 9 + "time": 18 }, { "name": "wallet-hd.py", - "time": 111 + "time": 36 }, { "name": "wallet.py", - "time": 56 + "time": 46 }, { "name": "walletbackup.py", - "time": 137 + "time": 119 }, { "name": "zapwallettxes.py", - "time": 15 + "time": 14 }, { "name": "zmq_test.py", "time": 9 } -] +] \ No newline at end of file