mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
mempool/txgraph: remove unneeded functionality
This commit is contained in:
parent
a049e38501
commit
af3e8d2352
6 changed files with 0 additions and 551 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue