decred.org/dcrdex@v1.0.5/client/asset/bch/spv.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package bch
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"decred.org/dcrdex/client/asset"
    17  	"decred.org/dcrdex/client/asset/btc"
    18  	"decred.org/dcrdex/dex"
    19  	dexbch "decred.org/dcrdex/dex/networks/bch"
    20  	"github.com/btcsuite/btcd/btcec/v2"
    21  	"github.com/btcsuite/btcd/btcjson"
    22  	"github.com/btcsuite/btcd/btcutil"
    23  	"github.com/btcsuite/btcd/btcutil/gcs"
    24  	"github.com/btcsuite/btcd/btcutil/psbt"
    25  	"github.com/btcsuite/btcd/chaincfg"
    26  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    27  	"github.com/btcsuite/btcd/wire"
    28  	"github.com/btcsuite/btcwallet/waddrmgr"
    29  	btcwallet "github.com/btcsuite/btcwallet/wallet"
    30  	"github.com/btcsuite/btcwallet/wtxmgr"
    31  	"github.com/dcrlabs/bchwallet/chain"
    32  	bchwaddrmgr "github.com/dcrlabs/bchwallet/waddrmgr"
    33  	"github.com/dcrlabs/bchwallet/wallet"
    34  	"github.com/dcrlabs/bchwallet/wallet/txauthor"
    35  	"github.com/dcrlabs/bchwallet/walletdb"
    36  	_ "github.com/dcrlabs/bchwallet/walletdb/bdb"
    37  	bchwtxmgr "github.com/dcrlabs/bchwallet/wtxmgr"
    38  	neutrino "github.com/dcrlabs/neutrino-bch"
    39  	labschain "github.com/dcrlabs/neutrino-bch/chain"
    40  	"github.com/decred/slog"
    41  	"github.com/gcash/bchd/bchec"
    42  	bchchaincfg "github.com/gcash/bchd/chaincfg"
    43  	bchchainhash "github.com/gcash/bchd/chaincfg/chainhash"
    44  	bchtxscript "github.com/gcash/bchd/txscript"
    45  	bchwire "github.com/gcash/bchd/wire"
    46  	"github.com/gcash/bchlog"
    47  	"github.com/gcash/bchutil"
    48  	"github.com/jrick/logrotate/rotator"
    49  	btcneutrino "github.com/lightninglabs/neutrino"
    50  	"github.com/lightninglabs/neutrino/headerfs"
    51  )
    52  
    53  const (
    54  	DefaultM       uint64 = 784931 // From bchutil. Used for gcs filters.
    55  	logDirName            = "logs"
    56  	neutrinoDBName        = "neutrino.db"
    57  	defaultAcctNum        = 0
    58  )
    59  
    60  var (
    61  	waddrmgrNamespace = []byte("waddrmgr")
    62  	wtxmgrNamespace   = []byte("wtxmgr")
    63  )
    64  
    65  // bchSPVWallet is an implementation of btc.BTCWallet that runs a native Bitcoin
    66  // Cash SPV Wallet. bchSPVWallet mostly just translates types from the btcsuite
    67  // types to gcash and vice-versa. Startup and shutdown are notable exceptions,
    68  // and have some critical code that needed to be duplicated (in order to avoid
    69  // interface hell).
    70  type bchSPVWallet struct {
    71  	// This section is populated in openSPVWallet.
    72  	dir         string
    73  	chainParams *bchchaincfg.Params
    74  	btcParams   *chaincfg.Params
    75  	log         dex.Logger
    76  
    77  	// This section is populated in Start.
    78  	*wallet.Wallet
    79  	chainClient *labschain.NeutrinoClient
    80  	cl          *neutrino.ChainService
    81  	loader      *wallet.Loader
    82  	neutrinoDB  walletdb.DB
    83  
    84  	peerManager *btc.SPVPeerManager
    85  }
    86  
    87  var _ btc.BTCWallet = (*bchSPVWallet)(nil)
    88  
    89  // openSPVWallet creates a bchSPVWallet, but does not Start.
    90  // Satisfies btc.BTCWalletConstructor.
    91  func openSPVWallet(dir string, cfg *btc.WalletConfig, btcParams *chaincfg.Params, log dex.Logger) btc.BTCWallet {
    92  	var bchParams *bchchaincfg.Params
    93  	switch btcParams.Name {
    94  	case dexbch.MainNetParams.Name:
    95  		bchParams = &bchchaincfg.MainNetParams
    96  	case dexbch.TestNet4Params.Name:
    97  		bchParams = &bchchaincfg.TestNet4Params
    98  	case dexbch.RegressionNetParams.Name:
    99  		bchParams = &bchchaincfg.RegressionNetParams
   100  	}
   101  
   102  	w := &bchSPVWallet{
   103  		dir:         dir,
   104  		chainParams: bchParams,
   105  		btcParams:   btcParams,
   106  		log:         log,
   107  	}
   108  	return w
   109  }
   110  
   111  // createSPVWallet creates a new SPV wallet.
   112  func createSPVWallet(privPass []byte, seed []byte, bday time.Time, walletDir string, log dex.Logger, extIdx, intIdx uint32, net *bchchaincfg.Params) error {
   113  	if err := logNeutrino(walletDir, log); err != nil {
   114  		return fmt.Errorf("error initializing bchwallet+neutrino logging: %w", err)
   115  	}
   116  
   117  	loader := wallet.NewLoader(net, walletDir, true, 250)
   118  
   119  	pubPass := []byte(wallet.InsecurePubPassphrase)
   120  
   121  	btcw, err := loader.CreateNewWallet(pubPass, privPass, seed, bday)
   122  	if err != nil {
   123  		return fmt.Errorf("CreateNewWallet error: %w", err)
   124  	}
   125  
   126  	errCloser := dex.NewErrorCloser()
   127  	defer errCloser.Done(log)
   128  	errCloser.Add(loader.UnloadWallet)
   129  
   130  	if extIdx > 0 || intIdx > 0 {
   131  		err = extendAddresses(extIdx, intIdx, btcw)
   132  		if err != nil {
   133  			return fmt.Errorf("failed to set starting address indexes: %w", err)
   134  		}
   135  	}
   136  
   137  	// The chain service DB
   138  	neutrinoDBPath := filepath.Join(walletDir, neutrinoDBName)
   139  	db, err := walletdb.Create("bdb", neutrinoDBPath, true)
   140  	if err != nil {
   141  		return fmt.Errorf("unable to create neutrino db at %q: %w", neutrinoDBPath, err)
   142  	}
   143  	if err = db.Close(); err != nil {
   144  		return fmt.Errorf("error closing newly created wallet database: %w", err)
   145  	}
   146  
   147  	if err := loader.UnloadWallet(); err != nil {
   148  		return fmt.Errorf("error unloading wallet: %w", err)
   149  	}
   150  
   151  	errCloser.Success()
   152  	return nil
   153  }
   154  
   155  // Start initializes the *bchwallet.Wallet and its supporting players and starts
   156  // syncing.
   157  func (w *bchSPVWallet) Start() (btc.SPVService, error) {
   158  	if err := logNeutrino(w.dir, w.log); err != nil {
   159  		return nil, fmt.Errorf("error initializing bchwallet+neutrino logging: %v", err)
   160  	}
   161  	// recoverWindow arguments borrowed from bchwallet directly.
   162  	w.loader = wallet.NewLoader(w.chainParams, w.dir, true, 250)
   163  
   164  	exists, err := w.loader.WalletExists()
   165  	if err != nil {
   166  		return nil, fmt.Errorf("error verifying wallet existence: %v", err)
   167  	}
   168  	if !exists {
   169  		return nil, errors.New("wallet not found")
   170  	}
   171  
   172  	w.log.Debug("Starting native BCH wallet...")
   173  	w.Wallet, err = w.loader.OpenExistingWallet([]byte(wallet.InsecurePubPassphrase), false)
   174  	if err != nil {
   175  		return nil, fmt.Errorf("couldn't load wallet: %w", err)
   176  	}
   177  
   178  	errCloser := dex.NewErrorCloser()
   179  	defer errCloser.Done(w.log)
   180  	errCloser.Add(w.loader.UnloadWallet)
   181  
   182  	neutrinoDBPath := filepath.Join(w.dir, neutrinoDBName)
   183  	w.neutrinoDB, err = walletdb.Create("bdb", neutrinoDBPath, true)
   184  	if err != nil {
   185  		return nil, fmt.Errorf("unable to create wallet db at %q: %v", neutrinoDBPath, err)
   186  	}
   187  	errCloser.Add(w.neutrinoDB.Close)
   188  
   189  	w.log.Debug("Starting neutrino chain service...")
   190  	w.cl, err = neutrino.NewChainService(neutrino.Config{
   191  		DataDir:     w.dir,
   192  		Database:    w.neutrinoDB,
   193  		ChainParams: *w.chainParams,
   194  		// https://github.com/gcash/neutrino/pull/36
   195  		PersistToDisk: true, // keep cfilter headers on disk for efficient rescanning
   196  		// WARNING: PublishTransaction currently uses the entire duration
   197  		// because if an external bug, but even if the bug is resolved, a
   198  		// typical inv/getdata round trip is ~4 seconds, so we set this so
   199  		// neutrino does not cancel queries too readily.
   200  		BroadcastTimeout: 6 * time.Second,
   201  	})
   202  	if err != nil {
   203  		return nil, fmt.Errorf("couldn't create Neutrino ChainService: %v", err)
   204  	}
   205  	errCloser.Add(w.cl.Stop)
   206  
   207  	w.chainClient = labschain.NewNeutrinoClient(w.chainParams, w.cl, &logAdapter{w.log})
   208  
   209  	var defaultPeers []string
   210  	switch w.chainParams.Net {
   211  	// case bchwire.MainNet:
   212  	// 	addPeers = []string{"cfilters.ssgen.io"}
   213  	case bchwire.TestNet4:
   214  		// Add the address for a local bchd testnet4 node.
   215  		defaultPeers = []string{}
   216  	case bchwire.TestNet, bchwire.SimNet: // plain "wire.TestNet" is regnet!
   217  		defaultPeers = []string{"127.0.0.1:21577"}
   218  	}
   219  	peerManager := btc.NewSPVPeerManager(&spvService{w.cl}, defaultPeers, w.dir, w.log, w.chainParams.DefaultPort)
   220  	w.peerManager = peerManager
   221  
   222  	if err = w.chainClient.Start(); err != nil { // lazily starts connmgr
   223  		return nil, fmt.Errorf("couldn't start Neutrino client: %v", err)
   224  	}
   225  
   226  	w.log.Info("Synchronizing wallet with network...")
   227  	w.SynchronizeRPC(w.chainClient)
   228  
   229  	errCloser.Success()
   230  
   231  	w.peerManager.ConnectToInitialWalletPeers()
   232  
   233  	return &spvService{w.cl}, nil
   234  }
   235  
   236  func (w *bchSPVWallet) txDetails(txHash *bchchainhash.Hash) (*bchwtxmgr.TxDetails, error) {
   237  	details, err := wallet.UnstableAPI(w.Wallet).TxDetails(txHash)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	if details == nil {
   242  		return nil, btc.WalletTransactionNotFound
   243  	}
   244  
   245  	return details, nil
   246  }
   247  
   248  var _ btc.BTCWallet = (*bchSPVWallet)(nil)
   249  
   250  func (w *bchSPVWallet) PublishTransaction(btcTx *wire.MsgTx, label string) error {
   251  	bchTx, err := convertMsgTxToBCH(btcTx)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	return w.Wallet.PublishTransaction(bchTx)
   256  }
   257  
   258  func (w *bchSPVWallet) CalculateAccountBalances(account uint32, confirms int32) (btcwallet.Balances, error) {
   259  	bals, err := w.Wallet.CalculateAccountBalances(account, confirms)
   260  	if err != nil {
   261  		return btcwallet.Balances{}, err
   262  	}
   263  	return btcwallet.Balances{
   264  		Total:          btcutil.Amount(bals.Total),
   265  		Spendable:      btcutil.Amount(bals.Spendable),
   266  		ImmatureReward: btcutil.Amount(bals.ImmatureReward),
   267  	}, nil
   268  }
   269  
   270  func (w *bchSPVWallet) ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error) {
   271  	res, err := w.Wallet.ListSinceBlock(start, end, syncHeight)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	btcRes := make([]btcjson.ListTransactionsResult, len(res))
   277  	for i, r := range res {
   278  		btcRes[i] = btcjson.ListTransactionsResult{
   279  			Abandoned:         r.Abandoned,
   280  			Account:           r.Account,
   281  			Address:           r.Address,
   282  			Amount:            r.Amount,
   283  			BIP125Replaceable: r.BIP125Replaceable,
   284  			BlockHash:         r.BlockHash,
   285  			BlockIndex:        r.BlockIndex,
   286  			BlockTime:         r.BlockTime,
   287  			Category:          r.Category,
   288  			Confirmations:     r.Confirmations,
   289  			Fee:               r.Fee,
   290  			Generated:         r.Generated,
   291  			InvolvesWatchOnly: r.InvolvesWatchOnly,
   292  			Time:              r.Time,
   293  			TimeReceived:      r.TimeReceived,
   294  			Trusted:           r.Trusted,
   295  			TxID:              r.TxID,
   296  			Vout:              r.Vout,
   297  			WalletConflicts:   r.WalletConflicts,
   298  			Comment:           r.Comment,
   299  			OtherAccount:      r.OtherAccount,
   300  		}
   301  	}
   302  
   303  	return btcRes, nil
   304  }
   305  
   306  func (w *bchSPVWallet) GetTransactions(startBlock, endBlock int32, _ string, cancel <-chan struct{}) (*btcwallet.GetTransactionsResult, error) {
   307  	startID := wallet.NewBlockIdentifierFromHeight(startBlock)
   308  	endID := wallet.NewBlockIdentifierFromHeight(endBlock)
   309  	bchGTR, err := w.Wallet.GetTransactions(startID, endID, cancel)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	convertTxs := func(txs []wallet.TransactionSummary) []btcwallet.TransactionSummary {
   315  		transactions := make([]btcwallet.TransactionSummary, len(txs))
   316  		for i, tx := range txs {
   317  			txHash := chainhash.Hash(*tx.Hash)
   318  			inputs := make([]btcwallet.TransactionSummaryInput, len(tx.MyInputs))
   319  			for k, in := range tx.MyInputs {
   320  				inputs[k] = btcwallet.TransactionSummaryInput{
   321  					Index:           in.Index,
   322  					PreviousAccount: in.PreviousAccount,
   323  					PreviousAmount:  btcutil.Amount(in.PreviousAmount),
   324  				}
   325  			}
   326  			outputs := make([]btcwallet.TransactionSummaryOutput, len(tx.MyOutputs))
   327  			for k, out := range tx.MyOutputs {
   328  				outputs[k] = btcwallet.TransactionSummaryOutput{
   329  					Index:    out.Index,
   330  					Account:  out.Account,
   331  					Internal: out.Internal,
   332  				}
   333  			}
   334  			transactions[i] = btcwallet.TransactionSummary{
   335  				Hash:        &txHash,
   336  				Transaction: tx.Transaction,
   337  				MyInputs:    inputs,
   338  				MyOutputs:   outputs,
   339  				Fee:         btcutil.Amount(tx.Fee),
   340  				Timestamp:   tx.Timestamp,
   341  			}
   342  		}
   343  		return transactions
   344  	}
   345  
   346  	btcGTR := &btcwallet.GetTransactionsResult{
   347  		MinedTransactions:   make([]btcwallet.Block, len(bchGTR.MinedTransactions)),
   348  		UnminedTransactions: convertTxs(bchGTR.UnminedTransactions),
   349  	}
   350  
   351  	for i, block := range bchGTR.MinedTransactions {
   352  		blockHash := chainhash.Hash(*block.Hash)
   353  		btcGTR.MinedTransactions[i] = btcwallet.Block{
   354  			Hash:         &blockHash,
   355  			Height:       block.Height,
   356  			Timestamp:    block.Timestamp,
   357  			Transactions: convertTxs(block.Transactions),
   358  		}
   359  	}
   360  
   361  	return btcGTR, nil
   362  }
   363  
   364  func (w *bchSPVWallet) ListUnspent(minconf, maxconf int32, acctName string) ([]*btcjson.ListUnspentResult, error) {
   365  	// bchwallet's ListUnspent takes either a list of addresses, or else returns
   366  	// all non-locked unspent outputs for all accounts. We need to iterate the
   367  	// results anyway to convert type.
   368  	uns, err := w.Wallet.ListUnspent(minconf, maxconf, nil)
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	outs := make([]*btcjson.ListUnspentResult, len(uns))
   374  	for i, u := range uns {
   375  		if u.Account != acctName {
   376  			continue
   377  		}
   378  		outs[i] = &btcjson.ListUnspentResult{
   379  			TxID:          u.TxID,
   380  			Vout:          u.Vout,
   381  			Address:       u.Address,
   382  			Account:       u.Account,
   383  			ScriptPubKey:  u.ScriptPubKey,
   384  			RedeemScript:  u.RedeemScript,
   385  			Amount:        u.Amount,
   386  			Confirmations: u.Confirmations,
   387  			Spendable:     u.Spendable,
   388  		}
   389  	}
   390  
   391  	return outs, nil
   392  }
   393  
   394  // FetchInputInfo is not actually implemented in bchwallet. This is based on the
   395  // btcwallet implementation. As this is used by btc.spvWallet, we really only
   396  // need the TxOut, and to show ownership.
   397  func (w *bchSPVWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx, *wire.TxOut, *psbt.Bip32Derivation, int64, error) {
   398  
   399  	td, err := w.txDetails((*bchchainhash.Hash)(&prevOut.Hash))
   400  	if err != nil {
   401  		return nil, nil, nil, 0, err
   402  	}
   403  
   404  	if prevOut.Index >= uint32(len(td.TxRecord.MsgTx.TxOut)) {
   405  		return nil, nil, nil, 0, fmt.Errorf("not enough outputs")
   406  	}
   407  
   408  	bchTxOut := td.TxRecord.MsgTx.TxOut[prevOut.Index]
   409  
   410  	// Verify we own at least one parsed address.
   411  	_, addrs, _, err := bchtxscript.ExtractPkScriptAddrs(bchTxOut.PkScript, w.chainParams)
   412  	if err != nil {
   413  		return nil, nil, nil, 0, err
   414  	}
   415  	notOurs := true
   416  	for i := 0; notOurs && i < len(addrs); i++ {
   417  		_, err := w.Wallet.AddressInfo(addrs[i])
   418  		notOurs = err != nil
   419  	}
   420  	if notOurs {
   421  		return nil, nil, nil, 0, btcwallet.ErrNotMine
   422  	}
   423  
   424  	btcTxOut := &wire.TxOut{
   425  		Value:    bchTxOut.Value,
   426  		PkScript: bchTxOut.PkScript,
   427  	}
   428  
   429  	return nil, btcTxOut, nil, 0, nil
   430  }
   431  
   432  func (w *bchSPVWallet) LockOutpoint(op wire.OutPoint) {
   433  	w.Wallet.LockOutpoint(bchwire.OutPoint{
   434  		Hash:  bchchainhash.Hash(op.Hash),
   435  		Index: op.Index,
   436  	})
   437  }
   438  
   439  func (w *bchSPVWallet) UnlockOutpoint(op wire.OutPoint) {
   440  	w.Wallet.UnlockOutpoint(bchwire.OutPoint{
   441  		Hash:  bchchainhash.Hash(op.Hash),
   442  		Index: op.Index,
   443  	})
   444  }
   445  
   446  func (w *bchSPVWallet) LockedOutpoints() []btcjson.TransactionInput {
   447  	locks := w.Wallet.LockedOutpoints()
   448  	locked := make([]btcjson.TransactionInput, len(locks))
   449  	for i, lock := range locks {
   450  		locked[i] = btcjson.TransactionInput{
   451  			Txid: lock.Txid,
   452  			Vout: lock.Vout,
   453  		}
   454  	}
   455  	return locked
   456  }
   457  
   458  func (w *bchSPVWallet) NewChangeAddress(account uint32, _ waddrmgr.KeyScope) (btcutil.Address, error) {
   459  	bchAddr, err := w.Wallet.NewChangeAddress(account, bchwaddrmgr.KeyScopeBIP0044)
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  	return dexbch.DecodeCashAddress(bchAddr.String(), w.btcParams)
   464  }
   465  
   466  func (w *bchSPVWallet) NewAddress(account uint32, _ waddrmgr.KeyScope) (btcutil.Address, error) {
   467  	bchAddr, err := w.Wallet.NewAddress(account, bchwaddrmgr.KeyScopeBIP0044)
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  	return dexbch.DecodeCashAddress(bchAddr.String(), w.btcParams)
   472  }
   473  
   474  func (w *bchSPVWallet) PrivKeyForAddress(a btcutil.Address) (*btcec.PrivateKey, error) {
   475  	bchAddr, err := dexbch.BTCAddrToBCHAddr(a, w.btcParams)
   476  	if err != nil {
   477  		return nil, err
   478  	}
   479  	bchKey, err := w.Wallet.PrivKeyForAddress(bchAddr)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	priv, _ /* pub */ := btcec.PrivKeyFromBytes(bchKey.Serialize())
   485  	return priv, nil
   486  }
   487  
   488  func (w *bchSPVWallet) SendOutputs(outputs []*wire.TxOut, _ *waddrmgr.KeyScope, account uint32, minconf int32,
   489  	satPerKb btcutil.Amount, _ btcwallet.CoinSelectionStrategy, label string) (*wire.MsgTx, error) {
   490  
   491  	bchOuts := make([]*bchwire.TxOut, len(outputs))
   492  	for i, op := range outputs {
   493  		bchOuts[i] = &bchwire.TxOut{
   494  			Value:    op.Value,
   495  			PkScript: op.PkScript,
   496  		}
   497  	}
   498  
   499  	bchTx, err := w.Wallet.SendOutputs(bchOuts, account, minconf, bchutil.Amount(satPerKb))
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  
   504  	btcTx, err := convertMsgTxToBTC(bchTx)
   505  	if err != nil {
   506  		return nil, err
   507  	}
   508  
   509  	return btcTx, nil
   510  }
   511  
   512  func (w *bchSPVWallet) HaveAddress(a btcutil.Address) (bool, error) {
   513  	bchAddr, err := dexbch.BTCAddrToBCHAddr(a, w.btcParams)
   514  	if err != nil {
   515  		return false, err
   516  	}
   517  
   518  	return w.Wallet.HaveAddress(bchAddr)
   519  }
   520  
   521  func (w *bchSPVWallet) Stop() {
   522  	w.log.Info("Unloading wallet")
   523  	if err := w.loader.UnloadWallet(); err != nil {
   524  		w.log.Errorf("UnloadWallet error: %v", err)
   525  	}
   526  	if w.chainClient != nil {
   527  		w.log.Trace("Stopping neutrino client chain interface")
   528  		w.chainClient.Stop()
   529  		w.chainClient.WaitForShutdown()
   530  	}
   531  	w.log.Trace("Stopping neutrino chain sync service")
   532  	if err := w.cl.Stop(); err != nil {
   533  		w.log.Errorf("error stopping neutrino chain service: %v", err)
   534  	}
   535  	w.log.Trace("Stopping neutrino DB.")
   536  	if err := w.neutrinoDB.Close(); err != nil {
   537  		w.log.Errorf("wallet db close error: %v", err)
   538  	}
   539  
   540  	w.log.Info("SPV wallet closed")
   541  }
   542  
   543  func (w *bchSPVWallet) AccountProperties(_ waddrmgr.KeyScope, acct uint32) (*waddrmgr.AccountProperties, error) {
   544  	props, err := w.Wallet.AccountProperties(bchwaddrmgr.KeyScopeBIP0044, acct)
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  	return &waddrmgr.AccountProperties{
   549  		// bch only has a subset of the btc AccountProperties. We'll give
   550  		// everything we have. The only two that are actually used are these
   551  		// first to fields.
   552  		ExternalKeyCount: props.ExternalKeyCount,
   553  		InternalKeyCount: props.InternalKeyCount,
   554  		AccountNumber:    props.AccountNumber,
   555  		AccountName:      props.AccountName,
   556  		ImportedKeyCount: props.ImportedKeyCount,
   557  	}, nil
   558  }
   559  
   560  func (w *bchSPVWallet) RescanAsync() error {
   561  	w.log.Info("Stopping wallet and chain client...")
   562  	w.Wallet.Stop() // stops Wallet and chainClient (not chainService)
   563  	w.Wallet.WaitForShutdown()
   564  	w.chainClient.WaitForShutdown()
   565  
   566  	w.ForceRescan()
   567  
   568  	w.log.Info("Starting wallet...")
   569  	w.Wallet.Start()
   570  
   571  	if err := w.chainClient.Start(); err != nil {
   572  		return fmt.Errorf("couldn't start Neutrino client: %v", err)
   573  	}
   574  
   575  	w.log.Info("Synchronizing wallet with network...")
   576  	w.Wallet.SynchronizeRPC(w.chainClient)
   577  	return nil
   578  }
   579  
   580  // ForceRescan forces a full rescan with active address discovery on wallet
   581  // restart by dropping the complete transaction history and setting the
   582  // "synced to" field to nil. See the btcwallet/cmd/dropwtxmgr app for more
   583  // information.
   584  func (w *bchSPVWallet) ForceRescan() {
   585  	w.log.Info("Dropping transaction history to perform full rescan...")
   586  	err := w.dropTransactionHistory()
   587  	if err != nil {
   588  		w.log.Errorf("Failed to drop wallet transaction history: %v", err)
   589  		// Continue to attempt restarting the wallet anyway.
   590  	}
   591  
   592  	err = walletdb.Update(w.Database(), func(dbtx walletdb.ReadWriteTx) error {
   593  		ns := dbtx.ReadWriteBucket(waddrmgrNamespace) // it'll be fine
   594  		return w.Manager.SetSyncedTo(ns, nil)         // never synced, forcing recover from birthday
   595  	})
   596  	if err != nil {
   597  		w.log.Errorf("Failed to reset wallet manager sync height: %v", err)
   598  	}
   599  }
   600  
   601  // dropTransactionHistory drops the transaction history. It is based off of the
   602  // dropwtxmgr utility in the bchwallet repo.
   603  func (w *bchSPVWallet) dropTransactionHistory() error {
   604  	w.log.Info("Dropping wallet transaction history")
   605  
   606  	return walletdb.Update(w.Database(), func(tx walletdb.ReadWriteTx) error {
   607  		err := tx.DeleteTopLevelBucket(wtxmgrNamespace)
   608  		if err != nil && err != walletdb.ErrBucketNotFound {
   609  			return err
   610  		}
   611  		ns, err := tx.CreateTopLevelBucket(wtxmgrNamespace)
   612  		if err != nil {
   613  			return err
   614  		}
   615  		err = bchwtxmgr.Create(ns)
   616  		if err != nil {
   617  			return err
   618  		}
   619  
   620  		ns = tx.ReadWriteBucket(waddrmgrNamespace)
   621  		birthdayBlock, err := bchwaddrmgr.FetchBirthdayBlock(ns)
   622  		if err != nil {
   623  			fmt.Println("Wallet does not have a birthday block " +
   624  				"set, falling back to rescan from genesis")
   625  
   626  			startBlock, err := bchwaddrmgr.FetchStartBlock(ns)
   627  			if err != nil {
   628  				return err
   629  			}
   630  			return bchwaddrmgr.PutSyncedTo(ns, startBlock)
   631  		}
   632  
   633  		// We'll need to remove our birthday block first because it
   634  		// serves as a barrier when updating our state to detect reorgs
   635  		// due to the wallet not storing all block hashes of the chain.
   636  		if err := bchwaddrmgr.DeleteBirthdayBlock(ns); err != nil {
   637  			return err
   638  		}
   639  
   640  		if err := bchwaddrmgr.PutSyncedTo(ns, &birthdayBlock); err != nil {
   641  			return err
   642  		}
   643  		return bchwaddrmgr.PutBirthdayBlock(ns, birthdayBlock)
   644  	})
   645  }
   646  
   647  func (w *bchSPVWallet) WalletTransaction(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) {
   648  	txDetails, err := w.txDetails((*bchchainhash.Hash)(txHash))
   649  	if err != nil {
   650  		return nil, err
   651  	}
   652  
   653  	btcTx, err := convertMsgTxToBTC(&txDetails.MsgTx)
   654  	if err != nil {
   655  		return nil, err
   656  	}
   657  
   658  	credits := make([]wtxmgr.CreditRecord, len(txDetails.Credits))
   659  	for i, c := range txDetails.Credits {
   660  		credits[i] = wtxmgr.CreditRecord{
   661  			Amount: btcutil.Amount(c.Amount),
   662  			Index:  c.Index,
   663  			Spent:  c.Spent,
   664  			Change: c.Change,
   665  		}
   666  	}
   667  
   668  	debits := make([]wtxmgr.DebitRecord, len(txDetails.Debits))
   669  	for i, d := range txDetails.Debits {
   670  		debits[i] = wtxmgr.DebitRecord{
   671  			Amount: btcutil.Amount(d.Amount),
   672  			Index:  d.Index,
   673  		}
   674  	}
   675  
   676  	return &wtxmgr.TxDetails{
   677  		TxRecord: wtxmgr.TxRecord{
   678  			MsgTx:        *btcTx,
   679  			Hash:         chainhash.Hash(txDetails.TxRecord.Hash),
   680  			Received:     txDetails.TxRecord.Received,
   681  			SerializedTx: txDetails.TxRecord.SerializedTx,
   682  		},
   683  		Block: wtxmgr.BlockMeta{
   684  			Block: wtxmgr.Block{
   685  				Hash:   chainhash.Hash(txDetails.Block.Hash),
   686  				Height: txDetails.Block.Height,
   687  			},
   688  			Time: txDetails.Block.Time,
   689  		},
   690  		Credits: credits,
   691  		Debits:  debits,
   692  	}, nil
   693  }
   694  
   695  func (w *bchSPVWallet) SyncedTo() waddrmgr.BlockStamp {
   696  	bs := w.Manager.SyncedTo()
   697  	return waddrmgr.BlockStamp{
   698  		Height:    bs.Height,
   699  		Hash:      chainhash.Hash(bs.Hash),
   700  		Timestamp: bs.Timestamp,
   701  	}
   702  
   703  }
   704  
   705  func (w *bchSPVWallet) SignTx(btcTx *wire.MsgTx) error {
   706  	bchTx, err := convertMsgTxToBCH(btcTx)
   707  	if err != nil {
   708  		return err
   709  	}
   710  
   711  	var prevPkScripts [][]byte
   712  	var inputValues []bchutil.Amount
   713  	for _, txIn := range btcTx.TxIn {
   714  		_, txOut, _, _, err := w.FetchInputInfo(&txIn.PreviousOutPoint)
   715  		if err != nil {
   716  			return err
   717  		}
   718  		inputValues = append(inputValues, bchutil.Amount(txOut.Value))
   719  		prevPkScripts = append(prevPkScripts, txOut.PkScript)
   720  		// Zero the previous witness and signature script or else
   721  		// AddAllInputScripts does some weird stuff.
   722  		txIn.SignatureScript = nil
   723  		txIn.Witness = nil
   724  	}
   725  
   726  	err = txauthor.AddAllInputScripts(bchTx, prevPkScripts, inputValues, &secretSource{w.Wallet, w.chainParams})
   727  	if err != nil {
   728  		return err
   729  	}
   730  
   731  	if len(bchTx.TxIn) != len(btcTx.TxIn) {
   732  		return fmt.Errorf("txin count mismatch")
   733  	}
   734  	for i, txIn := range btcTx.TxIn {
   735  		txIn.SignatureScript = bchTx.TxIn[i].SignatureScript
   736  	}
   737  	return nil
   738  }
   739  
   740  // BlockNotifications returns a channel on which to receive notifications of
   741  // newly processed blocks. The caller should only call BlockNotificaitons once.
   742  func (w *bchSPVWallet) BlockNotifications(ctx context.Context) <-chan *btc.BlockNotification {
   743  	cl := w.NtfnServer.TransactionNotifications()
   744  	ch := make(chan *btc.BlockNotification, 1)
   745  	go func() {
   746  		defer cl.Done()
   747  		for {
   748  			select {
   749  			case note := <-cl.C:
   750  				if len(note.AttachedBlocks) > 0 {
   751  					lastBlock := note.AttachedBlocks[len(note.AttachedBlocks)-1]
   752  					select {
   753  					case ch <- &btc.BlockNotification{
   754  						Hash:   chainhash.Hash(*lastBlock.Hash),
   755  						Height: lastBlock.Height,
   756  					}:
   757  					default:
   758  					}
   759  				}
   760  			case <-ctx.Done():
   761  				return
   762  			}
   763  		}
   764  	}()
   765  	return ch
   766  }
   767  
   768  func (w *bchSPVWallet) Birthday() time.Time {
   769  	return w.Manager.Birthday()
   770  }
   771  
   772  func (w *bchSPVWallet) updateDBBirthday(bday time.Time) error {
   773  	btcw, isLoaded := w.loader.LoadedWallet()
   774  	if !isLoaded {
   775  		return fmt.Errorf("wallet not loaded")
   776  	}
   777  	return walletdb.Update(btcw.Database(), func(dbtx walletdb.ReadWriteTx) error {
   778  		ns := dbtx.ReadWriteBucket(waddrmgrNamespace)
   779  		return btcw.Manager.SetBirthday(ns, bday)
   780  	})
   781  }
   782  
   783  func (w *bchSPVWallet) Peers() ([]*asset.WalletPeer, error) {
   784  	return w.peerManager.Peers()
   785  }
   786  
   787  func (w *bchSPVWallet) AddPeer(addr string) error {
   788  	return w.peerManager.AddPeer(addr)
   789  }
   790  
   791  func (w *bchSPVWallet) RemovePeer(addr string) error {
   792  	return w.peerManager.RemovePeer(addr)
   793  }
   794  
   795  func (w *bchSPVWallet) TotalReceivedForAddr(btcAddr btcutil.Address, minConf int32) (btcutil.Amount, error) {
   796  	bchAddr, err := dexbch.BTCAddrToBCHAddr(btcAddr, w.btcParams)
   797  	if err != nil {
   798  		return 0, err
   799  	}
   800  	amt, err := w.Wallet.TotalReceivedForAddr(bchAddr, 0)
   801  	if err != nil {
   802  		return 0, err
   803  	}
   804  	return btcutil.Amount(amt), nil
   805  }
   806  
   807  // secretSource is used to locate keys and redemption scripts while signing a
   808  // transaction. secretSource satisfies the txauthor.SecretsSource interface.
   809  type secretSource struct {
   810  	w           *wallet.Wallet
   811  	chainParams *bchchaincfg.Params
   812  }
   813  
   814  // ChainParams returns the chain parameters.
   815  func (s *secretSource) ChainParams() *bchchaincfg.Params {
   816  	return s.chainParams
   817  }
   818  
   819  // GetKey fetches a private key for the specified address.
   820  func (s *secretSource) GetKey(addr bchutil.Address) (*bchec.PrivateKey, bool, error) {
   821  	ma, err := s.w.AddressInfo(addr)
   822  	if err != nil {
   823  		return nil, false, err
   824  	}
   825  
   826  	mpka, ok := ma.(bchwaddrmgr.ManagedPubKeyAddress)
   827  	if !ok {
   828  		e := fmt.Errorf("managed address type for %v is `%T` but "+
   829  			"want waddrmgr.ManagedPubKeyAddress", addr, ma)
   830  		return nil, false, e
   831  	}
   832  
   833  	privKey, err := mpka.PrivKey()
   834  	if err != nil {
   835  		return nil, false, err
   836  	}
   837  
   838  	k, _ /* pub */ := bchec.PrivKeyFromBytes(bchec.S256(), privKey.Serialize())
   839  
   840  	return k, ma.Compressed(), nil
   841  }
   842  
   843  // GetScript fetches the redemption script for the specified p2sh/p2wsh address.
   844  func (s *secretSource) GetScript(addr bchutil.Address) ([]byte, error) {
   845  	ma, err := s.w.AddressInfo(addr)
   846  	if err != nil {
   847  		return nil, err
   848  	}
   849  
   850  	msa, ok := ma.(bchwaddrmgr.ManagedScriptAddress)
   851  	if !ok {
   852  		e := fmt.Errorf("managed address type for %v is `%T` but "+
   853  			"want waddrmgr.ManagedScriptAddress", addr, ma)
   854  		return nil, e
   855  	}
   856  	return msa.Script()
   857  }
   858  
   859  // spvService embeds gcash neutrino.ChainService and translates types.
   860  type spvService struct {
   861  	*neutrino.ChainService
   862  }
   863  
   864  var _ btc.SPVService = (*spvService)(nil)
   865  
   866  func (s *spvService) GetBlockHash(height int64) (*chainhash.Hash, error) {
   867  	bchHash, err := s.ChainService.GetBlockHash(height)
   868  	if err != nil {
   869  		return nil, err
   870  	}
   871  	return (*chainhash.Hash)(bchHash), nil
   872  }
   873  
   874  func (s *spvService) BestBlock() (*headerfs.BlockStamp, error) {
   875  	bs, err := s.ChainService.BestBlock()
   876  	if err != nil {
   877  		return nil, err
   878  	}
   879  	return &headerfs.BlockStamp{
   880  		Height:    bs.Height,
   881  		Hash:      chainhash.Hash(bs.Hash),
   882  		Timestamp: bs.Timestamp,
   883  	}, nil
   884  }
   885  
   886  func (s *spvService) Peers() []btc.SPVPeer {
   887  	rawPeers := s.ChainService.Peers()
   888  	peers := make([]btc.SPVPeer, len(rawPeers))
   889  	for i, p := range rawPeers {
   890  		peers[i] = p
   891  	}
   892  	return peers
   893  }
   894  
   895  func (s *spvService) AddPeer(addr string) error {
   896  	return s.ChainService.ConnectNode(addr, true)
   897  }
   898  
   899  func (s *spvService) RemovePeer(addr string) error {
   900  	return s.ChainService.RemoveNodeByAddr(addr)
   901  }
   902  
   903  func (s *spvService) GetBlockHeight(h *chainhash.Hash) (int32, error) {
   904  	return s.ChainService.GetBlockHeight((*bchchainhash.Hash)(h))
   905  }
   906  
   907  func (s *spvService) GetBlockHeader(h *chainhash.Hash) (*wire.BlockHeader, error) {
   908  	hdr, err := s.ChainService.GetBlockHeader((*bchchainhash.Hash)(h))
   909  	if err != nil {
   910  		return nil, err
   911  	}
   912  	return &wire.BlockHeader{
   913  		Version:    hdr.Version,
   914  		PrevBlock:  chainhash.Hash(hdr.PrevBlock),
   915  		MerkleRoot: chainhash.Hash(hdr.MerkleRoot),
   916  		Timestamp:  hdr.Timestamp,
   917  		Bits:       hdr.Bits,
   918  		Nonce:      hdr.Nonce,
   919  	}, nil
   920  }
   921  
   922  func (s *spvService) GetCFilter(blockHash chainhash.Hash, filterType wire.FilterType, _ ...btcneutrino.QueryOption) (*gcs.Filter, error) {
   923  	f, err := s.ChainService.GetCFilter(bchchainhash.Hash(blockHash), bchwire.GCSFilterRegular)
   924  	if err != nil {
   925  		return nil, err
   926  	}
   927  
   928  	b, err := f.Bytes()
   929  	if err != nil {
   930  		return nil, err
   931  	}
   932  
   933  	return gcs.FromBytes(f.N(), f.P(), DefaultM, b)
   934  }
   935  
   936  func (s *spvService) GetBlock(blockHash chainhash.Hash, _ ...btcneutrino.QueryOption) (*btcutil.Block, error) {
   937  	blk, err := s.ChainService.GetBlock(bchchainhash.Hash(blockHash))
   938  	if err != nil {
   939  		return nil, err
   940  	}
   941  
   942  	b, err := blk.Bytes()
   943  	if err != nil {
   944  		return nil, err
   945  	}
   946  
   947  	return btcutil.NewBlockFromBytes(b)
   948  }
   949  
   950  func convertMsgTxToBTC(tx *bchwire.MsgTx) (*wire.MsgTx, error) {
   951  	buf := new(bytes.Buffer)
   952  	if err := tx.Serialize(buf); err != nil {
   953  		return nil, err
   954  	}
   955  
   956  	btcTx := new(wire.MsgTx)
   957  	if err := btcTx.Deserialize(buf); err != nil {
   958  		return nil, err
   959  	}
   960  	return btcTx, nil
   961  }
   962  
   963  func convertMsgTxToBCH(tx *wire.MsgTx) (*bchwire.MsgTx, error) {
   964  	buf := new(bytes.Buffer)
   965  	if err := tx.Serialize(buf); err != nil {
   966  		return nil, err
   967  	}
   968  	bchTx := new(bchwire.MsgTx)
   969  	if err := bchTx.Deserialize(buf); err != nil {
   970  		return nil, err
   971  	}
   972  	return bchTx, nil
   973  }
   974  
   975  func extendAddresses(extIdx, intIdx uint32, bchw *wallet.Wallet) error {
   976  	scopedKeyManager, err := bchw.Manager.FetchScopedKeyManager(bchwaddrmgr.KeyScopeBIP0044)
   977  	if err != nil {
   978  		return err
   979  	}
   980  
   981  	return walletdb.Update(bchw.Database(), func(dbtx walletdb.ReadWriteTx) error {
   982  		ns := dbtx.ReadWriteBucket(waddrmgrNamespace)
   983  		if extIdx > 0 {
   984  			scopedKeyManager.ExtendExternalAddresses(ns, defaultAcctNum, extIdx)
   985  		}
   986  		if intIdx > 0 {
   987  			scopedKeyManager.ExtendInternalAddresses(ns, defaultAcctNum, intIdx)
   988  		}
   989  		return nil
   990  	})
   991  }
   992  
   993  var (
   994  	loggingInited uint32
   995  	logFileName   = "neutrino.log"
   996  )
   997  
   998  // logRotator initializes a rotating file logger.
   999  func logRotator(walletDir string) (*rotator.Rotator, error) {
  1000  	const maxLogRolls = 8
  1001  	logDir := filepath.Join(walletDir, logDirName)
  1002  	if err := os.MkdirAll(logDir, 0744); err != nil {
  1003  		return nil, fmt.Errorf("error creating log directory: %w", err)
  1004  	}
  1005  
  1006  	logFilename := filepath.Join(logDir, logFileName)
  1007  	return rotator.New(logFilename, 32*1024, false, maxLogRolls)
  1008  }
  1009  
  1010  // logNeutrino initializes logging in the neutrino + wallet packages. Logging
  1011  // only has to be initialized once, so an atomic flag is used internally to
  1012  // return early on subsequent invocations.
  1013  //
  1014  // In theory, the rotating file logger must be Closed at some point, but
  1015  // there are concurrency issues with that since btcd and btcwallet have
  1016  // unsupervised goroutines still running after shutdown. So we leave the rotator
  1017  // running at the risk of losing some logs.
  1018  func logNeutrino(walletDir string, errorLogger dex.Logger) error {
  1019  	if !atomic.CompareAndSwapUint32(&loggingInited, 0, 1) {
  1020  		return nil
  1021  	}
  1022  
  1023  	logSpinner, err := logRotator(walletDir)
  1024  	if err != nil {
  1025  		return fmt.Errorf("error initializing log rotator: %w", err)
  1026  	}
  1027  
  1028  	backendLog := bchlog.NewBackend(logSpinner)
  1029  
  1030  	logger := func(name string, lvl bchlog.Level) bchlog.Logger {
  1031  		l := backendLog.Logger(name)
  1032  		l.SetLevel(lvl)
  1033  		return &fileLoggerPlus{Logger: l, log: errorLogger.SubLogger(name)}
  1034  	}
  1035  
  1036  	neutrino.UseLogger(logger("NTRNO", bchlog.LevelDebug))
  1037  	wallet.UseLogger(logger("BTCW", bchlog.LevelInfo))
  1038  	bchwtxmgr.UseLogger(logger("TXMGR", bchlog.LevelInfo))
  1039  	chain.UseLogger(logger("CHAIN", bchlog.LevelInfo))
  1040  
  1041  	return nil
  1042  }
  1043  
  1044  // fileLoggerPlus logs everything to a file, and everything with level >= warn
  1045  // to both file and a specified dex.Logger.
  1046  type fileLoggerPlus struct {
  1047  	bchlog.Logger
  1048  	log dex.Logger
  1049  }
  1050  
  1051  func (f *fileLoggerPlus) Warnf(format string, params ...any) {
  1052  	f.log.Warnf(format, params...)
  1053  	f.Logger.Warnf(format, params...)
  1054  }
  1055  
  1056  func (f *fileLoggerPlus) Errorf(format string, params ...any) {
  1057  	f.log.Errorf(format, params...)
  1058  	f.Logger.Errorf(format, params...)
  1059  }
  1060  
  1061  func (f *fileLoggerPlus) Criticalf(format string, params ...any) {
  1062  	f.log.Criticalf(format, params...)
  1063  	f.Logger.Criticalf(format, params...)
  1064  }
  1065  
  1066  func (f *fileLoggerPlus) Warn(v ...any) {
  1067  	f.log.Warn(v...)
  1068  	f.Logger.Warn(v...)
  1069  }
  1070  
  1071  func (f *fileLoggerPlus) Error(v ...any) {
  1072  	f.log.Error(v...)
  1073  	f.Logger.Error(v...)
  1074  }
  1075  
  1076  func (f *fileLoggerPlus) Critical(v ...any) {
  1077  	f.log.Critical(v...)
  1078  	f.Logger.Critical(v...)
  1079  }
  1080  
  1081  type logAdapter struct {
  1082  	dex.Logger
  1083  }
  1084  
  1085  var _ bchlog.Logger = (*logAdapter)(nil)
  1086  
  1087  func (a *logAdapter) Level() bchlog.Level {
  1088  	return bchlog.Level(a.Logger.Level())
  1089  }
  1090  
  1091  func (a *logAdapter) SetLevel(lvl bchlog.Level) {
  1092  	a.Logger.SetLevel(slog.Level(lvl))
  1093  }