1#!/usr/bin/env python3
2# Copyright (c) 2014-2018 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 logic for skipping signature validation on old blocks.
6
7Test logic for skipping signature validation on blocks which we've assumed
8valid (https://github.com/bitcoin/bitcoin/pull/9484)
9
10We build a chain that includes and invalid signature for one of the
11transactions:
12
13    0:        genesis block
14    1:        block 1 with coinbase transaction output.
15    2-101:    bury that block with 100 blocks so the coinbase transaction
16              output can be spent
17    102:      a block containing a transaction spending the coinbase
18              transaction output. The transaction has an invalid signature.
19    103-2202: bury the bad block with just over two weeks' worth of blocks
20              (2100 blocks)
21
22Start three nodes:
23
24    - node0 has no -assumevalid parameter. Try to sync to block 2202. It will
25      reject block 102 and only sync as far as block 101
26    - node1 has -assumevalid set to the hash of block 102. Try to sync to
27      block 2202. node1 will sync all the way to block 2202.
28    - node2 has -assumevalid set to the hash of block 102. Try to sync to
29      block 200. node2 will reject block 102 since it's assumed valid, but it
30      isn't buried by at least two weeks' work.
31"""
32import time
33
34from test_framework.blocktools import (create_block, create_coinbase)
35from test_framework.key import ECKey
36from test_framework.messages import (
37    CBlockHeader,
38    COutPoint,
39    CTransaction,
40    CTxIn,
41    CTxOut,
42    msg_block,
43    msg_headers
44)
45from test_framework.mininode import P2PInterface
46from test_framework.script import (CScript, OP_TRUE)
47from test_framework.test_framework import BitcoinTestFramework, SkipTest
48from test_framework.util import assert_equal
49
50class BaseNode(P2PInterface):
51    def send_header_for_blocks(self, new_blocks):
52        headers_message = msg_headers()
53        headers_message.headers = [CBlockHeader(b) for b in new_blocks]
54        self.send_message(headers_message)
55
56class AssumeValidTest(BitcoinTestFramework):
57    def set_test_params(self):
58        self.setup_clean_chain = True
59        self.num_nodes = 3
60
61    def setup_network(self):
62        self.add_nodes(3)
63        # Start node0. We don't start the other nodes yet since
64        # we need to pre-mine a block with an invalid transaction
65        # signature so we can pass in the block hash as assumevalid.
66        self.start_node(0)
67
68    def send_blocks_until_disconnected(self, p2p_conn):
69        """Keep sending blocks to the node until we're disconnected."""
70        for i in range(len(self.blocks)):
71            if not p2p_conn.is_connected:
72                break
73            try:
74                p2p_conn.send_message(msg_block(self.blocks[i]))
75            except IOError as e:
76                assert not p2p_conn.is_connected
77                break
78
79    def assert_blockchain_height(self, node, height):
80        """Wait until the blockchain is no longer advancing and verify it's reached the expected height."""
81        last_height = node.getblock(node.getbestblockhash())['height']
82        timeout = 10
83        while True:
84            time.sleep(0.25)
85            current_height = node.getblock(node.getbestblockhash())['height']
86            if current_height != last_height:
87                last_height = current_height
88                if timeout < 0:
89                    assert False, "blockchain too short after timeout: %d" % current_height
90                timeout - 0.25
91                continue
92            elif current_height > height:
93                assert False, "blockchain too long: %d" % current_height
94            elif current_height == height:
95                break
96
97    def run_test(self):
98        if True:
99            raise SkipTest("TO-DO")
100
101        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
102
103        # Build the blockchain
104        self.tip = int(self.nodes[0].getbestblockhash(), 16)
105        self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
106
107        self.blocks = []
108
109        # Get a pubkey for the coinbase TXO
110        coinbase_key = ECKey()
111        coinbase_key.generate()
112        coinbase_pubkey = coinbase_key.get_pubkey().get_bytes()
113
114        # Create the first block with a coinbase output to our key
115        height = 1
116        block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time, version=0x20000000)
117        self.blocks.append(block)
118        self.block_time += 1
119        block.solve()
120        # Save the coinbase for later
121        self.block1 = block
122        self.tip = block.sha256
123        height += 1
124
125        # Bury the block 100 deep so the coinbase output is spendable
126        for i in range(100):
127            block = create_block(self.tip, create_coinbase(height), self.block_time, version=0x20000000)
128            block.solve()
129            self.blocks.append(block)
130            self.tip = block.sha256
131            self.block_time += 1
132            height += 1
133
134        # Create a transaction spending the coinbase output with an invalid (null) signature
135        tx = CTransaction()
136        tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
137        tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
138        tx.calc_sha256()
139
140        block102 = create_block(self.tip, create_coinbase(height), self.block_time, version=0x20000000)
141        self.block_time += 1
142        block102.vtx.extend([tx])
143        block102.hashMerkleRoot = block102.calc_merkle_root()
144        block102.rehash()
145        block102.solve()
146        self.blocks.append(block102)
147        self.tip = block102.sha256
148        self.block_time += 1
149        height += 1
150
151        # Bury the assumed valid block 2100 deep
152        for i in range(2100):
153            block = create_block(self.tip, create_coinbase(height), self.block_time)
154            block.nVersion = 0x20000002
155            block.solve()
156            self.blocks.append(block)
157            self.tip = block.sha256
158            self.block_time += 1
159            height += 1
160
161        self.nodes[0].disconnect_p2ps()
162
163        # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
164        self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)])
165        self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)])
166
167        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
168        p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
169        p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
170
171        # send header lists to all three nodes
172        p2p0.send_header_for_blocks(self.blocks[0:2000])
173        p2p0.send_header_for_blocks(self.blocks[2000:])
174        p2p1.send_header_for_blocks(self.blocks[0:2000])
175        p2p1.send_header_for_blocks(self.blocks[2000:])
176        p2p2.send_header_for_blocks(self.blocks[0:200])
177
178        # Send blocks to node0. Block 102 will be rejected.
179        self.send_blocks_until_disconnected(p2p0)
180        self.assert_blockchain_height(self.nodes[0], 101)
181
182        # Send all blocks to node1. All blocks will be accepted.
183        for i in range(2202):
184            p2p1.send_message(msg_block(self.blocks[i]))
185        # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
186        p2p1.sync_with_ping(200)
187        assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)
188
189        # Send blocks to node2. Block 102 will be rejected.
190        self.send_blocks_until_disconnected(p2p2)
191        self.assert_blockchain_height(self.nodes[2], 101)
192
193if __name__ == '__main__':
194    AssumeValidTest().main()
195