Skip to content

Commit

Permalink
Auto-finalize block once they reached a certain depth (by default 10)
Browse files Browse the repository at this point in the history
Summary:
This ensures that deep reorg are not possible.

Can be controlled by the -maxreorgdepth parameter. Set to -1 to disable.

Depends on D2083

Test Plan: Added an integration test.

Reviewers: #bitcoin_abc, jasonbcox

Reviewed By: #bitcoin_abc, jasonbcox

Subscribers: jasonbcox, teamcity

Differential Revision: https://reviews.bitcoinabc.org/D2102
  • Loading branch information
deadalnix committed Nov 20, 2018
1 parent 59ee4de commit 917d657
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 114 deletions.
2 changes: 1 addition & 1 deletion src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ UniValue finalizeblock(const Config &config, const JSONRPCRequest &request) {
}

CBlockIndex *pblockindex = mapBlockIndex[hash];
FinalizeBlock(config, state, pblockindex);
FinalizeBlockAndInvalidate(config, state, pblockindex);
}

if (state.IsValid()) {
Expand Down
63 changes: 45 additions & 18 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2289,6 +2289,31 @@ class ConnectTrace {
}
};

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.
Expand Down Expand Up @@ -2338,6 +2363,20 @@ static bool ConnectTip(const Config &config, CValidationState &state,
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",
Expand Down Expand Up @@ -2852,28 +2891,15 @@ static bool UnwindBlock(const Config &config, CValidationState &state,
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);
}
Expand Down Expand Up @@ -4669,6 +4695,7 @@ void UnloadBlockIndex() {
LOCK(cs_main);
setBlockIndexCandidates.clear();
chainActive.SetTip(nullptr);
pindexFinalized = nullptr;
pindexBestInvalid = nullptr;
pindexBestParked = nullptr;
pindexBestHeader = nullptr;
Expand Down
14 changes: 9 additions & 5 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ static const Amount DEFAULT_UTXO_FEE = Amount::zero();
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;
Expand All @@ -93,7 +94,8 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
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;
/**
Expand Down Expand Up @@ -191,6 +193,8 @@ static const bool DEFAULT_PEERBLOOMFILTERS = true;

/** 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;
Expand Down Expand Up @@ -629,8 +633,8 @@ bool PreciousBlock(const Config &config, CValidationState &state,
* 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,
Expand Down
2 changes: 1 addition & 1 deletion test/functional/abc-parkedchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions test/functional/bip68-sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
3 changes: 2 additions & 1 deletion test/functional/p2p-fullblocktest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class FullBlockTest(ComparisonTestFramework):
# 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()
Expand Down
26 changes: 14 additions & 12 deletions test/functional/pruning.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ def set_test_params(self):
# 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()
Expand Down Expand Up @@ -152,7 +152,7 @@ def reorg_test(self):
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)
Expand All @@ -179,7 +179,7 @@ def reorg_test(self):
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)
Expand Down Expand Up @@ -371,7 +371,8 @@ def wallet_test(self):
# 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.
Expand All @@ -381,7 +382,8 @@ def wallet_test(self):
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):
Expand Down
Loading

0 comments on commit 917d657

Please sign in to comment.