Exemple #1
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))

        _, 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

        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)
        easier_block_num = block_count
        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

        # make child block of easier block
        self.chain.set_tip(easier_block_num)
        block5 = self.chain.next_block(block_count)
        block5_num = block_count
        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)

        # send blocks via different p2p connection
        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)

        # send easier block through different p2p connection too
        node2.send_message(msg_block(block3_easier))
        self.log.info(f"easier block hash: {block3_easier.hash}")
        self.nodes[0].waitforblockheight(102)
        assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())

        # child block of block3_easier
        self.log.info(f"child block hash: {block5.hash}")
        self.nodes[0].waitaftervalidatingblock(block5.hash, "add")

        # make sure child block is in waiting list and then send it
        wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log)
        node2.send_message(msg_block(block5))

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

        # finish validation on block2_hard
        self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove")
        wait_for_not_validating_blocks({block2_hard.hash}, self.nodes[0],
                                       self.log)

        # finish validation on child block
        self.nodes[0].waitaftervalidatingblock(block5.hash, "remove")
        wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log)

        # block5 should be active at this point
        assert_equal(block5.hash, self.nodes[0].getbestblockhash())

        # finish validation on block4_hard
        self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove")
        wait_for_not_validating_blocks({block4_hard.hash}, self.nodes[0],
                                       self.log)

        # block5 should still be active at this point
        assert_equal(block5.hash, self.nodes[0].getbestblockhash())

        # Make three siblings and send them via same p2p connection.
        block6_hard = self.chain.next_block(block_count,
                                            spend=out[1],
                                            extra_txns=8)
        block_count += 1

        self.chain.set_tip(block5_num)

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

        self.chain.set_tip(block5_num)

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

        self.log.info(f"hard block6 hash: {block6_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block6_hard.hash, "add")
        self.log.info(f"hard block8 hash: {block8_hard.hash}")
        self.nodes[0].waitaftervalidatingblock(block8_hard.hash, "add")
        # make sure block hashes are in waiting list
        wait_for_waiting_blocks({block6_hard.hash, block8_hard.hash},
                                self.nodes[0], self.log)

        # sending blocks via same p2p connection
        node0.send_message(msg_block(block6_hard))
        node0.send_message(msg_block(block8_hard))

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

        # send easier block through same p2p connection too
        node0.send_message(msg_block(block7_easier))

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

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

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

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

        # shorthand for functions
        block = self.chain.next_block
        node = get_rpc_proxy(self.nodes[0].url,
                             1,
                             timeout=6000,
                             coveragedir=self.nodes[0].coverage_dir)

        self.chain.set_genesis_hash(int(node.getbestblockhash(), 16))

        block(0)
        yield self.accepted()

        test, out, _ = prepare_init_chain(self.chain, 200, 200)

        yield test

        # Create transaction that will almost fill block file when next block will be generated (~130 MB)
        tx1 = create_transaction(
            out[0].tx, out[0].n, b"", ONE_MEGABYTE * 120,
            CScript(
                [OP_TRUE, OP_RETURN,
                 bytearray([42] * (ONE_MEGABYTE * 120))]))
        self.test.connections[0].send_message(msg_tx(tx1))
        # Wait for transaction processing
        self.check_mempool(node, [tx1], timeout=6000)

        # Mine block with new transaction.
        minedBlock1 = node.generate(1)

        # Send 4 large (~1GB) transactions that will go into next block
        for i in range(4):
            txLarge = create_transaction(
                out[1 + i].tx, out[1 + i].n, b"", ONE_GIGABYTE,
                CScript([
                    OP_TRUE, OP_RETURN,
                    bytearray([42] * (ONE_GIGABYTE - ONE_MEGABYTE))
                ]))
            self.test.connections[0].send_message(msg_tx(txLarge))
            self.check_mempool(node, [txLarge], timeout=6000)

        # Send overflow
        txOverflow = create_transaction(
            out[5].tx, out[5].n, b"", ONE_MEGABYTE * 305,
            CScript(
                [OP_TRUE, OP_RETURN,
                 bytearray([42] * (ONE_MEGABYTE * 305))]))
        self.test.connections[0].send_message(msg_tx(txOverflow))
        self.check_mempool(node, [txOverflow], timeout=6000)

        # Mine block with new transactions.
        minedBlock2 = node.generate(1)

        txLast = create_transaction(
            out[6].tx, out[6].n, b"", ONE_MEGABYTE,
            CScript([OP_TRUE, OP_RETURN,
                     bytearray([42] * (ONE_MEGABYTE))]))
        self.test.connections[0].send_message(msg_tx(txLast))
        self.check_mempool(node, [txLast], timeout=6000)

        # Mine block with new transaction.
        minedBlock3 = node.generate(1)

        # Restart node to make sure that the index is written to disk
        self.stop_nodes()
        self.nodes[0].rpc_timeout = 6000
        self.start_nodes(self.extra_args)

        # Get proxy with bigger timeout
        node = get_rpc_proxy(self.nodes[0].url,
                             1,
                             timeout=6000,
                             coveragedir=self.nodes[0].coverage_dir)

        # Verify that blocks were correctly written / read
        blockDetails1 = node.getblock(minedBlock1[0])
        blockDetails2 = node.getblock(minedBlock2[0])
        blockDetails3 = node.getblock(minedBlock3[0])

        assert_equal(minedBlock1[0], blockDetails1['hash'])
        assert_equal(minedBlock2[0], blockDetails2['hash'])
        assert_equal(minedBlock3[0], blockDetails3['hash'])

        for txId in blockDetails1['tx']:
            txCopy = node.getrawtransaction(txId, 1)
            assert_equal(txId, txCopy['txid'])

        for txId in blockDetails2['tx']:
            txCopy = node.getrawtransaction(txId, 1)
            assert_equal(txId, txCopy['txid'])

        for txId in blockDetails3['tx']:
            txCopy = node.getrawtransaction(txId, 1)
            assert_equal(txId, txCopy['txid'])
Exemple #3
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())
Exemple #4
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)

        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

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

        self.nodes[0].waitforblockheight(102)

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

        self.chain.set_tip(block2_num)

        block4 = self.chain.next_block(block_count, spend=out[1], 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.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({block3.hash, block4.hash}, self.nodes[0],
                                self.log)

        node0.send_message(msg_block(block3))
        node2.send_message(msg_block(block4))

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

        # right branch
        self.chain.set_tip(tip_block_num)
        block5 = self.chain.next_block(block_count,
                                       spend=out[0],
                                       extra_txns=10)
        block_count += 1
        node1.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"
        block6 = self.chain.next_block(block_count)
        node1.send_message(msg_block(block6))
        self.log.info(f"block6 hash: {block6.hash}")
        block_count += 1

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

        self.nodes[0].waitforblockheight(104)
        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(block3.hash, "remove")
        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())
    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())
