Skip to content

Commit 42b42c8

Browse files
committed
mempool/txgraph: add package documentation and user guide
This commit provides two levels of documentation for the transaction graph package—godoc comments for API reference and a comprehensive README for user onboarding. The doc.go file establishes package-level documentation that appears in godoc output, providing a high-level introduction to the package's purpose and capabilities. It outlines the core features (efficient lookups, edge creation, cluster management, package identification, orphan detection, traversal strategies) and describes the major concepts (graph structure, packages, iteration, orphan detection, thread safety). The documentation includes practical code examples showing common usage patterns. Rather than exhaustive API coverage, these examples demonstrate the typical workflow—creating a graph, adding transactions, querying ancestors and descendants, identifying packages, and iterating with custom filters. This example-driven approach helps developers understand not just what methods exist but how they fit together in real applications. The README.md takes a different approach, targeting developers who are evaluating or integrating the package. It starts with motivation—explaining why transaction graphs matter for mempool management and what problems they solve. This context helps readers understand when they need this package and what benefits it provides. The core concepts section defines terms like "transaction graph," "cluster," and "package" with emphasis on their practical implications. For example, clusters are explained not just as connected components but as the unit of analysis for RBF validation—replacements must improve the entire cluster's fee rate, not individual transactions. This connects abstract concepts to concrete use cases. The quick start examples are runnable code demonstrating key workflows. The first example shows basic graph construction with automatic edge creation. The second demonstrates cluster iteration for fee analysis. The third covers package identification and validation. The fourth shows advanced iteration with functional options and filters. These examples progress from simple to complex, building understanding incrementally. The common patterns section captures best practices discovered during implementation. It covers incremental graph building, package-aware relay, ancestor/descendant limit enforcement, and efficient cleanup on block confirmation. These patterns represent battle-tested approaches to recurring problems, saving readers from rediscovering solutions. The PackageAnalyzer interface receives special attention because it represents the main extension point. The documentation explains the abstraction purpose (separating graph structure from protocol validation) and lists the methods each implementation must provide. This section helps developers who need to add new package types or customize validation logic. Performance characteristics provide guidance on the computational cost of operations, helping developers make informed architectural decisions. The O(1), O(n), and O(d) complexity annotations set expectations for different graph sizes and query patterns. Mentioning the ~1KB memory overhead per transaction helps with capacity planning for mempool implementations.
1 parent ba7f98a commit 42b42c8

File tree

2 files changed

+455
-0
lines changed

2 files changed

+455
-0
lines changed

