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

     1  // Copyright (c) 2013-2015 The btcsuite developers
     2  // Copyright (c) 2015-2021 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 wallet
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  	"time"
    12  
    13  	"decred.org/dcrwallet/v3/deployments"
    14  	"decred.org/dcrwallet/v3/errors"
    15  	"decred.org/dcrwallet/v3/wallet/txrules"
    16  	"decred.org/dcrwallet/v3/wallet/udb"
    17  	"decred.org/dcrwallet/v3/wallet/walletdb"
    18  	"github.com/decred/dcrd/blockchain/stake/v5"
    19  	blockchain "github.com/decred/dcrd/blockchain/standalone/v2"
    20  	"github.com/decred/dcrd/chaincfg/chainhash"
    21  	"github.com/decred/dcrd/dcrutil/v4"
    22  	gcs2 "github.com/decred/dcrd/gcs/v4"
    23  	"github.com/decred/dcrd/txscript/v4"
    24  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    25  	"github.com/decred/dcrd/txscript/v4/stdscript"
    26  	"github.com/decred/dcrd/wire"
    27  )
    28  
    29  func (w *Wallet) extendMainChain(ctx context.Context, op errors.Op, dbtx walletdb.ReadWriteTx,
    30  	header *wire.BlockHeader, f *gcs2.FilterV2, transactions []*wire.MsgTx) ([]wire.OutPoint, error) {
    31  	txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
    32  
    33  	blockHash := header.BlockHash()
    34  
    35  	// Enforce checkpoints
    36  	height := int32(header.Height)
    37  	ckpt := CheckpointHash(w.chainParams.Net, height)
    38  	if ckpt != nil && blockHash != *ckpt {
    39  		err := errors.Errorf("block hash %v does not satisify "+
    40  			"checkpoint hash %v for height %v", blockHash,
    41  			ckpt, height)
    42  		return nil, errors.E(errors.Consensus, err)
    43  	}
    44  
    45  	// Propagate the error unless this block is already included in the main
    46  	// chain.
    47  	err := w.txStore.ExtendMainChain(txmgrNs, header, f)
    48  	if err != nil && !errors.Is(err, errors.Exist) {
    49  		return nil, errors.E(op, err)
    50  	}
    51  
    52  	// Notify interested clients of the connected block.
    53  	w.NtfnServer.notifyAttachedBlock(dbtx, header, &blockHash)
    54  
    55  	blockMeta, err := w.txStore.GetBlockMetaForHash(txmgrNs, &blockHash)
    56  	if err != nil {
    57  		return nil, errors.E(op, err)
    58  	}
    59  
    60  	var watch []wire.OutPoint
    61  	for _, tx := range transactions {
    62  		// In manual ticket mode, tickets are only ever added to the
    63  		// wallet using AddTransaction.  Skip over any relevant tickets
    64  		// seen in this block unless they already exist in the wallet.
    65  		if w.manualTickets && stake.IsSStx(tx) {
    66  			txHash := tx.TxHash()
    67  			if !w.txStore.ExistsTx(txmgrNs, &txHash) {
    68  				continue
    69  			}
    70  		}
    71  
    72  		rec, err := udb.NewTxRecordFromMsgTx(tx, time.Now())
    73  		if err != nil {
    74  			return nil, errors.E(op, err)
    75  		}
    76  		ops, err := w.processTransactionRecord(ctx, dbtx, rec, header, &blockMeta)
    77  		if err != nil {
    78  			return nil, errors.E(op, err)
    79  		}
    80  		watch = append(watch, ops...)
    81  	}
    82  
    83  	return watch, nil
    84  }
    85  
    86  // ChainSwitch updates the wallet's main chain, either by extending the chain
    87  // with new blocks, or switching to a better sidechain.  A sidechain for removed
    88  // blocks (if any) is returned.  If relevantTxs is non-nil, the block marker for
    89  // the latest block with processed transactions is updated for the new tip
    90  // block.
    91  func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain []*BlockNode,
    92  	relevantTxs map[chainhash.Hash][]*wire.MsgTx) ([]*BlockNode, error) {
    93  	const op errors.Op = "wallet.ChainSwitch"
    94  
    95  	if len(chain) == 0 {
    96  		return nil, errors.E(op, errors.Invalid, "zero-length chain")
    97  	}
    98  
    99  	chainTipChanges := &MainTipChangedNotification{
   100  		AttachedBlocks: make([]*chainhash.Hash, 0, len(chain)),
   101  		DetachedBlocks: nil,
   102  		NewHeight:      int32(chain[len(chain)-1].Header.Height),
   103  	}
   104  
   105  	sideChainForkHeight := int32(chain[0].Header.Height)
   106  	var prevChain []*BlockNode
   107  
   108  	newWork := chain[len(chain)-1].workSum
   109  	oldWork := new(big.Int)
   110  
   111  	w.lockedOutpointMu.Lock()
   112  
   113  	var watchOutPoints []wire.OutPoint
   114  	err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   115  		txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
   116  
   117  		tipHash, tipHeight := w.txStore.MainChainTip(dbtx)
   118  
   119  		if tipHash == *chain[len(chain)-1].Hash {
   120  			return nil
   121  		}
   122  
   123  		if sideChainForkHeight <= tipHeight {
   124  			chainTipChanges.DetachedBlocks = make([]*chainhash.Hash, tipHeight-sideChainForkHeight+1)
   125  			prevChain = make([]*BlockNode, tipHeight-sideChainForkHeight+1)
   126  			for i := tipHeight; i >= sideChainForkHeight; i-- {
   127  				hash, err := w.txStore.GetMainChainBlockHashForHeight(txmgrNs, i)
   128  				if err != nil {
   129  					return err
   130  				}
   131  				header, err := w.txStore.GetBlockHeader(dbtx, &hash)
   132  				if err != nil {
   133  					return err
   134  				}
   135  				_, filter, err := w.txStore.CFilterV2(dbtx, &hash)
   136  				if err != nil {
   137  					return err
   138  				}
   139  
   140  				// DetachedBlocks and prevChain are sorted in order of increasing heights.
   141  				chainTipChanges.DetachedBlocks[i-sideChainForkHeight] = &hash
   142  				prevChain[i-sideChainForkHeight] = NewBlockNode(header, &hash, filter)
   143  
   144  				// For transaction notifications, the blocks are notified in reverse
   145  				// height order.
   146  				w.NtfnServer.notifyDetachedBlock(header)
   147  
   148  				oldWork.Add(oldWork, blockchain.CalcWork(header.Bits))
   149  			}
   150  
   151  			if newWork.Cmp(oldWork) != 1 {
   152  				return errors.Errorf("failed reorganize: sidechain ending at block %v has less total work "+
   153  					"than the main chain tip block %v", chain[len(chain)-1].Hash, &tipHash)
   154  			}
   155  
   156  			// Remove blocks on the current main chain that are at or above the
   157  			// height of the block that begins the side chain.
   158  			err := w.txStore.Rollback(dbtx, sideChainForkHeight)
   159  			if err != nil {
   160  				return err
   161  			}
   162  		}
   163  
   164  		for _, n := range chain {
   165  			if voteVersion(w.chainParams) < n.Header.StakeVersion {
   166  				log.Warnf("Old vote version detected (v%v), please update your "+
   167  					"wallet to the latest version.", voteVersion(w.chainParams))
   168  			}
   169  
   170  			watch, err := w.extendMainChain(ctx, op, dbtx, n.Header, n.FilterV2, relevantTxs[*n.Hash])
   171  			if err != nil {
   172  				return err
   173  			}
   174  			watchOutPoints = append(watchOutPoints, watch...)
   175  
   176  			// Add the block hash to the notification.
   177  			chainTipChanges.AttachedBlocks = append(chainTipChanges.AttachedBlocks, n.Hash)
   178  		}
   179  
   180  		if relevantTxs != nil {
   181  			// To avoid skipped blocks, the marker is not advanced if there is a
   182  			// gap between the existing rescan point (main chain fork point of
   183  			// the current marker) and the first block attached in this chain
   184  			// switch.
   185  			r, err := w.rescanPoint(dbtx)
   186  			if err != nil {
   187  				return err
   188  			}
   189  			rHeader, err := w.txStore.GetBlockHeader(dbtx, r)
   190  			if err != nil {
   191  				return err
   192  			}
   193  			if !(rHeader.Height+1 < chain[0].Header.Height) {
   194  				marker := chain[len(chain)-1].Hash
   195  				log.Debugf("Updating processed txs block marker to %v", marker)
   196  				err := w.txStore.UpdateProcessedTxsBlockMarker(dbtx, marker)
   197  				if err != nil {
   198  					return err
   199  				}
   200  			}
   201  		}
   202  
   203  		// Prune unmined transactions that don't belong on the extended chain.
   204  		// An error here is not fatal and should just be logged.
   205  		//
   206  		// TODO: The stake difficulty passed here is not correct.  This must be
   207  		// the difficulty of the next block, not the tip block.
   208  		tip := chain[len(chain)-1]
   209  		hashes, err := w.txStore.PruneUnmined(dbtx, tip.Header.SBits)
   210  		if err != nil {
   211  			log.Errorf("Failed to prune unmined transactions when "+
   212  				"connecting block height %v: %v", tip.Header.Height, err)
   213  		}
   214  
   215  		for _, hash := range hashes {
   216  			w.NtfnServer.notifyRemovedTransaction(*hash)
   217  		}
   218  		return nil
   219  	})
   220  	w.lockedOutpointMu.Unlock()
   221  	if err != nil {
   222  		return nil, errors.E(op, err)
   223  	}
   224  
   225  	if len(chainTipChanges.AttachedBlocks) != 0 {
   226  		w.recentlyPublishedMu.Lock()
   227  		for _, node := range chain {
   228  			for _, tx := range relevantTxs[*node.Hash] {
   229  				txHash := tx.TxHash()
   230  				delete(w.recentlyPublished, txHash)
   231  			}
   232  		}
   233  		w.recentlyPublishedMu.Unlock()
   234  	}
   235  
   236  	if n, err := w.NetworkBackend(); err == nil {
   237  		_, err = w.watchHDAddrs(ctx, false, n)
   238  		if err != nil {
   239  			return nil, errors.E(op, err)
   240  		}
   241  
   242  		if len(watchOutPoints) > 0 {
   243  			err = n.LoadTxFilter(ctx, false, nil, watchOutPoints)
   244  			if err != nil {
   245  				log.Errorf("Failed to watch outpoints: %v", err)
   246  			}
   247  		}
   248  	}
   249  
   250  	forest.PruneTree(chain[0].Hash)
   251  	forest.Prune(int32(chain[len(chain)-1].Header.Height), w.chainParams)
   252  
   253  	w.NtfnServer.notifyMainChainTipChanged(chainTipChanges)
   254  	w.NtfnServer.sendAttachedBlockNotification(ctx)
   255  
   256  	return prevChain, nil
   257  }
   258  
   259  // evaluateStakePoolTicket evaluates a stake pool ticket to see if it's
   260  // acceptable to the stake pool. The ticket must pay out to the stake
   261  // pool cold wallet, and must have a sufficient fee.
   262  func (w *Wallet) evaluateStakePoolTicket(rec *udb.TxRecord, blockHeight int32, poolUser stdaddr.Address) bool {
   263  	tx := rec.MsgTx
   264  
   265  	// Check the first commitment output (txOuts[1])
   266  	// and ensure that the address found there exists
   267  	// in the list of approved addresses. Also ensure
   268  	// that the fee exists and is of the amount
   269  	// requested by the pool.
   270  	commitmentOut := tx.TxOut[1]
   271  	commitAddr, err := stake.AddrFromSStxPkScrCommitment(
   272  		commitmentOut.PkScript, w.chainParams)
   273  	if err != nil {
   274  		log.Warnf("Cannot parse commitment address from ticket %v: %v",
   275  			&rec.Hash, err)
   276  		return false
   277  	}
   278  
   279  	// Extract the fee from the ticket.
   280  	in := dcrutil.Amount(0)
   281  	for i := range tx.TxOut {
   282  		if i%2 != 0 {
   283  			commitAmt, err := stake.AmountFromSStxPkScrCommitment(
   284  				tx.TxOut[i].PkScript)
   285  			if err != nil {
   286  				log.Warnf("Cannot parse commitment amount for output %i from ticket %v: %v",
   287  					i, &rec.Hash, err)
   288  				return false
   289  			}
   290  			in += commitAmt
   291  		}
   292  	}
   293  	out := dcrutil.Amount(0)
   294  	for i := range tx.TxOut {
   295  		out += dcrutil.Amount(tx.TxOut[i].Value)
   296  	}
   297  	fees := in - out
   298  
   299  	_, exists := w.stakePoolColdAddrs[commitAddr.String()]
   300  	if exists {
   301  		commitAmt, err := stake.AmountFromSStxPkScrCommitment(
   302  			commitmentOut.PkScript)
   303  		if err != nil {
   304  			log.Warnf("Cannot parse commitment amount from ticket %v: %v", &rec.Hash, err)
   305  			return false
   306  		}
   307  
   308  		// Calculate the fee required based on the current
   309  		// height and the required amount from the pool.
   310  		feeNeeded := txrules.StakePoolTicketFee(dcrutil.Amount(
   311  			tx.TxOut[0].Value), fees, blockHeight, w.poolFees,
   312  			w.chainParams, false)
   313  		if commitAmt < feeNeeded {
   314  			log.Warnf("User %s submitted ticket %v which "+
   315  				"has less fees than are required to use this "+
   316  				"stake pool and is being skipped (required: %v"+
   317  				", found %v)", commitAddr,
   318  				tx.TxHash(), feeNeeded, commitAmt)
   319  
   320  			// Reject the entire transaction if it didn't
   321  			// pay the pool server fees.
   322  			return false
   323  		}
   324  	} else {
   325  		log.Warnf("Unknown pool commitment address %s for ticket %v",
   326  			commitAddr, tx.TxHash())
   327  		return false
   328  	}
   329  
   330  	log.Debugf("Accepted valid stake pool ticket %v committing %v in fees",
   331  		tx.TxHash(), tx.TxOut[0].Value)
   332  
   333  	return true
   334  }
   335  
   336  // AddTransaction stores tx, marking it as mined in the block described by
   337  // blockHash, or recording it to the wallet's mempool when nil.
   338  //
   339  // This method will always add ticket transactions to the wallet, even when
   340  // configured in manual ticket mode.  It is up to network syncers to avoid
   341  // calling this method on unmined tickets.
   342  func (w *Wallet) AddTransaction(ctx context.Context, tx *wire.MsgTx, blockHash *chainhash.Hash) error {
   343  	const op errors.Op = "wallet.AddTransaction"
   344  
   345  	w.recentlyPublishedMu.Lock()
   346  	_, recent := w.recentlyPublished[tx.TxHash()]
   347  	w.recentlyPublishedMu.Unlock()
   348  	if recent {
   349  		return nil
   350  	}
   351  
   352  	// Prevent recording unmined tspends since they need to go through
   353  	// voting for potentially a long time.
   354  	if isTreasurySpend(tx) && blockHash == nil {
   355  		log.Debugf("Ignoring unmined TSPend %s", tx.TxHash())
   356  		return nil
   357  	}
   358  
   359  	w.lockedOutpointMu.Lock()
   360  	var watchOutPoints []wire.OutPoint
   361  	err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   362  		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   363  
   364  		rec, err := udb.NewTxRecordFromMsgTx(tx, time.Now())
   365  		if err != nil {
   366  			return err
   367  		}
   368  
   369  		// Prevent orphan votes from entering the wallet's unmined transaction
   370  		// set.
   371  		if isVote(&rec.MsgTx) && blockHash == nil {
   372  			votedBlock, _ := stake.SSGenBlockVotedOn(&rec.MsgTx)
   373  			tipBlock, _ := w.txStore.MainChainTip(dbtx)
   374  			if votedBlock != tipBlock {
   375  				log.Debugf("Rejected unmined orphan vote %v which votes on block %v",
   376  					&rec.Hash, &votedBlock)
   377  				return nil
   378  			}
   379  		}
   380  
   381  		var header *wire.BlockHeader
   382  		var meta *udb.BlockMeta
   383  		switch {
   384  		case blockHash != nil:
   385  			inChain, _ := w.txStore.BlockInMainChain(dbtx, blockHash)
   386  			if !inChain {
   387  				break
   388  			}
   389  			header, err = w.txStore.GetBlockHeader(dbtx, blockHash)
   390  			if err != nil {
   391  				return err
   392  			}
   393  			meta = new(udb.BlockMeta)
   394  			*meta, err = w.txStore.GetBlockMetaForHash(txmgrNs, blockHash)
   395  			if err != nil {
   396  				return err
   397  			}
   398  		}
   399  
   400  		watchOutPoints, err = w.processTransactionRecord(ctx, dbtx, rec, header, meta)
   401  		return err
   402  	})
   403  	w.lockedOutpointMu.Unlock()
   404  	if err != nil {
   405  		return errors.E(op, err)
   406  	}
   407  	if n, err := w.NetworkBackend(); err == nil && len(watchOutPoints) > 0 {
   408  		_, err := w.watchHDAddrs(ctx, false, n)
   409  		if err != nil {
   410  			return errors.E(op, err)
   411  		}
   412  		if len(watchOutPoints) > 0 {
   413  			err = n.LoadTxFilter(ctx, false, nil, watchOutPoints)
   414  			if err != nil {
   415  				log.Errorf("Failed to watch outpoints: %v", err)
   416  			}
   417  		}
   418  	}
   419  	return nil
   420  }
   421  
   422  func (w *Wallet) processTransactionRecord(ctx context.Context, dbtx walletdb.ReadWriteTx, rec *udb.TxRecord,
   423  	header *wire.BlockHeader, blockMeta *udb.BlockMeta) (watchOutPoints []wire.OutPoint, err error) {
   424  
   425  	const op errors.Op = "wallet.processTransactionRecord"
   426  
   427  	addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
   428  	stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey)
   429  	txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
   430  
   431  	height := int32(-1)
   432  	if header != nil {
   433  		height = int32(header.Height)
   434  	}
   435  
   436  	// At the moment all notified transactions are assumed to actually be
   437  	// relevant.  This assumption will not hold true when SPV support is
   438  	// added, but until then, simply insert the transaction because there
   439  	// should either be one or more relevant inputs or outputs.
   440  	if header == nil {
   441  		err = w.txStore.InsertMemPoolTx(dbtx, rec)
   442  		if errors.Is(err, errors.Exist) {
   443  			log.Warnf("Refusing to add unmined transaction %v since same "+
   444  				"transaction already exists mined", &rec.Hash)
   445  			return nil, nil
   446  		}
   447  	} else {
   448  		err = w.txStore.InsertMinedTx(dbtx, rec, &blockMeta.Hash)
   449  	}
   450  	if err != nil {
   451  		return nil, errors.E(op, err)
   452  	}
   453  
   454  	// Handle incoming SStx; store them in the stake manager if we own
   455  	// the OP_SSTX tagged out, except if we're operating as a stake pool
   456  	// server. In that case, additionally consider the first commitment
   457  	// output as well.
   458  	if w.stakePoolEnabled && header != nil && rec.TxType == stake.TxTypeSStx {
   459  		// Errors don't matter here.  If addrs is nil, the range below
   460  		// does nothing.
   461  		txOut := rec.MsgTx.TxOut[0]
   462  		_, addrs := stdscript.ExtractAddrs(txOut.Version, txOut.PkScript, w.chainParams)
   463  		insert := false
   464  		for _, addr := range addrs {
   465  			switch addr := addr.(type) {
   466  			case stdaddr.Hash160er:
   467  				if !w.manager.ExistsHash160(addrmgrNs, addr.Hash160()[:]) {
   468  					continue
   469  				}
   470  			default:
   471  				continue
   472  			}
   473  
   474  			// We are operating as a stake pool. The below
   475  			// function will ONLY add the ticket into the
   476  			// stake pool if it has been found within a
   477  			// block.
   478  			if header == nil {
   479  				break
   480  			}
   481  
   482  			if w.evaluateStakePoolTicket(rec, height, addr) {
   483  				// Be sure to insert this into the user's stake
   484  				// pool entry into the stake manager.
   485  				poolTicket := &udb.PoolTicket{
   486  					Ticket:       rec.Hash,
   487  					HeightTicket: uint32(height),
   488  					Status:       udb.TSImmatureOrLive,
   489  				}
   490  				err := w.stakeMgr.UpdateStakePoolUserTickets(
   491  					stakemgrNs, addr, poolTicket)
   492  				if err != nil {
   493  					log.Warnf("Failed to insert stake pool "+
   494  						"user ticket: %v", err)
   495  				}
   496  				log.Debugf("Inserted stake pool ticket %v for user %v "+
   497  					"into the stake store database", &rec.Hash, addr)
   498  
   499  				insert = true
   500  				break
   501  			}
   502  
   503  			// At this point the ticket must be invalid, so insert it into the
   504  			// list of invalid user tickets.
   505  			err := w.stakeMgr.UpdateStakePoolUserInvalTickets(
   506  				stakemgrNs, addr, &rec.Hash)
   507  			if err != nil {
   508  				log.Warnf("Failed to update pool user %v with "+
   509  					"invalid ticket %v", addr, rec.Hash)
   510  			}
   511  		}
   512  
   513  		if insert {
   514  			err := w.stakeMgr.InsertSStx(stakemgrNs, dcrutil.NewTx(&rec.MsgTx))
   515  			if err != nil {
   516  				log.Errorf("Failed to insert SStx %v"+
   517  					"into the stake store.", &rec.Hash)
   518  			}
   519  		}
   520  	}
   521  
   522  	// Handle incoming mined votes (only in stakepool mode)
   523  	if w.stakePoolEnabled && rec.TxType == stake.TxTypeSSGen && header != nil {
   524  		ticketHash := &rec.MsgTx.TxIn[1].PreviousOutPoint.Hash
   525  		txInHeight := rec.MsgTx.TxIn[1].BlockHeight
   526  		poolTicket := &udb.PoolTicket{
   527  			Ticket:       *ticketHash,
   528  			HeightTicket: txInHeight,
   529  			Status:       udb.TSVoted,
   530  			SpentBy:      rec.Hash,
   531  			HeightSpent:  uint32(height),
   532  		}
   533  
   534  		poolUser, err := w.stakeMgr.SStxAddress(stakemgrNs, ticketHash)
   535  		if err != nil {
   536  			log.Warnf("Failed to fetch stake pool user for "+
   537  				"ticket %v (voted ticket): %v", ticketHash, err)
   538  		} else {
   539  			err = w.stakeMgr.UpdateStakePoolUserTickets(
   540  				stakemgrNs, poolUser, poolTicket)
   541  			if err != nil {
   542  				log.Warnf("Failed to update stake pool ticket for "+
   543  					"stake pool user %s after voting",
   544  					poolUser)
   545  			} else {
   546  				log.Debugf("Updated voted stake pool ticket %v "+
   547  					"for user %v into the stake store database ("+
   548  					"vote hash: %v)", ticketHash, poolUser, &rec.Hash)
   549  			}
   550  		}
   551  	}
   552  
   553  	// Handle incoming mined revocations (only in stakepool mode)
   554  	if w.stakePoolEnabled && rec.TxType == stake.TxTypeSSRtx && header != nil {
   555  		txInHash := &rec.MsgTx.TxIn[0].PreviousOutPoint.Hash
   556  		txInHeight := rec.MsgTx.TxIn[0].BlockHeight
   557  		poolTicket := &udb.PoolTicket{
   558  			Ticket:       *txInHash,
   559  			HeightTicket: txInHeight,
   560  			Status:       udb.TSMissed,
   561  			SpentBy:      rec.Hash,
   562  			HeightSpent:  uint32(height),
   563  		}
   564  
   565  		poolUser, err := w.stakeMgr.SStxAddress(stakemgrNs, txInHash)
   566  		if err != nil {
   567  			log.Warnf("failed to fetch stake pool user for "+
   568  				"ticket %v (missed ticket)", txInHash)
   569  		} else {
   570  			err = w.stakeMgr.UpdateStakePoolUserTickets(
   571  				stakemgrNs, poolUser, poolTicket)
   572  			if err != nil {
   573  				log.Warnf("failed to update stake pool ticket for "+
   574  					"stake pool user %s after revoking",
   575  					poolUser)
   576  			} else {
   577  				log.Debugf("Updated missed stake pool ticket %v "+
   578  					"for user %v into the stake store database ("+
   579  					"revocation hash: %v)", txInHash, poolUser, &rec.Hash)
   580  			}
   581  		}
   582  	}
   583  
   584  	// Skip unlocking outpoints if the transaction is a vote or revocation as the lock
   585  	// is not held.
   586  	skipOutpoints := rec.TxType == stake.TxTypeSSGen || rec.TxType == stake.TxTypeSSRtx
   587  
   588  	// Handle input scripts that contain P2PKs that we care about.
   589  	for i, input := range rec.MsgTx.TxIn {
   590  		if !skipOutpoints {
   591  			prev := input.PreviousOutPoint
   592  			delete(w.lockedOutpoints, outpoint{prev.Hash, prev.Index})
   593  		}
   594  		// TODO: the prevout's actual pkScript version is needed.
   595  		if stdscript.IsMultiSigSigScript(scriptVersionAssumed, input.SignatureScript) {
   596  			rs := stdscript.MultiSigRedeemScriptFromScriptSigV0(input.SignatureScript)
   597  
   598  			class, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, rs, w.chainParams)
   599  			if class != stdscript.STMultiSig {
   600  				// This should never happen, but be paranoid.
   601  				continue
   602  			}
   603  
   604  			isRelevant := false
   605  			for _, addr := range addrs {
   606  				ma, err := w.manager.Address(addrmgrNs, addr)
   607  				if err != nil {
   608  					// Missing addresses are skipped.  Other errors should be
   609  					// propagated.
   610  					if errors.Is(err, errors.NotExist) {
   611  						continue
   612  					}
   613  					return nil, errors.E(op, err)
   614  				}
   615  				isRelevant = true
   616  				err = w.markUsedAddress(op, dbtx, ma)
   617  				if err != nil {
   618  					return nil, err
   619  				}
   620  				log.Debugf("Marked address %v used", addr)
   621  			}
   622  
   623  			// Add the script to the script databases.
   624  			// TODO Markused script address? cj
   625  			if isRelevant {
   626  				n, _ := w.NetworkBackend()
   627  				addr, err := w.manager.ImportScript(addrmgrNs, rs)
   628  				switch {
   629  				case errors.Is(err, errors.Exist):
   630  				case err != nil:
   631  					return nil, errors.E(op, err)
   632  				case n != nil:
   633  					addrs := []stdaddr.Address{addr.Address()}
   634  					err := n.LoadTxFilter(ctx, false, addrs, nil)
   635  					if err != nil {
   636  						return nil, errors.E(op, err)
   637  					}
   638  				}
   639  			}
   640  
   641  			// If we're spending a multisig outpoint we know about,
   642  			// update the outpoint. Inefficient because you deserialize
   643  			// the entire multisig output info. Consider a specific
   644  			// exists function in udb. The error here is skipped
   645  			// because the absence of an multisignature output for
   646  			// some script can not always be considered an error. For
   647  			// example, the wallet might be rescanning as called from
   648  			// the above function and so does not have the output
   649  			// included yet.
   650  			mso, err := w.txStore.GetMultisigOutput(txmgrNs, &input.PreviousOutPoint)
   651  			if mso != nil && err == nil {
   652  				err = w.txStore.SpendMultisigOut(txmgrNs, &input.PreviousOutPoint,
   653  					rec.Hash, uint32(i))
   654  				if err != nil {
   655  					return nil, errors.E(op, err)
   656  				}
   657  			}
   658  		}
   659  	}
   660  
   661  	// Check every output to determine whether it is controlled by a
   662  	// wallet key.  If so, mark the output as a credit and mark
   663  	// outpoints to watch.
   664  	for i, output := range rec.MsgTx.TxOut {
   665  		class, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, w.chainParams)
   666  		if class == stdscript.STNonStandard {
   667  			// Non-standard outputs are skipped.
   668  			continue
   669  		}
   670  		subClass, isStakeType := txrules.StakeSubScriptType(class)
   671  		if isStakeType {
   672  			class = subClass
   673  		}
   674  
   675  		isTicketCommit := rec.TxType == stake.TxTypeSStx && i%2 == 1
   676  		watchOutPoint := true
   677  		if isTicketCommit {
   678  			// For ticket commitments, decode the address stored in the pkscript
   679  			// and evaluate ownership of that.
   680  			addr, err := stake.AddrFromSStxPkScrCommitment(output.PkScript,
   681  				w.chainParams)
   682  			if err != nil {
   683  				log.Warnf("failed to decode ticket commitment script of %s:%d",
   684  					rec.Hash, i)
   685  				continue
   686  			}
   687  			addrs = []stdaddr.Address{addr}
   688  			watchOutPoint = false
   689  		} else if output.Value == 0 {
   690  			// The only case of outputs with 0 value that we need to handle are
   691  			// ticket commitments. All other outputs can be ignored.
   692  			continue
   693  		}
   694  
   695  		var tree int8
   696  		if isStakeType {
   697  			tree = 1
   698  		}
   699  		outpoint := wire.OutPoint{Hash: rec.Hash, Tree: tree}
   700  		for _, addr := range addrs {
   701  			ma, err := w.manager.Address(addrmgrNs, addr)
   702  			// Missing addresses are skipped.  Other errors should
   703  			// be propagated.
   704  			if errors.Is(err, errors.NotExist) {
   705  				continue
   706  			}
   707  			if err != nil {
   708  				return nil, errors.E(op, err)
   709  			}
   710  			if isTicketCommit {
   711  				err = w.txStore.AddTicketCommitment(txmgrNs, rec, uint32(i),
   712  					ma.Account())
   713  			} else {
   714  				err = w.txStore.AddCredit(dbtx, rec, blockMeta,
   715  					uint32(i), ma.Internal(), ma.Account())
   716  			}
   717  			if err != nil {
   718  				return nil, errors.E(op, err)
   719  			}
   720  			err = w.markUsedAddress(op, dbtx, ma)
   721  			if err != nil {
   722  				return nil, err
   723  			}
   724  			if watchOutPoint {
   725  				outpoint.Index = uint32(i)
   726  				watchOutPoints = append(watchOutPoints, outpoint)
   727  			}
   728  			log.Debugf("Marked address %v used", addr)
   729  		}
   730  
   731  		// Handle P2SH addresses that are multisignature scripts
   732  		// with keys that we own.
   733  		if class == stdscript.STScriptHash {
   734  			var expandedScript []byte
   735  			for _, addr := range addrs {
   736  				expandedScript, err = w.manager.RedeemScript(addrmgrNs, addr)
   737  				if err != nil {
   738  					log.Debugf("failed to find redeemscript for "+
   739  						"address %v in address manager: %v",
   740  						addr, err)
   741  					continue
   742  				}
   743  			}
   744  
   745  			// Otherwise, extract the actual addresses and see if any are ours.
   746  			expClass, multisigAddrs := stdscript.ExtractAddrs(scriptVersionAssumed, expandedScript, w.chainParams)
   747  
   748  			// Skip non-multisig scripts.
   749  			if expClass != stdscript.STMultiSig {
   750  				continue
   751  			}
   752  
   753  			for _, maddr := range multisigAddrs {
   754  				_, err := w.manager.Address(addrmgrNs, maddr)
   755  				// An address we own; handle accordingly.
   756  				if err == nil {
   757  					err := w.txStore.AddMultisigOut(
   758  						dbtx, rec, blockMeta, uint32(i))
   759  					if err != nil {
   760  						// This will throw if there are multiple private keys
   761  						// for this multisignature output owned by the wallet,
   762  						// so it's routed to debug.
   763  						log.Debugf("unable to add multisignature output: %v", err)
   764  					}
   765  				}
   766  			}
   767  		}
   768  	}
   769  
   770  	if (rec.TxType == stake.TxTypeSSGen) || (rec.TxType == stake.TxTypeSSRtx) {
   771  		err = w.txStore.RedeemTicketCommitments(txmgrNs, rec, blockMeta)
   772  		if err != nil {
   773  			log.Errorf("Error redeeming ticket commitments: %v", err)
   774  		}
   775  	}
   776  
   777  	// Send notification of mined or unmined transaction to any interested
   778  	// clients.
   779  	//
   780  	// TODO: Avoid the extra db hits.
   781  	if header == nil {
   782  		details, err := w.txStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil)
   783  		if err != nil {
   784  			log.Errorf("Cannot query transaction details for notifiation: %v", err)
   785  		} else {
   786  			w.NtfnServer.notifyUnminedTransaction(dbtx, details)
   787  		}
   788  	} else {
   789  		details, err := w.txStore.UniqueTxDetails(txmgrNs, &rec.Hash, &blockMeta.Block)
   790  		if err != nil {
   791  			log.Errorf("Cannot query transaction details for notifiation: %v", err)
   792  		} else {
   793  			w.NtfnServer.notifyMinedTransaction(dbtx, details, blockMeta)
   794  		}
   795  	}
   796  
   797  	return watchOutPoints, nil
   798  }
   799  
   800  // selectOwnedTickets returns a slice of tickets hashes from the tickets
   801  // argument that are owned by the wallet.
   802  //
   803  // Because votes must be created for tickets tracked by both the transaction
   804  // manager and the stake manager, this function checks both.
   805  func selectOwnedTickets(w *Wallet, dbtx walletdb.ReadTx, tickets []*chainhash.Hash) []*chainhash.Hash {
   806  	var owned []*chainhash.Hash
   807  	for _, ticketHash := range tickets {
   808  		if w.txStore.OwnTicket(dbtx, ticketHash) || w.stakeMgr.OwnTicket(ticketHash) {
   809  			owned = append(owned, ticketHash)
   810  		}
   811  	}
   812  	return owned
   813  }
   814  
   815  // VoteOnOwnedTickets creates and publishes vote transactions for all owned
   816  // tickets in the winningTicketHashes slice if wallet voting is enabled.  The
   817  // vote is only valid when voting on the block described by the passed block
   818  // hash and height.  When a network backend is associated with the wallet,
   819  // relevant commitment outputs are loaded as watched data.
   820  func (w *Wallet) VoteOnOwnedTickets(ctx context.Context, winningTicketHashes []*chainhash.Hash, blockHash *chainhash.Hash, blockHeight int32) error {
   821  	const op errors.Op = "wallet.VoteOnOwnedTickets"
   822  
   823  	if !w.votingEnabled || blockHeight < int32(w.chainParams.StakeValidationHeight)-1 {
   824  		return nil
   825  	}
   826  
   827  	n, err := w.NetworkBackend()
   828  	if err != nil {
   829  		return errors.E(op, err)
   830  	}
   831  	dcp0010Active, err := deployments.DCP0010Active(ctx, blockHeight,
   832  		w.chainParams, n)
   833  	if err != nil {
   834  		return errors.E(op, err)
   835  	}
   836  	dcp0012Active, err := deployments.DCP0012Active(ctx, blockHeight,
   837  		w.chainParams, n)
   838  	if err != nil {
   839  		return errors.E(op, err)
   840  	}
   841  
   842  	// TODO The behavior of this is not quite right if tons of blocks
   843  	// are coming in quickly, because the transaction store will end up
   844  	// out of sync with the voting channel here. This should probably
   845  	// be fixed somehow, but this should be stable for networks that
   846  	// are voting at normal block speeds.
   847  
   848  	var ticketHashes []*chainhash.Hash
   849  	var votes []*wire.MsgTx
   850  	var usedVoteBits []stake.VoteBits
   851  	defaultVoteBits := w.VoteBits()
   852  	var watchOutPoints []wire.OutPoint
   853  	err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   854  		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   855  
   856  		// Only consider tickets owned by this wallet.
   857  		ticketHashes = selectOwnedTickets(w, dbtx, winningTicketHashes)
   858  		if len(ticketHashes) == 0 {
   859  			return nil
   860  		}
   861  
   862  		votes = make([]*wire.MsgTx, len(ticketHashes))
   863  		usedVoteBits = make([]stake.VoteBits, len(ticketHashes))
   864  
   865  		addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
   866  
   867  		for i, ticketHash := range ticketHashes {
   868  			ticketPurchase, err := w.txStore.Tx(txmgrNs, ticketHash)
   869  			if err != nil && errors.Is(err, errors.NotExist) {
   870  				ticketPurchase, err = w.stakeMgr.TicketPurchase(dbtx, ticketHash)
   871  			}
   872  			if err != nil {
   873  				log.Errorf("Failed to read ticket purchase transaction for "+
   874  					"owned winning ticket %v: %v", ticketHash, err)
   875  				continue
   876  			}
   877  
   878  			// Don't create votes when this wallet doesn't have voting
   879  			// authority or the private key to vote.
   880  			owned, haveKey, err := w.hasVotingAuthority(addrmgrNs, ticketPurchase)
   881  			if err != nil {
   882  				return err
   883  			}
   884  			if !(owned && haveKey) {
   885  				continue
   886  			}
   887  
   888  			ticketVoteBits := defaultVoteBits
   889  			// Check for and use per-ticket votebits if set for this ticket.
   890  			if tvb, found := w.readDBTicketVoteBits(dbtx, ticketHash); found {
   891  				ticketVoteBits = tvb
   892  			}
   893  
   894  			// When not on mainnet, randomly disapprove blocks based
   895  			// on the disapprove percent.
   896  			dp := w.DisapprovePercent()
   897  			if dp > 0 {
   898  				if w.chainParams.Net == wire.MainNet {
   899  					log.Warnf("block disapprove percent set on mainnet")
   900  				} else if int64(dp) > randInt63n(100) {
   901  					log.Infof("Disapproving block %v voted with ticket %v",
   902  						blockHash, ticketHash)
   903  					// Set the BlockValid bit to zero,
   904  					// disapproving the block.
   905  					const blockIsValidBit = uint16(0x01)
   906  					ticketVoteBits.Bits &= ^blockIsValidBit
   907  				}
   908  			}
   909  
   910  			// Deal with treasury votes
   911  			tspends := w.GetAllTSpends(ctx)
   912  
   913  			// Dealwith consensus votes
   914  			vote, err := createUnsignedVote(ticketHash, ticketPurchase,
   915  				blockHeight, blockHash, ticketVoteBits, w.subsidyCache,
   916  				w.chainParams, dcp0010Active, dcp0012Active)
   917  			if err != nil {
   918  				log.Errorf("Failed to create vote transaction for ticket "+
   919  					"hash %v: %v", ticketHash, err)
   920  				continue
   921  			}
   922  
   923  			// Iterate over all tpends and determine if they are
   924  			// within the voting window.
   925  			tVotes := make([]byte, 0, 256)
   926  			tVotes = append(tVotes, 'T', 'V')
   927  			for _, v := range tspends {
   928  				if !blockchain.InsideTSpendWindow(int64(blockHeight),
   929  					v.Expiry, w.chainParams.TreasuryVoteInterval,
   930  					w.chainParams.TreasuryVoteIntervalMultiplier) {
   931  					continue
   932  				}
   933  
   934  				// Get policy for tspend, falling back to any
   935  				// policy for the Pi key.
   936  				tspendHash := v.TxHash()
   937  				tspendVote := w.TSpendPolicy(&tspendHash, ticketHash)
   938  				if tspendVote == stake.TreasuryVoteInvalid {
   939  					continue
   940  				}
   941  
   942  				// Append tspend hash and vote bits
   943  				tVotes = append(tVotes, tspendHash[:]...)
   944  				tVotes = append(tVotes, byte(tspendVote))
   945  			}
   946  			if len(tVotes) > 2 {
   947  				// Vote was appended. Create output and flip
   948  				// script version.
   949  				var b txscript.ScriptBuilder
   950  				b.AddOp(txscript.OP_RETURN)
   951  				b.AddData(tVotes)
   952  				tspendVoteScript, err := b.Script()
   953  				if err != nil {
   954  					// Log error and continue.
   955  					log.Errorf("Failed to create treasury "+
   956  						"vote for ticket hash %v: %v",
   957  						ticketHash, err)
   958  				} else {
   959  					// Success.
   960  					vote.AddTxOut(wire.NewTxOut(0, tspendVoteScript))
   961  					vote.Version = 3
   962  				}
   963  			}
   964  
   965  			// Sign vote and sumit.
   966  			err = w.signVote(addrmgrNs, ticketPurchase, vote)
   967  			if err != nil {
   968  				log.Errorf("Failed to sign vote for ticket hash %v: %v",
   969  					ticketHash, err)
   970  				continue
   971  			}
   972  			votes[i] = vote
   973  			usedVoteBits[i] = ticketVoteBits
   974  
   975  			watchOutPoints = w.appendRelevantOutpoints(watchOutPoints, dbtx, vote)
   976  		}
   977  		return nil
   978  	})
   979  	if err != nil {
   980  		log.Errorf("View failed: %v", errors.E(op, err))
   981  	}
   982  
   983  	// Remove nil votes without preserving order.
   984  	for i := 0; i < len(votes); {
   985  		if votes[i] == nil {
   986  			votes[i], votes[len(votes)-1] = votes[len(votes)-1], votes[i]
   987  			votes = votes[:len(votes)-1]
   988  			continue
   989  		}
   990  		i++
   991  	}
   992  
   993  	voteRecords := make([]*udb.TxRecord, 0, len(votes))
   994  	for i := range votes {
   995  		rec, err := udb.NewTxRecordFromMsgTx(votes[i], time.Now())
   996  		if err != nil {
   997  			log.Errorf("Failed to create transaction record: %v", err)
   998  			continue
   999  		}
  1000  		voteRecords = append(voteRecords, rec)
  1001  	}
  1002  	w.recentlyPublishedMu.Lock()
  1003  	for i := range voteRecords {
  1004  		w.recentlyPublished[voteRecords[i].Hash] = struct{}{}
  1005  
  1006  		log.Infof("Voting on block %v (height %v) using ticket %v "+
  1007  			"(vote hash: %v bits: %v)", blockHash, blockHeight,
  1008  			ticketHashes[i], &voteRecords[i].Hash, usedVoteBits[i].Bits)
  1009  	}
  1010  	w.recentlyPublishedMu.Unlock()
  1011  
  1012  	// Publish before recording votes in database to slightly reduce latency.
  1013  	err = n.PublishTransactions(ctx, votes...)
  1014  	if err != nil {
  1015  		log.Errorf("Failed to send one or more votes: %v", err)
  1016  	}
  1017  
  1018  	if len(watchOutPoints) > 0 {
  1019  		err := n.LoadTxFilter(ctx, false, nil, watchOutPoints)
  1020  		if err != nil {
  1021  			log.Errorf("Failed to watch outpoints: %v", err)
  1022  		}
  1023  	}
  1024  
  1025  	// w.lockedOutpointMu is intentionally not locked.
  1026  	err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
  1027  		for i := range voteRecords {
  1028  			_, err := w.processTransactionRecord(ctx, dbtx, voteRecords[i], nil, nil)
  1029  			if err != nil {
  1030  				return err
  1031  			}
  1032  		}
  1033  		return nil
  1034  	})
  1035  	if err != nil {
  1036  		return err
  1037  	}
  1038  
  1039  	if n, err := w.NetworkBackend(); err == nil {
  1040  		_, err := w.watchHDAddrs(ctx, false, n)
  1041  		if err != nil {
  1042  			return err
  1043  		}
  1044  	}
  1045  	return nil
  1046  }
  1047  
  1048  // RevokeOwnedTickets no longer revokes any tickets since revocations are now
  1049  // automatically created per DCP0009.
  1050  //
  1051  // Deprecated: this method will be removed in the next major version.
  1052  func (w *Wallet) RevokeOwnedTickets(ctx context.Context, missedTicketHashes []*chainhash.Hash) error {
  1053  	return nil
  1054  }