From af3e8d2352e48aaa4080f03a5840e58a4974829d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 1 Oct 2025 15:10:02 -0700 Subject: [PATCH] mempool/txgraph: remove unneeded functionality --- mempool/txgraph/graph.go | 100 ---------------- mempool/txgraph/graph_test.go | 74 ------------ mempool/txgraph/interfaces.go | 187 ------------------------------ mempool/txgraph/iterator.go | 63 ---------- mempool/txgraph/iterator_test.go | 36 ------ mempool/txgraph/traversal_test.go | 91 --------------- 6 files changed, 551 deletions(-) diff --git a/mempool/txgraph/graph.go b/mempool/txgraph/graph.go index ec1562ee..3c7586c0 100644 --- a/mempool/txgraph/graph.go +++ b/mempool/txgraph/graph.go @@ -46,20 +46,6 @@ type Config struct { // mempool eviction policies in the caller. MaxNodes int - // MaxEdges limits the total number of parent-child relationships. This - // provides defense against attacks that try to create extremely - // connected transaction graphs to degrade performance. - MaxEdges int - - // EnableCaching enables memoization of expensive computations like - // ancestor/descendant counts. This trades memory for speed, which is - // beneficial in production but may complicate debugging. - EnableCaching bool - - // CacheTimeout defines how long cached computation results remain - // valid. Shorter timeouts trade freshness for computation cost. - CacheTimeout time.Duration - // MaxPackageSize limits the number of transactions in a package. // Bitcoin Core uses 101 (25 ancestors + 25 descendants + 1 root), // enforced here to prevent package relay DoS attacks. @@ -75,9 +61,6 @@ type Config struct { func DefaultConfig() *Config { return &Config{ MaxNodes: 100000, - MaxEdges: 200000, - EnableCaching: true, - CacheTimeout: 5 * time.Second, MaxPackageSize: 101, } } @@ -444,61 +427,6 @@ func (g *TxGraph) HasTransaction(hash chainhash.Hash) bool { return exists } -// AddEdge adds an edge between two nodes. -func (g *TxGraph) AddEdge(parentHash, childHash chainhash.Hash) error { - g.mu.Lock() - defer g.mu.Unlock() - - parent, parentExists := g.nodes[parentHash] - child, childExists := g.nodes[childHash] - - if !parentExists || !childExists { - return ErrNodeNotFound - } - - // Check if edge already exists. - if _, exists := parent.Children[childHash]; exists { - return nil // Already connected - } - - // Check for cycles. - if g.wouldCreateCycle(parent, child) { - return ErrCycleDetected - } - - // Add edge. - parent.Children[childHash] = child - child.Parents[parentHash] = parent - atomic.AddInt32(&g.metrics.edgeCount, 1) - - // Update clusters. - g.mergeNodeClusters(parent, child) - - return nil -} - -// RemoveEdge removes an edge between two nodes. -func (g *TxGraph) RemoveEdge(parentHash, childHash chainhash.Hash) error { - g.mu.Lock() - defer g.mu.Unlock() - - parent, parentExists := g.nodes[parentHash] - child, childExists := g.nodes[childHash] - - if !parentExists || !childExists { - return ErrNodeNotFound - } - - // Remove edge if it exists. - if _, exists := parent.Children[childHash]; exists { - delete(parent.Children, childHash) - delete(child.Parents, parentHash) - atomic.AddInt32(&g.metrics.edgeCount, -1) - } - - return nil -} - // GetAncestors returns all ancestors of a transaction up to maxDepth. func (g *TxGraph) GetAncestors(hash chainhash.Hash, maxDepth int) map[chainhash.Hash]*TxGraphNode { @@ -511,13 +439,6 @@ func (g *TxGraph) GetAncestors(hash chainhash.Hash, return nil } - // Check cache if enabled. - if g.config.EnableCaching && - time.Since(node.cachedMetrics.LastUpdated) < g.config.CacheTimeout { - // For now, skip cache and compute directly. - // TODO: Implement proper caching. - } - ancestors := make(map[chainhash.Hash]*TxGraphNode) visited := make(map[chainhash.Hash]bool) g.collectAncestorsRecursive(node, ancestors, visited, 0, maxDepth) @@ -798,8 +719,6 @@ func (g *TxGraph) createNewCluster(node *TxGraphNode) { } cluster.Nodes[node.TxHash] = node - cluster.Roots = []*TxGraphNode{node} - cluster.Leaves = []*TxGraphNode{node} g.indexes.clusters[clusterID] = cluster g.indexes.nodeToCluster[node.TxHash] = clusterID @@ -822,9 +741,6 @@ func (g *TxGraph) addToCluster(node *TxGraphNode, clusterID ClusterID) { g.indexes.nodeToCluster[node.TxHash] = clusterID node.Metadata.ClusterID = clusterID - - // Update roots and leaves. - g.updateClusterBoundaries(cluster) } // mergeClusters merges multiple clusters into one. @@ -878,7 +794,6 @@ func (g *TxGraph) mergeClusters( } targetCluster.Size = len(targetCluster.Nodes) - g.updateClusterBoundaries(targetCluster) } // mergeNodeClusters merges clusters when adding an edge. @@ -897,18 +812,3 @@ func (g *TxGraph) mergeNodeClusters(parent, child *TxGraphNode) { g.mergeClusters(parent, clusters) } -// updateClusterBoundaries updates roots and leaves of a cluster. -func (g *TxGraph) updateClusterBoundaries(cluster *TxCluster) { - cluster.Roots = nil - cluster.Leaves = nil - - for _, node := range cluster.Nodes { - if len(node.Parents) == 0 { - cluster.Roots = append(cluster.Roots, node) - } - if len(node.Children) == 0 { - cluster.Leaves = append(cluster.Leaves, node) - } - } -} - diff --git a/mempool/txgraph/graph_test.go b/mempool/txgraph/graph_test.go index f9eae67a..aed2f815 100644 --- a/mempool/txgraph/graph_test.go +++ b/mempool/txgraph/graph_test.go @@ -191,16 +191,6 @@ func TestGraphEdges(t *testing.T) { metrics := g.GetMetrics() require.Equal(t, 2, metrics.NodeCount) require.Equal(t, 1, metrics.EdgeCount) - - err = g.RemoveEdge(*parent.Hash(), *child.Hash()) - require.NoError(t, err) - - // Edge removal should update both nodes' relationship maps and - // decrement the edge count metric. - parentNode, _ = g.GetNode(*parent.Hash()) - childNode, _ = g.GetNode(*child.Hash()) - require.Len(t, parentNode.Children, 0) - require.Len(t, childNode.Parents, 0) } // TestGraphAncestorsDescendants verifies that ancestor and descendant @@ -256,30 +246,6 @@ func TestGraphAncestorsDescendants(t *testing.T) { require.NotNil(t, descendants[*txs[2].Hash()]) } -// TestCycleDetection verifies that the graph prevents cycles, which would -// violate the DAG property required for transaction dependencies. Cycles -// would make ancestor/descendant queries infinite loop and break topological -// ordering for block template construction. -func TestCycleDetection(t *testing.T) { - g := New(DefaultConfig()) - - tx1, desc1 := createTestTx(nil, 1) - tx2, desc2 := createTestTx( - []wire.OutPoint{{Hash: *tx1.Hash(), Index: 0}}, 1, - ) - - err := g.AddTransaction(tx1, desc1) - require.NoError(t, err) - err = g.AddTransaction(tx2, desc2) - require.NoError(t, err) - - // Attempting to add an edge that would create a cycle (tx2 -> tx1 - // when tx1 -> tx2 already exists) must be rejected to maintain the - // DAG invariant. - err = g.AddEdge(*tx2.Hash(), *tx1.Hash()) - require.ErrorIs(t, err, ErrCycleDetected) -} - // TestClusterManagement verifies that transactions are correctly grouped // into clusters (connected components) and that clusters merge when a // transaction bridges two previously separate clusters. This is essential @@ -393,46 +359,6 @@ func TestGetNodeCount(t *testing.T) { require.Equal(t, 2, g.GetNodeCount()) } -// TestAddEdgeErrors tests error cases in AddEdge. -func TestAddEdgeErrors(t *testing.T) { - g := New(DefaultConfig()) - - // Try to add edge between non-existent nodes. - tx1Msg := wire.NewMsgTx(1) - tx2Msg := wire.NewMsgTx(1) - hash1 := tx1Msg.TxHash() - hash2 := tx2Msg.TxHash() - - err := g.AddEdge(hash1, hash2) - require.Error(t, err) - require.Equal(t, ErrNodeNotFound, err) - - // Add one node and try to add edge. - tx1, desc1 := createTestTx(nil, 1) - require.NoError(t, g.AddTransaction(tx1, desc1)) - - err = g.AddEdge(*tx1.Hash(), hash2) - require.Error(t, err) - require.Equal(t, ErrNodeNotFound, err) - - // Add second node. - tx2, desc2 := createTestTx(nil, 1) - require.NoError(t, g.AddTransaction(tx2, desc2)) - - // Add valid edge. - err = g.AddEdge(*tx1.Hash(), *tx2.Hash()) - require.NoError(t, err) - - // Try to add duplicate edge. - err = g.AddEdge(*tx1.Hash(), *tx2.Hash()) - require.NoError(t, err) - - // Try to create cycle. - err = g.AddEdge(*tx2.Hash(), *tx1.Hash()) - require.Error(t, err) - require.Equal(t, ErrCycleDetected, err) -} - // TestRemoveTransactionComplex tests complex removal scenarios. func TestRemoveTransactionComplex(t *testing.T) { g := New(DefaultConfig()) diff --git a/mempool/txgraph/interfaces.go b/mempool/txgraph/interfaces.go index 69307463..325eebfb 100644 --- a/mempool/txgraph/interfaces.go +++ b/mempool/txgraph/interfaces.go @@ -110,21 +110,6 @@ type PackageTopology struct { IsTree bool } -// TxEdge represents metadata about an edge. -type TxEdge struct { - // OutPoints identifies which specific outputs are being spent in this - // relationship, enabling detection of conflicts and double-spends. - OutPoints []wire.OutPoint - - // Value tracks the total satoshi amount flowing through this edge, - // enabling economic analysis of transaction relationships. - Value int64 - - // Created records when this edge was established, useful for - // time-based analysis and debugging. - Created time.Time -} - // GraphMetrics provides statistics about the transaction graph. type GraphMetrics struct { // NodeCount tracks the total number of transactions in the graph for @@ -186,32 +171,6 @@ type TxGraphNode struct { // scanning slices. Children map[chainhash.Hash]*TxGraphNode - // cachedMetrics stores expensive-to-compute graph properties to avoid - // repeated traversals during policy checks. The cache is invalidated - // when ancestors or descendants change. - cachedMetrics struct { - // AncestorCount enables quick checks against BIP 125 limits. - AncestorCount int32 - - // DescendantCount enforces mempool policy limits efficiently. - DescendantCount int32 - - // AncestorSize tracks cumulative size for package limit checks. - AncestorSize int64 - - // DescendantSize enables fast descendant limit validation. - DescendantSize int64 - - // AncestorFees supports CPFP calculations. - AncestorFees int64 - - // DescendantFees enables descendant fee rate computations. - DescendantFees int64 - - // LastUpdated allows cache invalidation based on graph changes. - LastUpdated time.Time - } - // Metadata holds feature-specific flags and relationships that don't // affect core graph structure but enable specialized processing. Metadata struct { @@ -245,15 +204,6 @@ type TxCluster struct { // O(1) membership tests during cluster merges and splits. Nodes map[chainhash.Hash]*TxGraphNode - // Roots identifies transactions with no unconfirmed parents in this - // cluster. These are entry points for package evaluation and block - // template building. - Roots []*TxGraphNode - - // Leaves identifies transactions with no children in this cluster. - // These are candidates for eviction when the mempool is full. - Leaves []*TxGraphNode - // Size tracks the number of transactions for quick cluster size checks // without iterating the Nodes map. Size int @@ -317,21 +267,6 @@ type TxPackage struct { LastValidated time.Time } -// EdgePair represents a parent-child relationship. -type EdgePair struct { - // Parent is the transaction being spent from, providing context for - // graph traversal and validation. - Parent *TxGraphNode - - // Child is the transaction doing the spending, enabling forward - // traversal during descendant queries. - Child *TxGraphNode - - // Edge contains metadata about the specific outputs being spent, - // enabling detailed analysis of fund flows. - Edge *TxEdge -} - // Graph defines the primary interface for transaction graph operations. type Graph interface { // AddTransaction inserts a new transaction into the graph and @@ -362,17 +297,6 @@ type Graph interface { // data isn't needed. HasTransaction(hash chainhash.Hash) bool - // AddEdge creates a parent-child relationship between two transactions - // that are already in the graph. This enables explicit edge management - // when transaction dependencies need to be added after initial - // insertion. - AddEdge(parent, child chainhash.Hash) error - - // RemoveEdge severs a parent-child relationship without removing the - // transactions themselves. This is useful for handling reorganizations - // where relationships change but transactions remain valid. - RemoveEdge(parent, child chainhash.Hash) error - // GetAncestors returns all ancestor transactions up to maxDepth. // This is used to enforce ancestor count/size limits for policy // validation and to compute ancestor fee rates for CPFP. @@ -419,11 +343,6 @@ type Graph interface { // without allocating memory for all matches upfront. Iterate(opts IteratorOption) iter.Seq[*TxGraphNode] - // IteratePairs returns an iterator over parent-child edges in the - // graph. This enables efficient edge-based analysis like conflict - // detection and fund flow tracking. - IteratePairs(opts IteratorOption) iter.Seq[EdgePair] - // IteratePackages returns an iterator over all identified packages. // This enables package-by-package processing during block template // construction and relay decisions. @@ -566,112 +485,6 @@ func WithIncludeStart(include bool) IterOption { } } -// GraphQuery provides advanced query operations. -type GraphQuery interface { - // FindTransactions searches for transactions matching the specified - // criteria. This enables complex filtering operations like finding - // all TRUC transactions above a certain fee rate. - FindTransactions(criteria TxCriteria) []*TxGraphNode - - // FindPackages searches for packages matching the specified criteria. - // This enables targeted package queries like finding all valid 1P1C - // packages above a minimum fee rate. - FindPackages(criteria PackageCriteria) []*TxPackage - - // FindPath searches for a dependency path between two transactions. - // This is useful for understanding transaction relationships and - // debugging unexpected dependencies. - FindPath(from, to *chainhash.Hash) []*TxGraphNode - - // HasPath checks if a dependency path exists without computing it. - // This enables efficient reachability checks for cycle detection and - // conflict analysis. - HasPath(from, to *chainhash.Hash) bool - - // GetTopologicalOrder returns all transactions in topological order, - // ensuring parents appear before children. This is essential for block - // template construction where dependencies must be satisfied. - GetTopologicalOrder() []*TxGraphNode - - // DetectCycles finds circular dependencies in the graph, which should - // never exist but can occur due to bugs. Each inner slice represents - // one cycle detected in the graph. - DetectCycles() [][]*TxGraphNode - - // GetFeerateDistribution computes the cumulative feerate diagram for - // all transactions. This enables analysis of mempool composition and - // fee rate distributions. - GetFeerateDistribution() []FeeratePoint - - // GetPackageFeerates computes the effective fee rate for each package. - // This enables package-based comparisons for relay and mining - // decisions. - GetPackageFeerates() map[PackageID]int64 -} - -// TxCriteria defines criteria for finding transactions. -type TxCriteria struct { - // MinFeeRate filters for transactions at or above this fee rate, - // enabling queries for high-priority transactions. - MinFeeRate int64 - - // MaxFeeRate filters for transactions at or above this fee rate, - // enabling queries for low-fee transactions that may need eviction. - MaxFeeRate int64 - - // MinSize filters for transactions at or above this size, useful for - // identifying large transactions that consume significant mempool - // space. - MinSize int64 - - // MaxSize filters for transactions at or below this size, useful for - // finding small transactions or enforcing size limits. - MaxSize int64 - - // IsTRUC filters by v3 transaction status. Nil means don't filter, - // true means only v3, false means only non-v3. - IsTRUC *bool - - // IsEphemeral filters by ephemeral dust status. Nil means don't - // filter, enabling queries specific to ephemeral transactions. - IsEphemeral *bool - - // HasAncestors filters by ancestor presence. Nil means don't filter, - // true finds transactions with parents, false finds root transactions. - HasAncestors *bool - - // HasChildren filters by child presence. Nil means don't filter, true - // finds transactions with children, false finds leaf transactions. - HasChildren *bool -} - -// PackageCriteria defines criteria for finding packages. -type PackageCriteria struct { - // Type filters by package type (1P1C, TRUC, ephemeral), enabling - // type-specific package queries. - Type PackageType - - // MinSize filters for packages at or above this transaction count, - // useful for finding complex multi-transaction packages. - MinSize int - - // MaxSize filters for packages at or below this transaction count, - // useful for finding simple packages or enforcing limits. - MaxSize int - - // MinFeeRate filters for packages at or above this effective fee rate, - // enabling high-fee package identification. - MinFeeRate int64 - - // MaxFeeRate filters for packages at or below this effective fee rate, - // useful for low-fee package queries. - MaxFeeRate int64 - - // IsValid filters by validation status. Nil means don't filter, - // enabling queries for valid or invalid packages separately. - IsValid *bool -} - // 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 diff --git a/mempool/txgraph/iterator.go b/mempool/txgraph/iterator.go index 7ac3410b..721bd54c 100644 --- a/mempool/txgraph/iterator.go +++ b/mempool/txgraph/iterator.go @@ -4,7 +4,6 @@ import ( "iter" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) // Iterate returns an iterator over graph nodes. @@ -428,55 +427,6 @@ func (g *TxGraph) iterateFeeRate( } } -// IteratePairs returns an iterator over parent-child pairs. -func (g *TxGraph) IteratePairs(options ...IterOption) iter.Seq[EdgePair] { - // Build options with defaults. - opts := DefaultIteratorOption() - for _, option := range options { - option(&opts) - } - - return func(yield func(EdgePair) bool) { - g.mu.RLock() - defer g.mu.RUnlock() - - visited := make(map[string]bool) // Track visited edges - - // Iterate directly over all nodes in the graph. - for _, node := range g.nodes { - // Apply filter if specified. - if opts.Filter != nil && !opts.Filter(node) { - continue - } - - for _, child := range node.Children { - // Create unique edge key. - edgeKey := node.TxHash.String() + "->" + child.TxHash.String() - if visited[edgeKey] { - continue - } - visited[edgeKey] = true - - // Create edge metadata. - edge := &TxEdge{ - OutPoints: g.findOutpoints(node, child), - Created: node.Metadata.AddedTime, - } - - pair := EdgePair{ - Parent: node, - Child: child, - Edge: edge, - } - - if !yield(pair) { - return - } - } - } - } -} - // IteratePackages returns an iterator over packages. func (g *TxGraph) IteratePackages() iter.Seq[*TxPackage] { return func(yield func(*TxPackage) bool) { @@ -637,17 +587,4 @@ func (g *TxGraph) addNeighborsToQueue( } } } -} - -// findOutpoints finds the outpoints connecting parent to child. -func (g *TxGraph) findOutpoints(parent, child *TxGraphNode) []wire.OutPoint { - var outpoints []wire.OutPoint - - for _, txIn := range child.Tx.MsgTx().TxIn { - if txIn.PreviousOutPoint.Hash == parent.TxHash { - outpoints = append(outpoints, txIn.PreviousOutPoint) - } - } - - return outpoints } \ No newline at end of file diff --git a/mempool/txgraph/iterator_test.go b/mempool/txgraph/iterator_test.go index 95bd6b74..3b72faf2 100644 --- a/mempool/txgraph/iterator_test.go +++ b/mempool/txgraph/iterator_test.go @@ -242,42 +242,6 @@ func TestIteratorTraversalMethods(t *testing.T) { }) } -// TestIteratePairs verifies that edge pair iteration produces all parent- -// child relationships in the graph. This is useful for analyzing spending -// patterns and computing aggregate statistics about transaction dependencies. -func TestIteratePairs(t *testing.T) { - g := New(DefaultConfig()) - - tx1, desc1 := createTestTx(nil, 1) - require.NoError(t, g.AddTransaction(tx1, desc1)) - - tx2, desc2 := createTestTx( - []wire.OutPoint{{Hash: *tx1.Hash(), Index: 0}}, 1, - ) - require.NoError(t, g.AddTransaction(tx2, desc2)) - - tx3, desc3 := createTestTx( - []wire.OutPoint{{Hash: *tx2.Hash(), Index: 0}}, 1, - ) - require.NoError(t, g.AddTransaction(tx3, desc3)) - - edges := make(map[string]bool) - for pair := range g.IteratePairs() { - edgeKey := pair.Parent.TxHash.String() + "->" + - pair.Child.TxHash.String() - edges[edgeKey] = true - - // Each edge pair should include metadata about which outputs - // are being spent, enabling detailed dependency analysis. - require.NotNil(t, pair.Edge) - require.NotEmpty(t, pair.Edge.OutPoints) - } - - require.Len(t, edges, 2) - require.True(t, edges[tx1.Hash().String()+"->"+tx2.Hash().String()]) - require.True(t, edges[tx2.Hash().String()+"->"+tx3.Hash().String()]) -} - // TestIteratePackages verifies that package iteration produces all // identified transaction packages in the graph. Package iteration enables // efficient processing of transaction groups for package relay policies and diff --git a/mempool/txgraph/traversal_test.go b/mempool/txgraph/traversal_test.go index cc0de7e7..522c17d8 100644 --- a/mempool/txgraph/traversal_test.go +++ b/mempool/txgraph/traversal_test.go @@ -259,97 +259,6 @@ func TestIterateWithDirectionBoth(t *testing.T) { ) } -// TestIteratePairsWithOptions validates that IteratePairs correctly emits -// parent-child relationships as pairs, which is essential for CPFP (Child -// Pays For Parent) analysis. By iterating edges rather than nodes, we can -// efficiently compute fee deltas and determine which children are boosting -// low-fee ancestors. -func TestIteratePairsWithOptions(t *testing.T) { - g := New(DefaultConfig()) - - // Build a tree with one parent and two children to test edge - // enumeration. - tx1, desc1 := createTestTx(nil, 2) - require.NoError(t, g.AddTransaction(tx1, desc1)) - - tx2, desc2 := createTestTx( - []wire.OutPoint{{Hash: *tx1.Hash(), Index: 0}}, 1, - ) - require.NoError(t, g.AddTransaction(tx2, desc2)) - - tx3, desc3 := createTestTx( - []wire.OutPoint{{Hash: *tx1.Hash(), Index: 1}}, 1, - ) - require.NoError(t, g.AddTransaction(tx3, desc3)) - - // IteratePairs emits one pair per edge, allowing us to analyze - // each parent-child relationship independently for fee rate - // calculations. - pairs := slices.Collect(g.IteratePairs( - WithOrder(TraversalDefault), - WithStartNode(tx1.Hash()), - WithDirection(DirectionForward), - )) - - require.Len(t, pairs, 2, "should have 2 edges from tx1") - - // Verify each pair represents a valid edge from tx1 to one of its - // children. - for _, pair := range pairs { - require.Equal(t, *tx1.Hash(), pair.Parent.TxHash) - require.True(t, - pair.Child.TxHash == *tx2.Hash() || - pair.Child.TxHash == *tx3.Hash(), - "child should be tx2 or tx3", - ) - } -} - -// TestIteratePairsWithFilter validates that filters are applied to -// parent-child pairs, enabling selective analysis of specific -// relationships. This is used in RBF (Replace-By-Fee) scenarios where we -// need to identify which high-value dependencies would be broken by -// replacing a transaction. -func TestIteratePairsWithFilter(t *testing.T) { - g := New(DefaultConfig()) - - // Create two independent parent-child chains with different fee - // rates to test filtering at the edge level. - tx1, desc1 := createTestTx(nil, 1) - desc1.FeePerKB = 10000 - require.NoError(t, g.AddTransaction(tx1, desc1)) - - tx2, desc2 := createTestTx(nil, 1) - desc2.FeePerKB = 1000 - require.NoError(t, g.AddTransaction(tx2, desc2)) - - tx3, desc3 := createTestTx( - []wire.OutPoint{{Hash: *tx1.Hash(), Index: 0}}, 1, - ) - require.NoError(t, g.AddTransaction(tx3, desc3)) - - tx4, desc4 := createTestTx( - []wire.OutPoint{{Hash: *tx2.Hash(), Index: 0}}, 1, - ) - require.NoError(t, g.AddTransaction(tx4, desc4)) - - // The filter applies to parent nodes in the pairs, allowing us to - // focus analysis on edges originating from high-fee transactions. - highFeeFilter := func(n *TxGraphNode) bool { - return n.TxDesc.FeePerKB >= 5000 - } - - pairs := slices.Collect(g.IteratePairs( - WithOrder(TraversalDefault), - WithFilter(highFeeFilter), - )) - - // Only the edge from high-fee tx1 should appear. - require.Len(t, pairs, 1, "should filter out low-fee parent edges") - require.Equal(t, *tx1.Hash(), pairs[0].Parent.TxHash) - require.Equal(t, *tx3.Hash(), pairs[0].Child.TxHash) -} - // TestIterateBackwardWithMaxDepth ensures that depth limits correctly // bound backward traversal. This prevents unbounded ancestor walks in // large transaction chains and enables efficient "bounded ancestor