Skip to content

Commit 7f71a3c

Browse files
committed
Merge #6996: Add preciousblock RPC
5805ac8 Add preciousblock tests (Pieter Wuille) 5127c4f Add preciousblock RPC (Pieter Wuille)
2 parents df7519c + 5805ac8 commit 7f71a3c

File tree

7 files changed

+207
-3
lines changed

7 files changed

+207
-3
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
'invalidtxrequest.py',
141141
'abandonconflict.py',
142142
'p2p-versionbits-warning.py',
143+
'preciousblock.py',
143144
'importprunedfunds.py',
144145
'signmessages.py',
145146
'p2p-compactblocks.py',

qa/rpc-tests/preciousblock.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2015 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
#
7+
# Test PreciousBlock code
8+
#
9+
10+
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.util import *
12+
13+
def unidirectional_node_sync_via_rpc(node_src, node_dest):
14+
blocks_to_copy = []
15+
blockhash = node_src.getbestblockhash()
16+
while True:
17+
try:
18+
assert(len(node_dest.getblock(blockhash, False)) > 0)
19+
break
20+
except:
21+
blocks_to_copy.append(blockhash)
22+
blockhash = node_src.getblockheader(blockhash, True)['previousblockhash']
23+
blocks_to_copy.reverse()
24+
for blockhash in blocks_to_copy:
25+
blockdata = node_src.getblock(blockhash, False)
26+
assert(node_dest.submitblock(blockdata) in (None, 'inconclusive'))
27+
28+
def node_sync_via_rpc(nodes):
29+
for node_src in nodes:
30+
for node_dest in nodes:
31+
if node_src is node_dest:
32+
continue
33+
unidirectional_node_sync_via_rpc(node_src, node_dest)
34+
35+
class PreciousTest(BitcoinTestFramework):
36+
def setup_chain(self):
37+
print("Initializing test directory "+self.options.tmpdir)
38+
initialize_chain_clean(self.options.tmpdir, 3)
39+
40+
def setup_network(self):
41+
self.nodes = []
42+
self.is_network_split = False
43+
self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
44+
self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"]))
45+
self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"]))
46+
47+
def run_test(self):
48+
print("Ensure submitblock can in principle reorg to a competing chain")
49+
self.nodes[0].generate(1)
50+
assert(self.nodes[0].getblockcount() == 1)
51+
(hashY, hashZ) = self.nodes[1].generate(2)
52+
assert(self.nodes[1].getblockcount() == 2)
53+
node_sync_via_rpc(self.nodes[0:3])
54+
assert(self.nodes[0].getbestblockhash() == hashZ)
55+
56+
print("Mine blocks A-B-C on Node 0")
57+
(hashA, hashB, hashC) = self.nodes[0].generate(3)
58+
assert(self.nodes[0].getblockcount() == 5)
59+
print("Mine competing blocks E-F-G on Node 1")
60+
(hashE, hashF, hashG) = self.nodes[1].generate(3)
61+
assert(self.nodes[1].getblockcount() == 5)
62+
assert(hashC != hashG)
63+
print("Connect nodes and check no reorg occurs")
64+
# Submit competing blocks via RPC so any reorg should occur before we proceed (no way to wait on inaction for p2p sync)
65+
node_sync_via_rpc(self.nodes[0:2])
66+
connect_nodes_bi(self.nodes,0,1)
67+
assert(self.nodes[0].getbestblockhash() == hashC)
68+
assert(self.nodes[1].getbestblockhash() == hashG)
69+
print("Make Node0 prefer block G")
70+
self.nodes[0].preciousblock(hashG)
71+
assert(self.nodes[0].getbestblockhash() == hashG)
72+
print("Make Node0 prefer block C again")
73+
self.nodes[0].preciousblock(hashC)
74+
assert(self.nodes[0].getbestblockhash() == hashC)
75+
print("Make Node1 prefer block C")
76+
self.nodes[1].preciousblock(hashC)
77+
sync_chain(self.nodes[0:2]) # wait because node 1 may not have downloaded hashC
78+
assert(self.nodes[1].getbestblockhash() == hashC)
79+
print("Make Node1 prefer block G again")
80+
self.nodes[1].preciousblock(hashG)
81+
assert(self.nodes[1].getbestblockhash() == hashG)
82+
print("Make Node0 prefer block G again")
83+
self.nodes[0].preciousblock(hashG)
84+
assert(self.nodes[0].getbestblockhash() == hashG)
85+
print("Make Node1 prefer block C again")
86+
self.nodes[1].preciousblock(hashC)
87+
assert(self.nodes[1].getbestblockhash() == hashC)
88+
print("Mine another block (E-F-G-)H on Node 0 and reorg Node 1")
89+
self.nodes[0].generate(1)
90+
assert(self.nodes[0].getblockcount() == 6)
91+
sync_blocks(self.nodes[0:2])
92+
hashH = self.nodes[0].getbestblockhash()
93+
assert(self.nodes[1].getbestblockhash() == hashH)
94+
print("Node1 should not be able to prefer block C anymore")
95+
self.nodes[1].preciousblock(hashC)
96+
assert(self.nodes[1].getbestblockhash() == hashH)
97+
print("Mine competing blocks I-J-K-L on Node 2")
98+
self.nodes[2].generate(4)
99+
assert(self.nodes[2].getblockcount() == 6)
100+
hashL = self.nodes[2].getbestblockhash()
101+
print("Connect nodes and check no reorg occurs")
102+
node_sync_via_rpc(self.nodes[0:3])
103+
connect_nodes_bi(self.nodes,1,2)
104+
connect_nodes_bi(self.nodes,0,2)
105+
assert(self.nodes[0].getbestblockhash() == hashH)
106+
assert(self.nodes[1].getbestblockhash() == hashH)
107+
assert(self.nodes[2].getbestblockhash() == hashL)
108+
print("Make Node1 prefer block L")
109+
self.nodes[1].preciousblock(hashL)
110+
assert(self.nodes[1].getbestblockhash() == hashL)
111+
print("Make Node2 prefer block H")
112+
self.nodes[2].preciousblock(hashH)
113+
assert(self.nodes[2].getbestblockhash() == hashH)
114+
115+
if __name__ == '__main__':
116+
PreciousTest().main()

