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.messages import *
11from test_framework.qtum import *
12import time
13import io
14
15
16class QtumHeaderSpamTest(BitcoinTestFramework):
17    def set_test_params(self):
18        self.setup_clean_chain = True
19        self.num_nodes = 2
20
21    def skip_test_if_missing_module(self):
22        self.skip_if_no_wallet()
23
24    def start_p2p_connection(self):
25        self.p2p_node = self.node.add_p2p_connection(P2PInterface())
26
27    def _remove_from_staking_prevouts(self, block):
28        for j in range(len(self.staking_prevouts)):
29            prevout = self.staking_prevouts[j]
30            if prevout[0].serialize() == block.prevoutStake.serialize():
31                self.staking_prevouts.pop(j)
32                break
33
34    def sign_block_with_standard_private_key(self, block):
35        block_sig_key = ECKey()
36        block_sig_key.set(hash256(struct.pack('<I', 0)), False)
37        block.sign_block(block_sig_key)
38
39    def create_pos_block(self, staking_prevouts, parent_block, parent_block_stake_modifier, block_height, block_reward=2000400000000):
40        coinbase = create_coinbase(block_height+1)
41        coinbase.vout[0].nValue = 0
42        coinbase.vout[0].scriptPubKey = b""
43        coinbase.rehash()
44        block = create_block(parent_block.sha256, coinbase, (parent_block.nTime + 0x10) & 0xfffffff0)
45        if not block.solve_stake(parent_block_stake_modifier, staking_prevouts):
46            return None
47
48        # create a new private key used for block signing.
49        block_sig_key = ECKey()
50        block_sig_key.set(hash256(struct.pack('<I', 0)), False)
51        pubkey = block_sig_key.get_pubkey().get_bytes()
52        scriptPubKey = CScript([pubkey, OP_CHECKSIG])
53        stake_tx_unsigned = CTransaction()
54
55        stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake))
56        stake_tx_unsigned.vout.append(CTxOut())
57        stake_tx_unsigned.vout.append(CTxOut(2000400000000, scriptPubKey))
58
59        stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']
60        f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
61        stake_tx_signed = CTransaction()
62        stake_tx_signed.deserialize(f)
63        block.vtx.append(stake_tx_signed)
64        block.hashMerkleRoot = block.calc_merkle_root()
65        block.sign_block(block_sig_key)
66        return block
67
68    def calculate_stake_modifier(self, parent_block_modifier, current_block):
69        data = b""
70        data += ser_uint256(current_block.sha256 if current_block.prevoutStake.serialize() == COutPoint(0, 0xffffffff).serialize() else current_block.prevoutStake.hash)
71        data += ser_uint256(parent_block_modifier)
72        return uint256_from_str(hash256(data))
73
74    def run_test(self):
75        self.node = self.nodes[0]
76        self.alt_node = self.nodes[1]
77        privkey = byte_to_base58(hash256(struct.pack('<I', 0)), 239)
78        self.node.importprivkey(privkey)
79        self.start_p2p_connection()
80        self.node.setmocktime(int(time.time())-10000)
81        generatesynchronized(self.node, 100+COINBASE_MATURITY, "qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq", self.nodes)
82        self.sync_all()
83        disconnect_nodes(self.node, 1)
84
85        self.node.setmocktime(0)
86        self.staking_prevouts = collect_prevouts(self.node)
87        tip = self.node.getblock(self.node.getbestblockhash())
88        stake_modifier = int(tip['modifier'], 16)
89        block_height = tip['height']
90        block_raw_hex = self.node.getblock(self.node.getbestblockhash(), False)
91        f = io.BytesIO(hex_str_to_bytes(block_raw_hex))
92        block = CBlock()
93        block.deserialize(f)
94        block.rehash()
95
96        # Make sure that first sending a header and then announcing its block succeeds
97        block = self.create_pos_block(self.staking_prevouts, block, stake_modifier, block_height)
98        self._remove_from_staking_prevouts(block)
99        block.rehash()
100        self._remove_from_staking_prevouts(block)
101        self.p2p_node.send_message(msg_headers([CBlockHeader(block)]))
102        time.sleep(0.05)
103        assert(self.node.getblockheader(block.hash))
104        assert_raises_rpc_error(-1, "Block not found on disk", self.node.getblock, block.hash)
105        self.p2p_node.send_message(msg_block(block))
106        time.sleep(0.05)
107        assert(self.node.getblockheader(block.hash))
108        assert(self.node.getblock(block.hash))
109
110        # Make sure that the identical block with only a modified nonce (i.e. the same prevoutStake but different block hash is not accepted)
111        block.nNonce += 1
112        self.sign_block_with_standard_private_key(block)
113        self.p2p_node.send_message(msg_headers([CBlockHeader(block)]))
114        time.sleep(0.05)
115        block.rehash()
116        assert_raises_rpc_error(-5, "Block not found", self.node.getblockheader, block.hash)
117        assert_raises_rpc_error(-5, "Block not found", self.node.getblock, block.hash)
118
119        # Make sure that the chain is still reorgable if presented with a longer chain
120        blocks = [block]
121        child_stake_modifier = self.calculate_stake_modifier(stake_modifier, block)
122        child_block = self.create_pos_block(self.staking_prevouts, block, child_stake_modifier, block_height+1)
123        self._remove_from_staking_prevouts(child_block)
124        child_block.rehash()
125        print(self.alt_node.submitblock(bytes_to_hex_str(block.serialize())))
126        print(self.alt_node.submitblock(bytes_to_hex_str(child_block.serialize())))
127        self.node.setmocktime(child_block.nTime-16)
128        connect_nodes(self.node, 1)
129        time.sleep(1)
130        self.node.setmocktime(0)
131        self.alt_node.generate(1)
132        self.sync_all()
133
134
135if __name__ == '__main__':
136    QtumHeaderSpamTest().main()
137