def send_blocks_until_disconnected(self, node):
     """Keep sending blocks to the node until we're disconnected."""
     for i in range(len(self.blocks)):
         try:
             node.send_message(msg_block(self.blocks[i]))
         except IOError as e:
             assert str(e) == 'Not connected, no pushbuf'
             break
 def send_blocks_until_disconnected(self, p2p_conn):
     """Keep sending blocks to the node until we're disconnected."""
     for i in range(len(self.blocks)):
         if not p2p_conn.is_connected:
             break
         try:
             p2p_conn.send_message(msg_block(self.blocks[i]))
         except IOError as e:
             assert not p2p_conn.is_connected
             break
Example #3
0
    def test_null_locators(self, test_node, inv_node):
        tip = self.nodes[0].getblockheader(self.nodes[0].generate(1)[0])
        tip_hash = int(tip["hash"], 16)

        inv_node.check_last_announcement(inv=[tip_hash], headers=[])
        test_node.check_last_announcement(inv=[tip_hash], headers=[])

        self.log.info("Verify getheaders with null locator and valid hashstop returns headers.")
        test_node.clear_last_announcement()
        test_node.send_get_headers(locator=[], hashstop=tip_hash)
        test_node.check_last_announcement(headers=[tip_hash])

        self.log.info("Verify getheaders with null locator and invalid hashstop does not return headers.")
        block = create_block(int(tip["hash"], 16), create_coinbase(tip["height"] + 1), tip["mediantime"] + 1)
        block.solve()
        test_node.send_header_for_blocks([block])
        test_node.clear_last_announcement()
        test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16))
        test_node.sync_with_ping()
        assert_equal(test_node.block_announced, False)
        inv_node.clear_last_announcement()
        test_node.send_message(msg_block(block))
        inv_node.check_last_announcement(inv=[int(block.hash, 16)], headers=[])
    def run_scenario6(self,
                      conn,
                      num_of_chains,
                      chain_length,
                      spend,
                      allowhighfees=False,
                      dontcheckfee=False,
                      timeout=30):
        # Create and send tx chains.
        txchains, bad, orphan = self.get_txchains_n(num_of_chains,
                                                    chain_length,
                                                    spend,
                                                    num_of_bad_chains=0)
        to_mine = []
        # Prepare inputs for sendrawtransactions
        rpc_txs_bulk_input = []
        for tx in range(len(txchains)):
            # Collect txn input data for bulk submit through rpc interface.
            rpc_txs_bulk_input.append({
                'hex': ToHex(txchains[tx]),
                'allowhighfees': allowhighfees,
                'dontcheckfee': dontcheckfee
            })
            if tx < len(txchains) // 2:
                # First half of txns will be mined in a block submitted through p2p interface.
                to_mine.append(txchains[tx])

        root_block_info = conn.rpc.getblock(conn.rpc.getbestblockhash())
        root_hash = root_block_info["hash"]
        root_height = root_block_info["height"]
        root_time = root_block_info["time"]

        # create the block
        block = self.make_block(to_mine, root_hash, root_height, root_time)
        conn.send_message(msg_block(block))
        wait_until(lambda: conn.rpc.getbestblockhash() == block.hash,
                   check_interval=0.3)

        # Check if there is an expected number of transactions in the mempool
        assert_equal(conn.rpc.getmempoolinfo()['size'], 0)

        # Submit a batch of txns through rpc interface.
        rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input)

        # There should be to_mine rejected transactions.
        assert_equal(len(rejected_txns['invalid']), len(to_mine))
        # bitcoind knows about the outputs of the last already mined transaction
        assert_equal(
            len([
                tx for tx in rejected_txns['invalid']
                if tx['reject_reason'] == 'txn-already-known'
            ]), 1)
        # bitcoind knows nothing about the previous already mined transactions so it considers them orphans
        assert_equal(
            len([
                tx for tx in rejected_txns['invalid']
                if tx['reject_reason'] == 'missing-inputs'
            ]),
            len(to_mine) - 1)
        # No transactions that were already mined should be in the mempool. The rest should be
        assert_equal(conn.rpc.getmempoolinfo()['size'],
                     len(txchains) - len(to_mine))