qa/rpc-tests/test_framework/util.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ def sync_blocks(rpc_connections, wait=1, timeout=60):
137137
maxheight = max(heights)
138138
raise AssertionError("Block sync failed")
139139

140+
def sync_chain(rpc_connections, wait=1):
141+
"""
142+
Wait until everybody has the same best block
143+
"""
144+
while True:
145+
counts = [ x.getbestblockhash() for x in rpc_connections ]
146+
if counts == [ counts[0] ]*len(counts):
147+
break
148+
time.sleep(wait)
149+
140150
def sync_mempools(rpc_connections, wait=1, timeout=60):
141151
"""
142152
Wait until everybody has the same transactions in their memory

src/chain.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class CBlockIndex
200200
unsigned int nNonce;
201201

202202
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
203-
uint32_t nSequenceId;
203+
int32_t nSequenceId;
204204

205205
void SetNull()
206206
{

src/main.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,11 @@ namespace {
169169
*/
170170
CCriticalSection cs_nBlockSequenceId;
171171
/** Blocks loaded from disk are assigned id 0, so start the counter at 1. */
172-
uint32_t nBlockSequenceId = 1;
172+
int32_t nBlockSequenceId = 1;
173+
/** Decreasing counter (used by subsequent preciousblock calls). */
174+
int32_t nBlockReverseSequenceId = -1;
175+
/** chainwork for the last block that preciousblock has been applied to. */
176+
arith_uint256 nLastPreciousChainwork = 0;
173177

174178
/**
175179
* Sources of received blocks, saved to be able to send them reject
@@ -3137,6 +3141,36 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
31373141
return true;
31383142
}
31393143

3144+
3145+
bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex)
3146+
{
3147+
{
3148+
LOCK(cs_main);
3149+
if (pindex->nChainWork < chainActive.Tip()->nChainWork) {
3150+
// Nothing to do, this block is not at the tip.
3151+
return true;
3152+
}
3153+
if (chainActive.Tip()->nChainWork > nLastPreciousChainwork) {
3154+
// The chain has been extended since the last call, reset the counter.
3155+
nBlockReverseSequenceId = -1;
3156+
}
3157+
nLastPreciousChainwork = chainActive.Tip()->nChainWork;
3158+
setBlockIndexCandidates.erase(pindex);
3159+
pindex->nSequenceId = nBlockReverseSequenceId;
3160+
if (nBlockReverseSequenceId > std::numeric_limits<int32_t>::min()) {
3161+
// We can't keep reducing the counter if somebody really wants to
3162+
// call preciousblock 2**31-1 times on the same set of tips...
3163+
nBlockReverseSequenceId--;
3164+
}
3165+
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->nChainTx) {
3166+
setBlockIndexCandidates.insert(pindex);
3167+
PruneBlockIndexCandidates();
3168+
}
3169+
}
3170+
3171+
return ActivateBestChain(state, params);
3172+
}
3173+
31403174
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex)
31413175
{
31423176
AssertLockHeld(cs_main);
@@ -4531,7 +4565,7 @@ void static CheckBlockIndex(const Consensus::Params& consensusParams)
45314565
assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match.
45324566
assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block.
45334567
}
4534-
if (pindex->nChainTx == 0) assert(pindex->nSequenceId == 0); // nSequenceId can't be set for blocks that aren't linked
4568+
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)
45354569
// VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred).
45364570
// HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred.
45374571
if (!fHavePruned) {

src/main.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ class CVerifyDB {
509509
/** Find the last common block between the parameter chain and a locator. */
510510
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator);
511511

512+
/** Mark a block as precious and reorganize. */
513+
bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex);
514+
512515
/** Mark a block as invalid. */
513516
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex);
514517

src/rpc/blockchain.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,44 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp)
12511251
return mempoolInfoToJSON();
12521252
}
12531253

1254+
UniValue preciousblock(const UniValue& params, bool fHelp)
1255+
{
1256+
if (fHelp || params.size() != 1)
1257+
throw runtime_error(
1258+
"preciousblock \"hash\"\n"
1259+
"\nTreats a block as if it were received before others with the same work.\n"
1260+
"\nA later preciousblock call can override the effect of an earlier one.\n"
1261+
"\nThe effects of preciousblock are not retained across restarts.\n"
1262+
"\nArguments:\n"
1263+
"1. hash (string, required) the hash of the block to mark as precious\n"
1264+
"\nResult:\n"
1265+
"\nExamples:\n"
1266+
+ HelpExampleCli("preciousblock", "\"blockhash\"")
1267+
+ HelpExampleRpc("preciousblock", "\"blockhash\"")
1268+
);
1269+
1270+
std::string strHash = params[0].get_str();
1271+
uint256 hash(uint256S(strHash));
1272+
CBlockIndex* pblockindex;
1273+
1274+
{
1275+
LOCK(cs_main);
1276+
if (mapBlockIndex.count(hash) == 0)
1277+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
1278+
1279+
pblockindex = mapBlockIndex[hash];
1280+
}
1281+
1282+
CValidationState state;
1283+
PreciousBlock(state, Params(), pblockindex);
1284+
1285+
if (!state.IsValid()) {
1286+
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
1287+
}
1288+
1289+
return NullUniValue;
1290+
}
1291+
12541292
UniValue invalidateblock(const UniValue& params, bool fHelp)
12551293
{
12561294
if (fHelp || params.size() != 1)
@@ -1346,6 +1384,8 @@ static const CRPCCommand commands[] =
13461384
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
13471385
{ "blockchain", "verifychain", &verifychain, true },
13481386

1387+
{ "blockchain", "preciousblock", &preciousblock, true },
1388+
13491389
/* Not shown in help */
13501390
{ "hidden", "invalidateblock", &invalidateblock, true },
13511391
{ "hidden", "reconsiderblock", &reconsiderblock, true },

0 commit comments

Comments
 (0)