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(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(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 102 blocks to node0. Block 102 will be rejected.
        for i in range(101):
            node0.send_message(msg_block(self.blocks[i]))
        node0.sync_with_ping()  # make sure the most recent block is synced
        node0.send_message(msg_block(self.blocks[101]))
        assert_equal(self.nodes[0].getblock(
            self.nodes[0].getbestblockhash())['height'], 101)

        # Send 3102 blocks to node1. All blocks will be accepted.
        for i in range(2202):
            node1.send_message(msg_block(self.blocks[i]))
        node1.sync_with_ping()  # make sure the most recent block is synced
        assert_equal(self.nodes[1].getblock(
            self.nodes[1].getbestblockhash())['height'], 2202)

        # Send 102 blocks to node2. Block 102 will be rejected.
        for i in range(101):
            node2.send_message(msg_block(self.blocks[i]))
        node2.sync_with_ping()  # make sure the most recent block is synced
        node2.send_message(msg_block(self.blocks[101]))
        assert_equal(self.nodes[2].getblock(
            self.nodes[2].getbestblockhash())['height'], 101)
    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            [tx.rehash() for tx in new_transactions]
            block = self.blocks[block_number]
            block.vtx.extend(new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block
        node = self.nodes[0]

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"replayprotection")
        public_key = private_key.get_pubkey()

        # This is a little handier to use than the version in blocktools.py
        def create_fund_and_spend_tx(spend, forkvalue=0):
            # Fund transaction
            script = CScript([public_key, OP_CHECKSIG])
            txfund = create_transaction(
                spend.tx, spend.n, b'', 50 * COIN, script)
            txfund.rehash()

            # Spend transaction
            txspend = CTransaction()
            txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE])))
            txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b''))

            # Sign the transaction
            sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID
            sighash = SignatureHashForkId(
                script, txspend, 0, sighashtype, 50 * COIN)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            txspend.vin[0].scriptSig = CScript([sig])
            txspend.rehash()

            return [txfund, txspend]

        def send_transaction_to_mempool(tx):
            tx_id = node.sendrawtransaction(ToHex(tx))
            assert(tx_id in set(node.getrawmempool()))
            return tx_id

        # Before the fork, no replay protection required to get in the mempool.
        txns = create_fund_and_spend_tx(out[0])
        send_transaction_to_mempool(txns[0])
        send_transaction_to_mempool(txns[1])

        # And txns get mined in a block properly.
        block(1)
        update_block(1, txns)
        yield accepted()

        # Replay protected transactions are rejected.
        replay_txns = create_fund_and_spend_tx(out[1], 0xffdead)
        send_transaction_to_mempool(replay_txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        # And block containing them are rejected as well.
        block(2)
        update_block(2, replay_txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(1)

        # Create a block that would activate the replay protection.
        bfork = block(5555)
        bfork.nTime = REPLAY_PROTECTION_START_TIME - 1
        update_block(5555, [])
        yield accepted()

        for i in range(5):
            block(5100 + i)
            test.blocks_and_transactions.append([self.tip, True])
        yield test

        # Check we are just before the activation time
        assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'],
                     REPLAY_PROTECTION_START_TIME - 1)

        # We are just before the fork, replay protected txns still are rejected
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        block(3)
        update_block(3, replay_txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(5104)

        # Send some non replay protected txns in the mempool to check
        # they get cleaned at activation.
        txns = create_fund_and_spend_tx(out[2])
        send_transaction_to_mempool(txns[0])
        tx_id = send_transaction_to_mempool(txns[1])

        # Activate the replay protection
        block(5556)
        yield accepted()

        # Non replay protected transactions are not valid anymore,
        # so they should be removed from the mempool.
        assert(tx_id not in set(node.getrawmempool()))

        # Good old transactions are now invalid.
        send_transaction_to_mempool(txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(txns[1]))

        # They also cannot be mined
        block(4)
        update_block(4, txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(5556)

        # The replay protected transaction is now valid
        send_transaction_to_mempool(replay_txns[0])
        replay_tx_id = send_transaction_to_mempool(replay_txns[1])

        # They also can also be mined
        b5 = block(5)
        update_block(5, replay_txns)
        yield accepted()

        # Ok, now we check if a reorg work properly accross the activation.
        postforkblockid = node.getbestblockhash()
        node.invalidateblock(postforkblockid)
        assert(replay_tx_id in set(node.getrawmempool()))

        # Deactivating replay protection.
        forkblockid = node.getbestblockhash()
        node.invalidateblock(forkblockid)
        assert(replay_tx_id not in set(node.getrawmempool()))

        # Check that we also do it properly on deeper reorg.
        node.reconsiderblock(forkblockid)
        node.reconsiderblock(postforkblockid)
        node.invalidateblock(forkblockid)
        assert(replay_tx_id not in set(node.getrawmempool()))
Esempio n. 3
0
class FullBlockTest(ComparisonTestFramework):

    ''' Can either run this test as 1 node with expected answers, or two and compare them. 
        Change the "outcome" variable from each TestInstance object to only do the comparison. '''
    def __init__(self):
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(bytes("horsebattery"))
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.block_time = int(time.time())+1
        self.tip = None
        self.blocks = {}

    def run_test(self):
        test = TestManager(self, self.options.tmpdir)
        test.add_all_connections(self.nodes)
        NetworkThread().start() # Start up network handling in another thread
        test.run()

    def add_transactions_to_block(self, block, tx_list):
        [ tx.rehash() for tx in tx_list ]
        block.vtx.extend(tx_list)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()
        return block
    
    # Create a block on top of self.tip, and advance self.tip to point to the new block
    # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output,
    # and rest will go to fees.
    def next_block(self, number, spend=None, additional_coinbase_value=0, script=None):
        if self.tip == None:
            base_block_hash = self.genesis_hash
        else:
            base_block_hash = self.tip.sha256
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, self.block_time)
        if (spend != None):
            tx = CTransaction()
            tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), "", 0xffffffff))  # no signature yet
            # This copies the java comparison tool testing behavior: the first
            # txout has a garbage scriptPubKey, "to make sure we're not
            # pre-verifying too much" (?)
            tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            # Now sign it if necessary
            scriptSig = ""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL)
                scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            block = self.add_transactions_to_block(block, [tx])
        block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        self.block_time += 1
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previous marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected():
            return TestInstance([[self.tip, False]])
       
        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # creates a new block and advances the tip to that block
        block = self.next_block


        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()


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


        # Start by bulding a couple of blocks on top (which output is spent is in parentheses):
        #     genesis -> b1 (0) -> b2 (1)
        out0 = get_spendable_output()
        block(1, spend=out0)
        save_spendable_output()
        yield accepted()

        out1 = get_spendable_output()
        block(2, spend=out1)
        # Inv again, then deliver twice (shouldn't break anything).
        yield accepted()


        # so fork like this:
        # 
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1)
        # 
        # Nothing should happen at this point. We saw b2 first so it takes priority.
        tip(1)
        block(3, spend=out1)
        # Deliver twice (should still not break anything)
        yield rejected()


        # Now we add another block to make the alternative chain longer.
        # 
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1) -> b4 (2)
        out2 = get_spendable_output()
        block(4, spend=out2)
        yield accepted()


        # ... and back to the first chain.
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                      \-> b3 (1) -> b4 (2)
        tip(2)
        block(5, spend=out2)
        save_spendable_output()
        yield rejected()

        out3 = get_spendable_output()
        block(6, spend=out3)
        yield accepted()


        # Try to create a fork that double-spends
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                          \-> b7 (2) -> b8 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(7, spend=out2)
        yield rejected()

        out4 = get_spendable_output()
        block(8, spend=out4)
        yield rejected()


        # Try to create a block that has too much fee
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                                    \-> b9 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(6)
        block(9, spend=out4, additional_coinbase_value=1)
        yield rejected()

        
        # Create a fork that ends in a block with too much fee (the one that causes the reorg)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b10 (3) -> b11 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(10, spend=out3)
        yield rejected()

        block(11, spend=out4, additional_coinbase_value=1)
        yield rejected()


        # Try again, but with a valid fork first
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b14 (5)
        #                                              (b12 added last)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        b12 = block(12, spend=out3)
        save_spendable_output()
        #yield TestInstance([[b12, False]])
        b13 = block(13, spend=out4)
        # Deliver the block header for b12, and the block b13.
        # b13 should be accepted but the tip won't advance until b12 is delivered.
        yield TestInstance([[CBlockHeader(b12), None], [b13, False]])

        save_spendable_output()
        out5 = get_spendable_output()
        # b14 is invalid, but the node won't know that until it tries to connect
        # Tip still can't advance because b12 is missing
        block(14, spend=out5, additional_coinbase_value=1)
        yield rejected()

        yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13.

        
        # Test that a block with a lot of checksigs is okay
        lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1))
        tip(13)
        block(15, spend=out5, script=lots_of_checksigs)
        yield accepted()


        # Test that a block with too many checksigs is rejected
        out6 = get_spendable_output()
        too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50))
        block(16, spend=out6, script=too_many_checksigs)
        yield rejected()
Esempio n. 4
0
class sQuorum_FakeStakeTest(BitcoinTestFramework):
    def set_test_params(self):
        ''' Setup test environment
        :param:
        :return:
        '''
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.extra_args = [['-staking=1', '-debug=net']] * self.num_nodes

    def setup_network(self):
        ''' Can't rely on syncing all the nodes when staking=1
        :param:
        :return:
        '''
        self.setup_nodes()
        for i in range(self.num_nodes - 1):
            for j in range(i + 1, self.num_nodes):
                connect_nodes_bi(self.nodes, i, j)

    def init_test(self):
        ''' Initializes test parameters
        :param:
        :return:
        '''
        title = "*** Starting %s ***" % self.__class__.__name__
        underline = "-" * len(title)
        self.log.info("\n\n%s\n%s\n%s\n", title, underline, self.description)
        # Global Test parameters (override in run_test)
        self.DEFAULT_FEE = 0.1
        # Spam blocks to send in current test
        self.NUM_BLOCKS = 30

        # Setup the p2p connections and start up the network thread.
        self.test_nodes = []
        for i in range(self.num_nodes):
            self.test_nodes.append(TestNode())
            self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i))

        network_thread_start()  # Start up network handling in another thread
        self.node = self.nodes[0]

        # Let the test nodes get in sync
        for i in range(self.num_nodes):
            self.test_nodes[i].wait_for_verack()

    def run_test(self):
        ''' Performs the attack of this test - run init_test first.
        :param:
        :return:
        '''
        self.description = ""
        self.init_test()
        return

    def create_spam_block(self,
                          hashPrevBlock,
                          stakingPrevOuts,
                          height,
                          fStakeDoubleSpent=False,
                          fZPoS=False,
                          spendingPrevOuts={}):
        ''' creates a block to spam the network with
        :param   hashPrevBlock:      (hex string) hash of previous block
                 stakingPrevOuts:    ({COutPoint --> (int, int, int, str)} dictionary)
                                      map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake
                 height:             (int) block height
                 fStakeDoubleSpent:  (bool) spend the coinstake input inside the block
                 fZPoS:              (bool) stake the block with zerocoin
                 spendingPrevOuts:   ({COutPoint --> (int, int, int, str)} dictionary)
                                      map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake
        :return  block:              (CBlock) generated block
        '''

        # If not given inputs to create spam txes, use a copy of the staking inputs
        if len(spendingPrevOuts) == 0:
            spendingPrevOuts = dict(stakingPrevOuts)

        # Get current time
        current_time = int(time.time())
        nTime = current_time & 0xfffffff0

        # Create coinbase TX
        # Even if PoS blocks have empty coinbase vout, the height is required for the vin script
        coinbase = create_coinbase(height)
        coinbase.vout[0].nValue = 0
        coinbase.vout[0].scriptPubKey = b""
        coinbase.nTime = nTime
        coinbase.rehash()

        # Create Block with coinbase
        block = create_block(int(hashPrevBlock, 16), coinbase, nTime)

        # Find valid kernel hash - Create a new private key used for block signing.
        if not block.solve_stake(stakingPrevOuts):
            raise Exception("Not able to solve for any prev_outpoint")

        # Sign coinstake TX and add it to the block
        signed_stake_tx = self.sign_stake_tx(
            block, stakingPrevOuts[block.prevoutStake][0], fZPoS)
        block.vtx.append(signed_stake_tx)

        # Remove coinstake input prevout unless we want to try double spending in the same block.
        # Skip for zPoS as the spendingPrevouts are just regular UTXOs
        if not fZPoS and not fStakeDoubleSpent:
            del spendingPrevOuts[block.prevoutStake]

        # remove a random prevout from the list
        # (to randomize block creation if the same height is picked two times)
        if len(spendingPrevOuts) > 0:
            del spendingPrevOuts[choice(list(spendingPrevOuts))]

        # Create spam for the block. Sign the spendingPrevouts
        for outPoint in spendingPrevOuts:
            value_out = int(spendingPrevOuts[outPoint][0] -
                            self.DEFAULT_FEE * COIN)
            tx = create_transaction(outPoint,
                                    b"",
                                    value_out,
                                    nTime,
                                    scriptPubKey=CScript([
                                        self.block_sig_key.get_pubkey(),
                                        OP_CHECKSIG
                                    ]))
            # sign txes
            signed_tx_hex = self.node.signrawtransaction(
                bytes_to_hex_str(tx.serialize()))['hex']
            signed_tx = CTransaction()
            signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex)))
            block.vtx.append(signed_tx)

        # Get correct MerkleRoot and rehash block
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()

        # Sign block with coinstake key and return it
        block.sign_block(self.block_sig_key)
        return block

    def spend_utxo(self, utxo, address_list):
        ''' spend amount from previously unspent output to a provided address
        :param      utxo:           (JSON) returned from listunspent used as input
                    addresslist:    (string) destination address
        :return:    txhash:         (string) tx hash if successful, empty string otherwise
        '''
        try:
            inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
            out_amount = (float(utxo["amount"]) -
                          self.DEFAULT_FEE) / len(address_list)
            outputs = {}
            for address in address_list:
                outputs[address] = out_amount
            spendingTx = self.node.createrawtransaction(inputs, outputs)
            spendingTx_signed = self.node.signrawtransaction(spendingTx)
            if spendingTx_signed["complete"]:
                txhash = self.node.sendrawtransaction(spendingTx_signed["hex"])
                return txhash
            else:
                self.log.warning("Error: %s" %
                                 str(spendingTx_signed["errors"]))
                return ""
        except JSONRPCException as e:
            self.log.error("JSONRPCException: %s" % str(e))
            return ""

    def spend_utxos(self, utxo_list, address_list=[]):
        ''' spend utxos to provided list of addresses or 10 new generate ones.
        :param      utxo_list:      (JSON list) returned from listunspent used as input
                    address_list:   (string list) [optional] recipient sQuorum addresses. if not set,
                                    10 new addresses will be generated from the wallet for each tx.
        :return:    txHashes        (string list) tx hashes
        '''
        txHashes = []

        # If not given, get 10 new addresses from self.node wallet
        if address_list == []:
            for i in range(10):
                address_list.append(self.node.getnewaddress())

        for utxo in utxo_list:
            try:
                # spend current utxo to provided addresses
                txHash = self.spend_utxo(utxo, address_list)
                if txHash != "":
                    txHashes.append(txHash)
            except JSONRPCException as e:
                self.log.error("JSONRPCException: %s" % str(e))
                continue
        return txHashes

    def stake_amplification_step(self, utxo_list, address_list=[]):
        ''' spends a list of utxos providing the list of new outputs
        :param      utxo_list:     (JSON list) returned from listunspent used as input
                    address_list:  (string list) [optional] recipient sQuorum addresses.
        :return:    new_utxos:     (JSON list) list of new (valid) inputs after the spends
        '''
        self.log.info("--> Stake Amplification step started with %d UTXOs",
                      len(utxo_list))
        txHashes = self.spend_utxos(utxo_list, address_list)
        num_of_txes = len(txHashes)
        new_utxos = []
        if num_of_txes > 0:
            self.log.info(
                "Created %d transactions...Mining 2 blocks to include them..."
                % num_of_txes)
            self.node.generate(2)
            time.sleep(2)
            new_utxos = self.node.listunspent()

        self.log.info(
            "Amplification step produced %d new \"Fake Stake\" inputs:" %
            len(new_utxos))
        return new_utxos

    def stake_amplification(self, utxo_list, iterations, address_list=[]):
        ''' performs the "stake amplification" which gives higher chances at finding fake stakes
        :param      utxo_list:    (JSON list) returned from listunspent used as input
                    iterations:   (int) amount of stake amplification steps to perform
                    address_list: (string list) [optional] recipient sQuorum addresses.
        :return:    all_inputs:   (JSON list) list of all spent inputs
        '''
        self.log.info("** Stake Amplification started with %d UTXOs",
                      len(utxo_list))
        valid_inputs = utxo_list
        all_inputs = []
        for i in range(iterations):
            all_inputs = all_inputs + valid_inputs
            old_inputs = valid_inputs
            valid_inputs = self.stake_amplification_step(
                old_inputs, address_list)
        self.log.info("** Stake Amplification ended with %d \"fake\" UTXOs",
                      len(all_inputs))
        return all_inputs

    def sign_stake_tx(self, block, stake_in_value, fZPoS=False):
        ''' signs a coinstake transaction
        :param      block:           (CBlock) block with stake to sign
                    stake_in_value:  (int) staked amount
                    fZPoS:           (bool) zerocoin stake
        :return:    stake_tx_signed: (CTransaction) signed tx
        '''
        self.block_sig_key = CECKey()

        if fZPoS:
            self.log.info("Signing zPoS stake...")
            # Create raw zerocoin stake TX (signed)
            raw_stake = self.node.createrawzerocoinstake(block.prevoutStake)
            stake_tx_signed_raw_hex = raw_stake["hex"]
            # Get stake TX private key to sign the block with
            stake_pkey = raw_stake["private-key"]
            self.block_sig_key.set_compressed(True)
            self.block_sig_key.set_secretbytes(bytes.fromhex(stake_pkey))

        else:
            # Create a new private key and get the corresponding public key
            self.block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff)))
            pubkey = self.block_sig_key.get_pubkey()
            # Create the raw stake TX (unsigned)
            scriptPubKey = CScript([pubkey, OP_CHECKSIG])
            outNValue = int(stake_in_value + 2 * COIN)
            stake_tx_unsigned = CTransaction()
            stake_tx_unsigned.nTime = block.nTime
            stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake))
            stake_tx_unsigned.vin[0].nSequence = 0xffffffff
            stake_tx_unsigned.vout.append(CTxOut())
            stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey))
            # Sign the stake TX
            stake_tx_signed_raw_hex = self.node.signrawtransaction(
                bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']

        # Deserialize the signed raw tx into a CTransaction object and return it
        stake_tx_signed = CTransaction()
        stake_tx_signed.deserialize(
            BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)))
        return stake_tx_signed

    def get_prevouts(self, utxo_list, blockHeight, zpos=False):
        ''' get prevouts (map) for each utxo in a list
        :param   utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input
                            <if zpos=True>  (JSON list) mints returned from listmintedzerocoins used as input
                 blockHeight:               (int) height of the previous block
                 zpos:                      (bool) type of utxo_list
        :return: stakingPrevOuts:           ({COutPoint --> (int, int, int, str)} dictionary)
                                            map outpoints to amount, block_time, nStakeModifier, hashStake
        '''
        zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000]
        stakingPrevOuts = {}

        for utxo in utxo_list:
            if zpos:
                # get mint checkpoint
                checkpointHeight = blockHeight - 200
                checkpointBlock = self.node.getblock(
                    self.node.getblockhash(checkpointHeight), True)
                checkpoint = int(checkpointBlock['acc_checkpoint'], 16)
                # parse checksum and get checksumblock
                pos = zerocoinDenomList.index(utxo['denomination'])
                checksum = (checkpoint >>
                            (32 *
                             (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF
                checksumBlock = self.node.getchecksumblock(
                    hex(checksum), utxo['denomination'], True)
                # get block hash and block time
                txBlockhash = checksumBlock['hash']
                txBlocktime = checksumBlock['time']
            else:
                # get raw transaction for current input
                utxo_tx = self.node.getrawtransaction(utxo['txid'], 1)
                # get block hash and block time
                txBlocktime = utxo_tx['blocktime']
                txBlockhash = utxo_tx['blockhash']

            # get Stake Modifier
            stakeModifier = int(
                self.node.getblock(txBlockhash)['modifier'], 16)
            # assemble prevout object
            utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime,
                                    stakeModifier, zpos)

        return stakingPrevOuts

    def log_data_dir_size(self):
        ''' Prints the size of the '/regtest/blocks' directory.
        :param:
        :return:
        '''
        init_size = dir_size(self.node.datadir + "/regtest/blocks")
        self.log.info("Size of data dir: %s kilobytes" % str(init_size))

    def test_spam(self,
                  name,
                  staking_utxo_list,
                  fRandomHeight=False,
                  randomRange=0,
                  randomRange2=0,
                  fDoubleSpend=False,
                  fMustPass=False,
                  fZPoS=False,
                  spending_utxo_list=[]):
        ''' General method to create, send and test the spam blocks
        :param    name:               (string) chain branch (usually either "Main" or "Forked")
                  staking_utxo_list:  (string list) utxos to use for staking
                  fRandomHeight:      (bool) send blocks at random height
                  randomRange:        (int) if fRandomHeight=True, height is >= current-randomRange
                  randomRange2:       (int) if fRandomHeight=True, height is < current-randomRange2
                  fDoubleSpend:       (bool) if true, stake input is double spent in block.vtx
                  fMustPass:          (bool) if true, the blocks must be stored on disk
                  fZPoS:              (bool) stake the block with zerocoin
                  spending_utxo_list: (string list) utxos to use for spending
        :return:  err_msgs:           (string list) reports error messages from the test
                                      or an empty list if test is successful
        '''
        # Create empty error messages list
        err_msgs = []
        # Log initial datadir size
        self.log_data_dir_size()
        # Get latest block number and hash
        block_count = self.node.getblockcount()
        pastBlockHash = self.node.getblockhash(block_count)
        randomCount = block_count
        self.log.info("Current height: %d" % block_count)
        for i in range(0, self.NUM_BLOCKS):
            if i != 0:
                self.log.info("Sent %d blocks out of %d" %
                              (i, self.NUM_BLOCKS))

            # if fRandomHeight=True get a random block number (in range) and corresponding hash
            if fRandomHeight:
                randomCount = randint(block_count - randomRange,
                                      block_count - randomRange2)
                pastBlockHash = self.node.getblockhash(randomCount)

            # Get spending prevouts and staking prevouts for the height of current block
            current_block_n = randomCount + 1
            stakingPrevOuts = self.get_prevouts(staking_utxo_list,
                                                randomCount,
                                                zpos=fZPoS)
            spendingPrevOuts = self.get_prevouts(spending_utxo_list,
                                                 randomCount)

            # Create the spam block
            block = self.create_spam_block(pastBlockHash,
                                           stakingPrevOuts,
                                           current_block_n,
                                           fStakeDoubleSpent=fDoubleSpend,
                                           fZPoS=fZPoS,
                                           spendingPrevOuts=spendingPrevOuts)

            # Log time and size of the block
            block_time = time.strftime('%Y-%m-%d %H:%M:%S',
                                       time.localtime(block.nTime))
            block_size = len(block.serialize()) / 1000
            self.log.info(
                "Sending block %d [%s...] - nTime: %s - Size (kb): %.2f",
                current_block_n, block.hash[:7], block_time, block_size)

            # Try submitblock
            var = self.node.submitblock(bytes_to_hex_str(block.serialize()))
            time.sleep(1)
            if (not fMustPass and var not in [None, "bad-txns-invalid-zsqr"]
                ) or (fMustPass and var != "inconclusive"):
                self.log.error("submitblock [fMustPass=%s] result: %s" %
                               (str(fMustPass), str(var)))
                err_msgs.append("submitblock %d: %s" %
                                (current_block_n, str(var)))

            # Try sending the message block
            msg = msg_block(block)
            try:
                self.test_nodes[0].handle_connect()
                self.test_nodes[0].send_message(msg)
                time.sleep(2)
                block_ret = self.node.getblock(block.hash)
                if not fMustPass and block_ret is not None:
                    self.log.error("Error, block stored in %s chain" % name)
                    err_msgs.append("getblock %d: result not None" %
                                    current_block_n)
                if fMustPass:
                    if block_ret is None:
                        self.log.error("Error, block NOT stored in %s chain" %
                                       name)
                        err_msgs.append("getblock %d: result is None" %
                                        current_block_n)
                    else:
                        self.log.info("Good. Block IS stored on disk.")

            except JSONRPCException as e:
                exc_msg = str(e)
                if exc_msg == "Can't read block from disk (-32603)":
                    if fMustPass:
                        self.log.warning("Bad! Block was NOT stored to disk.")
                        err_msgs.append(exc_msg)
                    else:
                        self.log.info("Good. Block was not stored on disk.")
                else:
                    self.log.warning(exc_msg)
                    err_msgs.append(exc_msg)

            except Exception as e:
                exc_msg = str(e)
                self.log.error(exc_msg)
                err_msgs.append(exc_msg)

        self.log.info("Sent all %s blocks." % str(self.NUM_BLOCKS))
        # Log final datadir size
        self.log_data_dir_size()
        # Return errors list
        return err_msgs
Esempio n. 5
0
    def run_test(self):
        # Generate enough blocks to trigger certain block votes
        self.nodes[0].generate(1150)
        self.sync_all()

        logging.info("not on chain tip")
        badtip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16)
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)

        coinbase = create_coinbase(height + 1)
        cur_time = int(time.time())
        self.nodes[0].setmocktime(cur_time)
        self.nodes[1].setmocktime(cur_time)

        block = create_block(badtip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()

        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: does not build on chain tip")

        logging.info("time too far in the past")
        block = create_block(tip, coinbase, cur_time)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: time-too-old")

        logging.info("time too far in the future")
        block = create_block(tip, coinbase, cur_time + 10000000)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: time-too-new")

        logging.info("bad version 1")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 1
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")
        logging.info("bad version 2")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 2
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")
        logging.info("bad version 3")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 3
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")

        logging.info("bad coinbase height")
        tip = int(self.nodes[0].getblockhash(height), 16)
        block = create_block(tip, create_coinbase(height), cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-cb-height")

        logging.info("bad merkle root")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.hashMerkleRoot = 0x12345678
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txnmrklroot")

        logging.info("no tx")
        block = create_block(tip, None, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-blk-length")

        logging.info("good block")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)

        # ------
        self.nodes[0].validateblocktemplate(hexblk)
        block.solve()
        hexblk = ToHex(block)
        self.nodes[0].submitblock(hexblk)
        self.sync_all()

        prev_block = block
        # out_value is less than 50BTC because regtest halvings happen every 150 blocks, and is in Satoshis
        out_value = block.vtx[0].vout[0].nValue
        tx1 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 2), int(out_value / 2)])
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = cur_time + 1200

        logging.info("no coinbase")
        block = create_block(tip, None, next_time, [tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-cb-missing")

        logging.info("double coinbase")

        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()

        coinbase2 = create_coinbase(height + 1, coinbase_pubkey)
        block = create_block(tip, coinbase, next_time, [coinbase2, tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-cb-multiple")

        logging.info("premature coinbase spend")
        block = create_block(tip, coinbase, next_time, [tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-premature-spend-of-coinbase")

        self.nodes[0].generate(100)
        self.sync_all()
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = cur_time + 1200

        logging.info("inputs below outputs")
        tx6 = create_transaction(prev_block.vtx[0], 0, b'\x51', [out_value + 1000])
        block = create_block(tip, coinbase, next_time, [tx6])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-in-belowout")

        tx5 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(21000001 * COIN)])
        logging.info("money range")
        block = create_block(tip, coinbase, next_time, [tx5])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-vout-toolarge")

        logging.info("bad tx offset")
        tx_bad = create_broken_transaction(prev_block.vtx[0], 1, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx_bad])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        logging.info("bad tx offset largest number")
        tx_bad = create_broken_transaction(prev_block.vtx[0], 0xffffffff, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx_bad])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        logging.info("double tx")
        tx2 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx2, tx2])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        tx3 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 9), int(out_value / 10)])
        tx4 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 8), int(out_value / 7)])
        logging.info("double spend")
        block = create_block(tip, coinbase, next_time, [tx3, tx4])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        tx_good = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 50)] * 50)
        logging.info("good tx")
        block = create_block(tip, coinbase, next_time, [tx_good])
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        self.nodes[0].validateblocktemplate(hexblk)
        self.nodes[0].submitblock(hexblk)

        self.sync_all()

        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = next_time + 600

        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()
        coinbase3 = create_coinbase(height + 1, coinbase_pubkey)

        txl = []
        for i in range(0, 50):
            ov = block.vtx[1].vout[i].nValue
            txl.append(create_transaction(block.vtx[1], i, b'\x51', [int(ov / 50)] * 50))
        block = create_block(tip, coinbase, next_time, txl)
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        for n in self.nodes:
            n.validateblocktemplate(hexblk)

        logging.info("excessive")
        self.nodes[0].setminingmaxblock(1000)
        self.nodes[0].setexcessiveblock(1000, 12)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: excessive")
        self.nodes[0].setexcessiveblock(16 * 1000 * 1000, 12)
        self.nodes[0].setminingmaxblock(1000 * 1000)

        for it in range(0, 100):
            # if (it&1023)==0: print(it)
            h2 = hexblk
            pos = random.randint(0, len(hexblk))
            val = random.randint(0, 15)
            h3 = h2[:pos] + ('%x' % val) + h2[pos + 1:]
            try:
                self.nodes[0].validateblocktemplate(h3)
            except JSONRPCException as e:
                if not (e.error["code"] == -1 or e.error["code"] == -22):
                    print(str(e))
                # its ok we expect garbage

        self.nodes[1].submitblock(hexblk)
        self.sync_all()

        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = next_time + 600
        prev_block = block
        txl = []
        for tx in prev_block.vtx:
            for outp in range(0, len(tx.vout)):
                ov = tx.vout[outp].nValue
                txl.append(create_transaction(tx, outp, CScript([OP_CHECKSIG] * 100), [int(ov / 2)] * 2))
        block = create_block(tip, coinbase, next_time, txl)
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        for n in self.nodes:
            expectException(lambda: n.validateblocktemplate(hexblk), JSONRPCException,
                            "invalid block: bad-blk-sigops")