Example #5
0
    def run_test_case(self,
                      description,
                      order=1,
                      wait=False,
                      numberOfSafeModeLevelChanges=1):

        self.log.info("Running test case: %s", description)

        # Remove test folder to start building chain from the beginning for each case
        if os.path.exists(os.path.join(self.nodes[0].datadir, "regtest")):
            shutil.rmtree(os.path.join(self.nodes[0].datadir, "regtest"))

        with self.run_node_with_connections(description, 0, None,
                                            3) as (conn1, conn2, conn3):
            last_block_time = 0
            conn1.rpc.generate(1)

            branch_1_root, last_block_time = make_block(
                conn1, last_block_time=last_block_time)
            branch_1_blocks = [branch_1_root]
            for _ in range(10):
                new_block, last_block_time = make_block(
                    conn1,
                    branch_1_blocks[-1],
                    last_block_time=last_block_time)
                branch_1_blocks.append(new_block)

            branch_2_root, last_block_time = make_block(
                conn2, last_block_time=last_block_time)
            branch_2_blocks = [branch_2_root]
            for _ in range(20):
                new_block, last_block_time = make_block(
                    conn2,
                    branch_2_blocks[-1],
                    last_block_time=last_block_time)
                branch_2_blocks.append(new_block)

            branch_3_root, last_block_time = make_block(
                conn3, last_block_time=last_block_time)

            if order == 1:
                self.send_branches(
                    {
                        'conn': conn1,
                        'blocks': branch_1_blocks,
                        'do_send_blocks': True
                    }, {
                        'conn': conn2,
                        'blocks': branch_2_blocks,
                        'do_send_blocks': False
                    }, wait)
            else:
                self.send_branches(
                    {
                        'conn': conn2,
                        'blocks': branch_2_blocks,
                        'do_send_blocks': False
                    }, {
                        'conn': conn1,
                        'blocks': branch_1_blocks,
                        'do_send_blocks': True
                    }, wait)

            # active tip is last block from branch 1 and branch 2 has status headers-only
            wait_for_tip(conn1, branch_1_blocks[-1].hash)
            wait_for_tip_status(conn2, branch_2_blocks[-1].hash,
                                "headers-only")

            # we should have entered the safe mode
            try:
                conn1.rpc.getbalance()
                assert False, "Should not come to here, should raise exception in line above."
            except JSONRPCException as e:
                assert e.error[
                    "message"] == "Safe mode: Warning: The network does not appear to fully agree! We received headers of a large fork. Still waiting for block data for more details."

            def wait_for_log():
                safeModeChanges = 0
                line_text = "NotifySafeModeLevelChange: Warning: Found chain at least ~6 blocks longer than our best chain."
                for line in open(
                        glob.glob(self.options.tmpdir + "/node0" +
                                  "/regtest/bitcoind.log")[0]):
                    if line_text in line:
                        self.log.info("Found line: %s", line)
                        safeModeChanges += 1
                        if safeModeChanges == numberOfSafeModeLevelChanges:
                            return True
                return False

            wait_until(wait_for_log)

            conn2.send_message(msg_block(branch_2_blocks[0]))
            conn2.cb.sync_with_ping()

            # send block from the third branch
            conn3.send_message(msg_block(branch_3_root))
            conn3.cb.sync_with_ping()

            # we should still be in safe mode
            try:
                conn1.rpc.getbalance()
                assert False, "Should not come to here, should raise exception in line above."
            except JSONRPCException as e:
                assert e.error[
                    "message"] == "Safe mode: Warning: The network does not appear to fully agree! We received headers of a large fork. Still waiting for block data for more details."
    def run_test(self):
        block_count = 0

        # Create a P2P connection
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

        node3 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node3)
        node3.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()
        node2.wait_for_verack()
        node3.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=10)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=12)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block5 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=14)
        block5_num = block_count
        block_count += 1

        block6 = self.chain.next_block(block_count, spend=out[1], extra_txns=8)
        block_count += 1

        self.chain.set_tip(block5_num)

        block7 = self.chain.next_block(block_count,
                                       spend=out[1],
                                       extra_txns=10)

        self.log.info(f"block2 hash: {block2.hash}")
        self.nodes[0].waitaftervalidatingblock(block2.hash, "add")
        self.log.info(f"block3 hash: {block3.hash}")
        self.nodes[0].waitaftervalidatingblock(block3.hash, "add")
        self.log.info(f"block4 hash: {block4.hash}")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2.hash, block3.hash, block4.hash},
                                self.nodes[0], self.log)

        node0.send_message(msg_block(block2))
        # make sure we started validating block2 first as we expect this one to
        # be terminated later on in the test before its validation is complete
        # (algorithm for premature termination selects based on block height and
        # and validation duration - those that are in validation with smaller
        # height and longer are terminated first)
        wait_for_validating_blocks({block2.hash}, self.nodes[0], self.log)

        node1.send_message(msg_block(block3))
        node2.send_message(msg_block(block4))
        # make sure we started validating blocks
        wait_for_validating_blocks({block2.hash, block3.hash, block4.hash},
                                   self.nodes[0], self.log)

        node3.send_message(msg_block(block5))
        self.log.info(f"block5 hash: {block5.hash}")

        # check log file for logging about which block validation was terminated
        termination_log_found = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"Block {block2.hash} will not be considered by the current tip activation as the maximum parallel block" in line:
                termination_log_found = True
                self.log.info("Found line: %s", line.strip())
                break

        self.log.info(f"block6 hash: {block6.hash}")
        self.nodes[0].waitaftervalidatingblock(block6.hash, "add")
        self.log.info(f"block7 hash: {block7.hash}")
        self.nodes[0].waitaftervalidatingblock(block7.hash, "add")

        wait_for_waiting_blocks({block6.hash, block7.hash}, self.nodes[0],
                                self.log)

        node3.send_message(msg_block(block6))
        wait_for_validating_blocks({block6.hash}, self.nodes[0], self.log)

        node3.send_message(msg_block(block7))
        wait_for_validating_blocks({block7.hash}, self.nodes[0], self.log)

        self.nodes[0].waitaftervalidatingblock(block2.hash, "remove")
        # block2 should be canceled.
        wait_for_not_validating_blocks({block2.hash}, self.nodes[0], self.log)

        self.log.info("removing wait status from block7")
        self.nodes[0].waitaftervalidatingblock(block7.hash, "remove")

        # finish block7 validation
        wait_for_not_validating_blocks({block7.hash}, self.nodes[0], self.log)

        # remove wait status from block to finish its validations so the test exits properly
        self.nodes[0].waitaftervalidatingblock(block3.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block6.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block7 should be active in the end
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        # check log file for logging about which block validation was terminated
        termination_log_found = False
        for line in open(
                glob.glob(self.options.tmpdir + "/node0" +
                          "/regtest/bitcoind.log")[0]):
            if f"Block {block2.hash} validation was terminated before completion." in line:
                termination_log_found = True
                self.log.info("Found line: %s", line.strip())
                break

        assert_equal(termination_log_found, True)
Example #7
0
    def run_test(self):
        """Main test logic"""

        # Create P2P connections to two of the nodes
        self.nodes[0].add_p2p_connection(BaseNode())

        # Start up network handling in another thread. This needs to be called
        # after the P2P connections have been created.
        network_thread_start()
        # wait_for_verack ensures that the P2P connection is fully up.
        self.nodes[0].p2p.wait_for_verack()

        # Generating a block on one of the nodes will get us out of IBD
        blocks = [int(self.nodes[0].generate(1)[0], 16)]
        self.sync_all([self.nodes[0:1]])

        # Notice above how we called an RPC by calling a method with the same
        # name on the node object. Notice also how we used a keyword argument
        # to specify a named RPC argument. Neither of those are defined on the
        # node object. Instead there's some __getattr__() magic going on under
        # the covers to dispatch unrecognised attribute calls to the RPC
        # interface.

        # Logs are nice. Do plenty of them. They can be used in place of comments for
        # breaking the test into sub-sections.
        self.log.info("Starting test!")

        self.log.info("Calling a custom function")
        custom_function()

        self.log.info("Calling a custom method")
        self.custom_method()

        self.log.info("Create some blocks")
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(
            self.nodes[0].getbestblockhash())['time'] + 1

        height = 1

        for i in range(10):
            # Use the mininode and blocktools functionality to manually build a block
            # Calling the generate() rpc is easier, but this allows us to exactly
            # control the blocks and transactions.
            block = create_block(self.tip, create_coinbase(height),
                                 self.block_time)
            block.solve()
            block_message = msg_block(block)
            # Send message is used to send a P2P message to the node over our P2PInterface
            self.nodes[0].p2p.send_message(block_message)
            self.tip = block.sha256
            blocks.append(self.tip)
            self.block_time += 1
            height += 1

        self.log.info(
            "Wait for node1 to reach current tip (height 11) using RPC")
        self.nodes[1].waitforblockheight(11)

        self.log.info("Connect node2 and node1")
        connect_nodes(self.nodes[1], 2)

        self.log.info("Add P2P connection to node2")
        # We can't add additional P2P connections once the network thread has started. Disconnect the connection
        # to node0, wait for the network thread to terminate, then connect to node2. This is specific to
        # the current implementation of the network thread and may be improved in future.
        self.nodes[0].disconnect_p2ps()
        network_thread_join()

        self.nodes[2].add_p2p_connection(BaseNode())
        network_thread_start()
        self.nodes[2].p2p.wait_for_verack()

        self.log.info(
            "Wait for node2 reach current tip. Test that it has propagated all the blocks to us"
        )

        getdata_request = msg_getdata()
        for block in blocks:
            getdata_request.inv.append(CInv(2, block))
        self.nodes[2].p2p.send_message(getdata_request)

        # wait_until() will loop until a predicate condition is met. Use it to test properties of the
        # P2PInterface objects.
        wait_until(lambda: sorted(blocks) == sorted(
            list(self.nodes[2].p2p.block_receive_map.keys())),
                   timeout=5,
                   lock=mininode_lock)

        self.log.info("Check that each block was received only once")
        # The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
        # messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
        # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
        with mininode_lock:
            for block in self.nodes[2].p2p.block_receive_map.values():
                assert_equal(block, 1)
Example #8
0
    def run_test(self):

        self.stop_node(0)

        with self.run_node_with_connections(
                "reject headers if previous block is missing", 0, [],
                self.num_peers) as p2p_connections:

            connection = p2p_connections[0]
            coinbase_height = 1

            # 1. Create first block.
            block_0 = prepareBlock(coinbase_height,
                                   self.nodes[0].getbestblockhash())

            # 2. Connection sends HEADERS msg to bitcoind and waits for GETDATA.
            headers_message = msg_headers()
            headers_message.headers = [CBlockHeader(block_0)]
            connection.cb.send_message(headers_message)
            connection.cb.wait_for_getdata()
            wait_until(lambda: connection.cb.last_message["getdata"].inv[0].
                       hash == block_0.sha256)

            # 3. Connection sends BLOCK to bitcoind.
            connection.cb.send_message(msg_block(block_0))

            # 4. Bitcoind adds block to active chain.
            wait_for_tip(self.nodes[0], block_0.hash)

            # 5. Create two chained blocks.
            block_1 = prepareBlock(coinbase_height + 1, block_0.hash)
            block_2 = prepareBlock(coinbase_height + 2, block_1.hash)

            # 6. Connection sends HEADERS of the second block to bitcoind. It should be rejected.
            headers_message = msg_headers()
            headers_message.headers = [CBlockHeader(block_2)]
            connection.cb.send_message(headers_message)
            wait_until(lambda: check_for_log_msg(
                self, "received header " + block_2.hash +
                ": missing prev block", "/node0"))

            # 7. Connection sends HEADERS of the first block to bitcoind. It should be accepted.
            headers_message = msg_headers()
            headers_message.headers = [CBlockHeader(block_1)]
            connection.cb.send_message(headers_message)
            wait_until(lambda: connection.cb.last_message["getdata"].inv[0].
                       hash == block_1.sha256)

            # 8. Connection sends HEADERS of the second block to bitcoind. It should be accepted now that previous block is known.
            headers_message = msg_headers()
            headers_message.headers = [CBlockHeader(block_2)]
            connection.cb.send_message(headers_message)
            wait_until(lambda: connection.cb.last_message["getdata"].inv[0].
                       hash == block_2.sha256)

            # 9. Try to send alternative Genesis block (no previous block). It should be rejected.
            genesis_block = create_block(hashprev=0,
                                         coinbase=create_coinbase(
                                             height=0, outputValue=25))
            genesis_block.solve()
            connection.cb.send_message(msg_block(genesis_block))
            wait_until(lambda: check_for_log_msg(
                self, "ERROR: FindPreviousBlockIndex: prev block not found",
                "/node0"))
Example #9
0
    def run_test(self):
        def build_block_with_immature_stake(node):
            height = node.getblockcount()
            stakes = node.listunspent()
            # Take the latest, immature stake
            stake = min(stakes, key=lambda x: x['confirmations'])
            snapshot_meta = get_tip_snapshot_meta(node)
            coinbase = sign_coinbase(
                node, create_coinbase(
                    height, stake, snapshot_meta.hash))

            tip = int(node.getbestblockhash(), 16)
            block_time = node.getblock(
                self.nodes[0].getbestblockhash())['time'] + 1
            block = create_block(tip, coinbase, block_time)

            return block

        def has_synced_blockchain(node):
            status = node.proposerstatus()
            return status['wallets'][0]['status'] != 'NOT_PROPOSING_SYNCING_BLOCKCHAIN'

        def wait_until_all_have_reached_state(expected, which_nodes):
            def predicate(i):
                status = nodes[i].proposerstatus()
                return status['wallets'][0]['status'] == expected
            wait_until(lambda: all(predicate(i)
                                   for i in which_nodes), timeout=5)
            return predicate

        def assert_number_of_connections(node, incoming, outgoing):
            status = node.proposerstatus()
            assert_equal(status['incoming_connections'], incoming)
            assert_equal(status['outgoing_connections'], outgoing)

        def check_reject(node, err, block):
            wait_until(lambda: node.p2p.has_reject(err, block), timeout=5)

        nodes = self.nodes

        # Create P2P connections to the second node
        self.nodes[1].add_p2p_connection(P2P())

        self.log.info("Waiting untill the P2P connection is fully up...")
        self.nodes[1].p2p.wait_for_verack()

        self.log.info("Waiting for nodes to have started up...")
        wait_until(lambda: all(has_synced_blockchain(node) for node in self.nodes), timeout=5)

        self.log.info("Connecting nodes")
        connect_nodes(nodes[0], nodes[1].index)

        assert_number_of_connections(
            self.nodes[0],
            self.num_nodes - 1,
            self.num_nodes - 1)
        assert_number_of_connections(
            self.nodes[1],
            self.num_nodes,
            self.num_nodes - 1)

        self.setup_stake_coins(*self.nodes)

        # Generate stakeable outputs on both nodes
        nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32'))
        sync_blocks(nodes)
        nodes[1].proposetoaddress(1, nodes[1].getnewaddress('', 'bech32'))
        sync_blocks(nodes)

        # Current chain length doesn't overcome stake threshold, so
        # stakeable_balance == balance
        for i in range(self.num_nodes):
            self.check_node_balance(nodes[i], 10000, 10000)

        # Generate another block to overcome the stake threshold
        nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32'))
        sync_blocks(nodes)

        # Maturity check in action and we have one immature stake output
        self.check_node_balance(nodes[0], 10000, 9000)

        # Let's go further and propose yet another block
        nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32'))
        sync_blocks(nodes)

        # Now we have two immature stake outputs
        self.check_node_balance(nodes[0], 10000, 8000)

        # Generate two more blocks at another node
        nodes[1].proposetoaddress(2, nodes[1].getnewaddress('', 'bech32'))
        sync_blocks(nodes)

        # Thus all stake ouputs of the first node are mature
        self.check_node_balance(nodes[0], 10000, 10000)

        # Second node still have two immature stake outputs
        self.check_node_balance(nodes[1], 10000, 8000)

        # Try to send the block with immature stake
        block = build_block_with_immature_stake(self.nodes[1])
        self.nodes[1].p2p.send_message(msg_block(block))
        check_reject(self.nodes[1], b'bad-stake-immature', block.sha256)
Example #10
0
    def run_test(self):

        # Connect to node0
        node0 = BaseNode()
        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
        node0.add_connection(connections[0])

        NetworkThread().start()  # Start up network handling in another thread
        node0.wait_for_verack()

        # Build the blockchain
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(
            self.nodes[0].getbestblockhash())['time'] + 1

        self.blocks = []

        # Get a pubkey for the coinbase TXO
        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()

        # Create the first block with a coinbase output to our key
        height = 1
        block = create_block(self.tip, create_coinbase(height,
                                                       coinbase_pubkey),
                             self.block_time)
        self.blocks.append(block)
        self.block_time += 1
        block.solve()
        # Save the coinbase for later
        self.block1 = block
        self.tip = block.sha256
        height += 1

        # Bury the block 100 deep so the coinbase output is spendable
        for i in range(100):
            block = create_block(self.tip, create_coinbase(height),
                                 self.block_time)
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # Create a transaction spending the coinbase output with an invalid (null) signature
        tx = CTransaction()
        tx.vin.append(
            CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
        tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
        tx.calc_sha256()

        block102 = create_block(self.tip, create_coinbase(height),
                                self.block_time)
        self.block_time += 1
        block102.vtx.extend([tx])
        block102.hashMerkleRoot = block102.calc_merkle_root()
        block102.rehash()
        block102.solve()
        self.blocks.append(block102)
        self.tip = block102.sha256
        self.block_time += 1
        height += 1

        # Bury the assumed valid block 2100 deep
        for i in range(2100):
            block = create_block(self.tip, create_coinbase(height),
                                 self.block_time)
            block.nVersion = 4
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
        self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)])
        node1 = BaseNode()  # connects to node1
        connections.append(
            NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1))
        node1.add_connection(connections[1])
        node1.wait_for_verack()

        self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)])
        node2 = BaseNode()  # connects to node2
        connections.append(
            NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
        node2.add_connection(connections[2])
        node2.wait_for_verack()

        # send header lists to all three nodes
        node0.send_header_for_blocks(self.blocks[0:2000])
        node0.send_header_for_blocks(self.blocks[2000:])
        node1.send_header_for_blocks(self.blocks[0:2000])
        node1.send_header_for_blocks(self.blocks[2000:])
        node2.send_header_for_blocks(self.blocks[0:200])

        # Send blocks to node0. Block 102 will be rejected.
        self.send_blocks_until_disconnected(node0)
        self.assert_blockchain_height(self.nodes[0], 101)

        # Send all blocks to node1. All blocks will be accepted.
        for i in range(2202):
            node1.send_message(msg_block(self.blocks[i]))
        # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
        node1.sync_with_ping(120)
        assert_equal(
            self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'],
            2202)

        # Send blocks to node2. Block 102 will be rejected.
        self.send_blocks_until_disconnected(node2)
        self.assert_blockchain_height(self.nodes[2], 101)
Example #11
0
    def test_nonnull_locators(self, test_node, inv_node):
        tip = int(self.nodes[0].getbestblockhash(), 16)

        # PART 1
        # 1. Mine a block; expect inv announcements each time
        self.log.info("Part 1: headers don't start before sendheaders message...")
        for i in range(4):
            self.log.debug("Part 1.{}: starting...".format(i))
            old_tip = tip
            tip = self.mine_blocks(1)
            inv_node.check_last_inv_announcement(inv=[tip])
            test_node.check_last_inv_announcement(inv=[tip])
            # Try a few different responses; none should affect next announcement
            if i == 0:
                # first request the block
                test_node.send_get_data([tip])
                test_node.wait_for_block(tip)
            elif i == 1:
                # next try requesting header and block
                test_node.send_get_headers(locator=[old_tip], hashstop=tip)
                test_node.send_get_data([tip])
                test_node.wait_for_block(tip)
                test_node.clear_block_announcements()  # since we requested headers...
            elif i == 2:
                # this time announce own block via headers
                inv_node.clear_block_announcements()
                height = self.nodes[0].getblockcount()
                last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
                block_time = last_time + 1
                new_block = create_block(tip, create_coinbase(height + 1), block_time)
                new_block.solve()
                test_node.send_header_for_blocks([new_block])
                test_node.wait_for_getdata([new_block.sha256])
                test_node.send_message(msg_block(new_block))
                test_node.sync_with_ping()  # make sure this block is processed
                wait_until(lambda: inv_node.block_announced, timeout=60, lock=mininode_lock)
                inv_node.clear_block_announcements()
                test_node.clear_block_announcements()

        self.log.info("Part 1: success!")
        self.log.info("Part 2: announce blocks with headers after sendheaders message...")
        # PART 2
        # 2. Send a sendheaders message and test that headers announcements
        # commence and keep working.
        test_node.send_message(msg_sendheaders())
        prev_tip = int(self.nodes[0].getbestblockhash(), 16)
        test_node.send_get_headers(locator=[prev_tip], hashstop=0)
        test_node.sync_with_ping()

        # Now that we've synced headers, headers announcements should work
        tip = self.mine_blocks(1)
        inv_node.check_last_inv_announcement(inv=[tip])
        test_node.check_last_headers_announcement(headers=[tip])

        height = self.nodes[0].getblockcount() + 1
        block_time += 10  # Advance far enough ahead
        for i in range(10):
            self.log.debug("Part 2.{}: starting...".format(i))
            # Mine i blocks, and alternate announcing either via
            # inv (of tip) or via headers. After each, new blocks
            # mined by the node should successfully be announced
            # with block header, even though the blocks are never requested
            for j in range(2):
                self.log.debug("Part 2.{}.{}: starting...".format(i, j))
                blocks = []
                for b in range(i + 1):
                    blocks.append(create_block(tip, create_coinbase(height), block_time))
                    blocks[-1].solve()
                    tip = blocks[-1].sha256
                    block_time += 1
                    height += 1
                if j == 0:
                    # Announce via inv
                    test_node.send_block_inv(tip)
                    test_node.wait_for_getheaders()
                    # Should have received a getheaders now
                    test_node.send_header_for_blocks(blocks)
                    # Test that duplicate inv's won't result in duplicate
                    # getdata requests, or duplicate headers announcements
                    [inv_node.send_block_inv(x.sha256) for x in blocks]
                    test_node.wait_for_getdata([x.sha256 for x in blocks])
                    inv_node.sync_with_ping()
                else:
                    # Announce via headers
                    test_node.send_header_for_blocks(blocks)
                    test_node.wait_for_getdata([x.sha256 for x in blocks])
                    # Test that duplicate headers won't result in duplicate
                    # getdata requests (the check is further down)
                    inv_node.send_header_for_blocks(blocks)
                    inv_node.sync_with_ping()
                [test_node.send_message(msg_block(x)) for x in blocks]
                test_node.sync_with_ping()
                inv_node.sync_with_ping()
                # This block should not be announced to the inv node (since it also
                # broadcast it)
                assert "inv" not in inv_node.last_message
                assert "headers" not in inv_node.last_message
                tip = self.mine_blocks(1)
                inv_node.check_last_inv_announcement(inv=[tip])
                test_node.check_last_headers_announcement(headers=[tip])
                height += 1
                block_time += 1

        self.log.info("Part 2: success!")

        self.log.info("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...")

        # PART 3.  Headers announcements can stop after large reorg, and resume after
        # getheaders or inv from peer.
        for j in range(2):
            self.log.debug("Part 3.{}: starting...".format(j))
            # First try mining a reorg that can propagate with header announcement
            new_block_hashes = self.mine_reorg(length=7)
            tip = new_block_hashes[-1]
            inv_node.check_last_inv_announcement(inv=[tip])
            test_node.check_last_headers_announcement(headers=new_block_hashes)

            block_time += 8

            # Mine a too-large reorg, which should be announced with a single inv
            new_block_hashes = self.mine_reorg(length=8)
            tip = new_block_hashes[-1]
            inv_node.check_last_inv_announcement(inv=[tip])
            test_node.check_last_inv_announcement(inv=[tip])

            block_time += 9

            fork_point = self.nodes[0].getblock("%064x" % new_block_hashes[0])["previousblockhash"]
            fork_point = int(fork_point, 16)

            # Use getblocks/getdata
            test_node.send_getblocks(locator=[fork_point])
            test_node.check_last_inv_announcement(inv=new_block_hashes)
            test_node.send_get_data(new_block_hashes)
            test_node.wait_for_block(new_block_hashes[-1])

            for i in range(3):
                self.log.debug("Part 3.{}.{}: starting...".format(j, i))

                # Mine another block, still should get only an inv
                tip = self.mine_blocks(1)
                inv_node.check_last_inv_announcement(inv=[tip])
                test_node.check_last_inv_announcement(inv=[tip])
                if i == 0:
                    # Just get the data -- shouldn't cause headers announcements to resume
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                elif i == 1:
                    # Send a getheaders message that shouldn't trigger headers announcements
                    # to resume (best header sent will be too old)
                    test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1])
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                elif i == 2:
                    # This time, try sending either a getheaders to trigger resumption
                    # of headers announcements, or mine a new block and inv it, also
                    # triggering resumption of headers announcements.
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                    if j == 0:
                        test_node.send_get_headers(locator=[tip], hashstop=0)
                        test_node.sync_with_ping()
                    else:
                        test_node.send_block_inv(tip)
                        test_node.sync_with_ping()
            # New blocks should now be announced with header
            tip = self.mine_blocks(1)
            inv_node.check_last_inv_announcement(inv=[tip])
            test_node.check_last_headers_announcement(headers=[tip])

        self.log.info("Part 3: success!")

        self.log.info("Part 4: Testing direct fetch behavior...")
        tip = self.mine_blocks(1)
        height = self.nodes[0].getblockcount() + 1
        last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
        block_time = last_time + 1

        # Create 2 blocks.  Send the blocks, then send the headers.
        blocks = []
        for b in range(2):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1
            inv_node.send_message(msg_block(blocks[-1]))

        inv_node.sync_with_ping()  # Make sure blocks are processed
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks)
        test_node.sync_with_ping()
        # should not have received any getdata messages
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        # This time, direct fetch should work
        blocks = []
        for b in range(3):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        test_node.send_header_for_blocks(blocks)
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME)

        [test_node.send_message(msg_block(x)) for x in blocks]

        test_node.sync_with_ping()

        # Now announce a header that forks the last two blocks
        tip = blocks[0].sha256
        height -= 2
        blocks = []

        # Create extra blocks for later
        for b in range(20):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        # Announcing one block on fork should not trigger direct fetch
        # (less work than tip)
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks[0:1])
        test_node.sync_with_ping()
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        # Announcing one more block on fork should trigger direct fetch for
        # both blocks (same work as tip)
        test_node.send_header_for_blocks(blocks[1:2])
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME)

        # Announcing 16 more headers should trigger direct fetch for 14 more
        # blocks
        test_node.send_header_for_blocks(blocks[2:18])
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME)

        # Announcing 1 more header should not trigger any response
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks[18:19])
        test_node.sync_with_ping()
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        self.log.info("Part 4: success!")

        # Now deliver all those blocks we announced.
        [test_node.send_message(msg_block(x)) for x in blocks]

        self.log.info("Part 5: Testing handling of unconnecting headers")
        # First we test that receipt of an unconnecting header doesn't prevent
        # chain sync.
        for i in range(10):
            self.log.debug("Part 5.{}: starting...".format(i))
            test_node.last_message.pop("getdata", None)
            blocks = []
            # Create two more blocks.
            for j in range(2):
                blocks.append(create_block(tip, create_coinbase(height), block_time))
                blocks[-1].solve()
                tip = blocks[-1].sha256
                block_time += 1
                height += 1
            # Send the header of the second block -> this won't connect.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[1]])
            test_node.wait_for_getheaders()
            test_node.send_header_for_blocks(blocks)
            test_node.wait_for_getdata([x.sha256 for x in blocks])
            [test_node.send_message(msg_block(x)) for x in blocks]
            test_node.sync_with_ping()
            assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256)

        blocks = []
        # Now we test that if we repeatedly don't send connecting headers, we
        # don't go into an infinite loop trying to get them to connect.
        MAX_UNCONNECTING_HEADERS = 10
        for j in range(MAX_UNCONNECTING_HEADERS + 1):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        for i in range(1, MAX_UNCONNECTING_HEADERS):
            # Send a header that doesn't connect, check that we get a getheaders.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[i]])
            test_node.wait_for_getheaders()

        # Next header will connect, should re-set our count:
        test_node.send_header_for_blocks([blocks[0]])

        # Remove the first two entries (blocks[1] would connect):
        blocks = blocks[2:]

        # Now try to see how many unconnecting headers we can send
        # before we get disconnected.  Should be 5*MAX_UNCONNECTING_HEADERS
        for i in range(5 * MAX_UNCONNECTING_HEADERS - 1):
            # Send a header that doesn't connect, check that we get a getheaders.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[i % len(blocks)]])
            test_node.wait_for_getheaders()

        # Eventually this stops working.
        test_node.send_header_for_blocks([blocks[-1]])

        # Should get disconnected
        test_node.wait_for_disconnect()

        self.log.info("Part 5: success!")

        # Finally, check that the inv node never received a getdata request,
        # throughout the test
        assert "getdata" not in inv_node.last_message
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()
        node2.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        _, out, block_count = prepare_init_chain(self.chain,
                                                 101,
                                                 100,
                                                 block_0=False,
                                                 start_block=0,
                                                 node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count,
                                            spend=out[0],
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count,
                                              spend=out[0],
                                              extra_txns=2)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count,
                                            spend=out[0],
                                            extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash},
                                self.nodes[0], self.log)

        node0.send_message(msg_block(block2_hard))
        node1.send_message(msg_block(block4_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash},
                                   self.nodes[0], self.log)

        self.log.info(f"easier block3 hash: {block3_easier.hash}")
        node2.send_message(msg_block(block3_easier))

        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
    def run_test(self):
        """Main test logic"""

        # Create a P2P connection to one of the nodes
        node0 = BaseNode()
        connections = []
        connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
        node0.add_connection(connections[0])

        # Start up network handling in another thread. This needs to be called
        # after the P2P connections have been created.
        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()

        # Generating a block on one of the nodes will get us out of IBD
        blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)]
        self.sync_all([self.nodes[0:1]])

        # Notice above how we called an RPC by calling a method with the same
        # name on the node object. Notice also how we used a keyword argument
        # to specify a named RPC argument. Neither of those are defined on the
        # node object. Instead there's some __getattr__() magic going on under
        # the covers to dispatch unrecognised attribute calls to the RPC
        # interface.

        # Logs are nice. Do plenty of them. They can be used in place of comments for
        # breaking the test into sub-sections.
        self.log.info("Starting test!")

        self.log.info("Calling a custom function")
        custom_function()

        self.log.info("Calling a custom method")
        self.custom_method()

        self.log.info("Create some blocks")
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1

        height = 1

        for i in range(10):
            # Use the mininode and blocktools functionality to manually build a block
            # Calling the generate() rpc is easier, but this allows us to exactly
            # control the blocks and transactions.
            block = create_block(self.tip, create_coinbase(height), self.block_time)
            block.solve()
            block_message = msg_block(block)
            # Send message is used to send a P2P message to the node over our NodeConn connection
            node0.send_message(block_message)
            self.tip = block.sha256
            blocks.append(self.tip)
            self.block_time += 1
            height += 1

        self.log.info("Wait for node1 to reach current tip (height 11) using RPC")
        self.nodes[1].waitforblockheight(11)

        self.log.info("Connect node2 and node1")
        connect_nodes(self.nodes[1], 2)

        self.log.info("Add P2P connection to node2")
        node2 = BaseNode()
        connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
        node2.add_connection(connections[1])
        node2.wait_for_verack()

        self.log.info("Wait for node2 reach current tip. Test that it has propogated all the blocks to us")

        for block in blocks:
            getdata_request = msg_getdata()
            getdata_request.inv.append(CInv(2, block))
            node2.send_message(getdata_request)

        # wait_until() will loop until a predicate condition is met. Use it to test properties of the
        # NodeConnCB objects.
        assert wait_until(lambda: sorted(blocks) == sorted(list(node2.block_receive_map.keys())), timeout=5)

        self.log.info("Check that each block was received only once")
        # The network thread uses a global lock on data access to the NodeConn objects when sending and receiving
        # messages. The test thread should acquire the global lock before accessing any NodeConn data to avoid locking
        # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
        with mininode_lock:
            for block in node2.block_receive_map.values():
                assert_equal(block, 1)
