aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xqa/pull-tester/rpc-tests.py1
-rwxr-xr-xqa/rpc-tests/preciousblock.py116
-rw-r--r--qa/rpc-tests/test_framework/util.py10
-rw-r--r--src/chain.h2
-rw-r--r--src/main.cpp38
-rw-r--r--src/main.h3
-rw-r--r--src/rpc/blockchain.cpp40
7 files changed, 207 insertions, 3 deletions
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
index 4a953385e0..7430bb2256 100755
--- a/qa/pull-tester/rpc-tests.py
+++ b/qa/pull-tester/rpc-tests.py
@@ -140,6 +140,7 @@ testScripts = [
'invalidtxrequest.py',
'abandonconflict.py',
'p2p-versionbits-warning.py',
+ 'preciousblock.py',
'importprunedfunds.py',
'signmessages.py',
'p2p-compactblocks.py',
diff --git a/qa/rpc-tests/preciousblock.py b/qa/rpc-tests/preciousblock.py
new file mode 100755
index 0000000000..854dcc7251
--- /dev/null
+++ b/qa/rpc-tests/preciousblock.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test PreciousBlock code
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+def unidirectional_node_sync_via_rpc(node_src, node_dest):
+ blocks_to_copy = []
+ blockhash = node_src.getbestblockhash()
+ while True:
+ try:
+ assert(len(node_dest.getblock(blockhash, False)) > 0)
+ break
+ except:
+ blocks_to_copy.append(blockhash)
+ blockhash = node_src.getblockheader(blockhash, True)['previousblockhash']
+ blocks_to_copy.reverse()
+ for blockhash in blocks_to_copy:
+ blockdata = node_src.getblock(blockhash, False)
+ assert(node_dest.submitblock(blockdata) in (None, 'inconclusive'))
+
+def node_sync_via_rpc(nodes):
+ for node_src in nodes:
+ for node_dest in nodes:
+ if node_src is node_dest:
+ continue
+ unidirectional_node_sync_via_rpc(node_src, node_dest)
+
+class PreciousTest(BitcoinTestFramework):
+ def setup_chain(self):
+ print("Initializing test directory "+self.options.tmpdir)
+ initialize_chain_clean(self.options.tmpdir, 3)
+
+ def setup_network(self):
+ self.nodes = []
+ self.is_network_split = False
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"]))
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"]))
+
+ def run_test(self):
+ print("Ensure submitblock can in principle reorg to a competing chain")
+ self.nodes[0].generate(1)
+ assert(self.nodes[0].getblockcount() == 1)
+ (hashY, hashZ) = self.nodes[1].generate(2)
+ assert(self.nodes[1].getblockcount() == 2)
+ node_sync_via_rpc(self.nodes[0:3])
+ assert(self.nodes[0].getbestblockhash() == hashZ)
+
+ print("Mine blocks A-B-C on Node 0")
+ (hashA, hashB, hashC) = self.nodes[0].generate(3)
+ assert(self.nodes[0].getblockcount() == 5)
+ print("Mine competing blocks E-F-G on Node 1")
+ (hashE, hashF, hashG) = self.nodes[1].generate(3)
+ assert(self.nodes[1].getblockcount() == 5)
+ assert(hashC != hashG)
+ print("Connect nodes and check no reorg occurs")
+ # Submit competing blocks via RPC so any reorg should occur before we proceed (no way to wait on inaction for p2p sync)
+ node_sync_via_rpc(self.nodes[0:2])
+ connect_nodes_bi(self.nodes,0,1)
+ assert(self.nodes[0].getbestblockhash() == hashC)
+ assert(self.nodes[1].getbestblockhash() == hashG)
+ print("Make Node0 prefer block G")
+ self.nodes[0].preciousblock(hashG)
+ assert(self.nodes[0].getbestblockhash() == hashG)
+ print("Make Node0 prefer block C again")
+ self.nodes[0].preciousblock(hashC)
+ assert(self.nodes[0].getbestblockhash() == hashC)
+ print("Make Node1 prefer block C")
+ self.nodes[1].preciousblock(hashC)
+ sync_chain(self.nodes[0:2]) # wait because node 1 may not have downloaded hashC
+ assert(self.nodes[1].getbestblockhash() == hashC)
+ print("Make Node1 prefer block G again")
+ self.nodes[1].preciousblock(hashG)
+ assert(self.nodes[1].getbestblockhash() == hashG)
+ print("Make Node0 prefer block G again")
+ self.nodes[0].preciousblock(hashG)
+ assert(self.nodes[0].getbestblockhash() == hashG)
+ print("Make Node1 prefer block C again")
+ self.nodes[1].preciousblock(hashC)
+ assert(self.nodes[1].getbestblockhash() == hashC)
+ print("Mine another block (E-F-G-)H on Node 0 and reorg Node 1")
+ self.nodes[0].generate(1)
+ assert(self.nodes[0].getblockcount() == 6)
+ sync_blocks(self.nodes[0:2])
+ hashH = self.nodes[0].getbestblockhash()
+ assert(self.nodes[1].getbestblockhash() == hashH)
+ print("Node1 should not be able to prefer block C anymore")
+ self.nodes[1].preciousblock(hashC)
+ assert(self.nodes[1].getbestblockhash() == hashH)
+ print("Mine competing blocks I-J-K-L on Node 2")
+ self.nodes[2].generate(4)
+ assert(self.nodes[2].getblockcount() == 6)
+ hashL = self.nodes[2].getbestblockhash()
+ print("Connect nodes and check no reorg occurs")
+ node_sync_via_rpc(self.nodes[0:3])
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ assert(self.nodes[0].getbestblockhash() == hashH)
+ assert(self.nodes[1].getbestblockhash() == hashH)
+ assert(self.nodes[2].getbestblockhash() == hashL)
+ print("Make Node1 prefer block L")
+ self.nodes[1].preciousblock(hashL)
+ assert(self.nodes[1].getbestblockhash() == hashL)
+ print("Make Node2 prefer block H")
+ self.nodes[2].preciousblock(hashH)
+ assert(self.nodes[2].getbestblockhash() == hashH)
+
+if __name__ == '__main__':
+ PreciousTest().main()
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
index c6b0367b41..c818af4bd7 100644
--- a/qa/rpc-tests/test_framework/util.py
+++ b/qa/rpc-tests/test_framework/util.py
@@ -137,6 +137,16 @@ def sync_blocks(rpc_connections, wait=1, timeout=60):
maxheight = max(heights)
raise AssertionError("Block sync failed")
+def sync_chain(rpc_connections, wait=1):
+ """
+ Wait until everybody has the same best block
+ """
+ while True:
+ counts = [ x.getbestblockhash() for x in rpc_connections ]
+ if counts == [ counts[0] ]*len(counts):
+ break
+ time.sleep(wait)
+
def sync_mempools(rpc_connections, wait=1, timeout=60):
"""
Wait until everybody has the same transactions in their memory
diff --git a/src/chain.h b/src/chain.h
index 6588e8f57d..e2f8c56522 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -200,7 +200,7 @@ public:
unsigned int nNonce;
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
- uint32_t nSequenceId;
+ int32_t nSequenceId;
void SetNull()
{
diff --git a/src/main.cpp b/src/main.cpp
index 9f3116e358..cb3e771602 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -169,7 +169,11 @@ namespace {
*/
CCriticalSection cs_nBlockSequenceId;
/** Blocks loaded from disk are assigned id 0, so start the counter at 1. */
- uint32_t nBlockSequenceId = 1;
+ int32_t nBlockSequenceId = 1;
+ /** Decreasing counter (used by subsequent preciousblock calls). */
+ int32_t nBlockReverseSequenceId = -1;
+ /** chainwork for the last block that preciousblock has been applied to. */
+ arith_uint256 nLastPreciousChainwork = 0;
/**
* Sources of received blocks, saved to be able to send them reject
@@ -3137,6 +3141,36 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
return true;
}
+
+bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex)
+{
+ {
+ LOCK(cs_main);
+ if (pindex->nChainWork < chainActive.Tip()->nChainWork) {
+ // Nothing to do, this block is not at the tip.
+ return true;
+ }
+ if (chainActive.Tip()->nChainWork > nLastPreciousChainwork) {
+ // The chain has been extended since the last call, reset the counter.
+ nBlockReverseSequenceId = -1;
+ }
+ nLastPreciousChainwork = chainActive.Tip()->nChainWork;
+ setBlockIndexCandidates.erase(pindex);
+ pindex->nSequenceId = nBlockReverseSequenceId;
+ if (nBlockReverseSequenceId > std::numeric_limits<int32_t>::min()) {
+ // We can't keep reducing the counter if somebody really wants to
+ // call preciousblock 2**31-1 times on the same set of tips...
+ nBlockReverseSequenceId--;
+ }
+ if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->nChainTx) {
+ setBlockIndexCandidates.insert(pindex);
+ PruneBlockIndexCandidates();
+ }
+ }
+
+ return ActivateBestChain(state, params);
+}
+
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex)
{
AssertLockHeld(cs_main);
@@ -4531,7 +4565,7 @@ void static CheckBlockIndex(const Consensus::Params& consensusParams)
assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match.
assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block.
}
- if (pindex->nChainTx == 0) assert(pindex->nSequenceId == 0); // nSequenceId can't be set for blocks that aren't linked
+ if (pindex->nChainTx == 0) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock)
// VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred).
// HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred.
if (!fHavePruned) {
diff --git a/src/main.h b/src/main.h
index db58b27487..49768f21c1 100644
--- a/src/main.h
+++ b/src/main.h
@@ -509,6 +509,9 @@ public:
/** Find the last common block between the parameter chain and a locator. */
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator);
+/** Mark a block as precious and reorganize. */
+bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex);
+
/** Mark a block as invalid. */
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 1e04fcc01c..1ca4a7c6d7 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1251,6 +1251,44 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp)
return mempoolInfoToJSON();
}
+UniValue preciousblock(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ throw runtime_error(
+ "preciousblock \"hash\"\n"
+ "\nTreats a block as if it were received before others with the same work.\n"
+ "\nA later preciousblock call can override the effect of an earlier one.\n"
+ "\nThe effects of preciousblock are not retained across restarts.\n"
+ "\nArguments:\n"
+ "1. hash (string, required) the hash of the block to mark as precious\n"
+ "\nResult:\n"
+ "\nExamples:\n"
+ + HelpExampleCli("preciousblock", "\"blockhash\"")
+ + HelpExampleRpc("preciousblock", "\"blockhash\"")
+ );
+
+ std::string strHash = params[0].get_str();
+ uint256 hash(uint256S(strHash));
+ CBlockIndex* pblockindex;
+
+ {
+ LOCK(cs_main);
+ if (mapBlockIndex.count(hash) == 0)
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+
+ pblockindex = mapBlockIndex[hash];
+ }
+
+ CValidationState state;
+ PreciousBlock(state, Params(), pblockindex);
+
+ if (!state.IsValid()) {
+ throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
+ }
+
+ return NullUniValue;
+}
+
UniValue invalidateblock(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
@@ -1346,6 +1384,8 @@ static const CRPCCommand commands[] =
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
{ "blockchain", "verifychain", &verifychain, true },
+ { "blockchain", "preciousblock", &preciousblock, true },
+
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, true },
{ "hidden", "reconsiderblock", &reconsiderblock, true },