Example #1
0
    def test_anc_count_limits_bushy(self):
        """Create a tree with 20 transactions in the mempool and 6 in the package:
        M1...M4 M5...M8 M9...M12 M13...M16 M17...M20
            ^      ^       ^        ^         ^             (each with 4 parents)
            P0     P1      P2      P3        P4
             ^     ^       ^       ^         ^              (5 parents)
                           PC
        Where M(4i+1)...M+(4i+4) are the parents of Pi and P0, P1, P2, P3, and P4 are the parents of PC.
        P0... P4 individually only have 4 parents each, and PC has no in-mempool parents. But
        combined, PC has 25 in-mempool and in-package parents.
        """
        node = self.nodes[0]
        assert_equal(0, node.getmempoolinfo()["size"])
        package_hex = []
        parent_txns = []
        parent_values = []
        scripts = []
        for _ in range(5):  # Make package transactions P0 ... P4
            gp_tx = []
            gp_values = []
            gp_scripts = []
            for _ in range(4):  # Make mempool transactions M(4i+1)...M(4i+4)
                parent_coin = self.coins.pop()
                value = parent_coin["amount"]
                txid = parent_coin["txid"]
                (tx, txhex, value, spk) = make_chain(node, self.address,
                                                     self.privkeys, txid,
                                                     value)
                gp_tx.append(tx)
                gp_values.append(value)
                gp_scripts.append(spk)
                node.sendrawtransaction(txhex)
            # Package transaction Pi
            pi_hex = create_child_with_parents(node, self.address,
                                               self.privkeys, gp_tx, gp_values,
                                               gp_scripts)
            package_hex.append(pi_hex)
            pi_tx = tx_from_hex(pi_hex)
            parent_txns.append(pi_tx)
            parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN)
            scripts.append(pi_tx.vout[0].scriptPubKey.hex())
        # Package transaction PC
        package_hex.append(
            create_child_with_parents(node, self.address, self.privkeys,
                                      parent_txns, parent_values, scripts))

        assert_equal(20, node.getmempoolinfo()["size"])
        assert_equal(6, len(package_hex))
        testres = node.testmempoolaccept(rawtxs=package_hex)
        for txres in testres:
            assert_equal(txres["package-error"], "package-mempool-limits")

        # Clear mempool and check that the package passes now
        self.generate(node, 1)
        assert all([
            res["allowed"]
            for res in node.testmempoolaccept(rawtxs=package_hex)
        ])
Example #2
0
    def test_multiple_parents(self):
        node = self.nodes[0]

        self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package")
        for num_parents in [2, 10, 24]:
            # Test a package with num_parents parents and 1 child transaction.
            package_hex = []
            parents_tx = []
            values = []
            parent_locking_scripts = []
            for _ in range(num_parents):
                parent_coin = self.coins.pop()
                value = parent_coin["amount"]
                (tx, txhex, value, parent_locking_script) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
                package_hex.append(txhex)
                parents_tx.append(tx)
                values.append(value)
                parent_locking_scripts.append(parent_locking_script)
            child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, parent_locking_scripts)
            # Package accept should work with the parents in any order (as long as parents come before child)
            for _ in range(10):
                random.shuffle(package_hex)
                testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_hex])
                assert all([testres["allowed"] for testres in testres_multiple])

            testres_single = []
            # Test accept and then submit each one individually, which should be identical to package testaccept
            for rawtx in package_hex + [child_hex]:
                testres_single.append(node.testmempoolaccept([rawtx])[0])
                # Submit the transaction now so its child should have no problem validating
                node.sendrawtransaction(rawtx)
            assert_equal(testres_single, testres_multiple)
    def test_anc_size_limits(self):
        """Test Case with 2 independent transactions in the mempool and a parent + child in the
        package, where the package parent is the child of both mempool transactions (30KvB each):
              A     B
               ^   ^
                 C
                 ^
                 D
        The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool
        and in-package ancestors are all considered together.
        """
        node = self.nodes[0]
        assert_equal(0, node.getmempoolinfo()["size"])
        parents_tx = []
        values = []
        scripts = []
        target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB
        high_fee = Decimal("0.003") # 10 sats/vB
        self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
        # Mempool transactions A and B
        for _ in range(2):
            spk = None
            top_coin = self.coins.pop()
            txid = top_coin["txid"]
            value = top_coin["amount"]
            (tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
            bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys)
            node.sendrawtransaction(bulked_tx.serialize().hex())
            parents_tx.append(bulked_tx)
            values.append(Decimal(bulked_tx.vout[0].nValue) / COIN)
            scripts.append(bulked_tx.vout[0].scriptPubKey.hex())

        # Package transaction C
        small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee)
        pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys)
        pc_value = Decimal(pc_tx.vout[0].nValue) / COIN
        pc_spk = pc_tx.vout[0].scriptPubKey.hex()
        pc_hex = pc_tx.serialize().hex()

        # Package transaction D
        (small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee)
        prevtxs = [{
            "txid": pc_tx.rehash(),
            "vout": 0,
            "scriptPubKey": spk,
            "amount": val,
        }]
        pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs)
        pd_hex = pd_tx.serialize().hex()

        assert_equal(2, node.getmempoolinfo()["size"])
        testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
        for txres in testres_too_heavy:
            assert_equal(txres["package-error"], "package-mempool-limits")

        # Clear mempool and check that the package passes now
        node.generate(1)
        assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