Example #14
0
    def run_test(self):
        block_count = 0

        # Create a P2P connection
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()
        node2.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1
        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count, spend=out[0], extra_txns=2)
        block_count += 1
        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        node0.send_message(msg_block(block2_hard))
        node1.send_message(msg_block(block4_hard))
 
        # make sure we started validating blocks
        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        self.log.info(f"easier hash: {block3_easier.hash}")
        node2.send_message(msg_block(block3_easier))

        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # now we want our precious block to be one of the harder blocks (block4_hard)
        self.nodes[0].preciousblock(block4_hard.hash)
        assert_equal(block4_hard.hash, self.nodes[0].getbestblockhash())
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        num_blocks = 150
        for i in range(num_blocks):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(num_blocks):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 151 via rpc")
        self.nodes[0].waitforblockheight(num_blocks + 1)

        tip_block_num = block_count - 1

        # left branch
        block2 = self.chain.next_block(block_count,
                                       spend=out[0:9],
                                       extra_txns=8)
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        self.nodes[0].waitforblockheight(num_blocks + 2)

        # send blocks 3,4 for parallel validation on left branch
        self.chain.set_tip(tip_block_num)
        block3 = self.chain.next_block(block_count,
                                       spend=out[10:19],
                                       extra_txns=10)
        block_count += 1

        block4 = self.chain.next_block(block_count,
                                       spend=out[20:29],
                                       extra_txns=8)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"block3 hash: {block3.hash}")
        self.log.info(f"block4 hash: {block4.hash}")
        self.nodes[0].waitaftervalidatingblock(block4.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block4.hash}, self.nodes[0], self.log)

        node1.send_message(msg_block(block3))
        node1.send_message(msg_block(block4))

        # make sure we started validating blocks
        wait_for_validating_blocks({block4.hash}, self.nodes[0], self.log)

        # right branch
        self.chain.set_tip(tip_block_num)
        block5 = self.chain.next_block(block_count)
        # Add some txns from block2 & block3 to block5, just to check that they get
        # filtered from the mempool and not re-added
        block5_duplicated_txns = block3.vtx[1:3] + block2.vtx[1:3]
        self.chain.update_block(block_count, block5_duplicated_txns)
        block_count += 1
        node0.send_message(msg_block(block5))
        self.log.info(f"block5 hash: {block5.hash}")

        # and two blocks to extend second branch to cause reorg
        # - they must be sent from the same node as otherwise they will be
        #   rejected with "prev block not found" as we don't wait for the first
        #   block to arrive so there is a race condition which block is seen
        #   first when using multiple connections
        block6 = self.chain.next_block(block_count)
        node0.send_message(msg_block(block6))
        self.log.info(f"block6 hash: {block6.hash}")
        block_count += 1
        block7 = self.chain.next_block(block_count)
        node0.send_message(msg_block(block7))
        self.log.info(f"block7 hash: {block7.hash}")
        block_count += 1

        self.nodes[0].waitforblockheight(num_blocks + 4)
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        self.log.info(
            "releasing wait status on parallel blocks to finish their validation"
        )
        self.nodes[0].waitaftervalidatingblock(block4.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block7.hash, self.nodes[0].getbestblockhash())

        # make sure that transactions from block2 and 3 (except coinbase, and those also
        # in block 5) are in mempool
        not_expected_in_mempool = set()
        for txn in block5_duplicated_txns:
            not_expected_in_mempool.add(txn.hash)
        expected_in_mempool = set()
        for txn in block2.vtx[1:] + block3.vtx[1:]:
            expected_in_mempool.add(txn.hash)
        expected_in_mempool = expected_in_mempool.difference(
            not_expected_in_mempool)

        mempool = self.nodes[0].getrawmempool()
        assert_equal(expected_in_mempool, set(mempool))
Example #16
0
    def run_test(self):
        """Main test logic"""

        self.setup_stake_coins(*self.nodes)

        # Create P2P connections will wait for a verack to make sure the connection is fully up
        self.nodes[0].add_p2p_connection(BaseNode())

        # Generating a block on one of the nodes will get us out of IBD
        blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)]
        self.sync_all([self.nodes[0:2]])

        # Notice above how we called an RPC by calling a method with the same
        # name on the node object. Notice also how we used a keyword argument
        # to specify a named RPC argument. Neither of those are defined on the
        # node object. Instead there's some __getattr__() magic going on under
        # the covers to dispatch unrecognised attribute calls to the RPC
        # interface.

        # Logs are nice. Do plenty of them. They can be used in place of comments for
        # breaking the test into sub-sections.
        self.log.info("Starting test!")

        self.log.info("Calling a custom function")
        custom_function()

        self.log.info("Calling a custom method")
        self.custom_method()

        self.log.info("Create some blocks")
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1

        height = self.nodes[0].getblockcount()

        snapshot_meta = get_tip_snapshot_meta(self.nodes[0])
        stakes = self.nodes[0].listunspent()
        for stake in stakes:
            # Use the mininode and blocktools functionality to manually build a block
            # Calling the generate() rpc is easier, but this allows us to exactly
            # control the blocks and transactions.
            coinbase = sign_coinbase(self.nodes[0], create_coinbase(height, stake, snapshot_meta.hash))
            block = create_block(self.tip, coinbase, self.block_time)
            # Wait until the active chain picks up the previous block
            wait_until(lambda: self.nodes[0].getblockcount() == height, timeout=5)
            snapshot_meta = update_snapshot_with_tx(self.nodes[0], snapshot_meta, height + 1, coinbase)
            block.solve()
            block_message = msg_block(block)
            # Send message is used to send a P2P message to the node over our P2PInterface
            self.nodes[0].p2p.send_message(block_message)
            self.tip = block.sha256
            blocks.append(self.tip)
            self.block_time += 1
            height += 1

        self.log.info("Wait for node1 to reach current tip (height %d) using RPC" % height)
        self.nodes[1].waitforblockheight(height)

        self.log.info("Connect node2 and node1")
        connect_nodes(self.nodes[1], 2)

        self.log.info("Add P2P connection to node2")
        self.nodes[0].disconnect_p2ps()

        self.nodes[2].add_p2p_connection(BaseNode())

        self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us")
        self.nodes[2].waitforblockheight(height)

        getdata_request = msg_getdata()
        for block in blocks:
            getdata_request.inv.append(CInv(2, block))
        self.nodes[2].p2p.send_message(getdata_request)

        # wait_until() will loop until a predicate condition is met. Use it to test properties of the
        # P2PInterface objects.
        wait_until(lambda: sorted(blocks) == sorted(list(self.nodes[2].p2p.block_receive_map.keys())), timeout=5, lock=mininode_lock)

        self.log.info("Check that each block was received only once")
        # The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
        # messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
        # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
        with mininode_lock:
            for block in self.nodes[2].p2p.block_receive_map.values():
                assert_equal(block, 1)
