mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-15 12:50:17 +02:00
mempool/txgraph: add GetConflicts for RBF conflict detection
Add GetConflicts method to detect which mempool transactions would be replaced by a new transaction. Returns both individual conflicting transactions and their packages to support package-based eviction. Uses the spentBy index for O(1) conflict lookups per input.
This commit is contained in:
parent
af3e8d2352
commit
1cdd76fc50
2 changed files with 98 additions and 0 deletions
|
|
@ -556,6 +556,76 @@ func (g *TxGraph) GetOrphans(isConfirmed InputConfirmedPredicate,
|
|||
return slices.Collect(g.IterateOrphans(isConfirmed))
|
||||
}
|
||||
|
||||
// GetConflicts returns all transactions and packages that conflict with the
|
||||
// given transaction. A conflict occurs when the input transaction attempts to
|
||||
// spend an output that is already spent by a transaction in the mempool.
|
||||
//
|
||||
// The method uses the spentBy index for O(1) conflict detection per input.
|
||||
// For each conflicting transaction found, it includes all descendants since
|
||||
// they would become invalid if their ancestor is replaced. It also identifies
|
||||
// any packages that contain conflicting transactions.
|
||||
//
|
||||
// The returned ConflictSet provides both individual transactions (for
|
||||
// fine-grained analysis) and packages (for package-based eviction policies).
|
||||
//
|
||||
// Returns an empty ConflictSet if there are no conflicts.
|
||||
func (g *TxGraph) GetConflicts(tx *btcutil.Tx) *ConflictSet {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
result := &ConflictSet{
|
||||
Transactions: make(map[chainhash.Hash]*TxGraphNode),
|
||||
Packages: make(map[PackageID]*TxPackage),
|
||||
}
|
||||
|
||||
// Check each input of the candidate transaction for conflicts with
|
||||
// existing mempool transactions. The spentBy index maps each spent
|
||||
// output to the transaction that spends it, enabling O(1) lookups.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
// If this outpoint is already spent by a mempool transaction,
|
||||
// that transaction conflicts with our candidate.
|
||||
conflictNode, exists := g.indexes.spentBy[txIn.PreviousOutPoint]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the directly conflicting transaction.
|
||||
result.Transactions[conflictNode.TxHash] = conflictNode
|
||||
|
||||
// Add all descendants of the conflict. When we replace a
|
||||
// transaction via RBF, all its descendants must also be
|
||||
// removed because they spend outputs that will no longer
|
||||
// exist.
|
||||
descendants := g.GetDescendants(conflictNode.TxHash, -1)
|
||||
for hash, node := range descendants {
|
||||
result.Transactions[hash] = node
|
||||
}
|
||||
}
|
||||
|
||||
// Identify packages that contain any conflicting transactions. This
|
||||
// enables package-based eviction where entire packages are treated as
|
||||
// atomic units.
|
||||
for hash := range result.Transactions {
|
||||
pkgID, exists := g.indexes.nodeToPackage[hash]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only add each package once even if multiple transactions
|
||||
// from the same package are in the conflict set.
|
||||
if _, alreadyAdded := result.Packages[pkgID]; alreadyAdded {
|
||||
continue
|
||||
}
|
||||
|
||||
pkg, exists := g.indexes.packages[pkgID]
|
||||
if exists {
|
||||
result.Packages[pkgID] = pkg
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ValidatePackage validates a transaction package.
|
||||
func (g *TxGraph) ValidatePackage(pkg *TxPackage) error {
|
||||
if pkg == nil {
|
||||
|
|
|
|||
|
|
@ -372,6 +372,16 @@ type Graph interface {
|
|||
// indicates mempool fragmentation and is useful for understanding the
|
||||
// effectiveness of cluster-based optimizations.
|
||||
GetClusterCount() int
|
||||
|
||||
// GetConflicts returns all transactions and packages that would be
|
||||
// replaced if the given transaction were added to the mempool. A
|
||||
// conflict occurs when a transaction input spends an output that is
|
||||
// already spent by a transaction in the graph. The returned ConflictSet
|
||||
// includes both directly conflicting transactions and all their
|
||||
// descendants, since descendants become invalid when their ancestor is
|
||||
// replaced. It also includes any packages that contain conflicting
|
||||
// transactions, enabling package-based eviction policies.
|
||||
GetConflicts(tx *btcutil.Tx) *ConflictSet
|
||||
}
|
||||
|
||||
// TraversalOrder defines the traversal strategy for graph iteration.
|
||||
|
|
@ -485,6 +495,24 @@ func WithIncludeStart(include bool) IterOption {
|
|||
}
|
||||
}
|
||||
|
||||
// ConflictSet contains the result of a conflict check, providing both
|
||||
// individual conflicting transactions and their associated packages.
|
||||
type ConflictSet struct {
|
||||
// Transactions contains all conflicting transactions and their
|
||||
// descendants as a flat map. This enables direct iteration and
|
||||
// individual transaction analysis.
|
||||
Transactions map[chainhash.Hash]*TxGraphNode
|
||||
|
||||
// Packages contains all packages that include at least one conflicting
|
||||
// transaction. This enables package-based eviction policies where entire
|
||||
// packages are considered as atomic units.
|
||||
//
|
||||
// Note: Not all conflicting transactions are necessarily in packages.
|
||||
// Standalone transactions will appear in Transactions but not in
|
||||
// Packages.
|
||||
Packages map[PackageID]*TxPackage
|
||||
}
|
||||
|
||||
// InputConfirmedPredicate is a function that checks if a transaction input
|
||||
// references a confirmed UTXO. This is used to distinguish between:
|
||||
// - Orphans: transactions with unconfirmed inputs not in the mempool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue