1#!/usr/bin/env python3
2
3from test_framework.test_framework import BitcoinTestFramework
4from test_framework.util import *
5from test_framework.script import *
6from test_framework.mininode import *
7from test_framework.qtum import *
8from test_framework.blocktools import *
9import time
10import io
11
12class QtumIdenticalRefunds(BitcoinTestFramework):
13    def set_test_params(self):
14        self.setup_clean_chain = True
15        self.num_nodes = 1
16
17    def skip_test_if_missing_module(self):
18        self.skip_if_no_wallet()
19
20    def run_test(self):
21        self.node = self.nodes[0]
22        self.node.generate(100+COINBASE_MATURITY)
23        """
24        contract Test {
25            function() payable {}
26        }
27        """
28        code = "60606040523415600e57600080fd5b5b603980601c6000396000f30060606040525b600b5b5b565b0000a165627a7a72305820e28f464040162c6b98c6be21a9de622f2c8f102259a90476df697d0beb9ac9880029"
29        contract_address = self.node.createcontract(code)['address']
30        self.node.generate(1)
31
32        # Send 2 evm txs that will call the fallback function of the Test contract with the same sender.
33        # This will result in the same amount of gas being spent and thus the same amount being refunded.
34        sender_address = self.node.getnewaddress()
35        self.node.sendtoaddress(sender_address, 1)
36        self.node.sendtocontract(contract_address, "00", 0, 1000000, QTUM_MIN_GAS_PRICE_STR, sender_address)
37        self.node.sendtoaddress(sender_address, 1)
38        self.node.sendtocontract(contract_address, "00", 0, 1000000, QTUM_MIN_GAS_PRICE_STR, sender_address)
39
40        # Check that all txs were accepted into the mempool.
41        assert_equal(len(self.node.getrawmempool()), 4)
42        block_hash = self.node.generate(1)[0]
43
44        # Verify that all txs were included in the block.
45        assert_equal(len(self.node.getrawmempool()), 0)
46
47        # Fetch the last generated block containing the txs and refund outputs and deserialize it.
48        manipulation_block = CBlock()
49        block_raw = self.node.getblock(block_hash, False)
50        f = io.BytesIO(hex_str_to_bytes(block_raw))
51        manipulation_block.deserialize(f)
52
53        # Make sure that all expected outputs are present in the coinbase
54        assert_equal(len(manipulation_block.vtx[0].vout), 4)
55        assert_equal(manipulation_block.vtx[0].vout[-2].scriptPubKey == manipulation_block.vtx[0].vout[0].scriptPubKey, False)
56
57        # Now we change the last refund output's scriptPubKey to the miner's scriptPubKey.
58        manipulation_block.vtx[0].vout[-2].scriptPubKey = manipulation_block.vtx[0].vout[0].scriptPubKey
59        manipulation_block.hashMerkleRoot = manipulation_block.calc_merkle_root()
60        manipulation_block.solve()
61
62        # Invalidate the last block so that we can submit the manipulated block.
63        self.node.invalidateblock(self.node.getbestblockhash())
64
65        block_count = self.node.getblockcount()
66
67        # Resubmit the manipulated block.
68        ret = self.node.submitblock(bytes_to_hex_str(manipulation_block.serialize()))
69
70        # Check that the block was not accepted
71        assert_equal(ret is None, False)
72
73        # Make sure that the block was not accepted.
74        assert_equal(self.node.getblockcount(), block_count)
75
76if __name__ == '__main__':
77    QtumIdenticalRefunds().main()
78