Esempio n. 6
0
class FullBlockTest(ComparisonTestFramework):
    ''' Can either run this test as 1 node with expected answers, or two and compare them. 
        Change the "outcome" variable from each TestInstance object to only do the comparison. '''
    def __init__(self):
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(bytes("horsebattery"))
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.block_time = int(time.time()) + 1
        self.tip = None
        self.blocks = {}

    def run_test(self):
        test = TestManager(self, self.options.tmpdir)
        test.add_all_connections(self.nodes)
        NetworkThread().start()  # Start up network handling in another thread
        test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()
        return block

    # Create a block on top of self.tip, and advance self.tip to point to the new block
    # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output,
    # and rest will go to fees.
    def next_block(self,
                   number,
                   spend=None,
                   additional_coinbase_value=0,
                   script=None):
        if self.tip == None:
            base_block_hash = self.genesis_hash
        else:
            base_block_hash = self.tip.sha256
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1  # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, self.block_time)
        if (spend != None):
            tx = CTransaction()
            tx.vin.append(
                CTxIn(COutPoint(spend.tx.sha256, spend.n), "",
                      0xffffffff))  # no signature yet
            # This copies the java comparison tool testing behavior: the first
            # txout has a garbage scriptPubKey, "to make sure we're not
            # pre-verifying too much" (?)
            tx.vout.append(
                CTxOut(0, CScript([random.randint(0, 255), height & 255])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            # Now sign it if necessary
            scriptSig = ""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                (sighash,
                 err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx,
                                      0, SIGHASH_ALL)
                scriptSig = CScript([
                    self.coinbase_key.sign(sighash) +
                    bytes(bytearray([SIGHASH_ALL]))
                ])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            block = self.add_transactions_to_block(block, [tx])
        block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        self.block_time += 1
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previous marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # add transactions to a block produced by next_block
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            old_hash = block.sha256
            self.add_transactions_to_block(block, new_transactions)
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            self.block_heights[block.sha256] = self.block_heights[old_hash]
            del self.block_heights[old_hash]
            self.blocks[block_number] = block
            return block

        # creates a new block and advances the tip to that block
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

        # Start by building a couple of blocks on top (which output is spent is
        # in parentheses):
        #     genesis -> b1 (0) -> b2 (1)
        out0 = get_spendable_output()
        block(1, spend=out0)
        save_spendable_output()
        yield accepted()

        out1 = get_spendable_output()
        b2 = block(2, spend=out1)
        yield accepted()

        # so fork like this:
        #
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1)
        #
        # Nothing should happen at this point. We saw b2 first so it takes priority.
        tip(1)
        b3 = block(3, spend=out1)
        txout_b3 = PreviousSpendableOutput(b3.vtx[1], 1)
        yield rejected()

        # Now we add another block to make the alternative chain longer.
        #
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1) -> b4 (2)
        out2 = get_spendable_output()
        block(4, spend=out2)
        yield accepted()

        # ... and back to the first chain.
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                      \-> b3 (1) -> b4 (2)
        tip(2)
        block(5, spend=out2)
        save_spendable_output()
        yield rejected()

        out3 = get_spendable_output()
        block(6, spend=out3)
        yield accepted()

        # Try to create a fork that double-spends
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                          \-> b7 (2) -> b8 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(7, spend=out2)
        yield rejected()

        out4 = get_spendable_output()
        block(8, spend=out4)
        yield rejected()

        # Try to create a block that has too much fee
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                                    \-> b9 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(6)
        block(9, spend=out4, additional_coinbase_value=1)
        yield rejected(RejectResult(16, 'bad-cb-amount'))

        # Create a fork that ends in a block with too much fee (the one that causes the reorg)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b10 (3) -> b11 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(10, spend=out3)
        yield rejected()

        block(11, spend=out4, additional_coinbase_value=1)
        yield rejected(RejectResult(16, 'bad-cb-amount'))

        # Try again, but with a valid fork first
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b14 (5)
        #                                              (b12 added last)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        b12 = block(12, spend=out3)
        save_spendable_output()
        #yield TestInstance([[b12, False]])
        b13 = block(13, spend=out4)
        # Deliver the block header for b12, and the block b13.
        # b13 should be accepted but the tip won't advance until b12 is delivered.
        yield TestInstance([[CBlockHeader(b12), None], [b13, False]])

        save_spendable_output()
        out5 = get_spendable_output()
        # b14 is invalid, but the node won't know that until it tries to connect
        # Tip still can't advance because b12 is missing
        block(14, spend=out5, additional_coinbase_value=1)
        yield rejected()

        yield TestInstance([[b12, True, b13.sha256]])  # New tip should be b13.

        # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6)
        #                      \-> b3 (1) -> b4 (2)

        # Test that a block with a lot of checksigs is okay
        lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1))
        tip(13)
        block(15, spend=out5, script=lots_of_checksigs)
        yield accepted()

        # Test that a block with too many checksigs is rejected
        out6 = get_spendable_output()
        too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50))
        block(16, spend=out6, script=too_many_checksigs)
        yield rejected(RejectResult(16, 'bad-blk-sigops'))

        # Attempt to spend a transaction created on a different fork
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1])
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        block(17, spend=txout_b3)
        yield rejected(RejectResult(16, 'bad-txns-inputs-missingorspent'))

        # Attempt to spend a transaction created on a different fork (on a fork this time)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
        #                                                                \-> b18 (b3.vtx[1]) -> b19 (6)
        #                      \-> b3 (1) -> b4 (2)
        tip(13)
        block(18, spend=txout_b3)
        yield rejected()

        block(19, spend=out6)
        yield rejected()

        # Attempt to spend a coinbase at depth too low
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        out7 = get_spendable_output()
        block(20, spend=out7)
        yield rejected(RejectResult(16,
                                    'bad-txns-premature-spend-of-coinbase'))

        # Attempt to spend a coinbase at depth too low (on a fork this time)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
        #                                                                \-> b21 (6) -> b22 (5)
        #                      \-> b3 (1) -> b4 (2)
        tip(13)
        block(21, spend=out6)
        yield rejected()

        block(22, spend=out5)
        yield rejected()

        # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6)
        #                                                                           \-> b24 (6) -> b25 (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        b23 = block(23, spend=out6)
        old_hash = b23.sha256
        tx = CTransaction()
        script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69
        script_output = CScript([chr(0) * script_length])
        tx.vout.append(CTxOut(0, script_output))
        tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 1)))
        b23 = update_block(23, [tx])
        # Make sure the math above worked out to produce a max-sized block
        assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE)
        yield accepted()

        # Make the next block one byte bigger and check that it fails
        tip(15)
        b24 = block(24, spend=out6)
        script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69
        script_output = CScript([chr(0) * (script_length + 1)])
        tx.vout = [CTxOut(0, script_output)]
        b24 = update_block(24, [tx])
        assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE + 1)
        yield rejected(RejectResult(16, 'bad-blk-length'))

        b25 = block(25, spend=out7)
        yield rejected()

        # Create blocks with a coinbase input script size out of range
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7)
        #                                                                           \-> ... (6) -> ... (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        b26 = block(26, spend=out6)
        b26.vtx[0].vin[0].scriptSig = chr(0)
        b26.vtx[0].rehash()
        # update_block causes the merkle root to get updated, even with no new
        # transactions, and updates the required state.
        b26 = update_block(26, [])
        yield rejected(RejectResult(16, 'bad-cb-length'))

        # Extend the b26 chain to make sure netgoldd isn't accepting b26
        b27 = block(27, spend=out7)
        yield rejected()

        # Now try a too-large-coinbase script
        tip(15)
        b28 = block(28, spend=out6)
        b28.vtx[0].vin[0].scriptSig = chr(0) * 101
        b28.vtx[0].rehash()
        b28 = update_block(28, [])
        yield rejected(RejectResult(16, 'bad-cb-length'))

        # Extend the b28 chain to make sure netgoldd isn't accepted b28
        b29 = block(29, spend=out7)
        # TODO: Should get a reject message back with "bad-prevblk", except
        # there's a bug that prevents this from being detected.  Just note
        # failure for now, and add the reject result later.
        yield rejected()

        # b30 has a max-sized coinbase scriptSig.
        tip(23)
        b30 = block(30)
        b30.vtx[0].vin[0].scriptSig = chr(0) * 100
        b30.vtx[0].rehash()
        b30 = update_block(30, [])
        yield accepted()
