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

     1  // Copyright (c) 2015-2020 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package wallet
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"runtime"
    11  	"sync"
    12  
    13  	"decred.org/dcrwallet/v3/errors"
    14  	"decred.org/dcrwallet/v3/rpc/client/dcrd"
    15  	"decred.org/dcrwallet/v3/validate"
    16  	"decred.org/dcrwallet/v3/wallet/udb"
    17  	"decred.org/dcrwallet/v3/wallet/walletdb"
    18  	"github.com/decred/dcrd/blockchain/stake/v5"
    19  	"github.com/decred/dcrd/chaincfg/chainhash"
    20  	"github.com/decred/dcrd/gcs/v4/blockcf2"
    21  	hd "github.com/decred/dcrd/hdkeychain/v3"
    22  	"github.com/decred/dcrd/wire"
    23  	"golang.org/x/sync/errgroup"
    24  )
    25  
    26  // blockCommitmentCache records exact output scripts committed by block filters,
    27  // keyed by block hash, to check for GCS false positives.
    28  type blockCommitmentCache map[chainhash.Hash]map[string]struct{}
    29  
    30  func blockCommitments(block *wire.MsgBlock) map[string]struct{} {
    31  	c := make(map[string]struct{})
    32  	for _, tx := range block.Transactions {
    33  		for _, out := range tx.TxOut {
    34  			c[string(out.PkScript)] = struct{}{}
    35  		}
    36  	}
    37  	for _, tx := range block.STransactions {
    38  		switch stake.DetermineTxType(tx) {
    39  		case stake.TxTypeSStx: // Ticket purchase
    40  			for i := 2; i < len(tx.TxOut); i += 2 { // Iterate change outputs
    41  				out := tx.TxOut[i]
    42  				if out.Value != 0 {
    43  					script := out.PkScript[1:] // Slice off stake opcode
    44  					c[string(script)] = struct{}{}
    45  				}
    46  			}
    47  		case stake.TxTypeSSGen: // Vote
    48  			for _, out := range tx.TxOut[2:] { // Iterate generated coins
    49  				script := out.PkScript[1:] // Slice off stake opcode
    50  				c[string(script)] = struct{}{}
    51  			}
    52  		case stake.TxTypeSSRtx: // Revocation
    53  			for _, out := range tx.TxOut {
    54  				script := out.PkScript[1:] // Slice off stake opcode
    55  				c[string(script)] = struct{}{}
    56  			}
    57  		}
    58  	}
    59  	return c
    60  }
    61  
    62  func cacheMissingCommitments(ctx context.Context, p Peer, cache blockCommitmentCache, include []*chainhash.Hash) error {
    63  	for i := 0; i < len(include); i += wire.MaxBlocksPerMsg {
    64  		include := include[i:]
    65  		if len(include) > wire.MaxBlocksPerMsg {
    66  			include = include[:wire.MaxBlocksPerMsg]
    67  		}
    68  
    69  		var fetchBlocks []*chainhash.Hash
    70  		for _, b := range include {
    71  			if _, ok := cache[*b]; !ok {
    72  				fetchBlocks = append(fetchBlocks, b)
    73  			}
    74  		}
    75  		if len(fetchBlocks) == 0 {
    76  			return nil
    77  		}
    78  		blocks, err := p.Blocks(ctx, fetchBlocks)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		for i, b := range blocks {
    83  			cache[*fetchBlocks[i]] = blockCommitments(b)
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  type accountUsage struct {
    90  	account        uint32
    91  	extkey, intkey *hd.ExtendedKey
    92  	extLastUsed    uint32
    93  	intLastUsed    uint32
    94  	extlo, intlo   uint32
    95  	exthi, inthi   uint32 // Set to lo - 1 when finished, be cautious of unsigned underflow
    96  }
    97  
    98  type scriptPath struct {
    99  	usageIndex             int
   100  	account, branch, index uint32
   101  }
   102  
   103  type addrFinder struct {
   104  	w           *Wallet
   105  	gaplimit    uint32
   106  	segments    uint32
   107  	usage       []accountUsage
   108  	commitments blockCommitmentCache
   109  	mu          sync.RWMutex
   110  }
   111  
   112  func newAddrFinder(ctx context.Context, w *Wallet, gapLimit uint32) (*addrFinder, error) {
   113  	a := &addrFinder{
   114  		w:           w,
   115  		gaplimit:    gapLimit,
   116  		segments:    hd.HardenedKeyStart / gapLimit,
   117  		commitments: make(blockCommitmentCache),
   118  	}
   119  	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   120  		ns := dbtx.ReadBucket(waddrmgrNamespaceKey)
   121  		lastAcct, err := w.manager.LastAccount(ns)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		lastImported, err := w.manager.LastImportedAccount(dbtx)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		a.usage = make([]accountUsage, 0, lastAcct+1+lastImported-udb.ImportedAddrAccount)
   130  		addUsage := func(acct uint32) error {
   131  			extkey, err := w.manager.AccountBranchExtendedPubKey(dbtx, acct, 0)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			intkey, err := w.manager.AccountBranchExtendedPubKey(dbtx, acct, 1)
   136  			if err != nil {
   137  				return err
   138  			}
   139  			props, err := w.manager.AccountProperties(ns, acct)
   140  			if err != nil {
   141  				return err
   142  			}
   143  			var extlo, intlo uint32
   144  			if props.LastUsedExternalIndex != ^uint32(0) {
   145  				extlo = props.LastUsedExternalIndex / a.gaplimit
   146  			}
   147  			if props.LastUsedInternalIndex != ^uint32(0) {
   148  				intlo = props.LastUsedInternalIndex / a.gaplimit
   149  			}
   150  			a.usage = append(a.usage, accountUsage{
   151  				account:     acct,
   152  				extkey:      extkey,
   153  				intkey:      intkey,
   154  				extLastUsed: props.LastUsedExternalIndex,
   155  				intLastUsed: props.LastUsedInternalIndex,
   156  				extlo:       extlo,
   157  				exthi:       a.segments - 1,
   158  				intlo:       intlo,
   159  				inthi:       a.segments - 1,
   160  			})
   161  			return nil
   162  		}
   163  		for acct := uint32(0); acct <= lastAcct; acct++ {
   164  			if err := addUsage(acct); err != nil {
   165  				return err
   166  			}
   167  		}
   168  		for acct := uint32(udb.ImportedAddrAccount + 1); acct <= lastImported; acct++ {
   169  			if err := addUsage(acct); err != nil {
   170  				return err
   171  			}
   172  		}
   173  		return nil
   174  	})
   175  	return a, err
   176  }
   177  
   178  func (a *addrFinder) find(ctx context.Context, start *chainhash.Hash, p Peer) error {
   179  	// Load main chain cfilters beginning with start.
   180  	var fs []*udb.BlockCFilter
   181  	err := walletdb.View(ctx, a.w.db, func(dbtx walletdb.ReadTx) error {
   182  		h, err := a.w.txStore.GetBlockHeader(dbtx, start)
   183  		if err != nil {
   184  			return err
   185  		}
   186  		_, tipHeight := a.w.txStore.MainChainTip(dbtx)
   187  		storage := make([]*udb.BlockCFilter, tipHeight-int32(h.Height))
   188  		fs, err = a.w.txStore.GetMainChainCFilters(dbtx, start, true, storage)
   189  		return err
   190  	})
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	for {
   196  		if err := ctx.Err(); err != nil {
   197  			return err
   198  		}
   199  
   200  		// Derive one bsearch iteration of filter data for all branches.
   201  		// Map address scripts to their HD path.
   202  		var data [][]byte
   203  		scrPaths := make(map[string]scriptPath)
   204  		addBranch := func(branchPub *hd.ExtendedKey, usageIndex int, acct, branch, lo, hi uint32) error {
   205  			if lo > hi || hi >= a.segments { // Terminating condition
   206  				return nil
   207  			}
   208  			mid := (hi + lo) / 2
   209  			begin := mid * a.gaplimit
   210  			addrs, err := deriveChildAddresses(branchPub, begin, a.gaplimit, a.w.chainParams)
   211  			if err != nil {
   212  				return err
   213  			}
   214  			for i, addr := range addrs {
   215  				_, scr := addr.PaymentScript()
   216  				data = append(data, scr)
   217  				scrPaths[string(scr)] = scriptPath{
   218  					usageIndex: usageIndex,
   219  					account:    acct,
   220  					branch:     branch,
   221  					index:      mid*a.gaplimit + uint32(i),
   222  				}
   223  			}
   224  			return nil
   225  		}
   226  		for i := range a.usage {
   227  			u := &a.usage[i]
   228  			err = addBranch(u.extkey, i, u.account, 0, u.extlo, u.exthi)
   229  			if err != nil {
   230  				return err
   231  			}
   232  			err = addBranch(u.intkey, i, u.account, 1, u.intlo, u.inthi)
   233  			if err != nil {
   234  				return err
   235  			}
   236  		}
   237  
   238  		if len(data) == 0 {
   239  			return nil
   240  		}
   241  
   242  		// Record committed scripts of matching filters.
   243  		err := a.filter(ctx, fs, data, p)
   244  		if err != nil {
   245  			return err
   246  		}
   247  
   248  		var wg sync.WaitGroup
   249  		wg.Add(len(a.commitments))
   250  		for hash, commitments := range a.commitments {
   251  			hash, commitments := hash, commitments
   252  			go func() {
   253  				for _, scr := range data {
   254  					if _, ok := commitments[string(scr)]; !ok {
   255  						continue
   256  					}
   257  
   258  					// Found address script in this block.  Look up address path
   259  					// and record usage.
   260  					path := scrPaths[string(scr)]
   261  					log.Debugf("Found match for script %x path %v in block %v", scr, path, &hash)
   262  					u := &a.usage[path.usageIndex]
   263  					a.mu.Lock()
   264  					switch path.branch {
   265  					case 0: // external
   266  						if u.extLastUsed == ^uint32(0) || path.index > u.extLastUsed {
   267  							u.extLastUsed = path.index
   268  						}
   269  					case 1: // internal
   270  						if u.intLastUsed == ^uint32(0) || path.index > u.intLastUsed {
   271  							u.intLastUsed = path.index
   272  						}
   273  					}
   274  					a.mu.Unlock()
   275  				}
   276  				wg.Done()
   277  			}()
   278  		}
   279  		wg.Wait()
   280  
   281  		// Update hi/lo segments for next bisect iteration
   282  		for i := range a.usage {
   283  			u := &a.usage[i]
   284  			if u.extlo <= u.exthi {
   285  				mid := (u.exthi + u.extlo) / 2
   286  				// When the last used index is in this segment's index half open
   287  				// range [begin,end) then an address was found in this segment.
   288  				begin := mid * a.gaplimit
   289  				end := begin + a.gaplimit
   290  				if u.extLastUsed >= begin && u.extLastUsed < end {
   291  					u.extlo = mid + 1
   292  				} else {
   293  					u.exthi = mid - 1
   294  				}
   295  			}
   296  			if u.intlo <= u.inthi {
   297  				mid := (u.inthi + u.intlo) / 2
   298  				begin := mid * a.gaplimit
   299  				end := begin + a.gaplimit
   300  				if u.intLastUsed >= begin && u.intLastUsed < end {
   301  					u.intlo = mid + 1
   302  				} else {
   303  					u.inthi = mid - 1
   304  				}
   305  			}
   306  		}
   307  	}
   308  }
   309  
   310  func (a *addrFinder) filter(ctx context.Context, fs []*udb.BlockCFilter, data blockcf2.Entries, p Peer) error {
   311  	g, ctx := errgroup.WithContext(ctx)
   312  	for i := 0; i < len(fs); i += wire.MaxBlocksPerMsg {
   313  		fs := fs[i:]
   314  		if len(fs) > wire.MaxBlocksPerMsg {
   315  			fs = fs[:wire.MaxBlocksPerMsg]
   316  		}
   317  		g.Go(func() error {
   318  			var fetch []*chainhash.Hash
   319  			for _, f := range fs {
   320  				if f.FilterV2.N() == 0 {
   321  					continue
   322  				}
   323  				a.mu.RLock()
   324  				_, ok := a.commitments[f.BlockHash]
   325  				a.mu.RUnlock()
   326  				if ok {
   327  					continue // Previously fetched block
   328  				}
   329  				if f.FilterV2.MatchAny(f.Key, data) {
   330  					fetch = append(fetch, &f.BlockHash)
   331  				}
   332  			}
   333  			if len(fetch) == 0 {
   334  				return nil
   335  			}
   336  			blocks, err := p.Blocks(ctx, fetch)
   337  			if err != nil {
   338  				return err
   339  			}
   340  			for i, b := range blocks {
   341  				i, b := i, b
   342  				g.Go(func() error {
   343  					// validate blocks
   344  					err := validate.MerkleRoots(b)
   345  					if err != nil {
   346  						err = validate.DCP0005MerkleRoot(b)
   347  					}
   348  					if err != nil {
   349  						return err
   350  					}
   351  
   352  					c := blockCommitments(b)
   353  					a.mu.Lock()
   354  					a.commitments[*fetch[i]] = c
   355  					a.mu.Unlock()
   356  					return nil
   357  				})
   358  			}
   359  			return nil
   360  		})
   361  	}
   362  	return g.Wait()
   363  }
   364  
   365  // filterBlocks returns the block hashes of all blocks in the main chain,
   366  // starting at startBlock, whose cfilters match against data.
   367  func (w *Wallet) filterBlocks(ctx context.Context, startBlock *chainhash.Hash, data blockcf2.Entries) ([]*chainhash.Hash, error) {
   368  	var matches []*chainhash.Hash
   369  	var mu sync.Mutex
   370  	var wg sync.WaitGroup
   371  	wg.Add(runtime.NumCPU())
   372  	c := make(chan []*udb.BlockCFilter, runtime.NumCPU())
   373  	for i := 0; i < runtime.NumCPU(); i++ {
   374  		go func() {
   375  			for blocks := range c {
   376  				for _, b := range blocks {
   377  					if b.FilterV2.N() == 0 {
   378  						continue
   379  					}
   380  					if b.FilterV2.MatchAny(b.Key, data) {
   381  						h := b.BlockHash
   382  						mu.Lock()
   383  						matches = append(matches, &h)
   384  						mu.Unlock()
   385  					}
   386  				}
   387  			}
   388  			wg.Done()
   389  		}()
   390  	}
   391  	startHash := startBlock
   392  	inclusive := true
   393  	for {
   394  		if ctx.Err() != nil {
   395  			// Can return before workers finish
   396  			close(c)
   397  			return nil, ctx.Err()
   398  		}
   399  		storage := make([]*udb.BlockCFilter, 2000)
   400  		var filters []*udb.BlockCFilter
   401  		err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   402  			var err error
   403  			filters, err = w.txStore.GetMainChainCFilters(dbtx, startHash,
   404  				inclusive, storage)
   405  			return err
   406  		})
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  		if len(filters) == 0 {
   411  			break
   412  		}
   413  		c <- filters
   414  		startHash = &filters[len(filters)-1].BlockHash
   415  		inclusive = false
   416  	}
   417  	close(c)
   418  	wg.Wait()
   419  	return matches, ctx.Err()
   420  }
   421  
   422  func (w *Wallet) findLastUsedAccount(ctx context.Context, p Peer, blockCache blockCommitmentCache, coinTypeXpriv *hd.ExtendedKey, gapLimit uint32) (uint32, error) {
   423  	var (
   424  		acctGapLimit = uint32(w.accountGapLimit)
   425  		addrScripts  = make([][]byte, 0, acctGapLimit*gapLimit*2*2)
   426  	)
   427  
   428  	lastUsedInRange := func(begin, end uint32) (uint32, error) { // [begin,end)
   429  		addrScripts = addrScripts[:0]
   430  		addrScriptAccts := make(map[string]uint32)
   431  		if end >= hd.HardenedKeyStart {
   432  			end = hd.HardenedKeyStart - 1
   433  		}
   434  		for acct := begin; acct < end; acct++ {
   435  			xpriv, err := coinTypeXpriv.Child(hd.HardenedKeyStart + acct)
   436  			if err != nil {
   437  				return 0, err
   438  			}
   439  			xpub := xpriv.Neuter()
   440  			extKey, intKey, err := deriveBranches(xpub)
   441  			if err != nil {
   442  				xpriv.Zero()
   443  				return 0, err
   444  			}
   445  			addrs, err := deriveChildAddresses(extKey, 0, gapLimit, w.chainParams)
   446  			xpriv.Zero()
   447  			if err != nil {
   448  				return 0, err
   449  			}
   450  			for _, a := range addrs {
   451  				_, script := a.PaymentScript()
   452  				addrScriptAccts[string(script)] = acct
   453  				addrScripts = append(addrScripts, script)
   454  			}
   455  			addrs, err = deriveChildAddresses(intKey, 0, gapLimit, w.chainParams)
   456  			if err != nil {
   457  				return 0, err
   458  			}
   459  			for _, a := range addrs {
   460  				_, script := a.PaymentScript()
   461  				addrScriptAccts[string(script)] = acct
   462  				addrScripts = append(addrScripts, script)
   463  			}
   464  		}
   465  
   466  		searchBlocks, err := w.filterBlocks(ctx, &w.chainParams.GenesisHash, addrScripts)
   467  		if err != nil {
   468  			return 0, err
   469  		}
   470  
   471  		// Fetch blocks that have not been fetched yet, and reduce them to a set
   472  		// of output script commitments.
   473  		err = cacheMissingCommitments(ctx, p, blockCache, searchBlocks)
   474  		if err != nil {
   475  			return 0, err
   476  		}
   477  
   478  		// Search matching blocks for account usage.
   479  		var lastUsed uint32
   480  		for _, b := range searchBlocks {
   481  			commitments := blockCache[*b]
   482  			for _, script := range addrScripts {
   483  				if _, ok := commitments[string(script)]; !ok {
   484  					continue
   485  				}
   486  
   487  				// Filter match was not a false positive and an output pays to a
   488  				// matching address in the block.  Look up the account of the
   489  				// script and increase the last used account when necessary.
   490  				acct := addrScriptAccts[string(script)]
   491  				log.Debugf("Found match for script %x account %v in block %v",
   492  					script, acct, b)
   493  				if lastUsed < acct {
   494  					lastUsed = acct
   495  				}
   496  			}
   497  		}
   498  		return lastUsed, nil
   499  	}
   500  
   501  	// A binary search may be needed to efficiently find the last used account
   502  	// in the case where many accounts are used.  However, for most users, only
   503  	// a small number of accounts are ever created so a linear scan is performed
   504  	// first.  Search through the first two segments of accounts, and when the
   505  	// last used account is not in the second segment, the bsearch is
   506  	// unnecessary.
   507  	lastUsed, err := lastUsedInRange(0, acctGapLimit*2)
   508  	if err != nil {
   509  		return 0, err
   510  	}
   511  	if lastUsed < acctGapLimit {
   512  		return lastUsed, nil
   513  	}
   514  
   515  	// Fallback to a binary search, starting in the third segment
   516  	var lo, hi uint32 = 2, hd.HardenedKeyStart / acctGapLimit
   517  	for lo <= hi {
   518  		mid := (hi + lo) / 2
   519  		begin := mid * acctGapLimit
   520  		end := begin + acctGapLimit
   521  		last, err := lastUsedInRange(begin, end)
   522  		if err != nil {
   523  			return 0, err
   524  		}
   525  		if last > lastUsed {
   526  			lastUsed = last
   527  		}
   528  		if mid == 0 {
   529  			break
   530  		}
   531  		hi = mid - 1
   532  	}
   533  	return lastUsed, nil
   534  }
   535  
   536  // existsAddrIndexFinder implements address and account discovery using the
   537  // exists address index of a trusted dcrd RPC server.
   538  type existsAddrIndexFinder struct {
   539  	wallet   *Wallet
   540  	rpc      *dcrd.RPC
   541  	gapLimit uint32
   542  }
   543  
   544  func (f *existsAddrIndexFinder) findLastUsedAccount(ctx context.Context, coinTypeXpriv *hd.ExtendedKey) (uint32, error) {
   545  	scanLen := uint32(f.wallet.accountGapLimit)
   546  	var (
   547  		lastUsed uint32
   548  		lo, hi   uint32 = 0, hd.HardenedKeyStart / scanLen
   549  	)
   550  Bsearch:
   551  	for lo <= hi {
   552  		mid := (hi + lo) / 2
   553  		type result struct {
   554  			used    bool
   555  			account uint32
   556  			err     error
   557  		}
   558  		var results = make([]result, scanLen)
   559  		var wg sync.WaitGroup
   560  		for i := int(scanLen) - 1; i >= 0; i-- {
   561  			i := i
   562  			account := mid*scanLen + uint32(i)
   563  			if account >= hd.HardenedKeyStart {
   564  				continue
   565  			}
   566  			xpriv, err := coinTypeXpriv.Child(hd.HardenedKeyStart + account)
   567  			if err != nil {
   568  				return 0, err
   569  			}
   570  			xpub := xpriv.Neuter()
   571  			wg.Add(1)
   572  			go func() {
   573  				used, err := f.accountUsed(ctx, xpub)
   574  				xpriv.Zero()
   575  				results[i] = result{used, account, err}
   576  				wg.Done()
   577  			}()
   578  		}
   579  		wg.Wait()
   580  		for i := int(scanLen) - 1; i >= 0; i-- {
   581  			if results[i].err != nil {
   582  				return 0, results[i].err
   583  			}
   584  			if results[i].used {
   585  				lastUsed = results[i].account
   586  				lo = mid + 1
   587  				continue Bsearch
   588  			}
   589  		}
   590  		if mid == 0 {
   591  			break
   592  		}
   593  		hi = mid - 1
   594  	}
   595  	return lastUsed, nil
   596  }
   597  
   598  func (f *existsAddrIndexFinder) accountUsed(ctx context.Context, xpub *hd.ExtendedKey) (bool, error) {
   599  	extKey, intKey, err := deriveBranches(xpub)
   600  	if err != nil {
   601  		return false, err
   602  	}
   603  	type result struct {
   604  		used bool
   605  		err  error
   606  	}
   607  	results := make(chan result, 2)
   608  	merge := func(used bool, err error) {
   609  		results <- result{used, err}
   610  	}
   611  	go func() { merge(f.branchUsed(ctx, extKey)) }()
   612  	go func() { merge(f.branchUsed(ctx, intKey)) }()
   613  	for i := 0; i < 2; i++ {
   614  		r := <-results
   615  		if r.err != nil {
   616  			return false, err
   617  		}
   618  		if r.used {
   619  			return true, nil
   620  		}
   621  	}
   622  	return false, nil
   623  }
   624  
   625  func (f *existsAddrIndexFinder) branchUsed(ctx context.Context, branchXpub *hd.ExtendedKey) (bool, error) {
   626  	addrs, err := deriveChildAddresses(branchXpub, 0, f.wallet.gapLimit, f.wallet.chainParams)
   627  	if err != nil {
   628  		return false, err
   629  	}
   630  	bits, err := f.rpc.UsedAddresses(ctx, addrs)
   631  	if err != nil {
   632  		return false, err
   633  	}
   634  	for _, b := range bits {
   635  		if b != 0 {
   636  			return true, nil
   637  		}
   638  	}
   639  	return false, nil
   640  }
   641  
   642  // findLastUsedAddress returns the child index of the last used child address
   643  // derived from a branch key.  If no addresses are found, ^uint32(0) is
   644  // returned.
   645  func (f *existsAddrIndexFinder) findLastUsedAddress(ctx context.Context, xpub *hd.ExtendedKey) (uint32, error) {
   646  	var (
   647  		lastUsed        = ^uint32(0)
   648  		scanLen         = f.gapLimit
   649  		segments        = hd.HardenedKeyStart / scanLen
   650  		lo, hi   uint32 = 0, segments - 1
   651  	)
   652  Bsearch:
   653  	for lo <= hi {
   654  		mid := (hi + lo) / 2
   655  		addrs, err := deriveChildAddresses(xpub, mid*scanLen, scanLen, f.wallet.chainParams)
   656  		if err != nil {
   657  			return 0, err
   658  		}
   659  		existsBits, err := f.rpc.UsedAddresses(ctx, addrs)
   660  		if err != nil {
   661  			return 0, err
   662  		}
   663  		for i := len(addrs) - 1; i >= 0; i-- {
   664  			if existsBits.Get(i) {
   665  				lastUsed = mid*scanLen + uint32(i)
   666  				lo = mid + 1
   667  				continue Bsearch
   668  			}
   669  		}
   670  		if mid == 0 {
   671  			break
   672  		}
   673  		hi = mid - 1
   674  	}
   675  	return lastUsed, nil
   676  }
   677  
   678  func (f *existsAddrIndexFinder) find(ctx context.Context, finder *addrFinder) error {
   679  	var g errgroup.Group
   680  	lastUsed := func(acct, branch uint32, index *uint32) error {
   681  		var k *hd.ExtendedKey
   682  		err := walletdb.View(ctx, f.wallet.db, func(tx walletdb.ReadTx) error {
   683  			var err error
   684  			k, err = f.wallet.manager.AccountBranchExtendedPubKey(tx, acct, branch)
   685  			return err
   686  		})
   687  		if err != nil {
   688  			return err
   689  		}
   690  		lastUsed, err := f.findLastUsedAddress(ctx, k)
   691  		if err != nil {
   692  			return err
   693  		}
   694  		*index = lastUsed
   695  		return nil
   696  	}
   697  	for i := range finder.usage {
   698  		u := &finder.usage[i]
   699  		acct := u.account
   700  		g.Go(func() error { return lastUsed(acct, 0, &u.extLastUsed) })
   701  		g.Go(func() error { return lastUsed(acct, 1, &u.intLastUsed) })
   702  	}
   703  	return g.Wait()
   704  }
   705  
   706  func rpcFromPeer(p Peer) (*dcrd.RPC, bool) {
   707  	switch p := p.(type) {
   708  	case Caller:
   709  		return dcrd.New(p), true
   710  	default:
   711  		return nil, false
   712  	}
   713  }
   714  
   715  // DiscoverActiveAddresses searches for future wallet address usage in all
   716  // blocks starting from startBlock.  If discoverAccts is true, used accounts
   717  // will be discovered as well.  This feature requires the wallet to be unlocked
   718  // in order to derive hardened account extended pubkeys.
   719  //
   720  // If the wallet is currently on the legacy coin type and no address or account
   721  // usage is observed and coin type upgrades are not disabled, the wallet will be
   722  // upgraded to the SLIP0044 coin type and the address discovery will occur
   723  // again.
   724  func (w *Wallet) DiscoverActiveAddresses(ctx context.Context, p Peer, startBlock *chainhash.Hash, discoverAccts bool, gapLimit uint32) error {
   725  	const op errors.Op = "wallet.DiscoverActiveAddresses"
   726  	_, slip0044CoinType := udb.CoinTypes(w.chainParams)
   727  	var activeCoinType uint32
   728  	var coinTypeKnown, isSLIP0044CoinType bool
   729  	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   730  		var err error
   731  		activeCoinType, err = w.manager.CoinType(dbtx)
   732  		if errors.Is(err, errors.WatchingOnly) {
   733  			return nil
   734  		}
   735  		if err != nil {
   736  			return err
   737  		}
   738  		coinTypeKnown = true
   739  		isSLIP0044CoinType = activeCoinType == slip0044CoinType
   740  		log.Debugf("DiscoverActiveAddresses: activeCoinType=%d", activeCoinType)
   741  		return nil
   742  	})
   743  	if err != nil {
   744  		return errors.E(op, err)
   745  	}
   746  
   747  	// Map block hashes to a set of output scripts from the block.  This map is
   748  	// queried to avoid fetching the same block multiple times, and blocks are
   749  	// reduced to a set of committed scripts as that is the only thing being
   750  	// searched for.
   751  	blockAddresses := make(blockCommitmentCache)
   752  
   753  	// Start by rescanning the accounts and determining what the current account
   754  	// index is. This scan should only ever be performed if we're restoring our
   755  	// wallet from seed.
   756  	if discoverAccts {
   757  		log.Infof("Discovering used accounts")
   758  		var coinTypePrivKey *hd.ExtendedKey
   759  		defer func() {
   760  			if coinTypePrivKey != nil {
   761  				coinTypePrivKey.Zero()
   762  			}
   763  		}()
   764  		err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
   765  			var err error
   766  			coinTypePrivKey, err = w.manager.CoinTypePrivKey(tx)
   767  			return err
   768  		})
   769  		if err != nil {
   770  			return errors.E(op, err)
   771  		}
   772  		var lastUsed uint32
   773  		rpc, ok := rpcFromPeer(p)
   774  		if ok {
   775  			f := existsAddrIndexFinder{w, rpc, gapLimit}
   776  			lastUsed, err = f.findLastUsedAccount(ctx, coinTypePrivKey)
   777  		} else {
   778  			lastUsed, err = w.findLastUsedAccount(ctx, p, blockAddresses, coinTypePrivKey, gapLimit)
   779  		}
   780  		if err != nil {
   781  			return errors.E(op, err)
   782  		}
   783  		if lastUsed != 0 {
   784  			var lastRecorded uint32
   785  			acctXpubs := make(map[uint32]*hd.ExtendedKey)
   786  			w.addressBuffersMu.Lock()
   787  			err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error {
   788  				ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
   789  				var err error
   790  				lastRecorded, err = w.manager.LastAccount(ns)
   791  				if err != nil {
   792  					return err
   793  				}
   794  				for acct := lastRecorded + 1; acct <= lastUsed; acct++ {
   795  					acct, err := w.manager.NewAccount(ns, fmt.Sprintf("account-%d", acct))
   796  					if err != nil {
   797  						return err
   798  					}
   799  					xpub, err := w.manager.AccountExtendedPubKey(tx, acct)
   800  					if err != nil {
   801  						return err
   802  					}
   803  					acctXpubs[acct] = xpub
   804  				}
   805  				return nil
   806  			})
   807  			if err != nil {
   808  				w.addressBuffersMu.Unlock()
   809  				return errors.E(op, err)
   810  			}
   811  			for acct := lastRecorded + 1; acct <= lastUsed; acct++ {
   812  				_, ok := w.addressBuffers[acct]
   813  				if !ok {
   814  					xpub := acctXpubs[acct]
   815  					extKey, intKey, err := deriveBranches(xpub)
   816  					if err != nil {
   817  						w.addressBuffersMu.Unlock()
   818  						return errors.E(op, err)
   819  					}
   820  					w.addressBuffers[acct] = &bip0044AccountData{
   821  						xpub:        xpub,
   822  						albExternal: addressBuffer{branchXpub: extKey},
   823  						albInternal: addressBuffer{branchXpub: intKey},
   824  					}
   825  				}
   826  			}
   827  			w.addressBuffersMu.Unlock()
   828  		}
   829  	}
   830  
   831  	// Discover address usage within known accounts
   832  	// Usage recorded in finder.usage
   833  	finder, err := newAddrFinder(ctx, w, gapLimit)
   834  	if err != nil {
   835  		return errors.E(op, err)
   836  	}
   837  	log.Infof("Discovering used addresses for %d account(s)", len(finder.usage))
   838  	lastUsed := append([]accountUsage(nil), finder.usage...)
   839  	rpc, ok := rpcFromPeer(p)
   840  	if ok {
   841  		f := existsAddrIndexFinder{w, rpc, gapLimit}
   842  		err = f.find(ctx, finder)
   843  	} else {
   844  		err = finder.find(ctx, startBlock, p)
   845  	}
   846  	if err != nil {
   847  		return errors.E(op, err)
   848  	}
   849  	for i := range finder.usage {
   850  		u := &finder.usage[i]
   851  		log.Infof("Account %d next child indexes: external:%d internal:%d",
   852  			u.account, u.extLastUsed+1, u.intLastUsed+1)
   853  	}
   854  
   855  	// Save discovered addresses for each account plus additional future
   856  	// addresses that may be used by other wallets sharing the same seed.
   857  	// Multiple updates are used to allow cancellation.
   858  	log.Infof("Updating DB with discovered addresses...")
   859  	for i := range finder.usage {
   860  		u := &finder.usage[i]
   861  		acct := u.account
   862  
   863  		const N = 256
   864  		max := u.extLastUsed + gapLimit
   865  		for j := lastUsed[i].extLastUsed; ; j += N {
   866  			if ctx.Err() != nil {
   867  				return ctx.Err()
   868  			}
   869  
   870  			to := j + N
   871  			if to > max {
   872  				to = max
   873  			}
   874  			err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   875  				ns := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
   876  				return w.manager.SyncAccountToAddrIndex(ns, acct, to, 0)
   877  			})
   878  			if err != nil {
   879  				return errors.E(op, err)
   880  			}
   881  			if to == max {
   882  				break
   883  			}
   884  		}
   885  
   886  		max = u.intLastUsed + gapLimit
   887  		for j := lastUsed[i].intLastUsed; ; j += N {
   888  			if ctx.Err() != nil {
   889  				return ctx.Err()
   890  			}
   891  
   892  			to := j + N
   893  			if to > max {
   894  				to = max
   895  			}
   896  			err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   897  				ns := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
   898  				return w.manager.SyncAccountToAddrIndex(ns, acct, to, 1)
   899  			})
   900  			if err != nil {
   901  				return errors.E(op, err)
   902  			}
   903  			if to == max {
   904  				break
   905  			}
   906  		}
   907  
   908  		// To avoid deadlocks lock mutex before grabbing DB transaction, this is
   909  		// what we do in other places.
   910  		w.addressBuffersMu.Lock()
   911  		err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   912  			ns := dbtx.ReadBucket(waddrmgrNamespaceKey)
   913  			if u.extLastUsed < hd.HardenedKeyStart {
   914  				err = w.manager.MarkUsedChildIndex(dbtx, acct, 0, u.extLastUsed)
   915  				if err != nil {
   916  					return err
   917  				}
   918  			}
   919  			if u.intLastUsed < hd.HardenedKeyStart {
   920  				err = w.manager.MarkUsedChildIndex(dbtx, acct, 1, u.intLastUsed)
   921  				if err != nil {
   922  					return err
   923  				}
   924  			}
   925  
   926  			props, err := w.manager.AccountProperties(ns, acct)
   927  			if err != nil {
   928  				return err
   929  			}
   930  
   931  			// Update last used index and cursor for this account's address
   932  			// buffers.  The cursor must not be reset backwards to avoid the
   933  			// possibility of address reuse.
   934  			acctData := w.addressBuffers[acct]
   935  			extern := &acctData.albExternal
   936  			if props.LastUsedExternalIndex+1 > extern.lastUsed+1 {
   937  				extern.cursor += extern.lastUsed - props.LastUsedExternalIndex
   938  				if extern.cursor > ^uint32(0)>>1 {
   939  					extern.cursor = 0
   940  				}
   941  				extern.lastUsed = props.LastUsedExternalIndex
   942  			}
   943  			intern := &acctData.albInternal
   944  			if props.LastUsedInternalIndex+1 > intern.lastUsed+1 {
   945  				intern.cursor += intern.lastUsed - props.LastUsedInternalIndex
   946  				if intern.cursor > ^uint32(0)>>1 {
   947  					intern.cursor = 0
   948  				}
   949  				intern.lastUsed = props.LastUsedInternalIndex
   950  			}
   951  			return nil
   952  		})
   953  		w.addressBuffersMu.Unlock()
   954  		if err != nil {
   955  			return errors.E(op, err)
   956  		}
   957  	}
   958  
   959  	// If the wallet does not know the current coin type (e.g. it is a watching
   960  	// only wallet created from an account master pubkey) or when the wallet
   961  	// uses the SLIP0044 coin type, there is nothing more to do.
   962  	if !coinTypeKnown || isSLIP0044CoinType {
   963  		log.Infof("Finished address discovery")
   964  		return nil
   965  	}
   966  
   967  	// Do not upgrade legacy coin type wallets if there are returned or used
   968  	// addresses or coin type upgrades are disabled.
   969  	if !isSLIP0044CoinType && (w.disableCoinTypeUpgrades ||
   970  		len(finder.usage) != 1 ||
   971  		finder.usage[0].extLastUsed != ^uint32(0) ||
   972  		finder.usage[0].intLastUsed != ^uint32(0)) {
   973  		log.Infof("Finished address discovery")
   974  		log.Warnf("Wallet contains addresses derived for the legacy BIP0044 " +
   975  			"coin type and seed restores may not work with some other wallet " +
   976  			"software")
   977  		return nil
   978  	}
   979  
   980  	// Upgrade the coin type.
   981  	log.Infof("Upgrading wallet from legacy coin type %d to SLIP0044 coin type %d",
   982  		activeCoinType, slip0044CoinType)
   983  	err = w.UpgradeToSLIP0044CoinType(ctx)
   984  	if err != nil {
   985  		log.Errorf("Coin type upgrade failed: %v", err)
   986  		log.Warnf("Continuing with legacy BIP0044 coin type -- seed restores " +
   987  			"may not work with some other wallet software")
   988  		return nil
   989  	}
   990  	log.Infof("Upgraded coin type.")
   991  
   992  	// Perform address discovery a second time using the upgraded coin type.
   993  	return w.DiscoverActiveAddresses(ctx, p, startBlock, discoverAccts, gapLimit)
   994  }