diff --git a/wtxmgr/kahnsort.go b/wtxmgr/kahnsort.go new file mode 100644 index 0000000..b31884e --- /dev/null +++ b/wtxmgr/kahnsort.go @@ -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 +} diff --git a/wtxmgr/unconfirmed.go b/wtxmgr/unconfirmed.go index aacfc22..8e2e56b 100644 --- a/wtxmgr/unconfirmed.go +++ b/wtxmgr/unconfirmed.go @@ -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