func (w *Wallet) defaultScopeManagers() (
	map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager, error) {
	scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
	for _, scope := range waddrmgr.DefaultKeyScopes {
		scopedMgr, err := w.Manager.FetchScopedKeyManager(scope)
		if err != nil {
			return nil, err
		}
		scopedMgrs[scope] = scopedMgr
	}
	return scopedMgrs, nil
}
defaultScopeManagers fetches the ScopedKeyManagers from the wallet using the
default set of key scopes.
package wallet
import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"
	"golang.org/x/net/proxy"
	"github.com/davecgh/go-spew/spew"
	"github.com/gcash/bchd/bchec"
	"github.com/gcash/bchd/blockchain"
	"github.com/gcash/bchd/btcjson"
	"github.com/gcash/bchd/chaincfg"
	"github.com/gcash/bchd/chaincfg/chainhash"
	"github.com/gcash/bchd/txscript"
	"github.com/gcash/bchd/wire"
	"github.com/gcash/bchutil"
	"github.com/gcash/bchutil/hdkeychain"
	"github.com/gcash/bchwallet/chain"
	"github.com/gcash/bchwallet/waddrmgr"
	"github.com/gcash/bchwallet/wallet/txauthor"
	"github.com/gcash/bchwallet/wallet/txrules"
	"github.com/gcash/bchwallet/walletdb"
	"github.com/gcash/bchwallet/walletdb/migration"
	"github.com/gcash/bchwallet/wtxmgr"
)
const (
	InsecurePubPassphrase = "public"
	walletDbWatchingOnlyName = "wowallet.db"
	recoveryBatchSize = 2000
)
var (
	ErrNotSynced = errors.New("wallet is not synchronized with the chain server")
	ErrWalletShuttingDown = errors.New("wallet shutting down")
	waddrmgrNamespaceKey = []byte("waddrmgr")
	wtxmgrNamespaceKey   = []byte("wtxmgr")
)
type Wallet struct {
	publicPassphrase []byte
	db      walletdb.DB
	Manager *waddrmgr.Manager
	TxStore *wtxmgr.Store
	chainClient        chain.Interface
	chainClientLock    sync.Mutex
	chainClientSynced  bool
	chainClientSyncMtx sync.Mutex
	lockedOutpoints map[wire.OutPoint]struct{}
	recoveryWindow uint32
	rescanAddJob        chan *RescanJob
	rescanBatch         chan *rescanBatch
	rescanNotifications chan interface{}  
	rescanProgress      chan *RescanProgressMsg
	rescanFinished      chan *RescanFinishedMsg
	recoveryProgess chan *RecoveryProgessMsg
	createTxRequests chan createTxRequest
	unlockRequests     chan unlockRequest
	lockRequests       chan struct{}
	holdUnlockRequests chan chan heldUnlock
	lockState          chan bool
	changePassphrase   chan changePassphraseRequest
	changePassphrases  chan changePassphrasesRequest
	reorganizingLock sync.Mutex
	reorganizeToHash chainhash.Hash
	reorganizing     bool
	NtfnServer *NotificationServer
	chainParams *chaincfg.Params
	wg          sync.WaitGroup
	started bool
	quit    chan struct{}
	quitMu  sync.Mutex
	recoveryInterruptChan chan struct{}
	filterInterruptChans  []chan struct{}
	recoveryLock          sync.Mutex
	proxyDialer proxy.Dialer
}
func (w *Wallet) Start() {
	w.quitMu.Lock()
	select {
	case <-w.quit:
		w.WaitForShutdown()
		w.quit = make(chan struct{})
	default:
		if w.started {
			w.quitMu.Unlock()
			return
		}
		w.started = true
	}
	w.quitMu.Unlock()
	w.wg.Add(2)
	go w.txCreator()
	go w.walletLocker()
	go w.recoveryInterruptHandler()
}
func (w *Wallet) recoveryInterruptHandler() {
	for {
		select {
		case <-w.quit:
			return
		case <-w.recoveryInterruptChan:
			w.recoveryLock.Lock()
			for _, c := range w.filterInterruptChans {
				close(c)
			}
			w.filterInterruptChans = []chan struct{}{}
			w.recoveryLock.Unlock()
		}
	}
}
func (w *Wallet) SynchronizeRPC(chainClient chain.Interface) {
	w.quitMu.Lock()
	select {
	case <-w.quit:
		w.quitMu.Unlock()
		return
	default:
	}
	w.quitMu.Unlock()
	w.chainClientLock.Lock()
	if w.chainClient != nil {
		w.chainClientLock.Unlock()
		return
	}
	w.chainClient = chainClient
	switch cc := chainClient.(type) {
	case *chain.NeutrinoClient:
		cc.SetStartTime(w.Manager.Birthday())
	case *chain.BitcoindClient:
		cc.SetBirthday(w.Manager.Birthday())
	}
	w.chainClientLock.Unlock()
	w.wg.Add(4)
	go w.handleChainNotifications()
	go w.rescanBatchHandler()
	go w.rescanProgressHandler()
	go w.rescanRPCHandler()
}
func (w *Wallet) requireChainClient() (chain.Interface, error) {
	w.chainClientLock.Lock()
	chainClient := w.chainClient
	w.chainClientLock.Unlock()
	if chainClient == nil {
		return nil, errors.New("blockchain RPC is inactive")
	}
	return chainClient, nil
}
func (w *Wallet) ChainClient() chain.Interface {
	w.chainClientLock.Lock()
	chainClient := w.chainClient
	w.chainClientLock.Unlock()
	return chainClient
}
func (w *Wallet) quitChan() <-chan struct{} {
	w.quitMu.Lock()
	c := w.quit
	w.quitMu.Unlock()
	return c
}
func (w *Wallet) Stop() {
	w.quitMu.Lock()
	quit := w.quit
	w.quitMu.Unlock()
	select {
	case <-quit:
	default:
		close(quit)
		w.chainClientLock.Lock()
		if w.chainClient != nil {
			w.chainClient.Stop()
			w.chainClient = nil
		}
		w.chainClientLock.Unlock()
	}
}
func (w *Wallet) ShuttingDown() bool {
	select {
	case <-w.quitChan():
		return true
	default:
		return false
	}
}
func (w *Wallet) WaitForShutdown() {
	w.chainClientLock.Lock()
	if w.chainClient != nil {
		w.chainClient.WaitForShutdown()
	}
	w.chainClientLock.Unlock()
	w.wg.Wait()
}
func (w *Wallet) SynchronizingToNetwork() bool {
	w.chainClientSyncMtx.Lock()
	syncing := w.chainClient != nil
	w.chainClientSyncMtx.Unlock()
	return syncing
}
func (w *Wallet) ChainSynced() bool {
	w.chainClientSyncMtx.Lock()
	synced := w.chainClientSynced
	w.chainClientSyncMtx.Unlock()
	return synced
}
func (w *Wallet) SetChainSynced(synced bool) {
	w.chainClientSyncMtx.Lock()
	w.chainClientSynced = synced
	w.chainClientSyncMtx.Unlock()
}
func (w *Wallet) activeData(dbtx walletdb.ReadTx) ([]bchutil.Address, []wtxmgr.Credit, error) {
	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
	var addrs []bchutil.Address
	err := w.Manager.ForEachActiveAddress(addrmgrNs, func(addr bchutil.Address) error {
		addrs = append(addrs, addr)
		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	unspent, err := w.TxStore.UnspentOutputs(txmgrNs)
	return addrs, unspent, err
}
func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
	chainClient, err := w.requireChainClient()
	if err != nil {
		return err
	}
	neutrinoRecovery := chainClient.BackEnd() == "neutrino" &&
		w.recoveryWindow > 0
	if !w.isDevEnv() || neutrinoRecovery {
		log.Debug("Waiting for chain backend to sync to tip")
		if err := w.waitUntilBackendSynced(chainClient); err != nil {
			return err
		}
		log.Debug("Chain backend synced to tip!")
	}
	if birthdayStamp == nil {
		var err error
		birthdayStamp, err = locateBirthdayBlock(
			chainClient, w.Manager.Birthday(),
		)
		if err != nil {
			return fmt.Errorf("unable to locate birthday block: %v",
				err)
		}
		_, bestHeight, err := chainClient.GetBestBlock()
		if err != nil {
			return err
		}
		startHeight := bestHeight - waddrmgr.MaxReorgDepth + 1
		if startHeight < 0 {
			startHeight = 0
		}
		if birthdayStamp.Height < startHeight {
			startHeight = birthdayStamp.Height
		}
		startHash, err := chainClient.GetBlockHash(int64(startHeight))
		if err != nil {
			return err
		}
		startHeader, err := chainClient.GetBlockHeader(startHash)
		if err != nil {
			return err
		}
		err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
			ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
			err := w.Manager.SetSyncedTo(ns, &waddrmgr.BlockStamp{
				Hash:      *startHash,
				Height:    startHeight,
				Timestamp: startHeader.Timestamp,
			})
			if err != nil {
				return err
			}
			return w.Manager.SetBirthdayBlock(ns, *birthdayStamp, true)
		})
		if err != nil {
			return fmt.Errorf("unable to persist initial sync "+
				"data: %v", err)
		}
	}
	if w.recoveryWindow > 0 {
		if err := w.recovery(chainClient, birthdayStamp); err != nil {
			return fmt.Errorf("unable to perform wallet recovery: "+
				"%v", err)
		}
	}
	rollback := false
	rollbackStamp := w.Manager.SyncedTo()
	err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
		addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
		txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
		for height := rollbackStamp.Height; true; height-- {
			hash, err := w.Manager.BlockHash(addrmgrNs, height)
			if err != nil {
				return err
			}
			chainHash, err := chainClient.GetBlockHash(int64(height))
			if err != nil {
				return err
			}
			header, err := chainClient.GetBlockHeader(chainHash)
			if err != nil {
				return err
			}
			rollbackStamp.Hash = *chainHash
			rollbackStamp.Height = height
			rollbackStamp.Timestamp = header.Timestamp
			if bytes.Equal(hash[:], chainHash[:]) {
				break
			}
			rollback = true
		}
		if !rollback {
			return nil
		}
		err := w.Manager.SetSyncedTo(addrmgrNs, &rollbackStamp)
		if err != nil {
			return err
		}
		if rollbackStamp.Height <= birthdayStamp.Height &&
			rollbackStamp.Hash != birthdayStamp.Hash {
			err := w.Manager.SetBirthdayBlock(
				addrmgrNs, rollbackStamp, true,
			)
			if err != nil {
				return err
			}
		}
		return w.TxStore.Rollback(txmgrNs, rollbackStamp.Height+1)
	})
	if err != nil {
		return err
	}
	if err := chainClient.NotifyBlocks(); err != nil {
		return err
	}
	var (
		addrs   []bchutil.Address
		unspent []wtxmgr.Credit
	)
	err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
		addrs, unspent, err = w.activeData(dbtx)
		return err
	})
	if err != nil {
		return err
	}
	return w.rescanWithTarget(addrs, unspent, nil)
}
func (w *Wallet) isDevEnv() bool {
	switch uint32(w.ChainParams().Net) {
	case uint32(chaincfg.RegressionNetParams.Net):
	case uint32(chaincfg.SimNetParams.Net):
	default:
		return false
	}
	return true
}
func (w *Wallet) waitUntilBackendSynced(chainClient chain.Interface) error {
	t := time.NewTicker(time.Second)
	defer t.Stop()
	for {
		select {
		case <-t.C:
			if chainClient.IsCurrent() {
				return nil
			}
		case <-w.quitChan():
			return ErrWalletShuttingDown
		}
	}
}
func locateBirthdayBlock(chainClient chainConn,
	birthday time.Time) (*waddrmgr.BlockStamp, error) {
	startHeight := int32(0)
	_, bestHeight, err := chainClient.GetBestBlock()
	if err != nil {
		return nil, err
	}
	log.Debugf("Locating suitable block for birthday %v between blocks "+
		"%v-%v", birthday, startHeight, bestHeight)
	var (
		birthdayBlock *waddrmgr.BlockStamp
		left, right   = startHeight, bestHeight
	)
	for {
		mid := left + (right-left)/2
		hash, err := chainClient.GetBlockHash(int64(mid))
		if err != nil {
			return nil, err
		}
		header, err := chainClient.GetBlockHeader(hash)
		if err != nil {
			return nil, err
		}
		log.Debugf("Checking candidate block: height=%v, hash=%v, "+
			"timestamp=%v", mid, hash, header.Timestamp)
		if mid == startHeight || mid == bestHeight || mid == left {
			birthdayBlock = &waddrmgr.BlockStamp{
				Hash:      *hash,
				Height:    mid,
				Timestamp: header.Timestamp,
			}
			break
		}
		if header.Timestamp.Sub(birthday) > birthdayBlockDelta {
			right = mid
			continue
		}
		if header.Timestamp.Sub(birthday) < -birthdayBlockDelta {
			left = mid
			continue
		}
		birthdayBlock = &waddrmgr.BlockStamp{
			Hash:      *hash,
			Height:    mid,
			Timestamp: header.Timestamp,
		}
		break
	}
	log.Debugf("Found birthday block: height=%d, hash=%v, timestamp=%v",
		birthdayBlock.Height, birthdayBlock.Hash,
		birthdayBlock.Timestamp)
	return birthdayBlock, nil
}
func (w *Wallet) recovery(chainClient chain.Interface,
	birthdayBlock *waddrmgr.BlockStamp) error {
	log.Infof("RECOVERY MODE ENABLED -- rescanning for used addresses "+
		"with recovery_window=%d", w.recoveryWindow)
	recoveryMgr := NewRecoveryManager(
		w.recoveryWindow, recoveryBatchSize, w.chainParams,
	)
	scopedMgrs, err := w.defaultScopeManagers()
	if err != nil {
		return err
	}
	err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
		txMgrNS := tx.ReadBucket(wtxmgrNamespaceKey)
		credits, err := w.TxStore.UnspentOutputs(txMgrNS)
		if err != nil {
			return err
		}
		addrMgrNS := tx.ReadBucket(waddrmgrNamespaceKey)
		return recoveryMgr.Resurrect(addrMgrNS, scopedMgrs, credits)
	})
	if err != nil {
		return err
	}
	_, bestHeight, err := chainClient.GetBestBlock()
	if err != nil {
		return err
	}
	var blocks []*waddrmgr.BlockStamp
	startHeight := w.Manager.SyncedTo().Height + 1
	for height := startHeight; height <= bestHeight; height++ {
		hash, err := chainClient.GetBlockHash(int64(height))
		if err != nil {
			return err
		}
		header, err := chainClient.GetBlockHeader(hash)
		if err != nil {
			return err
		}
		blocks = append(blocks, &waddrmgr.BlockStamp{
			Hash:      *hash,
			Height:    height,
			Timestamp: header.Timestamp,
		})
		if height >= birthdayBlock.Height {
			recoveryMgr.AddToBlockBatch(
				hash, height, header.Timestamp,
			)
		}
		recoveryBatch := recoveryMgr.BlockBatch()
		var syncedToBlock *waddrmgr.BlockStamp
		if len(recoveryBatch) == recoveryBatchSize || height == bestHeight {
			err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
				ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
				syncedTo, err := w.recoverDefaultScopes(
					chainClient, tx, ns, recoveryBatch,
					recoveryMgr.State(),
				)
				if err != nil {
					return err
				}
				for _, block := range blocks {
					if block.Height <= syncedTo {
						err := w.Manager.SetSyncedTo(ns, block)
						if err != nil {
							return err
						}
					}
					if block.Height == syncedTo {
						syncedToBlock = block
					}
				}
				return nil
			})
			if err != nil {
				return err
			}
			if len(recoveryBatch) > 0 {
				log.Infof("Recovered addresses from blocks "+
					"%d-%d", recoveryBatch[0].Height,
					syncedToBlock.Height)
				w.rescanNotifications <- &chain.RecoveryProgress{
					Hash:   &syncedToBlock.Hash,
					Height: syncedToBlock.Height,
					Time:   syncedToBlock.Timestamp,
				}
			}
			if blocks[len(blocks)-1].Height != syncedToBlock.Height {
				height = syncedToBlock.Height
			}
			blocks = blocks[:0]
			recoveryMgr.ResetBlockBatch()
		}
	}
	return nil
}