Esempio n. 7
0
class PTVRPCTests(ComparisonTestFramework):
    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.genesisactivationheight = 600
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.locking_script = CScript([self.coinbase_pubkey, OP_CHECKSIG])
        self.default_args = [
            '-debug', '-maxgenesisgracefulperiod=0',
            '-genesisactivationheight=%d' % self.genesisactivationheight
        ]
        self.extra_args = [self.default_args] * self.num_nodes

    def run_test(self):
        self.test.run()

    # Sign a transaction, using the key we know about.
    # This signs input 0 in tx, which is assumed to be spending output n in spend_tx
    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0,
                                      SIGHASH_ALL | SIGHASH_FORKID,
                                      spend_tx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript([
            self.coinbase_key.sign(sighash) +
            bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
        ])

    def check_mempool(self, rpc, should_be_in_mempool, timeout=20):
        wait_until(lambda: set(rpc.getrawmempool()) ==
                   {t.hash
                    for t in should_be_in_mempool},
                   timeout=timeout)

    # Generating transactions in order so first transaction's output will be an input for second transaction
    def get_chained_transactions(self,
                                 spend,
                                 num_of_transactions,
                                 money_to_spend=5000000000):
        txns = []
        for _ in range(0, num_of_transactions):
            money_to_spend = money_to_spend - 1000  # one satoshi to fee
            tx = create_transaction(spend.tx, spend.n, b"", money_to_spend,
                                    self.locking_script)
            self.sign_tx(tx, spend.tx, spend.n)
            tx.rehash()
            txns.append(tx)
            spend = PreviousSpendableOutput(tx, 0)
        return txns

    # Create a required number of chains with equal length.
    def get_txchains_n(self, num_of_chains, chain_length, spend):
        if num_of_chains > len(spend):
            raise Exception('Insufficient number of spendable outputs.')
        txchains = []
        for x in range(0, num_of_chains):
            txchains += self.get_chained_transactions(spend[x], chain_length)
        return txchains

    # Test an attempt to resubmit transactions (via rpc interface) which are already known
    # - received earlier via p2p interface and not processed yet
    # - use sendrawtransaction rpc interface (a single txn submit) to submit duplicates
    def run_scenario1(self,
                      conn,
                      num_of_chains,
                      chain_length,
                      spend,
                      allowhighfees=False,
                      dontcheckfee=False,
                      timeout=30):
        # Create tx chains.
        txchains = self.get_txchains_n(num_of_chains, chain_length, spend)
        # Send txns, one by one, through p2p interface.
        for tx in range(len(txchains)):
            conn.send_message(msg_tx(txchains[tx]))
        # Check if there is an expected number of transactions in the validation queues
        # - this scenario relies on ptv delayed processing
        # - ptv is required to be paused
        wait_until(lambda: conn.rpc.getblockchainactivity()["transactions"] ==
                   num_of_chains * chain_length,
                   timeout=timeout)
        # No transactions should be in the mempool.
        assert_equal(conn.rpc.getmempoolinfo()['size'], 0)
        # Resubmit txns through rpc interface
        # - there should be num_of_chains*chain_length txns detected as known transactions
        #   - due to the fact that all were already received via p2p interface
        for tx in range(len(txchains)):
            assert_raises_rpc_error(-26, "txn-already-known",
                                    conn.rpc.sendrawtransaction,
                                    ToHex(txchains[tx]), allowhighfees,
                                    dontcheckfee)
        # No transactions should be in the mempool.
        assert_equal(conn.rpc.getmempoolinfo()['size'], 0)

        return txchains

    # An extension to the scenario1.
    # - submit txns through p2p interface
    # - resubmit transactions (via rpc interface) which are already known
    # - create a new block
    # - use invalidateblock to re-org back
    # - create a new block
    # - check if txns are present in the new block
    def run_scenario2(self,
                      conn,
                      num_of_chains,
                      chain_length,
                      spend,
                      allowhighfees=False,
                      dontcheckfee=False,
                      timeout=60):
        # Create tx chains.
        txchains = self.run_scenario1(conn, num_of_chains, chain_length, spend,
                                      allowhighfees, dontcheckfee, timeout)
        # Check if txchains txns are in the mempool.
        self.check_mempool(conn.rpc, set(txchains), timeout=60)
        # Check if there is only num_of_chains * chain_length txns in the mempool.
        assert_equal(conn.rpc.getmempoolinfo()['size'], len(txchains))
        # At this stage PTV asynch queues should be empty.
        wait_until(
            lambda: conn.rpc.getblockchainactivity()["transactions"] == 0,
            timeout=timeout)
        # Generate a single block.
        mined_block1 = conn.rpc.generate(1)
        # Mempool should be empty, all txns in the block.
        assert_equal(conn.rpc.getmempoolinfo()['size'], 0)
        # Use invalidateblock to re-org back; all transactions should
        # end up unconfirmed and back in the mempool.
        conn.rpc.invalidateblock(mined_block1[0])
        # There should be exactly num_of_chains * chain_length txns in the mempool.
        assert_equal(conn.rpc.getmempoolinfo()['size'], len(txchains))
        self.check_mempool(conn.rpc, set(txchains))
        # Generate another block, they should all get mined.
        mined_block2 = conn.rpc.generate(1)
        # Mempool should be empty, all txns confirmed.
        assert_equal(conn.rpc.getmempoolinfo()['size'], 0)
        # Check if txchains txns are included in the block.
        mined_block2_details = conn.rpc.getblock(mined_block2[0])
        assert_equal(mined_block2_details['num_tx'],
                     len(txchains) + 1)  # +1 for coinbase txn.
        assert_equal(
            len(
                set(mined_block2_details['tx']).intersection(
                    t.hash for t in txchains)), len(txchains))

    def get_tests(self):
        rejected_txs = []

        def on_reject(conn, msg):
            rejected_txs.append(msg)

        # 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, coinbase_pubkey=self.coinbase_pubkey)
        self.chain.save_spendable_output()
        yield self.accepted()

        # Now we need that block to mature so we can spend the coinbase.
        # Also, move block height on beyond Genesis activation.
        test = TestInstance(sync_every_block=False)
        for i in range(600):
            block(5000 + i, coinbase_pubkey=self.coinbase_pubkey)
            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(200):
            out.append(self.chain.get_spendable_output())

        self.stop_node(0)

        # Scenario 1 (TS1).
        # This test case checks if resubmited transactions (through sendrawtransaction interface) are rejected,
        # at the early stage of processing (before txn validation is executed).
        # - 1K txs used
        # - 1K txns are sent first through the p2p interface (and not processed as ptv is paused)
        # - allowhighfees=False (default)
        # - dontcheckfee=False (default)
        #
        # Test case config
        num_of_chains = 10
        chain_length = 100
        # Node's config
        args = [
            '-txnvalidationasynchrunfreq=10000', '-limitancestorcount=100',
            '-checkmempool=0', '-persistmempool=0'
        ]
        with self.run_node_with_connections(
                'TS1: {} chains of length {}. Test duplicates resubmitted via rpc.'
                .format(num_of_chains, chain_length),
                0,
                args + self.default_args,
                number_of_connections=1) as (conn, ):
            # Run test case.
            self.run_scenario1(conn, num_of_chains, chain_length, out)

        # Scenario 2 (TS2).
        # It's an extension to TS1. Resubmit duplicates, then create a new block and check if it is a valid block.
        # - 100 txs used
        # - allowhighfees=False (default)
        # - dontcheckfee=False (default)
        #
        # Test case config
        num_of_chains = 10
        chain_length = 10
        # Node's config
        args = [
            '-txnvalidationasynchrunfreq=2000',
            '-blockcandidatevaliditytest=1',  # on regtest it's enabled by default but for clarity let's add it explicitly.
            '-checkmempool=0',
            '-persistmempool=0'
        ]
        with self.run_node_with_connections(
                'TS2: {} chains of length {}. Test duplicates and generate a new block.'
                .format(num_of_chains, chain_length),
                0,
                args + self.default_args,
                number_of_connections=1) as (conn, ):
            # Run test case.
            self.run_scenario2(conn, num_of_chains, chain_length, out)
    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 (Ion 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)
Esempio n. 9
0
class FullBlockTest(ComparisonTestFramework):
    ''' Can either run this test as 1 node with expected answers, or two and compare them. 
        Change the "outcome" variable from each TestInstance object to only do the comparison. '''
    def __init__(self):
        super().__init__()
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.block_time = int(time.time()) + 1
        self.tip = None
        self.blocks = {}

    def run_test(self):
        test = TestManager(self, self.options.tmpdir)
        test.add_all_connections(self.nodes)
        NetworkThread().start()  # Start up network handling in another thread
        test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()
        return block

    # Create a block on top of self.tip, and advance self.tip to point to the new block
    # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output,
    # and rest will go to fees.
    def next_block(self,
                   number,
                   spend=None,
                   additional_coinbase_value=0,
                   script=None):
        if self.tip == None:
            base_block_hash = self.genesis_hash
        else:
            base_block_hash = self.tip.sha256
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1  # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, self.block_time)
        if (spend != None):
            tx = CTransaction()
            tx.vin.append(
                CTxIn(COutPoint(spend.tx.sha256, spend.n), b"",
                      0xffffffff))  # no signature yet
            # This copies the java comparison tool testing behavior: the first
            # txout has a garbage scriptPubKey, "to make sure we're not
            # pre-verifying too much" (?)
            tx.vout.append(
                CTxOut(0, CScript([random.randint(0, 255), height & 255])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            # Now sign it if necessary
            scriptSig = b""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                (sighash, err) = SignatureHash(
                    spend.tx.vout[spend.n].scriptPubKey,
                    tx,
                    0,
                    SIGHASH_ALL,
                    spend.tx.vout[spend.n].nValue,
                    SAPLING_BRANCH_ID,
                )
                scriptSig = CScript([
                    self.coinbase_key.sign(sighash) +
                    bytes(bytearray([SIGHASH_ALL]))
                ])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            block = self.add_transactions_to_block(block, [tx])
        block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        self.block_time += 1
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previous marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected():
            return TestInstance([[self.tip, False]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # creates a new block and advances the tip to that block
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

        # Start by building a couple of blocks on top (which output is spent is in parentheses):
        #     genesis -> b1 (0) -> b2 (1)
        out0 = get_spendable_output()
        block(1, spend=out0)
        save_spendable_output()
        yield accepted()

        out1 = get_spendable_output()
        block(2, spend=out1)
        # Inv again, then deliver twice (shouldn't break anything).
        yield accepted()

        # so fork like this:
        #
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1)
        #
        # Nothing should happen at this point. We saw b2 first so it takes priority.
        tip(1)
        block(3, spend=out1)
        # Deliver twice (should still not break anything)
        yield rejected()

        # Now we add another block to make the alternative chain longer.
        #
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1) -> b4 (2)
        out2 = get_spendable_output()
        block(4, spend=out2)
        yield accepted()

        # ... and back to the first chain.
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                      \-> b3 (1) -> b4 (2)
        tip(2)
        block(5, spend=out2)
        save_spendable_output()
        yield rejected()

        out3 = get_spendable_output()
        block(6, spend=out3)
        yield accepted()

        # Try to create a fork that double-spends
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                          \-> b7 (2) -> b8 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(7, spend=out2)
        yield rejected()

        out4 = get_spendable_output()
        block(8, spend=out4)
        yield rejected()

        # Try to create a block that has too much fee
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                                    \-> b9 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(6)
        block(9, spend=out4, additional_coinbase_value=1)
        yield rejected()

        # Create a fork that ends in a block with too much fee (the one that causes the reorg)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b10 (3) -> b11 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(10, spend=out3)
        yield rejected()

        block(11, spend=out4, additional_coinbase_value=1)
        yield rejected()

        # Try again, but with a valid fork first
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b14 (5)
        #                                              (b12 added last)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        b12 = block(12, spend=out3)
        save_spendable_output()
        #yield TestInstance([[b12, False]])
        b13 = block(13, spend=out4)
        # Deliver the block header for b12, and the block b13.
        # b13 should be accepted but the tip won't advance until b12 is delivered.
        yield TestInstance([[CBlockHeader(b12), None], [b13, False]])

        save_spendable_output()
        out5 = get_spendable_output()
        # b14 is invalid, but the node won't know that until it tries to connect
        # Tip still can't advance because b12 is missing
        block(14, spend=out5, additional_coinbase_value=1)
        yield rejected()

        yield TestInstance([[b12, True, b13.sha256]])  # New tip should be b13.

        # Test that a block with a lot of checksigs is okay
        lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50 - 1))
        tip(13)
        block(15, spend=out5, script=lots_of_checksigs)
        yield accepted()

        # Test that a block with too many checksigs is rejected
        out6 = get_spendable_output()
        too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50))
        block(16, spend=out6, script=too_many_checksigs)
        yield rejected()
Esempio n. 10
0
class BigBlockTests(BitcoinTestFramework):

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 3

        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.locking_script = CScript([self.coinbase_pubkey, OP_CHECKSIG])

        self.nodeArgs = [ '-genesisactivationheight=1',
                          '-blockmaxsize={}'.format(ONE_GIGABYTE * 5),
                          '-maxmempool=10000',
                          '-maxnonstdtxvalidationduration=100000',
                          '-maxtxnvalidatorasynctasksrunduration=100001',
                          '-blockdownloadtimeoutbasepercent=300' ]

        self.extra_args = [self.nodeArgs] * self.num_nodes

    def setup_nodes(self):
        self.add_nodes(self.num_nodes, self.extra_args, timewait=int(1200 * self.options.timeoutfactor))
        self.start_nodes()

    # Create and send block with coinbase
    def make_coinbase(self, conn):
        tip = conn.rpc.getblock(conn.rpc.getbestblockhash())

        coinbase_tx = create_coinbase(tip["height"] + 1, self.coinbase_pubkey)
        coinbase_tx.rehash()

        block = create_block(int(tip["hash"], 16), coinbase_tx, tip["time"] + 1)
        block.solve()

        conn.send_message(msg_block(block))
        wait_until(lambda: conn.rpc.getbestblockhash() == block.hash, timeout=int(30 * self.options.timeoutfactor))

        return coinbase_tx

    def sign_tx(self, tx, spendtx, n):
        scriptPubKey = bytearray(spendtx.vout[n].scriptPubKey)
        sighash = SignatureHashForkId(spendtx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spendtx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])

    # Generate some large transactions and put them in the mempool
    def create_and_send_transactions(self, conn, spendtx, num_of_transactions, money_to_spend=5000000000):
        for i in range(0, num_of_transactions):
            money_to_spend = money_to_spend - 500000000  # Large fee required for big txns
            tx = create_tx(spendtx, 0, money_to_spend, script=CScript([OP_DROP, OP_TRUE]))
            tx.vout.append(CTxOut(0, CScript([OP_FALSE, OP_RETURN, bytearray([0x00] * (ONE_MEGABYTE * 880))])))
            self.sign_tx(tx, spendtx, 0)
            tx.rehash()

            conn.send_message(msg_tx(tx))
            wait_until(lambda: tx.hash in conn.rpc.getrawmempool(), timeout=int(360 * self.options.timeoutfactor))
            logger.info("Submitted txn {} of {}".format(i+1, num_of_transactions))
            assert conn.rpc.getmempoolinfo()['size'] == i+1

            spendtx = tx

    def run_test(self):
        # Get out of IBD
        self.nodes[0].generate(1)
        self.sync_all()

        # Stop node so we can restart it with our connections
        self.stop_node(0)

        # Disconnect node1 and node2 for now
        disconnect_nodes_bi(self.nodes, 1, 2)

        connArgs = [ { "versionNum":MY_VERSION }, { "versionNum":70015 } ]
        with self.run_node_with_connections("Test old and new protocol versions", 0, self.nodeArgs, number_of_connections=2,
                                            connArgs=connArgs, cb_class=MyConnCB) as (newVerConn,oldVerConn):
            assert newVerConn.connected
            assert oldVerConn.connected

            # Generate small block, verify we get it over both connections
            self.nodes[0].generate(1)
            wait_until(lambda: newVerConn.cb.block_count == 1, timeout=int(30 * self.options.timeoutfactor))
            wait_until(lambda: oldVerConn.cb.block_count == 1, timeout=int(30 * self.options.timeoutfactor))

            # Get us a spendable output
            coinbase_tx = self.make_coinbase(newVerConn)
            self.nodes[0].generate(100)

            # Put some large txns into the nodes mempool until it exceeds 4GB in size
            self.create_and_send_transactions(newVerConn, coinbase_tx, 5)

            # Reconnect node0 and node2 and sync their blocks. Node2 will end up receiving the
            # large block via compact blocks
            connect_nodes(self.nodes, 0, 2)
            sync_blocks(itemgetter(0,2)(self.nodes))

            # Mine a >4GB block, verify we only get it over the new connection
            old_block_count = newVerConn.cb.block_count
            logger.info("Mining a big block")
            self.nodes[0].generate(1)
            assert(self.nodes[0].getmempoolinfo()['size'] == 0)
            logger.info("Waiting for block to arrive at test")
            wait_until(lambda: newVerConn.cb.block_count == old_block_count+1, timeout=int(1200 * self.options.timeoutfactor))

            # Look for log message saying we won't send to old peer
            wait_until(lambda: check_for_log_msg(self, "cannot be sent because it exceeds max P2P message limit", "/node0"))

            # Verify node2 gets the big block via a (not very) compact block
            wait_until(lambda: self.nodes[0].getbestblockhash() == self.nodes[2].getbestblockhash())
            peerinfo = self.nodes[2].getpeerinfo()
            assert(peerinfo[0]['bytesrecv_per_msg']['cmpctblock'] > 0)
            assert(peerinfo[0]['bytesrecv_per_msg']['blocktxn'] > 0)

            # Reconnect node0 to node1
            logger.info("Syncing bitcoind nodes to big block")
            connect_nodes(self.nodes, 0, 1)
            self.sync_all(timeout=int(1200 * self.options.timeoutfactor))

            # Verify node1 also got the big block
            assert(self.nodes[0].getbestblockhash() == self.nodes[1].getbestblockhash())
