-
Notifications
You must be signed in to change notification settings - Fork 788
/
abc-invalid-chains.py
executable file
·158 lines (126 loc) · 5.86 KB
/
abc-invalid-chains.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
# Copyright (c) 2019 The Bitcoin developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-ilncense.php.
import time
from test_framework.test_framework import BitcoinTestFramework
from test_framework.mininode import P2PDataStore
from test_framework.util import assert_equal
from test_framework.blocktools import (
create_block,
create_coinbase,
)
class InvalidChainsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.tip = None
self.blocks = {}
self.block_heights = {}
self.extra_args = [["-whitelist=127.0.0.1"]]
def next_block(self, number):
if self.tip is None:
base_block_hash = self.genesis_hash
block_time = int(time.time()) + 1
else:
base_block_hash = self.tip.sha256
block_time = self.tip.nTime + 1
height = self.block_heights[base_block_hash] + 1
coinbase = create_coinbase(height)
coinbase.rehash()
block = create_block(base_block_hash, coinbase, block_time)
block.solve()
self.tip = block
self.block_heights[block.sha256] = height
assert number not in self.blocks
self.blocks[number] = block
return block
def run_test(self):
node = self.nodes[0]
node.add_p2p_connection(P2PDataStore())
self.genesis_hash = int(node.getbestblockhash(), 16)
self.block_heights[self.genesis_hash] = 0
# move the tip back to a previous block
def tip(number):
self.tip = self.blocks[number]
# shorthand for functions
block = self.next_block
# Reference for blocks mined in this test:
#
# 11 21 -- 221 - 222
# / / /
# 0 - 1 - 2 - 22 - 23 - 24 - 25
# \
# -- 12 - 13 - 14
# \
# -- 15 - 16 - 17 - 18
# Generate some valid blocks
node.p2p.send_blocks_and_test([block(0), block(1), block(2)], node)
# Explicitly invalidate blocks 1 and 2
# See below for why we do this
node.invalidateblock(self.blocks[1].hash)
assert_equal(self.blocks[0].hash, node.getbestblockhash())
node.invalidateblock(self.blocks[2].hash)
assert_equal(self.blocks[0].hash, node.getbestblockhash())
# Mining on top of blocks 1 or 2 is rejected
tip(1)
node.p2p.send_blocks_and_test(
[block(11)], node, success=False, force_send=True, reject_reason='bad-prevblk')
tip(2)
node.p2p.send_blocks_and_test(
[block(21)], node, success=False, force_send=True, reject_reason='bad-prevblk')
# Reconsider block 2 to remove invalid status from *both* 1 and 2
# The goal is to test that block 1 is not retaining any internal state
# that prevents us from accepting blocks building on top of block 1
node.reconsiderblock(self.blocks[2].hash)
assert_equal(self.blocks[2].hash, node.getbestblockhash())
# Mining on the block 1 chain should be accepted
# (needs to mine two blocks because less-work chains are not processed)
tip(1)
node.p2p.send_blocks_and_test([block(12), block(13)], node)
# Mining on the block 2 chain should still be accepted
# (needs to mine two blocks because less-work chains are not processed)
tip(2)
node.p2p.send_blocks_and_test([block(22), block(221)], node)
# Mine more blocks from block 22 to be longest chain
tip(22)
node.p2p.send_blocks_and_test([block(23), block(24)], node)
# Sanity checks
assert_equal(self.blocks[24].hash, node.getbestblockhash())
assert any(self.blocks[221].hash == chaintip["hash"]
for chaintip in node.getchaintips())
# Invalidating the block 2 chain should reject new blocks on that chain
node.invalidateblock(self.blocks[2].hash)
assert_equal(self.blocks[13].hash, node.getbestblockhash())
# Mining on the block 2 chain should be rejected
tip(24)
node.p2p.send_blocks_and_test(
[block(25)], node, success=False, force_send=True, reject_reason='bad-prevblk')
# Continued mining on the block 1 chain is still ok
tip(13)
node.p2p.send_blocks_and_test([block(14)], node)
# Mining on a once-valid chain forking from block 2's longest chain,
# which is now invalid, should also be rejected.
tip(221)
node.p2p.send_blocks_and_test(
[block(222)], node, success=False, force_send=True, reject_reason='bad-prevblk')
self.log.info(
"Make sure that reconsidering a block behaves correctly when cousin chains (neither ancestors nor descendants) become available as a result")
# Reorg out 14 with four blocks.
tip(13)
node.p2p.send_blocks_and_test(
[block(15), block(16), block(17), block(18)], node)
# Invalidate 17 (so 18 now has failed parent)
node.invalidateblock(self.blocks[17].hash)
assert_equal(self.blocks[16].hash, node.getbestblockhash())
# Invalidate 13 (so 14 and 15 and 16 now also have failed parent)
node.invalidateblock(self.blocks[13].hash)
assert_equal(self.blocks[12].hash, node.getbestblockhash())
# Reconsider 14, which should reconsider 13 and remove failed parent
# from our cousins 15 and 16 as well. Even though we reconsidered
# 14, we end up on a different chain because 15/16 have more work.
# (But, this shouldn't undo our invalidation of 17)
node.reconsiderblock(self.blocks[14].hash)
assert_equal(self.blocks[16].hash, node.getbestblockhash())
if __name__ == '__main__':
InvalidChainsTest().main()