Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mempool_package_limits.py
Show All 18 Lines | |||||
FAR_IN_THE_FUTURE = 2000000000 | FAR_IN_THE_FUTURE = 2000000000 | ||||
class MempoolPackageLimitsTest(BitcoinTestFramework): | class MempoolPackageLimitsTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.extra_args = [[ | self.extra_args = [ | ||||
[ | |||||
# The packages mempool limits are no longer applied after wellington | # The packages mempool limits are no longer applied after wellington | ||||
# activation. | # activation. | ||||
f'-wellingtonactivationtime={FAR_IN_THE_FUTURE}', | f"-wellingtonactivationtime={FAR_IN_THE_FUTURE}", | ||||
]] | ] | ||||
] | |||||
def run_test(self): | def run_test(self): | ||||
self.log.info("Generate blocks to create UTXOs") | self.log.info("Generate blocks to create UTXOs") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
self.privkeys = [node.get_deterministic_priv_key().key] | self.privkeys = [node.get_deterministic_priv_key().key] | ||||
self.address = node.get_deterministic_priv_key().address | self.address = node.get_deterministic_priv_key().address | ||||
self.coins = [] | self.coins = [] | ||||
# The last 100 coinbase transactions are premature | # The last 100 coinbase transactions are premature | ||||
for b in self.generatetoaddress(node, 200, self.address)[:100]: | for b in self.generatetoaddress(node, 200, self.address)[:100]: | ||||
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] | coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] | ||||
self.coins.append({ | self.coins.append( | ||||
{ | |||||
"txid": coinbase["txid"], | "txid": coinbase["txid"], | ||||
"amount": coinbase["vout"][0]["value"], | "amount": coinbase["vout"][0]["value"], | ||||
"scriptPubKey": coinbase["vout"][0]["scriptPubKey"], | "scriptPubKey": coinbase["vout"][0]["scriptPubKey"], | ||||
}) | } | ||||
) | |||||
self.test_chain_limits() | self.test_chain_limits() | ||||
self.test_desc_count_limits() | self.test_desc_count_limits() | ||||
self.test_desc_count_limits_2() | self.test_desc_count_limits_2() | ||||
self.test_anc_count_limits() | self.test_anc_count_limits() | ||||
self.test_anc_count_limits_2() | self.test_anc_count_limits_2() | ||||
self.test_anc_count_limits_bushy() | self.test_anc_count_limits_bushy() | ||||
Show All 9 Lines | def test_chain_limits_helper(self, mempool_count, package_count): | ||||
spk = None | spk = None | ||||
txid = first_coin["txid"] | txid = first_coin["txid"] | ||||
chain_hex = [] | chain_hex = [] | ||||
chain_txns = [] | chain_txns = [] | ||||
value = first_coin["amount"] | value = first_coin["amount"] | ||||
for i in range(mempool_count + package_count): | for i in range(mempool_count + package_count): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
txid = tx.get_id() | txid = tx.get_id() | ||||
if i < mempool_count: | if i < mempool_count: | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
else: | else: | ||||
chain_hex.append(txhex) | chain_hex.append(txhex) | ||||
chain_txns.append(tx) | chain_txns.append(tx) | ||||
testres_too_long = node.testmempoolaccept(rawtxs=chain_hex) | testres_too_long = node.testmempoolaccept(rawtxs=chain_hex) | ||||
for txres in testres_too_long: | for txres in testres_too_long: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=chain_hex)) | res["allowed"] is True for res in node.testmempoolaccept(rawtxs=chain_hex) | ||||
) | |||||
def test_chain_limits(self): | def test_chain_limits(self): | ||||
"""Create chains from mempool and package transactions that are longer than 50, | """Create chains from mempool and package transactions that are longer than 50, | ||||
but only if both in-mempool and in-package transactions are considered together. | but only if both in-mempool and in-package transactions are considered together. | ||||
This checks that both mempool and in-package transactions are taken into account | This checks that both mempool and in-package transactions are taken into account | ||||
when calculating ancestors/descendant limits. | when calculating ancestors/descendant limits. | ||||
""" | """ | ||||
self.log.info( | self.log.info( | ||||
"Check that in-package ancestors count for mempool ancestor limits") | "Check that in-package ancestors count for mempool ancestor limits" | ||||
) | |||||
self.test_chain_limits_helper(mempool_count=49, package_count=2) | self.test_chain_limits_helper(mempool_count=49, package_count=2) | ||||
self.test_chain_limits_helper(mempool_count=2, package_count=49) | self.test_chain_limits_helper(mempool_count=2, package_count=49) | ||||
self.test_chain_limits_helper(mempool_count=26, package_count=26) | self.test_chain_limits_helper(mempool_count=26, package_count=26) | ||||
def test_desc_count_limits(self): | def test_desc_count_limits(self): | ||||
"""Create an 'A' shaped package with 49 transactions in the mempool and 2 in the package: | """Create an 'A' shaped package with 49 transactions in the mempool and 2 in the package: | ||||
M1 | M1 | ||||
^ ^ | ^ ^ | ||||
M2a M2b | M2a M2b | ||||
. . | . . | ||||
. . | . . | ||||
M25a M25b | M25a M25b | ||||
^ ^ | ^ ^ | ||||
Pa Pb | Pa Pb | ||||
The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package | The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package | ||||
descendants are all considered together (49 including in-mempool descendants and 51 including both | descendants are all considered together (49 including in-mempool descendants and 51 including both | ||||
package transactions). | package transactions). | ||||
""" | """ | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
assert_equal(0, node.getmempoolinfo()["size"]) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
self.log.info( | self.log.info( | ||||
"Check that in-mempool and in-package descendants are calculated properly in packages") | "Check that in-mempool and in-package descendants are calculated properly" | ||||
" in packages" | |||||
) | |||||
# Top parent in mempool, M1 | # Top parent in mempool, M1 | ||||
first_coin = self.coins.pop() | first_coin = self.coins.pop() | ||||
# Deduct reasonable fee and make 2 outputs | # Deduct reasonable fee and make 2 outputs | ||||
parent_value = (first_coin["amount"] - Decimal("200.00")) / 2 | parent_value = (first_coin["amount"] - Decimal("200.00")) / 2 | ||||
inputs = [{"txid": first_coin["txid"], "vout": 0}] | inputs = [{"txid": first_coin["txid"], "vout": 0}] | ||||
outputs = [{self.address: parent_value}, | outputs = [ | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}] | {self.address: parent_value}, | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, | |||||
] | |||||
rawtx = node.createrawtransaction(inputs, outputs) | rawtx = node.createrawtransaction(inputs, outputs) | ||||
parent_signed = node.signrawtransactionwithkey( | parent_signed = node.signrawtransactionwithkey( | ||||
hexstring=rawtx, privkeys=self.privkeys) | hexstring=rawtx, privkeys=self.privkeys | ||||
) | |||||
assert parent_signed["complete"] | assert parent_signed["complete"] | ||||
parent_tx = FromHex(CTransaction(), parent_signed["hex"]) | parent_tx = FromHex(CTransaction(), parent_signed["hex"]) | ||||
parent_txid = parent_tx.rehash() | parent_txid = parent_tx.rehash() | ||||
node.sendrawtransaction(parent_signed["hex"]) | node.sendrawtransaction(parent_signed["hex"]) | ||||
package_hex = [] | package_hex = [] | ||||
# Chain A | # Chain A | ||||
spk = parent_tx.vout[0].scriptPubKey.hex() | spk = parent_tx.vout[0].scriptPubKey.hex() | ||||
value = parent_value | value = parent_value | ||||
txid = parent_txid | txid = parent_txid | ||||
for i in range(25): | for i in range(25): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
txid = tx.get_id() | txid = tx.get_id() | ||||
if i < 24: | if i < 24: | ||||
# M2a... M25a | # M2a... M25a | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
else: | else: | ||||
# Pa | # Pa | ||||
package_hex.append(txhex) | package_hex.append(txhex) | ||||
# Chain B | # Chain B | ||||
value = parent_value - Decimal("100.00") | value = parent_value - Decimal("100.00") | ||||
rawtx_b = node.createrawtransaction( | rawtx_b = node.createrawtransaction( | ||||
[{"txid": parent_txid, "vout": 1}], {self.address: value}) | [{"txid": parent_txid, "vout": 1}], {self.address: value} | ||||
) | |||||
# M2b | # M2b | ||||
tx_child_b = FromHex(CTransaction(), rawtx_b) | tx_child_b = FromHex(CTransaction(), rawtx_b) | ||||
tx_child_b.vin[0].scriptSig = SCRIPTSIG_OP_TRUE | tx_child_b.vin[0].scriptSig = SCRIPTSIG_OP_TRUE | ||||
pad_tx(tx_child_b) | pad_tx(tx_child_b) | ||||
tx_child_b_hex = ToHex(tx_child_b) | tx_child_b_hex = ToHex(tx_child_b) | ||||
node.sendrawtransaction(tx_child_b_hex) | node.sendrawtransaction(tx_child_b_hex) | ||||
spk = tx_child_b.vout[0].scriptPubKey.hex() | spk = tx_child_b.vout[0].scriptPubKey.hex() | ||||
txid = tx_child_b.rehash() | txid = tx_child_b.rehash() | ||||
for i in range(24): | for i in range(24): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
txid = tx.get_id() | txid = tx.get_id() | ||||
if i < 23: | if i < 23: | ||||
# M3b... M25b | # M3b... M25b | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
else: | else: | ||||
# Pb | # Pb | ||||
package_hex.append(txhex) | package_hex.append(txhex) | ||||
assert_equal(49, node.getmempoolinfo()["size"]) | assert_equal(49, node.getmempoolinfo()["size"]) | ||||
assert_equal(2, len(package_hex)) | assert_equal(2, len(package_hex)) | ||||
testres_too_long = node.testmempoolaccept(rawtxs=package_hex) | testres_too_long = node.testmempoolaccept(rawtxs=package_hex) | ||||
for txres in testres_too_long: | for txres in testres_too_long: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=package_hex)) | res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) | ||||
) | |||||
def test_desc_count_limits_2(self): | def test_desc_count_limits_2(self): | ||||
"""Create a Package with 49 transactions in mempool and 2 transactions | """Create a Package with 49 transactions in mempool and 2 transactions | ||||
in package: | in package: | ||||
M1 | M1 | ||||
^ ^ | ^ ^ | ||||
M2 ^ | M2 ^ | ||||
. ^ | . ^ | ||||
Show All 10 Lines | def test_desc_count_limits_2(self): | ||||
including itself). | including itself). | ||||
""" | """ | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
package_hex = [] | package_hex = [] | ||||
# M1 | # M1 | ||||
first_coin_a = self.coins.pop() | first_coin_a = self.coins.pop() | ||||
# Deduct reasonable fee and make 2 outputs | # Deduct reasonable fee and make 2 outputs | ||||
parent_value = (first_coin_a["amount"] - Decimal('200.0')) / 2 | parent_value = (first_coin_a["amount"] - Decimal("200.0")) / 2 | ||||
inputs = [{"txid": first_coin_a["txid"], "vout": 0}] | inputs = [{"txid": first_coin_a["txid"], "vout": 0}] | ||||
outputs = [{self.address: parent_value}, | outputs = [ | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}] | {self.address: parent_value}, | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, | |||||
] | |||||
rawtx = node.createrawtransaction(inputs, outputs) | rawtx = node.createrawtransaction(inputs, outputs) | ||||
parent_signed = node.signrawtransactionwithkey( | parent_signed = node.signrawtransactionwithkey( | ||||
hexstring=rawtx, privkeys=self.privkeys) | hexstring=rawtx, privkeys=self.privkeys | ||||
) | |||||
assert parent_signed["complete"] | assert parent_signed["complete"] | ||||
parent_tx = FromHex(CTransaction(), parent_signed["hex"]) | parent_tx = FromHex(CTransaction(), parent_signed["hex"]) | ||||
pad_tx(parent_tx) | pad_tx(parent_tx) | ||||
parent_txid = parent_tx.rehash() | parent_txid = parent_tx.rehash() | ||||
node.sendrawtransaction(parent_signed["hex"]) | node.sendrawtransaction(parent_signed["hex"]) | ||||
# Chain M2...M49 | # Chain M2...M49 | ||||
spk = parent_tx.vout[0].scriptPubKey.hex() | spk = parent_tx.vout[0].scriptPubKey.hex() | ||||
value = parent_value | value = parent_value | ||||
txid = parent_txid | txid = parent_txid | ||||
for _ in range(48): | for _ in range(48): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
pad_tx(tx) | pad_tx(tx) | ||||
txid = tx.hash | txid = tx.hash | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
# P1 | # P1 | ||||
value_p1 = parent_value - Decimal('100') | value_p1 = parent_value - Decimal("100") | ||||
rawtx_p1 = node.createrawtransaction( | rawtx_p1 = node.createrawtransaction( | ||||
[{"txid": parent_txid, "vout": 1}], [{self.address: value_p1}]) | [{"txid": parent_txid, "vout": 1}], [{self.address: value_p1}] | ||||
) | |||||
tx_child_p1 = FromHex(CTransaction(), rawtx_p1) | tx_child_p1 = FromHex(CTransaction(), rawtx_p1) | ||||
tx_child_p1.vin[0].scriptSig = SCRIPTSIG_OP_TRUE | tx_child_p1.vin[0].scriptSig = SCRIPTSIG_OP_TRUE | ||||
pad_tx(tx_child_p1) | pad_tx(tx_child_p1) | ||||
tx_child_p1_hex = tx_child_p1.serialize().hex() | tx_child_p1_hex = tx_child_p1.serialize().hex() | ||||
package_hex.append(tx_child_p1_hex) | package_hex.append(tx_child_p1_hex) | ||||
tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex() | tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex() | ||||
# P2 | # P2 | ||||
(_, tx_child_p2_hex, _, _) = make_chain(node, self.address, | (_, tx_child_p2_hex, _, _) = make_chain( | ||||
self.privkeys, tx_child_p1.hash, value_p1, 0, tx_child_p1_spk) | node, | ||||
self.address, | |||||
self.privkeys, | |||||
tx_child_p1.hash, | |||||
value_p1, | |||||
0, | |||||
tx_child_p1_spk, | |||||
) | |||||
package_hex.append(tx_child_p2_hex) | package_hex.append(tx_child_p2_hex) | ||||
assert_equal(49, node.getmempoolinfo()["size"]) | assert_equal(49, node.getmempoolinfo()["size"]) | ||||
assert_equal(2, len(package_hex)) | assert_equal(2, len(package_hex)) | ||||
testres = node.testmempoolaccept(rawtxs=package_hex) | testres = node.testmempoolaccept(rawtxs=package_hex) | ||||
assert_equal(len(testres), len(package_hex)) | assert_equal(len(testres), len(package_hex)) | ||||
for txres in testres: | for txres in testres: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] | assert all(res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)) | ||||
for res in node.testmempoolaccept(rawtxs=package_hex)) | |||||
def test_anc_count_limits(self): | def test_anc_count_limits(self): | ||||
"""Create a 'V' shaped chain with 49 transactions in the mempool and 3 in the package: | """Create a 'V' shaped chain with 49 transactions in the mempool and 3 in the package: | ||||
M1a | M1a | ||||
^ M1b | ^ M1b | ||||
M2a ^ | M2a ^ | ||||
. M2b | . M2b | ||||
. . | . . | ||||
Show All 10 Lines | def test_anc_count_limits(self): | ||||
assert_equal(0, node.getmempoolinfo()["size"]) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
package_hex = [] | package_hex = [] | ||||
parents_tx = [] | parents_tx = [] | ||||
values = [] | values = [] | ||||
scripts = [] | scripts = [] | ||||
self.log.info( | self.log.info( | ||||
"Check that in-mempool and in-package ancestors are calculated " | "Check that in-mempool and in-package ancestors are calculated " | ||||
"properly in packages") | "properly in packages" | ||||
) | |||||
# Two chains of 26 & 25 transactions | # Two chains of 26 & 25 transactions | ||||
for chain_length in [26, 25]: | for chain_length in [26, 25]: | ||||
spk = None | spk = None | ||||
top_coin = self.coins.pop() | top_coin = self.coins.pop() | ||||
txid = top_coin["txid"] | txid = top_coin["txid"] | ||||
value = top_coin["amount"] | value = top_coin["amount"] | ||||
for i in range(chain_length): | for i in range(chain_length): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
txid = tx.get_id() | txid = tx.get_id() | ||||
if i < chain_length - 1: | if i < chain_length - 1: | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
else: | else: | ||||
# Save the last transaction for the package | # Save the last transaction for the package | ||||
package_hex.append(txhex) | package_hex.append(txhex) | ||||
parents_tx.append(tx) | parents_tx.append(tx) | ||||
scripts.append(spk) | scripts.append(spk) | ||||
values.append(value) | values.append(value) | ||||
# Child Pc | # Child Pc | ||||
child_hex = create_child_with_parents( | child_hex = create_child_with_parents( | ||||
node, self.address, self.privkeys, parents_tx, values, scripts) | node, self.address, self.privkeys, parents_tx, values, scripts | ||||
) | |||||
package_hex.append(child_hex) | package_hex.append(child_hex) | ||||
assert_equal(49, node.getmempoolinfo()["size"]) | assert_equal(49, node.getmempoolinfo()["size"]) | ||||
assert_equal(3, len(package_hex)) | assert_equal(3, len(package_hex)) | ||||
testres_too_long = node.testmempoolaccept(rawtxs=package_hex) | testres_too_long = node.testmempoolaccept(rawtxs=package_hex) | ||||
for txres in testres_too_long: | for txres in testres_too_long: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=package_hex)) | res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) | ||||
) | |||||
def test_anc_count_limits_2(self): | def test_anc_count_limits_2(self): | ||||
"""Create a 'Y' shaped chain with 49 transactions in the mempool and 2 in the package: | """Create a 'Y' shaped chain with 49 transactions in the mempool and 2 in the package: | ||||
M1a | M1a | ||||
^ M1b | ^ M1b | ||||
M2a ^ | M2a ^ | ||||
. M2b | . M2b | ||||
. . | . . | ||||
. . | . . | ||||
M25a M24b | M25a M24b | ||||
^ ^ | ^ ^ | ||||
Pc | Pc | ||||
^ | ^ | ||||
Pd | Pd | ||||
The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool | The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool | ||||
and in-package ancestors are all considered together. | and in-package ancestors are all considered together. | ||||
""" | """ | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
assert_equal(0, node.getmempoolinfo()["size"]) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
parents_tx = [] | parents_tx = [] | ||||
values = [] | values = [] | ||||
scripts = [] | scripts = [] | ||||
self.log.info( | self.log.info( | ||||
"Check that in-mempool and in-package ancestors are calculated properly in packages") | "Check that in-mempool and in-package ancestors are calculated properly in" | ||||
" packages" | |||||
) | |||||
# Two chains of 25 & 24 transactions | # Two chains of 25 & 24 transactions | ||||
for chain_length in [25, 24]: | for chain_length in [25, 24]: | ||||
spk = None | spk = None | ||||
top_coin = self.coins.pop() | top_coin = self.coins.pop() | ||||
txid = top_coin["txid"] | txid = top_coin["txid"] | ||||
value = top_coin["amount"] | value = top_coin["amount"] | ||||
for i in range(chain_length): | for i in range(chain_length): | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk) | node, self.address, self.privkeys, txid, value, 0, spk | ||||
) | |||||
txid = tx.get_id() | txid = tx.get_id() | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
if i == chain_length - 1: | if i == chain_length - 1: | ||||
# last 2 transactions will be the parents of Pc | # last 2 transactions will be the parents of Pc | ||||
parents_tx.append(tx) | parents_tx.append(tx) | ||||
values.append(value) | values.append(value) | ||||
scripts.append(spk) | scripts.append(spk) | ||||
# Child Pc | # Child Pc | ||||
pc_hex = create_child_with_parents( | pc_hex = create_child_with_parents( | ||||
node, self.address, self.privkeys, parents_tx, values, scripts) | node, self.address, self.privkeys, parents_tx, values, scripts | ||||
) | |||||
pc_tx = FromHex(CTransaction(), pc_hex) | pc_tx = FromHex(CTransaction(), pc_hex) | ||||
pc_value = sum(values) - Decimal("100.00") | pc_value = sum(values) - Decimal("100.00") | ||||
pc_spk = pc_tx.vout[0].scriptPubKey.hex() | pc_spk = pc_tx.vout[0].scriptPubKey.hex() | ||||
# Child Pd | # Child Pd | ||||
(_, pd_hex, _, _) = make_chain( | (_, pd_hex, _, _) = make_chain( | ||||
node, self.address, self.privkeys, pc_tx.get_id(), pc_value, 0, pc_spk) | node, self.address, self.privkeys, pc_tx.get_id(), pc_value, 0, pc_spk | ||||
) | |||||
assert_equal(49, node.getmempoolinfo()["size"]) | assert_equal(49, node.getmempoolinfo()["size"]) | ||||
testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | ||||
for txres in testres_too_long: | for txres in testres_too_long: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])) | res["allowed"] is True | ||||
for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | |||||
) | |||||
def test_anc_count_limits_bushy(self): | def test_anc_count_limits_bushy(self): | ||||
"""Create a tree with 45 transactions in the mempool and 6 in the package: | """Create a tree with 45 transactions in the mempool and 6 in the package: | ||||
M1...M9 M10...M18 M19...M27 M28...M36 M37...M45 | M1...M9 M10...M18 M19...M27 M28...M36 M37...M45 | ||||
^ ^ ^ ^ ^ (each with 9 parents) | ^ ^ ^ ^ ^ (each with 9 parents) | ||||
P0 P1 P2 P3 P4 | P0 P1 P2 P3 P4 | ||||
^ ^ ^ ^ ^ (5 parents) | ^ ^ ^ ^ ^ (5 parents) | ||||
PC | PC | ||||
Show All 13 Lines | def test_anc_count_limits_bushy(self): | ||||
gp_values = [] | gp_values = [] | ||||
gp_scripts = [] | gp_scripts = [] | ||||
# Make mempool transactions M(9i+1)...M(9i+9) | # Make mempool transactions M(9i+1)...M(9i+9) | ||||
for _ in range(9): | for _ in range(9): | ||||
parent_coin = self.coins.pop() | parent_coin = self.coins.pop() | ||||
value = parent_coin["amount"] | value = parent_coin["amount"] | ||||
txid = parent_coin["txid"] | txid = parent_coin["txid"] | ||||
(tx, txhex, value, spk) = make_chain( | (tx, txhex, value, spk) = make_chain( | ||||
node, self.address, self.privkeys, txid, value) | node, self.address, self.privkeys, txid, value | ||||
) | |||||
gp_tx.append(tx) | gp_tx.append(tx) | ||||
gp_values.append(value) | gp_values.append(value) | ||||
gp_scripts.append(spk) | gp_scripts.append(spk) | ||||
node.sendrawtransaction(txhex) | node.sendrawtransaction(txhex) | ||||
# Package transaction Pi | # Package transaction Pi | ||||
pi_hex = create_child_with_parents( | pi_hex = create_child_with_parents( | ||||
node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts) | node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts | ||||
) | |||||
package_hex.append(pi_hex) | package_hex.append(pi_hex) | ||||
pi_tx = FromHex(CTransaction(), pi_hex) | pi_tx = FromHex(CTransaction(), pi_hex) | ||||
parent_txns.append(pi_tx) | parent_txns.append(pi_tx) | ||||
parent_values.append(Decimal(pi_tx.vout[0].nValue) / XEC) | parent_values.append(Decimal(pi_tx.vout[0].nValue) / XEC) | ||||
scripts.append(pi_tx.vout[0].scriptPubKey.hex()) | scripts.append(pi_tx.vout[0].scriptPubKey.hex()) | ||||
# Package transaction PC | # Package transaction PC | ||||
package_hex.append( | package_hex.append( | ||||
create_child_with_parents(node, self.address, self.privkeys, | create_child_with_parents( | ||||
parent_txns, parent_values, scripts)) | node, self.address, self.privkeys, parent_txns, parent_values, scripts | ||||
) | |||||
) | |||||
assert_equal(45, node.getmempoolinfo()["size"]) | assert_equal(45, node.getmempoolinfo()["size"]) | ||||
assert_equal(6, len(package_hex)) | assert_equal(6, len(package_hex)) | ||||
testres = node.testmempoolaccept(rawtxs=package_hex) | testres = node.testmempoolaccept(rawtxs=package_hex) | ||||
for txres in testres: | for txres in testres: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=package_hex)) | res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) | ||||
) | |||||
def test_anc_size_limits(self): | def test_anc_size_limits(self): | ||||
"""Test Case with 2 independent transactions in the mempool and a parent + child in the | """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 (30KB each): | package, where the package parent is the child of both mempool transactions (30KB each): | ||||
A B | A B | ||||
^ ^ | ^ ^ | ||||
C | C | ||||
^ | ^ | ||||
D | D | ||||
The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool | The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool | ||||
and in-package ancestors are all considered together. | and in-package ancestors are all considered together. | ||||
""" | """ | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
assert_equal(0, node.getmempoolinfo()["size"]) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
parents_tx = [] | parents_tx = [] | ||||
values = [] | values = [] | ||||
scripts = [] | scripts = [] | ||||
target_size = 30_000 | target_size = 30_000 | ||||
# 10 sats/B | # 10 sats/B | ||||
high_fee = Decimal("3000.00") | high_fee = Decimal("3000.00") | ||||
self.log.info( | self.log.info( | ||||
"Check that in-mempool and in-package ancestor size limits are calculated properly in packages") | "Check that in-mempool and in-package ancestor size limits are calculated" | ||||
" properly in packages" | |||||
) | |||||
# Mempool transactions A and B | # Mempool transactions A and B | ||||
for _ in range(2): | for _ in range(2): | ||||
spk = None | spk = None | ||||
top_coin = self.coins.pop() | top_coin = self.coins.pop() | ||||
txid = top_coin["txid"] | txid = top_coin["txid"] | ||||
value = top_coin["amount"] | value = top_coin["amount"] | ||||
(tx, _, _, _) = make_chain( | (tx, _, _, _) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk, high_fee) | node, self.address, self.privkeys, txid, value, 0, spk, high_fee | ||||
) | |||||
bulked_tx = bulk_transaction(tx, node, target_size, self.privkeys) | bulked_tx = bulk_transaction(tx, node, target_size, self.privkeys) | ||||
node.sendrawtransaction(ToHex(bulked_tx)) | node.sendrawtransaction(ToHex(bulked_tx)) | ||||
parents_tx.append(bulked_tx) | parents_tx.append(bulked_tx) | ||||
values.append(Decimal(bulked_tx.vout[0].nValue) / XEC) | values.append(Decimal(bulked_tx.vout[0].nValue) / XEC) | ||||
scripts.append(bulked_tx.vout[0].scriptPubKey.hex()) | scripts.append(bulked_tx.vout[0].scriptPubKey.hex()) | ||||
# Package transaction C | # Package transaction C | ||||
small_pc_hex = create_child_with_parents( | small_pc_hex = create_child_with_parents( | ||||
node, self.address, self.privkeys, parents_tx, values, scripts, high_fee) | node, self.address, self.privkeys, parents_tx, values, scripts, high_fee | ||||
) | |||||
pc_tx = bulk_transaction( | pc_tx = bulk_transaction( | ||||
FromHex(CTransaction(), small_pc_hex), node, target_size, self.privkeys) | FromHex(CTransaction(), small_pc_hex), node, target_size, self.privkeys | ||||
) | |||||
pc_value = Decimal(pc_tx.vout[0].nValue) / XEC | pc_value = Decimal(pc_tx.vout[0].nValue) / XEC | ||||
pc_spk = pc_tx.vout[0].scriptPubKey.hex() | pc_spk = pc_tx.vout[0].scriptPubKey.hex() | ||||
pc_hex = ToHex(pc_tx) | pc_hex = ToHex(pc_tx) | ||||
# Package transaction D | # Package transaction D | ||||
(small_pd, _, val, spk) = make_chain( | (small_pd, _, val, spk) = make_chain( | ||||
node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee) | node, | ||||
prevtxs = [{ | self.address, | ||||
self.privkeys, | |||||
pc_tx.rehash(), | |||||
pc_value, | |||||
0, | |||||
pc_spk, | |||||
high_fee, | |||||
) | |||||
prevtxs = [ | |||||
{ | |||||
"txid": pc_tx.get_id(), | "txid": pc_tx.get_id(), | ||||
"vout": 0, | "vout": 0, | ||||
"scriptPubKey": spk, | "scriptPubKey": spk, | ||||
"amount": pc_value, | "amount": pc_value, | ||||
}] | } | ||||
pd_tx = bulk_transaction( | ] | ||||
small_pd, node, target_size, self.privkeys, prevtxs) | pd_tx = bulk_transaction(small_pd, node, target_size, self.privkeys, prevtxs) | ||||
pd_hex = ToHex(pd_tx) | pd_hex = ToHex(pd_tx) | ||||
assert_equal(2, node.getmempoolinfo()["size"]) | assert_equal(2, node.getmempoolinfo()["size"]) | ||||
testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | ||||
for txres in testres_too_heavy: | for txres in testres_too_heavy: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])) | res["allowed"] is True | ||||
for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) | |||||
) | |||||
def test_desc_size_limits(self): | def test_desc_size_limits(self): | ||||
"""Create 3 mempool transactions and 2 package transactions (25KB each): | """Create 3 mempool transactions and 2 package transactions (25KB each): | ||||
Ma | Ma | ||||
^ ^ | ^ ^ | ||||
Mb Mc | Mb Mc | ||||
^ ^ | ^ ^ | ||||
Pd Pe | Pd Pe | ||||
The top ancestor in the package exceeds descendant size limits but only if the in-mempool | The top ancestor in the package exceeds descendant size limits but only if the in-mempool | ||||
and in-package descendants are all considered together. | and in-package descendants are all considered together. | ||||
""" | """ | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
assert_equal(0, node.getmempoolinfo()["size"]) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
target_size = 21_000 | target_size = 21_000 | ||||
# 10 sats/vB | # 10 sats/vB | ||||
high_fee = Decimal("2100.00") | high_fee = Decimal("2100.00") | ||||
self.log.info( | self.log.info( | ||||
"Check that in-mempool and in-package descendant sizes are calculated properly in packages") | "Check that in-mempool and in-package descendant sizes are calculated" | ||||
" properly in packages" | |||||
) | |||||
# Top parent in mempool, Ma | # Top parent in mempool, Ma | ||||
first_coin = self.coins.pop() | first_coin = self.coins.pop() | ||||
# Deduct fee and make 2 outputs | # Deduct fee and make 2 outputs | ||||
parent_value = (first_coin["amount"] - high_fee) / 2 | parent_value = (first_coin["amount"] - high_fee) / 2 | ||||
inputs = [{"txid": first_coin["txid"], "vout": 0}] | inputs = [{"txid": first_coin["txid"], "vout": 0}] | ||||
outputs = [{self.address: parent_value}, | outputs = [ | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}] | {self.address: parent_value}, | ||||
{ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, | |||||
] | |||||
rawtx = node.createrawtransaction(inputs, outputs) | rawtx = node.createrawtransaction(inputs, outputs) | ||||
parent_tx = bulk_transaction( | parent_tx = bulk_transaction( | ||||
FromHex(CTransaction(), rawtx), node, target_size, self.privkeys) | FromHex(CTransaction(), rawtx), node, target_size, self.privkeys | ||||
) | |||||
node.sendrawtransaction(ToHex(parent_tx)) | node.sendrawtransaction(ToHex(parent_tx)) | ||||
package_hex = [] | package_hex = [] | ||||
# Two legs (left and right) | # Two legs (left and right) | ||||
for j in range(2): | for j in range(2): | ||||
# Mempool transaction (Mb and Mc) | # Mempool transaction (Mb and Mc) | ||||
spk = parent_tx.vout[j].scriptPubKey.hex() | spk = parent_tx.vout[j].scriptPubKey.hex() | ||||
value = Decimal(parent_tx.vout[j].nValue) / XEC | value = Decimal(parent_tx.vout[j].nValue) / XEC | ||||
txid = parent_tx.get_id() | txid = parent_tx.get_id() | ||||
prevtxs = [{ | prevtxs = [ | ||||
{ | |||||
"txid": txid, | "txid": txid, | ||||
"vout": j, | "vout": j, | ||||
"scriptPubKey": spk, | "scriptPubKey": spk, | ||||
"amount": value, | "amount": value, | ||||
}] | } | ||||
] | |||||
if j == 0: | if j == 0: | ||||
# normal key | # normal key | ||||
(tx_small, _, _, _) = make_chain( | (tx_small, _, _, _) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, j, spk, high_fee) | node, self.address, self.privkeys, txid, value, j, spk, high_fee | ||||
) | |||||
mempool_tx = bulk_transaction( | mempool_tx = bulk_transaction( | ||||
tx_small, node, target_size, self.privkeys, prevtxs) | tx_small, node, target_size, self.privkeys, prevtxs | ||||
) | |||||
else: | else: | ||||
# OP_TRUE | # OP_TRUE | ||||
inputs = [{"txid": txid, "vout": 1}] | inputs = [{"txid": txid, "vout": 1}] | ||||
outputs = {self.address: value - high_fee} | outputs = {self.address: value - high_fee} | ||||
small_tx = FromHex( | small_tx = FromHex( | ||||
CTransaction(), node.createrawtransaction(inputs, outputs)) | CTransaction(), node.createrawtransaction(inputs, outputs) | ||||
) | |||||
mempool_tx = bulk_transaction( | mempool_tx = bulk_transaction( | ||||
small_tx, node, target_size, None, prevtxs) | small_tx, node, target_size, None, prevtxs | ||||
) | |||||
node.sendrawtransaction(ToHex(mempool_tx)) | node.sendrawtransaction(ToHex(mempool_tx)) | ||||
# Package transaction (Pd and Pe) | # Package transaction (Pd and Pe) | ||||
spk = mempool_tx.vout[0].scriptPubKey.hex() | spk = mempool_tx.vout[0].scriptPubKey.hex() | ||||
value = Decimal(mempool_tx.vout[0].nValue) / XEC | value = Decimal(mempool_tx.vout[0].nValue) / XEC | ||||
txid = mempool_tx.get_id() | txid = mempool_tx.get_id() | ||||
(tx_small, _, _, _) = make_chain( | (tx_small, _, _, _) = make_chain( | ||||
node, self.address, self.privkeys, txid, value, 0, spk, high_fee) | node, self.address, self.privkeys, txid, value, 0, spk, high_fee | ||||
prevtxs = [{ | ) | ||||
prevtxs = [ | |||||
{ | |||||
"txid": txid, | "txid": txid, | ||||
"vout": 0, | "vout": 0, | ||||
"scriptPubKey": spk, | "scriptPubKey": spk, | ||||
"amount": value, | "amount": value, | ||||
}] | } | ||||
] | |||||
package_tx = bulk_transaction( | package_tx = bulk_transaction( | ||||
tx_small, node, target_size, self.privkeys, prevtxs) | tx_small, node, target_size, self.privkeys, prevtxs | ||||
) | |||||
package_hex.append(ToHex(package_tx)) | package_hex.append(ToHex(package_tx)) | ||||
assert_equal(3, node.getmempoolinfo()["size"]) | assert_equal(3, node.getmempoolinfo()["size"]) | ||||
assert_equal(2, len(package_hex)) | assert_equal(2, len(package_hex)) | ||||
testres_too_heavy = node.testmempoolaccept(rawtxs=package_hex) | testres_too_heavy = node.testmempoolaccept(rawtxs=package_hex) | ||||
for txres in testres_too_heavy: | for txres in testres_too_heavy: | ||||
assert_equal(txres["package-error"], "package-mempool-limits") | assert_equal(txres["package-error"], "package-mempool-limits") | ||||
# Clear mempool and check that the package passes now | # Clear mempool and check that the package passes now | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
assert all(res["allowed"] is True | assert all( | ||||
for res in node.testmempoolaccept(rawtxs=package_hex)) | res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) | ||||
) | |||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
MempoolPackageLimitsTest().main() | MempoolPackageLimitsTest().main() |