mempool/txgraph/README.md

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# txgraph: Transaction Graph for Bitcoin Mempool
2+
3+
The `txgraph` package provides a high-performance transaction graph data
4+
structure for tracking relationships between unconfirmed Bitcoin transactions.
5+
It enables efficient ancestor/descendant queries, transaction package
6+
identification, and cluster analysis—all critical for modern mempool policies
7+
including TRUC (v3 transactions), CPFP (Child-Pays-For-Parent), and ephemeral
8+
dust handling.
9+
10+
## Why txgraph?
11+
12+
Bitcoin's mempool needs to understand transaction dependencies to make
13+
intelligent relay and mining decisions. When a child transaction spends outputs
14+
from an unconfirmed parent, the mempool must:
15+
16+
- **Enforce policy limits**: Ancestor/descendant count and size restrictions
17+
(BIP 125)
18+
19+
- **Enable package relay**: Validate and relay transaction groups atomically
20+
21+
- **Calculate effective fee rates**: Consider CPFP when prioritizing transactions
22+
23+
- **Detect conflicts**: Identify transactions that spend the same outputs
24+
25+
- **Support RBF**: Compute incentive compatibility for replacements
26+
27+
The `txgraph` package provides the foundational graph structure that makes
28+
these operations efficient, replacing O(n) linear scans with O(1) lookups and
29+
cached computations.
30+
31+
## Core Concepts
32+
33+
### Transaction Graph
34+
35+
A **transaction graph** is a directed acyclic graph (DAG) where:
36+
37+
- **Nodes** represent unconfirmed transactions in the mempool
38+
39+
- **Edges** represent spend relationships (parent → child)
40+
41+
- An edge from tx A to tx B means tx B spends an output from tx A
42+
43+
The graph structure enables efficient traversal for ancestor/descendant queries
44+
without repeatedly scanning the entire mempool.
45+
46+
### Clusters
47+
48+
A **cluster** is a connected component in the transaction graph—a set of
49+
transactions that are related through spend relationships. Clusters are
50+
important for:
51+
52+
- **RBF validation**: Replacement transactions must improve the fee of the
53+
entire cluster
54+
55+
- **Mining optimization**: Miners can evaluate clusters as atomic units
56+
57+
- **Eviction policy**: When the mempool is full, low-fee clusters are
58+
candidates for removal
59+
60+
### Transaction Packages
61+
62+
A **package** is a specific type of transaction group identified by structure
63+
and validation rules:
64+
65+
- **1P1C (One Parent, One Child)**: Simple CPFP pattern with exactly one parent
66+
and one child
67+
68+
- **TRUC (v3)**: BIP 431 packages with topology restrictions to prevent pinning
69+
70+
- **Ephemeral**: Packages containing transactions with dust outputs that must
71+
be spent
72+
73+
- **Standard**: General connected transaction groups
74+
75+
Packages enable package-aware relay policies and optimized block template
76+
construction.
77+
78+
## Installation
79+
80+
```bash
81+
go get github.com/btcsuite/btcd/mempool/txgraph
82+
```
83+
84+
## Quick Start
85+
86+
### Example 1: Building a Transaction Graph
87+
88+
```go
89+
package main
90+
91+
import (
92+
"fmt"
93+
"github.com/btcsuite/btcd/btcutil"
94+
"github.com/btcsuite/btcd/mempool/txgraph"
95+
"github.com/btcsuite/btcd/wire"
96+
)
97+
98+
func main() {
99+
// Create a new transaction graph with default configuration.
100+
graph := txgraph.New(txgraph.DefaultConfig())
101+
102+
// Create a parent transaction (typically from P2P relay).
103+
parentTx := createTransaction()
104+
parentDesc := &txgraph.TxDesc{
105+
TxHash: *parentTx.Hash(),
106+
VirtualSize: 200,
107+
Fee: 1000,
108+
FeePerKB: 5000,
109+
}
110+
111+
// Add parent to the graph.
112+
if err := graph.AddTransaction(parentTx, parentDesc); err != nil {
113+
panic(err)
114+
}
115+
116+
// Create a child that spends from the parent.
117+
childTx := createChildTransaction(parentTx) // Spends parent's output
118+
childDesc := &txgraph.TxDesc{
119+
TxHash: *childTx.Hash(),
120+
VirtualSize: 150,
121+
Fee: 2000,
122+
FeePerKB: 13333,
123+
}
124+
125+
// Add child to the graph - edges are created automatically.
126+
if err := graph.AddTransaction(childTx, childDesc); err != nil {
127+
panic(err)
128+
}
129+
130+
// Query ancestors (returns parent).
131+
ancestors := graph.GetAncestors(*childTx.Hash(), -1) // -1 = unlimited depth
132+
fmt.Printf("Child has %d ancestor(s)\n", len(ancestors))
133+
134+
// Query descendants (returns child).
135+
descendants := graph.GetDescendants(*parentTx.Hash(), -1)
136+
fmt.Printf("Parent has %d descendant(s)\n", len(descendants))
137+
}
138+
```
139+
140+
### Example 2: Iterating Over Clusters
141+
142+
```go
143+
package main
144+
145+
import (
146+
"fmt"
147+
"github.com/btcsuite/btcd/mempool/txgraph"
148+
)
149+
150+
func analyzeMempool(graph *txgraph.TxGraph) {
151+
// Iterate over all connected components (clusters) in the mempool.
152+
for cluster := range graph.IterateClusters() {
153+
fmt.Printf("Cluster %d:\n", cluster.ID)
154+
fmt.Printf(" Transactions: %d\n", cluster.Size)
155+
fmt.Printf(" Total fees: %d sats\n", cluster.TotalFees)
156+
fmt.Printf(" Total size: %d vbytes\n", cluster.TotalVSize)
157+
158+
// Calculate cluster fee rate.
159+
if cluster.TotalVSize > 0 {
160+
feeRate := (cluster.TotalFees * 1000) / cluster.TotalVSize
161+
fmt.Printf(" Fee rate: %d sat/vB\n", feeRate)
162+
}
163+
164+
// Identify entry points (root transactions with no parents).
165+
fmt.Printf(" Roots: %d\n", len(cluster.Roots))
166+
167+
// Identify leaf transactions (no children, candidates for eviction).
168+
fmt.Printf(" Leaves: %d\n", len(cluster.Leaves))
169+
}
170+
}
171+
```
172+
173+
### Example 3: Package Identification and Validation
174+
175+
```go
176+
package main
177+
178+
import (
179+
"fmt"
180+
"github.com/btcsuite/btcd/mempool/txgraph"
181+
)
182+
183+
func validatePackages(graph *txgraph.TxGraph, analyzer txgraph.PackageAnalyzer) {
184+
// Identify all packages in the graph.
185+
packages, err := graph.IdentifyPackages()
186+
if err != nil {
187+
panic(err)
188+
}
189+
190+
for _, pkg := range packages {
191+
fmt.Printf("Package %s (type: %v):\n",
192+
pkg.ID.Hash.String()[:8], pkg.Type)
193+
194+
// Check package properties.
195+
fmt.Printf(" Transactions: %d\n", len(pkg.Transactions))
196+
fmt.Printf(" Total fees: %d sats\n", pkg.TotalFees)
197+
fmt.Printf(" Package fee rate: %d sat/vB\n", pkg.FeeRate)
198+
199+
// Validate package according to its type-specific rules.
200+
if err := graph.ValidatePackage(pkg); err != nil {
201+
fmt.Printf(" ❌ Validation failed: %v\n", err)
202+
continue
203+
}
204+
fmt.Printf(" ✅ Valid package\n")
205+
206+
// Check topology properties.
207+
topo := pkg.Topology
208+
if topo.IsLinear {
209+
fmt.Printf(" Structure: Linear chain (depth %d)\n", topo.MaxDepth)
210+
} else if topo.IsTree {
211+
fmt.Printf(" Structure: Tree (max width %d)\n", topo.MaxWidth)
212+
} else {
213+
fmt.Printf(" Structure: Complex DAG\n")
214+
}
215+
}
216+
}
217+
```
218+
219+
### Example 4: Advanced Iteration with Options
220+
221+
```go
222+
package main
223+
224+
import (
225+
"fmt"
226+
"github.com/btcsuite/btcd/mempool/txgraph"
227+
)
228+
229+
func findHighFeeTransactions(graph *txgraph.TxGraph, minFeeRate int64) {
230+
// Use functional options to configure iteration.
231+
highFeeFilter := func(node *txgraph.TxGraphNode) bool {
232+
return node.TxDesc.FeePerKB >= minFeeRate
233+
}
234+
235+
// Iterate in fee rate order (high to low), filtered by minimum fee rate.
236+
for node := range graph.Iterate(
237+
txgraph.WithOrder(txgraph.TraversalFeeRate),
238+
txgraph.WithFilter(highFeeFilter),
239+
) {
240+
fmt.Printf("High-fee tx: %s (%d sat/kB)\n",
241+
node.TxHash.String()[:8],
242+
node.TxDesc.FeePerKB,
243+
)
244+
}
245+
}
246+
247+
func getAncestorsWithLimit(graph *txgraph.TxGraph, txHash chainhash.Hash) {
248+
// Iterate ancestors up to depth 3 in BFS order.
249+
for ancestor := range graph.Iterate(
250+
txgraph.WithOrder(txgraph.TraversalBFS),
251+
txgraph.WithStartNode(&txHash),
252+
txgraph.WithDirection(txgraph.DirectionBackward),
253+
txgraph.WithMaxDepth(3),
254+
txgraph.WithIncludeStart(false), // Exclude starting transaction
255+
) {
256+
fmt.Printf("Ancestor: %s\n", ancestor.TxHash.String()[:8])
257+
}
258+
}
259+
```
260+
261+
## Common Patterns
262+
263+
### Building Graphs Incrementally
264+
265+
Add transactions as they arrive from P2P relay. The graph automatically creates
266+
edges when it detects spend relationships:
267+
268+
```go
269+
// As each transaction arrives...
270+
if err := graph.AddTransaction(tx, txDesc); err != nil {
271+
// Handle error (duplicate, invalid, etc.)
272+
}
273+
// Edges to existing parents are created automatically.
274+
```
275+
276+
### Package-Aware Relay
277+
278+
Use package identification to enable package relay policies:
279+
280+
```go
281+
packages, _ := graph.IdentifyPackages()
282+
for _, pkg := range packages {
283+
if pkg.Type == txgraph.PackageTypeTRUC {
284+
// Apply TRUC-specific relay rules
285+
}
286+
}
287+
```
288+
289+
### Ancestor/Descendant Limits
290+
291+
Enforce BIP 125 policy limits before accepting transactions:
292+
293+
```go
294+
const maxAncestorCount = 25
295+
const maxDescendantCount = 25
296+
297+
ancestors := graph.GetAncestors(txHash, -1)
298+
if len(ancestors) > maxAncestorCount {
299+
return errors.New("exceeds ancestor limit")
300+
}
301+
302+
descendants := graph.GetDescendants(txHash, -1)
303+
if len(descendants) > maxDescendantCount {
304+
return errors.New("exceeds descendant limit")
305+
}
306+
```
307+
308+
### Efficient Graph Cleanup
309+
310+
Remove confirmed transactions efficiently when blocks arrive:
311+
312+
```go
313+
for _, tx := range block.Transactions() {
314+
// Cascade removal: removes tx and all descendants
315+
graph.RemoveTransaction(*tx.Hash())
316+
}
317+
```
318+
319+
## Package Analyzer Interface
320+
321+
The `PackageAnalyzer` interface abstracts protocol-specific validation logic,
322+
enabling testing and future upgrades without modifying the core graph:
323+
324+
```go
325+
type PackageAnalyzer interface {
326+
IsTRUCTransaction(tx *wire.MsgTx) bool
327+
HasEphemeralDust(tx *wire.MsgTx) bool
328+
IsZeroFee(desc *TxDesc) bool
329+
ValidateTRUCPackage(nodes []*TxGraphNode) bool
330+
ValidateEphemeralPackage(nodes []*TxGraphNode) bool
331+
AnalyzePackageType(nodes []*TxGraphNode) PackageType
332+
}
333+
```
334+
335+
Implement this interface to customize package validation for your use case or
336+
to add new package types.
337+
338+
## Performance Characteristics
339+
340+
- **Transaction lookup**: O(1) via hash map
341+
- **Add transaction**: O(1) for graph insertion + O(k) for k parent edges
342+
- **Remove transaction**: O(1) for node + O(d) for d descendants (cascade)
343+
- **Ancestor/descendant query**: O(a) or O(d) where a/d is count
344+
- **Package identification**: O(n) where n is number of root nodes
345+
346+
## Thread Safety
347+
348+
All graph operations are thread-safe and protected by a read-write mutex. Read
349+
operations (queries, iteration) can proceed concurrently, while write
350+
operations (add, remove) acquire exclusive access.
351+
352+
## Further Reading
353+
354+
- **API Documentation**: Run `go doc github.com/btcsuite/btcd/mempool/txgraph`
355+
- **BIP 431 (TRUC)**: https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki
356+
- **BIP 125 (RBF)**: https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki

0 commit comments

Comments
 (0)