Example #4
0
    def test_submit_child_with_parents(self, num_parents, partial_submit):
        node = self.nodes[0]
        peer = node.add_p2p_connection(P2PTxInvStore())
        # Test a package with num_parents parents and 1 child transaction.
        package_hex = []
        package_txns = []
        values = []
        scripts = []
        for _ in range(num_parents):
            parent_coin = self.coins.pop()
            value = parent_coin["amount"]
            (tx, txhex, value, spk) = make_chain(node, self.address,
                                                 self.privkeys,
                                                 parent_coin["txid"], value)
            package_hex.append(txhex)
            package_txns.append(tx)
            values.append(value)
            scripts.append(spk)
            if partial_submit and random.choice([True, False]):
                node.sendrawtransaction(txhex)
        child_hex = create_child_with_parents(node, self.address,
                                              self.privkeys, package_txns,
                                              values, scripts)
        package_hex.append(child_hex)
        package_txns.append(tx_from_hex(child_hex))

        testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex)
        submitpackage_result = node.submitpackage(package=package_hex)

        # Check that each result is present, with the correct size and fees
        for i in range(num_parents + 1):
            tx = package_txns[i]
            wtxid = tx.getwtxid()
            assert wtxid in submitpackage_result["tx-results"]
            tx_result = submitpackage_result["tx-results"][wtxid]
            assert_equal(
                tx_result, {
                    "txid": tx.rehash(),
                    "vsize": tx.get_vsize(),
                    "fees": {
                        "base": DEFAULT_FEE,
                    }
                })

        # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
        self.assert_equal_package_results(node, testmempoolaccept_result,
                                          submitpackage_result)

        # Package feerate is calculated for the remaining transactions after deduplication and
        # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
        # had high-feerates or were already in the mempool, no package feerate is provided.
        # In this case, since all of the parents have high fees, each is accepted individually.
        assert "package-feerate" not in submitpackage_result

        # The node should announce each transaction. No guarantees for propagation.
        peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
        self.generate(node, 1)
    def test_anc_count_limits_2(self):
        """Create a 'Y' shaped chain with 24 transactions in the mempool and 2 in the package:
        M1a                M1b
         ^                ^
          M2a            M2b
           .            .
            .          .
             .        .
            M12a    M12b
               ^    ^
                 Pc
                 ^
                 Pd
        The lowest descendant, Pd, exceeds ancestor limits, but only if the in-mempool
        and in-package ancestors are all considered together.
        """
        node = self.nodes[0]
        assert_equal(0, node.getmempoolinfo()["size"])
        parents_tx = []
        values = []
        scripts = []

        self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
        # Two chains of 12 transactions each
        for _ in range(2):
            spk = None
            top_coin = self.coins.pop()
            txid = top_coin["txid"]
            value = top_coin["amount"]
            for i in range(12):
                (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
                txid = tx.rehash()
                value -= Decimal("0.0001")
                node.sendrawtransaction(txhex)
                if i == 11:
                    # last 2 transactions will be the parents of Pc
                    parents_tx.append(tx)
                    values.append(value)
                    scripts.append(spk)

        # Child Pc
        pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
        pc_tx = tx_from_hex(pc_hex)
        pc_value = sum(values) - Decimal("0.0002")
        pc_spk = pc_tx.vout[0].scriptPubKey.hex()

        # Child Pd
        (_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk)

        assert_equal(24, node.getmempoolinfo()["size"])
        testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
        for txres in testres_too_long:
            assert_equal(txres["package-error"], "package-mempool-limits")

        # Clear mempool and check that the package passes now
        node.generate(1)
        assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
    def test_anc_count_limits(self):
        """Create a 'V' shaped chain with 24 transactions in the mempool and 3 in the package:
        M1a                    M1b
         ^                     ^
          M2a                M2b
           .                 .
            .               .
             .             .
             M12a        M12b
               ^         ^
                Pa     Pb
                 ^    ^
                   Pc
        The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool
        and in-package ancestors are all considered together.
        """
        node = self.nodes[0]
        assert_equal(0, node.getmempoolinfo()["size"])
        package_hex = []
        parents_tx = []
        values = []
        scripts = []

        self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")

        # Two chains of 13 transactions each
        for _ in range(2):
            spk = None
            top_coin = self.coins.pop()
            txid = top_coin["txid"]
            value = top_coin["amount"]
            for i in range(13):
                (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
                txid = tx.rehash()
                if i < 12:
                    node.sendrawtransaction(txhex)
                else: # Save the 13th transaction for the package
                    package_hex.append(txhex)
                    parents_tx.append(tx)
                    scripts.append(spk)
                    values.append(value)

        # Child Pc
        child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
        package_hex.append(child_hex)

        assert_equal(24, node.getmempoolinfo()["size"])
        assert_equal(3, len(package_hex))
        testres_too_long = node.testmempoolaccept(rawtxs=package_hex)
        for txres in testres_too_long:
            assert_equal(txres["package-error"], "package-mempool-limits")

        # Clear mempool and check that the package passes now
        node.generate(1)
        assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
Example #7
0
    def test_submit_cpfp(self):
        node = self.nodes[0]
        peer = node.add_p2p_connection(P2PTxInvStore())

        # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
        # fee-bumped by the child.
        coin_rich = self.coins.pop()
        coin_poor = self.coins.pop()
        tx_rich, hex_rich, value_rich, spk_rich = make_chain(
            node, self.address, self.privkeys, coin_rich["txid"],
            coin_rich["amount"])
        tx_poor, hex_poor, value_poor, spk_poor = make_chain(
            node,
            self.address,
            self.privkeys,
            coin_poor["txid"],
            coin_poor["amount"],
            fee=0)
        package_txns = [tx_rich, tx_poor]
        hex_child = create_child_with_parents(node, self.address,
                                              self.privkeys, package_txns,
                                              [value_rich, value_poor],
                                              [spk_rich, spk_poor])
        tx_child = tx_from_hex(hex_child)
        package_txns.append(tx_child)

        submitpackage_result = node.submitpackage(
            [hex_rich, hex_poor, hex_child])

        rich_parent_result = submitpackage_result["tx-results"][
            tx_rich.getwtxid()]
        poor_parent_result = submitpackage_result["tx-results"][
            tx_poor.getwtxid()]
        child_result = submitpackage_result["tx-results"][tx_child.getwtxid()]
        assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
        assert_equal(poor_parent_result["fees"]["base"], 0)
        assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
        # Package feerate is calculated for the remaining transactions after deduplication and
        # individual submission. Since this package had a 0-fee parent, package feerate must have
        # been used and returned.
        assert "package-feerate" in submitpackage_result
        assert_fee_amount(DEFAULT_FEE,
                          rich_parent_result["vsize"] + child_result["vsize"],
                          submitpackage_result["package-feerate"])

        # The node will broadcast each transaction, still abiding by its peer's fee filter
        peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
        self.generate(node, 1)