Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13115777
D17717.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Subscribers
None
D17717.diff
View Options
diff --git a/src/txmempool.h b/src/txmempool.h
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -521,15 +521,7 @@
}
bool setAvalancheFinalized(const CTxMemPoolEntryRef &tx)
- EXCLUSIVE_LOCKS_REQUIRED(cs) {
- const bool ret = finalizedTxs.insert(tx);
- if (ret) {
- totalFinalizedTxSize += tx->GetTxSize();
- totalFinalizedTxSigchecks += tx->GetSigChecks();
- }
-
- return ret;
- }
+ EXCLUSIVE_LOCKS_REQUIRED(cs);
bool isAvalancheFinalized(const TxId &txid) const {
LOCK(cs);
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -511,6 +511,36 @@
return ret;
}
+bool CTxMemPool::setAvalancheFinalized(const CTxMemPoolEntryRef &tx) {
+ AssertLockHeld(cs);
+
+ auto it = mapTx.find(tx->GetTx().GetId());
+ if (it == mapTx.end()) {
+ // Trying to finalize a tx that is not in the mempool !
+ return false;
+ }
+
+ setEntries setAncestors;
+ setAncestors.insert(it);
+ if (!CalculateMemPoolAncestors(tx, setAncestors,
+ /*fSearchForParents=*/false)) {
+ // Failed to get a list of parents for this tx. If we finalize it we
+ // might be missing a parent and generate an invalid block.
+ return false;
+ }
+
+ for (txiter ancestor_it : setAncestors) {
+ // It is possible (and normal) that an ancestor is already finalized.
+ // Beware to not account for it in this case.
+ if (finalizedTxs.insert(*ancestor_it)) {
+ totalFinalizedTxSize += (*ancestor_it)->GetTxSize();
+ totalFinalizedTxSigchecks += (*ancestor_it)->GetSigChecks();
+ }
+ }
+
+ return true;
+}
+
CTransactionRef CTxMemPool::get(const TxId &txid) const {
LOCK(cs);
indexed_transaction_set::const_iterator i = mapTx.find(txid);
diff --git a/test/functional/abc_p2p_avalanche_transaction_finalization.py b/test/functional/abc_p2p_avalanche_transaction_finalization.py
new file mode 100644
--- /dev/null
+++ b/test/functional/abc_p2p_avalanche_transaction_finalization.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2025 The Bitcoin developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test avalanche transaction finalization."""
+
+from test_framework.avatools import can_find_inv_in_poll, get_ava_p2p_interface
+from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.messages import AvalancheTxVoteError
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, uint256_hex
+from test_framework.wallet import MiniWallet
+
+QUORUM_NODE_COUNT = 16
+
+
+class AvalancheTransactionFinalizationTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.noban_tx_relay = True
+ self.extra_args = [
+ [
+ "-avalanchepreconsensus=1",
+ "-avacooldown=0",
+ "-avaproofstakeutxoconfirmations=1",
+ # Low enough for coinbase transactions to be staked in valid proofs
+ "-avaproofstakeutxodustthreshold=1000000",
+ "-avaminquorumstake=0",
+ "-avaminavaproofsnodecount=0",
+ ]
+ ]
+
+ def finalize_tip(self):
+ tip = self.nodes[0].getbestblockhash()
+
+ def vote_until_final():
+ can_find_inv_in_poll(
+ self.quorum,
+ int(tip, 16),
+ response=AvalancheTxVoteError.ACCEPTED,
+ other_response=AvalancheTxVoteError.UNKNOWN,
+ )
+ return self.nodes[0].isfinalblock(tip)
+
+ self.wait_until(vote_until_final)
+
+ def finalize_tx(self, txid, other_response=AvalancheTxVoteError.ACCEPTED):
+ def vote_until_final():
+ can_find_inv_in_poll(
+ self.quorum,
+ txid,
+ response=AvalancheTxVoteError.ACCEPTED,
+ other_response=other_response,
+ )
+ return self.nodes[0].isfinaltransaction(uint256_hex(txid))
+
+ self.wait_until(vote_until_final, timeout=10)
+
+ def test_simple_txs(self):
+ self.log.info("Check the finalization of simple non-chained txs")
+
+ node = self.nodes[0]
+
+ # Make some valid txs
+ num_txs = 5
+ self.generate(self.wallet, num_txs)
+ self.finalize_tip()
+
+ assert_equal(node.getmempoolinfo()["size"], 0)
+ txids = [
+ int(self.wallet.send_self_transfer(from_node=node)["txid"], 16)
+ for _ in range(num_txs)
+ ]
+ assert_equal(node.getmempoolinfo()["size"], num_txs)
+
+ for txid in txids:
+ self.finalize_tx(txid)
+
+ def test_chained_txs(self):
+ self.log.info("Check the finalization of chained txs")
+
+ node = self.nodes[0]
+
+ # Make some valid chained txs
+ num_txs = 5
+ self.generate(self.wallet, num_txs)
+ self.finalize_tip()
+
+ assert_equal(node.getmempoolinfo()["size"], 0)
+ txs = self.wallet.send_self_transfer_chain(from_node=node, chain_length=num_txs)
+ assert_equal(node.getmempoolinfo()["size"], num_txs)
+
+ # Keep the chain ordering. We only vote for the penultimate child tx
+ txids = [tx["txid"] for tx in txs]
+ self.finalize_tx(
+ int(txids[-2], 16), other_response=AvalancheTxVoteError.UNKNOWN
+ )
+
+ # The ancestors should all be final as well
+ assert all(node.isfinaltransaction(txid) for txid in txids[:-1])
+ # But not the descendant
+ assert not node.isfinaltransaction(txids[-1])
+
+ def test_diamond_txs(self):
+ self.log.info("Check the finalization of diamond shaped txs")
+
+ node = self.nodes[0]
+
+ # tx2
+ # / \
+ # tx1 tx4
+ # \ /
+ # tx3
+ # \
+ # tx5
+ num_txs = 5
+ self.generate(self.wallet, num_txs)
+ self.finalize_tip()
+
+ assert_equal(node.getmempoolinfo()["size"], 0)
+ tx1 = self.wallet.create_self_transfer_multi(num_outputs=2)
+ tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1["new_utxos"][0])
+ tx3 = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx1["new_utxos"][1]], num_outputs=2
+ )
+ tx4 = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx2["new_utxo"], tx3["new_utxos"][0]]
+ )
+ tx5 = self.wallet.create_self_transfer(utxo_to_spend=tx3["new_utxos"][1])
+
+ txids = [
+ self.wallet.sendrawtransaction(from_node=node, tx_hex=tx["hex"])
+ for tx in (tx1, tx2, tx3, tx4, tx5)
+ ]
+ assert_equal(node.getmempoolinfo()["size"], num_txs)
+
+ # Finalizing tx4 should finalize all the ancestors as well
+ self.finalize_tx(
+ int(txids[-2], 16), other_response=AvalancheTxVoteError.UNKNOWN
+ )
+ assert all(node.isfinaltransaction(txid) for txid in txids[:-1])
+
+ # But not tx5
+ assert not node.isfinaltransaction(txids[-1])
+
+ def run_test(self):
+ def get_quorum():
+ return [
+ get_ava_p2p_interface(self, self.nodes[0])
+ for _ in range(0, QUORUM_NODE_COUNT)
+ ]
+
+ self.quorum = get_quorum()
+ assert self.nodes[0].getavalancheinfo()["ready_to_poll"]
+
+ # Let's clean up the non transaction inventories from our avalanche polls
+ def has_finalized_proof(proofid):
+ can_find_inv_in_poll(self.quorum, proofid)
+ return self.nodes[0].getrawavalancheproof(uint256_hex(proofid))["finalized"]
+
+ for q in self.quorum:
+ self.wait_until(lambda: has_finalized_proof(q.proof.proofid))
+
+ self.finalize_tip()
+
+ self.wallet = MiniWallet(self.nodes[0])
+ # Mature the coinbases
+ self.generate(self.wallet, COINBASE_MATURITY)
+
+ self.test_simple_txs()
+ self.test_chained_txs()
+ self.test_diamond_txs()
+
+
+if __name__ == "__main__":
+ AvalancheTransactionFinalizationTest().main()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 1, 12:03 (3 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5187759
Default Alt Text
D17717.diff (8 KB)
Attached To
D17717: [avalanche] Also finalize the ancestors of a transaction
Event Timeline
Log In to Comment