def create_segwit_fund_and_spend_tx(spend, case0=False): if not case0: # Spending from a P2SH-P2WPKH coin, # txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 redeem_script0 = bytearray.fromhex( '0014fcf9969ce1c98a135ed293719721fb69f0b686cb') # Spending from a P2SH-P2WSH coin, # txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f redeem_script1 = bytearray.fromhex( '0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4' ) else: redeem_script0 = bytearray.fromhex('51020000') redeem_script1 = bytearray.fromhex('53020080') redeem_scripts = [redeem_script0, redeem_script1] # Fund transaction to segwit addresses txfund = CTransaction() txfund.vin = [CTxIn(COutPoint(spend.tx.sha256, spend.n))] amount = (50 * COIN - 1000) // len(redeem_scripts) for redeem_script in redeem_scripts: txfund.vout.append( CTxOut( amount, CScript([OP_HASH160, hash160(redeem_script), OP_EQUAL]))) txfund.rehash() # Segwit spending transaction # We'll test if a node that checks for standardness accepts this # txn. It should fail exclusively because of the restriction in # the scriptSig (non clean stack..), so all other characteristcs # must pass standardness checks. For this reason, we create # standard P2SH outputs. txspend = CTransaction() for i in range(len(redeem_scripts)): txspend.vin.append( CTxIn(COutPoint(txfund.sha256, i), CScript([redeem_scripts[i]]))) txspend.vout = [ CTxOut( 50 * COIN - 2000, CScript( [OP_HASH160, hash160(CScript([OP_TRUE])), OP_EQUAL])) ] txspend.rehash() return txfund, txspend
def run_test(self): node = self.nodes[0] self.pynode = P2PDataStore() self.connection = NodeConn('127.0.0.1', p2p_port(0), node, self.pynode) self.pynode.add_connection(self.connection) NetworkThread().start() self.pynode.wait_for_verack() # Get out of IBD node.generate(1) tip = self.getbestblock(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(20): tip = self.build_block(tip) blocks.append(tip) self.pynode.send_blocks_and_test(blocks, node, success=True) self.spendable_outputs = deque(block.vtx[0] for block in blocks) logging.info("Mature the blocks.") node.generate(100) tip = self.getbestblock(node) # To make compact and fast-to-verify transactions, we'll use # CHECKDATASIG over and over with the same data. # (Using the same stuff over and over again means we get to hit the # node's signature cache and don't need to make new signatures every # time.) cds_message = b'' # r=1 and s=1 ecdsa, the minimum values. cds_signature = bytes.fromhex('3006020101020101') # Recovered pubkey cds_pubkey = bytes.fromhex( '03089b476b570d66fad5a20ae6188ebbaf793a4c2a228c65f3d79ee8111d56c932' ) def minefunding2(n): """ Mine a block with a bunch of outputs that are very dense sigchecks when spent (2 sigchecks each); return the inputs that can be used to spend. """ cds_scriptpubkey = CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]) # The scriptsig is carefully padded to have size 26, which is the # shortest allowed for 2 sigchecks for mempool admission. # The resulting inputs have size 67 bytes, 33.5 bytes/sigcheck. cds_scriptsig = CScript([b'x' * 16, cds_signature]) assert_equal(len(cds_scriptsig), 26) logging.debug( "Gen {} with locking script {} unlocking script {} .".format( n, cds_scriptpubkey.hex(), cds_scriptsig.hex())) tx = self.spendable_outputs.popleft() usable_inputs = [] txes = [] for i in range(n): tx = create_transaction(tx, cds_scriptpubkey, bytes([OP_TRUE]) if i == 0 else b"") txes.append(tx) usable_inputs.append( CTxIn(COutPoint(tx.sha256, 1), cds_scriptsig)) newtip = self.build_block(tip, txes) self.pynode.send_blocks_and_test([newtip], node, timeout=10) return usable_inputs, newtip logging.info("Funding special coins that have high sigchecks") # mine 5000 funded outputs (10000 sigchecks) # will be used pre-activation and post-activation usable_inputs, tip = minefunding2(5000) # assemble them into 50 txes with 100 inputs each (200 sigchecks) submittxes_1 = [] while len(usable_inputs) >= 100: tx = CTransaction() tx.vin = [usable_inputs.pop() for _ in range(100)] tx.vout = [CTxOut(0, CScript([OP_RETURN]))] tx.rehash() submittxes_1.append(tx) # mine 5000 funded outputs (10000 sigchecks) # will be used post-activation usable_inputs, tip = minefunding2(5000) # assemble them into 50 txes with 100 inputs each (200 sigchecks) submittxes_2 = [] while len(usable_inputs) >= 100: tx = CTransaction() tx.vin = [usable_inputs.pop() for _ in range(100)] tx.vout = [CTxOut(0, CScript([OP_RETURN]))] tx.rehash() submittxes_2.append(tx) # Check high sigcheck transactions logging.info("Create transaction that have high sigchecks") fundings = [] def make_spend(sigcheckcount): # Add a funding tx to fundings, and return a tx spending that using # scriptsig. logging.debug("Gen tx with {} sigchecks.".format(sigcheckcount)) def get_script_with_sigcheck(count): return CScript([cds_message, cds_pubkey] + (count - 1) * [OP_3DUP, OP_CHECKDATASIGVERIFY] + [OP_CHECKDATASIG]) # get funds locked with OP_1 sourcetx = self.spendable_outputs.popleft() # make funding that forwards to scriptpubkey last_sigcheck_count = ((sigcheckcount - 1) % 30) + 1 fundtx = create_transaction( sourcetx, get_script_with_sigcheck(last_sigcheck_count)) fill_sigcheck_script = get_script_with_sigcheck(30) remaining_sigcheck = sigcheckcount while remaining_sigcheck > 30: fundtx.vout[0].nValue -= 1000 fundtx.vout.append(CTxOut(100, bytes(fill_sigcheck_script))) remaining_sigcheck -= 30 fundtx.rehash() fundings.append(fundtx) # make the spending scriptsig = CScript([cds_signature]) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(fundtx.sha256, 1), scriptsig)) input_index = 2 remaining_sigcheck = sigcheckcount while remaining_sigcheck > 30: tx.vin.append( CTxIn(COutPoint(fundtx.sha256, input_index), scriptsig)) remaining_sigcheck -= 30 input_index += 1 tx.vout.append(CTxOut(0, CScript([OP_RETURN]))) pad_tx(tx) tx.rehash() return tx # Create transactions with many sigchecks. good_tx = make_spend(MAX_TX_SIGCHECK) bad_tx = make_spend(MAX_TX_SIGCHECK + 1) tip = self.build_block(tip, fundings) self.pynode.send_blocks_and_test([tip], node) # Both tx are accepted before the activation. pre_activation_sigcheck_block = self.build_block( tip, [good_tx, bad_tx]) self.pynode.send_blocks_and_test([pre_activation_sigcheck_block], node) node.invalidateblock(pre_activation_sigcheck_block.hash) # after block is invalidated these tx are put back into the mempool. Test uses them later so evict. waitFor(10, lambda: node.getmempoolinfo()["size"] == 2) node.evicttransaction(good_tx.hash) node.evicttransaction(bad_tx.hash) # Activation tests logging.info("Approach to just before upgrade activation") # Move our clock to the uprade time so we will accept such # future-timestamped blocks. node.setmocktime(SIGCHECKS_ACTIVATION_TIME + 10) # Mine six blocks with timestamp starting at # SIGCHECKS_ACTIVATION_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=SIGCHECKS_ACTIVATION_TIME + i) blocks.append(tip) self.pynode.send_blocks_and_test(blocks, node, timeout=TIMEOUT) assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME - 1) logging.info( "The next block will activate, but the activation block itself must follow old rules" ) # Send the 50 txes and get the node to mine as many as possible (it should do all) # The node is happy mining and validating a 10000 sigcheck block before # activation. self.pynode.send_txs_and_test(submittxes_1, node, timeout=TIMEOUT) [blockhash] = node.generate(1) assert_equal(set(node.getblock(blockhash, 1)["tx"][1:]), {t.hash for t in submittxes_1}) # We have activated, but let's invalidate that. assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME) node.invalidateblock(blockhash) # Try again manually and invalidate that too goodblock = self.build_block(tip, submittxes_1) self.pynode.send_blocks_and_test([goodblock], node, timeout=TIMEOUT) node.invalidateblock(goodblock.hash) # All transactions should be back in mempool: validation is very slow in debug build waitFor( 60, lambda: set(node.getrawmempool()) == {t.hash for t in submittxes_1}) logging.info("Mine the activation block itself") tip = self.build_block(tip) self.pynode.send_blocks_and_test([tip], node, timeout=TIMEOUT) logging.info("We have activated!") assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME) # All transactions get re-evaluated to count sigchecks, so wait for them waitFor( 60, lambda: set(node.getrawmempool()) == {t.hash for t in submittxes_1}) logging.info( "Try a block with a transaction going over the limit (limit: {})". format(MAX_TX_SIGCHECK)) bad_tx_block = self.build_block(tip, [bad_tx]) check_for_ban_on_rejected_block( self.pynode, node, bad_tx_block, reject_reason=BLOCK_SIGCHECKS_BAD_TX_SIGCHECKS) logging.info( "Try a block with a transaction just under the limit (limit: {})". format(MAX_TX_SIGCHECK)) good_tx_block = self.build_block(tip, [good_tx]) self.pynode.send_blocks_and_test([good_tx_block], node, timeout=TIMEOUT) node.invalidateblock(good_tx_block.hash) # save this tip for later # ~ upgrade_block = tip # Transactions still in pool: waitFor( 60, lambda: set(node.getrawmempool()) == {t.hash for t in submittxes_1}) logging.info( "Try sending 10000-sigcheck blocks after activation (limit: {})". format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) # Send block with same txes we just tried before activation badblock = self.build_block(tip, submittxes_1) check_for_ban_on_rejected_block( self.pynode, node, badblock, reject_reason="Invalid block due to bad-blk-sigchecks", expect_ban=True) logging.info( "There are too many sigchecks in mempool to mine in a single block. Make sure the node won't mine invalid blocks. Num tx: %s" % str(node.getmempoolinfo())) blk = node.generate(1) tip = self.getbestblock(node) # only 39 txes got mined. assert_equal(len(node.getrawmempool()), 11) logging.info( "Try sending 10000-sigcheck block with fresh transactions after activation (limit: {})" .format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) # Note: in the following tests we'll be bumping timestamp in order # to bypass any kind of 'bad block' cache on the node, and get a # fresh evaluation each time. # Try another block with 10000 sigchecks but all fresh transactions badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 5) check_for_ban_on_rejected_block( self.pynode, node, badblock, reject_reason=BLOCK_SIGCHECKS_BAD_BLOCK_SIGCHECKS) # Send the same txes again with different block hash. Currently we don't # cache valid transactions in invalid blocks so nothing changes. badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 6) check_for_ban_on_rejected_block( self.pynode, node, badblock, reject_reason=BLOCK_SIGCHECKS_BAD_BLOCK_SIGCHECKS) # Put all the txes in mempool, in order to get them cached: self.pynode.send_txs_and_test(submittxes_2, node, timeout=TIMEOUT) # Send them again, the node still doesn't like it. But the log # error message has now changed because the txes failed from cache. badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 7) check_for_ban_on_rejected_block( self.pynode, node, badblock, reject_reason=BLOCK_SIGCHECKS_BAD_BLOCK_SIGCHECKS) logging.info( "Try sending 8000-sigcheck block after activation (limit: {})". format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) badblock = self.build_block(tip, submittxes_2[:40], nTime=SIGCHECKS_ACTIVATION_TIME + 5) check_for_ban_on_rejected_block( self.pynode, node, badblock, reject_reason=BLOCK_SIGCHECKS_BAD_BLOCK_SIGCHECKS) # redundant, but just to mirror the following test... node.set("consensus.maxBlockSigChecks=%d" % MAX_BLOCK_SIGCHECKS) logging.info( "Bump the excessiveblocksize limit by 1 byte, and send another block with same txes (new sigchecks limit: {})" .format((MAXBLOCKSIZE + 1) // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) node.set("consensus.maxBlockSigChecks=%d" % (MAX_BLOCK_SIGCHECKS + 1)) tip = self.build_block(tip, submittxes_2[:40], nTime=SIGCHECKS_ACTIVATION_TIME + 6) # It should succeed now since limit should be 8000. self.pynode.send_blocks_and_test([tip], node, timeout=TIMEOUT)