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 }