notifications.go raw

   1  package wallet
   2  
   3  import (
   4  	"bytes"
   5  	"github.com/p9c/p9/pkg/amt"
   6  	"github.com/p9c/p9/pkg/btcaddr"
   7  	"sync"
   8  	
   9  	"github.com/p9c/p9/pkg/chainhash"
  10  	"github.com/p9c/p9/pkg/txscript"
  11  	"github.com/p9c/p9/pkg/waddrmgr"
  12  	"github.com/p9c/p9/pkg/walletdb"
  13  	"github.com/p9c/p9/pkg/wtxmgr"
  14  )
  15  
  16  // AccountBalance associates a total (zero confirmation) balance with an account. Balances for other minimum
  17  // confirmation counts require more expensive logic and it is not clear which minimums a client is interested in, so
  18  // they are not included.
  19  type AccountBalance struct {
  20  	Account      uint32
  21  	TotalBalance amt.Amount
  22  }
  23  
  24  // AccountNotification contains properties regarding an account, such as its name and the number of derived and imported
  25  // keys. When any of these properties change, the notification is fired.
  26  type AccountNotification struct {
  27  	AccountNumber    uint32
  28  	AccountName      string
  29  	ExternalKeyCount uint32
  30  	InternalKeyCount uint32
  31  	ImportedKeyCount uint32
  32  }
  33  
  34  // AccountNotificationsClient receives AccountNotifications over the channel C.
  35  type AccountNotificationsClient struct {
  36  	C      chan *AccountNotification
  37  	server *NotificationServer
  38  }
  39  
  40  // Block contains the properties and all relevant transactions of an attached
  41  // block.
  42  type Block struct {
  43  	Hash         *chainhash.Hash
  44  	Height       int32
  45  	Timestamp    int64
  46  	Transactions []TransactionSummary
  47  }
  48  
  49  // TODO: It would be good to send errors during notification creation to the rpc server instead of just logging them
  50  //  here so the client is aware that wallet isn't working correctly and notifications are missing.
  51  //
  52  // TODO: Anything dealing with accounts here is expensive because the database is not organized correctly for true
  53  //  account support, but do the slow thing instead of the easy thing since the db can be fixed later, and we want the api
  54  //  correct now.
  55  
  56  // NotificationServer is a server that interested clients may hook into to receive notifications of changes
  57  //  in a wallet. A client is created for each registered notification. Clients are guaranteed to receive messages in the
  58  //  order wallet created them, but there is no guaranteed synchronization between different clients.
  59  type NotificationServer struct {
  60  	transactions   []chan *TransactionNotifications
  61  	currentTxNtfn  *TransactionNotifications // coalesce this since wallet does not add mined txs together
  62  	spentness      map[uint32][]chan *SpentnessNotifications
  63  	accountClients []chan *AccountNotification
  64  	mu             sync.Mutex // Only protects registered client channels
  65  	wallet         *Wallet    // smells like hacks
  66  }
  67  
  68  // SpentnessNotifications is a notification that is fired for transaction outputs controlled by some account's keys. The
  69  // notification may be about a newly added unspent transaction output or that a previously unspent output is now spent.
  70  // When spent, the notification includes the spending transaction's hash and input index.
  71  type SpentnessNotifications struct {
  72  	hash         *chainhash.Hash
  73  	spenderHash  *chainhash.Hash
  74  	index        uint32
  75  	spenderIndex uint32
  76  }
  77  
  78  // SpentnessNotificationsClient receives SpentnessNotifications from the NotificationServer over the channel C.
  79  type SpentnessNotificationsClient struct {
  80  	C       <-chan *SpentnessNotifications
  81  	account uint32
  82  	server  *NotificationServer
  83  }
  84  
  85  // TransactionNotifications is a notification of changes to the wallet's transaction set and the current chain tip that
  86  // wallet is considered to be synced with. All transactions added to the blockchain are organized by the block they were
  87  // mined in.
  88  //
  89  // During a chain switch, all removed block hashes are included. Detached blocks are sorted in the reverse order they
  90  // were mined. Attached blocks are sorted in the order mined.
  91  //
  92  // All newly added unmined transactions are included. Removed unmined transactions are not explicitly included. Instead,
  93  // the hashes of all transactions still unmined are included.
  94  //
  95  // If any transactions were involved, each affected account's new total balance is included.
  96  //
  97  // TODO: Because this includes stuff about blocks and can be fired without any changes to transactions, it needs a
  98  //  better name.
  99  type TransactionNotifications struct {
 100  	AttachedBlocks           []Block
 101  	DetachedBlocks           []*chainhash.Hash
 102  	UnminedTransactions      []TransactionSummary
 103  	UnminedTransactionHashes []*chainhash.Hash
 104  	NewBalances              []AccountBalance
 105  }
 106  
 107  // TransactionNotificationsClient receives TransactionNotifications from the NotificationServer over the channel C.
 108  type TransactionNotificationsClient struct {
 109  	C      <-chan *TransactionNotifications
 110  	server *NotificationServer
 111  }
 112  
 113  // TransactionSummary contains a transaction relevant to the wallet and marks which inputs and outputs were relevant.
 114  type TransactionSummary struct {
 115  	Hash        *chainhash.Hash
 116  	Transaction []byte
 117  	MyInputs    []TransactionSummaryInput
 118  	MyOutputs   []TransactionSummaryOutput
 119  	Fee         amt.Amount
 120  	Timestamp   int64
 121  }
 122  
 123  // TransactionSummaryInput describes a transaction input that is relevant to the wallet. The Index field marks the
 124  // transaction input index of the transaction (not included here). The PreviousAccount and PreviousAmount fields
 125  // describe how much this input debits from a wallet account.
 126  type TransactionSummaryInput struct {
 127  	Index           uint32
 128  	PreviousAccount uint32
 129  	PreviousAmount  amt.Amount
 130  }
 131  
 132  // TransactionSummaryOutput describes wallet properties of a transaction output controlled by the wallet. The Index
 133  // field marks the transaction output index of the transaction (not included here).
 134  type TransactionSummaryOutput struct {
 135  	Index    uint32
 136  	Account  uint32
 137  	Internal bool
 138  }
 139  
 140  // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
 141  // the client is finished receiving notifications.
 142  func (c *AccountNotificationsClient) Done() {
 143  	go func() {
 144  		for range c.C {
 145  		}
 146  	}()
 147  	go func() {
 148  		s := c.server
 149  		s.mu.Lock()
 150  		clients := s.accountClients
 151  		for i, ch := range clients {
 152  			if c.C == ch {
 153  				clients[i] = clients[len(clients)-1]
 154  				s.accountClients = clients[:len(clients)-1]
 155  				close(ch)
 156  				break
 157  			}
 158  		}
 159  		s.mu.Unlock()
 160  	}()
 161  }
 162  
 163  // AccountNotifications returns a client for receiving AccountNotifications over a channel. The channel is unbuffered.
 164  // When finished, the client's Done method should be called to disassociate the client from the server.
 165  func (s *NotificationServer) AccountNotifications() AccountNotificationsClient {
 166  	c := make(chan *AccountNotification)
 167  	s.mu.Lock()
 168  	s.accountClients = append(s.accountClients, c)
 169  	s.mu.Unlock()
 170  	return AccountNotificationsClient{
 171  		C:      c,
 172  		server: s,
 173  	}
 174  }
 175  
 176  // AccountSpentnessNotifications registers a client for spentness changes of outputs controlled by the account.
 177  func (s *NotificationServer) AccountSpentnessNotifications(account uint32) SpentnessNotificationsClient {
 178  	c := make(chan *SpentnessNotifications)
 179  	s.mu.Lock()
 180  	s.spentness[account] = append(s.spentness[account], c)
 181  	s.mu.Unlock()
 182  	return SpentnessNotificationsClient{
 183  		C:       c,
 184  		account: account,
 185  		server:  s,
 186  	}
 187  }
 188  
 189  // TransactionNotifications returns a client for receiving TransactionNotifications notifications over a channel. The
 190  // channel is unbuffered.
 191  //
 192  // When finished, the Done method should be called on the client to disassociate it from the server.
 193  func (s *NotificationServer) TransactionNotifications() TransactionNotificationsClient {
 194  	c := make(chan *TransactionNotifications)
 195  	s.mu.Lock()
 196  	s.transactions = append(s.transactions, c)
 197  	s.mu.Unlock()
 198  	return TransactionNotificationsClient{
 199  		C:      c,
 200  		server: s,
 201  	}
 202  }
 203  func (s *NotificationServer) notifyAccountProperties(props *waddrmgr.AccountProperties) {
 204  	defer s.mu.Unlock()
 205  	s.mu.Lock()
 206  	clients := s.accountClients
 207  	if len(clients) == 0 {
 208  		return
 209  	}
 210  	n := &AccountNotification{
 211  		AccountNumber:    props.AccountNumber,
 212  		AccountName:      props.AccountName,
 213  		ExternalKeyCount: props.ExternalKeyCount,
 214  		InternalKeyCount: props.InternalKeyCount,
 215  		ImportedKeyCount: props.ImportedKeyCount,
 216  	}
 217  	for _, c := range clients {
 218  		c <- n
 219  	}
 220  }
 221  func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wtxmgr.BlockMeta) {
 222  	if s.currentTxNtfn == nil {
 223  		s.currentTxNtfn = &TransactionNotifications{}
 224  	}
 225  	// Add block details if it wasn't already included for previously notified mined transactions.
 226  	n := len(s.currentTxNtfn.AttachedBlocks)
 227  	if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
 228  		s.currentTxNtfn.AttachedBlocks = append(
 229  			s.currentTxNtfn.AttachedBlocks, Block{
 230  				Hash:      &block.Hash,
 231  				Height:    block.Height,
 232  				Timestamp: block.Time.Unix(),
 233  			},
 234  		)
 235  	}
 236  	// For now (until notification coalescing isn't necessary) just use chain length to determine if this is the new
 237  	// best block.
 238  	if s.wallet.ChainSynced() {
 239  		if len(s.currentTxNtfn.DetachedBlocks) >= len(s.currentTxNtfn.AttachedBlocks) {
 240  			return
 241  		}
 242  	}
 243  	defer s.mu.Unlock()
 244  	s.mu.Lock()
 245  	clients := s.transactions
 246  	if len(clients) == 0 {
 247  		s.currentTxNtfn = nil
 248  		return
 249  	}
 250  	// The UnminedTransactions field is intentionally not set. Since the hashes of all detached blocks are reported, and
 251  	// all transactions moved from a mined block back to unconfirmed are either in the UnminedTransactionHashes slice or
 252  	// don't exist due to conflicting with a mined transaction in the new best chain, there is no possiblity of a new,
 253  	// previously unseen transaction appearing in unconfirmed.
 254  	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
 255  	unminedHashes, e := s.wallet.TxStore.UnminedTxHashes(txmgrNs)
 256  	if e != nil {
 257  		E.Ln(
 258  			"cannot fetch unmined transaction hashes:", e,
 259  		)
 260  		return
 261  	}
 262  	s.currentTxNtfn.UnminedTransactionHashes = unminedHashes
 263  	bals := make(map[uint32]amt.Amount)
 264  	for _, b := range s.currentTxNtfn.AttachedBlocks {
 265  		relevantAccounts(s.wallet, bals, b.Transactions)
 266  	}
 267  	e = totalBalances(dbtx, s.wallet, bals)
 268  	if e != nil {
 269  		E.Ln(
 270  			"cannot determine balances for relevant accounts:", e,
 271  		)
 272  		return
 273  	}
 274  	s.currentTxNtfn.NewBalances = flattenBalanceMap(bals)
 275  	for _, c := range clients {
 276  		c <- s.currentTxNtfn
 277  	}
 278  	s.currentTxNtfn = nil
 279  }
 280  func (s *NotificationServer) notifyDetachedBlock(hash *chainhash.Hash) {
 281  	if s.currentTxNtfn == nil {
 282  		s.currentTxNtfn = &TransactionNotifications{}
 283  	}
 284  	s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, hash)
 285  }
 286  func (s *NotificationServer) notifyMinedTransaction(
 287  	dbtx walletdb.ReadTx,
 288  	details *wtxmgr.TxDetails,
 289  	block *wtxmgr.BlockMeta,
 290  ) {
 291  	if s.currentTxNtfn == nil {
 292  		s.currentTxNtfn = &TransactionNotifications{}
 293  	}
 294  	n := len(s.currentTxNtfn.AttachedBlocks)
 295  	if n == 0 || *s.currentTxNtfn.AttachedBlocks[n-1].Hash != block.Hash {
 296  		s.currentTxNtfn.AttachedBlocks = append(
 297  			s.currentTxNtfn.AttachedBlocks, Block{
 298  				Hash:      &block.Hash,
 299  				Height:    block.Height,
 300  				Timestamp: block.Time.Unix(),
 301  			},
 302  		)
 303  		n++
 304  	}
 305  	txs := s.currentTxNtfn.AttachedBlocks[n-1].Transactions
 306  	s.currentTxNtfn.AttachedBlocks[n-1].Transactions =
 307  		append(txs, makeTxSummary(dbtx, s.wallet, details))
 308  }
 309  
 310  // // notifySpentOutput notifies registered clients that a previously-unspent
 311  // // output is now spent, and includes the spender hash and input index in the
 312  // // notification.
 313  // func (s *NotificationServer) notifySpentOutput(account uint32, op *wire.OutPoint, spenderHash *chainhash.Hash, spenderIndex uint32) {
 314  // 	defer s.mu.Unlock()
 315  // 	s.mu.Lock()
 316  // 	clients := s.spentness[account]
 317  // 	if len(clients) == 0 {
 318  // 		return
 319  // 	}
 320  // 	n := &SpentnessNotifications{
 321  // 		hash:         &op.Hash,
 322  // 		index:        op.Index,
 323  // 		spenderHash:  spenderHash,
 324  // 		spenderIndex: spenderIndex,
 325  // 	}
 326  // 	for _, c := range clients {
 327  // 		c <- n
 328  // 	}
 329  // }
 330  func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails) {
 331  	// Sanity check: should not be currently coalescing a notification for mined transactions at the same time that an
 332  	// unmined tx is notified.
 333  	if s.currentTxNtfn != nil {
 334  		E.Ln(
 335  			"notifying unmined tx notification (",
 336  			details.Hash.String(),
 337  			") while creating notification for blocks",
 338  		)
 339  	}
 340  	defer s.mu.Unlock()
 341  	s.mu.Lock()
 342  	clients := s.transactions
 343  	if len(clients) == 0 {
 344  		return
 345  	}
 346  	unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
 347  	unminedHashes, e := s.wallet.TxStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
 348  	if e != nil {
 349  		E.Ln(
 350  			"cannot fetch unmined transaction hashes:", e,
 351  		)
 352  		return
 353  	}
 354  	bals := make(map[uint32]amt.Amount)
 355  	relevantAccounts(s.wallet, bals, unminedTxs)
 356  	e = totalBalances(dbtx, s.wallet, bals)
 357  	if e != nil {
 358  		E.Ln(
 359  			"cannot determine balances for relevant accounts:", e,
 360  		)
 361  		return
 362  	}
 363  	n := &TransactionNotifications{
 364  		UnminedTransactions:      unminedTxs,
 365  		UnminedTransactionHashes: unminedHashes,
 366  		NewBalances:              flattenBalanceMap(bals),
 367  	}
 368  	for _, c := range clients {
 369  		c <- n
 370  	}
 371  }
 372  
 373  // notifyUnspentOutput notifies registered clients of a new unspent output that is controlled by the wallet.
 374  func (s *NotificationServer) notifyUnspentOutput(account uint32, hash *chainhash.Hash, index uint32) {
 375  	defer s.mu.Unlock()
 376  	s.mu.Lock()
 377  	clients := s.spentness[account]
 378  	if len(clients) == 0 {
 379  		return
 380  	}
 381  	n := &SpentnessNotifications{
 382  		hash:  hash,
 383  		index: index,
 384  	}
 385  	for _, c := range clients {
 386  		c <- n
 387  	}
 388  }
 389  
 390  // Hash returns the transaction hash of the spent output.
 391  func (n *SpentnessNotifications) Hash() *chainhash.Hash {
 392  	return n.hash
 393  }
 394  
 395  // Index returns the transaction output index of the spent output.
 396  func (n *SpentnessNotifications) Index() uint32 {
 397  	return n.index
 398  }
 399  
 400  // Spender returns the spending transaction's hash and input index, if any. If the output is unspent, the final bool
 401  // return is false.
 402  func (n *SpentnessNotifications) Spender() (*chainhash.Hash, uint32, bool) {
 403  	return n.spenderHash, n.spenderIndex, n.spenderHash != nil
 404  }
 405  
 406  // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
 407  // the client is finished receiving notifications.
 408  func (c *SpentnessNotificationsClient) Done() {
 409  	go func() {
 410  		// Drain notifications until the client channel is removed from the server and closed.
 411  		for range c.C {
 412  		}
 413  	}()
 414  	go func() {
 415  		s := c.server
 416  		s.mu.Lock()
 417  		clients := s.spentness[c.account]
 418  		for i, ch := range clients {
 419  			if c.C == ch {
 420  				clients[i] = clients[len(clients)-1]
 421  				s.spentness[c.account] = clients[:len(clients)-1]
 422  				close(ch)
 423  				break
 424  			}
 425  		}
 426  		s.mu.Unlock()
 427  	}()
 428  }
 429  
 430  // Done unregisters the client from the server and drains any remaining messages. It must be called exactly once when
 431  // the client is finished receiving notifications.
 432  func (c *TransactionNotificationsClient) Done() {
 433  	go func() {
 434  		// Drain notifications until the client channel is removed from the server and closed.
 435  		for range c.C {
 436  		}
 437  	}()
 438  	go func() {
 439  		s := c.server
 440  		s.mu.Lock()
 441  		clients := s.transactions
 442  		for i, ch := range clients {
 443  			if c.C == ch {
 444  				clients[i] = clients[len(clients)-1]
 445  				s.transactions = clients[:len(clients)-1]
 446  				close(ch)
 447  				break
 448  			}
 449  		}
 450  		s.mu.Unlock()
 451  	}()
 452  }
 453  func flattenBalanceMap(m map[uint32]amt.Amount) []AccountBalance {
 454  	s := make([]AccountBalance, 0, len(m))
 455  	for k, v := range m {
 456  		s = append(s, AccountBalance{Account: k, TotalBalance: v})
 457  	}
 458  	return s
 459  }
 460  func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 {
 461  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
 462  	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
 463  	// TODO: Debits should record which account(s?) they
 464  	// debit from so this doesn't need to be looked up.
 465  	prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
 466  	prev, e := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash)
 467  	if e != nil {
 468  		E.F(
 469  			"cannot query previous transaction details for %v: %v",
 470  			prevOP.Hash, e,
 471  		)
 472  		return 0
 473  	}
 474  	if prev == nil {
 475  		E.Ln(
 476  			"missing previous transaction", prevOP.Hash,
 477  		)
 478  		return 0
 479  	}
 480  	prevOut := prev.MsgTx.TxOut[prevOP.Index]
 481  	var addrs []btcaddr.Address
 482  	_, addrs, _, e = txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams)
 483  	var inputAcct uint32
 484  	if e == nil && len(addrs) > 0 {
 485  		_, inputAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0])
 486  	}
 487  	if e != nil {
 488  		E.F(
 489  			"cannot fetch account for previous output %v: %v", prevOP, e,
 490  		)
 491  		inputAcct = 0
 492  	}
 493  	return inputAcct
 494  }
 495  func lookupOutputChain(
 496  	dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails,
 497  	cred wtxmgr.CreditRecord,
 498  ) (account uint32, internal bool) {
 499  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
 500  	output := details.MsgTx.TxOut[cred.Index]
 501  	var addrs []btcaddr.Address
 502  	var e error
 503  	_, addrs, _, e = txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams)
 504  	var ma waddrmgr.ManagedAddress
 505  	if e == nil && len(addrs) > 0 {
 506  		ma, e = w.Manager.Address(addrmgrNs, addrs[0])
 507  	}
 508  	if e != nil {
 509  		E.Ln(
 510  			"cannot fetch account for wallet output:", e,
 511  		)
 512  	} else {
 513  		account = ma.Account()
 514  		internal = ma.Internal()
 515  	}
 516  	return
 517  }
 518  func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
 519  	serializedTx := details.SerializedTx
 520  	if serializedTx == nil {
 521  		var buf bytes.Buffer
 522  		e := details.MsgTx.Serialize(&buf)
 523  		if e != nil {
 524  			E.Ln("transaction serialization:", e)
 525  		}
 526  		serializedTx = buf.Bytes()
 527  	}
 528  	var fee amt.Amount
 529  	if len(details.Debits) == len(details.MsgTx.TxIn) {
 530  		for _, deb := range details.Debits {
 531  			fee += deb.Amount
 532  		}
 533  		for _, txOut := range details.MsgTx.TxOut {
 534  			fee -= amt.Amount(txOut.Value)
 535  		}
 536  	}
 537  	var inputs []TransactionSummaryInput
 538  	if len(details.Debits) != 0 {
 539  		inputs = make([]TransactionSummaryInput, len(details.Debits))
 540  		for i, d := range details.Debits {
 541  			inputs[i] = TransactionSummaryInput{
 542  				Index:           d.Index,
 543  				PreviousAccount: lookupInputAccount(dbtx, w, details, d),
 544  				PreviousAmount:  d.Amount,
 545  			}
 546  		}
 547  	}
 548  	outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
 549  	for i := range details.MsgTx.TxOut {
 550  		credIndex := len(outputs)
 551  		mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
 552  		if !mine {
 553  			continue
 554  		}
 555  		acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
 556  		output := TransactionSummaryOutput{
 557  			Index:    uint32(i),
 558  			Account:  acct,
 559  			Internal: internal,
 560  		}
 561  		outputs = append(outputs, output)
 562  	}
 563  	return TransactionSummary{
 564  		Hash:        &details.Hash,
 565  		Transaction: serializedTx,
 566  		MyInputs:    inputs,
 567  		MyOutputs:   outputs,
 568  		Fee:         fee,
 569  		Timestamp:   details.Received.Unix(),
 570  	}
 571  }
 572  func newNotificationServer(wallet *Wallet) *NotificationServer {
 573  	return &NotificationServer{
 574  		spentness: make(map[uint32][]chan *SpentnessNotifications),
 575  		wallet:    wallet,
 576  	}
 577  }
 578  func relevantAccounts(w *Wallet, m map[uint32]amt.Amount, txs []TransactionSummary) {
 579  	for _, tx := range txs {
 580  		for _, d := range tx.MyInputs {
 581  			m[d.PreviousAccount] = 0
 582  		}
 583  		for _, c := range tx.MyOutputs {
 584  			m[c.Account] = 0
 585  		}
 586  	}
 587  }
 588  func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]amt.Amount) (e error) {
 589  	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
 590  	unspent, e := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey))
 591  	if e != nil {
 592  		return e
 593  	}
 594  	for i := range unspent {
 595  		output := &unspent[i]
 596  		var outputAcct uint32
 597  		var addrs []btcaddr.Address
 598  		_, addrs, _, e = txscript.ExtractPkScriptAddrs(
 599  			output.PkScript, w.chainParams,
 600  		)
 601  		if e == nil && len(addrs) > 0 {
 602  			_, outputAcct, e = w.Manager.AddrAccount(addrmgrNs, addrs[0])
 603  		}
 604  		if e == nil {
 605  			_, ok := m[outputAcct]
 606  			if ok {
 607  				m[outputAcct] += output.Amount
 608  			}
 609  		}
 610  	}
 611  	return nil
 612  }
 613