Example #17
0
    def run_test(self):
        node0 = NodeConnCB()
        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
        node0.add_connection(connections[0])

        NetworkThread().start()  # Start up network handling in another thread

        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()

        self.log.info("Mining %d blocks", CLTV_HEIGHT - 2)
        self.coinbase_blocks = self.nodes[0].generate(CLTV_HEIGHT - 2)
        self.nodeaddress = self.nodes[0].getnewaddress()

        self.log.info(
            "Test that an invalid-according-to-CLTV transaction can still appear in a block"
        )

        spendtx = create_transaction(self.nodes[0], self.coinbase_blocks[0],
                                     self.nodeaddress, 1.0)
        cltv_invalidate(spendtx)
        spendtx.rehash()

        tip = self.nodes[0].getbestblockhash()
        block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1
        block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1),
                             block_time)
        block.nVersion = 3
        block.vtx.append(spendtx)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.solve()

        node0.send_and_ping(msg_block(block))
        assert_equal(self.nodes[0].getbestblockhash(), block.hash)

        self.log.info("Test that blocks must now be at least version 4")
        tip = block.sha256
        block_time += 1
        block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time)
        block.nVersion = 3
        block.solve()
        node0.send_and_ping(msg_block(block))
        assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

        wait_until(lambda: "reject" in node0.last_message.keys(),
                   lock=mininode_lock)
        with mininode_lock:
            assert_equal(node0.last_message["reject"].code, REJECT_OBSOLETE)
            assert_equal(node0.last_message["reject"].reason,
                         b'bad-version(0x00000003)')
            assert_equal(node0.last_message["reject"].data, block.sha256)
            del node0.last_message["reject"]

        self.log.info(
            "Test that invalid-according-to-cltv transactions cannot appear in a block"
        )
        block.nVersion = 4

        spendtx = create_transaction(self.nodes[0], self.coinbase_blocks[1],
                                     self.nodeaddress, 1.0)
        cltv_invalidate(spendtx)
        spendtx.rehash()

        # First we show that this tx is valid except for CLTV by getting it
        # accepted to the mempool (which we can achieve with
        # -promiscuousmempoolflags).
        node0.send_and_ping(msg_tx(spendtx))
        assert spendtx.hash in self.nodes[0].getrawmempool()

        # Now we verify that a block with this transaction is invalid.
        block.vtx.append(spendtx)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.solve()

        node0.send_and_ping(msg_block(block))
        assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

        wait_until(lambda: "reject" in node0.last_message.keys(),
                   lock=mininode_lock)
        with mininode_lock:
            assert node0.last_message["reject"].code in [
                REJECT_INVALID, REJECT_NONSTANDARD
            ]
            assert_equal(node0.last_message["reject"].data, block.sha256)
            if node0.last_message["reject"].code == REJECT_INVALID:
                # Generic rejection when a block is invalid
                assert_equal(node0.last_message["reject"].reason,
                             b'block-validation-failed')
            else:
                assert b'Negative locktime' in node0.last_message[
                    "reject"].reason

        self.log.info(
            "Test that a version 4 block with a valid-according-to-CLTV transaction is accepted"
        )
        spendtx = cltv_validate(self.nodes[0], spendtx, CLTV_HEIGHT - 1)
        spendtx.rehash()

        block.vtx.pop(1)
        block.vtx.append(spendtx)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.solve()

        node0.send_and_ping(msg_block(block))
        assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
    def run_test(self):

        MAX_FORK_DISTANCE = 10
        MIN_FORK_LENGTH = 3
        MIN_FORK_DIFFERENCE = 1

        args= [f"-safemodemaxforkdistance={MAX_FORK_DISTANCE}",
               f"-safemodeminforklength={MIN_FORK_LENGTH}",
               f"-safemodeminblockdifference={MIN_FORK_DIFFERENCE}",]

        with self.run_node_with_connections("Preparation", 0, args, 2) as (conn1, conn2):
            last_block_time = 0
            conn1.rpc.generate(1)

            branch_1_root, last_block_time = make_block(conn1, last_block_time = last_block_time)
            branch_1_blocks = [branch_1_root]
            for _ in range(MAX_FORK_DISTANCE):
                new_block, last_block_time = make_block(conn1, branch_1_blocks[-1], last_block_time = last_block_time)
                branch_1_blocks.append(new_block)

            branch_2_root, last_block_time = make_block(conn2, makeValid=False, last_block_time = last_block_time)
            branch_2_blocks = [branch_2_root]
            for _ in range(MAX_FORK_DISTANCE + MIN_FORK_DIFFERENCE + 1):
                new_block, last_block_time = make_block(conn2, branch_2_blocks[-1], last_block_time = last_block_time)
                branch_2_blocks.append(new_block)

            # send first branch that should be active tip
            send_by_headers(conn1, branch_1_blocks, do_send_blocks=True)
            wait_for_tip(conn1, branch_1_blocks[-1].hash)

            # send second branch with more POW
            send_by_headers(conn2, branch_2_blocks, do_send_blocks=False)
            wait_for_tip(conn1, branch_1_blocks[-1].hash)
            wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "headers-only")
    
            # we should not be in safe mode (distance to the fork is too large)
            assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"]

            conn1.rpc.invalidateblock(branch_1_blocks[-1].hash)
            wait_for_tip(conn1, branch_1_blocks[-2].hash)
            # here we have shortened distance from the active tip to the fork root so the safe mode should be activated
            assert conn1.rpc.getsafemodeinfo()["safemodeenabled"]

            conn1.rpc.reconsiderblock(branch_1_blocks[-1].hash)
            wait_for_tip(conn1, branch_1_blocks[-1].hash)
            # returning to the old state (distance to the fork is too large)
            assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"]

            # From time to time this test can run faster than expected and
            # the older blocks for batch 2 headers are not yet requested.
            # In that case they will be rejected due to being too far away
            # form the tip. In that case we need to send them again once they
            # are requested.
            def on_getdata(conn, msg):
                for i in msg.inv:
                    if i.type != 2: # MSG_BLOCK
                        error_msg = f"Unexpected data requested {i}"
                        self.log.error(error_msg)
                        raise NotImplementedError(error_msg)
                    for block in branch_2_blocks:
                        if int(block.hash, 16) == i.hash:
                            conn.send_message(msg_block(block))
                            break

            conn2.cb.on_getdata = on_getdata

            # send sencond branch full blocks
            for block in branch_2_blocks:
                conn2.send_message(msg_block(block))

            tips = conn2.rpc.getchaintips()

            # second branch should now be invalid
            wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "invalid")
            wait_for_tip(conn1, branch_1_blocks[-1].hash)
            
            # we should not be in safe mode
            assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"]
    def run_test(self):

        with self.run_node_with_connections(
                "Eviction order test; fill the memppol over its size and see what txs will be evicted.",
                0,
            [
                "-blockmintxfee=0.00001",  # 1 satoshi/byte
                "-minrelaytxfee=0",
                "-maxmempool=300MB",
                "-maxmempoolsizedisk=0",
                "-genesisactivationheight=1",
                '-maxstdtxvalidationduration=5000',
                '-maxnonstdtxvalidationduration=5001',
                '-maxstackmemoryusageconsensus=5MB',
                '-maxstackmemoryusagepolicy=5MB',
                '-maxscriptsizepolicy=5MB',
                '-checkmempool=0',
            ],
                number_of_connections=1) as (conn, ):

            mining_fee = 1.01  # in satoshi per byte

            # create block with coinbase
            coinbase1 = create_coinbase(height=1)
            first_block = create_block(int(conn.rpc.getbestblockhash(), 16),
                                       coinbase=coinbase1)
            first_block.solve()
            conn.send_message(msg_block(first_block))
            wait_until(lambda: conn.rpc.getbestblockhash() == first_block.hash,
                       check_interval=1)

            coinbase2 = create_coinbase(height=2)
            second_block = create_block(int(conn.rpc.getbestblockhash(), 16),
                                        coinbase=coinbase2)
            second_block.solve()
            conn.send_message(msg_block(second_block))
            wait_until(
                lambda: conn.rpc.getbestblockhash() == second_block.hash,
                check_interval=1)

            #mature the coinbase
            conn.rpc.generate(100)

            funding_tx = self.create_tx([(coinbase1, 0), (coinbase2, 0)], 16,
                                        mining_fee, 0)

            conn.send_message(msg_tx(funding_tx))
            check_mempool_equals(conn.rpc, [funding_tx])
            conn.rpc.generate(1)

            #        (funding_tx, 0)          (funding_tx, 1)  (funding_tx, 2)  (funding_tx, 3)   (funding_tx, 4-14)       (funding_tx, 15)
            # ---------------------------------------------------------------------------------------------------------------------
            #           group1tx1                lowPaying3           tx1            tx3            (long chain of      (chain of high
            #               |                         |                |              |             high paying txs)    paying txs used to
            #           group1tx2                lowPaying4           tx2          lowPaying5     to fill the mempool)  push low paying txs
            #          /          \                                                                                      out of mempools)
            #    group1paying   group2tx1
            #         |             |
            #    lowPaying1     group2tx2
            #                       |
            #                   group2paying
            #                       |
            #                   lowPaying2

            group1tx1 = self.create_tx([(funding_tx, 0)],
                                       noutput=1,
                                       feerate=0,
                                       totalSize=ONE_MEGABYTE)
            group1tx2 = self.create_tx([(group1tx1, 0)],
                                       noutput=2,
                                       feerate=0,
                                       totalSize=ONE_MEGABYTE)
            group1paying = self.create_tx(
                [(group1tx2, 0)],
                noutput=1,
                feerate=1.4,
                totalSize=ONE_MEGABYTE,
                size_of_nonpayin_txs=self.tx_size(group1tx1) +
                self.tx_size(group1tx2))

            group2tx1 = self.create_tx([(group1tx2, 1)],
                                       noutput=1,
                                       feerate=0,
                                       totalSize=ONE_MEGABYTE)
            group2tx2 = self.create_tx([(group2tx1, 0)],
                                       noutput=1,
                                       feerate=0,
                                       totalSize=ONE_MEGABYTE)
            group2paying = self.create_tx(
                [(group2tx2, 0)],
                noutput=1,
                feerate=1.6,
                totalSize=ONE_MEGABYTE,
                size_of_nonpayin_txs=self.tx_size(group2tx1) +
                self.tx_size(group2tx2))

            tx1 = self.create_tx([(funding_tx, 2)],
                                 noutput=1,
                                 feerate=1.1,
                                 totalSize=ONE_MEGABYTE)
            tx2 = self.create_tx([(tx1, 0)],
                                 noutput=1,
                                 feerate=1.8,
                                 totalSize=ONE_MEGABYTE)
            tx3 = self.create_tx([(funding_tx, 3)],
                                 noutput=1,
                                 feerate=1.1,
                                 totalSize=ONE_MEGABYTE)

            lowPaying1 = self.create_tx([(group1paying, 0)],
                                        noutput=1,
                                        feerate=0.1,
                                        totalSize=ONE_MEGABYTE)
            lowPaying2 = self.create_tx([(group2paying, 0)],
                                        noutput=1,
                                        feerate=0.2,
                                        totalSize=ONE_MEGABYTE)
            lowPaying3 = self.create_tx([(funding_tx, 1)],
                                        noutput=1,
                                        feerate=0.3,
                                        totalSize=ONE_MEGABYTE)
            lowPaying4 = self.create_tx([(lowPaying3, 0)],
                                        noutput=1,
                                        feerate=0.4,
                                        totalSize=ONE_MEGABYTE)
            lowPaying5 = self.create_tx([(tx3, 0)],
                                        noutput=1,
                                        feerate=0.5,
                                        totalSize=ONE_MEGABYTE)

            primaryMempoolTxs = [
                group1tx1, group1tx2, group1paying, group2tx1, group2tx2,
                group2paying, tx1, tx2, tx3
            ]
            secondaryMempoolTxs = [
                lowPaying1, lowPaying2, lowPaying3, lowPaying4, lowPaying5
            ]

            for tx in primaryMempoolTxs + secondaryMempoolTxs:
                conn.send_message(msg_tx(tx))
            check_mempool_equals(conn.rpc,
                                 primaryMempoolTxs + secondaryMempoolTxs)
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == len(
                primaryMempoolTxs) + 1)

            txs_in_mempool = set(primaryMempoolTxs + secondaryMempoolTxs)
            outpoints_to_spend = [(funding_tx, n) for n in range(4, 15)]

            while len(txs_in_mempool) < 299:
                tx = self.create_tx([
                    outpoints_to_spend.pop(0),
                ],
                                    noutput=2,
                                    feerate=5,
                                    totalSize=ONE_MEGABYTE)
                outpoints_to_spend.append((tx, 0))
                outpoints_to_spend.append((tx, 1))
                conn.send_message(msg_tx(tx))
                txs_in_mempool.add(tx)

            check_mempool_equals(conn.rpc,
                                 txs_in_mempool,
                                 timeout=600,
                                 check_interval=2)

            eviction_order = [
                lowPaying1, lowPaying2, lowPaying4, lowPaying3, lowPaying5,
                tx3, group2paying, group2tx2, group2tx1, group1paying,
                group1tx2, group1tx1, tx2, tx1
            ]

            conn.rpc.log.info(f"lowPaying1 = {lowPaying1.hash}")
            conn.rpc.log.info(f"lowPaying2 = {lowPaying2.hash}")
            conn.rpc.log.info(f"lowPaying3 = {lowPaying3.hash}")
            conn.rpc.log.info(f"lowPaying4 = {lowPaying4.hash}")
            conn.rpc.log.info(f"lowPaying5 = {lowPaying5.hash}")
            conn.rpc.log.info(f"tx1 = {tx1.hash}")
            conn.rpc.log.info(f"tx2 = {tx2.hash}")
            conn.rpc.log.info(f"tx3 = {tx3.hash}")
            conn.rpc.log.info(f"group2paying = {group2paying.hash}")
            conn.rpc.log.info(f"group2tx2 = {group2tx2.hash}")
            conn.rpc.log.info(f"group2tx1 = {group2tx1.hash}")
            conn.rpc.log.info(f"group1paying = {group1paying.hash}")
            conn.rpc.log.info(f"group1tx2 = {group1tx2.hash}")
            conn.rpc.log.info(f"group1tx1 = {group1tx1.hash}")

            outpoint_to_spend = (funding_tx, 15)

            for evicting in eviction_order:
                tx = self.create_tx([
                    outpoint_to_spend,
                ],
                                    noutput=1,
                                    feerate=30,
                                    totalSize=ONE_MEGABYTE)
                outpoint_to_spend = (tx, 0)
                conn.send_message(msg_tx(tx))
                txs_in_mempool.add(tx)
                txs_in_mempool.remove(evicting)
                check_mempool_equals(conn.rpc,
                                     txs_in_mempool,
                                     check_interval=0.5,
                                     timeout=60)

                # when there are still some secondary mempool transaction in the mempool
                if len(txs_in_mempool & set(secondaryMempoolTxs)) != 0:
                    # the mempoolminfee should not exceed blockmintxfee
                    assert conn.rpc.getmempoolinfo(
                    )['mempoolminfee'] <= conn.rpc.getsettings(
                    )['blockmintxfee']

        with self.run_node_with_connections(
                "Restart the node with using the disk for storing transactions.",
                0,
            [
                "-blockmintxfee=0.00001",  # 1 satoshi/byte
                "-minrelaytxfee=0",
                "-maxmempool=300MB",
                "-maxmempoolsizedisk=10MB",
                "-genesisactivationheight=1",
                '-maxstdtxvalidationduration=5000',
                '-maxnonstdtxvalidationduration=5001',
                '-maxstackmemoryusageconsensus=5MB',
                '-maxstackmemoryusagepolicy=5MB',
                '-maxscriptsizepolicy=5MB',
                '-checkmempool=0',
            ],
                number_of_connections=1) as (conn, ):

            # check that we have all txs in the mempool
            check_mempool_equals(conn.rpc,
                                 txs_in_mempool,
                                 check_interval=1,
                                 timeout=60)

            # check that we are not using the tx database
            assert conn.rpc.getmempoolinfo()['usagedisk'] == 0

            #now we have room for some more txs
            for _ in range(3):
                tx = self.create_tx([
                    outpoint_to_spend,
                ],
                                    noutput=1,
                                    feerate=1,
                                    totalSize=ONE_MEGABYTE)
                outpoint_to_spend = (tx, 0)
                conn.send_message(msg_tx(tx))
                txs_in_mempool.add(tx)
                check_mempool_equals(conn.rpc,
                                     txs_in_mempool,
                                     check_interval=0.5,
                                     timeout=60)

            # make sure that we are using the tx database now
            assert conn.rpc.getmempoolinfo()['usagedisk'] != 0

        with self.run_node_with_connections(
                "Restart the node once again to see if transaction were stored in the db.",
                0,
            [
                "-blockmintxfee=0.00001",  # 1 satoshi/byte
                "-minrelaytxfee=0",
                "-maxmempool=300MB",
                "-maxmempoolsizedisk=10MB",
                "-genesisactivationheight=1",
                '-maxstdtxvalidationduration=5000',
                '-maxnonstdtxvalidationduration=5001',
                '-maxstackmemoryusageconsensus=5MB',
                '-maxstackmemoryusagepolicy=5MB',
                '-maxscriptsizepolicy=5MB',
                '-checkmempool=0',
            ],
                number_of_connections=1) as (conn, ):

            # check that we have all txs in the mempool
            check_mempool_equals(conn.rpc,
                                 txs_in_mempool,
                                 check_interval=1,
                                 timeout=60)

            # make sure that we are using the tx database
            assert conn.rpc.getmempoolinfo()['usagedisk'] != 0
Example #20
0
 def send_block(self, block):
     self.send_message(msg_block(block))
Example #21
0
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection0 = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection0)

        node1 = NodeConnCB()
        connection1 = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection1)

        # *** Prepare node connection for early announcements testing
        node2 = NodeConnCB()
        node2.add_connection(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2))

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()

        # *** Activate early announcement functionality for this connection
        #     After this point the early announcements are not received yet -
        #     we still need to set latest announced block (CNode::pindexBestKnownBlock)
        #     which is set for e.g. by calling best headers message with locator
        #     set to non-null
        node2.wait_for_verack()
        node2.send_message(msg_sendcmpct(announce=True))

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        _, outs, block_count = prepare_init_chain(self.chain,
                                                  101,
                                                  1,
                                                  block_0=False,
                                                  start_block=0,
                                                  node=node0)
        out = outs[0]

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count,
                                            spend=out,
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count,
                                              spend=out,
                                              extra_txns=2)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block4_hard = self.chain.next_block(block_count,
                                            spend=out,
                                            extra_txns=10)
        block_count += 1

        # send three "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash},
                                self.nodes[0], self.log)

        # *** Complete early announcement setup by sending getheaders message
        #     with a non-null locator (pointing to the last block that we know
        #     of on python side - we claim that we know of all the blocks that
        #     bitcoind node knows of)
        #
        #     We also set on_cmpctblock handler as early announced blocks are
        #     announced via compact block messages instead of inv messages
        node2.send_and_ping(
            msg_getheaders(
                locator_have=[int(self.nodes[0].getbestblockhash(), 16)]))
        receivedAnnouncement = False
        waiting_for_announcement_block_hash = block2_hard.sha256

        def on_cmpctblock(conn, message):
            nonlocal receivedAnnouncement
            message.header_and_shortids.header.calc_sha256()
            if message.header_and_shortids.header.sha256 == waiting_for_announcement_block_hash:
                receivedAnnouncement = True

        node2.on_cmpctblock = on_cmpctblock

        # send one block via p2p and one via rpc
        node0.send_message(msg_block(block2_hard))

        # *** make sure that we receive announcement of the block before it has
        #     been validated
        wait_until(lambda: receivedAnnouncement)

        # making rpc call submitblock in a separate thread because waitaftervalidation is blocking
        # the return of submitblock
        submitblock_thread = threading.Thread(target=self.nodes[0].submitblock,
                                              args=(ToHex(block4_hard), ))
        submitblock_thread.start()

        # because self.nodes[0] rpc is blocked we use another rpc client
        rpc_client = get_rpc_proxy(rpc_url(
            get_datadir_path(self.options.tmpdir, 0), 0),
                                   0,
                                   coveragedir=self.options.coveragedir)

        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash},
                                   rpc_client, self.log)

        # *** prepare to intercept block3_easier announcement - it will not be
        #     announced before validation is complete as early announcement is
        #     limited to announcing one block per height (siblings are ignored)
        #     but after validation is complete we should still get the announcing
        #     compact block message
        receivedAnnouncement = False
        waiting_for_announcement_block_hash = block3_easier.sha256

        self.log.info(f"easy block3 hash: {block3_easier.hash}")
        node1.send_message(msg_block(block3_easier))

        # *** Make sure that we receive compact block announcement of the block
        #     after the validation is complete even though it was not the first
        #     block that was received by bitcoind node.
        #
        #     Also make sure that we receive inv announcement of the block after
        #     the validation is complete by the nodes that are not using early
        #     announcement functionality.
        wait_until(lambda: receivedAnnouncement)
        node0.wait_for_inv([CInv(2, block3_easier.sha256)
                            ])  # 2 == GetDataMsg::MSG_BLOCK
        # node 1 was the sender but receives inv for block non the less
        # (with early announcement that's not the case - sender does not receive the announcement)
        node1.wait_for_inv([CInv(2, block3_easier.sha256)
                            ])  # 2 == GetDataMsg::MSG_BLOCK

        rpc_client.waitforblockheight(102)
        assert_equal(block3_easier.hash, rpc_client.getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove")
        rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove")
        submitblock_thread.join()

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
Example #22
0
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        block1 = self.chain.next_block(block_count, spend=out[0], extra_txns=8)
        block_count += 1
        # send block but block him at validation point
        self.nodes[0].waitaftervalidatingblock(block1.hash, "add")
        node0.send_message(msg_block(block1))
        self.log.info(f"block1 hash: {block1.hash}")

        # make sure block hash is in waiting list
        wait_for_waiting_blocks({block1.hash}, self.nodes[0], self.log)

        # send child block
        block2 = self.chain.next_block(block_count, spend=out[1], extra_txns=10)
        block_count += 1
        node0.send_message(msg_block(block2))
        self.log.info(f"block2 hash: {block2.hash}")

        def wait_for_log():
            line_text = block2.hash + " will not be considered by the current"
            for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]):
                if line_text in line:
                    self.log.info("Found line: %s", line)
                    return True
            return False
        wait_until(wait_for_log)

        self.nodes[0].waitaftervalidatingblock(block1.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # block that arrived last on competing chain should be active
        assert_equal(block2.hash, self.nodes[0].getbestblockhash())
    def run_test(self):

        block_count = 0

        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        node2 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)
        node2.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()
        node2.wait_for_verack()

        self.log.info("Sending blocks to get spendable output")
        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))
        block = self.chain.next_block(block_count)
        block_count += 1
        self.chain.save_spendable_output()
        node0.send_message(msg_block(block))

        for i in range(100):
            block = self.chain.next_block(block_count)
            block_count += 1
            self.chain.save_spendable_output()
            node0.send_message(msg_block(block))

        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=1)
        block2_count = block_count
        block_count += 1
        self.log.info(f"blockA hash: {block2.hash}")
        node0.send_message(msg_block(block2))
        self.nodes[0].waitforblockheight(102)

        block3_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=8)
        block_count += 1
        self.chain.set_tip(block2_count)

        block4_easier = self.chain.next_block(block_count,
                                              spend=out[1],
                                              extra_txns=2)
        block_count += 1
        self.chain.set_tip(block2_count)

        block5_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=10)
        block_count += 1

        # send two "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block3 hash: {block3_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "add")
        self.log.info(f"hard block5 hash: {block5_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block3_hard.hash, block5_hard.hash},
                                self.nodes[0], self.log)

        self.log.info(
            "Sending blocks 3,4,5 on branch 2 for parallel validation")
        node0.send_message(msg_block(block3_hard))
        node2.send_message(msg_block(block5_hard))

        # make sure we started validating blocks
        wait_for_validating_blocks({block3_hard.hash, block5_hard.hash},
                                   self.nodes[0], self.log)

        self.log.info(f"easier hash: {block4_easier.hash}")
        node1.send_message(msg_block(block4_easier))
        self.nodes[0].waitforblockheight(103)

        # Because 4 is easy to validate it will be validated first and set as active tip
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "remove")
        self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "remove")

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())

        self.log.info("Sending blocks 6,7,8 on competing chain to cause reorg")
        self.chain.set_tip(tip_block_num)

        block6 = self.chain.next_block(block_count, spend=out[0], extra_txns=2)
        block_count += 1
        self.log.info(f"block6 hash: {block6.hash}")
        node0.send_message(msg_block(block6))

        block7 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block7: {block7.hash}")
        node0.send_message(msg_block(block7))

        # send one to cause reorg this should be active
        block8 = self.chain.next_block(block_count)
        block_count += 1
        self.log.info(f"block8: {block8.hash}")
        node0.send_message(msg_block(block8))

        self.nodes[0].waitforblockheight(104)
        assert_equal(block8.hash, self.nodes[0].getbestblockhash())

        self.log.info(
            "Invalidating block7 on competing chain to reorg to first branch again"
        )
        self.log.info(f"invalidating hash {block7.hash}")
        self.nodes[0].invalidateblock(block7.hash)

        #after invalidating, active block should be the one first validated on first branch
        assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())