Esempio n. 11
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 800 deep so the coinbase output is spendable
        for i in range(800):
            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(24 * 100000000, CScript([OP_TRUE])))
        tx.calc_sha256()

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

        # Bury the assumed valid block 2100*8 deep
        for i in range(16800):
            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

        print("prepared Block tip=%s height=%d" % (self.tip, height))

        # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
        self.start_node(1, extra_args=["-assumevalid=" + hex(block802.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(block802.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 nodes
        node0.send_header_for_blocks(self.blocks[0:2000])
        # Send blocks to node0. Block 802 will be rejected.
        self.send_blocks_until_disconnected(node0)
        self.assert_blockchain_height(self.nodes[0], 801)

        # Send blocks to node2. Block 802 will be rejected.
        node2.send_header_for_blocks(self.blocks[0:900])
        self.send_blocks_until_disconnected(node2)
        self.assert_blockchain_height(self.nodes[2], 801)

        # Send all blocks to node1. All blocks will be accepted.
        for i in range(8):
            node1.send_header_for_blocks(self.blocks[i * 2000:(i + 1) * 2000])
            for j in range(2000):
                node1.send_message(msg_block(self.blocks[i * 2000 + j]))
            node1.sync_with_ping(240)

        node1.send_header_for_blocks(self.blocks[16000:17602])
        for i in range(162):
            node1.send_message(msg_block(self.blocks[i]))

        # Syncing 17602 blocks can take a while on slow systems. Give it plenty of time to sync.
        node1.sync_with_ping(240 * 8)
        assert_equal(
            self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'],
            17602)
Esempio n. 12
0
    def make_transactions(self,
                          txtype,
                          num_txns,
                          stxn_vin_size,
                          create_double_spends=False):
        key = CECKey()
        key.set_secretbytes(b"horsebattery")
        key.set_compressed(True)
        # Each coin being spent will always result in at least 14 expensive ECDSA checks.
        # 0x7f03 33 OP_NUM2BIN creates a valid non-zero compressed pubkey.
        redeem_script = CScript([
            OP_1,
            key.get_pubkey(), 0x7f03, 33, OP_NUM2BIN, OP_DUP, OP_2DUP, OP_2DUP,
            OP_2DUP, OP_3DUP, OP_3DUP, OP_15, OP_CHECKMULTISIG
        ])

        # Calculate how many found txns are needed to create a required spend money txns (num_txns)
        # - a fund txns are of type 1 - N (N=vouts_size_per_fund_txn)
        # - a spend money txns are of type M-1 (M inputs & 1 output)
        def estimate_fund_txns_number(num_txns, vouts_size_per_fund_txn):
            fund_txns_num = 1
            if num_txns >= vouts_size_per_fund_txn:
                if num_txns % vouts_size_per_fund_txn == 0:
                    fund_txns_num = num_txns // vouts_size_per_fund_txn
                else:
                    fund_txns_num = num_txns // vouts_size_per_fund_txn + 1
            return fund_txns_num * vouts_size_per_fund_txn

        # Create funding transactions that will provide funds for other transcations
        def make_fund_txn(node, out_value, num_vout_txns):
            # Create fund txn
            ftx = CTransaction()
            for i in range(num_vout_txns):
                ftx.vout.append(
                    CTxOut(
                        out_value,
                        CScript([OP_HASH160,
                                 hash160(redeem_script), OP_EQUAL])))
            # fund the transcation:
            ftxHex = node.fundrawtransaction(
                ToHex(ftx), {'changePosition': len(ftx.vout)})['hex']
            ftxHex = node.signrawtransaction(ftxHex)['hex']
            ftx = FromHex(CTransaction(), ftxHex)
            ftx.rehash()
            return ftx, ftxHex

        # Create a spend txn
        def make_spend_txn(txtype, fund_txn_hash, fund_txn_num_vouts,
                           out_value):
            # Create txn
            spend_tx = CTransaction()
            for idx in range(fund_txn_num_vouts):
                spend_tx.vin.append(CTxIn(COutPoint(fund_txn_hash, idx), b''))
                sighash = SignatureHashForkId(
                    redeem_script, spend_tx, idx,
                    SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE,
                    out_value)
                sig = key.sign(sighash) + bytes(
                    bytearray([
                        SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE
                    ]))
                spend_tx.vin[idx].scriptSig = CScript(
                    [OP_0, sig, redeem_script])
                # Standard transaction
                if TxType.standard == txtype:
                    spend_tx.vout.append(
                        CTxOut(out_value - 1000, CScript([OP_RETURN])))
                # Non-standard transaction
                elif TxType.nonstandard == txtype:
                    spend_tx.vout.append(
                        CTxOut(out_value - 1000, CScript([OP_TRUE])))
                spend_tx.rehash()
            return spend_tx

        #
        # Generate some blocks to have enough spendable coins
        #
        node = self.nodes[0]
        node.generate(101)

        #
        # Estimate a number of required fund txns
        #
        out_value = 2000
        # Number of outputs in each fund txn
        fund_txn_num_vouts = stxn_vin_size
        fund_txns_num = estimate_fund_txns_number(num_txns, fund_txn_num_vouts)

        #
        # Create and send fund txns to the mempool
        #
        fund_txns = []
        for i in range(fund_txns_num):
            ftx, ftxHex = make_fund_txn(node, out_value, fund_txn_num_vouts)
            node.sendrawtransaction(ftxHex)
            fund_txns.append(ftx)
        # Ensure that mempool is empty to avoid 'too-long-mempool-chain' errors in next test
        node.generate(1)

        #
        # Create spend transactions.
        #
        txtype_to_create = txtype
        spend_txs = []
        for i in range(len(fund_txns)):
            # If standard and non-standard txns are required then create equal (in size) sets.
            if TxType.std_and_nonstd == txtype:
                if i % 2:
                    txtype_to_create = TxType.standard
                else:
                    txtype_to_create = TxType.nonstandard
            # Create a spend money txn with fund_txn_num_vouts number of inputs.
            spend_tx = make_spend_txn(txtype_to_create, fund_txns[i].sha256,
                                      fund_txn_num_vouts, out_value)
            # Create double spend txns if required
            if create_double_spends and len(spend_txs) < num_txns // 2:
                # The first half of the array are double spend txns
                spend_tx.vin.append(
                    CTxIn(
                        COutPoint(fund_txns[len(fund_txns) - i - 1].sha256, 0),
                        b''))
                sighash = SignatureHashForkId(
                    redeem_script, spend_tx, stxn_vin_size,
                    SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE,
                    out_value)
                sig = key.sign(sighash) + bytes(
                    bytearray([
                        SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE
                    ]))
                spend_tx.vin[stxn_vin_size].scriptSig = CScript(
                    [OP_0, sig, redeem_script])
                spend_tx.rehash()
            spend_txs.append(spend_tx)
        return spend_txs
Esempio n. 13
0
class P2SH(ComparisonTestFramework):
    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.genesisactivationheight = 203
        # Build the redeem script, hash it, use hash to create the p2sh script
        self.redeem_script = CScript(
            [self.coinbase_pubkey, OP_2DUP, OP_CHECKSIGVERIFY, OP_CHECKSIG])
        self.p2sh_script = CScript(
            [OP_HASH160, hash160(self.redeem_script), OP_EQUAL])

    def setup_network(self):
        self.extra_args = [[
            '-norelaypriority', '-acceptnonstdtxn=0', '-acceptnonstdoutputs=0',
            '-banscore=1000000',
            f'-genesisactivationheight={self.genesisactivationheight}',
            '-maxgenesisgracefulperiod=1'
        ]]
        self.add_nodes(self.num_nodes, self.extra_args)
        self.start_nodes()
        self.init_network()

    def run_test(self):
        self.test.run()

    # Creates a new transaction using a p2sh transaction as input
    def spend_p2sh_tx(self,
                      p2sh_tx_to_spend,
                      output_script=SPEND_OUTPUT,
                      privateKey=None):
        privateKey = privateKey or self.coinbase_key
        # Create the transaction
        spent_p2sh_tx = CTransaction()
        spent_p2sh_tx.vin.append(
            CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b''))
        spent_p2sh_tx.vout.append(
            CTxOut(p2sh_tx_to_spend.vout[0].nValue - 100, output_script))
        # Sign the transaction using the redeem script
        sighash = SignatureHashForkId(self.redeem_script, spent_p2sh_tx, 0,
                                      SIGHASH_ALL | SIGHASH_FORKID,
                                      p2sh_tx_to_spend.vout[0].nValue)
        sig = privateKey.sign(sighash) + bytes(
            bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
        spent_p2sh_tx.vin[0].scriptSig = CScript([sig, self.redeem_script])
        spent_p2sh_tx.rehash()
        return spent_p2sh_tx

    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 and mature coinbase txs
        test = TestInstance(sync_every_block=False)
        for i in range(200):
            block(i, coinbase_pubkey=self.coinbase_pubkey)
            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
        coinbase_utxos = [self.chain.get_spendable_output() for _ in range(50)]

        # Create a p2sh transactions that spends coinbase tx
        def new_P2SH_tx():
            output = coinbase_utxos.pop(0)
            return create_and_sign_transaction(spend_tx=output.tx,
                                               n=output.n,
                                               value=output.tx.vout[0].nValue -
                                               100,
                                               coinbase_key=self.coinbase_key,
                                               script=self.p2sh_script)

        # Add the transactions to the block
        block(200)
        p2sh_txs = [new_P2SH_tx() for _ in range(4)]
        self.chain.update_block(200, p2sh_txs)
        yield self.accepted()

        coinbase_to_p2sh_tx = new_P2SH_tx()

        # rpc tests
        node.signrawtransaction(
            ToHex(coinbase_to_p2sh_tx
                  ))  # check if we can sign this tx (sign is unused)
        coinbase_to_p2sh_tx_id = node.sendrawtransaction(
            ToHex(coinbase_to_p2sh_tx))  # sending using rpc

        # Create new private key that will fail with the redeem script
        wrongPrivateKey = CECKey()
        wrongPrivateKey.set_secretbytes(b"wrongkeysecret")
        wrongkey_txn = self.spend_p2sh_tx(p2sh_txs[0],
                                          privateKey=wrongPrivateKey)
        # A transaction with this output script can't get into the mempool
        assert_raises_rpc_error(-26, "mandatory-script-verify-flag-failed",
                                node.sendrawtransaction, ToHex(wrongkey_txn))

        # A transaction with this output script can get into the mempool
        correctkey_tx = self.spend_p2sh_tx(p2sh_txs[1])
        correctkey_tx_id = node.sendrawtransaction(ToHex(correctkey_tx))
        assert_equal(set(node.getrawmempool()),
                     {correctkey_tx_id, coinbase_to_p2sh_tx_id})

        block(201)
        self.chain.update_block(201, [correctkey_tx, coinbase_to_p2sh_tx])
        yield self.accepted()

        assert node.getblockcount() == self.genesisactivationheight - 1
        # This block would be at genesis height
        # transactions with P2SH output will be rejected
        block(202)
        p2sh_tx_after_genesis = new_P2SH_tx()
        self.chain.update_block(202, [p2sh_tx_after_genesis])
        yield self.rejected(RejectResult(16, b'bad-txns-vout-p2sh'))

        self.chain.set_tip(201)
        block(203, coinbase_pubkey=self.coinbase_pubkey)
        yield self.accepted()

        # we are at gensis height
        assert node.getblockcount() == self.genesisactivationheight

        # P2SH transactions are rejected and cant enter the mempool
        assert_raises_rpc_error(-26, "bad-txns-vout-p2sh",
                                node.sendrawtransaction, ToHex(new_P2SH_tx()))

        # Create new private key that would fail with the old redeem script, the same behavior as before genesis
        wrongPrivateKey = CECKey()
        wrongPrivateKey.set_secretbytes(b"wrongkeysecret")
        wrongkey_txn = self.spend_p2sh_tx(p2sh_txs[2],
                                          privateKey=wrongPrivateKey)
        # A transaction with this output script can't get into the mempool
        assert_raises_rpc_error(-26, "mandatory-script-verify-flag-failed",
                                node.sendrawtransaction, ToHex(wrongkey_txn))

        # We can spend old P2SH transactions
        correctkey_tx = self.spend_p2sh_tx(p2sh_txs[3])
        sign_result = node.signrawtransaction(ToHex(correctkey_tx))
        assert sign_result['complete'], "Should be able to sign"
        correctkey_tx_id = node.sendrawtransaction(ToHex(correctkey_tx))
        assert_equal(set(node.getrawmempool()), {correctkey_tx_id})

        tx1_raw = node.getrawtransaction(p2sh_txs[0].hash, True)
        assert tx1_raw["vout"][0]["scriptPubKey"]["type"] == "scripthash"
Esempio n. 14
0
    def run_test(self):
        node, = self.nodes

        self.bootstrap_p2p()

        tip = self.getbestblock(node)

        self.log.info("Create some blocks with OP_1 coinbase for spending.")
        blocks = []
        for _ in range(10):
            tip = self.build_block(tip)
            blocks.append(tip)
        node.p2p.send_blocks_and_test(blocks, node, success=True)
        spendable_outputs = [block.vtx[0] for block in blocks]

        self.log.info("Mature the blocks and get out of IBD.")
        node.generate(100)

        tip = self.getbestblock(node)

        self.log.info("Setting up spends to test and mining the fundings.")
        fundings = []

        # Generate a key pair
        privkeybytes = b"Schnorr!" * 4
        private_key = CECKey()
        private_key.set_secretbytes(privkeybytes)
        # get uncompressed public key serialization
        public_key = private_key.get_pubkey()

        def create_fund_and_spend_tx(multi=False, sig='schnorr'):
            spendfrom = spendable_outputs.pop()

            if multi:
                script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG])
            else:
                script = CScript([public_key, OP_CHECKSIG])

            value = spendfrom.vout[0].nValue

            # Fund transaction
            txfund = create_transaction(spendfrom, 0, b'', value, script)
            txfund.rehash()
            fundings.append(txfund)

            # Spend transaction
            txspend = CTransaction()
            txspend.vout.append(CTxOut(value - 1000, CScript([OP_TRUE])))
            txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b''))

            # Sign the transaction
            sighashtype = SIGHASH_ALL | SIGHASH_FORKID
            hashbyte = bytes([sighashtype & 0xff])
            sighash = SignatureHashForkId(script, txspend, 0, sighashtype,
                                          value)
            if sig == 'schnorr':
                txsig = schnorr.sign(privkeybytes, sighash) + hashbyte
            elif sig == 'ecdsa':
                txsig = private_key.sign(sighash) + hashbyte
            elif isinstance(sig, bytes):
                txsig = sig + hashbyte
            if multi:
                txspend.vin[0].scriptSig = CScript([b'', txsig])
            else:
                txspend.vin[0].scriptSig = CScript([txsig])
            txspend.rehash()

            return txspend

        schnorrchecksigtx = create_fund_and_spend_tx()
        schnorrmultisigtx = create_fund_and_spend_tx(multi=True)
        ecdsachecksigtx = create_fund_and_spend_tx(sig='ecdsa')
        sig64checksigtx = create_fund_and_spend_tx(sig=sig64)
        sig64multisigtx = create_fund_and_spend_tx(multi=True, sig=sig64)

        tip = self.build_block(tip, fundings)
        node.p2p.send_blocks_and_test([tip], node)

        self.log.info("Typical ECDSA and Schnorr CHECKSIG are valid.")
        node.p2p.send_txs_and_test([schnorrchecksigtx, ecdsachecksigtx], node)
        # They get mined as usual.
        node.generate(1)
        tip = self.getbestblock(node)
        # Make sure they are in the block, and mempool is now empty.
        txhashes = set([schnorrchecksigtx.hash, ecdsachecksigtx.hash])
        assert txhashes.issubset(tx.rehash() for tx in tip.vtx)
        assert not node.getrawmempool()

        self.log.info("Schnorr in multisig is rejected with mandatory error.")
        assert_raises_rpc_error(-26, SCHNORR_MULTISIG_ERROR,
                                node.sendrawtransaction,
                                ToHex(schnorrmultisigtx))
        # And it is banworthy.
        self.check_for_ban_on_rejected_tx(schnorrmultisigtx,
                                          SCHNORR_MULTISIG_ERROR)
        # And it can't be mined
        self.check_for_ban_on_rejected_block(
            self.build_block(tip, [schnorrmultisigtx]), BADINPUTS_ERROR)

        self.log.info("Bad 64-byte sig is rejected with mandatory error.")
        # In CHECKSIG it's invalid Schnorr and hence NULLFAIL.
        assert_raises_rpc_error(-26, NULLFAIL_ERROR, node.sendrawtransaction,
                                ToHex(sig64checksigtx))
        # In CHECKMULTISIG it's invalid length and hence BAD_LENGTH.
        assert_raises_rpc_error(-26, SCHNORR_MULTISIG_ERROR,
                                node.sendrawtransaction,
                                ToHex(sig64multisigtx))
        # Sending these transactions is banworthy.
        self.check_for_ban_on_rejected_tx(sig64checksigtx, NULLFAIL_ERROR)
        self.check_for_ban_on_rejected_tx(sig64multisigtx,
                                          SCHNORR_MULTISIG_ERROR)
        # And they can't be mined either...
        self.check_for_ban_on_rejected_block(
            self.build_block(tip, [sig64checksigtx]), BADINPUTS_ERROR)
        self.check_for_ban_on_rejected_block(
            self.build_block(tip, [sig64multisigtx]), BADINPUTS_ERROR)
Esempio n. 15
0
    def run_test(self):
        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())

        # 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

        self.nodes[0].disconnect_p2ps()

        # 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)])
        self.start_node(2, 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())

        # send header lists to all three nodes
        p2p0.send_header_for_blocks(self.blocks[0:2000])
        p2p0.send_header_for_blocks(self.blocks[2000:])
        p2p1.send_header_for_blocks(self.blocks[0:2000])
        p2p1.send_header_for_blocks(self.blocks[2000:])
        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 all blocks to node1. All blocks will be accepted.
        for i in range(2202):
            p2p1.send_message(msg_block(self.blocks[i]))
        # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
        p2p1.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(p2p2)
        self.assert_blockchain_height(self.nodes[2], 101)
Esempio n. 16
0
    def get_tests(self):
        node = self.nodes[0]
        self.chain.set_genesis_hash(int(node.getbestblockhash(), 16))

        # shorthand for functions
        block = self.chain.next_block

        # 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(99):
            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())

        # Let's build some blocks and test them.
        for i in range(16):
            n = i + 1
            block(n, spend=out[i], block_size=n * ONE_MEGABYTE // 2)
            yield self.accepted()

        # block of maximal size
        block(17, spend=out[16], block_size=self.excessive_block_size)
        yield self.accepted()

        # Oversized blocks will cause us to be disconnected
        assert (not self.test.test_nodes[0].closed)
        block(18, spend=out[17], block_size=self.excessive_block_size + 1)
        self.test.connections[0].send_message(msg_block((self.chain.tip)))
        self.test.wait_for_disconnections()
        assert (self.test.test_nodes[0].closed)

        # Rewind bad block and remake connection to node
        self.chain.set_tip(17)
        self.restart_network()
        self.test.wait_for_verack()

        # Accept many sigops
        lots_of_checksigs = CScript([OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB)
        block(19,
              spend=out[17],
              script=lots_of_checksigs,
              block_size=ONE_MEGABYTE)
        yield self.accepted()

        block(20,
              spend=out[18],
              script=lots_of_checksigs,
              block_size=ONE_MEGABYTE,
              extra_sigops=1)
        yield self.rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        self.chain.set_tip(19)

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(21,
              spend=out[18],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=ONE_MEGABYTE + 1)
        yield self.accepted()

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(22,
              spend=out[19],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE)
        yield self.accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(23,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=ONE_MEGABYTE + 1)
        yield self.rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        self.chain.set_tip(22)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(24,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE)
        yield self.rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        self.chain.set_tip(22)

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(25,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE + 1)
        yield self.accepted()

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(26,
              spend=out[21],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=3 * ONE_MEGABYTE)
        yield self.accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(27,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE + 1)
        yield self.rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        self.chain.set_tip(26)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(28,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=3 * ONE_MEGABYTE)
        yield self.rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        self.chain.set_tip(26)

        # Too many sigops in one txn
        too_many_tx_checksigs = CScript([OP_CHECKSIG] *
                                        (MAX_BLOCK_SIGOPS_PER_MB + 1))
        block(29,
              spend=out[22],
              script=too_many_tx_checksigs,
              block_size=ONE_MEGABYTE + 1)
        yield self.rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        self.chain.set_tip(26)

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"fatstacks")
        public_key = private_key.get_pubkey()

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([public_key] +
                                [OP_2DUP, OP_CHECKSIGVERIFY] * 5 +
                                [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Create a p2sh transaction
        p2sh_tx = self.chain.create_tx_with_script(out[22], 1, p2sh_script)

        # Add the transaction to the block
        block(30)
        self.chain.update_block(30, [p2sh_tx])
        yield self.accepted()

        # Creates a new transaction using the p2sh transaction included in the
        # last block
        def spend_p2sh_tx(output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0,
                                          SIGHASH_ALL | SIGHASH_FORKID,
                                          p2sh_tx.vout[0].nValue)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # Sigops p2sh limit
        p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh txn
        too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1))
        block(31, spend=out[23], block_size=ONE_MEGABYTE + 1)
        self.chain.update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)])
        yield self.rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        self.chain.set_tip(30)

        # Max sigops in one p2sh txn
        max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit))
        block(32, spend=out[23], block_size=ONE_MEGABYTE + 1)
        self.chain.update_block(32, [spend_p2sh_tx(max_p2sh_sigops)])
        yield self.accepted()

        # Submit a very large block via RPC
        large_block = block(33,
                            spend=out[24],
                            block_size=self.excessive_block_size)
        node.submitblock(ToHex(large_block))
    def run_test(self):
        node = self.nodes[0]
        node.add_p2p_connection(P2PDataStore())
        node.setmocktime(REPLAY_PROTECTION_START_TIME)

        self.genesis_hash = int(node.getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            block.vtx.extend(new_transactions)
            old_sha256 = block.sha256
            make_conform_to_ctor(block)
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        node.p2p.send_blocks_and_test([self.tip], node)

        # Now we need that block to mature so we can spend the coinbase.
        maturity_blocks = []
        for i in range(99):
            block(5000 + i)
            maturity_blocks.append(self.tip)
            save_spendable_output()
        node.p2p.send_blocks_and_test(maturity_blocks, node)

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

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"replayprotection")
        public_key = private_key.get_pubkey()

        # This is a little handier to use than the version in blocktools.py
        def create_fund_and_spend_tx(spend, forkvalue=0):
            # Fund transaction
            script = CScript([public_key, OP_CHECKSIG])
            txfund = create_transaction(spend.tx, spend.n, b'',
                                        50 * COIN - 1000, script)
            txfund.rehash()

            # Spend transaction
            txspend = CTransaction()
            txspend.vout.append(CTxOut(50 * COIN - 2000, CScript([OP_TRUE])))
            txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b''))

            # Sign the transaction
            sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID
            sighash = SignatureHashForkId(script, txspend, 0, sighashtype,
                                          50 * COIN - 1000)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            txspend.vin[0].scriptSig = CScript([sig])
            txspend.rehash()

            return [txfund, txspend]

        def send_transaction_to_mempool(tx):
            tx_id = node.sendrawtransaction(ToHex(tx))
            assert tx_id in set(node.getrawmempool())
            return tx_id

        # Before the fork, no replay protection required to get in the mempool.
        txns = create_fund_and_spend_tx(out[0])
        send_transaction_to_mempool(txns[0])
        send_transaction_to_mempool(txns[1])

        # And txns get mined in a block properly.
        block(1)
        update_block(1, txns)
        node.p2p.send_blocks_and_test([self.tip], node)

        # Replay protected transactions are rejected.
        replay_txns = create_fund_and_spend_tx(out[1], 0xffdead)
        send_transaction_to_mempool(replay_txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        # And block containing them are rejected as well.
        block(2)
        update_block(2, replay_txns)
        node.p2p.send_blocks_and_test([self.tip],
                                      node,
                                      success=False,
                                      reject_reason='blk-bad-inputs')

        # Rewind bad block
        tip(1)

        # Create a block that would activate the replay protection.
        bfork = block(5555)
        bfork.nTime = REPLAY_PROTECTION_START_TIME - 1
        update_block(5555, [])
        node.p2p.send_blocks_and_test([self.tip], node)

        activation_blocks = []
        for i in range(5):
            block(5100 + i)
            activation_blocks.append(self.tip)
        node.p2p.send_blocks_and_test(activation_blocks, node)

        # Check we are just before the activation time
        assert_equal(
            node.getblockheader(node.getbestblockhash())['mediantime'],
            REPLAY_PROTECTION_START_TIME - 1)

        # We are just before the fork, replay protected txns still are rejected
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        block(3)
        update_block(3, replay_txns)
        node.p2p.send_blocks_and_test([self.tip],
                                      node,
                                      success=False,
                                      reject_reason='blk-bad-inputs')

        # Rewind bad block
        tip(5104)

        # Send some non replay protected txns in the mempool to check
        # they get cleaned at activation.
        txns = create_fund_and_spend_tx(out[2])
        send_transaction_to_mempool(txns[0])
        tx_id = send_transaction_to_mempool(txns[1])

        # Activate the replay protection
        block(5556)
        node.p2p.send_blocks_and_test([self.tip], node)

        # Check we just activated the replay protection
        assert_equal(
            node.getblockheader(node.getbestblockhash())['mediantime'],
            REPLAY_PROTECTION_START_TIME)

        # Non replay protected transactions are not valid anymore,
        # so they should be removed from the mempool.
        assert tx_id not in set(node.getrawmempool())

        # Good old transactions are now invalid.
        send_transaction_to_mempool(txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(txns[1]))

        # They also cannot be mined
        block(4)
        update_block(4, txns)
        node.p2p.send_blocks_and_test([self.tip],
                                      node,
                                      success=False,
                                      reject_reason='blk-bad-inputs')

        # Rewind bad block
        tip(5556)

        # The replay protected transaction is now valid
        replay_tx0_id = send_transaction_to_mempool(replay_txns[0])
        replay_tx1_id = send_transaction_to_mempool(replay_txns[1])

        # Make sure the transaction are ready to be mined.
        tmpl = node.getblocktemplate()

        found_id0 = False
        found_id1 = False

        for txn in tmpl['transactions']:
            txid = txn['txid']
            if txid == replay_tx0_id:
                found_id0 = True
            elif txid == replay_tx1_id:
                found_id1 = True

        assert found_id0 and found_id1

        # And the mempool is still in good shape.
        assert replay_tx0_id in set(node.getrawmempool())
        assert replay_tx1_id in set(node.getrawmempool())

        # They also can also be mined
        block(5)
        update_block(5, replay_txns)
        node.p2p.send_blocks_and_test([self.tip], node)

        # Ok, now we check if a reorg work properly across the activation.
        postforkblockid = node.getbestblockhash()
        node.invalidateblock(postforkblockid)
        assert replay_tx0_id in set(node.getrawmempool())
        assert replay_tx1_id in set(node.getrawmempool())

        # Deactivating replay protection.
        forkblockid = node.getbestblockhash()
        node.invalidateblock(forkblockid)
        # The funding tx is not evicted from the mempool, since it's valid in
        # both sides of the fork
        assert replay_tx0_id in set(node.getrawmempool())
        assert replay_tx1_id not in set(node.getrawmempool())

        # Check that we also do it properly on deeper reorg.
        node.reconsiderblock(forkblockid)
        node.reconsiderblock(postforkblockid)
        node.invalidateblock(forkblockid)
        assert replay_tx0_id in set(node.getrawmempool())
        assert replay_tx1_id not in set(node.getrawmempool())
Esempio n. 18
0
class FullBlockTest(ComparisonTestFramework):

    # Can either run this test as 1 node with expected answers, or two and compare them.
    # Change the "outcome" variable from each TestInstance object to only do
    # the comparison.

    def __init__(self):
        super().__init__()
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.tip = None
        self.blocks = {}

    def setup_network(self):
        self.extra_args = [['-allowfreetx=0']]
        self.nodes = start_nodes(self.num_nodes,
                                 self.options.tmpdir,
                                 self.extra_args,
                                 binary=[self.options.testbinary])

    def add_options(self, parser):
        super().add_options(parser)
        parser.add_option("--runbarelyexpensive",
                          dest="runbarelyexpensive",
                          default=True)

    def run_test(self):
        self.test = TestManager(self, self.options.tmpdir)
        self.test.add_all_connections(self.nodes)
        # Start up network handling in another thread
        NetworkThread().start()
        self.test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)
        ltor_sort_block(block)

    # this is a little handier to use than the version in blocktools.py
    def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = create_transaction(spend_tx, n, b"", value, script)
        return tx

    # sign a transaction, using the key we know about
    # this signs input 0 in tx, which is assumed to be spending output n in
    # spend_tx
    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        if (scriptPubKey[0] == OP_TRUE):  # an anyone-can-spend
            tx.vin[0].scriptSig = CScript()
            return
        sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0,
                                      SIGHASH_ALL | SIGHASH_FORKID,
                                      spend_tx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript([
            self.coinbase_key.sign(sighash) +
            bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
        ])

    def create_and_sign_transaction(self,
                                    spend_tx,
                                    n,
                                    value,
                                    script=CScript([OP_TRUE])):
        tx = self.create_tx(spend_tx, n, value, script)
        self.sign_tx(tx, spend_tx, n)
        tx.rehash()
        return tx

    def next_block(self,
                   number,
                   spend=None,
                   additional_coinbase_value=0,
                   script=CScript([OP_TRUE]),
                   solve=True):
        if self.tip == None:
            base_block_hash = self.genesis_hash
            block_time = int(time.time()) + 1
        else:
            base_block_hash = self.tip.sha256
            block_time = self.tip.nTime + 1
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(absoluteHeight=height,
                                   pubkey=self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        coinbase.rehash()
        if spend == None:
            block = create_block(base_block_hash, coinbase, block_time)
        else:
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1  # all but one satoshi to fees
            coinbase.rehash()
            block = create_block(base_block_hash, coinbase, block_time)
            tx = create_transaction(spend.tx, spend.n, b"", 1,
                                    script)  # spend 1 satoshi
            self.sign_tx(tx, spend.tx, spend.n)
            self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
        if solve:
            block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block
        create_tx = self.create_tx

        # shorthand for variables
        node = self.nodes[0]

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([self.coinbase_pubkey] +
                                [OP_2DUP, OP_CHECKSIGVERIFY] * 5 +
                                [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Creates a new transaction using a p2sh transaction as input
        def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(
                CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0,
                                          SIGHASH_ALL | SIGHASH_FORKID,
                                          p2sh_tx_to_spend.vout[0].nValue)
            sig = self.coinbase_key.sign(sighash) + bytes(
                bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # P2SH tests
        # Create a p2sh transaction
        value = out[0].tx.vout[out[0].n].nValue  # absurdly high fee
        p2sh_tx = self.create_and_sign_transaction(out[0].tx, out[0].n, value,
                                                   p2sh_script)

        # Add the transaction to the block
        block(1)
        update_block(1, [p2sh_tx])
        yield accepted()

        # Sigops p2sh limit for the mempool test
        p2sh_sigops_limit_mempool = MAX_STANDARD_TX_SIGOPS - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh script
        too_many_p2sh_sigops_mempool = CScript([OP_CHECKSIG] *
                                               (p2sh_sigops_limit_mempool + 1))

        # A transaction with this output script can't get into the mempool
        try:
            node.sendrawtransaction(
                ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool)))
        except JSONRPCException as exp:
            assert_equal(exp.error["message"], RPC_TXNS_TOO_MANY_SIGOPS_ERROR)
        else:
            assert (False)

        # The transaction is rejected, so the mempool should still be empty
        assert_equal(set(node.getrawmempool()), set())

        # Max sigops in one p2sh txn
        max_p2sh_sigops_mempool = CScript([OP_CHECKSIG] *
                                          (p2sh_sigops_limit_mempool))

        # A transaction with this output script can get into the mempool
        max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool)
        max_p2sh_sigops_txn_id = node.sendrawtransaction(
            ToHex(max_p2sh_sigops_txn), True)
        assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id})

        # Mine the transaction
        block(2, spend=out[1])
        update_block(2, [max_p2sh_sigops_txn])
        yield accepted()

        # The transaction has been mined, it's not in the mempool anymore
        assert_equal(set(node.getrawmempool()), set())
