Sort unmined transactions in their dependency order.
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.
This commit is contained in:
parent
c2ed8ffc2b
commit
e12d23716f
2 changed files with 135 additions and 15 deletions
115
wtxmgr/kahnsort.go
Normal file
115
wtxmgr/kahnsort.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// 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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -123,37 +123,42 @@ func (s *Store) removeConflict(ns walletdb.Bucket, rec *TxRecord) error {
|
|||
}
|
||||
|
||||
// UnminedTxs returns the underlying transactions for all unmined transactions
|
||||
// which are not known to have been mined in a block.
|
||||
// which are not known to have been mined in a block. Transactions are
|
||||
// guaranteed to be sorted by their dependency order.
|
||||
func (s *Store) UnminedTxs() ([]*wire.MsgTx, error) {
|
||||
var txs []*wire.MsgTx
|
||||
var recSet map[wire.ShaHash]*TxRecord
|
||||
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
|
||||
var err error
|
||||
txs, err = s.unminedTxs(ns)
|
||||
recSet, err = s.unminedTxRecords(ns)
|
||||
return err
|
||||
})
|
||||
return txs, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recs := dependencySort(recSet)
|
||||
txs := make([]*wire.MsgTx, 0, len(recs))
|
||||
for _, rec := range recs {
|
||||
txs = append(txs, &rec.MsgTx)
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func (s *Store) unminedTxs(ns walletdb.Bucket) ([]*wire.MsgTx, error) {
|
||||
var unmined []*wire.MsgTx
|
||||
func (s *Store) unminedTxRecords(ns walletdb.Bucket) (map[wire.ShaHash]*TxRecord, error) {
|
||||
unmined := make(map[wire.ShaHash]*TxRecord)
|
||||
err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error {
|
||||
// TODO: Parsing transactions from the db may be a little
|
||||
// expensive. It's possible the caller only wants the
|
||||
// serialized transactions.
|
||||
var txHash wire.ShaHash
|
||||
err := readRawUnminedHash(k, &txHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rec TxRecord
|
||||
err = readRawTxRecord(&txHash, v, &rec)
|
||||
rec := new(TxRecord)
|
||||
err = readRawTxRecord(&txHash, v, rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := rec.MsgTx
|
||||
unmined = append(unmined, &tx)
|
||||
unmined[rec.Hash] = rec
|
||||
return nil
|
||||
})
|
||||
return unmined, err
|
||||
|
|
Loading…
Reference in a new issue