Example #24
0
    def run_test(self):
        test_node = NodeConnCB()
        connections = []
        connections.append(
            NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
        test_node.add_connection(connections[0])
        NetworkThread().start()
        test_node.wait_for_verack()

        starting_height = 3
        self.nodes[0].generate(starting_height)

        # Create block with P2SH output and send it to node.
        # It should be validated and accepted.
        block = self.make_block_withP2SH_coinbase()
        test_node.send_message(msg_block(block))
        test_node.sync_with_ping()
        # check if block was accepted
        assert_equal(self.nodes[0].getbestblockhash(), block.hash)

        # submitblock with P2SH in coinbase tx (not included in blockchain)
        block = self.make_block_withP2SH_coinbase()
        block.solve()
        assert_raises_rpc_error(-26, "bad-txns-vout-p2sh",
                                self.nodes[0].submitblock, ToHex(block))

        # verifyblockcandidate with P2SH in coinbase tx (not included in blockchain)
        assert_raises_rpc_error(-26, "bad-txns-vout-p2sh",
                                self.nodes[0].verifyblockcandidate,
                                ToHex(block))

        # submitblock without P2SH in coinbase tx (included in blockchain)
        hashPrev = int(self.nodes[0].getbestblockhash(), 16)
        ctx = create_coinbase(self.nodes[0].getblockcount() + 1)
        block2 = create_block(hashPrev, ctx)
        block2.solve()
        self.nodes[0].submitblock(ToHex(block2))
        assert_equal(block2.hash, self.nodes[0].getbestblockhash())

        # submit block with: submitminingsolution
        # Add P2SH to coinbase output - should be rejected
        candidate = self.nodes[0].getminingcandidate(False)
        block, ctx = create_block_from_candidate(candidate, False)
        coinbase_tx = create_coinbase_P2SH(self.nodes[0].getblockcount() + 1,
                                           example_script_hash)

        # submitminingsolution with P2SH in coinbase tx - should be denied.
        assert_raises_rpc_error(
            -26, "bad-txns-vout-p2sh", self.nodes[0].submitminingsolution, {
                'id': candidate['id'],
                'nonce': block.nNonce,
                'coinbase': '{}'.format(ToHex(coinbase_tx))
            })
        # submitminingsolution without P2SH in coinbase - should be accepted
        candidate = self.nodes[0].getminingcandidate(False)
        block, ctx = create_block_from_candidate(candidate, False)
        result = self.nodes[0].submitminingsolution({
            'id':
            candidate['id'],
            'nonce':
            block.nNonce,
            'coinbase':
            '{}'.format(ToHex(ctx))
        })
        assert_equal(result, True)
        assert_equal(block.hash, self.nodes[0].getbestblockhash())

        # generatetoaddress with nonP2SH address
        height_before = self.nodes[0].getblockcount()
        address = self.nodes[0].getnewaddress()
        self.nodes[0].generatetoaddress(1, address)
        height_after = self.nodes[0].getblockcount()
        assert_equal(height_before + 1, height_after)

        # generatetoaddress with P2SH address (example for regtest: 2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc)
        assert_raises_rpc_error(-26, "bad-txns-vout-p2sh",
                                self.nodes[0].generatetoaddress, 1,
                                '2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc')
Example #25
0
    def run_test(self):
        # Setup the p2p connections and start up the network thread.
        test_node = TestNode()   # connects to node0 (not whitelisted)
        white_node = TestNode()  # connects to node1 (whitelisted)

        connections = []
        connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
        connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node))
        test_node.add_connection(connections[0])
        white_node.add_connection(connections[1])

        NetworkThread().start() # Start up network handling in another thread

        # Test logic begins here
        test_node.wait_for_verack()
        white_node.wait_for_verack()

        # 1. Have both nodes mine a block (leave IBD)
        [ n.generate(1) for n in self.nodes ]
        tips = [ int ("0x" + n.getbestblockhash() + "L", 0) for n in self.nodes ]

        # 2. Send one block that builds on each tip.
        # This should be accepted.
        blocks_h2 = []  # the height 2 blocks on each node's chain
        block_time = time.time() + 1
        for i in xrange(2):
            blocks_h2.append(create_block(tips[i], create_coinbase(), block_time))
            blocks_h2[i].solve()
            block_time += 1
        test_node.send_message(msg_block(blocks_h2[0]))
        white_node.send_message(msg_block(blocks_h2[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        assert_equal(self.nodes[0].getblockcount(), 2)
        assert_equal(self.nodes[1].getblockcount(), 2)
        print "First height 2 block accepted by both nodes"

        # 3. Send another block that builds on the original tip.
        blocks_h2f = []  # Blocks at height 2 that fork off the main chain
        for i in xrange(2):
            blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1))
            blocks_h2f[i].solve()
        test_node.send_message(msg_block(blocks_h2f[0]))
        white_node.send_message(msg_block(blocks_h2f[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        for x in self.nodes[0].getchaintips():
            if x['hash'] == blocks_h2f[0].hash:
                assert_equal(x['status'], "headers-only")

        for x in self.nodes[1].getchaintips():
            if x['hash'] == blocks_h2f[1].hash:
                assert_equal(x['status'], "valid-headers")

        print "Second height 2 block accepted only from whitelisted peer"

        # 4. Now send another block that builds on the forking chain.
        blocks_h3 = []
        for i in xrange(2):
            blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1))
            blocks_h3[i].solve()
        test_node.send_message(msg_block(blocks_h3[0]))
        white_node.send_message(msg_block(blocks_h3[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        # Since the earlier block was not processed by node0, the new block
        # can't be fully validated.
        for x in self.nodes[0].getchaintips():
            if x['hash'] == blocks_h3[0].hash:
                assert_equal(x['status'], "headers-only")

        # But this block should be accepted by node0 since it has more work.
        try:
            self.nodes[0].getblock(blocks_h3[0].hash)
            print "Unrequested more-work block accepted from non-whitelisted peer"
        except:
            raise AssertionError("Unrequested more work block was not processed")

        # Node1 should have accepted and reorged.
        assert_equal(self.nodes[1].getblockcount(), 3)
        print "Successfully reorged to length 3 chain from whitelisted peer"

        # 4b. Now mine 288 more blocks and deliver; all should be processed but
        # the last (height-too-high) on node0.  Node1 should process the tip if
        # we give it the headers chain leading to the tip.
        tips = blocks_h3
        headers_message = msg_headers()
        all_blocks = []   # node0's blocks
        for j in xrange(2):
            for i in xrange(288):
                next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1)
                next_block.solve()
                if j==0:
                    test_node.send_message(msg_block(next_block))
                    all_blocks.append(next_block)
                else:
                    headers_message.headers.append(CBlockHeader(next_block))
                tips[j] = next_block

        time.sleep(2)
        for x in all_blocks:
            try:
                self.nodes[0].getblock(x.hash)
                if x == all_blocks[287]:
                    raise AssertionError("Unrequested block too far-ahead should have been ignored")
            except:
                if x == all_blocks[287]:
                    print "Unrequested block too far-ahead not processed"
                else:
                    raise AssertionError("Unrequested block with more work should have been accepted")

        headers_message.headers.pop() # Ensure the last block is unrequested
        white_node.send_message(headers_message) # Send headers leading to tip
        white_node.send_message(msg_block(tips[1]))  # Now deliver the tip
        try:
            white_node.sync_with_ping()
            self.nodes[1].getblock(tips[1].hash)
            print "Unrequested block far ahead of tip accepted from whitelisted peer"
        except:
            raise AssertionError("Unrequested block from whitelisted peer not accepted")

        # 5. Test handling of unrequested block on the node that didn't process
        # Should still not be processed (even though it has a child that has more
        # work).
        test_node.send_message(msg_block(blocks_h2f[0]))

        # Here, if the sleep is too short, the test could falsely succeed (if the
        # node hasn't processed the block by the time the sleep returns, and then
        # the node processes it and incorrectly advances the tip).
        # But this would be caught later on, when we verify that an inv triggers
        # a getdata request for this block.
        test_node.sync_with_ping()
        assert_equal(self.nodes[0].getblockcount(), 2)
        print "Unrequested block that would complete more-work chain was ignored"

        # 6. Try to get node to request the missing block.
        # Poke the node with an inv for block at height 3 and see if that
        # triggers a getdata on block 2 (it should if block 2 is missing).
        with mininode_lock:
            # Clear state so we can check the getdata request
            test_node.last_getdata = None
            test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)]))

        test_node.sync_with_ping()
        with mininode_lock:
            getdata = test_node.last_getdata

        # Check that the getdata includes the right block
        assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256)
        print "Inv at tip triggered getdata for unprocessed block"

        # 7. Send the missing block for the third time (now it is requested)
        test_node.send_message(msg_block(blocks_h2f[0]))

        test_node.sync_with_ping()
        assert_equal(self.nodes[0].getblockcount(), 290)
        print "Successfully reorged to longer chain from non-whitelisted peer"

        [ c.disconnect_node() for c in connections ]
Example #26
0
    def run_test(self):
        with self.run_node_with_connections("Preparation", 0, None,
                                            2) as (conn1, conn2):
            last_block_time = 0
            conn1.rpc.generate(1)

            branch_1_root, last_block_time = make_block(
                conn1, last_block_time=last_block_time)
            branch_1_blocks = [branch_1_root]
            for _ in range(SAFE_MODE_MAX_FORK_DISTANCE):
                new_block, last_block_time = make_block(
                    conn1,
                    branch_1_blocks[-1],
                    last_block_time=last_block_time)
                branch_1_blocks.append(new_block)

            branch_2_root, last_block_time = make_block(
                conn2, makeValid=False, last_block_time=last_block_time)
            branch_2_blocks = [branch_2_root]
            for _ in range(SAFE_MODE_MAX_FORK_DISTANCE +
                           SAFE_MODE_MIN_POW_DIFFERENCE + 1):
                new_block, last_block_time = make_block(
                    conn2,
                    branch_2_blocks[-1],
                    last_block_time=last_block_time)
                branch_2_blocks.append(new_block)

            # send first branch that should be active tip
            send_by_headers(conn1, branch_1_blocks, do_send_blocks=True)

            # wait for active tip
            wait_for_tip(conn1, branch_1_blocks[-1].hash)

            # send second branch with more POW
            send_by_headers(conn2, branch_2_blocks, do_send_blocks=False)

            # active tip should be from first branch and second branch should have headers-only status
            wait_for_tip(conn1, branch_1_blocks[-1].hash)
            wait_for_tip_status(conn1, branch_2_blocks[-1].hash,
                                "headers-only")

            # we should not be in safe mode
            conn1.rpc.getbalance()

            # From time to time this test can run faster than expected and
            # the older blocks for batch 2 headers are not yet requested.
            # In that case they will be rejected due to being too far away
            # form the tip. In that case we need to send them again once they
            # are requested.
            def on_getdata(conn, msg):
                for i in msg.inv:
                    if i.type != 2:  # MSG_BLOCK
                        error_msg = f"Unexpected data requested {i}"
                        self.log.error(error_msg)
                        raise NotImplementedError(error_msg)
                    for block in branch_2_blocks:
                        if int(block.hash, 16) == i.hash:
                            conn.send_message(msg_block(block))
                            break

            conn2.cb.on_getdata = on_getdata

            # send sencond branch full blocks
            for block in branch_2_blocks:
                conn2.send_message(msg_block(block))

            # second branch should now be invalid
            wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "invalid")
            wait_for_tip(conn1, branch_1_blocks[-1].hash)

            # we should not be in safe mode
            conn1.rpc.getbalance()
