From 2b8b8471969ae69ad26d03025c967a174f81626a Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 10 Jan 2021 02:17:27 +0300 Subject: [PATCH 1/3] Implement auto-recovery from hardforks This should help users who fail to update their nodes/wallets in time when there is a hardfork. --- src/init.cpp | 2 ++ src/validation.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/init.cpp b/src/init.cpp index 3ec84089ab01..a9161183c8ed 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2106,6 +2106,8 @@ bool AppInitMain() strLoadError = _("Corrupted block database detected"); break; } + + ResetBlockFailureFlags(nullptr); } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); diff --git a/src/validation.cpp b/src/validation.cpp index 7485d73f9a76..65eaa5c96d50 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3377,6 +3377,16 @@ bool MarkConflictingBlock(CValidationState& state, const CChainParams& chainpara bool CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { AssertLockHeld(cs_main); + if (!pindex) { + if (pindexBestInvalid && pindexBestInvalid->GetAncestor(chainActive.Height()) == chainActive.Tip()) { + LogPrintf("%s: the best known invalid block (%s) is ahead of our tip, reconsidering\n", + __func__, pindexBestInvalid->GetBlockHash().ToString()); + pindex = pindexBestInvalid; + } else { + return true; + } + } + int nHeight = pindex->nHeight; // Remove the invalidity flag from this block and all its descendants. From b8693f7763042082de550907d306aa22fe54d085 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 17 Jan 2021 00:22:45 +0300 Subject: [PATCH 2/3] tests: tweak feature_llmq_chainlocks.py to check new behaviour --- test/functional/feature_llmq_chainlocks.py | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/functional/feature_llmq_chainlocks.py b/test/functional/feature_llmq_chainlocks.py index d1b197720046..41dad219fae5 100755 --- a/test/functional/feature_llmq_chainlocks.py +++ b/test/functional/feature_llmq_chainlocks.py @@ -96,16 +96,34 @@ def run_test(self): self.nodes[0].generate(2) time.sleep(6) assert(self.nodes[1].getbestblockhash() == good_tip) - self.nodes[0].generate(2) + bad_tip = self.nodes[0].generate(2)[-1] time.sleep(6) assert(self.nodes[1].getbestblockhash() == good_tip) self.log.info("Now let the node which is on the wrong chain reorg back to the locked chain") self.nodes[0].reconsiderblock(good_tip) assert(self.nodes[0].getbestblockhash() != good_tip) - self.nodes[1].generatetoaddress(1, node0_mining_addr) - self.wait_for_chainlocked_block(self.nodes[0], self.nodes[1].getbestblockhash()) - assert(self.nodes[0].getbestblockhash() == self.nodes[1].getbestblockhash()) + good_fork = good_tip + good_tip = self.nodes[1].generatetoaddress(1, node0_mining_addr)[-1] # this should mark bad_tip as conflicting + self.wait_for_chainlocked_block(self.nodes[0], good_tip) + assert(self.nodes[0].getbestblockhash() == good_tip) + found = False + for tip in self.nodes[0].getchaintips(2): + if tip["hash"] == bad_tip: + assert(tip["status"] == "conflicting") + found = True + break + assert(found) + + self.log.info("Should switch to the best non-conflicting tip (not to the most work chain) on restart") + assert(int(self.nodes[0].getblock(bad_tip)["chainwork"], 16) > int(self.nodes[1].getblock(good_tip)["chainwork"], 16)) + self.stop_node(0) + self.start_node(0) + self.nodes[0].invalidateblock(good_fork) + self.stop_node(0) + self.start_node(0) + time.sleep(1) + assert(self.nodes[0].getbestblockhash() == good_tip) self.log.info("Isolate a node and let it create some transactions which won't get IS locked") isolate_node(self.nodes[0]) From 71ec709f0635a341fc788bfb271ac228aa302142 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 17 Jan 2021 00:26:04 +0300 Subject: [PATCH 3/3] tests: tidy up feature_llmq_chainlocks.py a bit --- test/functional/feature_llmq_chainlocks.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/functional/feature_llmq_chainlocks.py b/test/functional/feature_llmq_chainlocks.py index 41dad219fae5..363352ee6685 100755 --- a/test/functional/feature_llmq_chainlocks.py +++ b/test/functional/feature_llmq_chainlocks.py @@ -91,13 +91,15 @@ def run_test(self): self.stop_node(0) self.start_node(0) connect_nodes(self.nodes[0], 1) - self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) + assert(self.nodes[0].getbestblockhash() == good_tip) + self.nodes[0].invalidateblock(good_tip) self.log.info("Now try to reorg the chain") self.nodes[0].generate(2) time.sleep(6) assert(self.nodes[1].getbestblockhash() == good_tip) bad_tip = self.nodes[0].generate(2)[-1] time.sleep(6) + assert(self.nodes[0].getbestblockhash() == bad_tip) assert(self.nodes[1].getbestblockhash() == good_tip) self.log.info("Now let the node which is on the wrong chain reorg back to the locked chain") @@ -132,12 +134,14 @@ def run_test(self): txs.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)) txs += self.create_chained_txs(self.nodes[0], 1) self.log.info("Assert that after block generation these TXs are NOT included (as they are \"unsafe\")") - self.nodes[0].generate(1) + node0_tip = self.nodes[0].generate(1)[-1] for txid in txs: tx = self.nodes[0].getrawtransaction(txid, 1) assert("confirmations" not in tx) time.sleep(1) - assert(not self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"]) + node0_tip_block = self.nodes[0].getblock(node0_tip) + assert(not node0_tip_block["chainlock"]) + assert(node0_tip_block["previousblockhash"] == good_tip) self.log.info("Disable LLMQ based InstantSend for a very short time (this never gets propagated to other nodes)") self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 4070908800) self.log.info("Now the TXs should be included")