486b441971912bdc63921425d5e40a8c168186ea
pretty sure there's at least one bug here. f.e., if you have three chunks in one cluster, I think the conditional here is only hit the first time, as current_chunk_feerate is never reset to the next chunk's size, so it'll just start going negative.
Wrote a test which I think covers it
diff --git a/test/functional/mempool_cluster.py b/test/functional/mempool_cluster.py
index 4b4812e619..a7eabef039 100755
--- a/test/functional/mempool_cluster.py
+++ b/test/functional/mempool_cluster.py
@@ -299,4 +299,73 @@ class MempoolClusterTest(BitcoinTestFramework):
assert_equal(node.getmempoolcluster(tx_replacer["txid"])['txcount'], 2)
+ [@cleanup](/bitcoin-bitcoin/contributor/cleanup/)
+ def test_getmempoolcluster(self):
+ node = self.nodes[0]
+
+ self.log.info("Testing getmempoolcluster")
+
+ assert_equal(node.getrawmempool(), [])
+
+ # Not in-mempool
+ not_mempool_tx = self.wallet.create_self_transfer()
+ assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolcluster, not_mempool_tx["txid"])
+
+ # Test that chunks are being recomputed properly
+
+ # One chunk with one tx
+ first_chunk_tx = self.wallet.send_self_transfer(from_node=node)
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ assert_equal(first_chunk_info, {'weight': first_chunk_tx["tx"].get_weight(), 'txcount': 1, 'chunks': [{'chunkfee': first_chunk_tx["fee"], 'chunkweight': first_chunk_tx["tx"].get_weight(), 'txs': [first_chunk_tx["txid"]]}]})
+
+ # Another unconnected tx, nothing should change
+ other_chunk_tx = self.wallet.send_self_transfer(from_node=node)
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ assert_equal(first_chunk_info, {'weight': first_chunk_tx["tx"].get_weight(), 'txcount': 1, 'chunks': [{'chunkfee': first_chunk_tx["fee"], 'chunkweight': first_chunk_tx["tx"].get_weight(), 'txs': [first_chunk_tx["txid"]]}]})
+
+ # Second connected tx, makes one chunk still with high enough fee
+ second_chunk_tx = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=first_chunk_tx["new_utxo"], fee_rate=Decimal("0.01"))
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ # output is same across same cluster transactions
+ assert_equal(first_chunk_info, node.getmempoolcluster(second_chunk_tx["txid"]))
+ chunkweight = first_chunk_tx["tx"].get_weight() + second_chunk_tx["tx"].get_weight()
+ chunkfee = first_chunk_tx["fee"] + second_chunk_tx["fee"]
+ assert_equal(first_chunk_info, {'weight': chunkweight, 'txcount': 2, 'chunks': [{'chunkfee': chunkfee, 'chunkweight': chunkweight, 'txs': [first_chunk_tx["txid"], second_chunk_tx["txid"]]}]})
+
+ # Third connected tx, makes one chunk still with high enough fee
+ third_chunk_tx = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=second_chunk_tx["new_utxo"], fee_rate=Decimal("0.1"))
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ # output is same across same cluster transactions
+ assert_equal(first_chunk_info, node.getmempoolcluster(third_chunk_tx["txid"]))
+ chunkweight = first_chunk_tx["tx"].get_weight() + second_chunk_tx["tx"].get_weight() + third_chunk_tx["tx"].get_weight()
+ chunkfee = first_chunk_tx["fee"] + second_chunk_tx["fee"] + third_chunk_tx["fee"]
+ assert_equal(first_chunk_info, {'weight': chunkweight, 'txcount': 3, 'chunks': [{'chunkfee': chunkfee, 'chunkweight': chunkweight, 'txs': [first_chunk_tx["txid"], second_chunk_tx["txid"], third_chunk_tx["txid"]]}]})
+
+ # Now test single cluster with each tx being its own chunk
+
+ # One chunk with one tx
+ first_chunk_tx = self.wallet.send_self_transfer(from_node=node)
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ assert_equal(first_chunk_info, {'weight': first_chunk_tx["tx"].get_weight(), 'txcount': 1, 'chunks': [{'chunkfee': first_chunk_tx["fee"], 'chunkweight': first_chunk_tx["tx"].get_weight(), 'txs': [first_chunk_tx["txid"]]}]})
+
+ # Second connected tx, lower fee
+ second_chunk_tx = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=first_chunk_tx["new_utxo"], fee_rate=Decimal("0.000002"))
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ # output is same across same cluster transactions
+ assert_equal(first_chunk_info, node.getmempoolcluster(second_chunk_tx["txid"]))
+ first_chunkweight = first_chunk_tx["tx"].get_weight()
+ second_chunkweight = second_chunk_tx["tx"].get_weight()
+ assert_equal(first_chunk_info, {'weight': first_chunkweight + second_chunkweight, 'txcount': 2, 'chunks': [{'chunkfee': first_chunk_tx["fee"], 'chunkweight': first_chunkweight, 'txs': [first_chunk_tx["txid"]]}, {'chunkfee': second_chunk_tx["fee"], 'chunkweight': second_chunkweight, 'txs': [second_chunk_tx["txid"]]}]})
+
+ # Third connected tx, even lower fee
+ third_chunk_tx = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=second_chunk_tx["new_utxo"], fee_rate=Decimal("0.000001"))
+ first_chunk_info = node.getmempoolcluster(first_chunk_tx["txid"])
+ # output is same across same cluster transactions
+ assert_equal(first_chunk_info, node.getmempoolcluster(third_chunk_tx["txid"]))
+ first_chunkweight = first_chunk_tx["tx"].get_weight()
+ second_chunkweight = second_chunk_tx["tx"].get_weight()
+ third_chunkweight = third_chunk_tx["tx"].get_weight()
+ chunkfee = first_chunk_tx["fee"] + second_chunk_tx["fee"] + third_chunk_tx["fee"]
+ # FIXME I think there's a big consolidating the second and third tx into one chunk
+ assert_equal(first_chunk_info, {'weight': first_chunkweight + second_chunkweight + third_chunkweight, 'txcount': 3, 'chunks': [{'chunkfee': first_chunk_tx["fee"], 'chunkweight': first_chunkweight, 'txs': [first_chunk_tx["txid"]]}, {'chunkfee': second_chunk_tx["fee"], 'chunkweight': second_chunkweight, 'txs': [second_chunk_tx["txid"]]}, {'chunkfee': third_chunk_tx["fee"], 'chunkweight': third_chunkweight, 'txs': [third_chunk_tx["txid"]]}]})
def run_test(self):
@@ -305,4 +374,6 @@ class MempoolClusterTest(BitcoinTestFramework):
self.generate(self.wallet, 400)
+ self.test_getmempoolcluster()
+
self.test_cluster_limit_rbf(DEFAULT_CLUSTER_LIMIT)