Example #27
0
    def run_test(self):

        # Connect to node0
        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())

        network_thread_start()
        self.nodes[0].p2p.wait_for_verack()

        # Build the blockchain
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(
            self.nodes[0].getbestblockhash())['time'] + 1

        self.blocks = []

        # Get a pubkey for the coinbase TXO
        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()

        # Create the first block with a coinbase output to our key
        height = 1
        block = create_block(self.tip, create_coinbase(height,
                                                       coinbase_pubkey),
                             self.block_time)
        self.blocks.append(block)
        self.block_time += 1
        block.solve()
        # Save the coinbase for later
        self.block1 = block
        self.tip = block.sha256
        height += 1

        # Bury the block 100 deep so the coinbase output is spendable
        for i in range(100):
            block = create_block(self.tip, create_coinbase(height),
                                 self.block_time)
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # Create a transaction spending the coinbase output with an invalid (null) signature
        tx = CTransaction()
        tx.vin.append(
            CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
        tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
        tx.calc_sha256()

        block102 = create_block(self.tip, create_coinbase(height),
                                self.block_time)
        self.block_time += 1
        block102.vtx.extend([tx])
        block102.hashMerkleRoot = block102.calc_merkle_root()
        block102.rehash()
        block102.solve()
        self.blocks.append(block102)
        self.tip = block102.sha256
        self.block_time += 1
        height += 1

        # Bury the assumed valid block 8400 deep (BiblePay needs 4x as much blocks to allow -assumevalid to work)
        for i in range(8400):
            block = create_block(self.tip, create_coinbase(height),
                                 self.block_time)
            block.nVersion = 4
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # We're adding new connections so terminate the network thread
        self.nodes[0].disconnect_p2ps()
        network_thread_join()

        # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
        self.start_node(1,
                        extra_args=self.extra_args +
                        ["-assumevalid=" + hex(block102.sha256)])
        self.start_node(2,
                        extra_args=self.extra_args +
                        ["-assumevalid=" + hex(block102.sha256)])

        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
        p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
        p2p2 = self.nodes[2].add_p2p_connection(BaseNode())

        network_thread_start()

        p2p0.wait_for_verack()
        p2p1.wait_for_verack()
        p2p2.wait_for_verack()

        # Make sure nodes actually accept the many headers
        self.mocktime = self.block_time
        set_node_times(self.nodes, self.mocktime)

        # send header lists to all three nodes.
        # node0 does not need to receive all headers
        # node1 must receive all headers as otherwise assumevalid is ignored in ConnectBlock
        # node2 should NOT receive all headers to force skipping of the assumevalid check in ConnectBlock
        p2p0.send_header_for_blocks(self.blocks[0:2000])
        p2p1.send_header_for_blocks(self.blocks[0:2000])
        p2p1.send_header_for_blocks(self.blocks[2000:4000])
        p2p1.send_header_for_blocks(self.blocks[4000:6000])
        p2p1.send_header_for_blocks(self.blocks[6000:8000])
        p2p1.send_header_for_blocks(self.blocks[8000:])
        p2p2.send_header_for_blocks(self.blocks[0:200])

        # Send blocks to node0. Block 102 will be rejected.
        self.send_blocks_until_disconnected(p2p0)
        self.assert_blockchain_height(self.nodes[0], 101)

        # Send 200 blocks to node1. All blocks, including block 102, will be accepted.
        for i in range(200):
            p2p1.send_message(msg_block(self.blocks[i]))
        # Syncing so many blocks can take a while on slow systems. Give it plenty of time to sync.
        p2p1.sync_with_ping(300)
        assert_equal(
            self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'],
            200)

        # Send blocks to node2. Block 102 will be rejected.
        self.send_blocks_until_disconnected(p2p2)
        self.assert_blockchain_height(self.nodes[2], 101)
    def run_test(self):
        block_count = 0

        # Create a P2P connection
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        network_thread = NetworkThread()
        network_thread.start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        _, out, block_count = prepare_init_chain(self.chain, 101, 100, block_0=False, start_block=0, node=node0)

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        block1_num = block_count - 1

        # num of sig operations in one transaction
        num_of_sig_checks = 70

        expensive_scriptPubKey = [OP_DUP, OP_HASH160, hash160(self.coinbase_pubkey),
                                  OP_EQUALVERIFY, OP_CHECKSIG, OP_DROP] * num_of_sig_checks + [OP_DUP, OP_HASH160,
                                                                                               hash160(
                                                                                                   self.coinbase_pubkey),
                                                                                               OP_EQUALVERIFY,
                                                                                               OP_CHECKSIG]

        money_to_spend = 5000000000
        spend = out[0]

        block2_hard = self.chain.next_block(block_count)

        # creates 4000 hard transaction and 4000 transaction to spend them. It will be 8k transactions in total
        add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=4000,
                                              num_of_sig_checks=num_of_sig_checks,
                                              expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        block_count += 1
        self.log.info(f"block2_hard hash: {block2_hard.hash}")

        self.chain.set_tip(block1_num)
        block3_easier = self.chain.next_block(block_count)
        add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=1000,
                                              num_of_sig_checks=num_of_sig_checks,
                                              expensive_script=expensive_scriptPubKey)
        self.chain.update_block(block_count, add_txns)
        self.log.info(f"block3_easier hash: {block3_easier.hash}")

        node0.send_message(msg_block(block2_hard))
        node0.send_message(msg_block(block3_easier))

        def wait_for_log():
            text_activation = f"Block {block2_hard.hash} was not activated as best"
            text_block2 = "Verify 8000 txins"
            text_block3 = "Verify 2000 txins"
            results = 0
            for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]):
                if text_activation in line:
                    results += 1
                elif text_block2 in line:
                    results += 1
                elif text_block3 in line:
                    results += 1
            return True if results == 3 else False

        # wait that everything is written to the log
        # try accounting for slower machines by having a large timeout
        wait_until(wait_for_log, timeout=120)

        text_activation = f"Block {block2_hard.hash} was not activated as best"
        text_block2 = "Verify 8000 txins"
        text_block3 = "Verify 2000 txins"
        for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]):
            if text_activation in line:
                self.log.info(f"block2_hard was not activated as block3_easy won the validation race")
            elif text_block2 in line:
                line = line.split()
                self.log.info(f"block2_hard took {line[len(line) - 1]} to verify")
            elif text_block3 in line:
                line = line.split()
                self.log.info(f"block3_easy took {line[len(line)-1]} to verify")

        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
        node0.connection.close()
    def run_test(self):
        node0 = self.nodes[0].add_p2p_connection(P2PInterface())

        network_thread_start()
        node0.wait_for_verack()

        # Set node time to 60 days ago
        self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60)

        # Generating a chain of 10 blocks
        block_hashes = self.nodes[0].generate(10)

        # Create longer chain starting 2 blocks before current tip
        height = len(block_hashes) - 2
        block_hash = block_hashes[height - 1]
        block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1
        new_blocks = self.build_chain(5, block_hash, height, block_time)

        # Force reorg to a longer chain
        node0.send_message(msg_headers(new_blocks))
        node0.wait_for_getdata()
        for block in new_blocks:
            node0.send_and_ping(msg_block(block))

        # Check that reorg succeeded
        assert_equal(self.nodes[0].getblockcount(), 13)

        stale_hash = int(block_hashes[-1], 16)

        # Check that getdata request for stale block succeeds
        self.send_block_request(stale_hash, node0)
        test_function = lambda: self.last_block_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Check that getheader request for stale block header succeeds
        self.send_header_request(stale_hash, node0)
        test_function = lambda: self.last_header_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Longest chain is extended so stale is much older than chain tip
        self.nodes[0].setmocktime(0)
        tip = self.nodes[0].generate(nblocks=1)[0]
        assert_equal(self.nodes[0].getblockcount(), 14)

        # Send getdata & getheaders to refresh last received getheader message
        block_hash = int(tip, 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        # Request for very old stale block should now fail
        self.send_block_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_block_equals(stale_hash, node0)

        # Request for very old stale block header should now fail
        self.send_header_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_header_equals(stale_hash, node0)

        # Verify we can fetch very old blocks and headers on the active chain
        block_hash = int(block_hashes[2], 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        self.send_block_request(block_hash, node0)
        test_function = lambda: self.last_block_equals(block_hash, node0)
        wait_until(test_function, timeout=3)

        self.send_header_request(block_hash, node0)
        test_function = lambda: self.last_header_equals(block_hash, node0)
        wait_until(test_function, timeout=3)
    def get_tests(self):
        # shorthand for functions
        block = self.chain.next_block
        node = self.nodes[0]
        self.chain.set_genesis_hash( int(node.getbestblockhash(), 16) )

        block(0)
        yield self.accepted()

        test, out, _ = prepare_init_chain(self.chain, 101, 100)

        yield test

        ########## SCENARIO 1

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2)

        # Create and send tx1 and tx2 that are valid before genesis.
        tx1 = create_transaction(out[0].tx, out[0].n, b'', 100000, CScript([OP_IF, OP_0, OP_ELSE, OP_1, OP_ELSE, OP_2, OP_ENDIF]))
        tx2 = create_transaction(tx1, 0, CScript([OP_0]), 1, CScript([OP_TRUE]))

        self.test.connections[0].send_message(msg_tx(tx1))
        self.test.connections[0].send_message(msg_tx(tx2))
        # wait for transactions to be accepted.
        wait_until(lambda: len(node.getrawmempool()) == 2, timeout=5)
        assert_equal(True, tx1.hash in node.getrawmempool())
        assert_equal(True, tx2.hash in node.getrawmempool())

        # Generate an empty block, height is then 103 and mempool is cleared.
        block103 = block(1, spend=out[1])
        yield self.accepted()

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1)
        assert_equal(len(node.getrawmempool()), 0)

        # Send transactions tx1 and tx2 once again, this time with Genesis rules (mempool height is 104).
        self.test.connections[0].send_message(msg_tx(tx1))
        self.test.connections[0].send_message(msg_tx(tx2))
        # wait for transaction processing
        # Tx2 should not be valid anymore.
        wait_until(lambda: len(node.getrawmempool()) == 1, timeout=5)
        assert_equal(True, tx1.hash in node.getrawmempool())
        assert_equal(False, tx2.hash in node.getrawmempool())

        # Now send tx1 and tx2 again, but this time in block. Block should be rejected.
        block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25))
        add_tx_to_block(block, [tx1,tx2])
        
        rejected_blocks = []
        rejected_txs = []
        def on_reject(conn, msg):
            if (msg.message == b'block'):
                rejected_blocks.append(msg)
                assert_equal(msg.reason, b'blk-bad-inputs')

            if msg.message == b'tx':
                rejected_txs.append(msg)

        self.test.connections[0].cb.on_reject = on_reject
        self.test.connections[0].send_message(msg_block(block))

        wait_until(lambda: len(rejected_blocks) > 0, timeout=5, lock=mininode_lock)
        assert_equal(rejected_blocks[0].data, block.sha256)
        assert_equal(False, block.hash == node.getbestblockhash())

        ########## SCENARIO 2

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1)

        # Create and send tx3 and tx4 that are valid after genesis.
        tx3 = create_transaction(out[2].tx, out[2].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1]))
        tx4 = create_transaction(tx3, 0, CScript([OP_0]), 1, CScript([OP_TRUE]))

        self.test.connections[0].send_message(msg_tx(tx3))
        self.test.connections[0].send_message(msg_tx(tx4))
        # wait for transaction in mempool
        wait_until(lambda: tx3.hash in node.getrawmempool(), timeout=5)
        wait_until(lambda: tx4.hash in node.getrawmempool(), timeout=5)

        # Invalidate block -->  we are then at state before Genesis. Mempool is cleared.
        node.invalidateblock(hashToHex(block103.sha256))
        assert_equal(False, tx3.hash in node.getrawmempool())
        assert_equal(False, tx4.hash in node.getrawmempool())

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2)

        # Send tx3 again, this time in pre-genesis rules. It is accepted to mempool.
        self.test.connections[0].send_message(msg_tx(tx3))

        wait_until(lambda: tx3.hash in node.getrawmempool(), timeout=5)

        # Generate a block (height 103) with tx3 in it.
        node.generate(1)
        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1)
        assert_equal(True, tx3.hash in node.getblock(node.getbestblockhash())['tx'])

        # Send tx4 again, again with Genesis rules. It should not be accepted to mempool.
        self.test.connections[0].send_message(msg_tx(tx4))

        wait_until(lambda: tx4.sha256 in [msg.data for msg in rejected_txs], timeout=5, lock=mininode_lock)
        assert_equal(len(node.getrawmempool()), 0)

        # Send tx4 again, this time in block. Block should be rejected.
        block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25))
        add_tx_to_block(block, [tx4])
       
        self.test.connections[0].send_message(msg_block(block))

        # Wait for hash in rejected blocks
        wait_until(lambda: block.sha256 in [msg.data for msg in rejected_blocks], timeout=5, lock=mininode_lock)
        assert_equal(False, block.hash == node.getbestblockhash())
        
        ########## SCENARIO 3

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1)

        # Generate a block (height 104) with tx5 and tx6 (valid after genesis).
        tx5 = create_transaction(out[3].tx, out[3].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1]))
        tx6 = create_transaction(tx5, 0, CScript([OP_0]), 1, CScript([OP_TRUE]))
        blockGenesis = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25))
        add_tx_to_block(blockGenesis, [tx5, tx6])
        self.test.connections[0].send_message(msg_block(blockGenesis))

        wait_until(lambda: blockGenesis.hash == node.getbestblockhash(), timeout=5)
        assert_equal(True, tx5.hash in node.getblock(node.getbestblockhash())['tx'])
        assert_equal(True, tx6.hash in node.getblock(node.getbestblockhash())['tx'])

        # Invalidate block 104. tx5 and tx6 are in now in mempool.
        node.invalidateblock(hashToHex(blockGenesis.sha256))
        assert_equal(True, tx5.hash in node.getrawmempool())
        assert_equal(True, tx6.hash in node.getrawmempool())

        assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1)

        # Invalidate block 103. tx5 and tx6 are not in mempool anymore.
        node.invalidateblock(node.getbestblockhash())
        assert_equal(False, tx5.hash in node.getrawmempool())
        assert_equal(False, tx6.hash in node.getrawmempool())
Example #31
0
    def run_test(self):
        # Setup the p2p connections and start up the network thread.
        test_node = TestNode()   # connects to node0 (not whitelisted)
        white_node = TestNode()  # connects to node1 (whitelisted)

        connections = []
        connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
        connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node))
        test_node.add_connection(connections[0])
        white_node.add_connection(connections[1])

        NetworkThread().start() # Start up network handling in another thread

        # Test logic begins here
        test_node.wait_for_verack()
        white_node.wait_for_verack()

        # 1. Have both nodes mine a block (leave IBD)
        [ n.generate(1) for n in self.nodes ]
        tips = [ int ("0x" + n.getbestblockhash() + "L", 0) for n in self.nodes ]

        # 2. Send one block that builds on each tip.
        # This should be accepted.
        blocks_h2 = []  # the height 2 blocks on each node's chain
        block_time = time.time() + 1
        for i in xrange(2):
            blocks_h2.append(create_block(tips[i], create_coinbase(), block_time))
            blocks_h2[i].solve()
            block_time += 1
        test_node.send_message(msg_block(blocks_h2[0]))
        white_node.send_message(msg_block(blocks_h2[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        assert_equal(self.nodes[0].getblockcount(), 2)
        assert_equal(self.nodes[1].getblockcount(), 2)
        print "First height 2 block accepted by both nodes"

        # 3. Send another block that builds on the original tip.
        blocks_h2f = []  # Blocks at height 2 that fork off the main chain
        for i in xrange(2):
            blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1))
            blocks_h2f[i].solve()
        test_node.send_message(msg_block(blocks_h2f[0]))
        white_node.send_message(msg_block(blocks_h2f[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        for x in self.nodes[0].getchaintips():
            if x['hash'] == blocks_h2f[0].hash:
                assert_equal(x['status'], "headers-only")

        for x in self.nodes[1].getchaintips():
            if x['hash'] == blocks_h2f[1].hash:
                assert_equal(x['status'], "valid-headers")

        print "Second height 2 block accepted only from whitelisted peer"

        # 4. Now send another block that builds on the forking chain.
        blocks_h3 = []
        for i in xrange(2):
            blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1))
            blocks_h3[i].solve()
        test_node.send_message(msg_block(blocks_h3[0]))
        white_node.send_message(msg_block(blocks_h3[1]))

        [ x.sync_with_ping() for x in [test_node, white_node] ]
        # Since the earlier block was not processed by node0, the new block
        # can't be fully validated.
        for x in self.nodes[0].getchaintips():
            if x['hash'] == blocks_h3[0].hash:
                assert_equal(x['status'], "headers-only")

        # But this block should be accepted by node0 since it has more work.
        try:
            self.nodes[0].getblock(blocks_h3[0].hash)
            print "Unrequested more-work block accepted from non-whitelisted peer"
        except:
            raise AssertionError("Unrequested more work block was not processed")

        # Node1 should have accepted and reorged.
        assert_equal(self.nodes[1].getblockcount(), 3)
        print "Successfully reorged to length 3 chain from whitelisted peer"

        # 4b. Now mine 288 more blocks and deliver; all should be processed but
        # the last (height-too-high) on node0.  Node1 should process the tip if
        # we give it the headers chain leading to the tip.
        tips = blocks_h3
        headers_message = msg_headers()
        all_blocks = []   # node0's blocks
        for j in xrange(2):
            for i in xrange(288):
                next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1)
                next_block.solve()
                if j==0:
                    test_node.send_message(msg_block(next_block))
                    all_blocks.append(next_block)
                else:
                    headers_message.headers.append(CBlockHeader(next_block))
                tips[j] = next_block

        time.sleep(2)
        for x in all_blocks:
            try:
                self.nodes[0].getblock(x.hash)
                if x == all_blocks[287]:
                    raise AssertionError("Unrequested block too far-ahead should have been ignored")
            except:
                if x == all_blocks[287]:
                    print "Unrequested block too far-ahead not processed"
                else:
                    raise AssertionError("Unrequested block with more work should have been accepted")

        headers_message.headers.pop() # Ensure the last block is unrequested
        white_node.send_message(headers_message) # Send headers leading to tip
        white_node.send_message(msg_block(tips[1]))  # Now deliver the tip
        try:
            white_node.sync_with_ping()
            self.nodes[1].getblock(tips[1].hash)
            print "Unrequested block far ahead of tip accepted from whitelisted peer"
        except:
            raise AssertionError("Unrequested block from whitelisted peer not accepted")

        # 5. Test handling of unrequested block on the node that didn't process
        # Should still not be processed (even though it has a child that has more
        # work).
        test_node.send_message(msg_block(blocks_h2f[0]))

        # Here, if the sleep is too short, the test could falsely succeed (if the
        # node hasn't processed the block by the time the sleep returns, and then
        # the node processes it and incorrectly advances the tip).
        # But this would be caught later on, when we verify that an inv triggers
        # a getdata request for this block.
        test_node.sync_with_ping()
        assert_equal(self.nodes[0].getblockcount(), 2)
        print "Unrequested block that would complete more-work chain was ignored"

        # 6. Try to get node to request the missing block.
        # Poke the node with an inv for block at height 3 and see if that
        # triggers a getdata on block 2 (it should if block 2 is missing).
        with mininode_lock:
            # Clear state so we can check the getdata request
            test_node.last_getdata = None
            test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)]))

        test_node.sync_with_ping()
        with mininode_lock:
            getdata = test_node.last_getdata

        # Check that the getdata includes the right block
        assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256)
        print "Inv at tip triggered getdata for unprocessed block"

        # 7. Send the missing block for the third time (now it is requested)
        test_node.send_message(msg_block(blocks_h2f[0]))

        test_node.sync_with_ping()
        assert_equal(self.nodes[0].getblockcount(), 290)
        print "Successfully reorged to longer chain from non-whitelisted peer"

        [ c.disconnect_node() for c in connections ]