Exemple #6
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())
    def get_tests(self):

        rejected_txs = []
        def on_reject(conn, msg):
            if msg.message == b'tx':
                rejected_txs.append(msg)
        
        self.test.connections[0].cb.on_reject = on_reject

        # 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, 150, 150)

        yield test

        # Create transaction with OP_ADD in the locking script which should be banned 
        txOpAdd1 = create_transaction(out[0].tx, out[0].n, b'', 100000, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd1])
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 1, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd2))
        # wait for transaction processing
        wait_until(lambda: txOpAdd2.sha256 in [msg.data for msg in rejected_txs], timeout=5, lock=mininode_lock)

        assert_equal(len(rejected_txs), 1) # rejected
        assert_equal(rejected_txs[0].reason, b'max-script-num-length-policy-limit-violated (Script number overflow)')
        wait_until(lambda: len(self.nodes[0].listbanned()) == 1, timeout=5)  # and banned
        self.nodes[0].clearbanned()
        wait_until(lambda: len(self.nodes[0].listbanned()) == 0, timeout=5)  # and not banned
        rejected_txs = []

        # node was banned we need to restart the node
        self.restart_network()
        # TODO: This sleep needs to be replaced with a proper wait_until function
        sleep(3)
        self.test.connections[0].cb.on_reject = on_reject

        # generate a block, height is genesis gracefull height - 2
        block(1)
        # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block
        txOpElse1 = create_transaction(out[1].tx, out[1].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        #update Block with OP_ELSE transaction
        self.chain.update_block(1, [txOpElse1])
        yield self.accepted()

        # generate a block, height is genesis gracefull height - 1
        block(2)
        # Create transaction that spends the previous transaction
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE]))
        # Update block with new transactions.
        self.chain.update_block(2, [txOpElse2])
        yield self.accepted()

        assert_equal(len(rejected_txs), 0) #not rejected
        assert len(self.nodes[0].listbanned()) == 0  # and not banned


        # Create transaction with OP_ELSE in the locking script and send it to mempool which should be accepted
        txOpElse1 = create_transaction(out[2].tx, out[2].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.test.connections[0].send_message(msg_tx(txOpElse1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse1])
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElse2))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse2])

        assert_equal(len(rejected_txs), 0) # not rejected
        assert len(self.nodes[0].listbanned()) == 0  # and not banned


        # generate a block to move into genesis gracefull height
        block(3)
        # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block and will be spent later when we move into Genesis
        txOpElseIsSpentInGenesis1 = create_transaction(out[3].tx, out[3].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.chain.update_block(3, [txOpElseIsSpentInGenesis1])
        yield self.accepted()


        # Create transaction with OP_ELSE in the locking script and send it to mempool which should be accepted
        txOpElse1 = create_transaction(out[4].tx, out[4].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.test.connections[0].send_message(msg_tx(txOpElse1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse1])
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElse2))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse2])

        assert_equal(len(rejected_txs), 0) #not rejected
        assert len(self.nodes[0].listbanned()) == 0  # and not banned


        # Create transaction with OP_ADD in the locking script and send it to mempool 
        # which should not be banned (but should be rejected instead), because we are in Genesis gracefull period now
        txOpAdd1 = create_transaction(out[5].tx, out[5].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd1])
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd2))
        # wait for transaction processing

        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Script number overflow)')
        assert len(self.nodes[0].listbanned()) == 0  # and not banned
        rejected_txs = []

        # generate a block, height is  genesis gracefull height + 1
        block(4)
        # Create transaction with OP_ELSE in the locking script which should be accepted to block
        txOpElse1 = create_transaction(out[6].tx, out[6].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        #update Block with OP_ELSE transaction
        self.chain.update_block(4, [txOpElse1])
        yield self.accepted()

        # generate a block, height is genesis gracefull height + 2
        block(5)
        # Create transaction that spends the previous transaction
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE]))
        # Update block with new transactions.
        self.chain.update_block(5, [txOpElse2])
        yield self.accepted()

        assert_equal(len(rejected_txs), 0) # not rejected
        assert len(self.nodes[0].listbanned()) == 0  # not banned

        # Create transaction that will check if CheckRegularTransaction method inside TxnValidation method will reject instead ban the node
        txPubKeys = create_transaction(out[7].tx, out[7].n, b'', 100000, CScript(([OP_1] + makePubKeys(1) + [5000, OP_CHECKMULTISIG])*1001))
        self.test.connections[0].send_message(msg_tx(txPubKeys))
        # wait for transaction processing
        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'flexible-bad-txn-sigops')
        assert len(self.nodes[0].listbanned()) == 0  # not banned
        rejected_txs = []


        #now we need to raise block count so we are in genesis
        height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height']
        for x in range(height, self.genesisactivationheight):
            block(6000 + x)
            test.blocks_and_transactions.append([self.chain.tip, True])
        yield test
        height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height']
        assert_equal(height, self.genesisactivationheight) # check if we are in right height


        # Create transaction with OP_ELSE in the locking script and send it to mempool 
        # which should not be banned but should be rejected now
        txOpElse1 = create_transaction(out[8].tx, out[8].n, b'', 100004, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.test.connections[0].send_message(msg_tx(txOpElse1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse1])
        # Create transaction that spends the previous transaction
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 4, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElse2))
        # wait for transaction processing
        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Invalid OP_IF construction)')
        assert len(self.nodes[0].listbanned()) == 0  # not banned
        rejected_txs = []


        # generate an empty block, height is Genesis + 1
        block(6)
        #Create transaction with OP_ADD in the locking script that should be accepted to block
        txOpAdd1 = create_transaction(out[9].tx, out[9].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.chain.update_block(6, [txOpAdd1])
        yield self.accepted()

        # generate an empty block, height is Genesis + 2 and make 
        block(7)
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE]))
        self.chain.update_block(7, [txOpAdd2])
        yield self.accepted()

        assert_equal(len(rejected_txs), 0) #not rejected
        assert len(self.nodes[0].listbanned()) == 0  # not banned


        # Create transaction with OP_ADD in the locking script and send it to mempool 
        # which should not be banned (but should be rejected instead), because we are in genesis gracefull period now
        txOpAdd1 = create_transaction(out[10].tx, out[10].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd1])
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd2))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd2])

        assert_equal(len(rejected_txs), 0) # not rejected
        assert len(self.nodes[0].listbanned()) == 0  # not banned


        # Create transaction that spends the OP_ELSE transaction that was put into block before Genesis
        txOpElseIsSpentInGenesis2 = create_transaction(txOpElseIsSpentInGenesis1, 0, b'', 4, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElseIsSpentInGenesis2))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElseIsSpentInGenesis2])

        assert_equal(len(rejected_txs), 0) #not rejected
        assert len(self.nodes[0].listbanned()) == 0  # not banned


        # generate a block
        block(8)
        # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block and will be spent later when we move into Genesis
        txOpElseIsSpentInGenesis3 = create_transaction(out[11].tx, out[11].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.chain.update_block(8, [txOpElseIsSpentInGenesis3])
        yield self.accepted()

        # Create transaction that spends the OP_ELSE transaction that was put into block after Genesis
        txOpElseIsSpentInGenesis4 = create_transaction(txOpElseIsSpentInGenesis3, 0, b'', 4, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElseIsSpentInGenesis4))
        # wait for transaction processing
        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Invalid OP_IF construction)')
        assert len(self.nodes[0].listbanned()) == 0  # not banned
        rejected_txs = []


        height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height']
        genesisHeightNoGracefullPeriod = self.genesisactivationheight + int(GENESIS_GRACEFULL_ACTIVATION_PERIOD)

        #now we need to raise block count so we are in genesis but before gracefull period is over
        for x in range(height, genesisHeightNoGracefullPeriod):
            block(6000 + x)
            test.blocks_and_transactions.append([self.chain.tip, True])
        yield test
        height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height']
        assert_equal(height, self.genesisactivationheight + int(GENESIS_GRACEFULL_ACTIVATION_PERIOD)) # check if we are in right height


        # generate an empty block, height is Genesis + gracefull period + 1, we moved beyond gracefull period now
        block(9, spend=out[12])
        yield self.accepted()

        # generate a block, height is Genesis + gracefull period + 2
        block(10)
        #Create transaction with OP_ADD in the locking script that should be accepted to block
        txOpAdd1 = create_transaction(out[13].tx, out[13].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.chain.update_block(10, [txOpAdd1])
        yield self.accepted()

        # generate a block, height is Genesis + gracefull period + 3
        block(11)
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE]))
        self.chain.update_block(11, [txOpAdd2])
        yield self.accepted()
 
        assert_equal(len(rejected_txs), 0) # accepted
        assert len(self.nodes[0].listbanned()) == 0  # not banned


        # Create transaction with OP_ADD in the locking script and send it to mempool 
        # which should not be banned (but should be rejected instead), because we are in genesis gracefull period now
        txOpAdd1 = create_transaction(out[14].tx, out[14].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd1])
        # Create transaction that spends the previous transaction
        txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpAdd2))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpAdd2])

        assert_equal(len(rejected_txs), 0) # accepted
        assert len(self.nodes[0].listbanned()) == 0  # not banned


        # Create transaction with OP_ELSE in the locking script which should now be banned
        txOpElse1 = create_transaction(out[15].tx, out[15].n, b'', 100004, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF]))
        self.test.connections[0].send_message(msg_tx(txOpElse1))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txOpElse1])
        # Create transaction that spends the previous transaction
        txOpElse2 = create_transaction(txOpElse1, 0, b'', 4, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txOpElse2))
        # wait for transaction processing
        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'mandatory-script-verify-flag-failed (Invalid OP_IF construction)')
        wait_until(lambda: len(self.nodes[0].listbanned()) == 1, timeout=5) # banned
        rejected_txs = []


        self.nodes[0].clearbanned()
        wait_until(lambda: len(self.nodes[0].listbanned()) == 0, timeout=5) # and not banned
        rejected_txs = []
        # node was banned we need to restart the node
        self.restart_network()
        # TODO: This sleep needs to be replaced with a proper wait_until function
        sleep(3)
        self.test.connections[0].cb.on_reject = on_reject


        # Create transaction with OP_NOP that exceeds policy limits to check that node does not get banned for exceeding our policy limit
        txPubKeys = create_transaction(out[16].tx, out[16].n, b'', 100004, CScript([OP_TRUE] + [OP_NOP] * 1001))
        self.test.connections[0].send_message(msg_tx(txPubKeys))
        # wait for transaction processing
        self.check_mempool(self.test.connections[0].rpc, [txPubKeys])
        # Create transaction that spends the previous transaction
        txPubKeys2 = create_transaction(txPubKeys, 0, b'', 4, CScript([OP_TRUE]))
        self.test.connections[0].send_message(msg_tx(txPubKeys2))
        # wait for transaction processing
        wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock)  # rejected
        assert_equal(len(rejected_txs), 1)
        assert_equal(rejected_txs[0].reason, b'non-mandatory-script-verify-flag (Operation limit exceeded)')
        assert len(self.nodes[0].listbanned()) == 0  # banned
    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):
        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())
