unconfirmed.go raw

   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