Example #32
0
    def run_test(self):
        with self.run_node_with_connections("Scenario 1: Low fee, non-whitelisted peer", 0, ["-blockmintxfee=0.00001"],
                                            number_of_connections=1) as (conn,):

            mining_fee = 1.01 # in satoshi per byte
            relayfee = float(conn.rpc.getnetworkinfo()["relayfee"] * COIN / 1000) + 0.01  # in satoshi per byte

            # create block with coinbase
            coinbase = create_coinbase(height=1)
            first_block = create_block(int(conn.rpc.getbestblockhash(), 16), coinbase=coinbase)
            first_block.solve()
            conn.send_message(msg_block(first_block))
            wait_until(lambda: conn.rpc.getbestblockhash() == first_block.hash, check_interval=0.3)

            #mature the coinbase
            conn.rpc.generate(150)


            funding_tx = self.create_tx([(coinbase, 0)], 10, 1.5)

            conn.send_message(msg_tx(funding_tx))
            check_mempool_equals(conn.rpc, [funding_tx])
            conn.rpc.generate(1)

            last_block_info = conn.rpc.getblock(conn.rpc.getbestblockhash())
            block = create_block(int(last_block_info["hash"], 16), coinbase=create_coinbase(height=last_block_info["height"] + 1), nTime=last_block_info["time"] + 1)
            low_fee_tx = self.create_tx([(funding_tx, 0)], 2, relayfee)
            block.vtx.append(low_fee_tx)
            block.hashMerkleRoot = block.calc_merkle_root()
            block.calc_sha256()
            block.solve()

            conn.send_message(msg_block(block))
            wait_until(lambda: conn.rpc.getbestblockhash() == block.hash, check_interval=0.3)

            tx_pays_relay1 =            self.create_tx([(low_fee_tx,     0)], 2, relayfee)
            tx_pays_relay2 =            self.create_tx([(tx_pays_relay1, 0)], 1, relayfee)
            tx_pays_enough_for_itself = self.create_tx([(tx_pays_relay1, 1)], 1, mining_fee)
            tx_pays_for_ancestors =     self.create_tx([(tx_pays_relay2, 0)], 1, 3.5 * mining_fee)
            tx_pays_relay3 =            self.create_tx([(tx_pays_for_ancestors, 0)], 1, relayfee)

            conn.send_message(msg_tx(tx_pays_relay1))
            check_mempool_equals(conn.rpc, [tx_pays_relay1])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"

            conn.send_message(msg_tx(tx_pays_relay2))
            check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"

            conn.send_message(msg_tx(tx_pays_enough_for_itself))
            check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"

            # this will trigger cpfp for two unpaying antcestors (tx_pays_relay1 and tx_pays_relay1) then
            # after that tx_pays_enough_for_itself will be free of ancestor debt and it will be accepted also
            conn.send_message(msg_tx(tx_pays_for_ancestors))
            check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 5), #"all tx plus coinbase"

            # still, non paying child of the tx_pays_for_ancestors will not be accepted
            conn.send_message(msg_tx(tx_pays_relay3))
            check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors, tx_pays_relay3])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 5), #"all tx, except tx_pays_relay3 plus coinbase"

            # we will mine a new block, all transactions from the journal will end up in new block, mempool will contain
            # only tx_pays_relay3
            conn.rpc.generate(1)
            check_mempool_equals(conn.rpc, [tx_pays_relay3])

            # now we invalidate block two blocks, at this point tx_pays_for_ancestors does not pay enough for
            # all non-paying children, nothing will end up in the journal.
            # non paying children are: low_fee_tx, tx_pays_relay1, tx_pays_relay2
            conn.rpc.invalidateblock(block.hash)
            check_mempool_equals(conn.rpc, [low_fee_tx, tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors, tx_pays_relay3])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"

            # when we reconsider invalidate block, everything should be the same
            conn.rpc.reconsiderblock(block.hash)
            check_mempool_equals(conn.rpc, [tx_pays_relay3])
            wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"
Example #33
0
    def run_test(self):

        # Connect to node0
        node0 = BaseNode()
        connections = []
        connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
        node0.add_connection(connections[0])

        NetworkThread().start()  # Start up network handling in another thread
        node0.wait_for_verack()

        # Build the blockchain
        self.tip = int(self.nodes[0].getbestblockhash(), 16)
        self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1

        self.blocks = []

        # Get a pubkey for the coinbase TXO
        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()

        # Create the first block with a coinbase output to our key
        height = 1
        block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time)
        self.blocks.append(block)
        self.block_time += 1
        block.solve()
        # Save the coinbase for later
        self.block1 = block
        self.tip = block.sha256
        height += 1

        # Bury the block 100 deep so the coinbase output is spendable
        for i in range(100):
            block = create_block(self.tip, create_coinbase(height), self.block_time)
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # Create a transaction spending the coinbase output with an invalid (null) signature
        tx = CTransaction()
        tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
        tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
        tx.calc_sha256()

        block102 = create_block(self.tip, create_coinbase(height), self.block_time)
        self.block_time += 1
        block102.vtx.extend([tx])
        block102.hashMerkleRoot = block102.calc_merkle_root()
        block102.rehash()
        block102.solve()
        self.blocks.append(block102)
        self.tip = block102.sha256
        self.block_time += 1
        height += 1

        # Bury the assumed valid block 2100 deep
        for i in range(2100):
            block = create_block(self.tip, create_coinbase(height), self.block_time)
            block.nVersion = 4
            block.solve()
            self.blocks.append(block)
            self.tip = block.sha256
            self.block_time += 1
            height += 1

        # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
        self.nodes.append(self.start_node(1, self.options.tmpdir,
                                     ["-assumevalid=" + hex(block102.sha256)]))
        node1 = BaseNode()  # connects to node1
        connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1))
        node1.add_connection(connections[1])
        node1.wait_for_verack()

        self.nodes.append(self.start_node(2, self.options.tmpdir,
                                     ["-assumevalid=" + hex(block102.sha256)]))
        node2 = BaseNode()  # connects to node2
        connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
        node2.add_connection(connections[2])
        node2.wait_for_verack()

        # send header lists to all three nodes
        node0.send_header_for_blocks(self.blocks[0:2000])
        node0.send_header_for_blocks(self.blocks[2000:])
        node1.send_header_for_blocks(self.blocks[0:2000])
        node1.send_header_for_blocks(self.blocks[2000:])
        node2.send_header_for_blocks(self.blocks[0:200])

        # Send blocks to node0. Block 102 will be rejected.
        self.send_blocks_until_disconnected(node0)
        self.assert_blockchain_height(self.nodes[0], 101)

        # Send all blocks to node1. All blocks will be accepted.
        for i in range(2202):
            node1.send_message(msg_block(self.blocks[i]))
        # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
        node1.sync_with_ping(120)
        assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)

        # Send blocks to node2. Block 102 will be rejected.
        self.send_blocks_until_disconnected(node2)
        self.assert_blockchain_height(self.nodes[2], 101)
Example #34
0
    def run_test(self):
        block_count = 0

        # Create a P2P connections
        node0 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)
        node0.add_connection(connection)

        node1 = NodeConnCB()
        connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1)
        node1.add_connection(connection)

        NetworkThread().start()
        # wait_for_verack ensures that the P2P connection is fully up.
        node0.wait_for_verack()
        node1.wait_for_verack()

        self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16))

        _, outs, block_count = prepare_init_chain(self.chain, 101, 1, block_0=False, start_block=0, node=node0)
        out = outs[0]

        self.log.info("waiting for block height 101 via rpc")
        self.nodes[0].waitforblockheight(101)

        tip_block_num = block_count - 1

        # adding extra transactions to get different block hashes
        block2_hard = self.chain.next_block(block_count, spend=out, extra_txns=8)
        block_count += 1

        self.chain.set_tip(tip_block_num)

        block3_easier = self.chain.next_block(block_count, spend=out, extra_txns=2)
        block_count += 1

        mining_candidate = self.nodes[0].getminingcandidate()
        block4_hard = self.chain.next_block(block_count)
        block4_hard.hashPrevBlock = int(mining_candidate["prevhash"], 16)
        block4_hard.nTime = mining_candidate["time"]
        block4_hard.nVersion = mining_candidate["version"]
        block4_hard.solve()

        mining_solution = {"id": mining_candidate["id"],
                           "nonce": block4_hard.nNonce,
                           "coinbase": ToHex(block4_hard.vtx[0]),
                           "time": mining_candidate["time"],
                           "version": mining_candidate["version"]}

        # send three "hard" blocks, with waitaftervalidatingblock we artificially
        # extend validation time.
        self.log.info(f"hard block2 hash: {block2_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add")
        self.log.info(f"hard block4 hash: {block4_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add")

        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log)

        # send one block via p2p and one via rpc
        node0.send_message(msg_block(block2_hard))

        # making rpc call submitminingsolution in a separate thread because waitaftervalidation is blocking
        # the return of submitminingsolution
        submitminingsolution_thread = threading.Thread(target=self.nodes[0].submitminingsolution, args=(mining_solution,))
        submitminingsolution_thread.start()

        # because self.nodes[0] rpc is blocked we use another rpc client
        rpc_client = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0), 0,
                                   coveragedir=self.options.coveragedir)

        wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, rpc_client, self.log)

        self.log.info(f"easy block3 hash: {block3_easier.hash}")
        node1.send_message(msg_block(block3_easier))

        rpc_client.waitforblockheight(102)
        assert_equal(block3_easier.hash, rpc_client.getbestblockhash())

        # now we can remove waiting status from blocks and finish their validation
        rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove")
        rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove")
        submitminingsolution_thread.join()

        # wait till validation of block or blocks finishes
        node0.sync_with_ping()

        # easier block should still be on tip
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
Example #35
0
    def test_nonnull_locators(self, test_node, inv_node):
        tip = int(self.nodes[0].getbestblockhash(), 16)

        # PART 1
        # 1. Mine a block; expect inv announcements each time
        self.log.info("Part 1: headers don't start before sendheaders message...")
        for i in range(4):
            old_tip = tip
            tip = self.mine_blocks(1)
            inv_node.check_last_announcement(inv=[tip], headers=[])
            test_node.check_last_announcement(inv=[tip], headers=[])
            # Try a few different responses; none should affect next announcement
            if i == 0:
                # first request the block
                test_node.send_get_data([tip])
                test_node.wait_for_block(tip)
            elif i == 1:
                # next try requesting header and block
                test_node.send_get_headers(locator=[old_tip], hashstop=tip)
                test_node.send_get_data([tip])
                test_node.wait_for_block(tip)
                test_node.clear_last_announcement()  # since we requested headers...
            elif i == 2:
                # this time announce own block via headers
                height = self.nodes[0].getblockcount()
                last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
                block_time = last_time + 1
                new_block = create_block(tip, create_coinbase(height + 1), block_time)
                new_block.nVersion = 0x20000000
                new_block.solve()
                test_node.send_header_for_blocks([new_block])
                test_node.wait_for_getdata([new_block.sha256])
                test_node.send_message(msg_block(new_block))
                test_node.sync_with_ping()  # make sure this block is processed
                inv_node.clear_last_announcement()
                test_node.clear_last_announcement()

        self.log.info("Part 1: success!")
        self.log.info("Part 2: announce blocks with headers after sendheaders message...")
        # PART 2
        # 2. Send a sendheaders message and test that headers announcements
        # commence and keep working.
        test_node.send_message(msg_sendheaders())
        prev_tip = int(self.nodes[0].getbestblockhash(), 16)
        test_node.send_get_headers(locator=[prev_tip], hashstop=0)
        test_node.sync_with_ping()

        # Now that we've synced headers, headers announcements should work
        tip = self.mine_blocks(1)
        inv_node.check_last_announcement(inv=[tip], headers=[])
        test_node.check_last_announcement(headers=[tip])

        height = self.nodes[0].getblockcount() + 1
        block_time += 10  # Advance far enough ahead
        for i in range(10):
            # Mine i blocks, and alternate announcing either via
            # inv (of tip) or via headers. After each, new blocks
            # mined by the node should successfully be announced
            # with block header, even though the blocks are never requested
            for j in range(2):
                blocks = []
                for b in range(i + 1):
                    blocks.append(create_block(tip, create_coinbase(height), block_time))
                    blocks[-1].nVersion = 0x20000000
                    blocks[-1].solve()
                    tip = blocks[-1].sha256
                    block_time += 1
                    height += 1
                if j == 0:
                    # Announce via inv
                    test_node.send_block_inv(tip)
                    test_node.wait_for_getheaders()
                    # Should have received a getheaders now
                    test_node.send_header_for_blocks(blocks)
                    # Test that duplicate inv's won't result in duplicate
                    # getdata requests, or duplicate headers announcements
                    [inv_node.send_block_inv(x.sha256) for x in blocks]
                    test_node.wait_for_getdata([x.sha256 for x in blocks])
                    inv_node.sync_with_ping()
                else:
                    # Announce via headers
                    test_node.send_header_for_blocks(blocks)
                    test_node.wait_for_getdata([x.sha256 for x in blocks])
                    # Test that duplicate headers won't result in duplicate
                    # getdata requests (the check is further down)
                    inv_node.send_header_for_blocks(blocks)
                    inv_node.sync_with_ping()
                [test_node.send_message(msg_block(x)) for x in blocks]
                test_node.sync_with_ping()
                inv_node.sync_with_ping()
                # This block should not be announced to the inv node (since it also
                # broadcast it)
                assert "inv" not in inv_node.last_message
                assert "headers" not in inv_node.last_message
                tip = self.mine_blocks(1)
                inv_node.check_last_announcement(inv=[tip], headers=[])
                test_node.check_last_announcement(headers=[tip])
                height += 1
                block_time += 1

        self.log.info("Part 2: success!")

        self.log.info("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...")

        # PART 3.  Headers announcements can stop after large reorg, and resume after
        # getheaders or inv from peer.
        for j in range(2):
            # First try mining a reorg that can propagate with header announcement
            new_block_hashes = self.mine_reorg(length=7)
            tip = new_block_hashes[-1]
            inv_node.check_last_announcement(inv=[tip], headers=[])
            test_node.check_last_announcement(headers=new_block_hashes)

            block_time += 8

            # Mine a too-large reorg, which should be announced with a single inv
            new_block_hashes = self.mine_reorg(length=8)
            tip = new_block_hashes[-1]
            inv_node.check_last_announcement(inv=[tip], headers=[])
            test_node.check_last_announcement(inv=[tip], headers=[])

            block_time += 9

            fork_point = self.nodes[0].getblock("%02x" % new_block_hashes[0])["previousblockhash"]
            fork_point = int(fork_point, 16)

            # Use getblocks/getdata
            test_node.send_getblocks(locator=[fork_point])
            test_node.check_last_announcement(inv=new_block_hashes, headers=[])
            test_node.send_get_data(new_block_hashes)
            test_node.wait_for_block(new_block_hashes[-1])

            for i in range(3):
                # Mine another block, still should get only an inv
                tip = self.mine_blocks(1)
                inv_node.check_last_announcement(inv=[tip], headers=[])
                test_node.check_last_announcement(inv=[tip], headers=[])
                if i == 0:
                    # Just get the data -- shouldn't cause headers announcements to resume
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                elif i == 1:
                    # Send a getheaders message that shouldn't trigger headers announcements
                    # to resume (best header sent will be too old)
                    test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1])
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                elif i == 2:
                    test_node.send_get_data([tip])
                    test_node.wait_for_block(tip)
                    # This time, try sending either a getheaders to trigger resumption
                    # of headers announcements, or mine a new block and inv it, also
                    # triggering resumption of headers announcements.
                    if j == 0:
                        test_node.send_get_headers(locator=[tip], hashstop=0)
                        test_node.sync_with_ping()
                    else:
                        test_node.send_block_inv(tip)
                        test_node.sync_with_ping()
            # New blocks should now be announced with header
            tip = self.mine_blocks(1)
            inv_node.check_last_announcement(inv=[tip], headers=[])
            test_node.check_last_announcement(headers=[tip])

        self.log.info("Part 3: success!")

        self.log.info("Part 4: Testing direct fetch behavior...")
        tip = self.mine_blocks(1)
        height = self.nodes[0].getblockcount() + 1
        last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
        block_time = last_time + 1

        # Create 2 blocks.  Send the blocks, then send the headers.
        blocks = []
        for b in range(2):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].nVersion = 0x20000000
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1
            inv_node.send_message(msg_block(blocks[-1]))

        inv_node.sync_with_ping()  # Make sure blocks are processed
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks)
        test_node.sync_with_ping()
        # should not have received any getdata messages
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        # This time, direct fetch should work
        blocks = []
        for b in range(3):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].nVersion = 0x20000000
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        test_node.send_header_for_blocks(blocks)
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME)

        [test_node.send_message(msg_block(x)) for x in blocks]

        test_node.sync_with_ping()

        # Now announce a header that forks the last two blocks
        tip = blocks[0].sha256
        height -= 1
        blocks = []

        # Create extra blocks for later
        for b in range(20):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].nVersion = 0x20000000
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        # Announcing one block on fork should not trigger direct fetch
        # (less work than tip)
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks[0:1])
        test_node.sync_with_ping()
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        # Announcing one more block on fork should trigger direct fetch for
        # both blocks (same work as tip)
        test_node.send_header_for_blocks(blocks[1:2])
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME)

        # Announcing 16 more headers should trigger direct fetch for 14 more
        # blocks
        test_node.send_header_for_blocks(blocks[2:18])
        test_node.sync_with_ping()
        test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME)

        # Announcing 1 more header should not trigger any response
        test_node.last_message.pop("getdata", None)
        test_node.send_header_for_blocks(blocks[18:19])
        test_node.sync_with_ping()
        with mininode_lock:
            assert "getdata" not in test_node.last_message

        self.log.info("Part 4: success!")

        # Now deliver all those blocks we announced.
        [test_node.send_message(msg_block(x)) for x in blocks]

        self.log.info("Part 5: Testing handling of unconnecting headers")
        # First we test that receipt of an unconnecting header doesn't prevent
        # chain sync.
        for i in range(10):
            test_node.last_message.pop("getdata", None)
            blocks = []
            # Create two more blocks.
            for j in range(2):
                blocks.append(create_block(tip, create_coinbase(height), block_time))
                blocks[-1].nVersion = 0x20000000
                blocks[-1].solve()
                tip = blocks[-1].sha256
                block_time += 1
                height += 1
            # Send the header of the second block -> this won't connect.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[1]])
            test_node.wait_for_getheaders()
            test_node.send_header_for_blocks(blocks)
            test_node.wait_for_getdata([x.sha256 for x in blocks])
            [test_node.send_message(msg_block(x)) for x in blocks]
            test_node.sync_with_ping()
            assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256)

        blocks = []
        # Now we test that if we repeatedly don't send connecting headers, we
        # don't go into an infinite loop trying to get them to connect.
        MAX_UNCONNECTING_HEADERS = 10
        for j in range(MAX_UNCONNECTING_HEADERS + 1):
            blocks.append(create_block(tip, create_coinbase(height), block_time))
            blocks[-1].nVersion = 0x20000000
            blocks[-1].solve()
            tip = blocks[-1].sha256
            block_time += 1
            height += 1

        for i in range(1, MAX_UNCONNECTING_HEADERS):
            # Send a header that doesn't connect, check that we get a getheaders.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[i]])
            test_node.wait_for_getheaders()

        # Next header will connect, should re-set our count:
        test_node.send_header_for_blocks([blocks[0]])

        # Remove the first two entries (blocks[1] would connect):
        blocks = blocks[2:]

        # Now try to see how many unconnecting headers we can send
        # before we get disconnected.  Should be 5*MAX_UNCONNECTING_HEADERS
        for i in range(5 * MAX_UNCONNECTING_HEADERS - 1):
            # Send a header that doesn't connect, check that we get a getheaders.
            with mininode_lock:
                test_node.last_message.pop("getheaders", None)
            test_node.send_header_for_blocks([blocks[i % len(blocks)]])
            test_node.wait_for_getheaders()

        # Eventually this stops working.
        test_node.send_header_for_blocks([blocks[-1]])

        # Should get disconnected
        test_node.wait_for_disconnect()

        self.log.info("Part 5: success!")

        # Finally, check that the inv node never received a getdata request,
        # throughout the test
        assert "getdata" not in inv_node.last_message
