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

     1  // Copyright (c) 2015-2016 The btcsuite developers
     2  // Copyright (c) 2016-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  	"bytes"
    10  	"context"
    11  	"sync"
    12  
    13  	"decred.org/dcrwallet/v3/errors"
    14  	"decred.org/dcrwallet/v3/internal/compat"
    15  	"decred.org/dcrwallet/v3/wallet/udb"
    16  	"decred.org/dcrwallet/v3/wallet/walletdb"
    17  	"github.com/decred/dcrd/blockchain/stake/v5"
    18  	"github.com/decred/dcrd/chaincfg/chainhash"
    19  	"github.com/decred/dcrd/dcrutil/v4"
    20  	"github.com/decred/dcrd/hdkeychain/v3"
    21  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    22  	"github.com/decred/dcrd/txscript/v4/stdscript"
    23  	"github.com/decred/dcrd/wire"
    24  )
    25  
    26  // TODO: It would be good to send errors during notification creation to the rpc
    27  // server instead of just logging them here so the client is aware that wallet
    28  // isn't working correctly and notifications are missing.
    29  
    30  // TODO: Anything dealing with accounts here is expensive because the database
    31  // is not organized correctly for true account support, but do the slow thing
    32  // instead of the easy thing since the db can be fixed later, and we want the
    33  // api correct now.
    34  
    35  // NotificationServer is a server that interested clients may hook into to
    36  // receive notifications of changes in a wallet.  A client is created for each
    37  // registered notification.  Clients are guaranteed to receive messages in the
    38  // order wallet created them, but there is no guaranteed synchronization between
    39  // different clients.
    40  type NotificationServer struct {
    41  	transactions []chan *TransactionNotifications
    42  	// Coalesce transaction notifications since wallet previously did not add
    43  	// mined txs together.  Now it does and this can be rewritten.
    44  	currentTxNtfn             *TransactionNotifications
    45  	accountClients            []chan *AccountNotification
    46  	tipChangedClients         []chan *MainTipChangedNotification
    47  	confClients               []*ConfirmationNotificationsClient
    48  	removedTransactionClients []chan *RemovedTransactionNotification
    49  	mu                        sync.Mutex // Only protects registered clients
    50  	wallet                    *Wallet    // smells like hacks
    51  }
    52  
    53  func newNotificationServer(wallet *Wallet) *NotificationServer {
    54  	return &NotificationServer{
    55  		wallet: wallet,
    56  	}
    57  }
    58  
    59  func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails, deb udb.DebitRecord) uint32 {
    60  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
    61  	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
    62  
    63  	// TODO: Debits should record which account(s?) they
    64  	// debit from so this doesn't need to be looked up.
    65  	prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
    66  	prev, err := w.txStore.TxDetails(txmgrNs, &prevOP.Hash)
    67  	if err != nil {
    68  		log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err)
    69  		return 0
    70  	}
    71  	if prev == nil {
    72  		log.Errorf("Missing previous transaction %v", prevOP.Hash)
    73  		return 0
    74  	}
    75  	prevOut := prev.MsgTx.TxOut[prevOP.Index]
    76  	_, addrs := stdscript.ExtractAddrs(prevOut.Version, prevOut.PkScript, w.chainParams)
    77  	if len(addrs) == 0 {
    78  		return 0
    79  	}
    80  
    81  	inputAcct, err := w.manager.AddrAccount(addrmgrNs, addrs[0])
    82  	if err != nil {
    83  		log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err)
    84  		return 0
    85  	}
    86  	return inputAcct
    87  }
    88  
    89  func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails,
    90  	cred udb.CreditRecord) (account uint32, internal bool, address stdaddr.Address,
    91  	amount int64, outputScript []byte) {
    92  
    93  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
    94  
    95  	output := details.MsgTx.TxOut[cred.Index]
    96  	_, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, w.chainParams)
    97  	if len(addrs) == 0 {
    98  		return
    99  	}
   100  
   101  	ma, err := w.manager.Address(addrmgrNs, addrs[0])
   102  	if err != nil {
   103  		log.Errorf("Cannot fetch account for wallet output: %v", err)
   104  		return
   105  	}
   106  	account = ma.Account()
   107  	internal = ma.Internal()
   108  	address = ma.Address()
   109  	amount = output.Value
   110  	outputScript = output.PkScript
   111  	return
   112  }
   113  
   114  func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails) TransactionSummary {
   115  	serializedTx := details.SerializedTx
   116  	if serializedTx == nil {
   117  		var buf bytes.Buffer
   118  		buf.Grow(details.MsgTx.SerializeSize())
   119  		err := details.MsgTx.Serialize(&buf)
   120  		if err != nil {
   121  			log.Errorf("Transaction serialization: %v", err)
   122  		}
   123  		serializedTx = buf.Bytes()
   124  	}
   125  	var fee dcrutil.Amount
   126  	if len(details.Debits) == len(details.MsgTx.TxIn) {
   127  		for _, deb := range details.Debits {
   128  			fee += deb.Amount
   129  		}
   130  		for _, txOut := range details.MsgTx.TxOut {
   131  			fee -= dcrutil.Amount(txOut.Value)
   132  		}
   133  	}
   134  	var inputs []TransactionSummaryInput
   135  	if len(details.Debits) != 0 {
   136  		inputs = make([]TransactionSummaryInput, len(details.Debits))
   137  		for i, d := range details.Debits {
   138  			inputs[i] = TransactionSummaryInput{
   139  				Index:           d.Index,
   140  				PreviousAccount: lookupInputAccount(dbtx, w, details, d),
   141  				PreviousAmount:  d.Amount,
   142  			}
   143  		}
   144  	}
   145  	outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
   146  	for i := range details.MsgTx.TxOut {
   147  		credIndex := len(outputs)
   148  		mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
   149  		if !mine {
   150  			continue
   151  		}
   152  		acct, internal, address, amount, outputScript := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
   153  		output := TransactionSummaryOutput{
   154  			Index:        uint32(i),
   155  			Account:      acct,
   156  			Internal:     internal,
   157  			Amount:       dcrutil.Amount(amount),
   158  			Address:      address,
   159  			OutputScript: outputScript,
   160  		}
   161  		outputs = append(outputs, output)
   162  	}
   163  
   164  	var transactionType = TxTransactionType(&details.MsgTx)
   165  
   166  	// Use earliest of receive time or block time if the transaction is mined.
   167  	receiveTime := details.Received
   168  	if details.Height() >= 0 && details.Block.Time.Before(receiveTime) {
   169  		receiveTime = details.Block.Time
   170  	}
   171  
   172  	return TransactionSummary{
   173  		Hash:        &details.Hash,
   174  		Transaction: serializedTx,
   175  		MyInputs:    inputs,
   176  		MyOutputs:   outputs,
   177  		Fee:         fee,
   178  		Timestamp:   receiveTime.Unix(),
   179  		Type:        transactionType,
   180  	}
   181  }
   182  
   183  func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]dcrutil.Amount) error {
   184  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
   185  	unspent, err := w.txStore.UnspentOutputs(dbtx)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	for i := range unspent {
   190  		output := unspent[i]
   191  		_, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, output.PkScript, w.chainParams)
   192  		if len(addrs) == 0 {
   193  			continue
   194  		}
   195  		outputAcct, err := w.manager.AddrAccount(addrmgrNs, addrs[0])
   196  		if err == nil {
   197  			_, ok := m[outputAcct]
   198  			if ok {
   199  				m[outputAcct] += output.Amount
   200  			}
   201  		}
   202  	}
   203  	return nil
   204  }
   205  
   206  func flattenBalanceMap(m map[uint32]dcrutil.Amount) []AccountBalance {
   207  	s := make([]AccountBalance, 0, len(m))
   208  	for k, v := range m {
   209  		s = append(s, AccountBalance{Account: k, TotalBalance: v})
   210  	}
   211  	return s
   212  }
   213  
   214  func relevantAccounts(w *Wallet, m map[uint32]dcrutil.Amount, txs []TransactionSummary) {
   215  	for _, tx := range txs {
   216  		for _, d := range tx.MyInputs {
   217  			m[d.PreviousAccount] = 0
   218  		}
   219  		for _, c := range tx.MyOutputs {
   220  			m[c.Account] = 0
   221  		}
   222  	}
   223  }
   224  
   225  func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *udb.TxDetails) {
   226  	defer s.mu.Unlock()
   227  	s.mu.Lock()
   228  
   229  	// Sanity check: should not be currently coalescing a notification for
   230  	// mined transactions at the same time that an unmined tx is notified.
   231  	if s.currentTxNtfn != nil {
   232  		log.Tracef("Notifying unmined tx notification while creating notification for blocks")
   233  	}
   234  
   235  	clients := s.transactions
   236  	if len(clients) == 0 {
   237  		return
   238  	}
   239  
   240  	unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
   241  	unminedHashes, err := s.wallet.txStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
   242  	if err != nil {
   243  		log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
   244  		return
   245  	}
   246  	bals := make(map[uint32]dcrutil.Amount)
   247  	relevantAccounts(s.wallet, bals, unminedTxs)
   248  	err = totalBalances(dbtx, s.wallet, bals)
   249  	if err != nil {
   250  		log.Errorf("Cannot determine balances for relevant accounts: %v", err)
   251  		return
   252  	}
   253  	n := &TransactionNotifications{
   254  		UnminedTransactions:      unminedTxs,
   255  		UnminedTransactionHashes: unminedHashes,
   256  		NewBalances:              flattenBalanceMap(bals),
   257  	}
   258  	for _, c := range clients {
   259  		c <- n
   260  	}
   261  }
   262  
   263  func (s *NotificationServer) notifyDetachedBlock(header *wire.BlockHeader) {
   264  	defer s.mu.Unlock()
   265  	s.mu.Lock()
   266  
   267  	if s.currentTxNtfn == nil {
   268  		s.currentTxNtfn = &TransactionNotifications{}
   269  	}
   270  	s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, header)
   271  }
   272  
   273  func (s *NotificationServer) notifyMinedTransaction(dbtx walletdb.ReadTx, details *udb.TxDetails, block *udb.BlockMeta) {
   274  	defer s.mu.Unlock()
   275  	s.mu.Lock()
   276  
   277  	if s.currentTxNtfn == nil {
   278  		s.currentTxNtfn = &TransactionNotifications{}
   279  	}
   280  	n := len(s.currentTxNtfn.AttachedBlocks)
   281  	if n == 0 || s.currentTxNtfn.AttachedBlocks[n-1].Header.BlockHash() != block.Hash {
   282  		return
   283  	}
   284  	txs := &s.currentTxNtfn.AttachedBlocks[n-1].Transactions
   285  	*txs = append(*txs, makeTxSummary(dbtx, s.wallet, details))
   286  }
   287  
   288  func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wire.BlockHeader, blockHash *chainhash.Hash) {
   289  	defer s.mu.Unlock()
   290  	s.mu.Lock()
   291  
   292  	if s.currentTxNtfn == nil {
   293  		s.currentTxNtfn = &TransactionNotifications{}
   294  	}
   295  
   296  	// Add block details if it wasn't already included for previously
   297  	// notified mined transactions.
   298  	n := len(s.currentTxNtfn.AttachedBlocks)
   299  	if n == 0 || s.currentTxNtfn.AttachedBlocks[n-1].Header.BlockHash() != *blockHash {
   300  		s.currentTxNtfn.AttachedBlocks = append(s.currentTxNtfn.AttachedBlocks, Block{
   301  			Header: block,
   302  		})
   303  	}
   304  }
   305  
   306  func (s *NotificationServer) sendAttachedBlockNotification(ctx context.Context) {
   307  	// Avoid work if possible
   308  	s.mu.Lock()
   309  	if len(s.transactions) == 0 {
   310  		s.currentTxNtfn = nil
   311  		s.mu.Unlock()
   312  		return
   313  	}
   314  	currentTxNtfn := s.currentTxNtfn
   315  	s.currentTxNtfn = nil
   316  	s.mu.Unlock()
   317  
   318  	// The UnminedTransactions field is intentionally not set.  Since the
   319  	// hashes of all detached blocks are reported, and all transactions
   320  	// moved from a mined block back to unconfirmed are either in the
   321  	// UnminedTransactionHashes slice or don't exist due to conflicting with
   322  	// a mined transaction in the new best chain, there is no possiblity of
   323  	// a new, previously unseen transaction appearing in unconfirmed.
   324  
   325  	var (
   326  		w             = s.wallet
   327  		bals          = make(map[uint32]dcrutil.Amount)
   328  		unminedHashes []*chainhash.Hash
   329  	)
   330  	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   331  		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   332  		var err error
   333  		unminedHashes, err = w.txStore.UnminedTxHashes(txmgrNs)
   334  		if err != nil {
   335  			return err
   336  		}
   337  		for _, b := range currentTxNtfn.AttachedBlocks {
   338  			relevantAccounts(w, bals, b.Transactions)
   339  		}
   340  		return totalBalances(dbtx, w, bals)
   341  
   342  	})
   343  	if err != nil {
   344  		log.Errorf("Failed to construct attached blocks notification: %v", err)
   345  		return
   346  	}
   347  
   348  	currentTxNtfn.UnminedTransactionHashes = unminedHashes
   349  	currentTxNtfn.NewBalances = flattenBalanceMap(bals)
   350  
   351  	s.mu.Lock()
   352  	for _, c := range s.transactions {
   353  		c <- currentTxNtfn
   354  	}
   355  	s.mu.Unlock()
   356  }
   357  
   358  // TransactionNotifications is a notification of changes to the wallet's
   359  // transaction set and the current chain tip that wallet is considered to be
   360  // synced with.  All transactions added to the blockchain are organized by the
   361  // block they were mined in.
   362  //
   363  // During a chain switch, all removed block hashes are included.  Detached
   364  // blocks are sorted in the reverse order they were mined.  Attached blocks are
   365  // sorted in the order mined.
   366  //
   367  // All newly added unmined transactions are included.  Removed unmined
   368  // transactions are not explicitly included.  Instead, the hashes of all
   369  // transactions still unmined are included.
   370  //
   371  // If any transactions were involved, each affected account's new total balance
   372  // is included.
   373  //
   374  // TODO: Because this includes stuff about blocks and can be fired without any
   375  // changes to transactions, it needs a better name.
   376  type TransactionNotifications struct {
   377  	AttachedBlocks           []Block
   378  	DetachedBlocks           []*wire.BlockHeader
   379  	UnminedTransactions      []TransactionSummary
   380  	UnminedTransactionHashes []*chainhash.Hash
   381  	NewBalances              []AccountBalance
   382  }
   383  
   384  // Block contains the properties and all relevant transactions of an attached
   385  // block.
   386  type Block struct {
   387  	Header       *wire.BlockHeader // Nil if referring to mempool
   388  	Transactions []TransactionSummary
   389  }
   390  
   391  // TransactionSummary contains a transaction relevant to the wallet and marks
   392  // which inputs and outputs were relevant.
   393  type TransactionSummary struct {
   394  	Hash        *chainhash.Hash
   395  	Transaction []byte
   396  	MyInputs    []TransactionSummaryInput
   397  	MyOutputs   []TransactionSummaryOutput
   398  	Fee         dcrutil.Amount
   399  	Timestamp   int64
   400  	Type        TransactionType
   401  }
   402  
   403  // TransactionType describes the which type of transaction is has been observed to be.
   404  // For instance, if it has a ticket as an input and a stake base reward as an output,
   405  // it is known to be a vote.
   406  type TransactionType int8
   407  
   408  const (
   409  	// TransactionTypeRegular transaction type for all regular transactions.
   410  	TransactionTypeRegular TransactionType = iota
   411  
   412  	// TransactionTypeCoinbase is the transaction type for all coinbase transactions.
   413  	TransactionTypeCoinbase
   414  
   415  	// TransactionTypeTicketPurchase transaction type for all transactions that
   416  	// consume regular transactions as inputs and have commitments for future votes
   417  	// as outputs.
   418  	TransactionTypeTicketPurchase
   419  
   420  	// TransactionTypeVote transaction type for all transactions that consume a ticket
   421  	// and also offer a stake base reward output.
   422  	TransactionTypeVote
   423  
   424  	// TransactionTypeRevocation transaction type for all transactions that consume a
   425  	// ticket, but offer no stake base reward.
   426  	TransactionTypeRevocation
   427  )
   428  
   429  // TxTransactionType returns the correct TransactionType given a wire transaction
   430  func TxTransactionType(tx *wire.MsgTx) TransactionType {
   431  	if compat.IsEitherCoinBaseTx(tx) {
   432  		return TransactionTypeCoinbase
   433  	} else if stake.IsSStx(tx) {
   434  		return TransactionTypeTicketPurchase
   435  	} else if stake.IsSSGen(tx) {
   436  		return TransactionTypeVote
   437  	} else if isRevocation(tx) {
   438  		return TransactionTypeRevocation
   439  	} else {
   440  		return TransactionTypeRegular
   441  	}
   442  }
   443  
   444  // TransactionSummaryInput describes a transaction input that is relevant to the
   445  // wallet.  The Index field marks the transaction input index of the transaction
   446  // (not included here).  The PreviousAccount and PreviousAmount fields describe
   447  // how much this input debits from a wallet account.
   448  type TransactionSummaryInput struct {
   449  	Index           uint32
   450  	PreviousAccount uint32
   451  	PreviousAmount  dcrutil.Amount
   452  }
   453  
   454  // TransactionSummaryOutput describes wallet properties of a transaction output
   455  // controlled by the wallet.  The Index field marks the transaction output index
   456  // of the transaction (not included here).
   457  type TransactionSummaryOutput struct {
   458  	Index        uint32
   459  	Account      uint32
   460  	Internal     bool
   461  	Amount       dcrutil.Amount
   462  	Address      stdaddr.Address
   463  	OutputScript []byte
   464  }
   465  
   466  // AccountBalance associates a total (zero confirmation) balance with an
   467  // account.  Balances for other minimum confirmation counts require more
   468  // expensive logic and it is not clear which minimums a client is interested in,
   469  // so they are not included.
   470  type AccountBalance struct {
   471  	Account      uint32
   472  	TotalBalance dcrutil.Amount
   473  }
   474  
   475  // TransactionNotificationsClient receives TransactionNotifications from the
   476  // NotificationServer over the channel C.
   477  type TransactionNotificationsClient struct {
   478  	C      <-chan *TransactionNotifications
   479  	server *NotificationServer
   480  }
   481  
   482  // TransactionNotifications returns a client for receiving
   483  // TransactionNotifiations notifications over a channel.  The channel is
   484  // unbuffered.
   485  //
   486  // When finished, the Done method should be called on the client to disassociate
   487  // it from the server.
   488  func (s *NotificationServer) TransactionNotifications() TransactionNotificationsClient {
   489  	c := make(chan *TransactionNotifications)
   490  	s.mu.Lock()
   491  	s.transactions = append(s.transactions, c)
   492  	s.mu.Unlock()
   493  	return TransactionNotificationsClient{
   494  		C:      c,
   495  		server: s,
   496  	}
   497  }
   498  
   499  // Done deregisters the client from the server and drains any remaining
   500  // messages.  It must be called exactly once when the client is finished
   501  // receiving notifications.
   502  func (c *TransactionNotificationsClient) Done() {
   503  	go func() {
   504  		// Drain notifications until the client channel is removed from
   505  		// the server and closed.
   506  		for range c.C {
   507  		}
   508  	}()
   509  	go func() {
   510  		s := c.server
   511  		s.mu.Lock()
   512  		clients := s.transactions
   513  		for i, ch := range clients {
   514  			if c.C == ch {
   515  				clients[i] = clients[len(clients)-1]
   516  				s.transactions = clients[:len(clients)-1]
   517  				close(ch)
   518  				break
   519  			}
   520  		}
   521  		s.mu.Unlock()
   522  	}()
   523  }
   524  
   525  // RemovedTransactionNotification includes the removed transaction hash.
   526  type RemovedTransactionNotification struct {
   527  	TxHash chainhash.Hash
   528  }
   529  
   530  // RemovedTransactionNotificationsClient receives RemovedTransactionNotifications over the channel C.
   531  type RemovedTransactionNotificationsClient struct {
   532  	C      chan *RemovedTransactionNotification
   533  	server *NotificationServer
   534  }
   535  
   536  // RemovedTransactionNotifications returns a client for receiving RemovedTransactionNotifications over
   537  // a channel.  The channel is unbuffered.  When finished, the client's Done
   538  // method should be called to disassociate the client from the server.
   539  func (s *NotificationServer) RemovedTransactionNotifications() RemovedTransactionNotificationsClient {
   540  	c := make(chan *RemovedTransactionNotification)
   541  	s.mu.Lock()
   542  	s.removedTransactionClients = append(s.removedTransactionClients, c)
   543  	s.mu.Unlock()
   544  	return RemovedTransactionNotificationsClient{
   545  		C:      c,
   546  		server: s,
   547  	}
   548  }
   549  
   550  // Done deregisters the client from the server and drains any remaining
   551  // messages.  It must be called exactly once when the client is finished
   552  // receiving notifications.
   553  func (c *RemovedTransactionNotificationsClient) Done() {
   554  	go func() {
   555  		for range c.C {
   556  		}
   557  	}()
   558  	go func() {
   559  		s := c.server
   560  		s.mu.Lock()
   561  		clients := s.removedTransactionClients
   562  		for i, ch := range clients {
   563  			if c.C == ch {
   564  				clients[i] = clients[len(clients)-1]
   565  				s.removedTransactionClients = clients[:len(clients)-1]
   566  				close(ch)
   567  				break
   568  			}
   569  		}
   570  		s.mu.Unlock()
   571  	}()
   572  }
   573  
   574  func (s *NotificationServer) notifyRemovedTransaction(hash chainhash.Hash) {
   575  	defer s.mu.Unlock()
   576  	s.mu.Lock()
   577  	clients := s.removedTransactionClients
   578  	if len(clients) == 0 {
   579  		return
   580  	}
   581  	n := &RemovedTransactionNotification{
   582  		TxHash: hash,
   583  	}
   584  	for _, c := range clients {
   585  		c <- n
   586  	}
   587  }
   588  
   589  // AccountNotification contains properties regarding an account, such as its
   590  // name and the number of derived and imported keys.  When any of these
   591  // properties change, the notification is fired.
   592  type AccountNotification struct {
   593  	AccountNumber    uint32
   594  	AccountName      string
   595  	ExternalKeyCount uint32
   596  	InternalKeyCount uint32
   597  	ImportedKeyCount uint32
   598  }
   599  
   600  func (s *NotificationServer) notifyAccountProperties(props *udb.AccountProperties) {
   601  	defer s.mu.Unlock()
   602  	s.mu.Lock()
   603  	clients := s.accountClients
   604  	if len(clients) == 0 {
   605  		return
   606  	}
   607  	n := &AccountNotification{
   608  		AccountNumber:    props.AccountNumber,
   609  		AccountName:      props.AccountName,
   610  		ExternalKeyCount: 0,
   611  		InternalKeyCount: 0,
   612  		ImportedKeyCount: props.ImportedKeyCount,
   613  	}
   614  	// Key counts have to be fudged for BIP0044 accounts a little bit because
   615  	// only the last used child index is saved.  Add the gap limit since these
   616  	// addresses have also been generated and are being watched for transaction
   617  	// activity.
   618  	if props.AccountNumber <= udb.MaxAccountNum {
   619  		n.ExternalKeyCount = minUint32(hdkeychain.HardenedKeyStart,
   620  			props.LastUsedExternalIndex+s.wallet.gapLimit)
   621  		n.InternalKeyCount = minUint32(hdkeychain.HardenedKeyStart,
   622  			props.LastUsedInternalIndex+s.wallet.gapLimit)
   623  	}
   624  	for _, c := range clients {
   625  		c <- n
   626  	}
   627  }
   628  
   629  // AccountNotificationsClient receives AccountNotifications over the channel C.
   630  type AccountNotificationsClient struct {
   631  	C      chan *AccountNotification
   632  	server *NotificationServer
   633  }
   634  
   635  // AccountNotifications returns a client for receiving AccountNotifications over
   636  // a channel.  The channel is unbuffered.  When finished, the client's Done
   637  // method should be called to disassociate the client from the server.
   638  func (s *NotificationServer) AccountNotifications() AccountNotificationsClient {
   639  	c := make(chan *AccountNotification)
   640  	s.mu.Lock()
   641  	s.accountClients = append(s.accountClients, c)
   642  	s.mu.Unlock()
   643  	return AccountNotificationsClient{
   644  		C:      c,
   645  		server: s,
   646  	}
   647  }
   648  
   649  // Done deregisters the client from the server and drains any remaining
   650  // messages.  It must be called exactly once when the client is finished
   651  // receiving notifications.
   652  func (c *AccountNotificationsClient) Done() {
   653  	go func() {
   654  		for range c.C {
   655  		}
   656  	}()
   657  	go func() {
   658  		s := c.server
   659  		s.mu.Lock()
   660  		clients := s.accountClients
   661  		for i, ch := range clients {
   662  			if c.C == ch {
   663  				clients[i] = clients[len(clients)-1]
   664  				s.accountClients = clients[:len(clients)-1]
   665  				close(ch)
   666  				break
   667  			}
   668  		}
   669  		s.mu.Unlock()
   670  	}()
   671  }
   672  
   673  // MainTipChangedNotification describes processed changes to the main chain tip
   674  // block.  Attached and detached blocks are sorted by increasing heights.
   675  //
   676  // This is intended to be a lightweight alternative to TransactionNotifications
   677  // when only info regarding the main chain tip block changing is needed.
   678  type MainTipChangedNotification struct {
   679  	AttachedBlocks []*chainhash.Hash
   680  	DetachedBlocks []*chainhash.Hash
   681  	NewHeight      int32
   682  }
   683  
   684  // MainTipChangedNotificationsClient receives MainTipChangedNotifications over
   685  // the channel C.
   686  type MainTipChangedNotificationsClient struct {
   687  	C      chan *MainTipChangedNotification
   688  	server *NotificationServer
   689  }
   690  
   691  // MainTipChangedNotifications returns a client for receiving
   692  // MainTipChangedNotification over a channel.  The channel is unbuffered.  When
   693  // finished, the client's Done method should be called to disassociate the
   694  // client from the server.
   695  func (s *NotificationServer) MainTipChangedNotifications() MainTipChangedNotificationsClient {
   696  	c := make(chan *MainTipChangedNotification)
   697  	s.mu.Lock()
   698  	s.tipChangedClients = append(s.tipChangedClients, c)
   699  	s.mu.Unlock()
   700  	return MainTipChangedNotificationsClient{
   701  		C:      c,
   702  		server: s,
   703  	}
   704  }
   705  
   706  // Done deregisters the client from the server and drains any remaining
   707  // messages.  It must be called exactly once when the client is finished
   708  // receiving notifications.
   709  func (c *MainTipChangedNotificationsClient) Done() {
   710  	go func() {
   711  		for range c.C {
   712  		}
   713  	}()
   714  	go func() {
   715  		s := c.server
   716  		s.mu.Lock()
   717  		clients := s.tipChangedClients
   718  		for i, ch := range clients {
   719  			if c.C == ch {
   720  				clients[i] = clients[len(clients)-1]
   721  				s.tipChangedClients = clients[:len(clients)-1]
   722  				close(ch)
   723  				break
   724  			}
   725  		}
   726  		s.mu.Unlock()
   727  	}()
   728  }
   729  
   730  func (s *NotificationServer) notifyMainChainTipChanged(n *MainTipChangedNotification) {
   731  	s.mu.Lock()
   732  
   733  	for _, c := range s.tipChangedClients {
   734  		c <- n
   735  	}
   736  
   737  	if len(s.confClients) > 0 {
   738  		var wg sync.WaitGroup
   739  		wg.Add(len(s.confClients))
   740  		for _, c := range s.confClients {
   741  			c := c
   742  			go func() {
   743  				c.process(n.NewHeight)
   744  				wg.Done()
   745  			}()
   746  		}
   747  		wg.Wait()
   748  	}
   749  
   750  	s.mu.Unlock()
   751  }
   752  
   753  // ConfirmationNotifications registers a client for confirmation notifications
   754  // from the notification server.
   755  func (s *NotificationServer) ConfirmationNotifications(ctx context.Context) *ConfirmationNotificationsClient {
   756  	c := &ConfirmationNotificationsClient{
   757  		watched: make(map[chainhash.Hash]int32),
   758  		r:       make(chan *confNtfnResult),
   759  		ctx:     ctx,
   760  		s:       s,
   761  	}
   762  
   763  	// Register with the server
   764  	s.mu.Lock()
   765  	s.confClients = append(s.confClients, c)
   766  	s.mu.Unlock()
   767  
   768  	// Cleanup when caller signals done.
   769  	go func() {
   770  		<-ctx.Done()
   771  
   772  		// Remove item from notification server's slice
   773  		s.mu.Lock()
   774  		slice := &s.confClients
   775  		for i, sc := range *slice {
   776  			if c == sc {
   777  				(*slice)[i] = (*slice)[len(*slice)-1]
   778  				*slice = (*slice)[:len(*slice)-1]
   779  				break
   780  			}
   781  		}
   782  		s.mu.Unlock()
   783  	}()
   784  
   785  	return c
   786  }
   787  
   788  // ConfirmationNotificationsClient provides confirmation notifications of watched
   789  // transactions until the caller's context signals done.  Callers register for
   790  // notifications using Watch and receive notifications by calling Recv.
   791  type ConfirmationNotificationsClient struct {
   792  	watched map[chainhash.Hash]int32
   793  	mu      sync.Mutex
   794  
   795  	r   chan *confNtfnResult
   796  	ctx context.Context
   797  	s   *NotificationServer
   798  }
   799  
   800  type confNtfnResult struct {
   801  	result []ConfirmationNotification
   802  	err    error
   803  }
   804  
   805  // ConfirmationNotification describes the number of confirmations of a single
   806  // transaction, or -1 if the transaction is unknown or removed from the wallet.
   807  // If the transaction is mined (Confirmations >= 1), the block hash and height
   808  // is included.  Otherwise the block hash is nil and the block height is set to
   809  // -1.
   810  type ConfirmationNotification struct {
   811  	TxHash        *chainhash.Hash
   812  	Confirmations int32
   813  	BlockHash     *chainhash.Hash // nil when unmined
   814  	BlockHeight   int32           // -1 when unmined
   815  }
   816  
   817  // Watch adds additional transactions to watch and create confirmation results
   818  // for.  Results are immediately created with the current number of
   819  // confirmations and are watched until stopAfter confirmations is met or the
   820  // transaction is unknown or removed from the wallet.
   821  func (c *ConfirmationNotificationsClient) Watch(txHashes []*chainhash.Hash, stopAfter int32) {
   822  	if len(txHashes) == 0 {
   823  		return
   824  	}
   825  	w := c.s.wallet
   826  	r := make([]ConfirmationNotification, 0, len(c.watched))
   827  	err := walletdb.View(c.ctx, w.db, func(dbtx walletdb.ReadTx) error {
   828  		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   829  		_, tipHeight := w.txStore.MainChainTip(dbtx)
   830  		// cannot range here, txHashes may be modified
   831  		for i := 0; i < len(txHashes); {
   832  			h := txHashes[i]
   833  			height, err := w.txStore.TxBlockHeight(dbtx, h)
   834  			var confs int32
   835  			switch {
   836  			case errors.Is(err, errors.NotExist):
   837  				confs = -1
   838  			case err != nil:
   839  				return err
   840  			default:
   841  				// Remove tx hash from watching list if tx block has been mined
   842  				// and then invalidated by next block
   843  				if tipHeight > height && height > 0 {
   844  					txDetails, err := w.txStore.TxDetails(txmgrNs, h)
   845  					if err != nil {
   846  						return err
   847  					}
   848  					_, invalidated := w.txStore.BlockInMainChain(dbtx, &txDetails.Block.Hash)
   849  					if invalidated {
   850  						confs = -1
   851  						break
   852  					}
   853  				}
   854  				confs = confirms(height, tipHeight)
   855  			}
   856  			r = append(r, ConfirmationNotification{
   857  				TxHash:        h,
   858  				Confirmations: confs,
   859  				BlockHeight:   -1,
   860  			})
   861  			if confs > 0 {
   862  				result := &r[len(r)-1]
   863  				height, err := w.txStore.TxBlockHeight(dbtx, result.TxHash)
   864  				if err != nil {
   865  					return err
   866  				}
   867  				blockHash, err := w.txStore.GetMainChainBlockHashForHeight(txmgrNs, height)
   868  				if err != nil {
   869  					return err
   870  				}
   871  				result.BlockHash = &blockHash
   872  				result.BlockHeight = height
   873  			}
   874  			if confs >= stopAfter || confs == -1 {
   875  				// Remove this hash from the slice so it is not added to the
   876  				// watch map.  Do not increment i so this same index is used
   877  				// next iteration with the new hash.
   878  				s := &txHashes
   879  				(*s)[i] = (*s)[len(*s)-1]
   880  				*s = (*s)[:len(*s)-1]
   881  			} else {
   882  				i++
   883  			}
   884  		}
   885  		return nil
   886  	})
   887  	if err != nil {
   888  		r = nil
   889  	}
   890  	select {
   891  	case c.r <- &confNtfnResult{r, err}:
   892  	case <-c.ctx.Done():
   893  	}
   894  
   895  	c.mu.Lock()
   896  	for _, h := range txHashes {
   897  		c.watched[*h] = stopAfter
   898  	}
   899  	c.mu.Unlock()
   900  }
   901  
   902  // Recv waits for the next notification.  Returns context.Canceled when the
   903  // context is canceled.
   904  func (c *ConfirmationNotificationsClient) Recv() ([]ConfirmationNotification, error) {
   905  	select {
   906  	case <-c.ctx.Done():
   907  		return nil, context.Canceled
   908  	case r := <-c.r:
   909  		return r.result, r.err
   910  	}
   911  }
   912  
   913  func (c *ConfirmationNotificationsClient) process(tipHeight int32) {
   914  	select {
   915  	case <-c.ctx.Done():
   916  		return
   917  	default:
   918  	}
   919  
   920  	c.mu.Lock()
   921  	w := c.s.wallet
   922  	r := &confNtfnResult{
   923  		result: make([]ConfirmationNotification, 0, len(c.watched)),
   924  	}
   925  	var unwatch []*chainhash.Hash
   926  	err := walletdb.View(c.ctx, w.db, func(dbtx walletdb.ReadTx) error {
   927  		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   928  		for txHash, stopAfter := range c.watched {
   929  			txHash := txHash // copy
   930  			height, err := w.txStore.TxBlockHeight(dbtx, &txHash)
   931  			var confs int32
   932  			switch {
   933  			case errors.Is(err, errors.NotExist):
   934  				confs = -1
   935  			case err != nil:
   936  				return err
   937  			default:
   938  				// Remove tx hash from watching list if tx block has been mined
   939  				// and then invalidated by next block
   940  				if tipHeight > height && height > 0 {
   941  					txDetails, err := w.txStore.TxDetails(txmgrNs, &txHash)
   942  					if err != nil {
   943  						return err
   944  					}
   945  					_, invalidated := w.txStore.BlockInMainChain(dbtx, &txDetails.Block.Hash)
   946  					if invalidated {
   947  						confs = -1
   948  						break
   949  					}
   950  				}
   951  				confs = confirms(height, tipHeight)
   952  			}
   953  			r.result = append(r.result, ConfirmationNotification{
   954  				TxHash:        &txHash,
   955  				Confirmations: confs,
   956  				BlockHeight:   -1,
   957  			})
   958  			if confs > 0 {
   959  				result := &r.result[len(r.result)-1]
   960  				height, err := w.txStore.TxBlockHeight(dbtx, result.TxHash)
   961  				if err != nil {
   962  					return err
   963  				}
   964  				blockHash, err := w.txStore.GetMainChainBlockHashForHeight(txmgrNs, height)
   965  				if err != nil {
   966  					return err
   967  				}
   968  				result.BlockHash = &blockHash
   969  				result.BlockHeight = height
   970  			}
   971  			if confs >= stopAfter || confs == -1 {
   972  				unwatch = append(unwatch, &txHash)
   973  			}
   974  		}
   975  		return nil
   976  	})
   977  	if err != nil {
   978  		r.result = nil
   979  		r.err = err
   980  	}
   981  	for _, h := range unwatch {
   982  		delete(c.watched, *h)
   983  	}
   984  	c.mu.Unlock()
   985  
   986  	select {
   987  	case c.r <- r:
   988  	case <-c.ctx.Done():
   989  	}
   990  }