Exemple #10
0
    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))

        _, 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

        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())
    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, 105, 100)

        yield test

        # Block with height 107.
        block(1, spend=out[0])
        yield self.accepted()

        # Create block with height 108 (genesis NOT activated).
        block(2, spend=out[1])
        tx0 = create_transaction(out[2].tx, out[2].n, b"", 100000,
                                 CScript([OP_RETURN]))
        self.chain.update_block(2, [tx0])
        # \x51 is OP_TRUE
        tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE]))
        b108_rejected = self.chain.update_block(2, [tx1])
        self.log.info(
            "Created block %s on height %d that tries to spend from block on height %d.",
            b108_rejected.hash, self.genesisactivationheight - 1,
            self.genesisactivationheight - 1)
        yield self.rejected(RejectResult(16,
                                         b'bad-txns-inputs-missingorspent'))

        # Rewind bad block (height is 107).
        self.chain.set_tip(1)

        # Create block on height 108 (genesis NOT activated).
        block(3, spend=out[3])
        tx0 = create_transaction(out[4].tx, out[4].n, b"", 100000,
                                 CScript([OP_RETURN]))
        b108 = self.chain.update_block(3, [tx0])
        self.log.info("Created block %s on height %d.", b108.hash,
                      self.genesisactivationheight - 1)
        yield self.accepted()

        # Create block on height 109 and try to spend from block on height 108
        block(4, spend=out[5])
        # \x51 is OP_TRUE
        tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE]))
        b109_rejected = self.chain.update_block(4, [tx1])
        self.log.info(
            "Created block %s on height %d that tries to spend from block on height %d.",
            b109_rejected.hash, self.genesisactivationheight,
            self.genesisactivationheight - 1)
        yield self.rejected(RejectResult(16,
                                         b'bad-txns-inputs-missingorspent'))

        # Rewind bad block (height is 108).
        self.chain.set_tip(3)

        # Create block on height 109 and try to spend from block on height 109.
        block(5, spend=out[6])
        tx0 = create_transaction(out[7].tx, out[7].n, b"", 100000,
                                 CScript([OP_RETURN]))

        self.chain.update_block(5, [tx0])
        # \x51 is OP_TRUE
        tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE]))
        b109_accepted = self.chain.update_block(5, [tx1])
        self.log.info(
            "Created block %s on height %d that tries to spend from block on height %d.",
            b109_accepted.hash, self.genesisactivationheight,
            self.genesisactivationheight)
        yield self.accepted()

        #############

        # At this point, we have tx0 and tx1 in cache script marked as valid.
        # Now, invalidate blocks 109 and 108 so that we are in state before genesis.

        node.invalidateblock(hashToHex(b109_accepted.sha256))
        sleep(1)

        # tx0 and tx1 are in mempool (currently valid because it was sent after genesis)
        assert_equal(True, tx0.hash in node.getrawmempool())
        assert_equal(True, tx1.hash in node.getrawmempool())

        node.invalidateblock(hashToHex(b108.sha256))

        # tx0 and tx1 are not in mempool (mempool is deleted when 108 is invalidated)
        assert_equal(False, tx0.hash in node.getrawmempool())
        assert_equal(False, tx1.hash in node.getrawmempool())

        # So we are at height 107.
        assert_equal(node.getblock(node.getbestblockhash())['height'], 107)

        self.nodes[0].generate(1)
        tx = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['tx']
        assert_equal(False, tx0.hash in tx)
        assert_equal(False, tx1.hash in tx)

        # Now we are at height 108.
        assert_equal(node.getblock(node.getbestblockhash())['height'], 108)
    def get_tests(self):

        # Shorthand for functions
        block = self.chain.next_block

        # Get proxy with bigger timeout
        node = get_rpc_proxy(self.nodes[0].url,
                             1,
                             timeout=6000,
                             coveragedir=self.nodes[0].coverage_dir)

        self.chain.set_genesis_hash(int(node.getbestblockhash(), 16))

        block(0)
        yield self.accepted()

        test, out, _ = prepare_init_chain(self.chain, 200, 200)

        yield test

        # Create transaction that will almost fill block file when next block will be generated (~130 MB)
        tx1 = create_transaction(
            out[0].tx, out[0].n, b"", ONE_MEGABYTE * 120,
            CScript(
                [OP_FALSE, OP_RETURN,
                 bytearray([42] * (ONE_MEGABYTE * 120))]))
        self.test.connections[0].send_message(msg_tx(tx1))
        # Wait for transaction processing
        self.check_mempool(node, [tx1], timeout=6000)
        # Mine block with new transaction.
        minedBlock1 = node.generate(1)

        # Send 4 large (~1GB) transactions that will go into next block
        for i in range(4):
            txLarge = create_transaction(
                out[1 + i].tx, out[1 + i].n, b"", ONE_GIGABYTE,
                CScript([
                    OP_FALSE, OP_RETURN,
                    bytearray([42] * (ONE_GIGABYTE - ONE_MEGABYTE))
                ]))
            self.test.connections[0].send_message(msg_tx(txLarge))
            self.check_mempool(node, [txLarge], timeout=6000)
        # Send transaction with size that will overflow 32 bit size
        txOverflow = create_transaction(
            out[5].tx, out[5].n, b"", ONE_MEGABYTE * 305,
            CScript(
                [OP_FALSE, OP_RETURN,
                 bytearray([42] * (ONE_MEGABYTE * 305))]))
        self.test.connections[0].send_message(msg_tx(txOverflow))
        self.check_mempool(node, [txOverflow], timeout=6000)
        # Mine block with new transactions with size > 4GB. This will write to new block file on disk.
        minedBlock2 = node.generate(1)
        # Make sure that block is larger than 32 bit max
        assert_greater_than(node.getblock(minedBlock2[0], True)['size'], 2**32)

        # Generate another transaction for next block
        tx2 = create_transaction(
            out[10].tx, out[10].n, b"", ONE_MEGABYTE,
            CScript([OP_FALSE, OP_RETURN,
                     bytearray([42] * (ONE_MEGABYTE))]))
        self.test.connections[0].send_message(msg_tx(tx2))
        self.check_mempool(node, [tx2], timeout=6000)
        # Mine block with new transactions. This will write to new block file on disk.
        minedBlock3 = node.generate(1)

        # Get block count
        blockcount = node.getblockcount()

        # Restart node with reindex option
        self.stop_nodes()
        self.extra_args[0].append("-reindex")
        self.start_nodes(self.extra_args)

        # Get proxy with bigger timeout
        node = get_rpc_proxy(self.nodes[0].url,
                             1,
                             timeout=6000,
                             coveragedir=self.nodes[0].coverage_dir)

        # Get block count after reindex and compare it to block count before node shutdown - should be equal
        while node.getblockcount() < blockcount:
            sleep(0.1)
        assert_equal(node.getblockcount(), blockcount)
    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))

        _, 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

        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)