From e12d23716fd2f012ed8e37db9b011d65dcd634fe Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Wed, 16 Mar 2016 19:07:38 -0400 Subject: [PATCH] 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. --- wtxmgr/kahnsort.go | 115 ++++++++++++++++++++++++++++++++++++++++++ wtxmgr/unconfirmed.go | 35 +++++++------ 2 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 wtxmgr/kahnsort.go 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