Esempio n. 19
0
    def run_test(self):
        # Generate enough blocks to trigger certain block votes
        self.nodes[0].generate(1150)
        self.sync_all()

        logging.info("not on chain tip")
        badtip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16)
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)

        coinbase = create_coinbase(height + 1)
        cur_time = int(time.time())
        self.nodes[0].setmocktime(cur_time)
        self.nodes[1].setmocktime(cur_time)

        block = create_block(badtip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()

        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: does not build on chain tip")

        logging.info("time too far in the past")
        block = create_block(tip, coinbase, cur_time)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: time-too-old")

        logging.info("time too far in the future")
        block = create_block(tip, coinbase, cur_time + 10000000)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: time-too-new")

        logging.info("bad version 1")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 1
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")
        logging.info("bad version 2")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 2
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")
        logging.info("bad version 3")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 3
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-version")

        logging.info("bad coinbase height")
        tip = int(self.nodes[0].getblockhash(height), 16)
        block = create_block(tip, create_coinbase(height), cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(
            hexblk), JSONRPCException, "invalid block: bad-cb-height")

        logging.info("bad merkle root")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.hashMerkleRoot = 0x12345678
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txnmrklroot")

        logging.info("no tx")
        block = create_block(tip, None, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-blk-length")

        logging.info("good block")
        block = create_block(tip, coinbase, cur_time + 600)
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)

        # ------
        self.nodes[0].validateblocktemplate(hexblk)
        block.solve()
        hexblk = ToHex(block)
        self.nodes[0].submitblock(hexblk)
        self.sync_all()

        prev_block = block
        # out_value is less than 50TONE because regtest halvings happen every 150 blocks, and is in Satoshis
        out_value = block.vtx[0].vout[0].nValue
        tx1 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 2), int(out_value / 2)])
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = cur_time + 1200

        logging.info("no coinbase")
        block = create_block(tip, None, next_time, [tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-cb-missing")

        logging.info("double coinbase")

        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()

        coinbase2 = create_coinbase(height + 1, coinbase_pubkey)
        block = create_block(tip, coinbase, next_time, [coinbase2, tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-cb-multiple")

        logging.info("premature coinbase spend")
        block = create_block(tip, coinbase, next_time, [tx1])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-premature-spend-of-coinbase")

        self.nodes[0].generate(100)
        self.sync_all()
        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = cur_time + 1200

        logging.info("inputs below outputs")
        tx6 = create_transaction(prev_block.vtx[0], 0, b'\x51', [out_value + 1000])
        block = create_block(tip, coinbase, next_time, [tx6])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-in-belowout")

        tx5 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(21000001 * COIN)])
        logging.info("money range")
        block = create_block(tip, coinbase, next_time, [tx5])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-vout-toolarge")

        logging.info("bad tx offset")
        tx_bad = create_broken_transaction(prev_block.vtx[0], 1, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx_bad])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        logging.info("bad tx offset largest number")
        tx_bad = create_broken_transaction(prev_block.vtx[0], 0xffffffff, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx_bad])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        logging.info("double tx")
        tx2 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 4)])
        block = create_block(tip, coinbase, next_time, [tx2, tx2])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        tx3 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 9), int(out_value / 10)])
        tx4 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 8), int(out_value / 7)])
        logging.info("double spend")
        block = create_block(tip, coinbase, next_time, [tx3, tx4])
        block.nVersion = 0x20000000
        block.rehash()
        hexblk = ToHex(block)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: bad-txns-inputs-missingorspent")

        tx_good = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 50)] * 50)
        logging.info("good tx")
        block = create_block(tip, coinbase, next_time, [tx_good])
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        self.nodes[0].validateblocktemplate(hexblk)
        self.nodes[0].submitblock(hexblk)

        self.sync_all()

        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = next_time + 600

        coinbase_key = CECKey()
        coinbase_key.set_secretbytes(b"horsebattery")
        coinbase_pubkey = coinbase_key.get_pubkey()
        coinbase3 = create_coinbase(height + 1, coinbase_pubkey)

        txl = []
        for i in range(0, 50):
            ov = block.vtx[1].vout[i].nValue
            txl.append(create_transaction(block.vtx[1], i, b'\x51', [int(ov / 50)] * 50))
        block = create_block(tip, coinbase, next_time, txl)
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        for n in self.nodes:
            n.validateblocktemplate(hexblk)

        logging.info("excessive")
        self.nodes[0].setminingmaxblock(1000)
        self.nodes[0].setexcessiveblock(1000, 12)
        expectException(lambda: self.nodes[0].validateblocktemplate(hexblk),
                        JSONRPCException, "invalid block: excessive")
        self.nodes[0].setexcessiveblock(16 * 1000 * 1000, 12)
        self.nodes[0].setminingmaxblock(1000 * 1000)

        for it in range(0, 100):
            # if (it&1023)==0: print(it)
            h2 = hexblk
            pos = random.randint(0, len(hexblk))
            val = random.randint(0, 15)
            h3 = h2[:pos] + ('%x' % val) + h2[pos + 1:]
            try:
                self.nodes[0].validateblocktemplate(h3)
            except JSONRPCException as e:
                if not (e.error["code"] == -1 or e.error["code"] == -22):
                    print(str(e))
                # its ok we expect garbage

        self.nodes[1].submitblock(hexblk)
        self.sync_all()

        height = self.nodes[0].getblockcount()
        tip = int(self.nodes[0].getblockhash(height), 16)
        coinbase = create_coinbase(height + 1)
        next_time = next_time + 600
        prev_block = block
        txl = []
        for tx in prev_block.vtx:
            for outp in range(0, len(tx.vout)):
                ov = tx.vout[outp].nValue
                txl.append(create_transaction(tx, outp, CScript([OP_CHECKSIG] * 100), [int(ov / 2)] * 2))
        block = create_block(tip, coinbase, next_time, txl)
        block.nVersion = 0x20000000
        block.rehash()
        block.solve()
        hexblk = ToHex(block)
        for n in self.nodes:
            expectException(lambda: n.validateblocktemplate(hexblk), JSONRPCException,
                            "invalid block: bad-blk-sigops")
