decred.org/dcrwallet/v3@v3.1.0/wallet/udb/txunmined.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2017 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package udb
     7  
     8  import (
     9  	"bytes"
    10  
    11  	"decred.org/dcrwallet/v3/errors"
    12  	"decred.org/dcrwallet/v3/wallet/walletdb"
    13  	"github.com/decred/dcrd/blockchain/stake/v5"
    14  	"github.com/decred/dcrd/chaincfg/chainhash"
    15  	"github.com/decred/dcrd/wire"
    16  )
    17  
    18  // InsertMemPoolTx inserts a memory pool transaction record.  It also marks
    19  // previous outputs referenced by its inputs as spent.  Errors with the
    20  // DoubleSpend code if another unmined transaction is a double spend of this
    21  // transaction.
    22  func (s *Store) InsertMemPoolTx(dbtx walletdb.ReadWriteTx, rec *TxRecord) error {
    23  	ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
    24  
    25  	_, recVal := latestTxRecord(ns, rec.Hash[:])
    26  	if recVal != nil {
    27  		return errors.E(errors.Exist, "transaction already exists mined")
    28  	}
    29  
    30  	v := existsRawUnmined(ns, rec.Hash[:])
    31  	if v != nil {
    32  		return nil
    33  	}
    34  
    35  	// Check for other unmined transactions which cause this tx to be a double
    36  	// spend.  Unlike mined transactions that double spend an unmined tx,
    37  	// existing unmined txs are not removed when inserting a double spending
    38  	// unmined tx.
    39  	//
    40  	// An exception is made for this rule for votes and revocations that double
    41  	// spend an unmined vote that doesn't vote on the tip block.
    42  	for _, input := range rec.MsgTx.TxIn {
    43  		prevOut := &input.PreviousOutPoint
    44  		k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
    45  		if v := existsRawUnminedInput(ns, k); v != nil {
    46  			var spenderHash chainhash.Hash
    47  			readRawUnminedInputSpenderHash(v, &spenderHash)
    48  
    49  			// A switch is used here instead of an if statement so it can be
    50  			// broken out of to the error below.
    51  		DoubleSpendVoteCheck:
    52  			switch rec.TxType {
    53  			case stake.TxTypeSSGen, stake.TxTypeSSRtx:
    54  				spenderVal := existsRawUnmined(ns, spenderHash[:])
    55  				spenderTxBytes := extractRawUnminedTx(spenderVal)
    56  				var spenderTx wire.MsgTx
    57  				err := spenderTx.Deserialize(bytes.NewReader(spenderTxBytes))
    58  				if err != nil {
    59  					return errors.E(errors.IO, err)
    60  				}
    61  				if stake.DetermineTxType(&spenderTx) != stake.TxTypeSSGen {
    62  					break DoubleSpendVoteCheck
    63  				}
    64  				votedBlock, _ := stake.SSGenBlockVotedOn(&spenderTx)
    65  				tipBlock, _ := s.MainChainTip(dbtx)
    66  				if votedBlock == tipBlock {
    67  					err := errors.Errorf("vote or revocation %v double spends unmined "+
    68  						"vote %v on the tip block", &rec.Hash, &spenderHash)
    69  					return errors.E(errors.DoubleSpend, err)
    70  				}
    71  
    72  				err = s.RemoveUnconfirmed(ns, &spenderTx, &spenderHash)
    73  				if err != nil {
    74  					return err
    75  				}
    76  				continue
    77  			}
    78  
    79  			err := errors.Errorf("%v conflicts with %v by double spending %v", &rec.Hash, &spenderHash, prevOut)
    80  			return errors.E(errors.DoubleSpend, err)
    81  		}
    82  	}
    83  
    84  	log.Infof("Inserting unconfirmed transaction %v", &rec.Hash)
    85  	v, err := valueTxRecord(rec)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	err = putRawUnmined(ns, rec.Hash[:], v)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if rec.Unpublished {
    94  		err = putUnpublished(ns, rec.Hash[:])
    95  		if err != nil {
    96  			return err
    97  		}
    98  	}
    99  
   100  	txType := stake.DetermineTxType(&rec.MsgTx)
   101  
   102  	for i, input := range rec.MsgTx.TxIn {
   103  		// Skip stakebases for votes.
   104  		if i == 0 && txType == stake.TxTypeSSGen {
   105  			continue
   106  		}
   107  		prevOut := &input.PreviousOutPoint
   108  		k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
   109  		err = putRawUnminedInput(ns, k, rec.Hash[:])
   110  		if err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	// If the transaction is a ticket purchase, record it in the ticket
   116  	// purchases bucket.
   117  	if txType == stake.TxTypeSStx {
   118  		tk := rec.Hash[:]
   119  		tv := existsRawTicketRecord(ns, tk)
   120  		if tv == nil {
   121  			tv = valueTicketRecord(-1)
   122  			err := putRawTicketRecord(ns, tk, tv)
   123  			if err != nil {
   124  				return err
   125  			}
   126  		}
   127  	}
   128  
   129  	// TODO: increment credit amount for each credit (but those are unknown
   130  	// here currently).
   131  
   132  	return nil
   133  }
   134  
   135  // SetPublished modifies the published state of an unmined transaction.
   136  func (s *Store) SetPublished(dbtx walletdb.ReadWriteTx, txHash *chainhash.Hash, published bool) error {
   137  	ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
   138  	if published {
   139  		return deleteUnpublished(ns, txHash[:])
   140  	}
   141  	return putUnpublished(ns, txHash[:])
   142  }
   143  
   144  // removeDoubleSpends checks for any unmined transactions which would introduce
   145  // a double spend if tx was added to the store (either as a confirmed or unmined
   146  // transaction).  Each conflicting transaction and all transactions which spend
   147  // it are recursively removed.
   148  func (s *Store) removeDoubleSpends(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
   149  	for _, input := range rec.MsgTx.TxIn {
   150  		prevOut := &input.PreviousOutPoint
   151  		prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
   152  		doubleSpendHash := existsRawUnminedInput(ns, prevOutKey)
   153  		if doubleSpendHash != nil {
   154  			var doubleSpend TxRecord
   155  			doubleSpendVal := existsRawUnmined(ns, doubleSpendHash)
   156  			copy(doubleSpend.Hash[:], doubleSpendHash) // Silly but need an array
   157  			err := readRawTxRecord(&doubleSpend.Hash, doubleSpendVal,
   158  				&doubleSpend)
   159  			if err != nil {
   160  				return err
   161  			}
   162  
   163  			log.Debugf("Removing double spending transaction %v",
   164  				doubleSpend.Hash)
   165  			err = s.RemoveUnconfirmed(ns, &doubleSpend.MsgTx, &doubleSpend.Hash)
   166  			if err != nil {
   167  				return err
   168  			}
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  // RemoveUnconfirmed removes an unmined transaction record and all spend chains
   175  // deriving from it from the store.  This is designed to remove transactions
   176  // that would otherwise result in double spend conflicts if left in the store,
   177  // and to remove transactions that spend coinbase transactions on reorgs. It
   178  // can also be used to remove old tickets that do not meet the network difficulty
   179  // and expired transactions.
   180  func (s *Store) RemoveUnconfirmed(ns walletdb.ReadWriteBucket, tx *wire.MsgTx, txHash *chainhash.Hash) error {
   181  
   182  	stxType := stake.DetermineTxType(tx)
   183  
   184  	// For each potential credit for this record, each spender (if any) must
   185  	// be recursively removed as well.  Once the spenders are removed, the
   186  	// credit is deleted.
   187  	numOuts := uint32(len(tx.TxOut))
   188  	for i := uint32(0); i < numOuts; i++ {
   189  		k := canonicalOutPoint(txHash, i)
   190  		spenderHash := existsRawUnminedInput(ns, k)
   191  		if spenderHash != nil {
   192  			var spender TxRecord
   193  			spenderVal := existsRawUnmined(ns, spenderHash)
   194  			copy(spender.Hash[:], spenderHash) // Silly but need an array
   195  			err := readRawTxRecord(&spender.Hash, spenderVal, &spender)
   196  			if err != nil {
   197  				return err
   198  			}
   199  
   200  			log.Debugf("Transaction %v is part of a removed conflict "+
   201  				"chain -- removing as well", spender.Hash)
   202  			err = s.RemoveUnconfirmed(ns, &spender.MsgTx, &spender.Hash)
   203  			if err != nil {
   204  				return err
   205  			}
   206  		}
   207  		err := deleteRawUnminedCredit(ns, k)
   208  		if err != nil {
   209  			return err
   210  		}
   211  
   212  		if (stxType == stake.TxTypeSStx) && (i%2 == 1) {
   213  			// An unconfirmed ticket leaving the store means we need to delete
   214  			// the respective commitment and its entry from the unspent
   215  			// commitments index.
   216  
   217  			// This assumes that the key to ticket commitment values in the db are
   218  			// canonicalOutPoints. If this ever changes, this needs to be changed to
   219  			// use keyTicketCommitment(...).
   220  			ktc := k
   221  			vtc := existsRawTicketCommitment(ns, ktc)
   222  			if vtc != nil {
   223  				log.Debugf("Removing unconfirmed ticket commitment %s:%d",
   224  					txHash, i)
   225  				err = deleteRawTicketCommitment(ns, ktc)
   226  				if err != nil {
   227  					return err
   228  				}
   229  				err = deleteRawUnspentTicketCommitment(ns, ktc)
   230  				if err != nil {
   231  					return err
   232  				}
   233  			}
   234  		}
   235  	}
   236  
   237  	if (stxType == stake.TxTypeSSGen) || (stxType == stake.TxTypeSSRtx) {
   238  		// An unconfirmed vote/revocation leaving the store means we need to
   239  		// unmark the commitments of the respective ticket as unminedSpent.
   240  		err := s.replaceTicketCommitmentUnminedSpent(ns, stxType, tx, false)
   241  		if err != nil {
   242  			return err
   243  		}
   244  	}
   245  
   246  	// If this tx spends any previous credits (either mined or unmined), set
   247  	// each unspent.  Mined transactions are only marked spent by having the
   248  	// output in the unmined inputs bucket.
   249  	for _, input := range tx.TxIn {
   250  		prevOut := &input.PreviousOutPoint
   251  		k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
   252  		err := deleteRawUnminedInput(ns, k)
   253  		if err != nil {
   254  			return err
   255  		}
   256  
   257  		// If a multisig output is recorded for this input, mark it unspent.
   258  		if v := existsMultisigOut(ns, k); v != nil {
   259  			vcopy := make([]byte, len(v))
   260  			copy(vcopy, v)
   261  			setMultisigOutUnSpent(vcopy)
   262  			err := putMultisigOutRawValues(ns, k, vcopy)
   263  			if err != nil {
   264  				return err
   265  			}
   266  			err = putMultisigOutUS(ns, k)
   267  			if err != nil {
   268  				return err
   269  			}
   270  		}
   271  	}
   272  
   273  	err := deleteUnpublished(ns, txHash[:])
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	return deleteRawUnmined(ns, txHash[:])
   279  }
   280  
   281  // UnminedTxs returns the transaction records for all unmined transactions
   282  // which are not known to have been mined in a block.  Transactions are
   283  // guaranteed to be sorted by their dependency order.
   284  func (s *Store) UnminedTxs(dbtx walletdb.ReadTx) ([]*TxRecord, error) {
   285  	ns := dbtx.ReadBucket(wtxmgrBucketKey)
   286  	recSet, err := s.unminedTxRecords(ns)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	recs := dependencySort(recSet)
   292  	return recs, nil
   293  }
   294  
   295  func (s *Store) unminedTxRecords(ns walletdb.ReadBucket) (map[chainhash.Hash]*TxRecord, error) {
   296  	unmined := make(map[chainhash.Hash]*TxRecord)
   297  	err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
   298  		var txHash chainhash.Hash
   299  		err := readRawUnminedHash(k, &txHash)
   300  		if err != nil {
   301  			return err
   302  		}
   303  
   304  		rec := new(TxRecord)
   305  		err = readRawTxRecord(&txHash, v, rec)
   306  		if err != nil {
   307  			return err
   308  		}
   309  		rec.Unpublished = existsUnpublished(ns, txHash[:])
   310  
   311  		unmined[rec.Hash] = rec
   312  		return nil
   313  	})
   314  	return unmined, err
   315  }
   316  
   317  // UnminedTxHashes returns the hashes of all transactions not known to have been
   318  // mined in a block.
   319  func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
   320  	return s.unminedTxHashes(ns)
   321  }
   322  
   323  func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
   324  	var hashes []*chainhash.Hash
   325  	err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
   326  		hash := new(chainhash.Hash)
   327  		err := readRawUnminedHash(k, hash)
   328  		if err == nil {
   329  			hashes = append(hashes, hash)
   330  		}
   331  		return err
   332  	})
   333  	return hashes, err
   334  }
   335  
   336  // PruneUnmined removes unmined transactions that no longer belong in the
   337  // unmined tx set.  This includes:
   338  //
   339  //   - Any transactions past a set expiry
   340  //   - Ticket purchases with a different ticket price than the passed stake
   341  //     difficulty
   342  //   - Votes that do not vote on the tip block
   343  func (s *Store) PruneUnmined(dbtx walletdb.ReadWriteTx, stakeDiff int64) ([]*chainhash.Hash, error) {
   344  	ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
   345  
   346  	tipHash, tipHeight := s.MainChainTip(dbtx)
   347  
   348  	type removeTx struct {
   349  		tx   wire.MsgTx
   350  		hash *chainhash.Hash
   351  	}
   352  	var toRemove []*removeTx
   353  
   354  	c := ns.NestedReadBucket(bucketUnmined).ReadCursor()
   355  	defer c.Close()
   356  	for k, v := c.First(); k != nil; k, v = c.Next() {
   357  		var tx wire.MsgTx
   358  		err := tx.Deserialize(bytes.NewReader(extractRawUnminedTx(v)))
   359  		if err != nil {
   360  			return nil, errors.E(errors.IO, err)
   361  		}
   362  
   363  		var expired, isTicketPurchase, isVote bool
   364  		switch {
   365  		case tx.Expiry != wire.NoExpiryValue && tx.Expiry <= uint32(tipHeight):
   366  			expired = true
   367  		case stake.IsSStx(&tx):
   368  			isTicketPurchase = true
   369  			if tx.TxOut[0].Value == stakeDiff {
   370  				continue
   371  			}
   372  		case stake.IsSSGen(&tx):
   373  			isVote = true
   374  			// This will never actually error
   375  			votedBlockHash, _ := stake.SSGenBlockVotedOn(&tx)
   376  			if votedBlockHash == tipHash {
   377  				continue
   378  			}
   379  		default:
   380  			continue
   381  		}
   382  
   383  		txHash, err := chainhash.NewHash(k)
   384  		if err != nil {
   385  			return nil, errors.E(errors.IO, err)
   386  		}
   387  
   388  		if expired {
   389  			log.Infof("Removing expired unmined transaction %v", txHash)
   390  		} else if isTicketPurchase {
   391  			log.Infof("Removing old unmined ticket purchase %v", txHash)
   392  		} else if isVote {
   393  			log.Infof("Removing missed or invalid vote %v", txHash)
   394  		}
   395  
   396  		toRemove = append(toRemove, &removeTx{tx, txHash})
   397  	}
   398  
   399  	removed := make([]*chainhash.Hash, 0, len(toRemove))
   400  	for _, r := range toRemove {
   401  		err := s.RemoveUnconfirmed(ns, &r.tx, r.hash)
   402  		if err != nil {
   403  			return removed, err
   404  		}
   405  		removed = append(removed, r.hash)
   406  	}
   407  
   408  	return removed, nil
   409  }