Auto merge of #2463 - str4d:713-rollback-limit, r=str4d
Implement roll-back limit for reorganisation Part of #2905. Closes #713.
This commit is contained in:
@@ -46,6 +46,7 @@ testScripts=(
|
|||||||
'zcjoinsplit.py'
|
'zcjoinsplit.py'
|
||||||
'zcjoinsplitdoublespend.py'
|
'zcjoinsplitdoublespend.py'
|
||||||
'zkey_import_export.py'
|
'zkey_import_export.py'
|
||||||
|
'reorg_limit.py'
|
||||||
'getblocktemplate.py'
|
'getblocktemplate.py'
|
||||||
'bip65-cltv-p2p.py'
|
'bip65-cltv-p2p.py'
|
||||||
'bipdersig-p2p.py'
|
'bipdersig-p2p.py'
|
||||||
|
|||||||
86
qa/rpc-tests/reorg_limit.py
Executable file
86
qa/rpc-tests/reorg_limit.py
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# Copyright (c) 2017 The Zcash developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test reorg limit
|
||||||
|
#
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
check_node,
|
||||||
|
connect_nodes_bi,
|
||||||
|
initialize_chain_clean,
|
||||||
|
start_node,
|
||||||
|
sync_blocks,
|
||||||
|
)
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
def check_stopped(i, timeout=10):
|
||||||
|
stopped = False
|
||||||
|
for x in xrange(1, timeout):
|
||||||
|
ret = check_node(i)
|
||||||
|
if ret is None:
|
||||||
|
sleep(1)
|
||||||
|
else:
|
||||||
|
stopped = True
|
||||||
|
break
|
||||||
|
return stopped
|
||||||
|
|
||||||
|
class ReorgLimitTest(BitcoinTestFramework):
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
assert(self.nodes[0].getblockcount() == 200)
|
||||||
|
assert(self.nodes[2].getblockcount() == 200)
|
||||||
|
|
||||||
|
self.split_network()
|
||||||
|
|
||||||
|
print "Test the maximum-allowed reorg:"
|
||||||
|
print "Mine 99 blocks on Node 0"
|
||||||
|
self.nodes[0].generate(99)
|
||||||
|
assert(self.nodes[0].getblockcount() == 299)
|
||||||
|
assert(self.nodes[2].getblockcount() == 200)
|
||||||
|
|
||||||
|
print "Mine competing 100 blocks on Node 2"
|
||||||
|
self.nodes[2].generate(100)
|
||||||
|
assert(self.nodes[0].getblockcount() == 299)
|
||||||
|
assert(self.nodes[2].getblockcount() == 300)
|
||||||
|
|
||||||
|
print "Connect nodes to force a reorg"
|
||||||
|
connect_nodes_bi(self.nodes, 0, 2)
|
||||||
|
self.is_network_split = False
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
|
print "Check Node 0 is still running and on the correct chain"
|
||||||
|
assert(self.nodes[0].getblockcount() == 300)
|
||||||
|
|
||||||
|
self.split_network()
|
||||||
|
|
||||||
|
print "Test the minimum-rejected reorg:"
|
||||||
|
print "Mine 100 blocks on Node 0"
|
||||||
|
self.nodes[0].generate(100)
|
||||||
|
assert(self.nodes[0].getblockcount() == 400)
|
||||||
|
assert(self.nodes[2].getblockcount() == 300)
|
||||||
|
|
||||||
|
print "Mine competing 101 blocks on Node 2"
|
||||||
|
self.nodes[2].generate(101)
|
||||||
|
assert(self.nodes[0].getblockcount() == 400)
|
||||||
|
assert(self.nodes[2].getblockcount() == 401)
|
||||||
|
|
||||||
|
print "Sync nodes to force a reorg"
|
||||||
|
connect_nodes_bi(self.nodes, 0, 2)
|
||||||
|
self.is_network_split = False
|
||||||
|
# sync_blocks uses RPC calls to wait for nodes to be synced, so don't
|
||||||
|
# call it here, because it will have a non-specific connection error
|
||||||
|
# when Node 0 stops. Instead, we explicitly check for the process itself
|
||||||
|
# to stop.
|
||||||
|
|
||||||
|
print "Check Node 0 is no longer running"
|
||||||
|
assert(check_stopped(0))
|
||||||
|
|
||||||
|
# Dummy stop to enable the test to tear down
|
||||||
|
self.nodes[0].stop = lambda: True
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ReorgLimitTest().main()
|
||||||
@@ -213,6 +213,10 @@ def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
|
|||||||
def log_filename(dirname, n_node, logname):
|
def log_filename(dirname, n_node, logname):
|
||||||
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
||||||
|
|
||||||
|
def check_node(i):
|
||||||
|
bitcoind_processes[i].poll()
|
||||||
|
return bitcoind_processes[i].returncode
|
||||||
|
|
||||||
def stop_node(node, i):
|
def stop_node(node, i):
|
||||||
node.stop()
|
node.stop()
|
||||||
bitcoind_processes[i].wait()
|
bitcoind_processes[i].wait()
|
||||||
|
|||||||
45
src/main.cpp
45
src/main.cpp
@@ -2761,6 +2761,31 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo
|
|||||||
const CBlockIndex *pindexOldTip = chainActive.Tip();
|
const CBlockIndex *pindexOldTip = chainActive.Tip();
|
||||||
const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork);
|
const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork);
|
||||||
|
|
||||||
|
// - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
|
||||||
|
// - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
|
||||||
|
// then pindexFork will be null, and we would need to remove the entire chain including
|
||||||
|
// our genesis block. In practice this (probably) won't happen because of checks elsewhere.
|
||||||
|
auto reorgLength = pindexOldTip ? pindexOldTip->nHeight - (pindexFork ? pindexFork->nHeight : -1) : 0;
|
||||||
|
static_assert(MAX_REORG_LENGTH > 0, "We must be able to reorg some distance");
|
||||||
|
if (reorgLength > MAX_REORG_LENGTH) {
|
||||||
|
auto msg = strprintf(_(
|
||||||
|
"A block chain reorganization has been detected that would roll back %d blocks! "
|
||||||
|
"This is larger than the maximum of %d blocks, and so the node is shutting down for your safety."
|
||||||
|
), reorgLength, MAX_REORG_LENGTH) + "\n\n" +
|
||||||
|
_("Reorganization details") + ":\n" +
|
||||||
|
"- " + strprintf(_("Current tip: %s, height %d, work %s"),
|
||||||
|
pindexOldTip->phashBlock->GetHex(), pindexOldTip->nHeight, pindexOldTip->nChainWork.GetHex()) + "\n" +
|
||||||
|
"- " + strprintf(_("New tip: %s, height %d, work %s"),
|
||||||
|
pindexMostWork->phashBlock->GetHex(), pindexMostWork->nHeight, pindexMostWork->nChainWork.GetHex()) + "\n" +
|
||||||
|
"- " + strprintf(_("Fork point: %s, height %d"),
|
||||||
|
pindexFork->phashBlock->GetHex(), pindexFork->nHeight) + "\n\n" +
|
||||||
|
_("Please help, human!");
|
||||||
|
LogPrintf("*** %s\n", msg);
|
||||||
|
uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_ERROR);
|
||||||
|
StartShutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Disconnect active blocks which are no longer in the best chain.
|
// Disconnect active blocks which are no longer in the best chain.
|
||||||
bool fBlocksDisconnected = false;
|
bool fBlocksDisconnected = false;
|
||||||
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
|
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
|
||||||
@@ -3952,6 +3977,26 @@ bool RewindBlockIndex(const CChainParams& params)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
|
// nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
|
||||||
|
auto rewindLength = chainActive.Height() - nHeight;
|
||||||
|
if (rewindLength > 0 && rewindLength > MAX_REORG_LENGTH) {
|
||||||
|
auto pindexOldTip = chainActive.Tip();
|
||||||
|
auto pindexRewind = chainActive[nHeight - 1];
|
||||||
|
auto msg = strprintf(_(
|
||||||
|
"A block chain rewind has been detected that would roll back %d blocks! "
|
||||||
|
"This is larger than the maximum of %d blocks, and so the node is shutting down for your safety."
|
||||||
|
), rewindLength, MAX_REORG_LENGTH) + "\n\n" +
|
||||||
|
_("Rewind details") + ":\n" +
|
||||||
|
"- " + strprintf(_("Current tip: %s, height %d"),
|
||||||
|
pindexOldTip->phashBlock->GetHex(), pindexOldTip->nHeight) + "\n" +
|
||||||
|
"- " + strprintf(_("Rewinding to: %s, height %d"),
|
||||||
|
pindexRewind->phashBlock->GetHex(), pindexRewind->nHeight) + "\n\n" +
|
||||||
|
_("Please help, human!");
|
||||||
|
LogPrintf("*** %s\n", msg);
|
||||||
|
uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_ERROR);
|
||||||
|
StartShutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
CBlockIndex* pindex = chainActive.Tip();
|
CBlockIndex* pindex = chainActive.Tip();
|
||||||
while (chainActive.Height() >= nHeight) {
|
while (chainActive.Height() >= nHeight) {
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ static const unsigned int DEFAULT_BLOCK_PRIORITY_SIZE = DEFAULT_BLOCK_MAX_SIZE /
|
|||||||
static const bool DEFAULT_ALERTS = true;
|
static const bool DEFAULT_ALERTS = true;
|
||||||
/** Minimum alert priority for enabling safe mode. */
|
/** Minimum alert priority for enabling safe mode. */
|
||||||
static const int ALERT_PRIORITY_SAFE_MODE = 4000;
|
static const int ALERT_PRIORITY_SAFE_MODE = 4000;
|
||||||
|
/** Maximum reorg length we will accept before we shut down and alert the user. */
|
||||||
|
static const unsigned int MAX_REORG_LENGTH = COINBASE_MATURITY - 1;
|
||||||
/** Maximum number of signature check operations in an IsStandard() P2SH script */
|
/** Maximum number of signature check operations in an IsStandard() P2SH script */
|
||||||
static const unsigned int MAX_P2SH_SIGOPS = 15;
|
static const unsigned int MAX_P2SH_SIGOPS = 15;
|
||||||
/** The maximum number of sigops we're willing to relay/mine in a single tx */
|
/** The maximum number of sigops we're willing to relay/mine in a single tx */
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "coins.h"
|
#include "coins.h"
|
||||||
#include "consensus/consensus.h"
|
|
||||||
#include "key.h"
|
#include "key.h"
|
||||||
#include "keystore.h"
|
#include "keystore.h"
|
||||||
|
#include "main.h"
|
||||||
#include "primitives/block.h"
|
#include "primitives/block.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "tinyformat.h"
|
#include "tinyformat.h"
|
||||||
@@ -58,7 +58,7 @@ static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
|
|||||||
//! Size of witness cache
|
//! Size of witness cache
|
||||||
// Should be large enough that we can expect not to reorg beyond our cache
|
// Should be large enough that we can expect not to reorg beyond our cache
|
||||||
// unless there is some exceptional network disruption.
|
// unless there is some exceptional network disruption.
|
||||||
static const unsigned int WITNESS_CACHE_SIZE = COINBASE_MATURITY;
|
static const unsigned int WITNESS_CACHE_SIZE = MAX_REORG_LENGTH + 1;
|
||||||
|
|
||||||
class CBlockIndex;
|
class CBlockIndex;
|
||||||
class CCoinControl;
|
class CCoinControl;
|
||||||
|
|||||||
Reference in New Issue
Block a user