diff --git a/test/functional/abc-cmdline.py b/test/functional/abc-cmdline.py index d6a5f78150..06ff56bee8 100755 --- a/test/functional/abc-cmdline.py +++ b/test/functional/abc-cmdline.py @@ -1,72 +1,73 @@ #!/usr/bin/env python3 # 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. """ Exercise the command line functions specific to ABC functionality. Currently: -excessiveblocksize= """ import re + +from test_framework.cdefs import LEGACY_MAX_BLOCK_SIZE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal -from test_framework.cdefs import LEGACY_MAX_BLOCK_SIZE MAX_GENERATED_BLOCK_SIZE_ERROR = ( 'Max generated block size (blockmaxsize) cannot exceed the excessive block size (excessiveblocksize)') class ABC_CmdLine_Test (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = False def check_excessive(self, expected_value): 'Check that the excessiveBlockSize is as expected' getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, expected_value) def check_subversion(self, pattern_str): 'Check that the subversion is set as expected' netinfo = self.nodes[0].getnetworkinfo() subversion = netinfo['subversion'] pattern = re.compile(pattern_str) assert(pattern.match(subversion)) def excessiveblocksize_test(self): self.log.info("Testing -excessiveblocksize") self.log.info(" Set to twice the default, i.e. {} bytes".format( 2 * LEGACY_MAX_BLOCK_SIZE)) self.stop_node(0) self.start_node(0, ["-excessiveblocksize={}".format( 2 * LEGACY_MAX_BLOCK_SIZE)]) self.check_excessive(2 * LEGACY_MAX_BLOCK_SIZE) # Check for EB correctness in the subver string self.check_subversion("/Bitcoin ABC:.*\(EB2\.0; .*\)/") self.log.info(" Attempt to set below legacy limit of 1MB - try {} bytes".format( LEGACY_MAX_BLOCK_SIZE)) self.stop_node(0) self.assert_start_raises_init_error( 0, ["-excessiveblocksize={}".format(LEGACY_MAX_BLOCK_SIZE)], 'Error: Excessive block size must be > 1,000,000 bytes (1MB)') self.log.info(" Attempt to set below blockmaxsize (mining limit)") self.assert_start_raises_init_error( 0, ['-blockmaxsize=1500000', '-excessiveblocksize=1300000'], 'Error: ' + MAX_GENERATED_BLOCK_SIZE_ERROR) # Make sure we leave the test with a node running as this is what thee # framework expects. self.start_node(0, []) def run_test(self): # Run tests on -excessiveblocksize option self.excessiveblocksize_test() if __name__ == '__main__': ABC_CmdLine_Test().main() diff --git a/test/functional/abc-finalize-block.py b/test/functional/abc-finalize-block.py index 967ea60d17..a5c8567682 100755 --- a/test/functional/abc-finalize-block.py +++ b/test/functional/abc-finalize-block.py @@ -1,313 +1,319 @@ #!/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. """Test the finalizeblock RPC calls.""" + import time from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until, set_node_times +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + set_node_times, + wait_until, +) RPC_FINALIZE_INVALID_BLOCK_ERROR = 'finalize-invalid-block' RPC_FORK_PRIOR_FINALIZED_ERROR = 'bad-fork-prior-finalized' RPC_BLOCK_NOT_FOUND_ERROR = 'Block not found' class FinalizeBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [["-finalizationdelay=0"], ["-finalizationdelay=0"], []] self.finalization_delay = 2 * 60 * 60 def run_test(self): node = self.nodes[0] self.mocktime = int(time.time()) self.log.info("Test block finalization...") node.generate(10) tip = node.getbestblockhash() node.finalizeblock(tip) assert_equal(node.getbestblockhash(), tip) assert_equal(node.getfinalizedblockhash(), tip) def wait_for_tip(node, tip): def check_tip(): return node.getbestblockhash() == tip wait_until(check_tip) alt_node = self.nodes[1] wait_for_tip(alt_node, tip) alt_node.invalidateblock(tip) # We will use this later fork_block = alt_node.getbestblockhash() # Node 0 should not accept the whole alt_node's chain due to tip being finalized, # even though it is longer. # Headers would not be accepted if previousblock is invalid: # - First block from alt node has same height than node tip, but is on a minority chain. Its # status is "valid-headers" # - Second block from alt node has height > node tip height, will be marked as invalid because # node tip is finalized # - Later blocks from alt node will be rejected because their previous block are invalid # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(218 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) # # On node: # >(210 valid-headers)->(211 invalid)->(212 to 218 dropped) # / # (200)->(201)-> // ->(209)->(210 finalized, tip) def wait_for_block(node, block, status="invalid"): def check_block(): for tip in node.getchaintips(): if tip["hash"] == block: assert(tip["status"] != "active") return tip["status"] == status return False wait_until(check_block) # First block header is accepted as valid-header alt_node.generate(1) wait_for_block(node, alt_node.getbestblockhash(), "valid-headers") # Second block header is accepted but set invalid alt_node.generate(1) invalid_block = alt_node.getbestblockhash() wait_for_block(node, invalid_block) # Later block headers are rejected for i in range(2, 9): alt_node.generate(1) assert_raises_rpc_error(-5, RPC_BLOCK_NOT_FOUND_ERROR, node.getblockheader, alt_node.getbestblockhash()) assert_equal(node.getbestblockhash(), tip) assert_equal(node.getfinalizedblockhash(), tip) self.log.info("Test that an invalid block cannot be finalized...") assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, node.finalizeblock, invalid_block) self.log.info( "Test that invalidating a finalized block moves the finalization backward...") # Node's finalized block will be invalidated, which causes the finalized block to # move to the previous block. # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(218 tip) # / # (200)->(201)-> // ->(208 auto-finalized)->(209)->(210 invalid) # # On node: # >(210 valid-headers)->(211 invalid)->(212 to 218 dropped) # / # (200)->(201)-> // ->(209 finalized)->(210 tip) node.invalidateblock(tip) node.reconsiderblock(tip) assert_equal(node.getbestblockhash(), tip) assert_equal(node.getfinalizedblockhash(), fork_block) assert_equal(alt_node.getfinalizedblockhash(), node.getblockheader( node.getfinalizedblockhash())['previousblockhash']) # The node will now accept that chain as the finalized block moved back. # Generate a new block on alt_node to trigger getheader from node # Previous 212-218 height blocks have been droped because their previous was invalid # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(218)->(219 tip) # / # (200)->(201)-> // ->(209 auto-finalized)->(210 invalid) # # On node: # >(210)->(211)->(212)-> // ->(218)->(219 tip) # / # (200)->(201)-> // ->(209 finalized)->(210) node.reconsiderblock(invalid_block) alt_node_tip = alt_node.generate(1)[-1] wait_for_tip(node, alt_node_tip) assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) assert_equal(node.getfinalizedblockhash(), fork_block) assert_equal(alt_node.getfinalizedblockhash(), fork_block) self.log.info("Trigger reorg via block finalization...") # Finalize node tip to reorg # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(218)->(219 tip) # / # (200)->(201)-> // ->(209 auto-finalized)->(210 invalid) # # On node: # >(210 invalid)-> // ->(219 invalid) # / # (200)->(201)-> // ->(209)->(210 finalized, tip) node.finalizeblock(tip) assert_equal(node.getfinalizedblockhash(), tip) self.log.info("Try to finalize a block on a competiting fork...") assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, node.finalizeblock, alt_node.getbestblockhash()) assert_equal(node.getfinalizedblockhash(), tip) self.log.info( "Check auto-finalization occurs as the tip move forward...") # Reconsider alt_node tip then generate some more blocks on alt_node. # Auto-finalization will occur on both chains. # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) # # On node: # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) node.reconsiderblock(alt_node.getbestblockhash()) block_to_autofinalize = alt_node.generate(1)[-1] alt_node_new_tip = alt_node.generate(9)[-1] wait_for_tip(node, alt_node_new_tip) assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) assert_equal(node.getfinalizedblockhash(), alt_node_tip) assert_equal(alt_node.getfinalizedblockhash(), alt_node_tip) self.log.info( "Try to finalize a block on an already finalized chain...") # Finalizing a block of an already finalized chain should have no effect block_218 = node.getblockheader(alt_node_tip)['previousblockhash'] node.finalizeblock(block_218) assert_equal(node.getfinalizedblockhash(), alt_node_tip) self.log.info( "Make sure reconsidering block move the finalization point...") # Reconsidering the tip will move back the finalized block on node # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) # # On node: # >(210)->(211)-> // ->(219)-> // ->(229 tip) # / # (200)->(201)-> // ->(209 finalized)->(210) node.reconsiderblock(tip) assert_equal(node.getbestblockhash(), alt_node_new_tip) assert_equal(node.getfinalizedblockhash(), fork_block) ### TEST FINALIZATION DELAY ### self.log.info("Check that finalization delay prevents eclipse attacks") # Because there has been no delay since the beginning of this test, # there should have been no auto-finalization on delay_node. # # Expected state: # # On alt_node: # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) # # On delay_node: # >(210)->(211)-> // ->(219)-> // ->(229 tip) # / # (200)->(201)-> // ->(209)->(210) delay_node = self.nodes[2] wait_for_tip(delay_node, alt_node_new_tip) assert_equal(delay_node.getfinalizedblockhash(), str()) self.log.info( "Check that finalization delay does not prevent auto-finalization") # Expire the delay, then generate 1 new block with alt_node to # update the tip on all chains. # Because the finalization delay is expired, auto-finalization # should occur. # # Expected state: # # On alt_node: # >(220 auto-finalized)-> // ->(230 tip) # / # (200)->(201)-> // ->(209)->(210 invalid) # # On delay_node: # >(220 auto-finalized)-> // ->(230 tip) # / # (200)->(201)-> // ->(209)->(210) self.mocktime += self.finalization_delay set_node_times([delay_node], self.mocktime) new_tip = alt_node.generate(1)[-1] wait_for_tip(delay_node, new_tip) assert_equal(alt_node.getbestblockhash(), new_tip) assert_equal(node.getfinalizedblockhash(), block_to_autofinalize) assert_equal(alt_node.getfinalizedblockhash(), block_to_autofinalize) self.log.info( "Check that finalization delay is effective on node boot") # Restart the new node, so the blocks have no header received time. self.restart_node(2) # There should be no finalized block (getfinalizedblockhash returns an empty string) assert_equal(delay_node.getfinalizedblockhash(), str()) # Generate 20 blocks with no delay. This should not trigger auto-finalization. # # Expected state: # # On delay_node: # >(220)-> // ->(250 tip) # / # (200)->(201)-> // ->(209)->(210) blocks = delay_node.generate(20) reboot_autofinalized_block = blocks[10] new_tip = blocks[-1] wait_for_tip(delay_node, new_tip) assert_equal(delay_node.getfinalizedblockhash(), str()) # Now let the finalization delay to expire, then generate one more block. # This should resume auto-finalization. # # Expected state: # # On delay_node: # >(220)-> // ->(241 auto-finalized)-> // ->(251 tip) # / # (200)->(201)-> // ->(209)->(210) self.mocktime += self.finalization_delay set_node_times([delay_node], self.mocktime) new_tip = delay_node.generate(1)[-1] wait_for_tip(delay_node, new_tip) assert_equal(delay_node.getfinalizedblockhash(), reboot_autofinalized_block) if __name__ == '__main__': FinalizeBlockTest().main() diff --git a/test/functional/abc-high_priority_transaction.py b/test/functional/abc-high_priority_transaction.py index c020576a3d..f3b9f8117b 100755 --- a/test/functional/abc-high_priority_transaction.py +++ b/test/functional/abc-high_priority_transaction.py @@ -1,103 +1,103 @@ #!/usr/bin/env python3 # 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. # # Test HighPriorityTransaction code # -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -from test_framework.mininode import COIN from test_framework.blocktools import create_confirmed_utxos +from test_framework.messages import COIN +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, gen_return_txouts, satoshi_round class HighPriorityTransactionTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [["-blockprioritypercentage=0", "-limitfreerelay=2"]] def create_small_transactions(self, node, utxos, num, fee): addr = node.getnewaddress() txids = [] for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] outputs = {} change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) signresult = node.signrawtransaction( rawtx, None, None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True) txids.append(txid) return txids def generate_high_priotransactions(self, node, count): # generate a bunch of spendable utxos self.txouts = gen_return_txouts() # create 150 simple one input one output hi prio txns hiprio_utxo_count = 150 age = 250 # be sure to make this utxo aged enough hiprio_utxos = create_confirmed_utxos(node, hiprio_utxo_count, age) txids = [] # Create hiprio_utxo_count number of txns with 0 fee range_size = [0, hiprio_utxo_count] start_range = range_size[0] end_range = range_size[1] txids = self.create_small_transactions( node, hiprio_utxos[start_range:end_range], end_range - start_range, 0) return txids def run_test(self): # this is the priority cut off as defined in AllowFreeThreshold() (see: src/txmempool.h) # anything above that value is considered an high priority transaction hiprio_threshold = COIN * 144 / 250 self.relayfee = self.nodes[0].getnetworkinfo()['relayfee'] # first test step: 0 reserved prio space in block txids = self.generate_high_priotransactions(self.nodes[0], 150) mempool_size_pre = self.nodes[0].getmempoolinfo()['bytes'] mempool = self.nodes[0].getrawmempool(True) # assert that all the txns are in the mempool and that all of them are hi prio for i in txids: assert(i in mempool) assert(mempool[i]['currentpriority'] > hiprio_threshold) # mine one block self.nodes[0].generate(1) self.log.info( "Assert that all high prio transactions haven't been mined") assert_equal(self.nodes[0].getmempoolinfo()['bytes'], mempool_size_pre) # restart with default blockprioritypercentage self.stop_nodes() self.nodes = [] self.add_nodes(self.num_nodes, [["-limitfreerelay=2"]]) self.start_nodes() # second test step: default reserved prio space in block (100K). # the mempool size is about 25K this means that all txns will be # included in the soon to be mined block txids = self.generate_high_priotransactions(self.nodes[0], 150) mempool_size_pre = self.nodes[0].getmempoolinfo()['bytes'] mempool = self.nodes[0].getrawmempool(True) # assert that all the txns are in the mempool and that all of them are hiprio for i in txids: assert(i in mempool) assert(mempool[i]['currentpriority'] > hiprio_threshold) # mine one block self.nodes[0].generate(1) self.log.info("Assert that all high prio transactions have been mined") assert(self.nodes[0].getmempoolinfo()['bytes'] == 0) if __name__ == '__main__': HighPriorityTransactionTest().main() diff --git a/test/functional/abc-invalid-message.py b/test/functional/abc-invalid-message.py index 6e220c762a..d8e72ebf6b 100755 --- a/test/functional/abc-invalid-message.py +++ b/test/functional/abc-invalid-message.py @@ -1,101 +1,102 @@ #!/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. """ABC Invalid Message Test Test that invalid messages get rejected and/or ban the sender as expected for each case. """ import struct + +from test_framework.messages import NODE_NETWORK, msg_version from test_framework.mininode import ( - P2PInterface, - msg_ping, - mininode_lock, MAGIC_BYTES, + mininode_lock, + msg_ping, network_thread_start, + P2PInterface, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import NODE_NETWORK, msg_version from test_framework.util import wait_until def msg_bad_checksum(connection, original_message): message_data = bytearray(connection.format_message(original_message)) data = original_message.serialize() i = 0 i += len(MAGIC_BYTES[connection.network]) i += 12 i += len(struct.pack(" 500000: # Make sure we eat up non-divisible by 100 amounts quickly # Also keep transaction less than 1 MB left_to_fill = 500000 + left_to_fill % 100 # Create the new transaction tx = get_base_transaction() pad_tx(tx, left_to_fill - overage_bytes) if len(tx.serialize()) + current_block_size > block_size: # Our padding was too big try again overage_bytes += 1 continue # 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 get_tests(self): self.genesis_hash = int(self.nodes[0].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) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() # Get to one block of the May 15, 2018 HF activation for i in range(6): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) # Send it all to the node at once. yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # There can be only one network thread running at a time. # Adding a new P2P connection here will try to start the network thread # at init, which will throw an assertion because it's already running. # This requires a few steps to avoid this: # 1/ Disconnect all the TestManager nodes # 2/ Terminate the network thread # 3/ Add the new P2P connection # 4/ Reconnect all the TestManager nodes # 5/ Restart the network thread # Disconnect all the TestManager nodes [n.disconnect_node() for n in self.test.p2p_connections] self.test.wait_for_disconnections() self.test.clear_all_connections() # Wait for the network thread to terminate network_thread_join() # Add the new connection node = self.nodes[0] node.add_p2p_connection(TestNode()) # Reconnect TestManager nodes self.test.add_all_connections(self.nodes) # Restart the network thread network_thread_start() # Wait for connection to be etablished peer = node.p2p peer.wait_for_verack() # Check that compact block also work for big blocks # Wait for SENDCMPCT def received_sendcmpct(): return (peer.last_sendcmpct != None) wait_until(received_sendcmpct, timeout=30) sendcmpct = msg_sendcmpct() sendcmpct.version = 1 sendcmpct.announce = True peer.send_and_ping(sendcmpct) # Exchange headers def received_getheaders(): return (peer.last_getheaders != None) wait_until(received_getheaders, timeout=30) # Return the favor peer.send_message(peer.last_getheaders) # Wait for the header list def received_headers(): return (peer.last_headers != None) wait_until(received_headers, timeout=30) # It's like we know about the same headers ! peer.send_message(peer.last_headers) # Send a block b1 = block(1, spend=out[0], block_size=ONE_MEGABYTE + 1) yield accepted() # Checks the node to forward it via compact block def received_block(): return (peer.last_cmpctblock != None) wait_until(received_block, timeout=30) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert(cmpctblk_header.sha256 == b1.sha256) # Send a large block with numerous transactions. peer.clear_block_data() b2 = block(2, spend=out[1], extra_txns=70000, block_size=self.excessive_block_size - 1000) yield accepted() # Checks the node forwards it via compact block wait_until(received_block, timeout=30) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert(cmpctblk_header.sha256 == b2.sha256) # In order to avoid having to resend a ton of transactions, we invalidate # b2, which will send all its transactions in the mempool. node.invalidateblock(node.getbestblockhash()) # Let's send a compact block and see if the node accepts it. # Let's modify b2 and use it so that we can reuse the mempool. tx = b2.vtx[0] tx.vout.append(CTxOut(0, CScript([random.randint(0, 256), OP_RETURN]))) tx.rehash() b2.vtx[0] = tx b2.hashMerkleRoot = b2.calc_merkle_root() b2.solve() # Now we create the compact block and send it comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(b2) peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) # Check that compact block is received properly assert(int(node.getbestblockhash(), 16) == b2.sha256) if __name__ == '__main__': FullBlockTest().main() diff --git a/test/functional/abc-p2p-fullblocktest.py b/test/functional/abc-p2p-fullblocktest.py index ced4bdf738..6153a56358 100755 --- a/test/functional/abc-p2p-fullblocktest.py +++ b/test/functional/abc-p2p-fullblocktest.py @@ -1,402 +1,434 @@ #!/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). """ -from test_framework.test_framework import ComparisonTestFramework -from test_framework.util import assert_equal -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * +from collections import deque +import random import time + +from test_framework.blocktools import ( + create_block, + create_coinbase, + create_transaction, + make_conform_to_ctor, +) +from test_framework.cdefs import ( + MAX_BLOCK_SIGOPS_PER_MB, + MAX_TX_SIGOPS_COUNT, + ONE_MEGABYTE, +) +from test_framework.comptool import RejectResult, TestInstance, TestManager from test_framework.key import CECKey -from test_framework.script import * -from test_framework.cdefs import (ONE_MEGABYTE, MAX_BLOCK_SIGOPS_PER_MB, - MAX_TX_SIGOPS_COUNT) -from collections import deque +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, + ser_compact_size, + ToHex, +) +from test_framework.mininode import network_thread_start +from test_framework.script import ( + CScript, + hash160, + OP_2DUP, + OP_CHECKSIG, + OP_CHECKSIGVERIFY, + OP_EQUAL, + OP_HASH160, + OP_RETURN, + OP_TRUE, + SIGHASH_ALL, + SIGHASH_FORKID, + SignatureHashForkId, +) +from test_framework.test_framework import ComparisonTestFramework +from test_framework.util import assert_equal REPLAY_PROTECTION_START_TIME = 2000000000 class PreviousSpendableOutput(): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n # the output we're spending class FullBlockTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. 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=127.0.0.1', "-replayprotectionactivationtime={}".format( REPLAY_PROTECTION_START_TIME), "-excessiveblocksize={}".format(self.excessive_block_size)]] def add_options(self, parser): super().add_options(parser) parser.add_argument( "--runbarelyexpensive", dest="runbarelyexpensive", default=True) def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) network_thread_start() # Set the blocksize to 2MB as initial condition self.nodes[0].setexcessiveblock(self.excessive_block_size) self.test.run() def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend, value, script=CScript([OP_TRUE])): tx = create_transaction(spend.tx, spend.n, b"", value, script) return tx def next_block(self, number, spend=None, script=CScript([OP_TRUE]), block_size=0, extra_sigops=0): if self.tip == 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 == 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 != 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 tx_sigops = min(extra_sigops, script_length, MAX_TX_SIGOPS_COUNT) extra_sigops -= tx_sigops script_pad_len = script_length - tx_sigops script_output = CScript( [b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops) 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 get_tests(self): node = self.nodes[0] 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) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # 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] 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() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # 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) yield accepted() # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) yield accepted() # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript( [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block(19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) yield accepted() block(20, spend=out[18], script=lots_of_checksigs, block_size=ONE_MEGABYTE, extra_sigops=1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) yield accepted() # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) yield accepted() # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript( [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block( 29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(26) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"fatstacks") public_key = private_key.get_pubkey() # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript( [public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_tx(out[22], 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) yield accepted() # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId( redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) yield accepted() # Submit a very large block via RPC large_block = block( 33, spend=out[24], block_size=self.excessive_block_size) node.submitblock(ToHex(large_block)) if __name__ == '__main__': FullBlockTest().main() diff --git a/test/functional/abc-replay-protection.py b/test/functional/abc-replay-protection.py index f2dc2e63e5..8811be37ea 100755 --- a/test/functional/abc-replay-protection.py +++ b/test/functional/abc-replay-protection.py @@ -1,308 +1,330 @@ #!/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. """ -from test_framework.test_framework import ComparisonTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * import time + +from test_framework.blocktools import ( + create_block, + create_coinbase, + create_transaction, + make_conform_to_ctor, +) +from test_framework.comptool import RejectResult, TestInstance, TestManager from test_framework.key import CECKey -from test_framework.script import * +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + ToHex, +) +from test_framework.mininode import network_thread_start +from test_framework.script import ( + CScript, + OP_CHECKSIG, + OP_TRUE, + SIGHASH_ALL, + SIGHASH_FORKID, + SignatureHashForkId, +) +from test_framework.test_framework import ComparisonTestFramework +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 INVALID_SIGNATURE_ERROR = b'mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)' RPC_INVALID_SIGNATURE_ERROR = "16: " + \ INVALID_SIGNATURE_ERROR.decode("utf-8") class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class ReplayProtectionTest(ComparisonTestFramework): 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=127.0.0.1', "-replayprotectionactivationtime={}".format(REPLAY_PROTECTION_START_TIME)]] def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) network_thread_start() self.nodes[0].setmocktime(REPLAY_PROTECTION_START_TIME) self.test.run() def next_block(self, number): if self.tip == 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 get_tests(self): self.genesis_hash = int(self.nodes[0].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) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # 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 # shorthand block = self.next_block node = self.nodes[0] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # 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 = CECKey() private_key.set_secretbytes(b"replayprotection") public_key = private_key.get_pubkey() # 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_transaction( spend.tx, spend.n, b'', 50 * COIN, script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 1000, 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) sig = private_key.sign(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) yield accepted() # 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) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) # Create a block that would activate the replay protection. bfork = block(5555) bfork.nTime = REPLAY_PROTECTION_START_TIME - 1 update_block(5555, []) yield accepted() for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check we are just before the activation time assert_equal(node.getblockheader(node.getbestblockhash())['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) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block 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) yield accepted() # Check we just activated the replay protection assert_equal(node.getblockheader(node.getbestblockhash())['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) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block 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 b5 = block(5) update_block(5, replay_txns) yield accepted() # Ok, now we check if a reorg work properly accross 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-rpc.py b/test/functional/abc-rpc.py index 21b43622b6..11ce05cc6a 100755 --- a/test/functional/abc-rpc.py +++ b/test/functional/abc-rpc.py @@ -1,83 +1,86 @@ #!/usr/bin/env python3 # 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. # Exercise the Bitcoin ABC RPC calls. import re + +from test_framework.cdefs import ( + DEFAULT_MAX_BLOCK_SIZE, + LEGACY_MAX_BLOCK_SIZE, + ONE_MEGABYTE, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import (assert_equal, assert_raises_rpc_error) -from test_framework.cdefs import (ONE_MEGABYTE, - LEGACY_MAX_BLOCK_SIZE, - DEFAULT_MAX_BLOCK_SIZE) +from test_framework.util import assert_equal, assert_raises_rpc_error class ABC_RPC_Test (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.tip = None self.setup_clean_chain = True self.extra_args = [['-norelaypriority', '-whitelist=127.0.0.1']] def check_subversion(self, pattern_str): # Check that the subversion is set as expected netinfo = self.nodes[0].getnetworkinfo() subversion = netinfo['subversion'] pattern = re.compile(pattern_str) assert(pattern.match(subversion)) def test_excessiveblock(self): # Check that we start with DEFAULT_MAX_BLOCK_SIZE getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, DEFAULT_MAX_BLOCK_SIZE) # Check that setting to legacy size is ok self.nodes[0].setexcessiveblock(LEGACY_MAX_BLOCK_SIZE + 1) getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, LEGACY_MAX_BLOCK_SIZE + 1) # Check that going below legacy size is not accepted assert_raises_rpc_error(-8, "Invalid parameter, excessiveblock must be larger than {}".format( LEGACY_MAX_BLOCK_SIZE), self.nodes[0].setexcessiveblock, LEGACY_MAX_BLOCK_SIZE) getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, LEGACY_MAX_BLOCK_SIZE + 1) # Check setting to 2MB self.nodes[0].setexcessiveblock(2 * ONE_MEGABYTE) getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, 2 * ONE_MEGABYTE) # Check for EB correctness in the subver string self.check_subversion("/Bitcoin ABC:.*\(EB2\.0; .*\)/") # Check setting to 13MB self.nodes[0].setexcessiveblock(13 * ONE_MEGABYTE) getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, 13 * ONE_MEGABYTE) # Check for EB correctness in the subver string self.check_subversion("/Bitcoin ABC:.*\(EB13\.0; .*\)/") # Check setting to 13.14MB self.nodes[0].setexcessiveblock(13140000) getsize = self.nodes[0].getexcessiveblock() ebs = getsize['excessiveBlockSize'] assert_equal(ebs, 13.14 * ONE_MEGABYTE) # check for EB correctness in the subver string self.check_subversion("/Bitcoin ABC:.*\(EB13\.1; .*\)/") def run_test(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.test_excessiveblock() if __name__ == '__main__': ABC_RPC_Test().main() diff --git a/test/functional/abc-schnorr-activation.py b/test/functional/abc-schnorr-activation.py index 9399c83446..ff38cc59a7 100755 --- a/test/functional/abc-schnorr-activation.py +++ b/test/functional/abc-schnorr-activation.py @@ -1,487 +1,515 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Copyright (c) 2017-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 activation of Schnorr transaction signatures: - rejection prior to upgrade both in mempool and blocks. - acceptance after upgrade both in mempool and blocks. - check non-banning for peers who send txns that would be valid on the other side of the upgrade. (e.g., if we are still before upgrade and peer is post-upgrade) - optional: tests of valid 64-byte DER signatures (same length as Schnorr). This requires a temporary patch to bitcoind; see fakeDER64 comment below. - advance and rewind mempool drop tests. Derived from abc-replay-protection.py with improvements borrowed from abc-segwit-recovery-activation.py. """ -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, sync_blocks -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * +from test_framework.blocktools import ( + create_block, + create_coinbase, + create_transaction, + make_conform_to_ctor, +) +from test_framework.comptool import RejectResult, TestInstance, TestManager from test_framework.key import CECKey +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + msg_tx, + ToHex, +) +from test_framework.mininode import ( + mininode_lock, + network_thread_start, + P2PInterface, +) from test_framework import schnorr -from test_framework.script import * +from test_framework.script import ( + CScript, + OP_1, + OP_CHECKMULTISIG, + OP_CHECKSIG, + OP_TRUE, + SIGHASH_ALL, + SIGHASH_FORKID, + SignatureHashForkId, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error, sync_blocks # far into the future GREAT_WALL_START_TIME = 2000000000 # First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 # If we don't do this, autoreplay protection will activate simultaneous with # great_wall and all our sigs will mysteriously fail. REPLAY_PROTECTION_START_TIME = GREAT_WALL_START_TIME * 2 # A mandatory (bannable) error occurs when people pass Schnorr signatures # into OP_CHECKMULTISIG. The precise error cause changes before/after upgrade # (DER / BADLENGTH) so we just match the start of the error. RPC_SCHNORR_MULTISIG_ERROR = '16: mandatory-script-verify-flag-failed' # These non-mandatory (forgiven) errors occur when your signature isn't valid # now, but would be valid on the other side of the upgrade. # Error due to passing a Schnorr signature to CHECKSIG before upgrade, but it # would have been valid after. EARLY_SCHNORR_ERROR = b'upgrade-conditional-script-failure (Non-canonical DER signature)' RPC_EARLY_SCHNORR_ERROR = '16: ' + \ EARLY_SCHNORR_ERROR.decode('utf8') # Error due to passing a 65-byte ECDSA CHECKSIG to mempool after upgrade, but # it would have been valid before. LATE_DER64_CHECKSIG_ERROR = b'upgrade-conditional-script-failure (Signature must be zero for failed CHECK(MULTI)SIG operation)' RPC_LATE_DER64_CHECKSIG_ERROR = '16: ' + \ LATE_DER64_CHECKSIG_ERROR.decode('utf8') # Error due to passing a 65-byte ECDSA CHECKMULTISIG to mempool after upgrade, # but it would have been valid before. LATE_DER64_CHECKMULTISIG_ERROR = b'upgrade-conditional-script-failure (Signature cannot be 65 bytes in CHECKMULTISIG)' RPC_LATE_DER64_CHECKMULTISIG_ERROR = '16: ' + \ LATE_DER64_CHECKMULTISIG_ERROR.decode('utf8') # For normal test running: fakeDER64 = b'' # To properly test activation, we need to make txes with 64 byte DER sigs. # (total 65 bytes with the appended hashtype byte, as in CHECKSIG/MULTISIG) # The easiest way to do this is to fake them, and then temporarily modify # VerifySignature in src/script/interpreter.cpp to always `return true;` # for ECDSA sigs, instead of `return pubkey.VerifyECDSA(sighash, vchSig);` # Once that patch is done, you can uncomment the following and tests should # pass. # fakeDER64 = bytes.fromhex('303e021d44444444444444444444444444444444444444444' # '44444444444444444021d4444444444444444444444444444' # '444444444444444444444444444444') assert len(fakeDER64) in [0, 64] class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class SchnorrActivationTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.extra_args = [['-whitelist=127.0.0.1', "-greatwallactivationtime={}".format( GREAT_WALL_START_TIME), "-replayprotectionactivationtime={}".format( REPLAY_PROTECTION_START_TIME)], ["-greatwallactivationtime={}".format( GREAT_WALL_START_TIME), "-replayprotectionactivationtime={}".format( REPLAY_PROTECTION_START_TIME)]] def run_test(self): for node in self.nodes: node.setmocktime(GREAT_WALL_START_TIME) test = TestManager(self, self.options.tmpdir) test.add_all_connections([self.nodes[0]]) # We have made a second node for ban-testing, to which we connect # the mininode (but not test framework). We make multiple connections # since each disconnect event consumes a connection (and, after we # run network_thread_start() we can't make any more connections). for _ in range(3): self.nodes[1].add_p2p_connection(P2PInterface()) network_thread_start() test.run() def next_block(self, number, transactions=None, nTime=None): if self.tip == 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 if nTime: block_time = nTime # 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) # add in transactions if transactions: block.vtx.extend(transactions) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() # 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 get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # shorthand block = self.next_block node = self.nodes[0] node_ban = self.nodes[1] # save the current tip so its coinbase can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get a coinbase that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(199): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # 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 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(spend, multi=False, sig='schnorr'): if multi: script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) else: script = CScript([public_key, OP_CHECKSIG]) # Fund transaction txfund = create_transaction( spend.tx, spend.n, b'', 50 * COIN, script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append( CTxOut(50 * COIN - 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, 50 * COIN) if sig == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sig == 'ecdsa': txsig = private_key.sign(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 txfund, txspend def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert(tx_id in set(node.getrawmempool())) return tx_id # Check we are not banned when sending a txn that node_ban rejects. def check_for_no_ban_on_rejected_tx(tx, reject_code, reject_reason): # Grab the first connection p2p = node_ban.p2p assert(p2p.state == 'connected') # The P2PConnection stores a public counter for each message type # and the last receive message of each type. We use this counter to # identify that we received a new reject message. with mininode_lock: rejects_count = p2p.message_count['reject'] # Send the transaction directly. We use a ping for synchronization: # if we have been banned, the pong message won't be received, a # timeout occurs and the test fails. p2p.send_message(msg_tx(tx)) p2p.sync_with_ping() # Check we haven't been disconnected assert(p2p.state == 'connected') # Check the reject message matches what we expected with mininode_lock: assert(p2p.message_count['reject'] == rejects_count + 1) reject_msg = p2p.last_message['reject'] assert(reject_msg.code == reject_code and reject_msg.reason == reject_reason and reject_msg.data == tx.sha256) # Check we are disconnected when sending a txn that node_ban rejects. # (Can't actually get banned, since bitcoind won't ban local peers.) def check_for_ban_on_rejected_tx(tx): # Take a connection p2p = node_ban.p2ps.pop() assert(p2p.state == 'connected') # make sure we can ping p2p.sync_with_ping() # send the naughty transaction p2p.send_message(msg_tx(tx)) # if not "banned", this will timeout and raise exception. p2p.wait_for_disconnect() # Setup fundings fundings = [] fund, schnorrchecksigtx = create_fund_and_spend_tx(out[0]) fundings.append(fund) fund, schnorrmultisigtx = create_fund_and_spend_tx(out[1], multi=True) fundings.append(fund) fund, ecdsachecksigtx = create_fund_and_spend_tx(out[2], sig='ecdsa') fundings.append(fund) if fakeDER64: fund, DER64checksigtx = create_fund_and_spend_tx( out[5], sig=fakeDER64) fundings.append(fund) fund, DER64multisigtx = create_fund_and_spend_tx( out[6], multi=True, sig=fakeDER64) fundings.append(fund) for fund in fundings: send_transaction_to_mempool(fund) block(1, transactions=fundings) yield accepted() # we're now set up for the various spends; make sure the other node # is set up, too. sync_blocks(self.nodes) # We are before the upgrade, no Schnorrs get in the mempool. assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, node.sendrawtransaction, ToHex(schnorrchecksigtx)) assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # And blocks containing them are rejected as well. block(2, transactions=[schnorrchecksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) block(3, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) # So far we were creating blocks well in advance of activation. # Now, start creating blocks that will move mediantime up to near # activation. bfork = block(5555, nTime=GREAT_WALL_START_TIME - 1) yield accepted() sync_blocks(self.nodes) # Create 5 more blocks with timestamps from GREAT_WALL_START_TIME+0 to +4 for i in range(5): block(5200 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check we are just before the activation time. assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) # We are just before the upgrade, still no Schnorrs get in the mempool, assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, node.sendrawtransaction, ToHex(schnorrchecksigtx)) assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # ... nor in blocks. block(10, transactions=[schnorrchecksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5204) block(11, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5204) # Ensure that sending future-valid schnorr txns is *non-bannable*. check_for_no_ban_on_rejected_tx( schnorrchecksigtx, 16, EARLY_SCHNORR_ERROR) # Ensure that sending schnorrs in multisig *is* bannable. check_for_ban_on_rejected_tx(schnorrmultisigtx) if fakeDER64: # Throw a couple of "valid" 65-byte ECDSA signatures into the # mempool just prior to the activation. faked_checksig_tx_id = send_transaction_to_mempool(DER64checksigtx) faked_multisig_tx_id = send_transaction_to_mempool(DER64multisigtx) # Put a proper ECDSA transaction into the mempool but it won't # be mined... ecdsa_tx_id = send_transaction_to_mempool(ecdsachecksigtx) # Activate the Schnorr! forkblock = block(5556) yield accepted() # We have exactly hit the activation time. assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) # Make sure ECDSA is still in -- we don't want to lose uninvolved txns # when the upgrade happens. assert ecdsa_tx_id in set(node.getrawmempool()) if fakeDER64: # The 64-byte DER sigs must be ejected. assert faked_checksig_tx_id not in set(node.getrawmempool()) assert faked_multisig_tx_id not in set(node.getrawmempool()) # If we try to re-add them, they fail with non-banning errors. # In CHECKSIG it's invalid Schnorr and hence NULLFAIL. assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKSIG_ERROR, node.sendrawtransaction, ToHex(DER64checksigtx)) # In CHECKMULTISIG it's invalid length and hence BAD_LENGTH. assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKMULTISIG_ERROR, node.sendrawtransaction, ToHex(DER64multisigtx)) # And they can't be mined either... block(14, transactions=[DER64checksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) block(15, transactions=[DER64multisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # Ensure that sending past-valid DER64 txns is *non-bannable*. check_for_no_ban_on_rejected_tx( DER64checksigtx, 16, LATE_DER64_CHECKSIG_ERROR) check_for_no_ban_on_rejected_tx( DER64multisigtx, 16, LATE_DER64_CHECKMULTISIG_ERROR) # The multisig throws a different error now assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # And it still can't be mined block(16, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # Sending schnorrs in multisig is STILL bannable. check_for_ban_on_rejected_tx(schnorrmultisigtx) # The Schnorr CHECKSIG is now valid schnorr_tx_id = send_transaction_to_mempool(schnorrchecksigtx) # It can also be mined postforkblock = block( 21, transactions=[schnorrchecksigtx, ecdsachecksigtx]) yield accepted() # (we mined the ecdsa tx too) assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id not in set(node.getrawmempool()) # Ok, now we check if a rewind works properly accross the activation. # First, rewind the normal post-fork block. node.invalidateblock(postforkblock.hash) # txes popped back into mempool assert schnorr_tx_id in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Deactivating upgrade. node.invalidateblock(forkblock.hash) # This should kick out the Schnorr sig, but not the valid ECDSA sig. assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Check that we also do it properly on deeper rewind. node.reconsiderblock(forkblock.hash) node.reconsiderblock(postforkblock.hash) node.invalidateblock(forkblock.hash) assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Try an actual reorg (deactivates then activates upgrade in one step) node.reconsiderblock(forkblock.hash) node.reconsiderblock(postforkblock.hash) tip(5204) test = TestInstance(sync_every_block=False) for i in range(3): block(5900 + i) test.blocks_and_transactions.append([self.tip, True]) # Perform the reorg yield test # reorg finishes after the fork assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME+2) # Schnorr didn't get lost! assert schnorr_tx_id in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) if __name__ == '__main__': SchnorrActivationTest().main() diff --git a/test/functional/abc-segwit-recovery-activation.py b/test/functional/abc-segwit-recovery-activation.py index 2f0c83371c..ace2c9e8a3 100755 --- a/test/functional/abc-segwit-recovery-activation.py +++ b/test/functional/abc-segwit-recovery-activation.py @@ -1,329 +1,357 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Copyright (c) 2017-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 activation of the SCRIPT_ALLOW_SEGWIT_RECOVERY flag """ +from test_framework.blocktools import ( + create_block, + create_coinbase, + make_conform_to_ctor, +) +from test_framework.comptool import RejectResult, TestInstance, TestManager +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + msg_tx, + ToHex, +) +from test_framework.mininode import ( + mininode_lock, + network_thread_start, + P2PInterface, +) +from test_framework.script import ( + CScript, + hash160, + OP_EQUAL, + OP_HASH160, + OP_TRUE, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, sync_blocks -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * -from test_framework.script import * +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + sync_blocks, +) # far into the future GREAT_WALL_START_TIME = 2000000000 # First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 # Error due to non clean stack CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' RPC_CLEANSTACK_ERROR = "64: " + \ CLEANSTACK_ERROR.decode("utf-8") class PreviousSpendableOutput(object): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n class SegwitRecoveryActivationTest(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's used to # test the activation itself via TestManager. # 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 and, before activation, # also invalid. We check, before and after activation, 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=127.0.0.1', "-acceptnonstdtxn", "-greatwallactivationtime={}".format( GREAT_WALL_START_TIME), "-replayprotectionactivationtime={}".format( 2 * GREAT_WALL_START_TIME)], ["-acceptnonstdtxn=0", "-greatwallactivationtime={}".format( GREAT_WALL_START_TIME), "-replayprotectionactivationtime={}".format( 2 * GREAT_WALL_START_TIME)]] def run_test(self): # Move the mocktime up to activation for node in self.nodes: node.setmocktime(GREAT_WALL_START_TIME) test = TestManager(self, self.options.tmpdir) # TestManager only connects to node_nonstd (nodes[0]) test.add_all_connections([self.nodes[0]]) # We connect directly to node_std (nodes[1]) self.nodes[1].add_p2p_connection(P2PInterface()) network_thread_start() test.run() def next_block(self, number): if self.tip == 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 get_tests(self): 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) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # 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 # 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): # To make sure we'll be able to recover coins sent to segwit addresses, # we test using historical recoveries from btc.com: # 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') 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 node_nonstd rejects. def check_for_no_ban_on_rejected_tx(tx, reject_code, reject_reason): # Check that our connection to node_std is open assert(node_std.p2p.state == 'connected') # The P2PConnection stores a public counter for each message type # and the last receive message of each type. We use this counter to # identify that we received a new reject message. with mininode_lock: rejects_count = node_std.p2p.message_count['reject'] # Send the transaction directly. We use a ping for synchronization: # if we have been banned, the pong message won't be received, a # timeout occurs and the test fails. node_std.p2p.send_message(msg_tx(tx)) node_std.p2p.sync_with_ping() # Check we haven't been disconnected assert(node_std.p2p.state == 'connected') # Check the reject message matches what we expected with mininode_lock: assert(node_std.p2p.message_count['reject'] == rejects_count + 1) reject_msg = node_std.p2p.last_message['reject'] assert(reject_msg.code == reject_code and reject_msg.reason == reject_reason and reject_msg.data == tx.sha256) # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # 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]) # Create blocks to get closer to activate the fork. # Mine txfund, as it can't go into node_std mempool because it's # nonstandard. b = block(5555) b.nTime = GREAT_WALL_START_TIME - 1 update_block(5555, [txfund]) yield accepted() for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Since the TestManager is not connected to node_std, we must check # both nodes are synchronized before continuing. sync_blocks(self.nodes) # Check we are just before the activation time assert_equal(node_nonstd.getblockheader( node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) assert_equal(node_std.getblockheader( node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) # Before the fork, segwit spending txns are rejected. assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, node_nonstd.sendrawtransaction, ToHex(txspend)) assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, node_std.sendrawtransaction, ToHex(txspend)) # Blocks containing segwit spending txns are rejected as well. block(2) update_block(2, [txspend]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5104) # Check that non-upgraded nodes checking for standardness are not # banning nodes sending segwit spending txns. check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) # Activate the fork in both nodes! forkblock = block(5556) yield accepted() sync_blocks(self.nodes) # Check we just activated the fork assert_equal(node_nonstd.getblockheader( node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) assert_equal(node_std.getblockheader( node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) # Segwit spending txns are accepted in the mempool of nodes not checking # for standardness, but rejected in nodes that check. node_nonstd.sendrawtransaction(ToHex(txspend)) assert(txspend.hash in node_nonstd.getrawmempool()) assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, node_std.sendrawtransaction, ToHex(txspend)) # Check that upgraded nodes checking for standardness are not banning # nodes sending segwit spending txns. check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) # Blocks containing segwit spending txns are now accepted in both # nodes. block(5) postforkblock = update_block(5, [txspend]) yield accepted() sync_blocks(self.nodes) # Ok, now we check if a reorg work properly accross the activation. node_nonstd.invalidateblock(postforkblock.hash) assert(txspend.hash in node_nonstd.getrawmempool()) # Also check that nodes checking for standardness don't return a segwit # spending txn into the mempool when disconnecting a block. node_std.invalidateblock(postforkblock.hash) assert(txspend.hash not in node_std.getrawmempool()) # Deactivate the fork. The spending tx has been evicted from the # mempool node_nonstd.invalidateblock(forkblock.hash) assert(len(node_nonstd.getrawmempool()) == 0) if __name__ == '__main__': SegwitRecoveryActivationTest().main() diff --git a/test/functional/abc-sync-chain.py b/test/functional/abc-sync-chain.py index 0eff0ae716..e5e06d5320 100755 --- a/test/functional/abc-sync-chain.py +++ b/test/functional/abc-sync-chain.py @@ -1,81 +1,78 @@ #!/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. """ Test that a node receiving many (potentially out of order) blocks exits initial block download (IBD; this occurs once it has passed minimumchainwork) and continues to sync without seizing. """ import random from test_framework.blocktools import create_block, create_coinbase -from test_framework.mininode import (CBlockHeader, - network_thread_start, - P2PInterface, - msg_block, - msg_headers) +from test_framework.messages import CBlockHeader, msg_block, msg_headers +from test_framework.mininode import network_thread_start, P2PInterface from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import wait_until, p2p_port +from test_framework.util import p2p_port, wait_until NUM_IBD_BLOCKS = 50 class BaseNode(P2PInterface): def send_header(self, block): msg = msg_headers() msg.headers = [CBlockHeader(block)] self.send_message(msg) def send_block(self, block): self.send_message(msg_block(block)) class SyncChainTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 # Setting minimumchainwork makes sure we test IBD as well as post-IBD self.extra_args = [ ["-minimumchainwork={:#x}".format(202 + 2 * NUM_IBD_BLOCKS)]] def run_test(self): node0conn = BaseNode() node0conn.peer_connect('127.0.0.1', p2p_port(0)) network_thread_start() node0conn.wait_for_verack() node0 = self.nodes[0] tip = int(node0.getbestblockhash(), 16) height = node0.getblockcount() + 1 time = node0.getblock(node0.getbestblockhash())['time'] + 1 blocks = [] for i in range(NUM_IBD_BLOCKS * 2): block = create_block(tip, create_coinbase(height), time) block.solve() blocks.append(block) tip = block.sha256 height += 1 time += 1 # Headers need to be sent in-order for b in blocks: node0conn.send_header(b) # Send blocks in some random order for b in random.sample(blocks, len(blocks)): node0conn.send_block(b) # The node should eventually, completely sync without getting stuck def node_synced(): return node0.getbestblockhash() == blocks[-1].hash wait_until(node_synced) if __name__ == '__main__': SyncChainTest().main() diff --git a/test/functional/abc-transaction-ordering.py b/test/functional/abc-transaction-ordering.py index 74070cbbed..9903afb8eb 100755 --- a/test/functional/abc-transaction-ordering.py +++ b/test/functional/abc-transaction-ordering.py @@ -1,245 +1,262 @@ #!/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. """ +from collections import deque +import random +import time + +from test_framework.blocktools import ( + create_block, + create_coinbase, + make_conform_to_ctor, +) +from test_framework.comptool import RejectResult, TestInstance, TestManager +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, +) +from test_framework.mininode import network_thread_start +from test_framework.script import ( + CScript, + OP_RETURN, + OP_TRUE, +) from test_framework.test_framework import ComparisonTestFramework from test_framework.util import assert_equal -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * -import time -from test_framework.script import * -from collections import deque # far into the future REPLAY_PROTECTION_START_TIME = 2000000000 class PreviousSpendableOutput(): def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n # the output we're spending class TransactionOrderingTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. 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=127.0.0.1', '-relaypriority=0', "-replayprotectionactivationtime={}".format(REPLAY_PROTECTION_START_TIME)]] def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) network_thread_start() # Set the blocksize to 2MB as initial condition self.test.run() 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 == 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 == 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 get_tests(self): node = self.nodes[0] 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) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # 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] self.add_transactions_to_block(block, new_transactions) 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() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # 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 block(n) yield accepted() block(5556) yield accepted() # Block with regular ordering are now rejected. block(5557, out[17], tx_count=16) yield rejected(RejectResult(16, b'tx-ordering')) # Rewind bad block. 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. ordered_block(4445, out[17]) yield accepted() ordered_block(4446, out[18]) yield accepted() # 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) yield rejected(RejectResult(16, b'bad-txns-duplicate')) # Rewind bad block. tip(4446) # Check over two blocks. proper_block = ordered_block(4448, out[20]) yield accepted() 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) yield rejected(RejectResult(16, b'bad-txns-BIP30')) if __name__ == '__main__': TransactionOrderingTest().main()