def run_test(self): n = self.nodes[0] self.bootstrap_p2p() coinbases = self.mine_blocks(n, 5) self.client = create_electrum_connection() # Test raw for tx in coinbases: assert_equal(ToHex(tx), self.client.call(TX_GET, tx.hash)) # Test verbose. # The spec is unclear. It states: # # "whatever the coin daemon returns when asked for a # verbose form of the raw transaction" # # Just check the basics. for tx in coinbases: electrum = self.client.call(TX_GET, tx.hash, True) bitcoind = n.getrawtransaction(tx.hash, True) assert_equal(bitcoind['txid'], electrum['txid']) assert_equal(bitcoind['locktime'], electrum['locktime']) assert_equal(bitcoind['size'], electrum['size']) assert_equal(bitcoind['hex'], electrum['hex']) assert_equal(len(bitcoind['vin']), len(bitcoind['vin'])) assert_equal(len(bitcoind['vout']), len(bitcoind['vout']))
def test_invalid_args(self, electrum_client): from test_framework.connectrum.exc import ElectrumErrorResponse error_code = "-32602" hash_param_methods = ("blockchain.scripthash.get_balance", "blockchain.scripthash.get_history", "blockchain.scripthash.listunspent") for method in hash_param_methods: assert_raises(ElectrumErrorResponse, electrum_client.call, method, "invalidhash") try: electrum_client.call(method, "invalidhash") except Exception as e: print("ERROR:" + str(e)) assert error_code in str(e) # invalid tx try: tx = CTransaction() tx.calc_sha256() tx.vin = [CTxIn(COutPoint(0xbeef, 1))] electrum_client.call("blockchain.transaction.broadcast", ToHex(tx)) except Exception as e: print("ERROR: " + str(e)) assert error_code in str(e)
def create_cashaccount_tx(self, n, spend, name, address): fee = 500 tx = create_transaction( FromHex(CTransaction(), n.getrawtransaction(spend['txid'])), spend['vout'], b"", spend['satoshi'] - fee) _, _, keyhash = decode_addr(address) name = bytes(name, 'ascii') keyhash = DATATYPE_KEYHASH + keyhash cashaccount_script = CScript( [OP_RETURN, CASHACCONT_PREFIX, name, keyhash]) tx.vout.append(CTxOut(0, cashaccount_script)) tx.rehash() return n.signrawtransaction(ToHex(tx))['hex']
async def async_tests(): cli = ElectrumConnection() await cli.connect() # Test raw for tx in coinbases: assert_equal(ToHex(tx), await cli.call(TX_GET, tx.hash)) # Test verbose. # The spec is unclear. It states: # # "whatever the coin daemon returns when asked for a # verbose form of the raw transaction" # # Just check the basics. for tx in coinbases: electrum = await cli.call(TX_GET, tx.hash, True) bitcoind = n.getrawtransaction(tx.hash, True) assert_equal(bitcoind['txid'], electrum['txid']) assert_equal(bitcoind['locktime'], electrum['locktime']) assert_equal(bitcoind['size'], electrum['size']) assert_equal(bitcoind['hex'], electrum['hex']) assert_equal(len(bitcoind['vin']), len(bitcoind['vin'])) assert_equal(len(bitcoind['vout']), len(bitcoind['vout']))
def run_test(self): logging.info("Initializing test directory " + self.options.tmpdir) node = self.nodes[0] self.bootstrap_p2p() tip = self.getbestblock(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] logging.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) logging.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair privkeybytes = b"Schnorr!" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): spendfrom = spendable_outputs.pop() script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_transaction(spendfrom, 0, b'', value, script) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(value - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = SIGHASH_ALL | SIGHASH_FORKID hashbyte = bytes([sighashtype & 0xff]) sighash = SignatureHashForkId(script, txspend, 0, sighashtype, value) if sigtype == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sigtype == 'ecdsa': txsig = private_key.sign(sighash) + hashbyte txspend.vin[0].scriptSig = CScript([dummy, txsig]) txspend.rehash() return txspend # This is valid. ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') # This is invalid. ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') # This is invalid. schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') # This is valid. schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') tip = self.build_block(tip, fundings) self.p2p.send_blocks_and_test([tip], node) logging.info("Send a legacy ECDSA multisig into mempool.") self.p2p.send_txs_and_test([ecdsa0tx], node) waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx.hash]) logging.info("Trying to mine a non-null-dummy ECDSA.") self.check_for_ban_on_rejected_block(self.build_block(tip, [ecdsa1tx]), BADINPUTS_ERROR) logging.info( "If we try to submit it by mempool or RPC, it is rejected and we are banned" ) assert_raises_rpc_error(-26, ECDSA_NULLDUMMY_ERROR, node.sendrawtransaction, ToHex(ecdsa1tx)) self.check_for_ban_on_rejected_tx(ecdsa1tx, ECDSA_NULLDUMMY_ERROR) logging.info( "Submitting a Schnorr-multisig via net, and mining it in a block") self.p2p.send_txs_and_test([schnorr1tx], node) waitFor( 10, lambda: set(node.getrawmempool()) == {ecdsa0tx.hash, schnorr1tx.hash}) tip = self.build_block(tip, [schnorr1tx]) self.p2p.send_blocks_and_test([tip], node) logging.info( "That legacy ECDSA multisig is still in mempool, let's mine it") waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx.hash]) tip = self.build_block(tip, [ecdsa0tx]) self.p2p.send_blocks_and_test([tip], node) waitFor(10, lambda: node.getrawmempool() == []) logging.info( "Trying Schnorr in legacy multisig is invalid and banworthy.") self.check_for_ban_on_rejected_tx(schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR)
def run_test(self): self.bootstrap_p2p() self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # shorthand block = self.next_block node_nonstd = self.nodes[0] node_std = self.nodes[1] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # submit current tip and check it was accepted def accepted(self, node): self.p2p.send_blocks_and_test([self.tip], node) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] block.vtx.extend(new_transactions) old_sha256 = block.sha256 make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # checks the mempool has exactly the same txns as in the provided list def check_mempool_equal(node, txns): assert set(node.getrawmempool()) == set(tx.hash for tx in txns) # Returns 2 transactions: # 1) txfund: create outputs in segwit addresses # 2) txspend: spends outputs from segwit addresses 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 # Check we are not banned when sending a txn that is rejected. def check_for_no_ban_on_rejected_tx(self, node, tx, reject_reason): self.p2p.send_txs_and_test( [tx], node, success=False, reject_reason=reject_reason) def check_for_accepted_tx(self, node, tx): self.p2p.send_txs_and_test([tx], node, success=True) # Create a new block block(0) save_spendable_output() accepted(self, node_nonstd) # Now we need that block to mature so we can spend the coinbase. matureblocks = [] for i in range(199): block(5000 + i) matureblocks.append(self.tip) save_spendable_output() self.p2p.send_blocks_and_test(matureblocks, node_nonstd) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Create segwit funding and spending transactions txfund, txspend = create_segwit_fund_and_spend_tx(out[0]) txfund_case0, txspend_case0 = create_segwit_fund_and_spend_tx( out[1], True) # Mine txfund, as it can't go into node_std mempool because it's # nonstandard. block(5555) update_block(5555, [txfund, txfund_case0]) # check that current tip is accepted by node0 (nonstd) accepted(self, node_nonstd) # Check both nodes are synchronized before continuing. sync_blocks(self.nodes) # Check that upgraded nodes checking for standardness are not banning # nodes sending segwit spending txns. check_for_no_ban_on_rejected_tx( self, node_std, txspend, CLEANSTACK_ERROR) check_for_no_ban_on_rejected_tx( self, node_std, txspend_case0, EVAL_FALSE_ERROR) txspend_id = node_nonstd.decoderawtransaction(ToHex(txspend))["txid"] txspend_case0_id = node_nonstd.decoderawtransaction(ToHex(txspend_case0))["txid"] # Segwit recovery txns are accept from node that accept not standard txs assert_equal(node_nonstd.sendrawtransaction(ToHex(txspend)), txspend_id) assert_equal(node_nonstd.sendrawtransaction(ToHex(txspend_case0)), txspend_case0_id) # Segwit recovery txs are reject if node does not accept stansard txs assert_raises_rpc_error(-26, CLEANSTACK_ERROR, node_std.sendrawtransaction, ToHex(txspend)) assert_raises_rpc_error(-26, EVAL_FALSE_ERROR, node_std.sendrawtransaction, ToHex(txspend_case0)) # Blocks containing segwit spending txns are accepted in both nodes. self.next_block(5) update_block(5, [txspend, txspend_case0]) accepted(self, node_nonstd) sync_blocks(self.nodes)
def run_test(self): (node, ) = self.nodes 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, timeout=10) 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' ) fundings = [] def make_spend(scriptpubkey, scriptsig): # Add a funding tx to fundings, and return a tx spending that using # scriptsig. logging.debug( "Gen tx with locking script {} unlocking script {} .".format( scriptpubkey.hex(), scriptsig.hex())) # get funds locked with OP_1 sourcetx = self.spendable_outputs.popleft() # make funding that forwards to scriptpubkey fundtx = create_transaction(sourcetx, scriptpubkey) fundings.append(fundtx) # make the spending tx = CTransaction() tx.vin.append(CTxIn(COutPoint(fundtx.sha256, 1), scriptsig)) tx.vout.append(CTxOut(0, CScript([OP_RETURN]))) pad_tx(tx) tx.rehash() return tx logging.info("Generating txes used in this test") # "Good" txns that pass our rule: goodtxes = [ # most dense allowed input -- 2 sigchecks with a 26-byte scriptsig. make_spend( CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]), CScript([b'x' * 16, cds_signature])), # 4 sigchecks with a 112-byte scriptsig, just at the limit for this # sigchecks count. make_spend( CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]), CScript([b'x' * 101, cds_signature])), # "nice" transaction - 1 sigcheck with 9-byte scriptsig. make_spend(CScript([cds_message, cds_pubkey, OP_CHECKDATASIG]), CScript([cds_signature])), # 1 sigcheck with 0-byte scriptsig. make_spend( CScript( [cds_signature, cds_message, cds_pubkey, OP_CHECKDATASIG]), CScript([])), ] badtxes = [ # "Bad" txns: # 2 sigchecks with a 25-byte scriptsig, just 1 byte too short. make_spend( CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]), CScript([b'x' * 15, cds_signature])), # 4 sigchecks with a 111-byte scriptsig, just 1 byte too short. make_spend( CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]), CScript([b'x' * 100, cds_signature])), ] goodtxids = set(t.hash for t in goodtxes) badtxids = set(t.hash for t in badtxes) logging.info("Funding the txes") tip = self.build_block(tip, fundings) self.pynode.send_blocks_and_test([tip], node, timeout=10) # 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(MAY2020_START_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=MAY2020_START_TIME + i) blocks.append(tip) self.pynode.send_blocks_and_test(blocks, node, timeout=10) assert_equal(node.getblockchaininfo()['mediantime'], MAY2020_START_TIME - 1) logging.info( "The next block will activate, but the activation block itself must follow old rules" ) logging.info("Send all the transactions just before upgrade") self.pynode.send_txs_and_test(goodtxes, node) self.pynode.send_txs_and_test(badtxes, node) assert_equal(set(node.getrawmempool()), goodtxids | badtxids) # ask the node to mine a block, it should include the bad txes. [blockhash] = node.generate(1) assert_equal(set(node.getblock(blockhash, 1)['tx'][1:]), goodtxids | badtxids) assert_equal(node.getrawmempool(), []) # discard that block node.invalidateblock(blockhash) waitFor(30, lambda: set(node.getrawmempool()) == goodtxids | badtxids) logging.info("Mine the activation block itself") tip = self.build_block(tip) self.pynode.send_blocks_and_test([tip], node, timeout=10) logging.info("We have activated!") assert_equal(node.getblockchaininfo()['mediantime'], MAY2020_START_TIME) logging.info( "The high-sigchecks transactions got evicted but the good ones are still around" ) waitFor( 20, lambda: True if set(node.getrawmempool()) == goodtxids else logging.info(node.getrawmempool())) logging.info( "Now the high-sigchecks transactions are rejected from mempool.") # try sending some of the bad txes again after the upgrade for tx in badtxes: self.check_for_no_ban_on_rejected_tx( node, tx, None) # No reject reason because we don't log on rejection assert_raises_rpc_error(-26, TX_INPUT_SIGCHECKS_ERROR, node.sendrawtransaction, ToHex(tx)) logging.info("But they can still be mined!") # Now make a block with all the txes, they still are accepted in blocks! tip = self.build_block(tip, goodtxes + badtxes) self.pynode.send_blocks_and_test([tip], node, timeout=10) assert_equal(node.getbestblockhash(), tip.hash)
def run_test(self): logging.info("Initializing test directory " + self.options.tmpdir) node = self.nodes[0] self.bootstrap_p2p() tip = self.get_best_block(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] logging.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.get_best_block(node) logging.info( "Set up spending transactions to test and mine the funding transactions." ) # Generate a key pair privkeybytes = b"xyzxyzhh" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(): spend_from = spendable_outputs.pop() value = spend_from.vout[0].nValue # Reversed data data = bytes.fromhex('0123456789abcdef') rev_data = bytes(reversed(data)) # Lockscript: provide a bytestring that reverses to X script = CScript([OP_REVERSEBYTES, rev_data, OP_EQUAL]) # Fund transaction: REVERSEBYTES <reversed(x)> EQUAL tx_fund = create_tx_with_script(spend_from, 0, b'', value, script) tx_fund.rehash() # Spend transaction: <x> tx_spend = CTransaction() tx_spend.vout.append( CTxOut(value - 1000, CScript([b'x' * 100, OP_RETURN]))) tx_spend.vin.append(CTxIn(COutPoint(tx_fund.sha256, 0), b'')) tx_spend.vin[0].scriptSig = CScript([data]) tx_spend.rehash() return tx_spend, tx_fund # Create funding/spending transaction pair tx_reversebytes_spend, tx_reversebytes_fund = create_fund_and_spend_tx( ) # Mine funding transaction into block. Pre-upgrade output scripts can have # OP_REVERSEBYTES and still be fully valid, but they cannot spend it. tip = self.build_block(tip, [tx_reversebytes_fund]) self.p2p.send_blocks_and_test([tip], node) logging.info("Start pre-upgrade tests") assert node.getblockheader( node.getbestblockhash())['mediantime'] < MAY2020_START_TIME logging.info( "Sending rejected transaction (bad opcode) via RPC (doesn't ban)") assert_raises_rpc_error(-26, PRE_UPGRADE_BAD_OPCODE_ERROR, node.sendrawtransaction, ToHex(tx_reversebytes_spend)) logging.info( "Sending rejected transaction (bad opcode) via net (no banning)") self.check_for_no_ban_on_rejected_tx(tx_reversebytes_spend, PRE_UPGRADE_BAD_OPCODE_ERROR) logging.info( "Sending invalid transactions in blocks (bad inputs, and get banned)" ) self.check_for_ban_on_rejected_block( self.build_block(tip, [tx_reversebytes_spend]), BAD_INPUTS_ERROR) logging.info("Start activation tests") logging.info("Approach to just before upgrade activation") # Move our clock to the upgrade time so we will accept such # future-timestamped blocks. node.setmocktime(MAY2020_START_TIME) # Mine six blocks with timestamp starting at MAY2020_START_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, n_time=MAY2020_START_TIME + i) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node) # Ensure our MTP is MAY2020_START_TIME-1, just before activation waitFor(10, lambda: node.getblockchaininfo()['mediantime'], MAY2020_START_TIME - 1) logging.info( "The next block will activate, but the activation block itself must follow old rules" ) self.check_for_ban_on_rejected_block( self.build_block(tip, [tx_reversebytes_spend]), BAD_INPUTS_ERROR) # Save pre-upgrade block, we will reorg based on this block later pre_upgrade_block = tip logging.info("Mine the activation block itself") tip = self.build_block(tip, []) self.p2p.send_blocks_and_test([tip], node) logging.info("We have activated!") # Ensure our MTP is MAY2020_START_TIME, exactly at activation waitFor( 10, lambda: node.getblockchaininfo()['mediantime'] == MAY2020_START_TIME) # Ensure empty mempool waitFor(10, lambda: node.getrawmempool() == []) # Save upgrade block, will invalidate and reconsider this later upgrade_block = tip logging.info( "Submitting a new OP_REVERSEBYTES tx via net, and mining it in a block" ) # Send OP_REVERSEBYTES tx self.p2p.send_txs_and_test([tx_reversebytes_spend], node) # Verify OP_REVERSEBYTES tx is in mempool waitFor( 10, lambda: set(node.getrawmempool()) == {tx_reversebytes_spend.hash}) # Mine OP_REVERSEBYTES tx into block tip = self.build_block(tip, [tx_reversebytes_spend]) self.p2p.send_blocks_and_test([tip], node) # Save post-upgrade block, will invalidate and reconsider this later post_upgrade_block = tip logging.info("Start deactivation tests") logging.info( "Invalidating the post-upgrade blocks returns OP_REVERSEBYTES transaction to mempool" ) node.invalidateblock(post_upgrade_block.hash) waitFor(5, lambda: node.getbestblockhash() == upgrade_block.hash) waitFor(5, lambda: len(node.getrawmempool()) > 0) assert_equal(set(node.getrawmempool()), {tx_reversebytes_spend.hash}) logging.info( "Invalidating the upgrade block evicts the OP_REVERSEBYTES transaction" ) node.invalidateblock(upgrade_block.hash) assert_equal(set(node.getrawmempool()), set()) logging.info("Return to our tip") try: node.reconsiderblock(upgrade_block.hash) node.reconsiderblock(post_upgrade_block.hash) except Exception as e: # Workaround for reconsiderblock bug; # Even though the block reconsidered was valid, if another block # is also reconsidered and fails, the call will return failure. pass waitFor(10, lambda: node.getbestblockhash() == tip.hash) waitFor(10, lambda: node.getrawmempool() == []) logging.info("Create an empty-block reorg that forks from pre-upgrade") tip = pre_upgrade_block blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node) logging.info( "Transactions from orphaned blocks are sent into mempool ready to be mined again, " "including upgrade-dependent ones even though the fork deactivated and reactivated " "the upgrade.") waitFor( 10, lambda: set(node.getrawmempool()) == {tx_reversebytes_spend.hash}) node.generate(1) tip = self.get_best_block(node) assert (set(tx.rehash() for tx in tip.vtx) >= {tx_reversebytes_spend.hash})
async def test_non_verbose(self, cli, coinbases, unconfirmed): for tx in coinbases + [unconfirmed]: assert_equal(ToHex(tx), await cli.call(TX_GET, tx.hash))
def run_test(self): n = self.nodes[0] self.bootstrap_p2p() coinbases = self.mine_blocks(n, 104) # non-coinbase transactions prevtx = coinbases[0] nonstandard_tx = create_transaction(prevtx=prevtx, value=prevtx.vout[0].nValue, n=0, sig=CScript([OP_TRUE]), out=CScript([OP_FALSE, OP_DROP])) prevtx = coinbases[1] p2sh_tx = create_transaction(prevtx=prevtx, value=prevtx.vout[0].nValue, n=0, sig=CScript([OP_TRUE]), out=CScript( [OP_HASH160, DUMMY_HASH, OP_EQUAL])) prevtx = coinbases[2] p2pkh_tx = create_transaction(prevtx=prevtx, value=prevtx.vout[0].nValue, n=0, sig=CScript([OP_TRUE]), out=CScript([ OP_DUP, OP_HASH160, DUMMY_HASH, OP_EQUALVERIFY, OP_CHECKSIG ])) prevtx = coinbases[3] unconfirmed_tx = create_transaction(prevtx=prevtx, value=prevtx.vout[0].nValue, n=0, sig=CScript([OP_TRUE]), out=CScript([ OP_DUP, OP_HASH160, DUMMY_HASH, OP_EQUALVERIFY, OP_CHECKSIG ])) for tx in [nonstandard_tx, p2sh_tx, p2pkh_tx, unconfirmed_tx]: pad_tx(tx) coinbases.extend( self.mine_blocks(n, 1, [nonstandard_tx, p2sh_tx, p2pkh_tx])) self.sync_height() n.sendrawtransaction(ToHex(unconfirmed_tx)) self.wait_for_mempool_count(count=1) async def async_tests(loop): cli = ElectrumConnection(loop) await cli.connect() return await asyncio.gather( self.test_verbose(n, cli, nonstandard_tx.hash, p2sh_tx.hash, p2pkh_tx.hash, unconfirmed_tx.hash), self.test_non_verbose(cli, coinbases, unconfirmed_tx)) loop = asyncio.get_event_loop() loop.run_until_complete(async_tests(loop))
def run_test(self): node = self.nodes[0] node.generate(1) self.bootstrap_p2p() tip = self.getbestblock(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] logging.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) logging.info("Setting up spends to test and mining the fundings.") fundings = [] def create_fund_and_spend_tx(): spendfrom = spendable_outputs.pop() script = CScript([OP_ADD]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_transaction(spendfrom, 0, b'', value, script) pad_tx(txfund) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(value - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction txspend.vin[0].scriptSig = CScript( b'\x01\x01\x51') # PUSH1(0x01) OP_1 pad_tx(txspend) txspend.rehash() return txspend # make a few of these, which are nonstandard before upgrade and invalid after. nonminimaltx = create_fund_and_spend_tx() nonminimaltx_2 = create_fund_and_spend_tx() nonminimaltx_3 = create_fund_and_spend_tx() tip = self.build_block(tip, fundings) self.p2p.send_blocks_and_test([tip], node) logging.info("Start preupgrade tests") logging.info("Sending rejected transactions via RPC") assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, node.sendrawtransaction, ToHex(nonminimaltx)) assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, node.sendrawtransaction, ToHex(nonminimaltx_2)) assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, node.sendrawtransaction, ToHex(nonminimaltx_3)) logging.info("Sending rejected transactions via net (no banning)") self.check_for_no_ban_on_rejected_tx(nonminimaltx, MINIMALPUSH_ERROR) self.check_for_no_ban_on_rejected_tx(nonminimaltx_2, MINIMALPUSH_ERROR) self.check_for_no_ban_on_rejected_tx(nonminimaltx_3, MINIMALPUSH_ERROR) assert_equal(node.getrawmempool(), []) logging.info("Successfully mine nonstandard transaction") tip = self.build_block(tip, [nonminimaltx]) self.p2p.send_blocks_and_test([tip], node) # 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(NOV2019_START_TIME) # Mine six blocks with timestamp starting at NOV2019_START_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=NOV2019_START_TIME + i) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node) assert_equal(node.getblockchaininfo()['mediantime'], NOV2019_START_TIME - 1) # save this tip for later preupgrade_block = tip logging.info( "Mine the activation block itself, including a minimaldata violation at the last possible moment" ) tip = self.build_block(tip, [nonminimaltx_2]) self.p2p.send_blocks_and_test([tip], node) logging.info("We have activated!") assert_equal(node.getblockchaininfo()['mediantime'], NOV2019_START_TIME) # save this tip for later upgrade_block = tip logging.info( "Trying to mine a minimaldata violation, but we are just barely too late" ) self.check_for_ban_on_rejected_block( self.build_block(tip, [nonminimaltx_3]), BADSIGNATURE_ERROR) return logging.info( "If we try to submit it by mempool or RPC we still aren't banned") assert_raises_rpc_error(-26, rpc_error(MINIMALPUSH_ERROR), node.sendrawtransaction, ToHex(nonminimaltx_3)) self.check_for_no_ban_on_rejected_tx(nonminimaltx_3, MINIMALPUSH_ERROR) logging.info("Mine a normal block") tip = self.build_block(tip) self.p2p.send_blocks_and_test([tip], node)
def run_test(self): node = self.nodes[0] self.bootstrap_p2p() tip = self.getbestblock(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] logging.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) logging.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair privkeybytes = b"Schnorr!" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): spendfrom = spendable_outputs.pop() script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_transaction(spendfrom, 0, b'', value, script) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append( CTxOut(value-1000, CScript([OP_TRUE]))) txspend.vin.append( CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = SIGHASH_ALL | SIGHASH_FORKID hashbyte = bytes([sighashtype & 0xff]) sighash = SignatureHashForkId( script, txspend, 0, sighashtype, value) if sigtype == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sigtype == 'ecdsa': txsig = private_key.sign(sighash) + hashbyte txspend.vin[0].scriptSig = CScript([dummy, txsig]) txspend.rehash() return txspend # two of these transactions, which are valid both before and after upgrade. ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') ecdsa0tx_2 = create_fund_and_spend_tx(OP_0, 'ecdsa') # two of these, which are nonstandard before upgrade and invalid after. ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') ecdsa1tx_2 = create_fund_and_spend_tx(OP_1, 'ecdsa') # this one is always invalid. schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') # this one is only going to be valid after the upgrade. schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') tip = self.build_block(tip, fundings) self.p2p.send_blocks_and_test([tip], node) logging.info("Start preupgrade tests") assert node.getblockheader(node.getbestblockhash())['mediantime'] < NOV2019_START_TIME logging.info("Sending rejected transactions via RPC") assert_raises_rpc_error(-26, PREUPGRADE_ECDSA_NULLDUMMY_ERROR, node.sendrawtransaction, ToHex(ecdsa1tx)) assert_raises_rpc_error(-26, SCHNORR_LEGACY_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorr0tx)) assert_raises_rpc_error(-26, PREUPGRADE_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorr1tx)) logging.info( "Sending rejected transactions via net (banning depending on situation)") self.check_for_no_ban_on_rejected_tx( ecdsa1tx, PREUPGRADE_ECDSA_NULLDUMMY_ERROR) self.check_for_ban_on_rejected_tx( schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_no_ban_on_rejected_tx( schnorr1tx, PREUPGRADE_SCHNORR_MULTISIG_ERROR) logging.info( "Sending invalid transactions in blocks (and get banned!)") self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), BADSIG_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr1tx]), BADSIG_ERROR) logging.info("Sending valid transaction via net, then mining it") self.p2p.send_txs_and_test([ecdsa0tx], node) waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx.hash]) tip = self.build_block(tip, [ecdsa0tx]) self.p2p.send_blocks_and_test([tip], node) waitFor(10, lambda: node.getrawmempool() == []) # 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(NOV2019_START_TIME) # Mine six blocks with timestamp starting at NOV2019_START_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=NOV2019_START_TIME + i) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node) waitFor(10, lambda: node.getblockchaininfo()[ 'mediantime'] == NOV2019_START_TIME - 1) logging.info( "The next block will activate, but the activation block itself must follow old rules") self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), BADSIG_ERROR) logging.info( "Send a lecacy ECDSA multisig into mempool, we will check after upgrade to make sure it didn't get cleaned out unnecessarily.") self.p2p.send_txs_and_test([ecdsa0tx_2], node) waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx_2.hash]) # save this tip for later preupgrade_block = tip logging.info( "Mine the activation block itself, including a legacy nulldummy violation at the last possible moment") tip = self.build_block(tip, [ecdsa1tx]) self.p2p.send_blocks_and_test([tip], node) logging.info("We have activated!") waitFor(10, lambda: node.getblockchaininfo()[ 'mediantime'] == NOV2019_START_TIME) waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx_2.hash]) # save this tip for later upgrade_block = tip logging.info( "Trying to mine a legacy nulldummy violation, but we are just barely too late") self.check_for_ban_on_rejected_block( self.build_block(tip, [ecdsa1tx_2]), BADSIG_ERROR) logging.info( "If we try to submit it by mempool or RPC, the error code has changed but we still aren't banned") assert_raises_rpc_error(-26, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR, node.sendrawtransaction, ToHex(ecdsa1tx_2)) self.check_for_no_ban_on_rejected_tx( ecdsa1tx_2, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR) logging.info( "Submitting a new Schnorr-multisig via net, and mining it in a block") self.p2p.send_txs_and_test([schnorr1tx], node) waitFor(10, lambda: set(node.getrawmempool()) == {ecdsa0tx_2.hash, schnorr1tx.hash}) tip = self.build_block(tip, [schnorr1tx]) self.p2p.send_blocks_and_test([tip], node) # save this tip for later postupgrade_block = tip logging.info( "That legacy ECDSA multisig is still in mempool, let's mine it") waitFor(10, lambda: node.getrawmempool() == [ecdsa0tx_2.hash]) tip = self.build_block(tip, [ecdsa0tx_2]) self.p2p.send_blocks_and_test([tip], node) waitFor(10, lambda: node.getrawmempool() == []) logging.info( "Trying Schnorr in legacy multisig remains invalid and banworthy as ever") self.check_for_ban_on_rejected_tx( schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), BADSIG_ERROR) # Deactivation tests logging.info( "Invalidating the post-upgrade blocks returns the transactions to mempool") node.invalidateblock(postupgrade_block.hash) waitFor(10, lambda: set(node.getrawmempool()) == {ecdsa0tx_2.hash, schnorr1tx.hash}) logging.info( "Invalidating the upgrade block evicts the transactions valid only after upgrade") node.invalidateblock(upgrade_block.hash) waitFor(10, lambda: set(node.getrawmempool()) == {ecdsa0tx_2.hash}) logging.info("Return to our tip") try: node.reconsiderblock(upgrade_block.hash) node.reconsiderblock(postupgrade_block.hash) except Exception as e: # Workaround for reconsiderblock bug; # Even though the block reconsidered was valid, if another block # is also reconsidered and fails, the call will return failure. pass waitFor(10, lambda: node.getbestblockhash() == tip.hash) waitFor(10, lambda: node.getrawmempool() == []) logging.info( "Create an empty-block reorg that forks from pre-upgrade") tip = preupgrade_block blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node) logging.info("Transactions from orphaned blocks are sent into mempool ready to be mined again, including upgrade-dependent ones even though the fork deactivated and reactivated the upgrade.") waitFor(10, lambda: set(node.getrawmempool()) == { ecdsa0tx_2.hash, schnorr1tx.hash}) node.generate(1) tip = self.getbestblock(node) assert set(tx.rehash() for tx in tip.vtx).issuperset( {ecdsa0tx_2.hash, schnorr1tx.hash})
def run_test(self): node = self.nodes[0] node.generate(1) self.bootstrap_p2p() tip = self.getbestblock(node) logging.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) self.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] logging.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) logging.info("Setting up spends to test and mining the fundings.") fundings = [] def create_fund_and_spend_tx(): spendfrom = spendable_outputs.pop() script = CScript([OP_ADD]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_transaction(spendfrom, 0, b'', value, script) pad_tx(txfund) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append( CTxOut(value-1000, CScript([OP_TRUE]))) txspend.vin.append( CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction txspend.vin[0].scriptSig = CScript( b'\x01\x01\x51') # PUSH1(0x01) OP_1 pad_tx(txspend) txspend.rehash() return txspend # no minimal tx are invalid nonminimaltx = create_fund_and_spend_tx() tip = self.build_block(tip, fundings) self.p2p.send_blocks_and_test([tip], node) logging.info("Trying to mine a minimaldata violation") self.check_for_ban_on_rejected_block( self.build_block(tip, [nonminimaltx]), BADSIGNATURE_ERROR) logging.info("If we try to submit it by mempool or RPC we are banned") assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, node.sendrawtransaction, ToHex(nonminimaltx)) self.check_for_ban_on_rejected_tx(nonminimaltx, MINIMALPUSH_ERROR) logging.info("Mine a normal block") tip = self.build_block(tip) self.p2p.send_blocks_and_test([tip], node)