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