1 #!/usr/bin/env python3
2 # Copyright (c) 2014-2022 The Limenka 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 7 Test logic for skipping signature validation on blocks which we've assumed
8 valid (https://github.com/limenka/limenka/pull/9484)
9 10 We build a chain that includes and invalid signature for one of the
11 transactions:
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 22 Start 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 """
32 33 from test_framework.blocktools import (
34 COINBASE_MATURITY,
35 create_block,
36 create_coinbase,
37 )
38 from test_framework.messages import (
39 CBlockHeader,
40 COutPoint,
41 CTransaction,
42 CTxIn,
43 CTxOut,
44 msg_block,
45 msg_headers,
46 )
47 from test_framework.p2p import P2PInterface
48 from test_framework.script import (
49 CScript,
50 OP_TRUE,
51 )
52 from test_framework.test_framework import LimenkaTestFramework
53 from test_framework.util import assert_equal
54 from test_framework.wallet_util import generate_keypair
55 56 57 class BaseNode(P2PInterface):
58 def send_header_for_blocks(self, new_blocks):
59 headers_message = msg_headers()
60 headers_message.headers = [CBlockHeader(b) for b in new_blocks]
61 self.send_message(headers_message)
62 63 64 class AssumeValidTest(LimenkaTestFramework):
65 def set_test_params(self):
66 self.setup_clean_chain = True
67 self.num_nodes = 3
68 self.rpc_timeout = 120
69 70 def setup_network(self):
71 self.add_nodes(3)
72 # Start node0. We don't start the other nodes yet since
73 # we need to pre-mine a block with an invalid transaction
74 # signature so we can pass in the block hash as assumevalid.
75 self.start_node(0)
76 77 def send_blocks_until_disconnected(self, p2p_conn):
78 """Keep sending blocks to the node until we're disconnected."""
79 for i in range(len(self.blocks)):
80 if not p2p_conn.is_connected:
81 break
82 try:
83 p2p_conn.send_message(msg_block(self.blocks[i]))
84 except IOError:
85 assert not p2p_conn.is_connected
86 break
87 88 def run_test(self):
89 # Build the blockchain
90 self.tip = int(self.nodes[0].getbestblockhash(), 16)
91 self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
92 93 self.blocks = []
94 95 # Get a pubkey for the coinbase TXO
96 _, coinbase_pubkey = generate_keypair()
97 98 # Create the first block with a coinbase output to our key
99 height = 1
100 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time)
101 self.blocks.append(block)
102 self.block_time += 1
103 block.solve()
104 # Save the coinbase for later
105 self.block1 = block
106 self.tip = block.sha256
107 height += 1
108 109 # Bury the block 100 deep so the coinbase output is spendable
110 for _ in range(100):
111 block = create_block(self.tip, create_coinbase(height), self.block_time)
112 block.solve()
113 self.blocks.append(block)
114 self.tip = block.sha256
115 self.block_time += 1
116 height += 1
117 118 # Create a transaction spending the coinbase output with an invalid (null) signature
119 tx = CTransaction()
120 tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
121 tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
122 tx.calc_sha256()
123 124 block102 = create_block(self.tip, create_coinbase(height), self.block_time, txlist=[tx])
125 self.block_time += 1
126 block102.solve()
127 self.blocks.append(block102)
128 self.tip = block102.sha256
129 self.block_time += 1
130 height += 1
131 132 # Bury the assumed valid block 2100 deep
133 for _ in range(2100):
134 block = create_block(self.tip, create_coinbase(height), self.block_time)
135 block.solve()
136 self.blocks.append(block)
137 self.tip = block.sha256
138 self.block_time += 1
139 height += 1
140 141 # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
142 self.start_node(1, extra_args=["-assumevalid=" + block102.hash])
143 self.start_node(2, extra_args=["-assumevalid=" + block102.hash])
144 145 p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
146 p2p0.send_header_for_blocks(self.blocks[0:2000])
147 p2p0.send_header_for_blocks(self.blocks[2000:])
148 149 # Send blocks to node0. Block 102 will be rejected.
150 self.send_blocks_until_disconnected(p2p0)
151 self.wait_until(lambda: self.nodes[0].getblockcount() >= COINBASE_MATURITY + 1)
152 assert_equal(self.nodes[0].getblockcount(), COINBASE_MATURITY + 1)
153 154 p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
155 p2p1.send_header_for_blocks(self.blocks[0:2000])
156 p2p1.send_header_for_blocks(self.blocks[2000:])
157 158 # Send all blocks to node1. All blocks will be accepted.
159 for i in range(2202):
160 p2p1.send_message(msg_block(self.blocks[i]))
161 # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
162 p2p1.sync_with_ping(timeout=960)
163 assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)
164 165 p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
166 p2p2.send_header_for_blocks(self.blocks[0:200])
167 168 # Send blocks to node2. Block 102 will be rejected.
169 self.send_blocks_until_disconnected(p2p2)
170 self.wait_until(lambda: self.nodes[2].getblockcount() >= COINBASE_MATURITY + 1)
171 assert_equal(self.nodes[2].getblockcount(), COINBASE_MATURITY + 1)
172 173 174 if __name__ == '__main__':
175 AssumeValidTest(__file__).main()
176