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