diff --git a/test/functional/abc-get-invalid-block.py b/test/functional/abc-get-invalid-block.py index 4a6396a98..582fc822b 100755 --- a/test/functional/abc-get-invalid-block.py +++ b/test/functional/abc-get-invalid-block.py @@ -1,76 +1,76 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test requesting invalid blocks behaves safely.""" from test_framework.messages import ( MSG_BLOCK, MSG_CMPCT_BLOCK, CInv, msg_getblocks, msg_getdata, msg_getheaders, ) from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class GetInvalidBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PInterface()) + peer = node.add_p2p_connection(P2PInterface()) chaintip = node.getbestblockhash() # Mine some blocks and invalidate them blocks = node.generatetoaddress( 3, node.get_deterministic_priv_key().address) assert_equal(blocks[-1], node.getbestblockhash()) node.invalidateblock(blocks[0]) assert_equal(chaintip, node.getbestblockhash()) # Clear any old messages with p2p_lock: - node.p2p.last_message.pop("block", None) - node.p2p.last_message.pop("cmpctblock", None) - node.p2p.last_message.pop("headers", None) + peer.last_message.pop("block", None) + peer.last_message.pop("cmpctblock", None) + peer.last_message.pop("headers", None) # Requests for the invalidated block and it's decendants should fail. # Not doing so is a potential DoS vector. for b in blocks: block_hash = int(b, 16) # Currently, the implementation for getblocks skips blocks which # are not on the currently active chain. This is the only logged # indication of such. with node.assert_debug_log(expected_msgs=["getblocks -1 to"]): msg = msg_getblocks() msg.locator.vHave = [block_hash] - node.p2p.send_message(msg) - node.p2p.sync_with_ping() + peer.send_message(msg) + peer.sync_with_ping() with node.assert_debug_log(expected_msgs=["ignoring request from peer=0 for old block that isn't in the main chain"]): msg = msg_getdata() msg.inv.append(CInv(MSG_BLOCK, block_hash)) - node.p2p.send_message(msg) - node.p2p.sync_with_ping() + peer.send_message(msg) + peer.sync_with_ping() with node.assert_debug_log(expected_msgs=["ignoring request from peer=0 for old block that isn't in the main chain"]): msg = msg_getdata() msg.inv.append(CInv(MSG_CMPCT_BLOCK, block_hash)) - node.p2p.send_message(msg) - node.p2p.sync_with_ping() + peer.send_message(msg) + peer.sync_with_ping() with node.assert_debug_log(expected_msgs=["ignoring request from peer=0 for old block header that isn't in the main chain"]): msg = msg_getheaders() msg.hashstop = block_hash - node.p2p.send_message(msg) - node.p2p.sync_with_ping() + peer.send_message(msg) + peer.sync_with_ping() if __name__ == '__main__': GetInvalidBlockTest().main() diff --git a/test/functional/abc-invalid-chains.py b/test/functional/abc-invalid-chains.py index 5e89f5705..e8486d533 100755 --- a/test/functional/abc-invalid-chains.py +++ b/test/functional/abc-invalid-chains.py @@ -1,157 +1,157 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-ilncense.php. import time from test_framework.blocktools import create_block, create_coinbase from test_framework.p2p import P2PDataStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class InvalidChainsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.tip = None self.blocks = {} self.block_heights = {} self.extra_args = [["-whitelist=noban@127.0.0.1"]] def next_block(self, number): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def set_tip(self, number: int): """ Move the tip back to a previous block. """ self.tip = self.blocks[number] def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 # shorthand for functions block = self.next_block # Reference for blocks mined in this test: # # 11 21 -- 221 - 222 # / / / # 0 - 1 - 2 - 22 - 23 - 24 - 25 # \ # -- 12 - 13 - 14 # \ # -- 15 - 16 - 17 - 18 # Generate some valid blocks - node.p2p.send_blocks_and_test([block(0), block(1), block(2)], node) + peer.send_blocks_and_test([block(0), block(1), block(2)], node) # Explicitly invalidate blocks 1 and 2 # See below for why we do this node.invalidateblock(self.blocks[1].hash) assert_equal(self.blocks[0].hash, node.getbestblockhash()) node.invalidateblock(self.blocks[2].hash) assert_equal(self.blocks[0].hash, node.getbestblockhash()) # Mining on top of blocks 1 or 2 is rejected self.set_tip(1) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [block(11)], node, success=False, force_send=True, reject_reason='bad-prevblk') self.set_tip(2) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [block(21)], node, success=False, force_send=True, reject_reason='bad-prevblk') # Reconsider block 2 to remove invalid status from *both* 1 and 2 # The goal is to test that block 1 is not retaining any internal state # that prevents us from accepting blocks building on top of block 1 node.reconsiderblock(self.blocks[2].hash) assert_equal(self.blocks[2].hash, node.getbestblockhash()) # Mining on the block 1 chain should be accepted # (needs to mine two blocks because less-work chains are not processed) self.set_tip(1) - node.p2p.send_blocks_and_test([block(12), block(13)], node) + peer.send_blocks_and_test([block(12), block(13)], node) # Mining on the block 2 chain should still be accepted # (needs to mine two blocks because less-work chains are not processed) self.set_tip(2) - node.p2p.send_blocks_and_test([block(22), block(221)], node) + peer.send_blocks_and_test([block(22), block(221)], node) # Mine more blocks from block 22 to be longest chain self.set_tip(22) - node.p2p.send_blocks_and_test([block(23), block(24)], node) + peer.send_blocks_and_test([block(23), block(24)], node) # Sanity checks assert_equal(self.blocks[24].hash, node.getbestblockhash()) assert any(self.blocks[221].hash == chaintip["hash"] for chaintip in node.getchaintips()) # Invalidating the block 2 chain should reject new blocks on that chain node.invalidateblock(self.blocks[2].hash) assert_equal(self.blocks[13].hash, node.getbestblockhash()) # Mining on the block 2 chain should be rejected self.set_tip(24) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [block(25)], node, success=False, force_send=True, reject_reason='bad-prevblk') # Continued mining on the block 1 chain is still ok self.set_tip(13) - node.p2p.send_blocks_and_test([block(14)], node) + peer.send_blocks_and_test([block(14)], node) # Mining on a once-valid chain forking from block 2's longest chain, # which is now invalid, should also be rejected. self.set_tip(221) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [block(222)], node, success=False, force_send=True, reject_reason='bad-prevblk') self.log.info( "Make sure that reconsidering a block behaves correctly when cousin chains (neither ancestors nor descendants) become available as a result") # Reorg out 14 with four blocks. self.set_tip(13) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [block(15), block(16), block(17), block(18)], node) # Invalidate 17 (so 18 now has failed parent) node.invalidateblock(self.blocks[17].hash) assert_equal(self.blocks[16].hash, node.getbestblockhash()) # Invalidate 13 (so 14 and 15 and 16 now also have failed parent) node.invalidateblock(self.blocks[13].hash) assert_equal(self.blocks[12].hash, node.getbestblockhash()) # Reconsider 14, which should reconsider 13 and remove failed parent # from our cousins 15 and 16 as well. Even though we reconsidered # 14, we end up on a different chain because 15/16 have more work. # (But, this shouldn't undo our invalidation of 17) node.reconsiderblock(self.blocks[14].hash) assert_equal(self.blocks[16].hash, node.getbestblockhash()) if __name__ == '__main__': InvalidChainsTest().main() diff --git a/test/functional/abc-mempool-coherence-on-activations.py b/test/functional/abc-mempool-coherence-on-activations.py index 1e83cd786..e38ba8fb2 100755 --- a/test/functional/abc-mempool-coherence-on-activations.py +++ b/test/functional/abc-mempool-coherence-on-activations.py @@ -1,373 +1,373 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This test checks the mempool coherence when changing validation rulesets, which happens on (de)activations of network upgrades (forks). We test the mempool coherence in 3 cases: 1) on activations, pre-fork-only transactions are evicted from the mempool, while always-valid transactions remain. 2) on deactivations, post-fork-only transactions (unconfirmed or once confirmed) are evicted from the mempool, while always-valid transactions are reincluded. 3) on a reorg to a chain that deactivates and reactivates the fork, post-fork-only and always-valid transactions (unconfirmed and/or once confirmed on the shorter chain) are kept or reincluded in the mempool. """ from test_framework.blocktools import ( create_block, create_coinbase, create_tx_with_script, make_conform_to_ctor, ) from test_framework.key import ECKey from test_framework.messages import ( COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( OP_CHECKSIG, OP_TRUE, SIGHASH_ALL, SIGHASH_FORKID, CScript, SignatureHashForkId, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error # ---Code specific to the activation used for this test--- # It might change depending on the activation code currently existing in the # client software. We use the replay protection activation for this test. ACTIVATION_TIME = 2000000000 EXTRA_ARG = "-replayprotectionactivationtime={}".format(ACTIVATION_TIME) # simulation starts before activation FIRST_BLOCK_TIME = ACTIVATION_TIME - 86400 # Expected RPC error when trying to send an activation specific spend txn. RPC_EXPECTED_ERROR = "mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)" def create_fund_and_activation_specific_spending_tx(spend, pre_fork_only): # Creates 2 transactions: # 1) txfund: create outputs to be used by txspend. Must be valid pre-fork. # 2) txspend: spending transaction that is specific to the activation # being used and can be pre-fork-only or post-fork-only, depending on the # function parameter. # This specific implementation uses the replay protection mechanism to # create transactions that are only valid before or after the fork. # Generate a key pair to test private_key = ECKey() private_key.generate() public_key = private_key.get_pubkey().get_bytes() # Fund transaction script = CScript([public_key, OP_CHECKSIG]) txfund = create_tx_with_script( spend.tx, spend.n, b'', amount=50 * COIN, script_pub_key=script) txfund.rehash() # Activation specific spending tx txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction # Use forkvalues that create pre-fork-only or post-fork-only # transactions. forkvalue = 0 if pre_fork_only else 0xffdead sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID sighash = SignatureHashForkId( script, txspend, 0, sighashtype, 50 * COIN) sig = private_key.sign_ecdsa(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) txspend.vin[0].scriptSig = CScript([sig]) txspend.rehash() return txfund, txspend def create_fund_and_pre_fork_only_tx(spend): return create_fund_and_activation_specific_spending_tx( spend, pre_fork_only=True) def create_fund_and_post_fork_only_tx(spend): return create_fund_and_activation_specific_spending_tx( spend, pre_fork_only=False) # ---Mempool coherence on activations test--- class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class MempoolCoherenceOnActivationsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.extra_args = [['-whitelist=noban@127.0.0.1', EXTRA_ARG, '-acceptnonstdtxn=1']] def next_block(self, number): if self.tip is None: base_block_hash = self.genesis_hash block_time = FIRST_BLOCK_TIME else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Do PoW, which is cheap on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) node.setmocktime(ACTIVATION_TIME) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # 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) # 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 # send a txn to the mempool and check it was accepted def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert tx_id in node.getrawmempool() # checks the mempool has exactly the same txns as in the provided list def check_mempool_equal(txns): assert set(node.getrawmempool()) == set(tx.hash for tx in txns) # Create an always-valid chained transaction. It spends a # scriptPub=OP_TRUE coin into another. Returns the transaction and its # spendable output for further chaining. def create_always_valid_chained_tx(spend): tx = create_tx_with_script( spend.tx, spend.n, b'', amount=spend.tx.vout[0].nValue - 1000, script_pub_key=CScript([OP_TRUE])) tx.rehash() return tx, PreviousSpendableOutput(tx, 0) # shorthand block = self.next_block # Create a new block block(0) save_spendable_output() - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(110): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() - node.p2p.send_blocks_and_test(maturity_blocks, node) + peer.send_blocks_and_test(maturity_blocks, node) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Create 2 pre-fork-only txns (tx_pre0, tx_pre1). Fund txns are valid # pre-fork, so we can mine them right away. txfund0, tx_pre0 = create_fund_and_pre_fork_only_tx(out[0]) txfund1, tx_pre1 = create_fund_and_pre_fork_only_tx(out[1]) # Create 2 post-fork-only txns (tx_post0, tx_post1). Fund txns are # valid pre-fork, so we can mine them right away. txfund2, tx_post0 = create_fund_and_post_fork_only_tx(out[2]) txfund3, tx_post1 = create_fund_and_post_fork_only_tx(out[3]) # Create blocks to activate the fork. Mine all funding transactions. bfork = block(5555) bfork.nTime = ACTIVATION_TIME - 1 update_block(5555, [txfund0, txfund1, txfund2, txfund3]) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) for i in range(5): - node.p2p.send_blocks_and_test([block(5200 + i)], node) + peer.send_blocks_and_test([block(5200 + i)], node) # Check we are just before the activation time assert_equal( node.getblockchaininfo()['mediantime'], ACTIVATION_TIME - 1) # We are just before the fork. Pre-fork-only and always-valid chained # txns (tx_chain0, tx_chain1) are valid, post-fork-only txns are # rejected. send_transaction_to_mempool(tx_pre0) send_transaction_to_mempool(tx_pre1) tx_chain0, last_chained_output = create_always_valid_chained_tx(out[4]) tx_chain1, last_chained_output = create_always_valid_chained_tx( last_chained_output) send_transaction_to_mempool(tx_chain0) send_transaction_to_mempool(tx_chain1) assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, node.sendrawtransaction, ToHex(tx_post0)) assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, node.sendrawtransaction, ToHex(tx_post1)) check_mempool_equal([tx_chain0, tx_chain1, tx_pre0, tx_pre1]) # Activate the fork. Mine the 1st always-valid chained txn and a # pre-fork-only txn. block(5556) update_block(5556, [tx_chain0, tx_pre0]) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) forkblockid = node.getbestblockhash() # Check we just activated the fork assert_equal(node.getblockheader(forkblockid)['mediantime'], ACTIVATION_TIME) # Check mempool coherence when activating the fork. Pre-fork-only txns # were evicted from the mempool, while always-valid txns remain. # Evicted: tx_pre1 check_mempool_equal([tx_chain1]) # Post-fork-only and always-valid txns are accepted, pre-fork-only txn # are rejected. send_transaction_to_mempool(tx_post0) send_transaction_to_mempool(tx_post1) tx_chain2, _ = create_always_valid_chained_tx(last_chained_output) send_transaction_to_mempool(tx_chain2) assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, node.sendrawtransaction, ToHex(tx_pre1)) check_mempool_equal([tx_chain1, tx_chain2, tx_post0, tx_post1]) # Mine the 2nd always-valid chained txn and a post-fork-only txn. block(5557) update_block(5557, [tx_chain1, tx_post0]) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) postforkblockid = node.getbestblockhash() # The mempool contains the 3rd chained txn and a post-fork-only txn. check_mempool_equal([tx_chain2, tx_post1]) # In the following we will testing block disconnections and reorgs. # - tx_chain2 will always be retained in the mempool since it is always # valid. Its continued presence shows that we are never simply # clearing the entire mempool. # - tx_post1 may be evicted from mempool if we land before the fork. # - tx_post0 is in a block and if 'de-mined', it will either be evicted # or end up in mempool depending if we land before/after the fork. # - tx_pre0 is in a block and if 'de-mined', it will either be evicted # or end up in mempool depending if we land after/before the fork. # First we do a disconnection of the post-fork block, which is a # normal disconnection that merely returns the block contents into # the mempool -- nothing is lost. node.invalidateblock(postforkblockid) # In old mempool: tx_chain2, tx_post1 # Recovered from blocks: tx_chain1 and tx_post0. # Lost from blocks: NONE # Retained from old mempool: tx_chain2, tx_post1 # Evicted from old mempool: NONE check_mempool_equal([tx_chain1, tx_chain2, tx_post0, tx_post1]) # Now, disconnect the fork block. This is a special disconnection # that requires reprocessing the mempool due to change in rules. node.invalidateblock(forkblockid) # In old mempool: tx_chain1, tx_chain2, tx_post0, tx_post1 # Recovered from blocks: tx_chain0, tx_pre0 # Lost from blocks: NONE # Retained from old mempool: tx_chain1, tx_chain2 # Evicted from old mempool: tx_post0, tx_post1 check_mempool_equal([tx_chain0, tx_chain1, tx_chain2, tx_pre0]) # Restore state node.reconsiderblock(postforkblockid) node.reconsiderblock(forkblockid) send_transaction_to_mempool(tx_post1) check_mempool_equal([tx_chain2, tx_post1]) # Test a reorg that crosses the fork. # If such a reorg happens, most likely it will both start *and end* # after the fork. We will test such a case here and make sure that # post-fork-only transactions are not unnecessarily discarded from # the mempool in such a reorg. Pre-fork-only transactions however can # get lost. # Set up a longer competing chain that doesn't confirm any of our txns. # This starts after 5204, so it contains neither the forkblockid nor # the postforkblockid from above. self.tip = self.blocks[5204] reorg_blocks = [] for i in range(3): reorg_blocks.append(block(5900 + i)) # Perform the reorg - node.p2p.send_blocks_and_test(reorg_blocks, node) + peer.send_blocks_and_test(reorg_blocks, node) # reorg finishes after the fork assert_equal( node.getblockchaininfo()['mediantime'], ACTIVATION_TIME + 2) # In old mempool: tx_chain2, tx_post1 # Recovered from blocks: tx_chain0, tx_chain1, tx_post0 # Lost from blocks: tx_pre0 # Retained from old mempool: tx_chain2, tx_post1 # Evicted from old mempool: NONE check_mempool_equal( [tx_chain0, tx_chain1, tx_chain2, tx_post0, tx_post1]) if __name__ == '__main__': MempoolCoherenceOnActivationsTest().main() diff --git a/test/functional/abc-minimaldata.py b/test/functional/abc-minimaldata.py index 647733744..dfb1f0c98 100755 --- a/test/functional/abc-minimaldata.py +++ b/test/functional/abc-minimaldata.py @@ -1,180 +1,173 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This tests the MINIMALDATA consensus rule. - test rejection in mempool, with error changing before/after activation. - test acceptance in blocks before activation, and rejection after. - check non-banning for peers who send invalid txns that would have been valid on the other side of the upgrade. Derived from abc-schnorr.py """ from test_framework.blocktools import ( create_block, create_coinbase, create_tx_with_script, make_conform_to_ctor, ) from test_framework.messages import ( CBlock, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import OP_ADD, OP_TRUE, CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx from test_framework.util import assert_raises_rpc_error # Minimal push violations in mempool are rejected with a bannable error. MINIMALPUSH_ERROR = 'mandatory-script-verify-flag-failed (Data push larger than necessary)' # Blocks with invalid scripts give this error: BADINPUTS_ERROR = 'blk-bad-inputs' class MinimaldataTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.block_heights = {} self.extra_args = [['-acceptnonstdtxn=1']] - def bootstrap_p2p(self, *, num_connections=1): - """Add a P2P connection to the node. - - Helper to connect and wait for version handshake.""" - for _ in range(num_connections): - self.nodes[0].add_p2p_connection(P2PDataStore()) - - def reconnect_p2p(self, **kwargs): + def reconnect_p2p(self): """Tear down and bootstrap the P2P connection to the node. The node gets disconnected several times in this test. This helper method reconnects the p2p and restarts the network thread.""" self.nodes[0].disconnect_p2ps() - self.bootstrap_p2p(**kwargs) + self.nodes[0].add_p2p_connection(P2PDataStore()) def getbestblock(self, node): """Get the best block. Register its height so we can use build_block.""" block_height = node.getblockcount() blockhash = node.getblockhash(block_height) block = FromHex(CBlock(), node.getblock(blockhash, 0)) block.calc_sha256() self.block_heights[block.sha256] = block_height return block def build_block(self, parent, transactions=(), nTime=None): """Make a new block with an OP_1 coinbase output. Requires parent to have its height registered.""" parent.calc_sha256() block_height = self.block_heights[parent.sha256] + 1 block_time = (parent.nTime + 1) if nTime is None else nTime block = create_block( parent.sha256, create_coinbase(block_height), block_time) block.vtx.extend(transactions) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.block_heights[block.sha256] = block_height return block def check_for_ban_on_rejected_tx(self, tx, reject_reason=None): """Check we are disconnected when sending a txn that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_txs_and_test( + self.nodes[0].p2ps[0].send_txs_and_test( [tx], self.nodes[0], success=False, expect_disconnect=True, reject_reason=reject_reason) self.reconnect_p2p() def check_for_ban_on_rejected_block(self, block, reject_reason=None): """Check we are disconnected when sending a block that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_blocks_and_test( + self.nodes[0].p2ps[0].send_blocks_and_test( [block], self.nodes[0], success=False, reject_reason=reject_reason, expect_disconnect=True) self.reconnect_p2p() def run_test(self): node, = self.nodes - self.bootstrap_p2p() + self.nodes[0].add_p2p_connection(P2PDataStore()) tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node, success=True) + node.p2ps[0].send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] self.log.info("Mature the blocks and get out of IBD.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.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_tx_with_script( spendfrom, 0, b'', amount=value, script_pub_key=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 txspend.vin[0].scriptSig = CScript( b'\x01\x01\x51') # PUSH1(0x01) OP_1 pad_tx(txspend) txspend.rehash() return txspend # Non minimal tx are invalid. nonminimaltx = create_fund_and_spend_tx() tip = self.build_block(tip, fundings) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) self.log.info("Trying to mine a minimaldata violation.") self.check_for_ban_on_rejected_block( self.build_block(tip, [nonminimaltx]), BADINPUTS_ERROR) self.log.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) self.log.info("Mine a normal block") tip = self.build_block(tip) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) if __name__ == '__main__': MinimaldataTest().main() diff --git a/test/functional/abc-replay-protection.py b/test/functional/abc-replay-protection.py index a88ebf3c2..05aab84fe 100755 --- a/test/functional/abc-replay-protection.py +++ b/test/functional/abc-replay-protection.py @@ -1,322 +1,322 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This test checks activation of UAHF and the different consensus related to this activation. It is derived from the much more complex p2p-fullblocktest. """ import time from test_framework.blocktools import ( create_block, create_coinbase, create_tx_with_script, make_conform_to_ctor, ) from test_framework.key import ECKey from test_framework.messages import ( COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( OP_CHECKSIG, OP_TRUE, SIGHASH_ALL, SIGHASH_FORKID, CScript, SignatureHashForkId, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error # far into the future REPLAY_PROTECTION_START_TIME = 2000000000 # Error due to invalid signature RPC_INVALID_SIGNATURE_ERROR = "mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)" class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class ReplayProtectionTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.extra_args = [['-whitelist=noban@127.0.0.1', "-replayprotectionactivationtime={}".format( REPLAY_PROTECTION_START_TIME), "-acceptnonstdtxn=1"]] def next_block(self, number): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Do PoW, which is cheap on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def set_tip(self, number: int): """ Move the tip back to a previous block. """ self.tip = self.blocks[number] def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) node.setmocktime(REPLAY_PROTECTION_START_TIME) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # 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) # 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 # shorthand block = self.next_block # Create a new block block(0) save_spendable_output() - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(99): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() - node.p2p.send_blocks_and_test(maturity_blocks, node) + peer.send_blocks_and_test(maturity_blocks, node) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Generate a key pair to test P2SH sigops count private_key = ECKey() private_key.generate() public_key = private_key.get_pubkey().get_bytes() # This is a little handier to use than the version in blocktools.py def create_fund_and_spend_tx(spend, forkvalue=0): # Fund transaction script = CScript([public_key, OP_CHECKSIG]) txfund = create_tx_with_script( spend.tx, spend.n, b'', amount=50 * COIN - 1000, script_pub_key=script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 2000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID sighash = SignatureHashForkId( script, txspend, 0, sighashtype, 50 * COIN - 1000) sig = private_key.sign_ecdsa(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) txspend.vin[0].scriptSig = CScript([sig]) txspend.rehash() return [txfund, txspend] def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert tx_id in set(node.getrawmempool()) return tx_id # Before the fork, no replay protection required to get in the mempool. txns = create_fund_and_spend_tx(out[0]) send_transaction_to_mempool(txns[0]) send_transaction_to_mempool(txns[1]) # And txns get mined in a block properly. block(1) update_block(1, txns) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Replay protected transactions are rejected. replay_txns = create_fund_and_spend_tx(out[1], 0xffdead) send_transaction_to_mempool(replay_txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) # And block containing them are rejected as well. block(2) update_block(2, replay_txns) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='blk-bad-inputs') # Rewind bad block self.set_tip(1) # Create a block that would activate the replay protection. bfork = block(5555) bfork.nTime = REPLAY_PROTECTION_START_TIME - 1 update_block(5555, []) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) activation_blocks = [] for i in range(5): block(5100 + i) activation_blocks.append(self.tip) - node.p2p.send_blocks_and_test(activation_blocks, node) + peer.send_blocks_and_test(activation_blocks, node) # Check we are just before the activation time assert_equal( node.getblockchaininfo()['mediantime'], REPLAY_PROTECTION_START_TIME - 1) # We are just before the fork, replay protected txns still are rejected assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) block(3) update_block(3, replay_txns) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='blk-bad-inputs') # Rewind bad block self.set_tip(5104) # Send some non replay protected txns in the mempool to check # they get cleaned at activation. txns = create_fund_and_spend_tx(out[2]) send_transaction_to_mempool(txns[0]) tx_id = send_transaction_to_mempool(txns[1]) # Activate the replay protection block(5556) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Check we just activated the replay protection assert_equal( node.getblockchaininfo()['mediantime'], REPLAY_PROTECTION_START_TIME) # Non replay protected transactions are not valid anymore, # so they should be removed from the mempool. assert tx_id not in set(node.getrawmempool()) # Good old transactions are now invalid. send_transaction_to_mempool(txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(txns[1])) # They also cannot be mined block(4) update_block(4, txns) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='blk-bad-inputs') # Rewind bad block self.set_tip(5556) # The replay protected transaction is now valid replay_tx0_id = send_transaction_to_mempool(replay_txns[0]) replay_tx1_id = send_transaction_to_mempool(replay_txns[1]) # Make sure the transaction are ready to be mined. tmpl = node.getblocktemplate() found_id0 = False found_id1 = False for txn in tmpl['transactions']: txid = txn['txid'] if txid == replay_tx0_id: found_id0 = True elif txid == replay_tx1_id: found_id1 = True assert found_id0 and found_id1 # And the mempool is still in good shape. assert replay_tx0_id in set(node.getrawmempool()) assert replay_tx1_id in set(node.getrawmempool()) # They also can also be mined block(5) update_block(5, replay_txns) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Ok, now we check if a reorg work properly across the activation. postforkblockid = node.getbestblockhash() node.invalidateblock(postforkblockid) assert replay_tx0_id in set(node.getrawmempool()) assert replay_tx1_id in set(node.getrawmempool()) # Deactivating replay protection. forkblockid = node.getbestblockhash() node.invalidateblock(forkblockid) # The funding tx is not evicted from the mempool, since it's valid in # both sides of the fork assert replay_tx0_id in set(node.getrawmempool()) assert replay_tx1_id not in set(node.getrawmempool()) # Check that we also do it properly on deeper reorg. node.reconsiderblock(forkblockid) node.reconsiderblock(postforkblockid) node.invalidateblock(forkblockid) assert replay_tx0_id in set(node.getrawmempool()) assert replay_tx1_id not in set(node.getrawmempool()) if __name__ == '__main__': ReplayProtectionTest().main() diff --git a/test/functional/abc-schnorr.py b/test/functional/abc-schnorr.py index 56ca86a96..5fc5ee06d 100755 --- a/test/functional/abc-schnorr.py +++ b/test/functional/abc-schnorr.py @@ -1,245 +1,239 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This tests the treatment of Schnorr transaction signatures: - acceptance both in mempool and blocks. - check banning for peers who send txns with 64 byte ECDSA DER sigs. Derived from a variety of functional tests. """ from test_framework.blocktools import ( create_block, create_coinbase, create_tx_with_script, make_conform_to_ctor, ) from test_framework.key import ECKey from test_framework.messages import ( CBlock, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( OP_1, OP_CHECKMULTISIG, OP_CHECKSIG, OP_TRUE, SIGHASH_ALL, SIGHASH_FORKID, CScript, SignatureHashForkId, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_raises_rpc_error # A mandatory (bannable) error occurs when people pass Schnorr signatures # into OP_CHECKMULTISIG. SCHNORR_MULTISIG_ERROR = 'mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' # A mandatory (bannable) error occurs when people send invalid Schnorr # sigs into OP_CHECKSIG. NULLFAIL_ERROR = 'mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)' # Blocks with invalid scripts give this error: BADINPUTS_ERROR = 'blk-bad-inputs' # This 64-byte signature is used to test exclusion & banning according to # the above error messages. # Tests of real 64 byte ECDSA signatures can be found in script_tests. sig64 = b'\0' * 64 class SchnorrTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.block_heights = {} self.extra_args = [[ "-acceptnonstdtxn=1"]] - def bootstrap_p2p(self, *, num_connections=1): - """Add a P2P connection to the node. - - Helper to connect and wait for version handshake.""" - for _ in range(num_connections): - self.nodes[0].add_p2p_connection(P2PDataStore()) - - def reconnect_p2p(self, **kwargs): + def reconnect_p2p(self): """Tear down and bootstrap the P2P connection to the node. The node gets disconnected several times in this test. This helper method reconnects the p2p and restarts the network thread.""" self.nodes[0].disconnect_p2ps() - self.bootstrap_p2p(**kwargs) + self.nodes[0].add_p2p_connection(P2PDataStore()) def getbestblock(self, node): """Get the best block. Register its height so we can use build_block.""" block_height = node.getblockcount() blockhash = node.getblockhash(block_height) block = FromHex(CBlock(), node.getblock(blockhash, 0)) block.calc_sha256() self.block_heights[block.sha256] = block_height return block def build_block(self, parent, transactions=(), nTime=None): """Make a new block with an OP_1 coinbase output. Requires parent to have its height registered.""" parent.calc_sha256() block_height = self.block_heights[parent.sha256] + 1 block_time = (parent.nTime + 1) if nTime is None else nTime block = create_block( parent.sha256, create_coinbase(block_height), block_time) block.vtx.extend(transactions) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.block_heights[block.sha256] = block_height return block def check_for_ban_on_rejected_tx(self, tx, reject_reason=None): """Check we are disconnected when sending a txn that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_txs_and_test( + self.nodes[0].p2ps[0].send_txs_and_test( [tx], self.nodes[0], success=False, reject_reason=reject_reason, expect_disconnect=True) self.reconnect_p2p() def check_for_ban_on_rejected_block(self, block, reject_reason=None): """Check we are disconnected when sending a block that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_blocks_and_test( + self.nodes[0].p2ps[0].send_blocks_and_test( [block], self.nodes[0], success=False, reject_reason=reject_reason, expect_disconnect=True) self.reconnect_p2p() def run_test(self): node, = self.nodes - self.bootstrap_p2p() + self.nodes[0].add_p2p_connection(P2PDataStore()) tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node, success=True) + node.p2ps[0].send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] self.log.info("Mature the blocks and get out of IBD.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair private_key = ECKey() private_key.set(b"Schnorr!" * 4, True) # get uncompressed public key serialization public_key = private_key.get_pubkey().get_bytes() def create_fund_and_spend_tx(multi=False, sig='schnorr'): spendfrom = spendable_outputs.pop() if multi: script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) else: script = CScript([public_key, OP_CHECKSIG]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_tx_with_script( spendfrom, 0, b'', amount=value, script_pub_key=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 sig == 'schnorr': txsig = private_key.sign_schnorr(sighash) + hashbyte elif sig == 'ecdsa': txsig = private_key.sign_ecdsa(sighash) + hashbyte elif isinstance(sig, bytes): txsig = sig + hashbyte if multi: txspend.vin[0].scriptSig = CScript([b'', txsig]) else: txspend.vin[0].scriptSig = CScript([txsig]) txspend.rehash() return txspend schnorrchecksigtx = create_fund_and_spend_tx() schnorrmultisigtx = create_fund_and_spend_tx(multi=True) ecdsachecksigtx = create_fund_and_spend_tx(sig='ecdsa') sig64checksigtx = create_fund_and_spend_tx(sig=sig64) sig64multisigtx = create_fund_and_spend_tx(multi=True, sig=sig64) tip = self.build_block(tip, fundings) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) self.log.info("Typical ECDSA and Schnorr CHECKSIG are valid.") - node.p2p.send_txs_and_test([schnorrchecksigtx, ecdsachecksigtx], node) + node.p2ps[0].send_txs_and_test( + [schnorrchecksigtx, ecdsachecksigtx], node) # They get mined as usual. node.generatetoaddress(1, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) # Make sure they are in the block, and mempool is now empty. txhashes = set([schnorrchecksigtx.hash, ecdsachecksigtx.hash]) assert txhashes.issubset(tx.rehash() for tx in tip.vtx) assert not node.getrawmempool() self.log.info("Schnorr in multisig is rejected with mandatory error.") assert_raises_rpc_error(-26, SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # And it is banworthy. self.check_for_ban_on_rejected_tx( schnorrmultisigtx, SCHNORR_MULTISIG_ERROR) # And it can't be mined self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorrmultisigtx]), BADINPUTS_ERROR) self.log.info("Bad 64-byte sig is rejected with mandatory error.") # In CHECKSIG it's invalid Schnorr and hence NULLFAIL. assert_raises_rpc_error(-26, NULLFAIL_ERROR, node.sendrawtransaction, ToHex(sig64checksigtx)) # In CHECKMULTISIG it's invalid length and hence BAD_LENGTH. assert_raises_rpc_error(-26, SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(sig64multisigtx)) # Sending these transactions is banworthy. self.check_for_ban_on_rejected_tx(sig64checksigtx, NULLFAIL_ERROR) self.check_for_ban_on_rejected_tx( sig64multisigtx, SCHNORR_MULTISIG_ERROR) # And they can't be mined either... self.check_for_ban_on_rejected_block( self.build_block(tip, [sig64checksigtx]), BADINPUTS_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [sig64multisigtx]), BADINPUTS_ERROR) if __name__ == '__main__': SchnorrTest().main() diff --git a/test/functional/abc-schnorrmultisig.py b/test/functional/abc-schnorrmultisig.py index fcf6116c8..d94aef51d 100755 --- a/test/functional/abc-schnorrmultisig.py +++ b/test/functional/abc-schnorrmultisig.py @@ -1,244 +1,237 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This tests the CHECKMULTISIG mode that uses Schnorr transaction signatures and repurposes the dummy element to indicate which signatures are being checked. - acceptance both in mempool and blocks. - check non-banning for peers who send invalid txns that would have been valid on the other side of the upgrade. - check banning of peers for some fully-invalid transactions. Derived from abc-schnorr.py """ from test_framework.blocktools import ( create_block, create_coinbase, create_tx_with_script, make_conform_to_ctor, ) from test_framework.key import ECKey from test_framework.messages import ( CBlock, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( OP_0, OP_1, OP_CHECKMULTISIG, OP_TRUE, SIGHASH_ALL, SIGHASH_FORKID, CScript, SignatureHashForkId, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error # ECDSA checkmultisig with non-null dummy are invalid since the new mode # refuses ECDSA. ECDSA_NULLDUMMY_ERROR = 'mandatory-script-verify-flag-failed (Only Schnorr signatures allowed in this operation)' # A mandatory (bannable) error occurs when people pass Schnorr signatures into # legacy OP_CHECKMULTISIG. SCHNORR_LEGACY_MULTISIG_ERROR = 'mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' # Blocks with invalid scripts give this error: BADINPUTS_ERROR = 'blk-bad-inputs' # This 64-byte signature is used to test exclusion & banning according to # the above error messages. # Tests of real 64 byte ECDSA signatures can be found in script_tests. sig64 = b'\0' * 64 class SchnorrMultisigTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.block_heights = {} self.extra_args = [["-acceptnonstdtxn=1"]] - def bootstrap_p2p(self, *, num_connections=1): - """Add a P2P connection to the node. - - Helper to connect and wait for version handshake.""" - for _ in range(num_connections): - self.nodes[0].add_p2p_connection(P2PDataStore()) - - def reconnect_p2p(self, **kwargs): + def reconnect_p2p(self): """Tear down and bootstrap the P2P connection to the node. The node gets disconnected several times in this test. This helper method reconnects the p2p and restarts the network thread.""" self.nodes[0].disconnect_p2ps() - self.bootstrap_p2p(**kwargs) + self.nodes[0].add_p2p_connection(P2PDataStore()) def getbestblock(self, node): """Get the best block. Register its height so we can use build_block.""" block_height = node.getblockcount() blockhash = node.getblockhash(block_height) block = FromHex(CBlock(), node.getblock(blockhash, 0)) block.calc_sha256() self.block_heights[block.sha256] = block_height return block def build_block(self, parent, transactions=(), nTime=None): """Make a new block with an OP_1 coinbase output. Requires parent to have its height registered.""" parent.calc_sha256() block_height = self.block_heights[parent.sha256] + 1 block_time = (parent.nTime + 1) if nTime is None else nTime block = create_block( parent.sha256, create_coinbase(block_height), block_time) block.vtx.extend(transactions) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.block_heights[block.sha256] = block_height return block def check_for_ban_on_rejected_tx(self, tx, reject_reason=None): """Check we are disconnected when sending a txn that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_txs_and_test( + self.nodes[0].p2ps[0].send_txs_and_test( [tx], self.nodes[0], success=False, expect_disconnect=True, reject_reason=reject_reason) self.reconnect_p2p() def check_for_ban_on_rejected_block(self, block, reject_reason=None): """Check we are disconnected when sending a block that the node rejects. (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_blocks_and_test( + self.nodes[0].p2ps[0].send_blocks_and_test( [block], self.nodes[0], success=False, reject_reason=reject_reason, expect_disconnect=True) self.reconnect_p2p() def run_test(self): node, = self.nodes - self.bootstrap_p2p() + self.nodes[0].add_p2p_connection(P2PDataStore()) tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node, success=True) + node.p2ps[0].send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] self.log.info("Mature the blocks and get out of IBD.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair private_key = ECKey() private_key.set(b"Schnorr!" * 4, True) # get uncompressed public key serialization public_key = private_key.get_pubkey().get_bytes() 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_tx_with_script( spendfrom, 0, b'', amount=value, script_pub_key=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 = private_key.sign_schnorr(sighash) + hashbyte elif sigtype == 'ecdsa': txsig = private_key.sign_ecdsa(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) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) self.log.info("Send a legacy ECDSA multisig into mempool.") - node.p2p.send_txs_and_test([ecdsa0tx], node) + node.p2ps[0].send_txs_and_test([ecdsa0tx], node) assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) self.log.info("Trying to mine a non-null-dummy ECDSA.") self.check_for_ban_on_rejected_block( self.build_block(tip, [ecdsa1tx]), BADINPUTS_ERROR) self.log.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) self.log.info( "Submitting a Schnorr-multisig via net, and mining it in a block") - node.p2p.send_txs_and_test([schnorr1tx], node) + node.p2ps[0].send_txs_and_test([schnorr1tx], node) assert_equal(set(node.getrawmempool()), { ecdsa0tx.hash, schnorr1tx.hash}) tip = self.build_block(tip, [schnorr1tx]) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) self.log.info( "That legacy ECDSA multisig is still in mempool, let's mine it") assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) tip = self.build_block(tip, [ecdsa0tx]) - node.p2p.send_blocks_and_test([tip], node) + node.p2ps[0].send_blocks_and_test([tip], node) assert_equal(node.getrawmempool(), []) self.log.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) if __name__ == '__main__': SchnorrMultisigTest().main() diff --git a/test/functional/abc-segwit-recovery.py b/test/functional/abc-segwit-recovery.py index cc41a126a..2c0d1592c 100755 --- a/test/functional/abc-segwit-recovery.py +++ b/test/functional/abc-segwit-recovery.py @@ -1,272 +1,248 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This test checks that blocks containing segwit recovery transactions will be accepted, that segwit recovery transactions are rejected from mempool acceptance (even with -acceptnonstdtxn=1), and that segwit recovery transactions don't result in bans. """ import time from test_framework.blocktools import ( create_block, create_coinbase, make_conform_to_ctor, ) from test_framework.messages import ( COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( OP_EQUAL, OP_HASH160, OP_TRUE, CScript, hash160, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_raises_rpc_error TEST_TIME = int(time.time()) # Error due to non clean stack CLEANSTACK_ERROR = 'non-mandatory-script-verify-flag (Extra items left on stack after execution)' RPC_CLEANSTACK_ERROR = CLEANSTACK_ERROR EVAL_FALSE_ERROR = 'non-mandatory-script-verify-flag (Script evaluated without error but finished with a false/empty top stack elem' RPC_EVAL_FALSE_ERROR = EVAL_FALSE_ERROR + "ent)" class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class SegwitRecoveryTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} # We have 2 nodes: # 1) node_nonstd (nodes[0]) accepts non-standard txns. It does not # accept Segwit recovery transactions, since it is included in # standard flags, and transactions that violate these flags are # never accepted into the mempool. # 2) node_std (nodes[1]) doesn't accept non-standard txns and # doesn't have us whitelisted. It's used to test for bans, as we # connect directly to it via mininode and send a segwit spending # txn. This transaction is non-standard. We check that sending # this transaction doesn't result in a ban. # Nodes are connected to each other, so node_std receives blocks and # transactions that node_nonstd has accepted. Since we are checking # that segwit spending txn are not resulting in bans, node_nonstd # doesn't get banned when forwarding this kind of transactions to # node_std. self.extra_args = [['-whitelist=noban@127.0.0.1', "-acceptnonstdtxn"], ["-acceptnonstdtxn=0"]] def next_block(self, number): if self.tip is None: base_block_hash = self.genesis_hash block_time = TEST_TIME else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Do PoW, which is cheap on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block - def bootstrap_p2p(self, *, num_connections=1): - """Add a P2P connection to the node. - - Helper to connect and wait for version handshake.""" - for node in self.nodes: - for _ in range(num_connections): - node.add_p2p_connection(P2PDataStore()) - - def reconnect_p2p(self, **kwargs): - """Tear down and bootstrap the P2P connection to the node. - - The node gets disconnected several times in this test. This helper - method reconnects the p2p and restarts the network thread.""" - for node in self.nodes: - node.disconnect_p2ps() - self.bootstrap_p2p(**kwargs) - 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] + peer_nonstd = node_nonstd.add_p2p_connection(P2PDataStore()) + peer_std = node_std.add_p2p_connection(P2PDataStore()) + # 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(node): - node.p2p.send_blocks_and_test([self.tip], node) - # 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(node, tx, reject_reason): - node.p2p.send_txs_and_test( - [tx], node, success=False, reject_reason=reject_reason) - # Create a new block block(0) save_spendable_output() - accepted(node_nonstd) + peer_nonstd.send_blocks_and_test([self.tip], 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() - node_nonstd.p2p.send_blocks_and_test(matureblocks, node_nonstd) + peer_nonstd.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]) - accepted(node_nonstd) + peer_nonstd.send_blocks_and_test([self.tip], node_nonstd) # Check both nodes are synchronized before continuing. self.sync_blocks() # Check that upgraded nodes checking for standardness are not banning # nodes sending segwit spending txns. - check_for_no_ban_on_rejected_tx( - node_nonstd, txspend, CLEANSTACK_ERROR) - check_for_no_ban_on_rejected_tx( - node_nonstd, txspend_case0, EVAL_FALSE_ERROR) - check_for_no_ban_on_rejected_tx( - node_std, txspend, CLEANSTACK_ERROR) - check_for_no_ban_on_rejected_tx( - node_std, txspend_case0, EVAL_FALSE_ERROR) + peer_nonstd.send_txs_and_test([txspend], node_nonstd, success=False, + reject_reason=CLEANSTACK_ERROR) + peer_nonstd.send_txs_and_test([txspend_case0], node_nonstd, success=False, + reject_reason=EVAL_FALSE_ERROR) + peer_std.send_txs_and_test([txspend], node_std, success=False, + reject_reason=CLEANSTACK_ERROR) + peer_std.send_txs_and_test([txspend_case0], node_std, success=False, + reject_reason=EVAL_FALSE_ERROR) # Segwit recovery txns are never accepted into the mempool, # as they are included in standard flags. assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, node_nonstd.sendrawtransaction, ToHex(txspend)) assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, node_nonstd.sendrawtransaction, ToHex(txspend_case0)) assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, node_std.sendrawtransaction, ToHex(txspend)) assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, node_std.sendrawtransaction, ToHex(txspend_case0)) # Blocks containing segwit spending txns are accepted in both nodes. block(5) update_block(5, [txspend, txspend_case0]) - accepted(node_nonstd) + peer_nonstd.send_blocks_and_test([self.tip], node_nonstd) self.sync_blocks() if __name__ == '__main__': SegwitRecoveryTest().main() diff --git a/test/functional/abc-transaction-ordering.py b/test/functional/abc-transaction-ordering.py index a688505cb..43cba40a8 100755 --- a/test/functional/abc-transaction-ordering.py +++ b/test/functional/abc-transaction-ordering.py @@ -1,227 +1,227 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This test checks that the node software accepts transactions in non topological order once the feature is activated. """ import random import time from collections import deque from test_framework.blocktools import ( create_block, create_coinbase, make_conform_to_ctor, ) from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut from test_framework.p2p import P2PDataStore from test_framework.script import OP_RETURN, OP_TRUE, CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class PreviousSpendableOutput(): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n # the output we're spending class TransactionOrderingTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.extra_args = [['-whitelist=noban@127.0.0.1']] def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) def next_block(self, number, spend=None, tx_count=0): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() if spend is None: # We need to have something to spend to fill the block. block = create_block(base_block_hash, coinbase, block_time) else: # all but one satoshi to fees coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Make sure we have plenty enough to spend going forward. spendable_outputs = deque([spend]) def get_base_transaction(): # Create the new transaction tx = CTransaction() # Spend from one of the spendable outputs spend = spendable_outputs.popleft() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n))) # Add spendable outputs for i in range(4): tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) spendable_outputs.append(PreviousSpendableOutput(tx, i)) # Put some random data into the transaction in order to randomize ids. # This also ensures that transaction are larger than 100 bytes. rand = random.getrandbits(256) tx.vout.append(CTxOut(0, CScript([rand, OP_RETURN]))) return tx tx = get_base_transaction() # Make it the same format as transaction added for padding and save the size. # It's missing the padding output, so we add a constant to account # for it. tx.rehash() # Add the transaction to the block self.add_transactions_to_block(block, [tx]) # If we have a transaction count requirement, just fill the block # until we get there while len(block.vtx) < tx_count: # Create the new transaction and add it. tx = get_base_transaction() self.add_transactions_to_block(block, [tx]) # Now that we added a bunch of transaction, we need to recompute # the merkle root. block.hashMerkleRoot = block.calc_merkle_root() if tx_count > 0: assert_equal(len(block.vtx), tx_count) # Do PoW, which is cheap on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def set_tip(self, number: int): """ Move the tip back to a previous block. """ self.tip = self.blocks[number] def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # 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) # update block state def update_block(block_number): block = self.blocks[block_number] old_sha256 = block.sha256 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 # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(99): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() - node.p2p.send_blocks_and_test(maturity_blocks, node) + peer.send_blocks_and_test(maturity_blocks, node) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(17): n = i + 1 - node.p2p.send_blocks_and_test([block(n)], node) + peer.send_blocks_and_test([block(n)], node) - node.p2p.send_blocks_and_test([block(5556)], node) + peer.send_blocks_and_test([block(5556)], node) # Block with regular ordering are now rejected. - node.p2p.send_blocks_and_test([block( + peer.send_blocks_and_test([block( 5557, out[17], tx_count=16)], node, success=False, reject_reason='tx-ordering') # Rewind bad block. self.set_tip(5556) # After we activate the Nov 15, 2018 HF, transaction order is enforced. def ordered_block(block_number, spend): b = block(block_number, spend=spend, tx_count=16) make_conform_to_ctor(b) update_block(block_number) return b # Now that the fork activated, we need to order transaction per txid. - node.p2p.send_blocks_and_test([ordered_block(4445, out[17])], node) - node.p2p.send_blocks_and_test([ordered_block(4446, out[18])], node) + peer.send_blocks_and_test([ordered_block(4445, out[17])], node) + peer.send_blocks_and_test([ordered_block(4446, out[18])], node) # Generate a block with a duplicated transaction. double_tx_block = ordered_block(4447, out[19]) assert_equal(len(double_tx_block.vtx), 16) double_tx_block.vtx = double_tx_block.vtx[:8] + \ [double_tx_block.vtx[8]] + double_tx_block.vtx[8:] update_block(4447) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='bad-txns-duplicate') # Rewind bad block. self.set_tip(4446) # Check over two blocks. proper_block = ordered_block(4448, out[20]) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) replay_tx_block = ordered_block(4449, out[21]) assert_equal(len(replay_tx_block.vtx), 16) replay_tx_block.vtx.append(proper_block.vtx[5]) replay_tx_block.vtx = [replay_tx_block.vtx[0]] + \ sorted(replay_tx_block.vtx[1:], key=lambda tx: tx.get_id()) update_block(4449) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='bad-txns-BIP30') if __name__ == '__main__': TransactionOrderingTest().main() diff --git a/test/functional/abc_p2p_fullblocktest.py b/test/functional/abc_p2p_fullblocktest.py index 3df5de38f..b5fcd6132 100755 --- a/test/functional/abc_p2p_fullblocktest.py +++ b/test/functional/abc_p2p_fullblocktest.py @@ -1,256 +1,256 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ This test checks simple acceptance of bigger blocks via p2p. It is derived from the much more complex p2p-fullblocktest. The intention is that small tests can be derived from this one, or this one can be extended, to cover the checks done for bigger blocks (e.g. sigops limits). """ import random import time from collections import deque from test_framework.blocktools import ( create_block, create_coinbase, make_conform_to_ctor, ) from test_framework.cdefs import ONE_MEGABYTE from test_framework.messages import ( COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ser_compact_size, ) from test_framework.p2p import P2PDataStore from test_framework.script import OP_RETURN, OP_TRUE, CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class PreviousSpendableOutput(): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx # the output we're spending self.n = n class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.excessive_block_size = 100 * ONE_MEGABYTE self.extra_args = [['-whitelist=noban@127.0.0.1', "-excessiveblocksize={}".format(self.excessive_block_size)]] self.supports_cli = False # The default timeout is not enough when submitting large blocks with # TSAN enabled self.rpc_timeout = 360 def add_options(self, parser): super().add_options(parser) parser.add_argument( "--runbarelyexpensive", dest="runbarelyexpensive", default=True) def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) def next_block(self, number, spend=None, script=CScript([OP_TRUE]), block_size=0): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height) coinbase.rehash() if spend is None: # We need to have something to spend to fill the block. assert_equal(block_size, 0) block = create_block(base_block_hash, coinbase, block_time) else: # all but one satoshi to fees coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Make sure we have plenty engough to spend going forward. spendable_outputs = deque([spend]) def get_base_transaction(): # Create the new transaction tx = CTransaction() # Spend from one of the spendable outputs spend = spendable_outputs.popleft() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n))) # Add spendable outputs for i in range(4): tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) spendable_outputs.append(PreviousSpendableOutput(tx, i)) return tx tx = get_base_transaction() # Make it the same format as transaction added for padding and save the size. # It's missing the padding output, so we add a constant to account # for it. tx.rehash() base_tx_size = len(tx.serialize()) + 18 # If a specific script is required, add it. if script is not None: tx.vout.append(CTxOut(1, script)) # Put some random data into the first transaction of the chain to # randomize ids. tx.vout.append( CTxOut(0, CScript([random.randint(0, 256), OP_RETURN]))) # Add the transaction to the block self.add_transactions_to_block(block, [tx]) # If we have a block size requirement, just fill # the block until we get there current_block_size = len(block.serialize()) while current_block_size < block_size: # We will add a new transaction. That means the size of # the field enumerating how many transaction go in the block # may change. current_block_size -= len(ser_compact_size(len(block.vtx))) current_block_size += len(ser_compact_size(len(block.vtx) + 1)) # Create the new transaction tx = get_base_transaction() # Add padding to fill the block. script_length = block_size - current_block_size - base_tx_size if script_length > 510000: if script_length < 1000000: # Make sure we don't find ourselves in a position where we # need to generate a transaction smaller than what we # expected. script_length = script_length // 2 else: script_length = 500000 script_pad_len = script_length script_output = CScript([b'\x00' * script_pad_len]) tx.vout.append(CTxOut(0, script_output)) # Add the tx to the list of transactions to be included # in the block. self.add_transactions_to_block(block, [tx]) current_block_size += len(tx.serialize()) # Now that we added a bunch of transaction, we need to recompute # the merkle root. make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() # Check that the block size is what's expected if block_size > 0: assert_equal(len(block.serialize()), block_size) # Do PoW, which is cheap on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # 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) # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, 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 # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(99): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() - node.p2p.send_blocks_and_test(maturity_blocks, node) + peer.send_blocks_and_test(maturity_blocks, node) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(16): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) - node.p2p.send_blocks_and_test([self.tip], node) + peer.send_blocks_and_test([self.tip], node) # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) - node.p2p.send_blocks_and_test( + peer.send_blocks_and_test( [self.tip], node, success=False, reject_reason='bad-blk-length') # Rewind bad block. self.tip = self.blocks[17] # Submit a very large block via RPC large_block = block( 33, spend=out[17], block_size=self.excessive_block_size) assert_equal(node.submitblock(ToHex(large_block)), None) if __name__ == '__main__': FullBlockTest().main()