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.blocktools import * 11from test_framework.address import * 12from test_framework.key import ECKey 13from test_framework.qtumconfig import TIMESTAMP_MASK 14import io 15import struct 16 17class QtumPOSTest(BitcoinTestFramework): 18 def set_test_params(self): 19 self.num_nodes = 1 20 self.setup_clean_chain = True 21 self.extra_args = [[]] 22 self.tip = None 23 24 def skip_test_if_missing_module(self): 25 self.skip_if_no_wallet() 26 27 def bootstrap_p2p(self): 28 """Add a P2P connection to the node. 29 30 Helper to connect and wait for version handshake.""" 31 self.nodes[0].add_p2p_connection(P2PDataStore()) 32 # We need to wait for the initial getheaders from the peer before we 33 # start populating our blockstore. If we don't, then we may run ahead 34 # to the next subtest before we receive the getheaders. We'd then send 35 # an INV for the next block and receive two getheaders - one for the 36 # IBD and one for the INV. We'd respond to both and could get 37 # unexpectedly disconnected if the DoS score for that error is 50. 38 self.nodes[0].p2p.wait_for_getheaders(timeout=5) 39 40 def reconnect_p2p(self): 41 """Tear down and bootstrap the P2P connection to the node. 42 43 The node gets disconnected several times in this test. This helper 44 method reconnects the p2p and restarts the network thread.""" 45 self.nodes[0].disconnect_p2ps() 46 self.bootstrap_p2p() 47 48 49 def sync_all_blocks(self, blocks, success=True, reject_code=None, reject_reason=None, force_send=False, reconnect=False, timeout=5): 50 """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. 51 52 Call with success = False if the tip shouldn't advance to the most recent block.""" 53 self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) 54 55 if reconnect: 56 self.reconnect_p2p() 57 58 def _remove_from_staking_prevouts(self, block): 59 for j in range(len(self.staking_prevouts)): 60 prevout = self.staking_prevouts[j] 61 if prevout[0].serialize() == block.prevoutStake.serialize(): 62 self.staking_prevouts.pop(j) 63 break 64 65 def run_test(self): 66 self.node = self.nodes[0] 67 privkey = byte_to_base58(hash256(struct.pack('<I', 0)), 239) 68 self.node.importprivkey(privkey) 69 self.bootstrap_p2p() 70 # returns a test case that asserts that the current tip was accepted 71 # First generate some blocks so we have some spendable coins 72 block_hashes = self.node.generatetoaddress(100, "qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq") 73 74 for i in range(COINBASE_MATURITY): 75 self.tip = create_block(int(self.node.getbestblockhash(), 16), create_coinbase(self.node.getblockcount()+1), int(time.time())) 76 self.tip.solve() 77 self.sync_all_blocks([self.tip], success=True) 78 79 for _ in range(10): 80 self.node.sendtoaddress("qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq", 1000) 81 block_hashes += self.node.generatetoaddress(1, "qSrM9K6FMhZ29Vkp8Rdk8Jp66bbfpjFETq") 82 83 blocks = [] 84 for block_hash in block_hashes: 85 blocks.append(self.node.getblock(block_hash)) 86 87 88 # These are our staking txs 89 self.staking_prevouts = [] 90 self.bad_vout_staking_prevouts = [] 91 self.bad_txid_staking_prevouts = [] 92 self.unconfirmed_staking_prevouts = [] 93 94 for unspent in self.node.listunspent(): 95 for block in blocks: 96 if unspent['txid'] in block['tx']: 97 tx_block_time = block['time'] 98 break 99 else: 100 assert(False) 101 102 if unspent['confirmations'] > COINBASE_MATURITY: 103 self.staking_prevouts.append((COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount'])*COIN, tx_block_time)) 104 self.bad_vout_staking_prevouts.append((COutPoint(int(unspent['txid'], 16), 0xff), int(unspent['amount'])*COIN, tx_block_time)) 105 self.bad_txid_staking_prevouts.append((COutPoint(int(unspent['txid'], 16)+1, unspent['vout']), int(unspent['amount'])*COIN, tx_block_time)) 106 107 108 if unspent['confirmations'] < COINBASE_MATURITY: 109 self.unconfirmed_staking_prevouts.append((COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount'])*COIN, tx_block_time)) 110 111 112 113 114 # First let 25 seconds pass so that we do not submit blocks directly after the last one 115 #time.sleep(100) 116 block_count = self.node.getblockcount() 117 118 119 # 1 A block that does not have the correct timestamp mask 120 t = int(time.time()) | 1 121 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) 122 self.tip.sign_block(block_sig_key) 123 self.tip.rehash() 124 self.sync_all_blocks([self.tip], success=False, force_send=True, reconnect=True) 125 self._remove_from_staking_prevouts(self.tip) 126 127 128 # 2 A block that with a too high reward 129 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, outNValue=30006) 130 self.tip.sign_block(block_sig_key) 131 self.tip.rehash() 132 self.sync_all_blocks([self.tip], success=False, reconnect=True) 133 self._remove_from_staking_prevouts(self.tip) 134 135 136 # 3 A block with an incorrect block sig 137 bad_key = ECKey() 138 bad_key.set(hash256(b'horse staple battery'), False) 139 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 140 self.tip.sign_block(bad_key) 141 self.tip.rehash() 142 self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True) 143 self._remove_from_staking_prevouts(self.tip) 144 145 146 # 4 A block that stakes with txs with too few confirmations 147 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.unconfirmed_staking_prevouts) 148 self.tip.sign_block(block_sig_key) 149 self.tip.rehash() 150 self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True) 151 self._remove_from_staking_prevouts(self.tip) 152 153 154 # 5 A block that with a coinbase reward 155 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 156 self.tip.vtx[0].vout[0].nValue = 1 157 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 158 self.tip.sign_block(block_sig_key) 159 self.tip.rehash() 160 self.sync_all_blocks([self.tip], success=False, reconnect=True) 161 self._remove_from_staking_prevouts(self.tip) 162 163 164 # 6 A block that with no vout in the coinbase 165 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 166 self.tip.vtx[0].vout = [] 167 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 168 self.tip.sign_block(block_sig_key) 169 self.tip.rehash() 170 self.sync_all_blocks([self.tip], success=False, reconnect=True) 171 self._remove_from_staking_prevouts(self.tip) 172 173 174 # 7 A block way into the future 175 t = (int(time.time())+100) & 0xfffffff0 176 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) 177 self.tip.sign_block(block_sig_key) 178 self.tip.rehash() 179 self.sync_all_blocks([self.tip], success=False, force_send=True) 180 self._remove_from_staking_prevouts(self.tip) 181 182 183 # 8 No vout in the staking tx 184 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 185 self.tip.vtx[1].vout = [] 186 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 187 self.tip.sign_block(block_sig_key) 188 self.tip.rehash() 189 self.sync_all_blocks([self.tip], success=False, reconnect=True) 190 self._remove_from_staking_prevouts(self.tip) 191 192 193 # 9 Unsigned coinstake. 194 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, signStakeTx=False) 195 self.tip.sign_block(block_sig_key) 196 self.tip.rehash() 197 self.sync_all_blocks([self.tip], success=False, reconnect=True) 198 self._remove_from_staking_prevouts(self.tip) 199 200 201 # 10 A block without a coinstake tx. 202 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 203 self.tip.vtx.pop(-1) 204 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 205 self.tip.sign_block(block_sig_key) 206 self.tip.rehash() 207 self.sync_all_blocks([self.tip], success=False, reconnect=True) 208 self._remove_from_staking_prevouts(self.tip) 209 210 211 # 11 A block without a coinbase. 212 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 213 self.tip.vtx.pop(0) 214 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 215 self.tip.sign_block(block_sig_key) 216 self.tip.rehash() 217 self.sync_all_blocks([self.tip], success=False, reconnect=True) 218 self._remove_from_staking_prevouts(self.tip) 219 220 221 # 12 A block where the coinbase has no outputs 222 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 223 self.tip.vtx[0].vout = [] 224 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 225 self.tip.sign_block(block_sig_key) 226 self.tip.rehash() 227 self.sync_all_blocks([self.tip], success=False, reconnect=True) 228 self._remove_from_staking_prevouts(self.tip) 229 230 231 # 13 A block where the coinstake has no outputs 232 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 233 self.tip.vtx[1].vout.pop(-1) 234 self.tip.vtx[1].vout.pop(-1) 235 stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex'] 236 f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) 237 self.tip.vtx[1] = CTransaction() 238 self.tip.vtx[1].deserialize(f) 239 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 240 self.tip.sign_block(block_sig_key) 241 self.tip.rehash() 242 self.sync_all_blocks([self.tip], success=False, reconnect=True) 243 self._remove_from_staking_prevouts(self.tip) 244 245 246 # 14 A block with an incorrect hashStateRoot 247 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 248 self.tip.hashStateRoot = 0xe 249 self.tip.sign_block(block_sig_key) 250 self.tip.rehash() 251 self.sync_all_blocks([self.tip], success=False, reconnect=True) 252 self._remove_from_staking_prevouts(self.tip) 253 254 255 # 15 A block with an incorrect hashUTXORoot 256 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 257 self.tip.hashUTXORoot = 0xe 258 self.tip.sign_block(block_sig_key) 259 self.tip.rehash() 260 self.sync_all_blocks([self.tip], success=False, reconnect=True) 261 self._remove_from_staking_prevouts(self.tip) 262 263 264 # 16 A block with an a signature on wrong header data 265 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 266 self.tip.sign_block(block_sig_key) 267 self.tip.nNonce = 0xfffe 268 self.tip.rehash() 269 self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True) 270 self._remove_from_staking_prevouts(self.tip) 271 272 # 17 A block with where the pubkey of the second output of the coinstake has been modified after block signing 273 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 274 scriptPubKey = self.tip.vtx[1].vout[1].scriptPubKey 275 # Modify a byte of the pubkey 276 self.tip.vtx[1].vout[1].scriptPubKey = scriptPubKey[0:20] + bytes.fromhex(hex(ord(scriptPubKey[20:21])+1)[2:4]) + scriptPubKey[21:] 277 assert_equal(len(scriptPubKey), len(self.tip.vtx[1].vout[1].scriptPubKey)) 278 stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex'] 279 f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) 280 self.tip.vtx[1] = CTransaction() 281 self.tip.vtx[1].deserialize(f) 282 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 283 self.tip.sign_block(block_sig_key) 284 self.tip.rehash() 285 self.sync_all_blocks([self.tip], success=False, reconnect=True) 286 self._remove_from_staking_prevouts(self.tip) 287 288 # 18. A block in the past 289 t = (int(time.time())-700) & 0xfffffff0 290 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) 291 self.tip.sign_block(block_sig_key) 292 self.tip.rehash() 293 self.sync_all_blocks([self.tip], success=False, force_send=True, reconnect=True) 294 self._remove_from_staking_prevouts(self.tip) 295 296 297 # 19. A block with too many coinbase vouts 298 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 299 self.tip.vtx[0].vout.append(CTxOut(0, CScript([OP_TRUE]))) 300 self.tip.vtx[0].rehash() 301 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() 302 self.tip.sign_block(block_sig_key) 303 self.tip.rehash() 304 self.sync_all_blocks([self.tip], success=False, reconnect=True) 305 self._remove_from_staking_prevouts(self.tip) 306 307 308 # 20. A block where the coinstake's vin is not the prevout specified in the block 309 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, coinStakePrevout=self.staking_prevouts[-1][0]) 310 self.tip.sign_block(block_sig_key) 311 self.tip.rehash() 312 self.sync_all_blocks([self.tip], success=False, reconnect=True) 313 self._remove_from_staking_prevouts(self.tip) 314 315 316 # 21. A block that stakes with valid txs but invalid vouts 317 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.bad_vout_staking_prevouts) 318 self.tip.sign_block(block_sig_key) 319 self.tip.rehash() 320 self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True) 321 self._remove_from_staking_prevouts(self.tip) 322 323 324 # 22. A block that stakes with txs that do not exist 325 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.bad_txid_staking_prevouts) 326 self.tip.sign_block(block_sig_key) 327 self.tip.rehash() 328 self.sync_all_blocks([self.tip], success=False, reconnect=True, force_send=True) 329 self._remove_from_staking_prevouts(self.tip) 330 331 332 # Make sure for certain that no blocks were accepted. (This is also to make sure that no segfaults ocurred) 333 assert_equal(self.node.getblockcount(), block_count) 334 335 # And at last, make sure that a valid pos block is accepted 336 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) 337 self.tip.sign_block(block_sig_key) 338 self.tip.rehash() 339 self.sync_all_blocks([self.tip], success=True) 340 assert_equal(self.node.getblockcount(), block_count+1) 341 342 343 344 345 def create_unsigned_pos_block(self, staking_prevouts, nTime=None, outNValue=10002, signStakeTx=True, bestBlockHash=None, coinStakePrevout=None): 346 if not nTime: 347 current_time = int(time.time()) + TIMESTAMP_MASK 348 nTime = current_time & (0xffffffff - TIMESTAMP_MASK) 349 350 if not bestBlockHash: 351 bestBlockHash = self.node.getbestblockhash() 352 block_height = self.node.getblockcount() 353 else: 354 block_height = self.node.getblock(bestBlockHash)['height'] 355 356 parent_block_stake_modifier = int(self.node.getblock(bestBlockHash)['modifier'], 16) 357 parent_block_raw_hex = self.node.getblock(bestBlockHash, False) 358 f = io.BytesIO(hex_str_to_bytes(parent_block_raw_hex)) 359 parent_block = CBlock() 360 parent_block.deserialize(f) 361 coinbase = create_coinbase(block_height+1) 362 coinbase.vout[0].nValue = 0 363 coinbase.vout[0].scriptPubKey = b"" 364 coinbase.rehash() 365 block = create_block(int(bestBlockHash, 16), coinbase, nTime) 366 block.hashPrevBlock = int(bestBlockHash, 16) 367 if not block.solve_stake(parent_block_stake_modifier, staking_prevouts): 368 return None 369 370 # create a new private key used for block signing. 371 block_sig_key = ECKey() 372 block_sig_key.set(hash256(struct.pack('<I', 0)), False) 373 pubkey = block_sig_key.get_pubkey().get_bytes() 374 scriptPubKey = CScript([pubkey, OP_CHECKSIG]) 375 stake_tx_unsigned = CTransaction() 376 377 if not coinStakePrevout: 378 coinStakePrevout = block.prevoutStake 379 380 stake_tx_unsigned.vin.append(CTxIn(coinStakePrevout)) 381 stake_tx_unsigned.vout.append(CTxOut()) 382 stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey)) 383 stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey)) 384 385 if signStakeTx: 386 stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] 387 f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) 388 stake_tx_signed = CTransaction() 389 stake_tx_signed.deserialize(f) 390 block.vtx.append(stake_tx_signed) 391 else: 392 block.vtx.append(stake_tx_unsigned) 393 block.hashMerkleRoot = block.calc_merkle_root() 394 return (block, block_sig_key) 395 396if __name__ == '__main__': 397 QtumPOSTest().main() 398