1#!/usr/bin/env python3
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
6from test_framework.test_framework import BitcoinTestFramework
7from test_framework.util import *
8from test_framework.script import *
9from test_framework.mininode import *
10from test_framework.blocktools import *
11from test_framework.address import *
12from test_framework.key import ECKey
13from test_framework.qtumconfig import TIMESTAMP_MASK
14import io
15import struct
16
17class QtumPOSTest(BitcoinTestFramework):
18    def set_test_params(self):
19        self.num_nodes = 1
20        self.setup_clean_chain = True
21        self.extra_args = [[]]
22        self.tip = None
23
24    def skip_test_if_missing_module(self):
25        self.skip_if_no_wallet()
26
27    def bootstrap_p2p(self):
28        """Add a P2P connection to the node.
29
30        Helper to connect and wait for version handshake."""
31        self.nodes[0].add_p2p_connection(P2PDataStore())
32        # We need to wait for the initial getheaders from the peer before we
33        # start populating our blockstore. If we don't, then we may run ahead
34        # to the next subtest before we receive the getheaders. We'd then send
35        # an INV for the next block and receive two getheaders - one for the
36        # IBD and one for the INV. We'd respond to both and could get
37        # unexpectedly disconnected if the DoS score for that error is 50.
38        self.nodes[0].p2p.wait_for_getheaders(timeout=5)
39
40    def reconnect_p2p(self):
41        """Tear down and bootstrap the P2P connection to the node.
42
43        The node gets disconnected several times in this test. This helper
44        method reconnects the p2p and restarts the network thread."""
45        self.nodes[0].disconnect_p2ps()
46        self.bootstrap_p2p()
47
48
49    def sync_all_blocks(self, blocks, success=True, reject_code=None, reject_reason=None, force_send=False, reconnect=False, timeout=5):
50        """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.
51
52        Call with success = False if the tip shouldn't advance to the most recent block."""
53        self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect)
54
55        if reconnect:
56            self.reconnect_p2p()
57
58    def _remove_from_staking_prevouts(self, block):
59        for j in range(len(self.staking_prevouts)):
60            prevout = self.staking_prevouts[j]
61            if prevout[0].serialize() == block.prevoutStake.serialize():
62                self.staking_prevouts.pop(j)
63                break
64
65    def run_test(self):
66        self.node = self.nodes[0]
67        privkey = byte_to_base58(hash256(struct.pack('<I', 0)), 239)
68        self.node.importprivkey(privkey)
69        self.bootstrap_p2p()
70        # returns a test case that asserts that the current tip was accepted
71        # First generate some blocks so we have some spendable coins
72        block_hashes = self.node.generatetoaddress(100, "qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq")
73
74        for i in range(COINBASE_MATURITY):
75            self.tip = create_block(int(self.node.getbestblockhash(), 16), create_coinbase(self.node.getblockcount()+1), int(time.time()))
76            self.tip.solve()
77            self.sync_all_blocks([self.tip], success=True)
78
79        for _ in range(10):
80            self.node.sendtoaddress("qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq", 1000)
81        block_hashes += self.node.generatetoaddress(1, "qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq")
82
83        blocks = []
84        for block_hash in block_hashes:
85            blocks.append(self.node.getblock(block_hash))
86
87
88        # These are our staking txs
89        self.staking_prevouts = []
90        self.bad_vout_staking_prevouts = []
91        self.bad_txid_staking_prevouts = []
92        self.unconfirmed_staking_prevouts = []
93
94        for unspent in self.node.listunspent():
95            for block in blocks:
96                if unspent['txid'] in block['tx']:
97                    tx_block_time = block['time']
98                    break
99            else:
100                assert(False)
101
102            if unspent['confirmations'] > COINBASE_MATURITY:
103                self.staking_prevouts.append((COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount'])*COIN, tx_block_time))
104                self.bad_vout_staking_prevouts.append((COutPoint(int(unspent['txid'], 16), 0xff), int(unspent['amount'])*COIN, tx_block_time))
105                self.bad_txid_staking_prevouts.append((COutPoint(int(unspent['txid'], 16)+1, unspent['vout']), int(unspent['amount'])*COIN, tx_block_time))
106
107
108            if unspent['confirmations'] < COINBASE_MATURITY:
109                self.unconfirmed_staking_prevouts.append((COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount'])*COIN, tx_block_time))
110
111
112
113
114        # First let 25 seconds pass so that we do not submit blocks directly after the last one
115        #time.sleep(100)
116        block_count = self.node.getblockcount()
117
118
119        # 1 A block that does not have the correct timestamp mask
120        t = int(time.time()) | 1
121        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t)
122        self.tip.sign_block(block_sig_key)
123        self.tip.rehash()
124        self.sync_all_blocks([self.tip], success=False, force_send=True, reconnect=True)
125        self._remove_from_staking_prevouts(self.tip)
126
127
128        # 2 A block that with a too high reward
129        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, outNValue=30006)
130        self.tip.sign_block(block_sig_key)
131        self.tip.rehash()
132        self.sync_all_blocks([self.tip], success=False, reconnect=True)
133        self._remove_from_staking_prevouts(self.tip)
134
135
136        # 3 A block with an incorrect block sig
137        bad_key = ECKey()
138        bad_key.set(hash256(b'horse staple battery'), False)
139        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
140        self.tip.sign_block(bad_key)
141        self.tip.rehash()
142        self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True)
143        self._remove_from_staking_prevouts(self.tip)
144
145
146        # 4 A block that stakes with txs with too few confirmations
147        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.unconfirmed_staking_prevouts)
148        self.tip.sign_block(block_sig_key)
149        self.tip.rehash()
150        self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True)
151        self._remove_from_staking_prevouts(self.tip)
152
153
154        # 5 A block that with a coinbase reward
155        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
156        self.tip.vtx[0].vout[0].nValue = 1
157        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
158        self.tip.sign_block(block_sig_key)
159        self.tip.rehash()
160        self.sync_all_blocks([self.tip], success=False, reconnect=True)
161        self._remove_from_staking_prevouts(self.tip)
162
163
164        # 6 A block that with no vout in the coinbase
165        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
166        self.tip.vtx[0].vout = []
167        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
168        self.tip.sign_block(block_sig_key)
169        self.tip.rehash()
170        self.sync_all_blocks([self.tip], success=False, reconnect=True)
171        self._remove_from_staking_prevouts(self.tip)
172
173
174        # 7 A block way into the future
175        t = (int(time.time())+100) & 0xfffffff0
176        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t)
177        self.tip.sign_block(block_sig_key)
178        self.tip.rehash()
179        self.sync_all_blocks([self.tip], success=False, force_send=True)
180        self._remove_from_staking_prevouts(self.tip)
181
182
183        # 8 No vout in the staking tx
184        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
185        self.tip.vtx[1].vout = []
186        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
187        self.tip.sign_block(block_sig_key)
188        self.tip.rehash()
189        self.sync_all_blocks([self.tip], success=False, reconnect=True)
190        self._remove_from_staking_prevouts(self.tip)
191
192
193        # 9 Unsigned coinstake.
194        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, signStakeTx=False)
195        self.tip.sign_block(block_sig_key)
196        self.tip.rehash()
197        self.sync_all_blocks([self.tip], success=False, reconnect=True)
198        self._remove_from_staking_prevouts(self.tip)
199
200
201        # 10 A block without a coinstake tx.
202        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
203        self.tip.vtx.pop(-1)
204        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
205        self.tip.sign_block(block_sig_key)
206        self.tip.rehash()
207        self.sync_all_blocks([self.tip], success=False, reconnect=True)
208        self._remove_from_staking_prevouts(self.tip)
209
210
211        # 11 A block without a coinbase.
212        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
213        self.tip.vtx.pop(0)
214        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
215        self.tip.sign_block(block_sig_key)
216        self.tip.rehash()
217        self.sync_all_blocks([self.tip], success=False, reconnect=True)
218        self._remove_from_staking_prevouts(self.tip)
219
220
221        # 12 A block where the coinbase has no outputs
222        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
223        self.tip.vtx[0].vout = []
224        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
225        self.tip.sign_block(block_sig_key)
226        self.tip.rehash()
227        self.sync_all_blocks([self.tip], success=False, reconnect=True)
228        self._remove_from_staking_prevouts(self.tip)
229
230
231        # 13 A block where the coinstake has no outputs
232        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
233        self.tip.vtx[1].vout.pop(-1)
234        self.tip.vtx[1].vout.pop(-1)
235        stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex']
236        f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
237        self.tip.vtx[1] = CTransaction()
238        self.tip.vtx[1].deserialize(f)
239        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
240        self.tip.sign_block(block_sig_key)
241        self.tip.rehash()
242        self.sync_all_blocks([self.tip], success=False, reconnect=True)
243        self._remove_from_staking_prevouts(self.tip)
244
245
246        # 14 A block with an incorrect hashStateRoot
247        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
248        self.tip.hashStateRoot = 0xe
249        self.tip.sign_block(block_sig_key)
250        self.tip.rehash()
251        self.sync_all_blocks([self.tip], success=False, reconnect=True)
252        self._remove_from_staking_prevouts(self.tip)
253
254
255        # 15 A block with an incorrect hashUTXORoot
256        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
257        self.tip.hashUTXORoot = 0xe
258        self.tip.sign_block(block_sig_key)
259        self.tip.rehash()
260        self.sync_all_blocks([self.tip], success=False, reconnect=True)
261        self._remove_from_staking_prevouts(self.tip)
262
263
264        # 16 A block with an a signature on wrong header data
265        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
266        self.tip.sign_block(block_sig_key)
267        self.tip.nNonce = 0xfffe
268        self.tip.rehash()
269        self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True)
270        self._remove_from_staking_prevouts(self.tip)
271
272        # 17 A block with where the pubkey of the second output of the coinstake has been modified after block signing
273        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
274        scriptPubKey = self.tip.vtx[1].vout[1].scriptPubKey
275        # Modify a byte of the pubkey
276        self.tip.vtx[1].vout[1].scriptPubKey = scriptPubKey[0:20] + bytes.fromhex(hex(ord(scriptPubKey[20:21])+1)[2:4]) + scriptPubKey[21:]
277        assert_equal(len(scriptPubKey), len(self.tip.vtx[1].vout[1].scriptPubKey))
278        stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex']
279        f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
280        self.tip.vtx[1] = CTransaction()
281        self.tip.vtx[1].deserialize(f)
282        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
283        self.tip.sign_block(block_sig_key)
284        self.tip.rehash()
285        self.sync_all_blocks([self.tip], success=False, reconnect=True)
286        self._remove_from_staking_prevouts(self.tip)
287
288        # 18. A block in the past
289        t = (int(time.time())-700) & 0xfffffff0
290        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t)
291        self.tip.sign_block(block_sig_key)
292        self.tip.rehash()
293        self.sync_all_blocks([self.tip], success=False, force_send=True, reconnect=True)
294        self._remove_from_staking_prevouts(self.tip)
295
296
297        # 19. A block with too many coinbase vouts
298        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
299        self.tip.vtx[0].vout.append(CTxOut(0, CScript([OP_TRUE])))
300        self.tip.vtx[0].rehash()
301        self.tip.hashMerkleRoot = self.tip.calc_merkle_root()
302        self.tip.sign_block(block_sig_key)
303        self.tip.rehash()
304        self.sync_all_blocks([self.tip], success=False, reconnect=True)
305        self._remove_from_staking_prevouts(self.tip)
306
307
308        # 20. A block where the coinstake's vin is not the prevout specified in the block
309        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, coinStakePrevout=self.staking_prevouts[-1][0])
310        self.tip.sign_block(block_sig_key)
311        self.tip.rehash()
312        self.sync_all_blocks([self.tip], success=False, reconnect=True)
313        self._remove_from_staking_prevouts(self.tip)
314
315
316        # 21. A block that stakes with valid txs but invalid vouts
317        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.bad_vout_staking_prevouts)
318        self.tip.sign_block(block_sig_key)
319        self.tip.rehash()
320        self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True)
321        self._remove_from_staking_prevouts(self.tip)
322
323
324        # 22. A block that stakes with txs that do not exist
325        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.bad_txid_staking_prevouts)
326        self.tip.sign_block(block_sig_key)
327        self.tip.rehash()
328        self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True)
329        self._remove_from_staking_prevouts(self.tip)
330
331
332        # Make sure for certain that no blocks were accepted. (This is also to make sure that no segfaults ocurred)
333        assert_equal(self.node.getblockcount(), block_count)
334
335        # And at last, make sure that a valid pos block is accepted
336        (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts)
337        self.tip.sign_block(block_sig_key)
338        self.tip.rehash()
339        self.sync_all_blocks([self.tip], success=True)
340        assert_equal(self.node.getblockcount(), block_count+1)
341
342
343
344
345    def create_unsigned_pos_block(self, staking_prevouts, nTime=None, outNValue=10002, signStakeTx=True, bestBlockHash=None, coinStakePrevout=None):
346        if not nTime:
347            current_time = int(time.time()) + TIMESTAMP_MASK
348            nTime = current_time & (0xffffffff - TIMESTAMP_MASK)
349
350        if not bestBlockHash:
351            bestBlockHash = self.node.getbestblockhash()
352            block_height = self.node.getblockcount()
353        else:
354            block_height = self.node.getblock(bestBlockHash)['height']
355
356        parent_block_stake_modifier = int(self.node.getblock(bestBlockHash)['modifier'], 16)
357        parent_block_raw_hex = self.node.getblock(bestBlockHash, False)
358        f = io.BytesIO(hex_str_to_bytes(parent_block_raw_hex))
359        parent_block = CBlock()
360        parent_block.deserialize(f)
361        coinbase = create_coinbase(block_height+1)
362        coinbase.vout[0].nValue = 0
363        coinbase.vout[0].scriptPubKey = b""
364        coinbase.rehash()
365        block = create_block(int(bestBlockHash, 16), coinbase, nTime)
366        block.hashPrevBlock = int(bestBlockHash, 16)
367        if not block.solve_stake(parent_block_stake_modifier, staking_prevouts):
368            return None
369
370        # create a new private key used for block signing.
371        block_sig_key = ECKey()
372        block_sig_key.set(hash256(struct.pack('<I', 0)), False)
373        pubkey = block_sig_key.get_pubkey().get_bytes()
374        scriptPubKey = CScript([pubkey, OP_CHECKSIG])
375        stake_tx_unsigned = CTransaction()
376
377        if not coinStakePrevout:
378            coinStakePrevout = block.prevoutStake
379
380        stake_tx_unsigned.vin.append(CTxIn(coinStakePrevout))
381        stake_tx_unsigned.vout.append(CTxOut())
382        stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey))
383        stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey))
384
385        if signStakeTx:
386            stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']
387            f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
388            stake_tx_signed = CTransaction()
389            stake_tx_signed.deserialize(f)
390            block.vtx.append(stake_tx_signed)
391        else:
392            block.vtx.append(stake_tx_unsigned)
393        block.hashMerkleRoot = block.calc_merkle_root()
394        return (block, block_sig_key)
395
396if __name__ == '__main__':
397    QtumPOSTest().main()
398