1 package wtxmgr
2 3 import (
4 "bytes"
5 "github.com/p9c/p9/pkg/amt"
6 "github.com/p9c/p9/pkg/chaincfg"
7 "time"
8 9 "github.com/p9c/p9/pkg/blockchain"
10 "github.com/p9c/p9/pkg/chainhash"
11 "github.com/p9c/p9/pkg/walletdb"
12 "github.com/p9c/p9/pkg/wire"
13 )
14 15 type (
16 // Block contains the minimum amount of data to uniquely identify any block on either the best or side chain.
17 Block struct {
18 Hash chainhash.Hash
19 Height int32
20 }
21 // BlockMeta contains the unique identification for a block and any metadata pertaining to the block. At the moment,
22 // this additional metadata only includes the block time from the block header.
23 BlockMeta struct {
24 Block
25 Time time.Time
26 }
27 // blockRecord is an in-memory representation of the block record saved in the database.
28 blockRecord struct {
29 Block
30 Time time.Time
31 transactions []chainhash.Hash
32 }
33 // incidence records the block hash and blockchain height of a mined transaction. Since a transaction hash alone is
34 // not enough to uniquely identify a mined transaction (duplicate transaction hashes are allowed), the incidence is
35 // used instead.
36 incidence struct {
37 txHash chainhash.Hash
38 block Block
39 }
40 // indexedIncidence records the transaction incidence and an input or output index.
41 indexedIncidence struct {
42 incidence
43 index uint32
44 }
45 // debit records the debits a transaction record makes from previous wallet transaction credits.
46 debit struct {
47 txHash chainhash.Hash
48 index uint32
49 amount amt.Amount
50 spends indexedIncidence
51 }
52 // credit describes a transaction output which was or is spendable by
53 // wallet.
54 credit struct {
55 outPoint wire.OutPoint
56 block Block
57 amount amt.Amount
58 change bool
59 spentBy indexedIncidence // Index == ^uint32(0) if unspent
60 }
61 // TxRecord represents a transaction managed by the Store.
62 TxRecord struct {
63 MsgTx wire.MsgTx
64 Hash chainhash.Hash
65 Received time.Time
66 SerializedTx []byte // Optional: may be nil
67 }
68 // Credit is the type representing a transaction output which was spent or is still spendable by wallet. A UTXO is
69 // an unspent Credit, but not all Credits are UTXOs.
70 Credit struct {
71 wire.OutPoint
72 BlockMeta
73 Amount amt.Amount
74 PkScript []byte
75 Received time.Time
76 FromCoinBase bool
77 }
78 // Store implements a transaction store for storing and managing wallet transactions.
79 Store struct {
80 chainParams *chaincfg.Params
81 // Event callbacks. These execute in the same goroutine as the wtxmgr caller.
82 NotifyUnspent func(hash *chainhash.Hash, index uint32)
83 }
84 )
85 86 // NewTxRecord creates a new transaction record that may be inserted into the store. It uses memoization to save the
87 // transaction hash and the serialized transaction.
88 func NewTxRecord(serializedTx []byte, received time.Time) (*TxRecord, error) {
89 rec := &TxRecord{
90 Received: received,
91 SerializedTx: serializedTx,
92 }
93 e := rec.MsgTx.Deserialize(bytes.NewReader(serializedTx))
94 if e != nil {
95 str := "failed to deserialize transaction"
96 return nil, storeError(ErrInput, str, e)
97 }
98 copy(rec.Hash[:], chainhash.DoubleHashB(serializedTx))
99 return rec, nil
100 }
101 102 // NewTxRecordFromMsgTx creates a new transaction record that may be inserted into the store.
103 func NewTxRecordFromMsgTx(msgTx *wire.MsgTx, received time.Time) (*TxRecord, error) {
104 buf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize()))
105 e := msgTx.Serialize(buf)
106 if e != nil {
107 str := "failed to serialize transaction"
108 return nil, storeError(ErrInput, str, e)
109 }
110 rec := &TxRecord{
111 MsgTx: *msgTx,
112 Received: received,
113 SerializedTx: buf.Bytes(),
114 Hash: msgTx.TxHash(),
115 }
116 return rec, nil
117 }
118 119 // DoUpgrades performs any necessary upgrades to the transaction history contained in the wallet database, namespaced by
120 // the top level bucket key namespaceKey.
121 func DoUpgrades(db walletdb.DB, namespaceKey []byte) (e error) {
122 // No upgrades
123 return nil
124 }
125 126 // Open opens the wallet transaction store from a walletdb namespace.
127 // If the store does not exist, ErrNoExist is returned.
128 func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (*Store, error) {
129 // Open the store.
130 e := openStore(ns)
131 if e != nil {
132 return nil, e
133 }
134 s := &Store{chainParams, nil} // TODO: set callbacks
135 return s, nil
136 }
137 138 // Create creates a new persistent transaction store in the walletdb namespace. Creating the store when one already
139 // exists in this namespace will error with ErrAlreadyExists.
140 func Create(ns walletdb.ReadWriteBucket) (e error) {
141 return createStore(ns)
142 }
143 144 // updateMinedBalance updates the mined balance within the store, if changed, after processing the given transaction
145 // record.
146 func (s *Store) updateMinedBalance(
147 ns walletdb.ReadWriteBucket, rec *TxRecord,
148 block *BlockMeta,
149 ) (e error) {
150 // Fetch the mined balance in case we need to update it.
151 minedBalance, e := fetchMinedBalance(ns)
152 if e != nil {
153 return e
154 }
155 // Add a debit record for each unspent credit spent by this transaction. The index is set in each iteration below.
156 spender := indexedIncidence{
157 incidence: incidence{
158 txHash: rec.Hash,
159 block: block.Block,
160 },
161 }
162 newMinedBalance := minedBalance
163 for i, input := range rec.MsgTx.TxIn {
164 unspentKey, credKey := existsUnspent(ns, &input.PreviousOutPoint)
165 if credKey == nil {
166 // Debits for unmined transactions are not explicitly tracked. Instead, all previous outputs spent by any
167 // unmined transaction are added to a map for quick lookups when it must be checked whether a mined output
168 // is unspent or not.
169 //
170 // Tracking individual debits for unmined transactions could be added later to simplify (and increase
171 // performance of) determining some details that need the previous outputs (e.g. determining a fee), but at
172 // the moment that is not done (and a db lookup is used for those cases instead).
173 //
174 // There is also a good chance that all unmined transaction handling will move entirely to the db rather
175 // than being handled in memory for atomicity reasons, so the simplist implementation is currently used.
176 continue
177 }
178 // If this output is relevant to us, we'll mark the it as spent and remove its amount from the store.
179 spender.index = uint32(i)
180 var amount amt.Amount
181 amount, e = spendCredit(ns, credKey, &spender)
182 if e != nil {
183 return e
184 }
185 e = putDebit(
186 ns, &rec.Hash, uint32(i), amount, &block.Block, credKey,
187 )
188 if e != nil {
189 return e
190 }
191 if e = deleteRawUnspent(ns, unspentKey); E.Chk(e) {
192 return e
193 }
194 newMinedBalance -= amount
195 }
196 // For each output of the record that is marked as a credit, if the output is marked as a credit by the unconfirmed
197 // store, remove the marker and mark the output as a credit in the db.
198 //
199 // Moved credits are added as unspents, even if there is another unconfirmed transaction which spends them.
200 cred := credit{
201 outPoint: wire.OutPoint{Hash: rec.Hash},
202 block: block.Block,
203 spentBy: indexedIncidence{index: ^uint32(0)},
204 }
205 it := makeUnminedCreditIterator(ns, &rec.Hash)
206 for it.next() {
207 // TODO: This should use the raw apis. The credit value (it.cv) can be moved from unmined directly to the
208 // credits bucket. The key needs a modification to include the block height/hash.
209 var index uint32
210 index, e = fetchRawUnminedCreditIndex(it.ck)
211 if e != nil {
212 return e
213 }
214 amount, change, e := fetchRawUnminedCreditAmountChange(it.cv)
215 if e != nil {
216 return e
217 }
218 cred.outPoint.Index = index
219 cred.amount = amount
220 cred.change = change
221 if e = putUnspentCredit(ns, &cred); E.Chk(e) {
222 return e
223 }
224 e = putUnspent(ns, &cred.outPoint, &block.Block)
225 if e != nil {
226 return e
227 }
228 newMinedBalance += amount
229 }
230 if it.err != nil {
231 return it.err
232 }
233 // Update the balance if it has changed.
234 if newMinedBalance != minedBalance {
235 return putMinedBalance(ns, newMinedBalance)
236 }
237 return nil
238 }
239 240 // deleteUnminedTx deletes an unmined transaction from the store.
241 //
242 // NOTE: This should only be used once the transaction has been mined.
243 func (s *Store) deleteUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) (e error) {
244 for i := range rec.MsgTx.TxOut {
245 k := canonicalOutPoint(&rec.Hash, uint32(i))
246 if e := deleteRawUnminedCredit(ns, k); E.Chk(e) {
247 return e
248 }
249 }
250 return deleteRawUnmined(ns, rec.Hash[:])
251 }
252 253 // InsertTx records a transaction as belonging to a wallet's transaction history. If block is nil, the transaction is
254 // considered unspent, and the transaction's index must be unset.
255 func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) (e error) {
256 if block == nil {
257 return s.insertMemPoolTx(ns, rec)
258 }
259 return s.insertMinedTx(ns, rec, block)
260 }
261 262 // RemoveUnminedTx attempts to remove an unmined transaction from the transaction store. This is to be used in the
263 // scenario that a transaction that we attempt to rebroadcast, turns out to double spend one of our existing inputs.
264 // This function we remove the conflicting transaction identified by the tx record, and also recursively remove all
265 // transactions that depend on it.
266 func (s *Store) RemoveUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) (e error) {
267 // As we already have a tx record, we can directly call the RemoveConflict method. This will do the job of
268 // recursively removing this unmined transaction, and any transactions that depend on it.
269 return RemoveConflict(ns, rec)
270 }
271 272 // insertMinedTx inserts a new transaction record for a mined transaction into the database under the confirmed bucket.
273 // It guarantees that, if the tranasction was previously unconfirmed, then it will take care of cleaning up the
274 // unconfirmed state. All other unconfirmed double spend attempts will be removed as well.
275 func (s *Store) insertMinedTx(
276 ns walletdb.ReadWriteBucket, rec *TxRecord,
277 block *BlockMeta,
278 ) (e error) {
279 // If a transaction record for this hash and block already exists, we can exit early.
280 if _, v := existsTxRecord(ns, &rec.Hash, &block.Block); v != nil {
281 return nil
282 }
283 // If a block record does not yet exist for any transactions from this block, insert a block record first.
284 // Otherwise, update it by adding the transaction hash to the set of transactions from this block.
285 blockKey, blockValue := existsBlockRecord(ns, block.Height)
286 if blockValue == nil {
287 e = putBlockRecord(ns, block, &rec.Hash)
288 } else {
289 blockValue, e = appendRawBlockRecord(blockValue, &rec.Hash)
290 if e != nil {
291 return e
292 }
293 e = putRawBlockRecord(ns, blockKey, blockValue)
294 }
295 if e != nil {
296 return e
297 }
298 if e := putTxRecord(ns, rec, &block.Block); E.Chk(e) {
299 return e
300 }
301 // Determine if this transaction has affected our balance, and if so, update it.
302 if e := s.updateMinedBalance(ns, rec, block); E.Chk(e) {
303 return e
304 }
305 // If this transaction previously existed within the store as unmined, we'll need to remove it from the unmined
306 // bucket.
307 if v := existsRawUnmined(ns, rec.Hash[:]); v != nil {
308 I.F("marking unconfirmed transaction %v mined in block %d", &rec.Hash, block.Height)
309 if e := s.deleteUnminedTx(ns, rec); E.Chk(e) {
310 return e
311 }
312 }
313 // As there may be unconfirmed transactions that are invalidated by this transaction (either being duplicates, or
314 // double spends), remove them from the unconfirmed set. This also handles removing unconfirmed transaction spend
315 // chains if any other unconfirmed transactions spend outputs of the removed double spend.
316 return s.removeDoubleSpends(ns, rec)
317 }
318 319 // AddCredit marks a transaction record as containing a transaction output spendable by wallet. The output is added
320 // unspent, and is marked spent when a new transaction spending the output is inserted into the store.
321 //
322 // TODO(jrick): This should not be necessary. Instead, pass the indexes that are known to contain credits when a
323 // transaction or merkleblock is inserted into the store.
324 func (s *Store) AddCredit(
325 ns walletdb.ReadWriteBucket,
326 rec *TxRecord,
327 block *BlockMeta,
328 index uint32,
329 change bool,
330 ) (e error) {
331 if int(index) >= len(rec.MsgTx.TxOut) {
332 str := "transaction output does not exist"
333 return storeError(ErrInput, str, nil)
334 }
335 isNew, e := s.addCredit(ns, rec, block, index, change)
336 if e == nil && isNew && s.NotifyUnspent != nil {
337 s.NotifyUnspent(&rec.Hash, index)
338 }
339 return e
340 }
341 342 // addCredit is an AddCredit helper that runs in an update transaction. The bool return specifies whether the unspent
343 // output is newly added ( true) or a duplicate (false).
344 func (s *Store) addCredit(
345 ns walletdb.ReadWriteBucket,
346 rec *TxRecord,
347 block *BlockMeta,
348 index uint32,
349 change bool,
350 ) (bool, error) {
351 if block == nil {
352 // If the outpoint that we should mark as credit already exists within the store, either as unconfirmed or
353 // confirmed, then we have nothing left to do and can exit.
354 k := canonicalOutPoint(&rec.Hash, index)
355 if existsRawUnminedCredit(ns, k) != nil {
356 return false, nil
357 }
358 if existsRawUnspent(ns, k) != nil {
359 return false, nil
360 }
361 v := valueUnminedCredit(amt.Amount(rec.MsgTx.TxOut[index].Value), change)
362 return true, putRawUnminedCredit(ns, k, v)
363 }
364 k, v := existsCredit(ns, &rec.Hash, index, &block.Block)
365 if v != nil {
366 return false, nil
367 }
368 txOutAmt := amt.Amount(rec.MsgTx.TxOut[index].Value)
369 T.F(
370 "marking transaction %v output %d (%v) spendable",
371 rec.Hash, index, txOutAmt,
372 )
373 cred := credit{
374 outPoint: wire.OutPoint{
375 Hash: rec.Hash,
376 Index: index,
377 },
378 block: block.Block,
379 amount: txOutAmt,
380 change: change,
381 spentBy: indexedIncidence{index: ^uint32(0)},
382 }
383 v = valueUnspentCredit(&cred)
384 e := putRawCredit(ns, k, v)
385 if e != nil {
386 return false, e
387 }
388 minedBalance, e := fetchMinedBalance(ns)
389 if e != nil {
390 return false, e
391 }
392 e = putMinedBalance(ns, minedBalance+txOutAmt)
393 if e != nil {
394 return false, e
395 }
396 return true, putUnspent(ns, &cred.outPoint, &block.Block)
397 }
398 399 // Rollback removes all blocks at height onwards, moving any transactions within each block to the unconfirmed pool.
400 func (s *Store) Rollback(ns walletdb.ReadWriteBucket, height int32) (e error) {
401 return s.rollback(ns, height)
402 }
403 func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) (e error) {
404 minedBalance, e := fetchMinedBalance(ns)
405 if e != nil {
406 return e
407 }
408 // Keep track of all credits that were removed from coinbase transactions. After detaching all blocks, if any
409 // transaction record exists in unmined that spends these outputs, remove them and their spend chains.
410 //
411 // It is necessary to keep these in memory and fix the unmined transactions later since blocks are removed in
412 // increasing order.
413 var coinBaseCredits []wire.OutPoint
414 var heightsToRemove []int32
415 it := makeReverseBlockIterator(ns)
416 for it.prev() {
417 b := &it.elem
418 if it.elem.Height < height {
419 break
420 }
421 heightsToRemove = append(heightsToRemove, it.elem.Height)
422 T.F("rolling back %d transactions from block %v height %d", len(b.transactions), b.Hash, b.Height)
423 for i := range b.transactions {
424 txHash := &b.transactions[i]
425 recKey := keyTxRecord(txHash, &b.Block)
426 recVal := existsRawTxRecord(ns, recKey)
427 var rec TxRecord
428 e = readRawTxRecord(txHash, recVal, &rec)
429 if e != nil {
430 return e
431 }
432 e = deleteTxRecord(ns, txHash, &b.Block)
433 if e != nil {
434 return e
435 }
436 // Handle coinbase transactions specially since they are not moved to the unconfirmed store. A coinbase
437 // cannot contain any debits, but all credits should be removed and the mined balance decremented.
438 if blockchain.IsCoinBaseTx(&rec.MsgTx) {
439 op := wire.OutPoint{Hash: rec.Hash}
440 for i, output := range rec.MsgTx.TxOut {
441 k, v := existsCredit(
442 ns, &rec.Hash,
443 uint32(i), &b.Block,
444 )
445 if v == nil {
446 continue
447 }
448 op.Index = uint32(i)
449 coinBaseCredits = append(coinBaseCredits, op)
450 unspentKey, credKey := existsUnspent(ns, &op)
451 if credKey != nil {
452 minedBalance -= amt.Amount(output.Value)
453 e = deleteRawUnspent(ns, unspentKey)
454 if e != nil {
455 return e
456 }
457 }
458 e = deleteRawCredit(ns, k)
459 if e != nil {
460 return e
461 }
462 }
463 continue
464 }
465 e = putRawUnmined(ns, txHash[:], recVal)
466 if e != nil {
467 return e
468 }
469 // For each debit recorded for this transaction, mark the credit it spends as unspent (as long as it still
470 // exists) and delete the debit. The previous output is recorded in the unconfirmed store for every previous
471 // output, not just debits.
472 for i, input := range rec.MsgTx.TxIn {
473 prevOut := &input.PreviousOutPoint
474 prevOutKey := canonicalOutPoint(
475 &prevOut.Hash,
476 prevOut.Index,
477 )
478 e = putRawUnminedInput(ns, prevOutKey, rec.Hash[:])
479 if e != nil {
480 return e
481 }
482 // If this input is a debit, remove the debit record and mark the credit that it spent as unspent,
483 // incrementing the mined balance.
484 var debKey, credKey []byte
485 debKey, credKey, e = existsDebit(
486 ns,
487 &rec.Hash, uint32(i), &b.Block,
488 )
489 if e != nil {
490 return e
491 }
492 if debKey == nil {
493 continue
494 }
495 // unspendRawCredit does not error in case the no credit exists for this key, but this behavior is
496 // correct. Since blocks are removed in increasing order, this credit may have already been removed from
497 // a previously removed transaction record in this rollback.
498 var amount amt.Amount
499 amount, e = unspendRawCredit(ns, credKey)
500 if e != nil {
501 return e
502 }
503 e = deleteRawDebit(ns, debKey)
504 if e != nil {
505 return e
506 }
507 // If the credit was previously removed in the rollback, the credit amount is zero. Only mark the
508 // previously spent credit as unspent if it still exists.
509 if amount == 0 {
510 continue
511 }
512 var unspentVal []byte
513 unspentVal, e = fetchRawCreditUnspentValue(credKey)
514 if e != nil {
515 return e
516 }
517 minedBalance += amount
518 e = putRawUnspent(ns, prevOutKey, unspentVal)
519 if e != nil {
520 return e
521 }
522 }
523 // For each detached non-coinbase credit, move the credit output to unmined. If the credit is marked
524 // unspent, it is removed from the utxo set and the mined balance is decremented.
525 //
526 // TODO: use a credit iterator
527 for i, output := range rec.MsgTx.TxOut {
528 k, v := existsCredit(
529 ns, &rec.Hash, uint32(i),
530 &b.Block,
531 )
532 if v == nil {
533 continue
534 }
535 var amountChange amt.Amount
536 change := false
537 amountChange, change, e = fetchRawCreditAmountChange(v)
538 if e != nil {
539 return e
540 }
541 outPointKey := canonicalOutPoint(&rec.Hash, uint32(i))
542 unminedCredVal := valueUnminedCredit(amountChange, change)
543 e = putRawUnminedCredit(ns, outPointKey, unminedCredVal)
544 if e != nil {
545 return e
546 }
547 e = deleteRawCredit(ns, k)
548 if e != nil {
549 return e
550 }
551 credKey := existsRawUnspent(ns, outPointKey)
552 if credKey != nil {
553 minedBalance -= amt.Amount(output.Value)
554 e = deleteRawUnspent(ns, outPointKey)
555 if e != nil {
556 return e
557 }
558 }
559 }
560 }
561 // reposition cursor before deleting this k/v pair and advancing to the previous.
562 it.reposition(it.elem.Height)
563 // Avoid cursor deletion until bolt issue #620 is resolved.
564 //
565 // e = it.delete() if e != nil {
566 // return e
567 // }
568 }
569 if it.err != nil {
570 return it.err
571 }
572 // Delete the block records outside of the iteration since cursor deletion is broken.
573 for _, h := range heightsToRemove {
574 e = deleteBlockRecord(ns, h)
575 if e != nil {
576 return e
577 }
578 }
579 for _, op := range coinBaseCredits {
580 opKey := canonicalOutPoint(&op.Hash, op.Index)
581 unminedSpendTxHashKeys := fetchUnminedInputSpendTxHashes(ns, opKey)
582 for _, unminedSpendTxHashKey := range unminedSpendTxHashKeys {
583 unminedVal := existsRawUnmined(ns, unminedSpendTxHashKey[:])
584 // If the spending transaction spends multiple outputs
585 // from the same transaction, we'll find duplicate
586 // entries within the store, so it's possible we're
587 // unable to find it if the conflicts have already been
588 // removed in a previous iteration.
589 if unminedVal == nil {
590 continue
591 }
592 var unminedRec TxRecord
593 unminedRec.Hash = unminedSpendTxHashKey
594 e = readRawTxRecord(&unminedRec.Hash, unminedVal, &unminedRec)
595 if e != nil {
596 return e
597 }
598 D.F(
599 "transaction %v spends a removed coinbase output -- removing as well %s",
600 unminedRec.Hash,
601 )
602 e = RemoveConflict(ns, &unminedRec)
603 if e != nil {
604 return e
605 }
606 }
607 }
608 return putMinedBalance(ns, minedBalance)
609 }
610 611 func // UnspentOutputs returns all unspent received transaction outputs.
612 // The order is undefined.
613 (s *Store) UnspentOutputs(ns walletdb.ReadBucket) ([]Credit, error) {
614 var unspent []Credit
615 var op wire.OutPoint
616 var block Block
617 e := ns.NestedReadBucket(bucketUnspent).ForEach(
618 func(k, v []byte) (e error) {
619 e = readCanonicalOutPoint(k, &op)
620 if e != nil {
621 return e
622 }
623 if existsRawUnminedInput(ns, k) != nil {
624 // Output is spent by an unmined transaction.
625 // Skip this k/v pair.
626 return nil
627 }
628 e = readUnspentBlock(v, &block)
629 if e != nil {
630 return e
631 }
632 blockTime, e := fetchBlockTime(ns, block.Height)
633 if e != nil {
634 return e
635 }
636 // TODO(jrick): reading the entire transaction should
637 // be avoidable. Creating the credit only requires the
638 // output amount and pkScript.
639 rec, e := fetchTxRecord(ns, &op.Hash, &block)
640 if e != nil {
641 return e
642 }
643 txOut := rec.MsgTx.TxOut[op.Index]
644 cred := Credit{
645 OutPoint: op,
646 BlockMeta: BlockMeta{
647 Block: block,
648 Time: blockTime,
649 },
650 Amount: amt.Amount(txOut.Value),
651 PkScript: txOut.PkScript,
652 Received: rec.Received,
653 FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx),
654 }
655 unspent = append(unspent, cred)
656 return nil
657 },
658 )
659 if e != nil {
660 if _, ok := e.(TxMgrError); ok {
661 return nil, e
662 }
663 str := "failed iterating unspent bucket"
664 return nil, storeError(ErrDatabase, str, e)
665 }
666 e = ns.NestedReadBucket(bucketUnminedCredits).ForEach(
667 func(k, v []byte) (e error) {
668 if existsRawUnminedInput(ns, k) != nil {
669 // Output is spent by an unmined transaction.
670 // Skip to next unmined credit.
671 return nil
672 }
673 e = readCanonicalOutPoint(k, &op)
674 if e != nil {
675 return e
676 }
677 // TODO(jrick): Reading/parsing the entire transaction record
678 // just for the output amount and script can be avoided.
679 recVal := existsRawUnmined(ns, op.Hash[:])
680 var rec TxRecord
681 e = readRawTxRecord(&op.Hash, recVal, &rec)
682 if e != nil {
683 return e
684 }
685 txOut := rec.MsgTx.TxOut[op.Index]
686 cred := Credit{
687 OutPoint: op,
688 BlockMeta: BlockMeta{
689 Block: Block{Height: -1},
690 },
691 Amount: amt.Amount(txOut.Value),
692 PkScript: txOut.PkScript,
693 Received: rec.Received,
694 FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx),
695 }
696 unspent = append(unspent, cred)
697 return nil
698 },
699 )
700 if e != nil {
701 if _, ok := e.(TxMgrError); ok {
702 return nil, e
703 }
704 str := "failed iterating unmined credits bucket"
705 return nil, storeError(ErrDatabase, str, e)
706 }
707 return unspent, nil
708 }
709 710 func // Balance returns the spendable wallet balance (total value of all unspent
711 // transaction outputs) given a minimum of minConf confirmations, calculated
712 // at a current chain height of curHeight. Coinbase outputs are only included
713 // in the balance if maturity has been reached.
714 //
715 // Balance may return unexpected results if syncHeight is lower than the block
716 // height of the most recent mined transaction in the store.
717 (s *Store) Balance(ns walletdb.ReadBucket, minConf int32, syncHeight int32) (amt.Amount, error) {
718 bal, e := fetchMinedBalance(ns)
719 if e != nil {
720 return 0, e
721 }
722 // Subtract the balance for each credit that is spent by an unmined
723 // transaction.
724 var op wire.OutPoint
725 var block Block
726 e = ns.NestedReadBucket(bucketUnspent).ForEach(
727 func(k, v []byte) (e error) {
728 e = readCanonicalOutPoint(k, &op)
729 if e != nil {
730 return e
731 }
732 e = readUnspentBlock(v, &block)
733 if e != nil {
734 return e
735 }
736 if existsRawUnminedInput(ns, k) != nil {
737 _, v := existsCredit(ns, &op.Hash, op.Index, &block)
738 var amount amt.Amount
739 amount, e = fetchRawCreditAmount(v)
740 if e != nil {
741 return e
742 }
743 bal -= amount
744 }
745 return nil
746 },
747 )
748 if e != nil {
749 if _, ok := e.(TxMgrError); ok {
750 return 0, e
751 }
752 str := "failed iterating unspent outputs"
753 return 0, storeError(ErrDatabase, str, e)
754 }
755 // Decrement the balance for any unspent credit with less than
756 // minConf confirmations and any (unspent) immature coinbase credit.
757 coinbaseMaturity := int32(s.chainParams.CoinbaseMaturity)
758 stopConf := minConf
759 if coinbaseMaturity > stopConf {
760 stopConf = coinbaseMaturity
761 }
762 lastHeight := syncHeight - stopConf
763 blockIt := makeReadReverseBlockIterator(ns)
764 for blockIt.prev() {
765 block := &blockIt.elem
766 if block.Height < lastHeight {
767 break
768 }
769 for i := range block.transactions {
770 txHash := &block.transactions[i]
771 var rec *TxRecord
772 rec, e = fetchTxRecord(ns, txHash, &block.Block)
773 if e != nil {
774 return 0, e
775 }
776 numOuts := uint32(len(rec.MsgTx.TxOut))
777 for i := uint32(0); i < numOuts; i++ {
778 // Avoid double decrementing the credit amount
779 // if it was already removed for being spent by
780 // an unmined tx.
781 opKey := canonicalOutPoint(txHash, i)
782 if existsRawUnminedInput(ns, opKey) != nil {
783 continue
784 }
785 _, v := existsCredit(ns, txHash, i, &block.Block)
786 if v == nil {
787 continue
788 }
789 var amountSpent amt.Amount
790 var spent bool
791 amountSpent, spent, e = fetchRawCreditAmountSpent(v)
792 if e != nil {
793 return 0, e
794 }
795 if spent {
796 continue
797 }
798 confs := syncHeight - block.Height + 1
799 if confs < minConf || (blockchain.IsCoinBaseTx(&rec.MsgTx) &&
800 confs < coinbaseMaturity) {
801 bal -= amountSpent
802 }
803 }
804 }
805 }
806 if blockIt.err != nil {
807 return 0, blockIt.err
808 }
809 // If unmined outputs are included, increment the balance for each
810 // output that is unspent.
811 if minConf == 0 {
812 e = ns.NestedReadBucket(bucketUnminedCredits).ForEach(
813 func(k, v []byte) (e error) {
814 if existsRawUnminedInput(ns, k) != nil {
815 // Output is spent by an unmined transaction.
816 // Skip to next unmined credit.
817 return nil
818 }
819 amount, e := fetchRawUnminedCreditAmount(v)
820 if e != nil {
821 return e
822 }
823 bal += amount
824 return nil
825 },
826 )
827 if e != nil {
828 if _, ok := e.(TxMgrError); ok {
829 return 0, e
830 }
831 str := "failed to iterate over unmined credits bucket"
832 return 0, storeError(ErrDatabase, str, e)
833 }
834 }
835 return bal, nil
836 }
837