2 # Copyright (c) 2015-2016 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 """Test BIP 9 soft forks.
7 Connect to a single node.
8 regtest lock-in with 108/144 block signalling
9 activation after a further 144 blocks
10 mine 2 block and save coinbases for later use
11 mine 141 blocks to transition from DEFINED to STARTED
12 mine 100 blocks signalling readiness and 44 not in order to fail to change state this period
13 mine 108 blocks signalling readiness and 36 blocks not signalling readiness (STARTED->LOCKED_IN)
14 mine a further 143 blocks (LOCKED_IN)
15 test that enforcement has not triggered (which triggers ACTIVE)
16 test that enforcement has triggered
18 from io
import BytesIO
23 from test_framework
.test_framework
import ComparisonTestFramework
24 from test_framework
.util
import *
25 from test_framework
.mininode
import CTransaction
, NetworkThread
26 from test_framework
.blocktools
import create_coinbase
, create_block
27 from test_framework
.comptool
import TestInstance
, TestManager
28 from test_framework
.script
import CScript
, OP_1NEGATE
, OP_CHECKSEQUENCEVERIFY
, OP_DROP
30 class BIP9SoftForksTest(ComparisonTestFramework
):
31 def set_test_params(self
):
33 self
.extra_args
= [['-whitelist=127.0.0.1']]
34 self
.setup_clean_chain
= True
37 self
.test
= TestManager(self
, self
.options
.tmpdir
)
38 self
.test
.add_all_connections(self
.nodes
)
39 NetworkThread().start() # Start up network handling in another thread
42 def create_transaction(self
, node
, coinbase
, to_address
, amount
):
43 from_txid
= node
.getblock(coinbase
)['tx'][0]
44 inputs
= [{ "txid" : from_txid
, "vout" : 0}]
45 outputs
= { to_address
: amount
}
46 rawtx
= node
.createrawtransaction(inputs
, outputs
)
48 f
= BytesIO(hex_str_to_bytes(rawtx
))
53 def sign_transaction(self
, node
, tx
):
54 signresult
= node
.signrawtransaction(bytes_to_hex_str(tx
.serialize()))
56 f
= BytesIO(hex_str_to_bytes(signresult
['hex']))
60 def generate_blocks(self
, number
, version
, test_blocks
= []):
61 for i
in range(number
):
62 block
= create_block(self
.tip
, create_coinbase(self
.height
), self
.last_block_time
+ 1)
63 block
.nVersion
= version
66 test_blocks
.append([block
, True])
67 self
.last_block_time
+= 1
68 self
.tip
= block
.sha256
72 def get_bip9_status(self
, key
):
73 info
= self
.nodes
[0].getblockchaininfo()
74 return info
['bip9_softforks'][key
]
76 def test_BIP(self
, bipName
, activated_version
, invalidate
, invalidatePostSignature
, bitno
):
77 assert_equal(self
.get_bip9_status(bipName
)['status'], 'defined')
78 assert_equal(self
.get_bip9_status(bipName
)['since'], 0)
80 # generate some coins for later
81 self
.coinbase_blocks
= self
.nodes
[0].generate(2)
82 self
.height
= 3 # height of the next block to build
83 self
.tip
= int("0x" + self
.nodes
[0].getbestblockhash(), 0)
84 self
.nodeaddress
= self
.nodes
[0].getnewaddress()
85 self
.last_block_time
= int(time
.time())
87 assert_equal(self
.get_bip9_status(bipName
)['status'], 'defined')
88 assert_equal(self
.get_bip9_status(bipName
)['since'], 0)
89 tmpl
= self
.nodes
[0].getblocktemplate({})
90 assert(bipName
not in tmpl
['rules'])
91 assert(bipName
not in tmpl
['vbavailable'])
92 assert_equal(tmpl
['vbrequired'], 0)
93 assert_equal(tmpl
['version'], 0x20000000)
96 # Advance from DEFINED to STARTED
97 test_blocks
= self
.generate_blocks(141, 4)
98 yield TestInstance(test_blocks
, sync_every_block
=False)
100 assert_equal(self
.get_bip9_status(bipName
)['status'], 'started')
101 assert_equal(self
.get_bip9_status(bipName
)['since'], 144)
102 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 0)
103 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 0)
104 tmpl
= self
.nodes
[0].getblocktemplate({})
105 assert(bipName
not in tmpl
['rules'])
106 assert_equal(tmpl
['vbavailable'][bipName
], bitno
)
107 assert_equal(tmpl
['vbrequired'], 0)
108 assert(tmpl
['version'] & activated_version
)
111 # check stats after max number of "signalling not" blocks such that LOCKED_IN still possible this period
112 test_blocks
= self
.generate_blocks(36, 4, test_blocks
) # 0x00000004 (signalling not)
113 test_blocks
= self
.generate_blocks(10, activated_version
) # 0x20000001 (signalling ready)
114 yield TestInstance(test_blocks
, sync_every_block
=False)
116 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 46)
117 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 10)
118 assert_equal(self
.get_bip9_status(bipName
)['statistics']['possible'], True)
121 # check stats after one additional "signalling not" block -- LOCKED_IN no longer possible this period
122 test_blocks
= self
.generate_blocks(1, 4, test_blocks
) # 0x00000004 (signalling not)
123 yield TestInstance(test_blocks
, sync_every_block
=False)
125 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 47)
126 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 10)
127 assert_equal(self
.get_bip9_status(bipName
)['statistics']['possible'], False)
130 # finish period with "ready" blocks, but soft fork will still fail to advance to LOCKED_IN
131 test_blocks
= self
.generate_blocks(97, activated_version
) # 0x20000001 (signalling ready)
132 yield TestInstance(test_blocks
, sync_every_block
=False)
134 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 0)
135 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 0)
136 assert_equal(self
.get_bip9_status(bipName
)['statistics']['possible'], True)
137 assert_equal(self
.get_bip9_status(bipName
)['status'], 'started')
140 # Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
141 # using a variety of bits to simulate multiple parallel softforks
142 test_blocks
= self
.generate_blocks(50, activated_version
) # 0x20000001 (signalling ready)
143 test_blocks
= self
.generate_blocks(20, 4, test_blocks
) # 0x00000004 (signalling not)
144 test_blocks
= self
.generate_blocks(50, activated_version
, test_blocks
) # 0x20000101 (signalling ready)
145 test_blocks
= self
.generate_blocks(24, 4, test_blocks
) # 0x20010000 (signalling not)
146 yield TestInstance(test_blocks
, sync_every_block
=False)
148 assert_equal(self
.get_bip9_status(bipName
)['status'], 'started')
149 assert_equal(self
.get_bip9_status(bipName
)['since'], 144)
150 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 0)
151 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 0)
152 tmpl
= self
.nodes
[0].getblocktemplate({})
153 assert(bipName
not in tmpl
['rules'])
154 assert_equal(tmpl
['vbavailable'][bipName
], bitno
)
155 assert_equal(tmpl
['vbrequired'], 0)
156 assert(tmpl
['version'] & activated_version
)
159 # 108 out of 144 signal bit 1 to achieve LOCKED_IN
160 # using a variety of bits to simulate multiple parallel softforks
161 test_blocks
= self
.generate_blocks(57, activated_version
) # 0x20000001 (signalling ready)
162 test_blocks
= self
.generate_blocks(26, 4, test_blocks
) # 0x00000004 (signalling not)
163 test_blocks
= self
.generate_blocks(50, activated_version
, test_blocks
) # 0x20000101 (signalling ready)
164 test_blocks
= self
.generate_blocks(10, 4, test_blocks
) # 0x20010000 (signalling not)
165 yield TestInstance(test_blocks
, sync_every_block
=False)
167 # check counting stats and "possible" flag before last block of this period achieves LOCKED_IN...
168 assert_equal(self
.get_bip9_status(bipName
)['statistics']['elapsed'], 143)
169 assert_equal(self
.get_bip9_status(bipName
)['statistics']['count'], 107)
170 assert_equal(self
.get_bip9_status(bipName
)['statistics']['possible'], True)
171 assert_equal(self
.get_bip9_status(bipName
)['status'], 'started')
173 # ...continue with Test 3
174 test_blocks
= self
.generate_blocks(1, activated_version
) # 0x20000001 (signalling ready)
175 yield TestInstance(test_blocks
, sync_every_block
=False)
177 assert_equal(self
.get_bip9_status(bipName
)['status'], 'locked_in')
178 assert_equal(self
.get_bip9_status(bipName
)['since'], 576)
179 tmpl
= self
.nodes
[0].getblocktemplate({})
180 assert(bipName
not in tmpl
['rules'])
183 # 143 more version 536870913 blocks (waiting period-1)
184 test_blocks
= self
.generate_blocks(143, 4)
185 yield TestInstance(test_blocks
, sync_every_block
=False)
187 assert_equal(self
.get_bip9_status(bipName
)['status'], 'locked_in')
188 assert_equal(self
.get_bip9_status(bipName
)['since'], 576)
189 tmpl
= self
.nodes
[0].getblocktemplate({})
190 assert(bipName
not in tmpl
['rules'])
193 # Check that the new rule is enforced
194 spendtx
= self
.create_transaction(self
.nodes
[0],
195 self
.coinbase_blocks
[0], self
.nodeaddress
, 1.0)
197 spendtx
= self
.sign_transaction(self
.nodes
[0], spendtx
)
199 invalidatePostSignature(spendtx
)
201 block
= create_block(self
.tip
, create_coinbase(self
.height
), self
.last_block_time
+ 1)
202 block
.nVersion
= activated_version
203 block
.vtx
.append(spendtx
)
204 block
.hashMerkleRoot
= block
.calc_merkle_root()
208 self
.last_block_time
+= 1
209 self
.tip
= block
.sha256
211 yield TestInstance([[block
, True]])
213 assert_equal(self
.get_bip9_status(bipName
)['status'], 'active')
214 assert_equal(self
.get_bip9_status(bipName
)['since'], 720)
215 tmpl
= self
.nodes
[0].getblocktemplate({})
216 assert(bipName
in tmpl
['rules'])
217 assert(bipName
not in tmpl
['vbavailable'])
218 assert_equal(tmpl
['vbrequired'], 0)
219 assert(not (tmpl
['version'] & (1 << bitno
)))
222 # Check that the new sequence lock rules are enforced
223 spendtx
= self
.create_transaction(self
.nodes
[0],
224 self
.coinbase_blocks
[1], self
.nodeaddress
, 1.0)
226 spendtx
= self
.sign_transaction(self
.nodes
[0], spendtx
)
228 invalidatePostSignature(spendtx
)
231 block
= create_block(self
.tip
, create_coinbase(self
.height
), self
.last_block_time
+ 1)
233 block
.vtx
.append(spendtx
)
234 block
.hashMerkleRoot
= block
.calc_merkle_root()
237 self
.last_block_time
+= 1
238 yield TestInstance([[block
, False]])
241 self
.test
.clear_all_connections()
244 shutil
.rmtree(self
.options
.tmpdir
+ "/node0")
247 self
.test
.add_all_connections(self
.nodes
)
248 NetworkThread().start()
249 self
.test
.test_nodes
[0].wait_for_verack()
252 for test
in itertools
.chain(
253 self
.test_BIP('csv', 0x20000001, self
.sequence_lock_invalidate
, self
.donothing
, 0),
254 self
.test_BIP('csv', 0x20000001, self
.mtp_invalidate
, self
.donothing
, 0),
255 self
.test_BIP('csv', 0x20000001, self
.donothing
, self
.csv_invalidate
, 0)
259 def donothing(self
, tx
):
262 def csv_invalidate(self
, tx
):
263 """Modify the signature in vin 0 of the tx to fail CSV
264 Prepends -1 CSV DROP in the scriptSig itself.
266 tx
.vin
[0].scriptSig
= CScript([OP_1NEGATE
, OP_CHECKSEQUENCEVERIFY
, OP_DROP
] +
267 list(CScript(tx
.vin
[0].scriptSig
)))
269 def sequence_lock_invalidate(self
, tx
):
270 """Modify the nSequence to make it fails once sequence lock rule is
271 activated (high timespan).
273 tx
.vin
[0].nSequence
= 0x00FFFFFF
276 def mtp_invalidate(self
, tx
):
277 """Modify the nLockTime to make it fails once MTP rule is activated."""
278 # Disable Sequence lock, Activate nLockTime
279 tx
.vin
[0].nSequence
= 0x90FFFFFF
280 tx
.nLockTime
= self
.last_block_time
282 if __name__
== '__main__':
283 BIP9SoftForksTest().main()