class FullBlockTest(ComparisonTestFramework):

    # Can either run this test as 1 node with expected answers, or two and compare them.
    # Change the "outcome" variable from each TestInstance object to only do the comparison.
    def __init__(self):
        super().__init__()
        self.excessive_block_size = 16 * ONE_MEGABYTE
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"fatstacks")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.tip = None
        self.blocks = {}

    def setup_network(self):
        self.extra_args = [[
            '-debug', '-norelaypriority', '-whitelist=127.0.0.1',
            '-limitancestorcount=9999', '-limitancestorsize=9999',
            '-limitdescendantcount=9999', '-limitdescendantsize=9999',
            '-maxmempool=999',
            "-uahfstarttime=%d" % UAHF_START_TIME,
            "-excessiveblocksize=%d" % self.excessive_block_size
        ]]
        self.nodes = start_nodes(self.num_nodes,
                                 self.options.tmpdir,
                                 self.extra_args,
                                 binary=[self.options.testbinary])

    def add_options(self, parser):
        super().add_options(parser)
        parser.add_option("--runbarelyexpensive",
                          dest="runbarelyexpensive",
                          default=True)

    def run_test(self):
        self.test = TestManager(self, self.options.tmpdir)
        self.test.add_all_connections(self.nodes)
        # Start up network handling in another thread
        NetworkThread().start()
        # Set the blocksize to 2MB as initial condition
        self.nodes[0].setexcessiveblock(self.excessive_block_size)
        self.nodes[0].setmocktime(UAHF_START_TIME)
        self.test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)

    # this is a little handier to use than the version in blocktools.py
    def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = create_transaction(spend_tx, n, b"", value, script)
        return tx

    # sign a transaction, using the key we know about
    # this signs input 0 in tx, which is assumed to be spending output n in spend_tx
    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        if (scriptPubKey[0] == OP_TRUE):  # an anyone-can-spend
            tx.vin[0].scriptSig = CScript()
            return
        (sighash, err) = SignatureHash(spend_tx.vout[n].scriptPubKey, tx, 0,
                                       SIGHASH_ALL)
        tx.vin[0].scriptSig = CScript([
            self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))
        ])

    def create_and_sign_transaction(self,
                                    spend_tx,
                                    n,
                                    value,
                                    script=CScript([OP_TRUE])):
        tx = self.create_tx(spend_tx, n, value, script)
        self.sign_tx(tx, spend_tx, n)
        tx.rehash()
        return tx

    def next_block(self,
                   number,
                   spend=None,
                   additional_coinbase_value=0,
                   script=None,
                   extra_sigops=0,
                   block_size=0,
                   solve=True):
        """
        Create a block on top of self.tip, and advance self.tip to point to the new block
        if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend
        output, and rest will go to fees.
        """
        if self.tip == None:
            base_block_hash = self.genesis_hash
            block_time = int(time.time()) + 1
        else:
            base_block_hash = self.tip.sha256
            block_time = self.tip.nTime + 1
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1  # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, block_time)
        spendable_output = None
        if (spend != None):
            tx = CTransaction()
            tx.vin.append(
                CTxIn(COutPoint(spend.tx.sha256, spend.n), b"",
                      0xffffffff))  # no signature yet
            # We put some random data into the first transaction of the chain to randomize ids
            tx.vout.append(
                CTxOut(0, CScript([random.randint(0, 255), OP_DROP, OP_TRUE])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            spendable_output = PreviousSpendableOutput(tx, 0)

            # Now sign it if necessary
            scriptSig = b""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                (sighash,
                 err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx,
                                      0, SIGHASH_ALL)
                scriptSig = CScript([
                    self.coinbase_key.sign(sighash) +
                    bytes(bytearray([SIGHASH_ALL]))
                ])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
        if spendable_output != None and block_size > 0:
            while len(block.serialize()) < block_size:
                tx = CTransaction()
                script_length = block_size - len(block.serialize()) - 79
                if script_length > 510000:
                    script_length = 500000
                tx_sigops = min(extra_sigops, script_length,
                                MAX_TX_SIGOPS_COUNT)
                extra_sigops -= tx_sigops
                script_pad_len = script_length - tx_sigops
                script_output = CScript([b'\x00' * script_pad_len] +
                                        [OP_CHECKSIG] * tx_sigops)
                tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
                tx.vout.append(CTxOut(0, script_output))
                tx.vin.append(
                    CTxIn(
                        COutPoint(spendable_output.tx.sha256,
                                  spendable_output.n)))
                spendable_output = PreviousSpendableOutput(tx, 0)
                self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
            # Make sure the math above worked out to produce the correct block size
            # (the math will fail if there are too many transactions in the block)
            assert_equal(len(block.serialize()), block_size)
            # Make sure all the requested sigops have been included
            assert_equal(extra_sigops, 0)
        if solve:
            block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

        # In order to trigger the HF, we need one block past activation time
        bfork = block(5555)
        bfork.nTime = UAHF_START_TIME
        update_block(5555, [])
        save_spendable_output()
        yield accepted()

        # Then we pile 5 blocks to move MTP forward and trigger the HF
        for i in range(5):
            block(5100 + i)
            test.blocks_and_transactions.append([self.tip, True])
            save_spendable_output()
        yield test

        # Create a new block and activate the fork, the block needs
        # to be > 1MB . For more specific tests about the fork activation,
        # check abc-p2p-activation.py
        block(5556,
              spend=get_spendable_output(),
              block_size=LEGACY_MAX_BLOCK_SIZE + 1)
        yield accepted()

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

        # Let's build some blocks and test them.
        for i in range(16):
            n = i + 1
            block(n, spend=out[i], block_size=n * ONE_MEGABYTE)
            yield accepted()

        # block of maximal size
        block(17, spend=out[16], block_size=self.excessive_block_size)
        yield accepted()

        # Reject oversized blocks with bad-blk-length error
        block(18, spend=out[17], block_size=self.excessive_block_size + 1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        # Rewind bad block.
        tip(17)

        # Accept many sigops
        lots_of_checksigs = CScript([OP_CHECKSIG] *
                                    (MAX_BLOCK_SIGOPS_PER_MB - 1))
        block(19,
              spend=out[17],
              script=lots_of_checksigs,
              block_size=ONE_MEGABYTE)
        yield accepted()

        too_many_blk_checksigs = CScript([OP_CHECKSIG] *
                                         MAX_BLOCK_SIGOPS_PER_MB)
        block(20,
              spend=out[18],
              script=too_many_blk_checksigs,
              block_size=ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(19)

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(21,
              spend=out[18],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(22,
              spend=out[19],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(23,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(24,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(25,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(26,
              spend=out[21],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=3 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(27,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(28,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=3 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Too many sigops in one txn
        too_many_tx_checksigs = CScript([OP_CHECKSIG] *
                                        (MAX_BLOCK_SIGOPS_PER_MB + 1))
        block(29,
              spend=out[22],
              script=too_many_tx_checksigs,
              block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(26)

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([self.coinbase_pubkey] +
                                [OP_2DUP, OP_CHECKSIGVERIFY] * 5 +
                                [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Create a p2sh transaction
        p2sh_tx = self.create_and_sign_transaction(out[22].tx, out[22].n, 1,
                                                   p2sh_script)

        # Add the transaction to the block
        block(30)
        update_block(30, [p2sh_tx])
        yield accepted()

        # Creates a new transaction using the p2sh transaction included in the last block
        def spend_p2sh_tx(output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            (sighash, err) = SignatureHash(redeem_script, spent_p2sh_tx, 0,
                                           SIGHASH_ALL)
            sig = self.coinbase_key.sign(sighash) + bytes(
                bytearray([SIGHASH_ALL]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # Sigops p2sh limit
        p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - redeem_script.GetSigOpCount(
            True)
        # Too many sigops in one p2sh txn
        too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1))
        block(31, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)])
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(30)

        # Max sigops in one p2sh txn
        max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit))
        block(32, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(32, [spend_p2sh_tx(max_p2sh_sigops)])
        yield accepted()

        # Check that compact block also work for big blocks
        node = self.nodes[0]
        peer = TestNode()
        peer.add_connection(NodeConn('127.0.0.1', p2p_port(0), node, peer))

        # Start up network handling in another thread and wait for connection
        # to be etablished
        NetworkThread().start()
        peer.wait_for_verack()

        # Wait for SENDCMPCT
        def received_sendcmpct():
            return (peer.last_sendcmpct != None)

        got_sendcmpt = wait_until(received_sendcmpct, timeout=30)
        assert (got_sendcmpt)

        sendcmpct = msg_sendcmpct()
        sendcmpct.version = 1
        sendcmpct.announce = True
        peer.send_and_ping(sendcmpct)

        # Exchange headers
        def received_getheaders():
            return (peer.last_getheaders != None)

        got_getheaders = wait_until(received_getheaders, timeout=30)
        assert (got_getheaders)

        # Return the favor
        peer.send_message(peer.last_getheaders)

        # Wait for the header list
        def received_headers():
            return (peer.last_headers != None)

        got_headers = wait_until(received_headers, timeout=30)
        assert (got_headers)

        # It's like we know about the same headers !
        peer.send_message(peer.last_headers)

        # Send a block
        b33 = block(33, spend=out[24], block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Checks the node to forward it via compact block
        def received_block():
            return (peer.last_cmpctblock != None)

        got_cmpctblock = wait_until(received_block, timeout=30)
        assert (got_cmpctblock)

        # Was it our block ?
        cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header
        cmpctblk_header.calc_sha256()
        assert (cmpctblk_header.sha256 == b33.sha256)

        # Send a bigger block
        peer.clear_block_data()
        b34 = block(34, spend=out[25], block_size=8 * ONE_MEGABYTE)
        yield accepted()

        # Checks the node to forward it via compact block
        got_cmpctblock = wait_until(received_block, timeout=30)
        assert (got_cmpctblock)

        # Was it our block ?
        cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header
        cmpctblk_header.calc_sha256()
        assert (cmpctblk_header.sha256 == b34.sha256)

        # Let's send a compact block and see if the node accepts it.
        # First, we generate the block and send all transaction to the mempool
        b35 = block(35, spend=out[26], block_size=8 * ONE_MEGABYTE)
        for i in range(1, len(b35.vtx)):
            node.sendrawtransaction(ToHex(b35.vtx[i]), True)

        # Now we create the compact block and send it
        comp_block = HeaderAndShortIDs()
        comp_block.initialize_from_block(b35)
        peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))

        # Check that compact block is received properly
        assert (int(node.getbestblockhash(), 16) == b35.sha256)
Esempio n. 21
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(
            start_node(1, self.options.tmpdir,
                       ["-debug", "-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(
            start_node(2, self.options.tmpdir,
                       ["-debug", "-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 102 blocks to node0. Block 102 will be rejected.
        for i in range(101):
            node0.send_message(msg_block(self.blocks[i]))
        node0.sync_with_ping()  # make sure the most recent block is synced
        node0.send_message(msg_block(self.blocks[101]))
        assert_equal(
            self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'],
            101)

        # Send 3102 blocks to node1. All blocks will be accepted.
        for i in range(2202):
            node1.send_message(msg_block(self.blocks[i]))
        node1.sync_with_ping()  # make sure the most recent block is synced
        assert_equal(
            self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'],
            2202)

        # Send 102 blocks to node2. Block 102 will be rejected.
        for i in range(101):
            node2.send_message(msg_block(self.blocks[i]))
        node2.sync_with_ping()  # make sure the most recent block is synced
        node2.send_message(msg_block(self.blocks[101]))
        assert_equal(
            self.nodes[2].getblock(self.nodes[2].getbestblockhash())['height'],
            101)
Esempio n. 22
0
    def get_tests(self):
        node = self.nodes[0]
        self.genesis_hash = int(node.getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            make_conform_to_ctor(block)
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # Let's build some blocks and test them.
        for i in range(16):
            n = i + 1
            block(n, spend=out[i], block_size=n * ONE_MEGABYTE)
            yield accepted()

        # block of maximal size
        block(17, spend=out[16], block_size=self.excessive_block_size)
        yield accepted()

        # Reject oversized blocks with bad-blk-length error
        block(18, spend=out[17], block_size=self.excessive_block_size + 1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        # Rewind bad block.
        tip(17)

        # Accept many sigops
        lots_of_checksigs = CScript([OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB)
        block(19,
              spend=out[17],
              script=lots_of_checksigs,
              block_size=ONE_MEGABYTE)
        yield accepted()

        block(20,
              spend=out[18],
              script=lots_of_checksigs,
              block_size=ONE_MEGABYTE,
              extra_sigops=1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(19)

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(21,
              spend=out[18],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(22,
              spend=out[19],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(23,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(24,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(25,
              spend=out[20],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=2 * ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(26,
              spend=out[21],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB,
              block_size=3 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(27,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=2 * ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(28,
              spend=out[22],
              script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1,
              block_size=3 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Too many sigops in one txn
        too_many_tx_checksigs = CScript([OP_CHECKSIG] *
                                        (MAX_BLOCK_SIGOPS_PER_MB + 1))
        block(29,
              spend=out[22],
              script=too_many_tx_checksigs,
              block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(26)

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"fatstacks")
        public_key = private_key.get_pubkey()

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([public_key] +
                                [OP_2DUP, OP_CHECKSIGVERIFY] * 5 +
                                [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Create a p2sh transaction
        p2sh_tx = self.create_tx(out[22], 1, p2sh_script)

        # Add the transaction to the block
        block(30)
        update_block(30, [p2sh_tx])
        yield accepted()

        # Creates a new transaction using the p2sh transaction included in the
        # last block
        def spend_p2sh_tx(output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0,
                                          SIGHASH_ALL | SIGHASH_FORKID,
                                          p2sh_tx.vout[0].nValue)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # Sigops p2sh limit
        p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh txn
        too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1))
        block(31, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)])
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(30)

        # Max sigops in one p2sh txn
        max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit))
        block(32, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(32, [spend_p2sh_tx(max_p2sh_sigops)])
        yield accepted()

        # Submit a very large block via RPC
        large_block = block(33,
                            spend=out[24],
                            block_size=self.excessive_block_size)
        node.submitblock(ToHex(large_block))
Esempio n. 23
0
    def run_test(self):
        p2p0 = self.nodes[0].add_p2p_connection(BaseNode())

        # 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

        self.nodes[0].disconnect_p2ps()

        # 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)])
        self.start_node(2, 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())

        # send header lists to all three nodes
        p2p0.send_header_for_blocks(self.blocks[0:2000])
        p2p0.send_header_for_blocks(self.blocks[2000:])
        p2p1.send_header_for_blocks(self.blocks[0:2000])
        p2p1.send_header_for_blocks(self.blocks[2000:])
        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 all blocks to node1. All blocks will be accepted.
        for i in range(2202):
            p2p1.send_message(msg_block(self.blocks[i]))
        # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
        p2p1.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(p2p2)
        self.assert_blockchain_height(self.nodes[2], 101)
class FullBlockTest(ComparisonTestFramework):

    # Can either run this test as 1 node with expected answers, or two and compare them.
    # Change the "outcome" variable from each TestInstance object to only do
    # the comparison.

    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.tip = None
        self.blocks = {}
        self.extra_args = [['-minrelaytxfee=0']]

    def add_options(self, parser):
        super().add_options(parser)
        parser.add_option("--runbarelyexpensive",
                          dest="runbarelyexpensive",
                          default=True)

    def run_test(self):
        self.test.run()

    def get_tests(self):
        # shorthand for functions
        block = self.chain.next_block
        update_block = self.chain.update_block
        save_spendable_output = self.chain.save_spendable_output
        get_spendable_output = self.chain.get_spendable_output
        accepted = self.accepted

        # shorthand for variables
        node = self.nodes[0]
        self.chain.set_genesis_hash(int(node.getbestblockhash(), 16))
        # Create a new block
        block(0)
        yield accepted()

        test, out, _ = prepare_init_chain(self.chain, 99, 33)

        yield test

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([self.coinbase_pubkey] +
                                [OP_2DUP, OP_CHECKSIGVERIFY] * 5 +
                                [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Creates a new transaction using a p2sh transaction as input
        def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(
                CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0,
                                          SIGHASH_ALL | SIGHASH_FORKID,
                                          p2sh_tx_to_spend.vout[0].nValue)
            sig = self.coinbase_key.sign(sighash) + bytes(
                bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # P2SH tests
        # Create a p2sh transaction
        p2sh_tx = create_and_sign_transaction(out[0].tx, out[0].n, 1,
                                              p2sh_script, self.coinbase_key)

        # Add the transaction to the block
        block(1)
        update_block(1, [p2sh_tx])
        yield accepted()

        # Sigops p2sh limit for the mempool test
        p2sh_sigops_limit_mempool = MAX_TX_SIGOPS_COUNT_POLICY_BEFORE_GENESIS - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh script
        too_many_p2sh_sigops_mempool = CScript([OP_CHECKSIG] *
                                               (p2sh_sigops_limit_mempool + 1))

        # A transaction with this output script can't get into the mempool
        assert_raises_rpc_error(
            -26, RPC_TXNS_TOO_MANY_SIGOPS_ERROR, node.sendrawtransaction,
            ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool)))

        # The transaction is rejected, so the mempool should still be empty
        assert_equal(set(node.getrawmempool()), set())

        # Max sigops in one p2sh txn
        max_p2sh_sigops_mempool = CScript([OP_CHECKSIG] *
                                          (p2sh_sigops_limit_mempool))

        # A transaction with this output script can get into the mempool
        max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool)
        max_p2sh_sigops_txn_id = node.sendrawtransaction(
            ToHex(max_p2sh_sigops_txn))
        assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id})

        # Mine the transaction
        block(2, spend=out[1])
        update_block(2, [max_p2sh_sigops_txn])
        yield accepted()

        # The transaction has been mined, it's not in the mempool anymore
        assert_equal(set(node.getrawmempool()), set())
Esempio n. 25
0
class FullBlockTest(ComparisonTestFramework):

    # Can either run this test as 1 node with expected answers, or two and compare them.
    # Change the "outcome" variable from each TestInstance object to only do
    # the comparison.

    def set_test_params(self):
        self.num_nodes = 1
        self.setup_clean_chain = True
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.tip = None
        self.blocks = {}

    def setup_network(self):
        self.extra_args = [['-norelaypriority']]
        self.add_nodes(self.num_nodes, self.extra_args)
        self.start_nodes()

    def add_options(self, parser):
        super().add_options(parser)
        parser.add_option(
            "--runbarelyexpensive", dest="runbarelyexpensive", default=True)

    def run_test(self):
        self.test = TestManager(self, self.options.tmpdir)
        self.test.add_all_connections(self.nodes)
        # Start up network handling in another thread
        NetworkThread().start()
        self.test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)

    # this is a little handier to use than the version in blocktools.py
    def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = create_transaction(spend_tx, n, b"", value, script)
        return tx

    # sign a transaction, using the key we know about
    # this signs input 0 in tx, which is assumed to be spending output n in
    # spend_tx
    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        if (scriptPubKey[0] == OP_TRUE):  # an anyone-can-spend
            tx.vin[0].scriptSig = CScript()
            return
        sighash = SignatureHashForkId(
            spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript(
            [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])

    def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = self.create_tx(spend_tx, n, value, script)
        self.sign_tx(tx, spend_tx, n)
        tx.rehash()
        return tx

    def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE])):
        if self.tip == None:
            base_block_hash = self.genesis_hash
            block_time = int(time.time()) + 1
        else:
            base_block_hash = self.tip.sha256
            block_time = self.tip.nTime + 1
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        coinbase.rehash()
        if spend == None:
            block = create_block(base_block_hash, coinbase, block_time)
        else:
            # all but one satoshi to fees
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1
            coinbase.rehash()
            block = create_block(base_block_hash, coinbase, block_time)
            # spend 1 satoshi
            tx = create_transaction(spend.tx, spend.n, b"", 1, script)
            self.sign_tx(tx, spend.tx, spend.n)
            self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
        # Do PoW, which is very inexpensive on regnet
        block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block
        create_tx = self.create_tx

        # shorthand for variables
        node = self.nodes[0]

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([self.coinbase_pubkey] + [
                                OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Creates a new transaction using a p2sh transaction as input
        def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(
                CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(
                redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx_to_spend.vout[0].nValue)
            sig = self.coinbase_key.sign(
                sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # P2SH tests
        # Create a p2sh transaction
        p2sh_tx = self.create_and_sign_transaction(
            out[0].tx, out[0].n, 1, p2sh_script)

        # Add the transaction to the block
        block(1)
        update_block(1, [p2sh_tx])
        yield accepted()

        # Sigops p2sh limit for the mempool test
        p2sh_sigops_limit_mempool = MAX_STANDARD_TX_SIGOPS - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh script
        too_many_p2sh_sigops_mempool = CScript(
            [OP_CHECKSIG] * (p2sh_sigops_limit_mempool + 1))

        # A transaction with this output script can't get into the mempool
        assert_raises_rpc_error(-26, RPC_TXNS_TOO_MANY_SIGOPS_ERROR, node.sendrawtransaction,
                                ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool)))

        # The transaction is rejected, so the mempool should still be empty
        assert_equal(set(node.getrawmempool()), set())

        # Max sigops in one p2sh txn
        max_p2sh_sigops_mempool = CScript(
            [OP_CHECKSIG] * (p2sh_sigops_limit_mempool))

        # A transaction with this output script can get into the mempool
        max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool)
        max_p2sh_sigops_txn_id = node.sendrawtransaction(
            ToHex(max_p2sh_sigops_txn))
        assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id})

        # Mine the transaction
        block(2, spend=out[1])
        update_block(2, [max_p2sh_sigops_txn])
        yield accepted()

        # The transaction has been mined, it's not in the mempool anymore
        assert_equal(set(node.getrawmempool()), set())
Esempio n. 26
0
    def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=0):
        api = self.nodes[node_n]
        # Get current time
        current_time = int(time.time())
        nTime = current_time & 0xfffffff0

        # Create coinbase TX
        coinbase = create_coinbase(height)
        coinbase.vout[0].nValue = 0
        coinbase.vout[0].scriptPubKey = b""
        coinbase.nTime = nTime
        coinbase.rehash()

        # Create Block with coinbase
        block = create_block(int(prev_hash, 16), coinbase, nTime)

        # Find valid kernel hash - Create a new private key used for block signing.
        if not block.solve_stake(staking_prevouts):
            raise Exception("Not able to solve for any prev_outpoint")

        # Create coinstake TX
        amount, prev_time, prevScript = staking_prevouts[block.prevoutStake]
        outNValue = int(amount + 250 * COIN)
        stake_tx_unsigned = CTransaction()
        stake_tx_unsigned.nTime = block.nTime
        stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake))
        stake_tx_unsigned.vin[0].nSequence = 0xffffffff
        stake_tx_unsigned.vout.append(CTxOut())
        stake_tx_unsigned.vout.append(CTxOut(outNValue, hex_str_to_bytes(prevScript)))

        if fInvalid == 1:
            # Create a new private key and get the corresponding public key
            block_sig_key = CECKey()
            block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff)))
            pubkey = block_sig_key.get_pubkey()
            stake_tx_unsigned.vout[1].scriptPubKey = CScript([pubkey, OP_CHECKSIG])
        else:
            # Export the staking private key to sign the block with it
            privKey, compressed = wif_to_privkey(api.dumpprivkey(s_address))
            block_sig_key = CECKey()
            block_sig_key.set_compressed(compressed)
            block_sig_key.set_secretbytes(bytes.fromhex(privKey))
            # check the address
            addy = key_to_p2pkh(bytes_to_hex_str(block_sig_key.get_pubkey()), False, True)
            assert (addy == s_address)
            if fInvalid == 2:
                # add a new output with 100 coins from the pot
                new_key = CECKey()
                new_key.set_secretbytes(hash256(pack('<I', 0xffff)))
                pubkey = new_key.get_pubkey()
                stake_tx_unsigned.vout.append(CTxOut(100 * COIN, CScript([pubkey, OP_CHECKSIG])))
                stake_tx_unsigned.vout[1].nValue = outNValue - 100 * COIN

        # Sign coinstake TX and add it to the block
        stake_tx_signed_raw_hex = api.signrawtransaction(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']
        stake_tx_signed = CTransaction()
        stake_tx_signed.deserialize(BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)))
        block.vtx.append(stake_tx_signed)

        # Get correct MerkleRoot and rehash block
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()

        # sign block with block signing key and return it
        block.sign_block(block_sig_key)
        return block
Esempio n. 27
0
    def get_tests(self):
        node = self.nodes[0]
        self.genesis_hash = int(node.getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # Let's build some blocks and test them.
        for i in range(15):
            n = i + 1
            block(n, spend=out[i], block_size=n * ONE_MEGABYTE // 2)
            yield accepted()

        # Start moving MTP forward
        bfork = block(5555, out[15], block_size=8 * ONE_MEGABYTE)
        bfork.nTime = MONOLITH_START_TIME - 1
        update_block(5555, [])
        yield accepted()

        # Get to one block of the May 15, 2018 HF activation
        for i in range(5):
            block(5100 + i)
            test.blocks_and_transactions.append([self.tip, True])
        yield test

        # Check that the MTP is just before the configured fork point.
        assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'],
                     MONOLITH_START_TIME - 1)

        # Before we acivate the May 15, 2018 HF, 8MB is the limit.
        block(4444, spend=out[16], block_size=8 * ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        # Rewind bad block.
        tip(5104)

        # Actiavte the May 15, 2018 HF
        block(5556)
        yield accepted()

        # Now MTP is exactly the fork time. Bigger blocks are now accepted.
        assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'],
                     MONOLITH_START_TIME)

        # block of maximal size
        block(17, spend=out[16], block_size=self.excessive_block_size)
        yield accepted()

        # Reject oversized blocks with bad-blk-length error
        block(18, spend=out[17], block_size=self.excessive_block_size + 1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        # Rewind bad block.
        tip(17)

        # Accept many sigops
        lots_of_checksigs = CScript(
            [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB)
        block(19, spend=out[17], script=lots_of_checksigs,
              block_size=ONE_MEGABYTE)
        yield accepted()

        block(20, spend=out[18], script=lots_of_checksigs,
              block_size=ONE_MEGABYTE, extra_sigops=1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(19)

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(21, spend=out[18], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(22, spend=out[19], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(23, spend=out[20], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(24, spend=out[20], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(26, spend=out[21], script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Too many sigops in one txn
        too_many_tx_checksigs = CScript(
            [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1))
        block(
            29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(26)

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"fatstacks")
        public_key = private_key.get_pubkey()

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript(
            [public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Create a p2sh transaction
        p2sh_tx = self.create_tx(out[22], 1, p2sh_script)

        # Add the transaction to the block
        block(30)
        update_block(30, [p2sh_tx])
        yield accepted()

        # Creates a new transaction using the p2sh transaction included in the
        # last block
        def spend_p2sh_tx(output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(
                redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # Sigops p2sh limit
        p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh txn
        too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1))
        block(31, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)])
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(30)

        # Max sigops in one p2sh txn
        max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit))
        block(32, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(32, [spend_p2sh_tx(max_p2sh_sigops)])
        yield accepted()

        # Submit a very large block via RPC
        large_block = block(
            33, spend=out[24], block_size=self.excessive_block_size)
        node.submitblock(ToHex(large_block))
Esempio n. 28
0
def makePubKeys(numOfKeys):
    key = CECKey()
    key.set_secretbytes(b"randombytes2")
    return [key.get_pubkey()] * numOfKeys
Esempio n. 29
0
class PIVX_FakeStakeTest(BitcoinTestFramework):

    def set_test_params(self):
        ''' Setup test environment
        :param:
        :return:
        '''
        self.setup_clean_chain = True
        self.num_nodes = 1
        self.extra_args = [['-staking=1', '-debug=net']]*self.num_nodes


    def setup_network(self):
        ''' Can't rely on syncing all the nodes when staking=1
        :param:
        :return:
        '''
        self.setup_nodes()
        for i in range(self.num_nodes - 1):
            for j in range(i+1, self.num_nodes):
                connect_nodes_bi(self.nodes, i, j)

    def init_test(self):
        ''' Initializes test parameters
        :param:
        :return:
        '''
        self.log.info("\n\n*** Starting %s ***\n------------------------\n%s\n", self.__class__.__name__, self.description)
        # Global Test parameters (override in run_test)
        self.DEFAULT_FEE = 0.1
        # Spam blocks to send in current test
        self.NUM_BLOCKS = 30

        # Setup the p2p connections and start up the network thread.
        self.test_nodes = []
        for i in range(self.num_nodes):
            self.test_nodes.append(TestNode())
            self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i))

        network_thread_start()  # Start up network handling in another thread
        self.node = self.nodes[0]

        # Let the test nodes get in sync
        for i in range(self.num_nodes):
            self.test_nodes[i].wait_for_verack()


    def run_test(self):
        ''' Performs the attack of this test - run init_test first.
        :param:
        :return:
        '''
        self.description = ""
        self.init_test()
        return



    def create_spam_block(self, hashPrevBlock, stakingPrevOuts, height, fStakeDoubleSpent=False, fZPoS=False, spendingPrevOuts={}):
        ''' creates a block to spam the network with
        :param   hashPrevBlock:      (hex string) hash of previous block
                 stakingPrevOuts:    ({COutPoint --> (int, int, int, str)} dictionary)
                                      map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake
                 height:             (int) block height
                 fStakeDoubleSpent:  (bool) spend the coinstake input inside the block
                 fZPoS:              (bool) stake the block with zerocoin
                 spendingPrevOuts:   ({COutPoint --> (int, int, int, str)} dictionary)
                                      map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake
        :return  block:              (CBlock) generated block
        '''

        self.log.info("Creating Spam Block")

        # If not given inputs to create spam txes, use a copy of the staking inputs
        if len(spendingPrevOuts) == 0:
            spendingPrevOuts = dict(stakingPrevOuts)

        # Get current time
        current_time = int(time.time())
        nTime = current_time & 0xfffffff0

        # Create coinbase TX
        # Even if PoS blocks have empty coinbase vout, the height is required for the vin script
        coinbase = create_coinbase(height)
        coinbase.vout[0].nValue = 0
        coinbase.vout[0].scriptPubKey = b""
        coinbase.nTime = nTime
        coinbase.rehash()

        # Create Block with coinbase
        block = create_block(int(hashPrevBlock, 16), coinbase, nTime)

        # Find valid kernel hash - Create a new private key used for block signing.
        if not block.solve_stake(stakingPrevOuts):
            raise Exception("Not able to solve for any prev_outpoint")

        self.log.info("Stake found. Signing block...")

        # Sign coinstake TX and add it to the block
        signed_stake_tx = self.sign_stake_tx(block, stakingPrevOuts[block.prevoutStake][0], fZPoS)
        block.vtx.append(signed_stake_tx)

        # Remove coinstake input prevout unless we want to try double spending in the same block.
        # Skip for zPoS as the spendingPrevouts are just regular UTXOs
        if not fZPoS and not fStakeDoubleSpent:
            del spendingPrevOuts[block.prevoutStake]

        # remove a random prevout from the list
        # (to randomize block creation if the same height is picked two times)
        del spendingPrevOuts[choice(list(spendingPrevOuts))]

        # Create spam for the block. Sign the spendingPrevouts
        self.log.info("Creating spam TXes...")
        for outPoint in spendingPrevOuts:
            value_out = int(spendingPrevOuts[outPoint][0] - self.DEFAULT_FEE * COIN)
            tx = create_transaction(outPoint, b"", value_out, nTime, scriptPubKey=CScript([self.block_sig_key.get_pubkey(), OP_CHECKSIG]))
            # sign txes
            signed_tx_hex = self.node.signrawtransaction(bytes_to_hex_str(tx.serialize()))['hex']
            signed_tx = CTransaction()
            signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex)))
            block.vtx.append(signed_tx)

        # Get correct MerkleRoot and rehash block
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()

        # Sign block with coinstake key and return it
        block.sign_block(self.block_sig_key)
        return block


    def spend_utxo(self, utxo, address_list):
        ''' spend amount from previously unspent output to a provided address
        :param      utxo:           (JSON) returned from listunspent used as input
                    addresslist:    (string) destination address
        :return:    txhash:         (string) tx hash if successful, empty string otherwise
        '''
        try:
            inputs = [{"txid":utxo["txid"], "vout":utxo["vout"]}]
            out_amount = (float(utxo["amount"]) - self.DEFAULT_FEE)/len(address_list)
            outputs = {}
            for address in address_list:
                outputs[address] = out_amount
            spendingTx = self.node.createrawtransaction(inputs, outputs)
            spendingTx_signed = self.node.signrawtransaction(spendingTx)
            if spendingTx_signed["complete"]:
                txhash = self.node.sendrawtransaction(spendingTx_signed["hex"])
                return txhash
            else:
                self.log.warning("Error: %s" % str(spendingTx_signed["errors"]))
                return ""
        except JSONRPCException as e:
            self.log.error("JSONRPCException: %s" % str(e))
            return ""


    def spend_utxos(self, utxo_list, address_list = []):
        ''' spend utxos to provided list of addresses or 10 new generate ones.
        :param      utxo_list:      (JSON list) returned from listunspent used as input
                    address_list:   (string list) [optional] recipient PIVX addresses. if not set,
                                    10 new addresses will be generated from the wallet for each tx.
        :return:    txHashes        (string list) tx hashes
        '''
        txHashes = []

        # If not given, get 10 new addresses from self.node wallet
        if address_list == []:
            for i in range(10):
                address_list.append(self.node.getnewaddress())

        for utxo in utxo_list:
            try:
                # spend current utxo to provided addresses
                txHash = self.spend_utxo(utxo, address_list)
                if txHash != "":
                    txHashes.append(txHash)
            except JSONRPCException as e:
                self.log.error("JSONRPCException: %s" % str(e))
                continue
        return txHashes


    def stake_amplification_step(self, utxo_list, address_list = []):
        ''' spends a list of utxos providing the list of new outputs
        :param      utxo_list:     (JSON list) returned from listunspent used as input
                    address_list:  (string list) [optional] recipient PIVX addresses.
        :return:    new_utxos:     (JSON list) list of new (valid) inputs after the spends
        '''
        self.log.info("--> Stake Amplification step started with %d UTXOs", len(utxo_list))
        txHashes = self.spend_utxos(utxo_list, address_list)
        num_of_txes = len(txHashes)
        new_utxos = []
        if num_of_txes> 0:
            self.log.info("Created %d transactions...Mining 2 blocks to include them..." % num_of_txes)
            self.node.generate(2)
            time.sleep(2)
            new_utxos = self.node.listunspent()

        self.log.info("Amplification step produced %d new \"Fake Stake\" inputs:" % len(new_utxos))
        return new_utxos



    def stake_amplification(self, utxo_list, iterations, address_list = []):
        ''' performs the "stake amplification" which gives higher chances at finding fake stakes
        :param      utxo_list:    (JSON list) returned from listunspent used as input
                    iterations:   (int) amount of stake amplification steps to perform
                    address_list: (string list) [optional] recipient PIVX addresses.
        :return:    all_inputs:   (JSON list) list of all spent inputs
        '''
        self.log.info("** Stake Amplification started with %d UTXOs", len(utxo_list))
        valid_inputs = utxo_list
        all_inputs = []
        for i in range(iterations):
            all_inputs = all_inputs + valid_inputs
            old_inputs = valid_inputs
            valid_inputs = self.stake_amplification_step(old_inputs, address_list)
        self.log.info("** Stake Amplification ended with %d \"fake\" UTXOs", len(all_inputs))
        return all_inputs



    def sign_stake_tx(self, block, stake_in_value, fZPoS=False):
        ''' signs a coinstake transaction
        :param      block:           (CBlock) block with stake to sign
                    stake_in_value:  (int) staked amount
                    fZPoS:           (bool) zerocoin stake
        :return:    stake_tx_signed: (CTransaction) signed tx
        '''
        self.block_sig_key = CECKey()

        if fZPoS:
            self.log.info("Signing zPoS stake...")
            # Create raw zerocoin stake TX (signed)
            raw_stake = self.node.createrawzerocoinstake(block.prevoutStake)
            stake_tx_signed_raw_hex = raw_stake["hex"]
            # Get stake TX private key to sign the block with
            stake_pkey = raw_stake["private-key"]
            self.block_sig_key.set_compressed(True)
            self.block_sig_key.set_secretbytes(bytes.fromhex(stake_pkey))

        else:
            # Create a new private key and get the corresponding public key
            self.block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff)))
            pubkey = self.block_sig_key.get_pubkey()
            # Create the raw stake TX (unsigned)
            scriptPubKey = CScript([pubkey, OP_CHECKSIG])
            outNValue = int(stake_in_value + 2*COIN)
            stake_tx_unsigned = CTransaction()
            stake_tx_unsigned.nTime = block.nTime
            stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake))
            stake_tx_unsigned.vin[0].nSequence = 0xffffffff
            stake_tx_unsigned.vout.append(CTxOut())
            stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey))
            # Sign the stake TX
            stake_tx_signed_raw_hex = self.node.signrawtransaction(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']

        # Deserialize the signed raw tx into a CTransaction object and return it
        stake_tx_signed = CTransaction()
        stake_tx_signed.deserialize(BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)))
        return stake_tx_signed


    def get_prevouts(self, utxo_list, blockHeight, zpos=False):
        ''' get prevouts (map) for each utxo in a list
        :param   utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input
                            <if zpos=True>  (JSON list) mints returned from listmintedzerocoins used as input
                 blockHeight:               (int) height of the previous block
                 zpos:                      (bool) type of utxo_list
        :return: stakingPrevOuts:           ({COutPoint --> (int, int, int, str)} dictionary)
                                            map outpoints to amount, block_time, nStakeModifier, hashStake
        '''
        zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000]
        stakingPrevOuts = {}

        for utxo in utxo_list:
            if zpos:
                # get mint checkpoint
                checkpointHeight = blockHeight - 200
                checkpointBlock = self.node.getblock(self.node.getblockhash(checkpointHeight), True)
                checkpoint = int(checkpointBlock['acc_checkpoint'], 16)
                # parse checksum and get checksumblock
                pos = zerocoinDenomList.index(utxo['denomination'])
                checksum = (checkpoint >> (32 * (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF
                checksumBlock = self.node.getchecksumblock(hex(checksum), utxo['denomination'], True)
                # get block hash and block time
                txBlockhash = checksumBlock['hash']
                txBlocktime = checksumBlock['time']
            else:
                # get raw transaction for current input
                utxo_tx = self.node.getrawtransaction(utxo['txid'], 1)
                # get block hash and block time
                txBlocktime = utxo_tx['blocktime']
                txBlockhash = utxo_tx['blockhash']

            # get Stake Modifier
            stakeModifier = int(self.node.getblock(txBlockhash)['modifier'], 16)
            # assemble prevout object
            utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime, stakeModifier, zpos)

        return stakingPrevOuts



    def log_data_dir_size(self):
        ''' Prints the size of the '/regtest/blocks' directory.
        :param:
        :return:
        '''
        init_size = dir_size(self.node.datadir + "/regtest/blocks")
        self.log.info("Size of data dir: %s kilobytes" % str(init_size))



    def test_spam(self, name, staking_utxo_list,
                  fRandomHeight=False, randomRange=0, randomRange2=0,
                  fDoubleSpend=False, fMustPass=False, fZPoS=False,
                  spending_utxo_list=[]):
        ''' General method to create, send and test the spam blocks
        :param    name:               (string) chain branch (usually either "Main" or "Forked")
                  staking_utxo_list:  (string list) utxos to use for staking
                  fRandomHeight:      (bool) send blocks at random height
                  randomRange:        (int) if fRandomHeight=True, height is >= current-randomRange
                  randomRange2:       (int) if fRandomHeight=True, height is < current-randomRange2
                  fDoubleSpend:       (bool) if true, stake input is double spent in block.vtx
                  fMustPass:          (bool) if true, the blocks must be stored on disk
                  fZPoS:              (bool) stake the block with zerocoin
                  spending_utxo_list: (string list) utxos to use for spending
        :return:  err_msgs:           (string list) reports error messages from the test
                                      or an empty list if test is successful
        '''
        # Create empty error messages list
        err_msgs = []
        # Log initial datadir size
        self.log_data_dir_size()
        # Get latest block number and hash
        block_count = self.node.getblockcount()
        pastBlockHash = self.node.getblockhash(block_count)
        randomCount = block_count
        self.log.info("Current height: %d" % block_count)
        for i in range(0, self.NUM_BLOCKS):
            if i !=0:
                self.log.info("Sent %d blocks out of %d" % (i, self.NUM_BLOCKS))

            # if fRandomHeight=True get a random block number (in range) and corresponding hash
            if fRandomHeight:
                randomCount = randint(block_count - randomRange, block_count - randomRange2)
                pastBlockHash = self.node.getblockhash(randomCount)

            # Get spending prevouts and staking prevouts for the height of current block
            current_block_n = randomCount + 1
            stakingPrevOuts = self.get_prevouts(staking_utxo_list, randomCount, zpos=fZPoS)
            spendingPrevOuts = self.get_prevouts(spending_utxo_list, randomCount)

            # Create the spam block
            block = self.create_spam_block(pastBlockHash, stakingPrevOuts, current_block_n,
                                           fStakeDoubleSpent=fDoubleSpend, fZPoS=fZPoS, spendingPrevOuts=spendingPrevOuts)

            # Log time and size of the block
            block_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(block.nTime))
            block_size = len(block.serialize())/1000
            self.log.info("Sending block %d [%s...] - nTime: %s - Size (kb): %.2f",
                          current_block_n, block.hash[:7], block_time, block_size)

            # Try submitblock
            var = self.node.submitblock(bytes_to_hex_str(block.serialize()))
            time.sleep(1)
            if (not fMustPass and var not in [None, "bad-txns-invalid-zpiv"]) or (fMustPass and var != "inconclusive"):
                self.log.error("submitblock [fMustPass=%s] result: %s" % (str(fMustPass), str(var)))
                err_msgs.append("submitblock %d: %s" % (current_block_n, str(var)))

            # Try sending the message block
            msg = msg_block(block)
            try:
                self.test_nodes[0].handle_connect()
                self.test_nodes[0].send_message(msg)
                time.sleep(2)
                block_ret = self.node.getblock(block.hash)
                if not fMustPass and block_ret is not None:
                    self.log.error("Error, block stored in %s chain" % name)
                    err_msgs.append("getblock %d: result not None" % current_block_n)
                if fMustPass:
                    if block_ret is None:
                        self.log.error("Error, block NOT stored in %s chain" % name)
                        err_msgs.append("getblock %d: result is None" % current_block_n)
                    else:
                        self.log.info("Good. Block IS stored on disk.")

            except JSONRPCException as e:
                exc_msg = str(e)
                if exc_msg == "Can't read block from disk (-32603)":
                    if fMustPass:
                        self.log.warning("Bad! Block was NOT stored to disk.")
                        err_msgs.append(exc_msg)
                    else:
                        self.log.info("Good. Block was not stored on disk.")
                else:
                    self.log.warning(exc_msg)
                    err_msgs.append(exc_msg)

            except Exception as e:
                exc_msg = str(e)
                self.log.error(exc_msg)
                err_msgs.append(exc_msg)


        self.log.info("Sent all %s blocks." % str(self.NUM_BLOCKS))
        # Log final datadir size
        self.log_data_dir_size()
        # Return errors list
        return err_msgs
    def run_test(self):
        node, = self.nodes

        self.bootstrap_p2p()

        tip = self.getbestblock(node)

        self.log.info("Create some blocks with OP_1 coinbase for spending.")
        blocks = []
        for _ in range(10):
            tip = self.build_block(tip)
            blocks.append(tip)
        node.p2p.send_blocks_and_test(blocks, node, success=True)
        spendable_outputs = [block.vtx[0] for block in blocks]

        self.log.info("Mature the blocks and get out of IBD.")
        node.generatetoaddress(100, node.get_deterministic_priv_key().address)

        tip = self.getbestblock(node)

        self.log.info("Setting up spends to test and mining the fundings.")
        fundings = []

        # Generate a key pair
        privkeybytes = b"Schnorr!" * 4
        private_key = CECKey()
        private_key.set_secretbytes(privkeybytes)
        # get uncompressed public key serialization
        public_key = private_key.get_pubkey()

        def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'):
            spendfrom = spendable_outputs.pop()

            script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG])

            value = spendfrom.vout[0].nValue

            # Fund transaction
            txfund = create_tx_with_script(spendfrom, 0, b'', value, script)
            txfund.rehash()
            fundings.append(txfund)

            # Spend transaction
            txspend = CTransaction()
            txspend.vout.append(
                CTxOut(value - 1000, CScript([OP_TRUE])))
            txspend.vin.append(
                CTxIn(COutPoint(txfund.sha256, 0), b''))

            # Sign the transaction
            sighashtype = SIGHASH_ALL | SIGHASH_FORKID
            hashbyte = bytes([sighashtype & 0xff])
            sighash = SignatureHashForkId(
                script, txspend, 0, sighashtype, value)
            if sigtype == 'schnorr':
                txsig = schnorr.sign(privkeybytes, sighash) + hashbyte
            elif sigtype == 'ecdsa':
                txsig = private_key.sign(sighash) + hashbyte
            txspend.vin[0].scriptSig = CScript([dummy, txsig])
            txspend.rehash()

            return txspend

        # This is valid.
        ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa')

        # This is invalid.
        ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa')

        # This is invalid.
        schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr')

        # This is valid.
        schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr')

        tip = self.build_block(tip, fundings)
        node.p2p.send_blocks_and_test([tip], node)

        self.log.info("Send a legacy ECDSA multisig into mempool.")
        node.p2p.send_txs_and_test([ecdsa0tx], node)
        assert_equal(node.getrawmempool(), [ecdsa0tx.hash])

        self.log.info("Trying to mine a non-null-dummy ECDSA.")
        self.check_for_ban_on_rejected_block(
            self.build_block(tip, [ecdsa1tx]), BADINPUTS_ERROR)
        self.log.info(
            "If we try to submit it by mempool or RPC, it is rejected and we are banned")
        assert_raises_rpc_error(-26, ECDSA_NULLDUMMY_ERROR,
                                node.sendrawtransaction, ToHex(ecdsa1tx))
        self.check_for_ban_on_rejected_tx(
            ecdsa1tx, ECDSA_NULLDUMMY_ERROR)

        self.log.info(
            "Submitting a Schnorr-multisig via net, and mining it in a block")
        node.p2p.send_txs_and_test([schnorr1tx], node)
        assert_equal(set(node.getrawmempool()), {
                     ecdsa0tx.hash, schnorr1tx.hash})
        tip = self.build_block(tip, [schnorr1tx])
        node.p2p.send_blocks_and_test([tip], node)

        self.log.info(
            "That legacy ECDSA multisig is still in mempool, let's mine it")
        assert_equal(node.getrawmempool(), [ecdsa0tx.hash])
        tip = self.build_block(tip, [ecdsa0tx])
        node.p2p.send_blocks_and_test([tip], node)
        assert_equal(node.getrawmempool(), [])

        self.log.info(
            "Trying Schnorr in legacy multisig is invalid and banworthy.")
        self.check_for_ban_on_rejected_tx(
            schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR)
        self.check_for_ban_on_rejected_block(
            self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR)
class FullBlockTest(ComparisonTestFramework):

    ''' Can either run this test as 1 node with expected answers, or two and compare them. 
        Change the "outcome" variable from each TestInstance object to only do the comparison. '''
    def __init__(self):
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"horsebattery")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.block_time = int(time.time())+1
        self.tip = None
        self.blocks = {}

    def run_test(self):
        test = TestManager(self, self.options.tmpdir)
        test.add_all_connections(self.nodes)
        NetworkThread().start() # Start up network handling in another thread
        test.run()

    def add_transactions_to_block(self, block, tx_list):
        [ tx.rehash() for tx in tx_list ]
        block.vtx.extend(tx_list)
        block.hashMerkleRoot = block.calc_merkle_root()
        block.rehash()
        return block
    
    # Create a block on top of self.tip, and advance self.tip to point to the new block
    # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output,
    # and rest will go to fees.
    def next_block(self, number, spend=None, additional_coinbase_value=0, script=None):
        if self.tip == None:
            base_block_hash = self.genesis_hash
        else:
            base_block_hash = self.tip.sha256
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, self.block_time)
        if (spend != None):
            tx = CTransaction()
            tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff))  # no signature yet
            # This copies the java comparison tool testing behavior: the first
            # txout has a garbage scriptPubKey, "to make sure we're not
            # pre-verifying too much" (?)
            tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            # Now sign it if necessary
            scriptSig = b""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL)
                scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            block = self.add_transactions_to_block(block, [tx])
        block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        self.block_time += 1
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previous marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject = None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])
       
        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # add transactions to a block produced by next_block
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            old_hash = block.sha256
            self.add_transactions_to_block(block, new_transactions)
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            self.block_heights[block.sha256] = self.block_heights[old_hash]
            del self.block_heights[old_hash]
            self.blocks[block_number] = block
            return block

        # creates a new block and advances the tip to that block
        block = self.next_block


        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()


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


        # Start by building a couple of blocks on top (which output is spent is
        # in parentheses):
        #     genesis -> b1 (0) -> b2 (1)
        out0 = get_spendable_output()
        block(1, spend=out0)
        save_spendable_output()
        yield accepted()

        out1 = get_spendable_output()
        b2 = block(2, spend=out1)
        yield accepted()


        # so fork like this:
        # 
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1)
        # 
        # Nothing should happen at this point. We saw b2 first so it takes priority.
        tip(1)
        b3 = block(3, spend=out1)
        txout_b3 = PreviousSpendableOutput(b3.vtx[1], 1)
        yield rejected()


        # Now we add another block to make the alternative chain longer.
        # 
        #     genesis -> b1 (0) -> b2 (1)
        #                      \-> b3 (1) -> b4 (2)
        out2 = get_spendable_output()
        block(4, spend=out2)
        yield accepted()


        # ... and back to the first chain.
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                      \-> b3 (1) -> b4 (2)
        tip(2)
        block(5, spend=out2)
        save_spendable_output()
        yield rejected()

        out3 = get_spendable_output()
        block(6, spend=out3)
        yield accepted()


        # Try to create a fork that double-spends
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                          \-> b7 (2) -> b8 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(7, spend=out2)
        yield rejected()

        out4 = get_spendable_output()
        block(8, spend=out4)
        yield rejected()


        # Try to create a block that has too much fee
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
        #                                                    \-> b9 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(6)
        block(9, spend=out4, additional_coinbase_value=1)
        yield rejected(RejectResult(16, b'bad-cb-amount'))

        
        # Create a fork that ends in a block with too much fee (the one that causes the reorg)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b10 (3) -> b11 (4)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        block(10, spend=out3)
        yield rejected()

        block(11, spend=out4, additional_coinbase_value=1)
        yield rejected(RejectResult(16, b'bad-cb-amount'))


        # Try again, but with a valid fork first
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b14 (5)
        #                                              (b12 added last)
        #                      \-> b3 (1) -> b4 (2)
        tip(5)
        b12 = block(12, spend=out3)
        save_spendable_output()
        #yield TestInstance([[b12, False]])
        b13 = block(13, spend=out4)
        # Deliver the block header for b12, and the block b13.
        # b13 should be accepted but the tip won't advance until b12 is delivered.
        yield TestInstance([[CBlockHeader(b12), None], [b13, False]])

        save_spendable_output()
        out5 = get_spendable_output()
        # b14 is invalid, but the node won't know that until it tries to connect
        # Tip still can't advance because b12 is missing
        block(14, spend=out5, additional_coinbase_value=1)
        yield rejected()

        yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13.

        # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6)
        #                      \-> b3 (1) -> b4 (2)
        
        # Test that a block with a lot of checksigs is okay
        lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50 - 1))
        tip(13)
        block(15, spend=out5, script=lots_of_checksigs)
        yield accepted()


        # Test that a block with too many checksigs is rejected
        out6 = get_spendable_output()
        too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50))
        block(16, spend=out6, script=too_many_checksigs)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))


        # Attempt to spend a transaction created on a different fork
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1])
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        block(17, spend=txout_b3)
        yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))

        # Attempt to spend a transaction created on a different fork (on a fork this time)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
        #                                                                \-> b18 (b3.vtx[1]) -> b19 (6)
        #                      \-> b3 (1) -> b4 (2)
        tip(13)
        block(18, spend=txout_b3)
        yield rejected()

        block(19, spend=out6)
        yield rejected()

        # Attempt to spend a coinbase at depth too low
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        out7 = get_spendable_output()
        block(20, spend=out7)
        yield rejected(RejectResult(16, b'bad-txns-premature-spend-of-coinbase'))

        # Attempt to spend a coinbase at depth too low (on a fork this time)
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
        #                                                                \-> b21 (6) -> b22 (5)
        #                      \-> b3 (1) -> b4 (2)
        tip(13)
        block(21, spend=out6)
        yield rejected()

        block(22, spend=out5)
        yield rejected()

        # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6)
        #                                                                           \-> b24 (6) -> b25 (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        b23 = block(23, spend=out6)
        old_hash = b23.sha256
        tx = CTransaction()
        script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69
        script_output = CScript([b'\x00' * script_length])
        tx.vout.append(CTxOut(0, script_output))
        tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 1)))
        b23 = update_block(23, [tx])
        # Make sure the math above worked out to produce a max-sized block
        assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE)
        yield accepted()

        # Make the next block one byte bigger and check that it fails
        tip(15)
        b24 = block(24, spend=out6)
        script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69
        script_output = CScript([b'\x00' * (script_length+1)])
        tx.vout = [CTxOut(0, script_output)]
        b24 = update_block(24, [tx])
        assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE+1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        b25 = block(25, spend=out7)
        yield rejected()

        # Create blocks with a coinbase input script size out of range
        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7)
        #                                                                           \-> ... (6) -> ... (7)
        #                      \-> b3 (1) -> b4 (2)
        tip(15)
        b26 = block(26, spend=out6)
        b26.vtx[0].vin[0].scriptSig = b'\x00'
        b26.vtx[0].rehash()
        # update_block causes the merkle root to get updated, even with no new
        # transactions, and updates the required state.
        b26 = update_block(26, [])
        yield rejected(RejectResult(16, b'bad-cb-length'))

        # Extend the b26 chain to make sure bitcreditd isn't accepting b26
        b27 = block(27, spend=out7)
        yield rejected()

        # Now try a too-large-coinbase script
        tip(15)
        b28 = block(28, spend=out6)
        b28.vtx[0].vin[0].scriptSig = b'\x00' * 101
        b28.vtx[0].rehash()
        b28 = update_block(28, [])
        yield rejected(RejectResult(16, b'bad-cb-length'))

        # Extend the b28 chain to make sure bitcreditd isn't accepted b28
        b29 = block(29, spend=out7)
        # TODO: Should get a reject message back with "bad-prevblk", except
        # there's a bug that prevents this from being detected.  Just note
        # failure for now, and add the reject result later.
        yield rejected()

        # b30 has a max-sized coinbase scriptSig.
        tip(23)
        b30 = block(30)
        b30.vtx[0].vin[0].scriptSig = b'\x00' * 100
        b30.vtx[0].rehash()
        b30 = update_block(30, [])
        yield accepted()
