Merge #6996: Add preciousblock RPC

5805ac8 Add preciousblock tests (Pieter Wuille)
5127c4f Add preciousblock RPC (Pieter Wuille)
This commit is contained in:
Wladimir J. van der Laan 2016-10-18 21:35:27 +02:00 committed by Alexander Block
parent b02e461fae
commit 6e871c0c2e
7 changed files with 207 additions and 3 deletions

View File

@ -151,6 +151,7 @@ testScripts = [
'invalidtxrequest.py', # NOTE: needs dash_hash to pass 'invalidtxrequest.py', # NOTE: needs dash_hash to pass
'abandonconflict.py', 'abandonconflict.py',
'p2p-versionbits-warning.py', 'p2p-versionbits-warning.py',
'preciousblock.py',
'importprunedfunds.py', 'importprunedfunds.py',
'signmessages.py', 'signmessages.py',
] ]

116
qa/rpc-tests/preciousblock.py Executable file
View File

@ -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()

View File

@ -147,6 +147,16 @@ def sync_blocks(rpc_connections, wait=1, timeout=60):
maxheight = max(heights) maxheight = max(heights)
raise AssertionError("Block sync failed") 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): def sync_mempools(rpc_connections, wait=1, timeout=60):
""" """
Wait until everybody has the same transactions in their memory Wait until everybody has the same transactions in their memory

View File

@ -198,7 +198,7 @@ public:
unsigned int nNonce; unsigned int nNonce;
//! (memory only) Sequential id assigned to distinguish order in which blocks are received. //! (memory only) Sequential id assigned to distinguish order in which blocks are received.
uint32_t nSequenceId; int32_t nSequenceId;
void SetNull() void SetNull()
{ {

View File

@ -1410,6 +1410,44 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp)
return mempoolInfoToJSON(); 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) UniValue invalidateblock(const UniValue& params, bool fHelp)
{ {
if (fHelp || params.size() != 1) if (fHelp || params.size() != 1)
@ -1507,6 +1545,8 @@ static const CRPCCommand commands[] =
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
{ "blockchain", "verifychain", &verifychain, true }, { "blockchain", "verifychain", &verifychain, true },
{ "blockchain", "preciousblock", &preciousblock, true },
/* Not shown in help */ /* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, true }, { "hidden", "invalidateblock", &invalidateblock, true },
{ "hidden", "reconsiderblock", &reconsiderblock, true }, { "hidden", "reconsiderblock", &reconsiderblock, true },

View File

@ -170,7 +170,11 @@ namespace {
*/ */
CCriticalSection cs_nBlockSequenceId; CCriticalSection cs_nBlockSequenceId;
/** Blocks loaded from disk are assigned id 0, so start the counter at 1. */ /** 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;
/** Dirty block index entries. */ /** Dirty block index entries. */
set<CBlockIndex*> setDirtyBlockIndex; set<CBlockIndex*> setDirtyBlockIndex;
@ -2924,6 +2928,36 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
return true; 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) bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex)
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
@ -4254,7 +4288,7 @@ void static CheckBlockIndex(const Consensus::Params& consensusParams)
assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match. 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. 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). // 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. // HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred.
if (!fHavePruned) { if (!fHavePruned) {

View File

@ -492,6 +492,9 @@ public:
/** Find the last common block between the parameter chain and a locator. */ /** Find the last common block between the parameter chain and a locator. */
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& 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. */ /** Mark a block as invalid. */
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex); bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex);