Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13115689
D2004.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Subscribers
None
D2004.diff
View Options
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2397,12 +2397,58 @@
while (hasValidAncestor && pindexTest && pindexTest != pindexFork) {
assert(pindexTest->nChainTx || pindexTest->nHeight == 0);
+ // If this is a parked chain, but it has enough PoW, clear the park
+ // state.
+ bool fParkedChain = pindexTest->nStatus.isOnParkedChain();
+ if (fParkedChain && gArgs.GetBoolArg("-parkdeepreorg", true)) {
+ const CBlockIndex *pindexTip = chainActive.Tip();
+
+ // During initialization, pindexTip and/or pindexFork may be
+ // null. In this case, we just ignore the fact that the chain is
+ // parked.
+ if (!pindexTip || !pindexFork) {
+ UnparkBlock(pindexTest);
+ continue;
+ }
+
+ // A parked chain can be unparked if it has twice as much PoW
+ // accumulated as the main chain has since the fork block.
+ CBlockIndex const *pindexExtraPow = pindexTip;
+ arith_uint256 requiredWork = pindexTip->nChainWork;
+ switch (pindexTip->nHeight - pindexFork->nHeight) {
+ // Limit the penality for depth 1, 2 and 3 to half a block
+ // worth of work to ensure we don't fork accidentaly.
+ case 3:
+ case 2:
+ pindexExtraPow = pindexExtraPow->pprev;
+ // FALLTHROUGH
+ case 1: {
+ const arith_uint256 deltaWork =
+ pindexExtraPow->nChainWork - pindexFork->nChainWork;
+ requiredWork += (deltaWork >> 1);
+ break;
+ }
+ default:
+ requiredWork +=
+ pindexExtraPow->nChainWork - pindexFork->nChainWork;
+ break;
+ }
+
+ if (pindexNew->nChainWork > requiredWork) {
+ // We have enough, clear the parked state.
+ LogPrintf("Unpark block %s as its chain has accumulated "
+ "enough PoW.\n",
+ pindexTest->GetBlockHash().ToString());
+ fParkedChain = false;
+ UnparkBlock(pindexTest);
+ }
+ }
+
// Pruned nodes may have entries in setBlockIndexCandidates for
// which block files have been deleted. Remove those as candidates
// for the most work chain if we come across them; we can't switch
// to a chain unless we have all the non-active-chain parent blocks.
bool fInvalidChain = pindexTest->nStatus.isInvalid();
- bool fParkedChain = pindexTest->nStatus.isOnParkedChain();
bool fMissingData = !pindexTest->nStatus.hasData();
if (!(fInvalidChain || fParkedChain || fMissingData)) {
// The current block is acceptable, move to the parent, up to
@@ -2721,6 +2767,10 @@
nBlockReverseSequenceId--;
}
+ // In case this was parked, unpark it.
+ UnparkBlock(pindex);
+
+ // Make sure it is added to the candidate list if apropriate.
if (pindex->IsValid(BlockValidity::TRANSACTIONS) && pindex->nChainTx) {
setBlockIndexCandidates.insert(pindex);
PruneBlockIndexCandidates();
@@ -3621,6 +3671,20 @@
block.GetHash().ToString());
}
+ // If this is a deep reorg (a regorg of more than one block), preemptively
+ // mark the chain as parked. If it has enough work, it'll unpark
+ // automatically. We mark the block as parked at the very last minute so we
+ // can make sure everything is ready to be reorged if needed.
+ if (gArgs.GetBoolArg("-parkdeepreorg", true)) {
+ const CBlockIndex *pindexFork = chainActive.FindFork(pindex);
+ if (pindexFork && pindexFork->nHeight + 1 < pindex->nHeight) {
+ LogPrintf("Park block %s as it would cause a deep reorg.\n",
+ pindex->GetBlockHash().ToString());
+ pindex->nStatus = pindex->nStatus.withParked();
+ setDirtyBlockIndex.insert(pindex);
+ }
+ }
+
// Header is valid/has work and the merkle tree is good.
// Relay now, but if it does not build on our best tip, let the
// SendMessages loop relay it.
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
@@ -6,12 +6,13 @@
import os
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal
+from test_framework.util import assert_equal, connect_nodes_bi, sync_blocks, wait_until
class ParkedChainTest(BitcoinTestFramework):
def set_test_params(self):
- self.num_nodes = 1
+ self.num_nodes = 2
+ self.extra_args = [["-noparkdeepreorg"], []]
# There should only be one chaintip, which is expected_tip
def only_valid_tip(self, expected_tip, other_tip_status=None):
@@ -36,6 +37,8 @@
# Let's park the chain.
assert(parked_tip != tip)
+ assert(block_to_park != tip)
+ assert(block_to_park != parked_tip)
node.parkblock(block_to_park)
assert_equal(node.getbestblockhash(), tip)
@@ -122,6 +125,49 @@
node.reconsiderblock(bad_tip)
self.only_valid_tip(good_tip)
+ # First, make sure both nodes are in sync.
+ parking_node = self.nodes[1]
+ connect_nodes_bi(self.nodes, 0, 1)
+ sync_blocks(self.nodes[0:2])
+
+ assert_equal(node.getbestblockhash(), parking_node.getbestblockhash())
+
+ # Wait for node 1 to park the chain.
+ def wait_for_parked_block(block):
+ def check_block():
+ for tip in parking_node.getchaintips():
+ if tip["hash"] == block:
+ assert(tip["status"] != "active")
+ return tip["status"] == "parked"
+ return False
+ wait_until(check_block)
+
+ def check_reorg_protection(depth, extra_blocks):
+ self.log.info("Test deep reorg parking, %d block deep" % depth)
+
+ # Invalidate the tip on node 0, so it doesn't follow node 1.
+ node.invalidateblock(node.getbestblockhash())
+ # Mine block to create a fork of proper depth
+ parking_node.generate(depth - 1)
+ node.generate(depth)
+ # extra block should now find themselves parked
+ for i in range(extra_blocks):
+ node.generate(1)
+ wait_for_parked_block(node.getbestblockhash())
+
+ # If we mine one more block, the node reorgs.
+ node.generate(1)
+ wait_until(lambda: parking_node.getbestblockhash()
+ == node.getbestblockhash())
+
+ check_reorg_protection(1, 0)
+ check_reorg_protection(2, 0)
+ check_reorg_protection(3, 1)
+ check_reorg_protection(4, 4)
+ check_reorg_protection(5, 5)
+ check_reorg_protection(6, 6)
+ check_reorg_protection(100, 100)
+
if __name__ == '__main__':
ParkedChainTest().main()
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,7 +26,7 @@
class BIP68Test(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- self.extra_args = [["-blockprioritypercentage=0"],
+ self.extra_args = [["-blockprioritypercentage=0", "-noparkdeepreorg"],
["-blockprioritypercentage=0", "-acceptnonstdtxn=0"]]
def run_test(self):
diff --git a/test/functional/dbcrash.py b/test/functional/dbcrash.py
--- a/test/functional/dbcrash.py
+++ b/test/functional/dbcrash.py
@@ -53,7 +53,8 @@
# Set -rpcservertimeout=900 to reduce socket disconnects in this
# long-running test
self.base_args = ["-limitdescendantsize=0", "-maxmempool=0",
- "-rpcservertimeout=900", "-dbbatchsize=200000"]
+ "-rpcservertimeout=900", "-dbbatchsize=200000",
+ "-noparkdeepreorg"]
# Set different crash ratios and cache sizes. Note that not all of
# -dbcache goes to pcoinsTip.
diff --git a/test/functional/getchaintips.py b/test/functional/getchaintips.py
--- a/test/functional/getchaintips.py
+++ b/test/functional/getchaintips.py
@@ -14,6 +14,7 @@
class GetChainTipsTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
+ self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
def run_test(self):
tips = self.nodes[0].getchaintips()
diff --git a/test/functional/invalidateblock.py b/test/functional/invalidateblock.py
--- a/test/functional/invalidateblock.py
+++ b/test/functional/invalidateblock.py
@@ -15,6 +15,7 @@
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
+ self.extra_args = [["-noparkdeepreorg"], [], []]
def setup_network(self):
self.setup_nodes()
diff --git a/test/functional/listsinceblock.py b/test/functional/listsinceblock.py
--- a/test/functional/listsinceblock.py
+++ b/test/functional/listsinceblock.py
@@ -11,6 +11,7 @@
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
+ self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
def run_test(self):
'''
diff --git a/test/functional/p2p-acceptblock.py b/test/functional/p2p-acceptblock.py
--- a/test/functional/p2p-acceptblock.py
+++ b/test/functional/p2p-acceptblock.py
@@ -76,7 +76,8 @@
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
- self.extra_args = [[], ["-whitelist=127.0.0.1"],
+ self.extra_args = [["-noparkdeepreorg"],
+ ["-noparkdeepreorg", "-whitelist=127.0.0.1"],
["-minimumchainwork=0x10"]]
def setup_network(self):
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,7 @@
# 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']]
+ self.extra_args = [['-whitelist=127.0.0.1', '-noparkdeepreorg']]
self.setup_clean_chain = True
self.block_heights = {}
self.coinbase_key = CECKey()
diff --git a/test/functional/preciousblock.py b/test/functional/preciousblock.py
--- a/test/functional/preciousblock.py
+++ b/test/functional/preciousblock.py
@@ -45,6 +45,8 @@
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
+ self.extra_args = [["-noparkdeepreorg"],
+ ["-noparkdeepreorg"], ["-noparkdeepreorg"]]
def setup_network(self):
self.setup_nodes()
diff --git a/test/functional/pruning.py b/test/functional/pruning.py
--- a/test/functional/pruning.py
+++ b/test/functional/pruning.py
@@ -37,16 +37,21 @@
# Create nodes 0 and 1 to mine.
# Create node 2 to test pruning.
- self.full_node_default_args = ["-maxreceivebuffer=20000", "-blockmaxsize=999000", "-checkblocks=5",
- "-limitdescendantcount=100", "-limitdescendantsize=5000", "-limitancestorcount=100", "-limitancestorsize=5000"]
+ self.full_node_default_args = ["-maxreceivebuffer=20000", "-blockmaxsize=999000",
+ "-checkblocks=5", "-noparkdeepreorg",
+ "-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"],
- ["-maxreceivebuffer=20000", "-blockmaxsize=999000"],
- ["-maxreceivebuffer=20000", "-blockmaxsize=999000"],
- ["-prune=550"]]
+ ["-maxreceivebuffer=20000",
+ "-prune=550", "-noparkdeepreorg"],
+ ["-maxreceivebuffer=20000",
+ "-blockmaxsize=999000", "-noparkdeepreorg"],
+ ["-maxreceivebuffer=20000",
+ "-blockmaxsize=999000", "-noparkdeepreorg"],
+ ["-prune=550", "-noparkdeepreorg"]]
def setup_network(self):
self.setup_nodes()
@@ -146,7 +151,8 @@
# transactions (from disconnected blocks)
self.stop_node(1)
self.start_node(1, extra_args=[
- "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"])
+ "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5",
+ "-disablesafemode", "-noparkdeepreorg"])
height = self.nodes[1].getblockcount()
self.log.info("Current block height: %d" % height)
@@ -172,7 +178,8 @@
# Reboot node1 to clear those giant tx's from mempool
self.stop_node(1)
self.start_node(1, extra_args=[
- "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"])
+ "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5",
+ "-disablesafemode", "-noparkdeepreorg"])
self.log.info("Generating new longer chain of 300 more blocks")
self.nodes[1].generate(300)
@@ -364,7 +371,7 @@
# 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"])
+ self.start_node(2, extra_args=["-prune=550", "-noparkdeepreorg"])
self.log.info("Success")
# check that wallet loads loads successfully when restarting a pruned node after IBD.
@@ -374,7 +381,7 @@
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"])
+ self.start_node(5, extra_args=["-prune=550", "-noparkdeepreorg"])
self.log.info("Success")
def run_test(self):
diff --git a/test/functional/sendheaders.py b/test/functional/sendheaders.py
--- a/test/functional/sendheaders.py
+++ b/test/functional/sendheaders.py
@@ -182,6 +182,7 @@
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
+ self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"]]
# mine count blocks and return the new tip
def mine_blocks(self, count):
diff --git a/test/functional/txn_clone.py b/test/functional/txn_clone.py
--- a/test/functional/txn_clone.py
+++ b/test/functional/txn_clone.py
@@ -11,6 +11,7 @@
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
+ self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
def add_options(self, parser):
parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true",
diff --git a/test/functional/txn_doublespend.py b/test/functional/txn_doublespend.py
--- a/test/functional/txn_doublespend.py
+++ b/test/functional/txn_doublespend.py
@@ -11,6 +11,7 @@
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
+ self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
def add_options(self, parser):
parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true",
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 1, 11:44 (6 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5187684
Default Alt Text
D2004.diff (16 KB)
Attached To
D2004: Reconsider parked chains when enough works piles up
Event Timeline
Log In to Comment