Esempio n. 32
0
    def create_unsigned_pos_block(self,
                                  staking_prevouts,
                                  nTime=None,
                                  outNValue=10002,
                                  signStakeTx=True,
                                  bestBlockHash=None,
                                  coinStakePrevout=None):
        if not nTime:
            current_time = int(time.time()) + 15
            nTime = current_time & 0xfffffff0

        if not bestBlockHash:
            bestBlockHash = self.node.getbestblockhash()
            block_height = self.node.getblockcount()
        else:
            block_height = self.node.getblock(bestBlockHash)['height']

        parent_block_stake_modifier = int(
            self.node.getblock(bestBlockHash)['modifier'], 16)
        parent_block_raw_hex = self.node.getblock(bestBlockHash, False)
        f = io.BytesIO(hex_str_to_bytes(parent_block_raw_hex))
        parent_block = CBlock()
        parent_block.deserialize(f)
        coinbase = create_coinbase(block_height + 1)
        coinbase.vout[0].nValue = 0
        coinbase.vout[0].scriptPubKey = b""
        coinbase.rehash()
        block = create_block(int(bestBlockHash, 16), coinbase, nTime)
        block.hashPrevBlock = int(bestBlockHash, 16)
        if not block.solve_stake(parent_block_stake_modifier,
                                 staking_prevouts):
            return None

        # create a new private key used for block signing.
        block_sig_key = CECKey()
        block_sig_key.set_secretbytes(hash256(struct.pack('<I', 0xffff)))
        pubkey = block_sig_key.get_pubkey()
        scriptPubKey = CScript([pubkey, OP_CHECKSIG])
        stake_tx_unsigned = CTransaction()

        if not coinStakePrevout:
            coinStakePrevout = block.prevoutStake

        stake_tx_unsigned.vin.append(CTxIn(coinStakePrevout))
        stake_tx_unsigned.vout.append(CTxOut())
        stake_tx_unsigned.vout.append(
            CTxOut(int(outNValue * COIN), scriptPubKey))
        stake_tx_unsigned.vout.append(
            CTxOut(int(outNValue * COIN), scriptPubKey))

        if signStakeTx:
            stake_tx_signed_raw_hex = self.node.signrawtransaction(
                bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']
            f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
            stake_tx_signed = CTransaction()
            stake_tx_signed.deserialize(f)
            block.vtx.append(stake_tx_signed)
        else:
            block.vtx.append(stake_tx_unsigned)
        block.hashMerkleRoot = block.calc_merkle_root()
        return (block, block_sig_key)
class FullBlockTest(ComparisonTestFramework):

    # Can either run this test as 1 node with expected answers, or two and compare them.
    # Change the "outcome" variable from each TestInstance object to only do
    # the comparison.

    def __init__(self):
        super().__init__()
        self.num_nodes = 1
        self.block_heights = {}
        self.coinbase_key = CECKey()
        self.coinbase_key.set_secretbytes(b"fatstacks")
        self.coinbase_pubkey = self.coinbase_key.get_pubkey()
        self.tip = None
        self.blocks = {}
        self.excessive_block_size = 16 * ONE_MEGABYTE
        self.extra_args = [['-norelaypriority',
                            '-whitelist=127.0.0.1',
                            '-limitancestorcount=9999',
                            '-limitancestorsize=9999',
                            '-limitdescendantcount=9999',
                            '-limitdescendantsize=9999',
                            '-maxmempool=999',
                            "-excessiveblocksize=%d"
                            % self.excessive_block_size]]

    def add_options(self, parser):
        super().add_options(parser)
        parser.add_option(
            "--runbarelyexpensive", dest="runbarelyexpensive", default=True)

    def run_test(self):
        self.test = TestManager(self, self.options.tmpdir)
        self.test.add_all_connections(self.nodes)
        # Start up network handling in another thread
        NetworkThread().start()
        # Set the blocksize to 2MB as initial condition
        self.nodes[0].setexcessiveblock(self.excessive_block_size)
        self.test.run()

    def add_transactions_to_block(self, block, tx_list):
        [tx.rehash() for tx in tx_list]
        block.vtx.extend(tx_list)

    # this is a little handier to use than the version in blocktools.py
    def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = create_transaction(spend_tx, n, b"", value, script)
        return tx

    # sign a transaction, using the key we know about
    # this signs input 0 in tx, which is assumed to be spending output n in
    # spend_tx
    def sign_tx(self, tx, spend_tx, n):
        scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
        if (scriptPubKey[0] == OP_TRUE):  # an anyone-can-spend
            tx.vin[0].scriptSig = CScript()
            return
        sighash = SignatureHashForkId(
            spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue)
        tx.vin[0].scriptSig = CScript(
            [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])

    def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])):
        tx = self.create_tx(spend_tx, n, value, script)
        self.sign_tx(tx, spend_tx, n)
        tx.rehash()
        return tx

    def next_block(self, number, spend=None, additional_coinbase_value=0, script=None, extra_sigops=0, block_size=0, solve=True):
        """
        Create a block on top of self.tip, and advance self.tip to point to the new block
        if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend
        output, and rest will go to fees.
        """
        if self.tip == None:
            base_block_hash = self.genesis_hash
            block_time = int(time.time()) + 1
        else:
            base_block_hash = self.tip.sha256
            block_time = self.tip.nTime + 1
        # First create the coinbase
        height = self.block_heights[base_block_hash] + 1
        coinbase = create_coinbase(height, self.coinbase_pubkey)
        coinbase.vout[0].nValue += additional_coinbase_value
        if (spend != None):
            coinbase.vout[0].nValue += spend.tx.vout[
                spend.n].nValue - 1  # all but one satoshi to fees
        coinbase.rehash()
        block = create_block(base_block_hash, coinbase, block_time)
        spendable_output = None
        if (spend != None):
            tx = CTransaction()
            # no signature yet
            tx.vin.append(
                CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff))
            # We put some random data into the first transaction of the chain
            # to randomize ids
            tx.vout.append(
                CTxOut(0, CScript([random.randint(0, 255), OP_DROP, OP_TRUE])))
            if script == None:
                tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
            else:
                tx.vout.append(CTxOut(1, script))
            spendable_output = PreviousSpendableOutput(tx, 0)

            # Now sign it if necessary
            scriptSig = b""
            scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
            if (scriptPubKey[0] == OP_TRUE):  # looks like an anyone-can-spend
                scriptSig = CScript([OP_TRUE])
            else:
                # We have to actually sign it
                sighash = SignatureHashForkId(
                    spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend.tx.vout[spend.n].nValue)
                scriptSig = CScript(
                    [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
            tx.vin[0].scriptSig = scriptSig
            # Now add the transaction to the block
            self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
        if spendable_output != None and block_size > 0:
            while len(block.serialize()) < block_size:
                tx = CTransaction()
                script_length = block_size - len(block.serialize()) - 79
                if script_length > 510000:
                    script_length = 500000
                tx_sigops = min(
                    extra_sigops, script_length, MAX_TX_SIGOPS_COUNT)
                extra_sigops -= tx_sigops
                script_pad_len = script_length - tx_sigops
                script_output = CScript(
                    [b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops)
                tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
                tx.vout.append(CTxOut(0, script_output))
                tx.vin.append(
                    CTxIn(COutPoint(spendable_output.tx.sha256, spendable_output.n)))
                spendable_output = PreviousSpendableOutput(tx, 0)
                self.add_transactions_to_block(block, [tx])
            block.hashMerkleRoot = block.calc_merkle_root()
            # Make sure the math above worked out to produce the correct block size
            # (the math will fail if there are too many transactions in the block)
            assert_equal(len(block.serialize()), block_size)
            # Make sure all the requested sigops have been included
            assert_equal(extra_sigops, 0)
        if solve:
            block.solve()
        self.tip = block
        self.block_heights[block.sha256] = height
        assert number not in self.blocks
        self.blocks[number] = block
        return block

    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            block = self.blocks[block_number]
            self.add_transactions_to_block(block, new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # Let's build some blocks and test them.
        for i in range(16):
            n = i + 1
            block(n, spend=out[i], block_size=n * ONE_MEGABYTE)
            yield accepted()

        # block of maximal size
        block(17, spend=out[16], block_size=self.excessive_block_size)
        yield accepted()

        # Reject oversized blocks with bad-blk-length error
        block(18, spend=out[17], block_size=self.excessive_block_size + 1)
        yield rejected(RejectResult(16, b'bad-blk-length'))

        # Rewind bad block.
        tip(17)

        # Accept many sigops
        lots_of_checksigs = CScript(
            [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB - 1))
        block(
            19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE)
        yield accepted()

        too_many_blk_checksigs = CScript(
            [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB)
        block(
            20, spend=out[18], script=too_many_blk_checksigs, block_size=ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(19)

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(21, spend=out[18], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 40k sigops per block > 1MB and <= 2MB
        block(22, spend=out[19], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(23, spend=out[20], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(24, spend=out[20], script=lots_of_checksigs,
              extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(22)

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1)
        yield accepted()

        # Accept 60k sigops per block > 2MB and <= 3MB
        block(26, spend=out[21], script=lots_of_checksigs,
              extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE)
        yield accepted()

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Reject more than 40k sigops per block > 1MB and <= 2MB.
        block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 *
              MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE)
        yield rejected(RejectResult(16, b'bad-blk-sigops'))

        # Rewind bad block
        tip(26)

        # Too many sigops in one txn
        too_many_tx_checksigs = CScript(
            [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1))
        block(
            29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1)
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(26)

        # P2SH
        # Build the redeem script, hash it, use hash to create the p2sh script
        redeem_script = CScript([self.coinbase_pubkey] + [
                                OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG])
        redeem_script_hash = hash160(redeem_script)
        p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])

        # Create a p2sh transaction
        p2sh_tx = self.create_and_sign_transaction(
            out[22].tx, out[22].n, 1, p2sh_script)

        # Add the transaction to the block
        block(30)
        update_block(30, [p2sh_tx])
        yield accepted()

        # Creates a new transaction using the p2sh transaction included in the
        # last block
        def spend_p2sh_tx(output_script=CScript([OP_TRUE])):
            # Create the transaction
            spent_p2sh_tx = CTransaction()
            spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b''))
            spent_p2sh_tx.vout.append(CTxOut(1, output_script))
            # Sign the transaction using the redeem script
            sighash = SignatureHashForkId(
                redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue)
            sig = self.coinbase_key.sign(sighash) + bytes(
                bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
            spent_p2sh_tx.rehash()
            return spent_p2sh_tx

        # Sigops p2sh limit
        p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \
            redeem_script.GetSigOpCount(True)
        # Too many sigops in one p2sh txn
        too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1))
        block(31, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)])
        yield rejected(RejectResult(16, b'bad-txn-sigops'))

        # Rewind bad block
        tip(30)

        # Max sigops in one p2sh txn
        max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit))
        block(32, spend=out[23], block_size=ONE_MEGABYTE + 1)
        update_block(32, [spend_p2sh_tx(max_p2sh_sigops)])
        yield accepted()

        # Check that compact block also work for big blocks
        node = self.nodes[0]
        peer = TestNode()
        peer.add_connection(NodeConn('127.0.0.1', p2p_port(0), node, peer))

        # Start up network handling in another thread and wait for connection
        # to be etablished
        NetworkThread().start()
        peer.wait_for_verack()

        # Wait for SENDCMPCT
        def received_sendcmpct():
            return (peer.last_sendcmpct != None)
        got_sendcmpt = wait_until(received_sendcmpct, timeout=30)
        assert(got_sendcmpt)

        sendcmpct = msg_sendcmpct()
        sendcmpct.version = 1
        sendcmpct.announce = True
        peer.send_and_ping(sendcmpct)

        # Exchange headers
        def received_getheaders():
            return (peer.last_getheaders != None)
        got_getheaders = wait_until(received_getheaders, timeout=30)
        assert(got_getheaders)

        # Return the favor
        peer.send_message(peer.last_getheaders)

        # Wait for the header list
        def received_headers():
            return (peer.last_headers != None)
        got_headers = wait_until(received_headers, timeout=30)
        assert(got_headers)

        # It's like we know about the same headers !
        peer.send_message(peer.last_headers)

        # Send a block
        b33 = block(33, spend=out[24], block_size=ONE_MEGABYTE + 1)
        yield accepted()

        # Checks the node to forward it via compact block
        def received_block():
            return (peer.last_cmpctblock != None)
        got_cmpctblock = wait_until(received_block, timeout=30)
        assert(got_cmpctblock)

        # Was it our block ?
        cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header
        cmpctblk_header.calc_sha256()
        assert(cmpctblk_header.sha256 == b33.sha256)

        # Send a bigger block
        peer.clear_block_data()
        b34 = block(34, spend=out[25], block_size=8 * ONE_MEGABYTE)
        yield accepted()

        # Checks the node to forward it via compact block
        got_cmpctblock = wait_until(received_block, timeout=30)
        assert(got_cmpctblock)

        # Was it our block ?
        cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header
        cmpctblk_header.calc_sha256()
        assert(cmpctblk_header.sha256 == b34.sha256)

        # Let's send a compact block and see if the node accepts it.
        # First, we generate the block and send all transaction to the mempool
        b35 = block(35, spend=out[26], block_size=8 * ONE_MEGABYTE)
        for i in range(1, len(b35.vtx)):
            node.sendrawtransaction(ToHex(b35.vtx[i]), True)

        # Now we create the compact block and send it
        comp_block = HeaderAndShortIDs()
        comp_block.initialize_from_block(b35)
        peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))

        # Check that compact block is received properly
        assert(int(node.getbestblockhash(), 16) == b35.sha256)