Example #36
0
    def get_tests(self):
        # shorthand for functions
        block = self.chain.next_block
        node = self.nodes[0]
        self.chain.set_genesis_hash(int(node.getbestblockhash(), 16))

        # Create a new block
        block(0)
        self.chain.save_spendable_output()
        yield self.accepted()

        # Now we need that block to mature so we can spend the coinbase.
        test = TestInstance(sync_every_block=False)
        for i in range(101):
            block(5000 + i)
            test.blocks_and_transactions.append([self.chain.tip, True])
            self.chain.save_spendable_output()
        yield test

        # collect spendable outputs now to avoid cluttering the code later on
        out = []
        for i in range(100):
            out.append(self.chain.get_spendable_output())

        ########## SCENARIO 1

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 2)

        # Create and send tx1 and tx2 that are valid before genesis.
        tx1 = create_transaction(
            out[0].tx, out[0].n, b'', 100000,
            CScript([OP_IF, OP_0, OP_ELSE, OP_1, OP_ELSE, OP_2, OP_ENDIF]))
        tx2 = create_transaction(tx1, 0, CScript([OP_0]), 1,
                                 CScript([OP_TRUE]))

        self.test.connections[0].send_message(msg_tx(tx1))
        self.test.connections[0].send_message(msg_tx(tx2))
        # wait for transaction processing
        sleep(1)

        # Both transactions are accepted.
        assert_equal(True, tx1.hash in node.getrawmempool())
        assert_equal(True, tx2.hash in node.getrawmempool())

        # Generate an empty block, height is then 103 and mempool is cleared.
        block103 = block(1, spend=out[1])
        yield self.accepted()

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 1)
        assert_equal(len(node.getrawmempool()), 0)

        # Send transactions tx1 and tx2 once again, this time with Genesis rules (mempool height is 104).
        self.test.connections[0].send_message(msg_tx(tx1))
        self.test.connections[0].send_message(msg_tx(tx2))
        # wait for transaction processing
        sleep(1)

        # Tx2 should not be valid anymore.
        assert_equal(len(node.getrawmempool()), 1)
        assert_equal(True, tx1.hash in node.getrawmempool())
        assert_equal(False, tx2.hash in node.getrawmempool())

        # Now send tx1 and tx2 again, but this time in block. Block should be rejected.
        block = create_block(int("0x" + node.getbestblockhash(), 16),
                             create_coinbase(height=1, outputValue=25))
        add_tx_to_block(block, [tx1, tx2])

        rejected_blocks = []

        def on_reject(conn, msg):
            if (msg.message == b'block'):
                rejected_blocks.append(msg)
                assert_equal(msg.reason, b'blk-bad-inputs')

        self.test.connections[0].cb.on_reject = on_reject
        self.test.connections[0].send_message(msg_block(block))
        sleep(1)

        assert_equal(rejected_blocks[0].data, block.sha256)
        assert_equal(False, block.hash == node.getbestblockhash())

        ########## SCENARIO 2

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 1)

        # Create and send tx3 and tx4 that are valid after genesis.
        tx3 = create_transaction(
            out[2].tx, out[2].n, b'', 100000,
            CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1]))
        tx4 = create_transaction(tx3, 0, CScript([OP_0]), 1,
                                 CScript([OP_TRUE]))

        self.test.connections[0].send_message(msg_tx(tx3))
        self.test.connections[0].send_message(msg_tx(tx4))
        # wait for transaction processing
        sleep(1)

        # Both transactions are accepted.
        assert_equal(True, tx3.hash in node.getrawmempool())
        assert_equal(True, tx4.hash in node.getrawmempool())

        # Invalidate block -->  we are then at state before Genesis. Mempool is cleared.
        node.invalidateblock(format(block103.sha256, 'x'))
        assert_equal(False, tx3.hash in node.getrawmempool())
        assert_equal(False, tx4.hash in node.getrawmempool())

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 2)

        # Send tx3 again, this time in pre-genesis rules. It is accepted to mempool.
        self.test.connections[0].send_message(msg_tx(tx3))
        sleep(1)
        assert_equal(True, tx3.hash in node.getrawmempool())

        # Generate a block (height 103) with tx3 in it.
        node.generate(1)
        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 1)
        assert_equal(True, tx3.hash
                     in node.getblock(node.getbestblockhash())['tx'])

        # Send tx4 again, again with Genesis rules. It should not be accepted to mempool.
        self.test.connections[0].send_message(msg_tx(tx4))
        sleep(1)
        assert_equal(len(node.getrawmempool()), 0)

        # Send tx4 again, this time in block. Block should be rejected.
        block = create_block(int("0x" + node.getbestblockhash(), 16),
                             create_coinbase(height=1, outputValue=25))
        add_tx_to_block(block, [tx4])

        self.test.connections[0].send_message(msg_block(block))
        sleep(1)

        assert_equal(rejected_blocks[1].data, block.sha256)
        assert_equal(False, block.hash == node.getbestblockhash())

        ########## SCENARIO 3

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 1)

        # Generate a block (height 104) with tx5 and tx6 (valid after genesis).
        tx5 = create_transaction(
            out[3].tx, out[3].n, b'', 100000,
            CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1]))
        tx6 = create_transaction(tx5, 0, CScript([OP_0]), 1,
                                 CScript([OP_TRUE]))
        blockGenesis = create_block(int("0x" + node.getbestblockhash(), 16),
                                    create_coinbase(height=1, outputValue=25))
        add_tx_to_block(blockGenesis, [tx5, tx6])
        self.test.connections[0].send_message(msg_block(blockGenesis))
        sleep(1)

        assert_equal(True, tx5.hash
                     in node.getblock(node.getbestblockhash())['tx'])
        assert_equal(True, tx6.hash
                     in node.getblock(node.getbestblockhash())['tx'])

        # Invalidate block 104. tx5 and tx6 are in now in mempool.
        node.invalidateblock(format(blockGenesis.sha256, 'x'))
        assert_equal(True, tx5.hash in node.getrawmempool())
        assert_equal(True, tx6.hash in node.getrawmempool())

        assert_equal(
            node.getblock(node.getbestblockhash())['height'],
            self.genesisactivationheight - 1)

        # Invalidate block 103. tx5 and tx6 are not in mempool anymore.
        node.invalidateblock(node.getbestblockhash())
        assert_equal(False, tx5.hash in node.getrawmempool())
        assert_equal(False, tx6.hash in node.getrawmempool())
    def run_test(self):
        node0 = self.nodes[0].add_p2p_connection(P2PInterface())

        # Set node time to 60 days ago
        self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60)

        # Generating a chain of 10 blocks
        block_hashes = self.nodes[0].generate(nblocks=10)

        # Create longer chain starting 2 blocks before current tip
        height = len(block_hashes) - 2
        block_hash = block_hashes[height - 1]
        block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1
        new_blocks = self.build_chain(5, block_hash, height, block_time)

        # Force reorg to a longer chain
        node0.send_message(msg_headers(new_blocks))
        node0.wait_for_getdata()
        for block in new_blocks:
            node0.send_and_ping(msg_block(block))

        # Check that reorg succeeded
        assert_equal(self.nodes[0].getblockcount(), 13)

        stale_hash = int(block_hashes[-1], 16)

        # Check that getdata request for stale block succeeds
        self.send_block_request(stale_hash, node0)
        test_function = lambda: self.last_block_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Check that getheader request for stale block header succeeds
        self.send_header_request(stale_hash, node0)
        test_function = lambda: self.last_header_equals(stale_hash, node0)
        wait_until(test_function, timeout=3)

        # Longest chain is extended so stale is much older than chain tip
        self.nodes[0].setmocktime(0)
        tip = self.nodes[0].generate(nblocks=1)[0]
        assert_equal(self.nodes[0].getblockcount(), 14)

        # Send getdata & getheaders to refresh last received getheader message
        block_hash = int(tip, 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        # Request for very old stale block should now fail
        self.send_block_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_block_equals(stale_hash, node0)

        # Request for very old stale block header should now fail
        self.send_header_request(stale_hash, node0)
        time.sleep(3)
        assert not self.last_header_equals(stale_hash, node0)

        # Verify we can fetch very old blocks and headers on the active chain
        block_hash = int(block_hashes[2], 16)
        self.send_block_request(block_hash, node0)
        self.send_header_request(block_hash, node0)
        node0.sync_with_ping()

        self.send_block_request(block_hash, node0)
        test_function = lambda: self.last_block_equals(block_hash, node0)
        wait_until(test_function, timeout=3)

        self.send_header_request(block_hash, node0)
        test_function = lambda: self.last_header_equals(block_hash, node0)
        wait_until(test_function, timeout=3)
    def _test_soft_consensus_freeze_competing_chains(self, spendable_txo,
                                                     node):
        self.log.info(
            "*** Performing soft consensus freeze with competing chains")

        frozen_tx = self._create_tx_mine_block_and_freeze_tx(
            node, spendable_txo)

        root_chain_tip = self.get_chain_tip()

        # mine 5 blocks on valid chain (one less than is needed for the frozen chain to become active)
        self.log.info("Mining blocks on valid chain")
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        self._mine_and_send_block(None, node)
        last_valid_block = self._mine_and_send_block(None, node)

        valid_chain_tip = self.get_chain_tip()

        self.set_chain_tip(root_chain_tip)

        self.log.info("Mining blocks on frozen chain")
        # block should not become new tip as it contains transaction spending frozen TXO
        spend_frozen_tx = self._create_tx(
            PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE]))
        frozen_block = self._mine_block(spend_frozen_tx)
        self.submit_block_and_check_tip(node, frozen_block,
                                        last_valid_block.hash)

        # next 4 blocks are also considered soft consensus frozen and must not become new tip
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        last_valid_block.hash)

        # this block is high enough for the frozen chain to become active and should become new tip
        new_frozen_tip = self._mine_and_send_block(None, node)

        frozen_chain_tip = self.get_chain_tip()

        self.log.info("Mining blocks on valid chain")
        # 2 new blocks on valid chain should trigger reorg back to valid chain
        self.set_chain_tip(valid_chain_tip)
        next_frozen_tip = self._mine_block(None)
        self.submit_block_and_check_tip(node, next_frozen_tip,
                                        new_frozen_tip.hash)
        new_valid_tip = self._mine_block(None)
        node.p2p.send_and_ping(msg_block(new_valid_tip))
        assert_equal(new_valid_tip.hash, node.rpc.getbestblockhash())
        assert (
            node.check_frozen_tx_log(next_frozen_tip.hash)
        )  # NOTE: Reject is expected because transaction spending frozen TXO is added back to mempool and its validation must fail when checked against new tip.

        self.log.info("Mining blocks on frozen chain")
        # 2 new blocks on frozen chain should trigger reorg back to frozen chain
        self.set_chain_tip(frozen_chain_tip)
        self.submit_block_and_check_tip(node, self._mine_block(None),
                                        new_valid_tip.hash)
        self._mine_and_send_block(None, node)