e12d23716f
This changes the wtxmgr.Store.UnminedTxs method to sort transactions, using the Kahn topological sort algorithm, before returning transactions to the caller. This is possible because transactions form a sort of directed acyclic graph (DAG) where transactions reference other transactions to spend their outputs (multiple referenced outputs from a single transaction spent by the same transaction count as a single graph edge). This prevents the possibility of orphan rejection errors when sending unmined transactions to a full node at startup. As these transactions are sent using the sendrawtransaction RPC, which does not permit orphans, this topological sort is required. Fixes #156.
115 lines
2.8 KiB
Go
115 lines
2.8 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package wtxmgr
|
|
|
|
import (
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
type graphNode struct {
|
|
value *TxRecord
|
|
outEdges []*wire.ShaHash
|
|
inDegree int
|
|
}
|
|
|
|
type hashGraph map[wire.ShaHash]graphNode
|
|
|
|
func makeGraph(set map[wire.ShaHash]*TxRecord) hashGraph {
|
|
graph := make(hashGraph)
|
|
|
|
for _, rec := range set {
|
|
// Add a node for every transaction record. The output edges
|
|
// and input degree are set by iterating over each record's
|
|
// inputs below.
|
|
if _, ok := graph[rec.Hash]; !ok {
|
|
graph[rec.Hash] = graphNode{value: rec}
|
|
}
|
|
|
|
inputLoop:
|
|
for _, input := range rec.MsgTx.TxIn {
|
|
// Transaction inputs that reference transactions not
|
|
// included in the set do not create any (local) graph
|
|
// edges.
|
|
if _, ok := set[input.PreviousOutPoint.Hash]; !ok {
|
|
continue
|
|
}
|
|
|
|
inputNode := graph[input.PreviousOutPoint.Hash]
|
|
|
|
// Skip duplicate edges.
|
|
for _, outEdge := range inputNode.outEdges {
|
|
if *outEdge == input.PreviousOutPoint.Hash {
|
|
continue inputLoop
|
|
}
|
|
}
|
|
|
|
// Mark a directed edge from the previous transaction
|
|
// hash to this transaction record and increase the
|
|
// input degree for this record's node.
|
|
inputRec := inputNode.value
|
|
if inputRec == nil {
|
|
inputRec = set[input.PreviousOutPoint.Hash]
|
|
}
|
|
graph[input.PreviousOutPoint.Hash] = graphNode{
|
|
value: inputRec,
|
|
outEdges: append(inputNode.outEdges, &rec.Hash),
|
|
inDegree: inputNode.inDegree,
|
|
}
|
|
node := graph[rec.Hash]
|
|
graph[rec.Hash] = graphNode{
|
|
value: rec,
|
|
outEdges: node.outEdges,
|
|
inDegree: node.inDegree + 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
return graph
|
|
}
|
|
|
|
// graphRoots returns the roots of the graph. That is, it returns the node's
|
|
// values for all nodes which contain an input degree of 0.
|
|
func graphRoots(graph hashGraph) []*TxRecord {
|
|
roots := make([]*TxRecord, 0, len(graph))
|
|
for _, node := range graph {
|
|
if node.inDegree == 0 {
|
|
roots = append(roots, node.value)
|
|
}
|
|
}
|
|
return roots
|
|
}
|
|
|
|
// dependencySort topologically sorts a set of transaction records by their
|
|
// dependency order. It is implemented using Kahn's algorithm.
|
|
func dependencySort(txs map[wire.ShaHash]*TxRecord) []*TxRecord {
|
|
graph := makeGraph(txs)
|
|
s := graphRoots(graph)
|
|
|
|
// If there are no edges (no transactions from the map reference each
|
|
// other), then Kahn's algorithm is unnecessary.
|
|
if len(s) == len(txs) {
|
|
return s
|
|
}
|
|
|
|
sorted := make([]*TxRecord, 0, len(txs))
|
|
for len(s) != 0 {
|
|
rec := s[0]
|
|
s = s[1:]
|
|
sorted = append(sorted, rec)
|
|
|
|
n := graph[rec.Hash]
|
|
for _, mHash := range n.outEdges {
|
|
m := graph[*mHash]
|
|
if m.inDegree != 0 {
|
|
m.inDegree--
|
|
graph[*mHash] = m
|
|
if m.inDegree == 0 {
|
|
s = append(s, m.value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return sorted
|
|
}
|