Esempio n. 34
0
    def get_tests(self):
        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
        self.block_heights[self.genesis_hash] = 0
        spendable_outputs = []

        # save the current tip so it can be spent by a later block
        def save_spendable_output():
            spendable_outputs.append(self.tip)

        # get an output that we previously marked as spendable
        def get_spendable_output():
            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)

        # returns a test case that asserts that the current tip was accepted
        def accepted():
            return TestInstance([[self.tip, True]])

        # returns a test case that asserts that the current tip was rejected
        def rejected(reject=None):
            if reject is None:
                return TestInstance([[self.tip, False]])
            else:
                return TestInstance([[self.tip, reject]])

        # move the tip back to a previous block
        def tip(number):
            self.tip = self.blocks[number]

        # adds transactions to the block and updates state
        def update_block(block_number, new_transactions):
            [tx.rehash() for tx in new_transactions]
            block = self.blocks[block_number]
            block.vtx.extend(new_transactions)
            old_sha256 = block.sha256
            block.hashMerkleRoot = block.calc_merkle_root()
            block.solve()
            # Update the internal state just like in next_block
            self.tip = block
            if block.sha256 != old_sha256:
                self.block_heights[
                    block.sha256] = self.block_heights[old_sha256]
                del self.block_heights[old_sha256]
            self.blocks[block_number] = block
            return block

        # shorthand for functions
        block = self.next_block
        node = self.nodes[0]

        # Create a new block
        block(0)
        save_spendable_output()
        yield accepted()

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

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

        # Generate a key pair to test P2SH sigops count
        private_key = CECKey()
        private_key.set_secretbytes(b"replayprotection")
        public_key = private_key.get_pubkey()

        # This is a little handier to use than the version in blocktools.py
        def create_fund_and_spend_tx(spend, forkvalue=0):
            # Fund transaction
            script = CScript([public_key, OP_CHECKSIG])
            txfund = create_transaction(spend.tx, spend.n, b'', 50 * COIN,
                                        script)
            txfund.rehash()

            # Spend transaction
            txspend = CTransaction()
            txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE])))
            txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b''))

            # Sign the transaction
            sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID
            sighash = SignatureHashForkId(script, txspend, 0, sighashtype,
                                          50 * COIN)
            sig = private_key.sign(sighash) + \
                bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
            txspend.vin[0].scriptSig = CScript([sig])
            txspend.rehash()

            return [txfund, txspend]

        def send_transaction_to_mempool(tx):
            tx_id = node.sendrawtransaction(ToHex(tx))
            assert (tx_id in set(node.getrawmempool()))
            return tx_id

        # Before the fork, no replay protection required to get in the mempool.
        txns = create_fund_and_spend_tx(out[0])
        send_transaction_to_mempool(txns[0])
        send_transaction_to_mempool(txns[1])

        # And txns get mined in a block properly.
        block(1)
        update_block(1, txns)
        yield accepted()

        # Replay protected transactions are rejected.
        replay_txns = create_fund_and_spend_tx(out[1], 0xffdead)
        send_transaction_to_mempool(replay_txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        # And block containing them are rejected as well.
        block(2)
        update_block(2, replay_txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(1)

        # Create a block that would activate the replay protection.
        bfork = block(5555)
        bfork.nTime = REPLAY_PROTECTION_START_TIME - 1
        update_block(5555, [])
        yield accepted()

        for i in range(5):
            block(5100 + i)
            test.blocks_and_transactions.append([self.tip, True])
        yield test

        # Check we are just before the activation time
        assert_equal(
            node.getblockheader(node.getbestblockhash())['mediantime'],
            REPLAY_PROTECTION_START_TIME - 1)

        # We are just before the fork, replay protected txns still are rejected
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(replay_txns[1]))

        block(3)
        update_block(3, replay_txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(5104)

        # Send some non replay protected txns in the mempool to check
        # they get cleaned at activation.
        txns = create_fund_and_spend_tx(out[2])
        send_transaction_to_mempool(txns[0])
        tx_id = send_transaction_to_mempool(txns[1])

        # Activate the replay protection
        block(5556)
        yield accepted()

        # Non replay protected transactions are not valid anymore,
        # so they should be removed from the mempool.
        assert (tx_id not in set(node.getrawmempool()))

        # Good old transactions are now invalid.
        send_transaction_to_mempool(txns[0])
        assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR,
                                node.sendrawtransaction, ToHex(txns[1]))

        # They also cannot be mined
        block(4)
        update_block(4, txns)
        yield rejected(RejectResult(16, b'blk-bad-inputs'))

        # Rewind bad block
        tip(5556)

        # The replay protected transaction is now valid
        send_transaction_to_mempool(replay_txns[0])
        replay_tx_id = send_transaction_to_mempool(replay_txns[1])

        # They also can also be mined
        b5 = block(5)
        update_block(5, replay_txns)
        yield accepted()

        # Ok, now we check if a reorg work properly accross the activation.
        postforkblockid = node.getbestblockhash()
        node.invalidateblock(postforkblockid)
        assert (replay_tx_id in set(node.getrawmempool()))

        # Deactivating replay protection.
        forkblockid = node.getbestblockhash()
        node.invalidateblock(forkblockid)
        assert (replay_tx_id not in set(node.getrawmempool()))

        # Check that we also do it properly on deeper reorg.
        node.reconsiderblock(forkblockid)
        node.reconsiderblock(postforkblockid)
        node.invalidateblock(forkblockid)
        assert (replay_tx_id not in set(node.getrawmempool()))