diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 45f2c9498..35139a011 100755 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -139,6 +139,7 @@ public: pchMessageStart[3] = 0xf1; nDefaultPort = 7312; nPruneAfterHeight = 100000; + nNlrLimit = 100; // 100 * ~40s = ~66 minutes genesis = CreateGenesisBlock(1371488396, 1000112548, 0x1e0ffff0, 1, 100 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); @@ -275,6 +276,7 @@ public: pchMessageStart[3] = 0xf2; nDefaultPort = 17312; nPruneAfterHeight = 100000; + nNlrLimit = 50; // 50 * ~40s = ~33 minutes genesis = CreateGenesisBlock(1371387277, 1000580675, 0x1e0ffff0, 1, 100 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); @@ -389,6 +391,7 @@ public: pchMessageStart[3] = 0xda; nDefaultPort = 17412; nPruneAfterHeight = 1000; + nNlrLimit = 10; genesis = CreateGenesisBlock(1371387277, 0, 0x207fffff, 1, 100 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); diff --git a/src/chainparams.h b/src/chainparams.h index a7b1c62a1..f4df46532 100755 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -63,6 +63,7 @@ public: const Consensus::Params& GetConsensus() const { return consensus; } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } int GetDefaultPort() const { return nDefaultPort; } + int NoLargeReorgLimit() const { return nNlrLimit; } const CBlock& GenesisBlock() const { return genesis; } /** Default value for -checkmempool and -checkblockindex argument */ @@ -87,6 +88,7 @@ protected: CMessageHeader::MessageStartChars pchMessageStart; int nDefaultPort; uint64_t nPruneAfterHeight; + int nNlrLimit; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string strNetworkID; diff --git a/src/init.cpp b/src/init.cpp index da71ebb5a..f52086286 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -401,6 +401,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-maxreceivebuffer=", strprintf(_("Maximum per-connection receive buffer, *1000 bytes (default: %u)"), DEFAULT_MAXRECEIVEBUFFER)); strUsage += HelpMessageOpt("-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, *1000 bytes (default: %u)"), DEFAULT_MAXSENDBUFFER)); strUsage += HelpMessageOpt("-maxtimeadjustment", strprintf(_("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)"), DEFAULT_MAX_TIME_ADJUSTMENT)); + strUsage += HelpMessageOpt("-nlrlimit=", strprintf(_("Set the limit to be considered a large reorg, -nlrlimit=0 to disable NLR mode (default: %u)"), defaultChainParams->NoLargeReorgLimit())); strUsage += HelpMessageOpt("-onion=", strprintf(_("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)"), "-proxy")); strUsage += HelpMessageOpt("-onlynet=", _("Only connect to nodes in network (ipv4, ipv6 or onion)")); strUsage += HelpMessageOpt("-permitbaremultisig", strprintf(_("Relay non-P2SH multisig (default: %u)"), DEFAULT_PERMIT_BAREMULTISIG)); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 046769454..b8cd8e532 100755 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1457,7 +1457,7 @@ UniValue reconsiderblock(const JSONRPCRequest& request) } CValidationState state; - ActivateBestChain(state, Params()); + ActivateBestChain(state, Params(), std::shared_ptr(), true); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason()); diff --git a/src/validation.cpp b/src/validation.cpp index 521f55ce6..5da8e7444 100755 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2313,12 +2313,42 @@ static void PruneBlockIndexCandidates() { * Try to make some progress towards making pindexMostWork the active block. * pblock is either nullptr or a pointer to a CBlock corresponding to pindexMostWork. */ -static bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) +static bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr& pblock, bool& fInvalidFound, ConnectTrace& connectTrace, bool reconsider) { AssertLockHeld(cs_main); const CBlockIndex *pindexOldTip = chainActive.Tip(); const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork); + int nNlrLimit = gArgs.GetArg("-nlrlimit", Params().NoLargeReorgLimit()); + if (nNlrLimit != 0 && !reconsider && !IsInitialBlockDownload() && pindexFork != nullptr + && chainActive.Tip()->nHeight - pindexFork->nHeight >= nNlrLimit) { + LogPrintf("%s: NLR triggered! current height=%d current hash=%s | fork height=%d fork hash=%s\n", __func__, + pindexOldTip->nHeight, pindexOldTip->GetBlockHash().ToString(), pindexMostWork->nHeight, + pindexMostWork->GetBlockHash().ToString()); + CBlockIndex *pindexWalk = pindexMostWork; + + // mark invalid_child from tip of fork to second block of fork + while (pindexWalk->nHeight != pindexFork->nHeight+2) { + pindexWalk = pindexWalk->pprev; + pindexWalk->nStatus |= BLOCK_FAILED_CHILD; + setDirtyBlockIndex.insert(pindexWalk); + setBlockIndexCandidates.erase(pindexWalk); + } + + // mark invalid first block of fork + pindexWalk = pindexWalk->pprev; + pindexWalk->nStatus |= BLOCK_FAILED_VALID; + setDirtyBlockIndex.insert(pindexWalk); + setBlockIndexCandidates.erase(pindexWalk); + + // sanity check + assert(pindexWalk->pprev == pindexFork); + + fInvalidFound = true; + InvalidChainFound(pindexMostWork); + return true; + } + // Disconnect active blocks which are no longer in the best chain. bool fBlocksDisconnected = false; DisconnectedBlockTransactions disconnectpool; @@ -2420,7 +2450,7 @@ static void NotifyHeaderTip() { * or an activated best chain. pblock is either nullptr or a pointer to a block * that is already loaded (to avoid loading it again from disk). */ -bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr pblock) { +bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr pblock, bool reconsider) { // Note that while we're often called here from ProcessNewBlock, this is // far from a guarantee. Things in the P2P/RPC will often end up calling // us in the middle of ProcessNewBlock - do not assume pblock is set @@ -2451,7 +2481,7 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, bool fInvalidFound = false; std::shared_ptr nullBlockPtr; - if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) + if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace, reconsider)) return false; if (fInvalidFound) { diff --git a/src/validation.h b/src/validation.h index 0e120cd56..ac7067e96 100755 --- a/src/validation.h +++ b/src/validation.h @@ -273,7 +273,7 @@ bool IsInitialBlockDownload(); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ -bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr pblock = std::shared_ptr()); +bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr pblock = std::shared_ptr(), bool reconsider=false); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */ diff --git a/test/functional/nlr.py b/test/functional/nlr.py new file mode 100644 index 000000000..bbc891c6b --- /dev/null +++ b/test/functional/nlr.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Copyright (c) Flo Developers 2013-2018 +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the getchaintips RPC. + +- introduce a network split +- work on chains of different lengths +- join the network together again +- verify that getchaintips now returns two chain tips. +""" + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +class GetChainTipsTest (BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + self.extra_args = [['-nlrlimit=0'], ['-nlrlimit=10'], ['-nlrlimit=10'], ['-nlrlimit=10']] + self.setup_clean_chain = False + + def run_test (self): + + tips = self.nodes[0].getchaintips () + assert_equal (len (tips), 1) + assert_equal (tips[0]['branchlen'], 0) + assert_equal (tips[0]['height'], 200) + assert_equal (tips[0]['status'], 'active') + + # Split the network and build two chains of different lengths. + self.split_network() + self.nodes[0].generate(10) + self.nodes[2].generate(20) + self.sync_all([self.nodes[:2], self.nodes[2:]]) + + tips = self.nodes[1].getchaintips () + assert_equal (len (tips), 1) + shortTip = tips[0] + assert_equal(shortTip['branchlen'], 0) + assert_equal(shortTip['height'], 210) + assert_equal(tips[0]['status'], 'active') + + tips = self.nodes[3].getchaintips () + assert_equal (len (tips), 1) + longTip = tips[0] + assert_equal (longTip['branchlen'], 0) + assert_equal (longTip['height'], 220) + assert_equal (tips[0]['status'], 'active') + + # Join the network halves and check that we now have two tips + # (at least at the nodes that previously had the short chain). + print("pre join") + self.join_network_no_sync () + time.sleep(10) + print("post join") + + print("node 0") + print(self.nodes[0].getchaintips()) + print("node 1") + print(self.nodes[1].getchaintips()) + print("node 2") + print(self.nodes[2].getchaintips()) + + print("reconsider") + self.nodes[1].reconsiderblock(self.nodes[2].getbestblockhash()) + self.sync_all() + + print("node 0") + print(self.nodes[0].getchaintips ()) + print("node 1") + print(self.nodes[1].getchaintips ()) + print("node 2") + print(self.nodes[2].getchaintips ()) + self.nodes[2].generate(20) + + self.sync_all() + self.nodes[1].generate(20) + + self.sync_all() + + print("node 0") + print(self.nodes[0].getchaintips ()) + print("node 1") + print(self.nodes[1].getchaintips ()) + print("node 2") + print(self.nodes[2].getchaintips ()) + + # tips = self.nodes[1].getchaintips () + # assert_equal (len (tips), 2) + # assert_equal (tips[0], longTip) + # + # assert_equal (tips[1]['branchlen'], 10) + # assert_equal (tips[1]['status'], 'valid-fork') + # tips[1]['branchlen'] = 0 + # tips[1]['status'] = 'active' + # assert_equal (tips[1], shortTip) + + assert_equal(1, 3) + +if __name__ == '__main__': + GetChainTipsTest().main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 3fd7685ce..3cbe5fd17 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -310,6 +310,12 @@ class BitcoinTestFramework(object): connect_nodes_bi(self.nodes, 1, 2) self.sync_all() + def join_network_no_sync(self): + """ + Join the (previously split) network halves together. Do not wait sync. + """ + connect_nodes_bi(self.nodes, 1, 2) + def sync_all(self, node_groups=None): if not node_groups: node_groups = [self.nodes]