1 package wtxmgr
2 3 import (
4 "github.com/p9c/p9/pkg/chainhash"
5 "github.com/p9c/p9/pkg/walletdb"
6 "github.com/p9c/p9/pkg/wire"
7 )
8 9 // insertMemPoolTx inserts the unmined transaction record. It also marks previous outputs referenced by the inputs as
10 // spent.
11 func (s *Store) insertMemPoolTx(ns walletdb.ReadWriteBucket, rec *TxRecord) (e error) {
12 // Chk whether the transaction has already been added to the unconfirmed bucket.
13 if existsRawUnmined(ns, rec.Hash[:]) != nil {
14 // TODO: compare serialized txs to ensure this isn't a hash
15 // collision?
16 return nil
17 }
18 // Since transaction records within the store are keyed by their transaction _and_ block confirmation, we'll iterate
19 // through the transaction's outputs to determine if we've already seen them to prevent from adding this transaction
20 // to the unconfirmed bucket.
21 for i := range rec.MsgTx.TxOut {
22 k := canonicalOutPoint(&rec.Hash, uint32(i))
23 if existsRawUnspent(ns, k) != nil {
24 return nil
25 }
26 }
27 I.Ln("inserting unconfirmed transaction", rec.Hash)
28 v, e := valueTxRecord(rec)
29 if e != nil {
30 return e
31 }
32 e = putRawUnmined(ns, rec.Hash[:], v)
33 if e != nil {
34 return e
35 }
36 for _, input := range rec.MsgTx.TxIn {
37 prevOut := &input.PreviousOutPoint
38 k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
39 e = putRawUnminedInput(ns, k, rec.Hash[:])
40 if e != nil {
41 return e
42 }
43 }
44 // TODO: increment credit amount for each credit (but those are unknown here currently).
45 return nil
46 }
47 48 // removeDoubleSpends checks for any unmined transactions which would introduce a double spend if tx was added to the
49 // store (either as a confirmed or unmined transaction). Each conflicting transaction and all transactions which spend
50 // it are recursively removed.
51 func (s *Store) removeDoubleSpends(ns walletdb.ReadWriteBucket, rec *TxRecord) (e error) {
52 for _, input := range rec.MsgTx.TxIn {
53 prevOut := &input.PreviousOutPoint
54 prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
55 doubleSpendHashes := fetchUnminedInputSpendTxHashes(ns, prevOutKey)
56 for _, doubleSpendHash := range doubleSpendHashes {
57 doubleSpendVal := existsRawUnmined(ns, doubleSpendHash[:])
58 // If the spending transaction spends multiple outputs from the same transaction, we'll find duplicate
59 // entries within the store, so it's possible we're unable to find it if the conflicts have already been
60 // removed in a previous iteration.
61 if doubleSpendVal == nil {
62 continue
63 }
64 var doubleSpend TxRecord
65 doubleSpend.Hash = doubleSpendHash
66 e := readRawTxRecord(
67 &doubleSpend.Hash, doubleSpendVal, &doubleSpend,
68 )
69 if e != nil {
70 return e
71 }
72 D.Ln(
73 "removing double spending transaction", doubleSpend.Hash,
74 )
75 if e := RemoveConflict(ns, &doubleSpend); E.Chk(e) {
76 return e
77 }
78 }
79 }
80 return nil
81 }
82 83 // RemoveConflict removes an unmined transaction record and all spend chains deriving from it from the store.
84 //
85 // This is designed to remove transactions that would otherwise result in double spend conflicts if left in the store,
86 // and to remove transactions that spend coinbase transactions on reorgs.
87 func RemoveConflict(ns walletdb.ReadWriteBucket, rec *TxRecord) (e error) {
88 // For each potential credit for this record, each spender (if any) must be recursively removed as well. Once the
89 // spenders are removed, the credit is deleted.
90 for i := range rec.MsgTx.TxOut {
91 k := canonicalOutPoint(&rec.Hash, uint32(i))
92 spenderHashes := fetchUnminedInputSpendTxHashes(ns, k)
93 for _, spenderHash := range spenderHashes {
94 spenderVal := existsRawUnmined(ns, spenderHash[:])
95 // If the spending transaction spends multiple outputs from the same transaction, we'll find duplicate
96 // entries within the store, so it's possible we're unable to find it if the conflicts have already been
97 // removed in a previous iteration.
98 if spenderVal == nil {
99 continue
100 }
101 var spender TxRecord
102 spender.Hash = spenderHash
103 e := readRawTxRecord(&spender.Hash, spenderVal, &spender)
104 if e != nil {
105 return e
106 }
107 D.F(
108 "transaction %v is part of a removed conflict chain -- removing as well %s",
109 spender.Hash,
110 )
111 if e := RemoveConflict(ns, &spender); E.Chk(e) {
112 return e
113 }
114 }
115 if e := deleteRawUnminedCredit(ns, k); E.Chk(e) {
116 return e
117 }
118 }
119 // If this tx spends any previous credits (either mined or unmined), set each unspent. Mined transactions are only
120 // marked spent by having the output in the unmined inputs bucket.
121 for _, input := range rec.MsgTx.TxIn {
122 prevOut := &input.PreviousOutPoint
123 k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
124 if e := deleteRawUnminedInput(ns, k); E.Chk(e) {
125 return e
126 }
127 }
128 return deleteRawUnmined(ns, rec.Hash[:])
129 }
130 131 // UnminedTxs returns the underlying transactions for all unmined transactions which are not known to have been mined in
132 // a block. Transactions are guaranteed to be sorted by their dependency order.
133 func (s *Store) UnminedTxs(ns walletdb.ReadBucket) ([]*wire.MsgTx, error) {
134 recSet, e := s.unminedTxRecords(ns)
135 if e != nil {
136 return nil, e
137 }
138 recs := dependencySort(recSet)
139 txs := make([]*wire.MsgTx, 0, len(recs))
140 for _, rec := range recs {
141 txs = append(txs, &rec.MsgTx)
142 }
143 return txs, nil
144 }
145 146 func (s *Store) unminedTxRecords(ns walletdb.ReadBucket) (map[chainhash.Hash]*TxRecord, error) {
147 unmined := make(map[chainhash.Hash]*TxRecord)
148 e := ns.NestedReadBucket(bucketUnmined).ForEach(
149 func(k, v []byte) (e error) {
150 var txHash chainhash.Hash
151 e = readRawUnminedHash(k, &txHash)
152 if e != nil {
153 return e
154 }
155 rec := new(TxRecord)
156 e = readRawTxRecord(&txHash, v, rec)
157 if e != nil {
158 return e
159 }
160 unmined[rec.Hash] = rec
161 return nil
162 },
163 )
164 return unmined, e
165 }
166 167 // UnminedTxHashes returns the hashes of all transactions not known to have been mined in a block.
168 func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
169 return s.unminedTxHashes(ns)
170 }
171 172 func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) (hashes []*chainhash.Hash, e error) {
173 e = ns.NestedReadBucket(bucketUnmined).ForEach(
174 func(k, v []byte) (e error) {
175 hash := new(chainhash.Hash)
176 e = readRawUnminedHash(k, hash)
177 if e == nil {
178 hashes = append(hashes, hash)
179 }
180 return e
181 },
182 )
183 return hashes, e
184 }
185