decred.org/dcrdex@v1.0.3/client/asset/btc/electrum_client.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 btc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"math/rand"
    16  	"net"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"decred.org/dcrdex/client/asset"
    25  	"decred.org/dcrdex/client/asset/btc/electrum"
    26  	"decred.org/dcrdex/dex"
    27  	"decred.org/dcrdex/dex/config"
    28  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    29  	"github.com/btcsuite/btcd/btcec/v2"
    30  	"github.com/btcsuite/btcd/btcutil"
    31  	"github.com/btcsuite/btcd/btcutil/psbt"
    32  	"github.com/btcsuite/btcd/chaincfg"
    33  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    34  	"github.com/btcsuite/btcd/txscript"
    35  	"github.com/btcsuite/btcd/wire"
    36  )
    37  
    38  type electrumWalletClient interface {
    39  	FeeRate(ctx context.Context, confTarget int64) (int64, error)
    40  	Broadcast(ctx context.Context, tx []byte) (string, error)
    41  	AddLocalTx(ctx context.Context, tx []byte) (string, error)
    42  	RemoveLocalTx(ctx context.Context, txid string) error
    43  	Commands(ctx context.Context) ([]string, error)
    44  	GetInfo(ctx context.Context) (*electrum.GetInfoResult, error)
    45  	GetServers(ctx context.Context) ([]*electrum.GetServersResult, error)
    46  	GetBalance(ctx context.Context) (*electrum.Balance, error)
    47  	ListUnspent(ctx context.Context) ([]*electrum.ListUnspentResult, error)
    48  	FreezeUTXO(ctx context.Context, txid string, out uint32) error
    49  	UnfreezeUTXO(ctx context.Context, txid string, out uint32) error
    50  	CreateNewAddress(ctx context.Context) (string, error)
    51  	GetUnusedAddress(ctx context.Context) (string, error)
    52  	CheckAddress(ctx context.Context, addr string) (valid, mine bool, err error)
    53  	SignTx(ctx context.Context, walletPass string, psbtB64 string) ([]byte, error)
    54  	GetPrivateKeys(ctx context.Context, walletPass, addr string) (string, error)
    55  	GetWalletTxConfs(ctx context.Context, txid string) (int, error)     // shortcut if owned
    56  	GetRawTransaction(ctx context.Context, txid string) ([]byte, error) // wallet method
    57  	GetAddressHistory(ctx context.Context, addr string) ([]*electrum.GetAddressHistoryResult, error)
    58  	GetAddressUnspent(ctx context.Context, addr string) ([]*electrum.GetAddressUnspentResult, error)
    59  	OnchainHistory(ctx context.Context, from, to int64) ([]electrum.TransactionResult, error)
    60  	Version(ctx context.Context) (string, error)
    61  	SetIncludeIgnoreWarnings(include bool)
    62  }
    63  
    64  type electrumNetworkClient interface {
    65  	Done() <-chan struct{}
    66  	Shutdown()
    67  	Features(ctx context.Context) (*electrum.ServerFeatures, error)
    68  	GetTransaction(ctx context.Context, txid string) (*electrum.GetTransactionResult, error)
    69  	BlockHeader(ctx context.Context, height uint32) (string, error)
    70  	BlockHeaders(ctx context.Context, startHeight, count uint32) (*electrum.GetBlockHeadersResult, error)
    71  }
    72  
    73  type electrumWallet struct {
    74  	log         dex.Logger
    75  	chainParams *chaincfg.Params
    76  	decodeAddr  dexbtc.AddressDecoder
    77  	stringAddr  dexbtc.AddressStringer
    78  	rpcCfg      *RPCConfig // supports live reconfigure check
    79  	wallet      electrumWalletClient
    80  	chainV      atomic.Value // electrumNetworkClient
    81  	segwit      bool
    82  
    83  	// ctx is set on connect, and used in asset.Wallet and btc.Wallet interface
    84  	// method implementations that have no ctx arg yet (refactoring TODO).
    85  	ctx context.Context
    86  
    87  	lockedOutpointsMtx sync.RWMutex
    88  	lockedOutpoints    map[OutPoint]struct{}
    89  
    90  	pwMtx    sync.RWMutex
    91  	pw       string
    92  	unlocked bool
    93  }
    94  
    95  func (ew *electrumWallet) chain() electrumNetworkClient {
    96  	cl, _ := ew.chainV.Load().(electrumNetworkClient)
    97  	return cl
    98  }
    99  
   100  func (ew *electrumWallet) resetChain(cl electrumNetworkClient) {
   101  	ew.chainV.Store(cl)
   102  }
   103  
   104  type electrumWalletConfig struct {
   105  	params       *chaincfg.Params
   106  	log          dex.Logger
   107  	addrDecoder  dexbtc.AddressDecoder
   108  	addrStringer dexbtc.AddressStringer
   109  	segwit       bool // indicates if segwit addresses are expected from requests
   110  	rpcCfg       *RPCConfig
   111  }
   112  
   113  func newElectrumWallet(ew electrumWalletClient, cfg *electrumWalletConfig) *electrumWallet {
   114  	addrDecoder := cfg.addrDecoder
   115  	if addrDecoder == nil {
   116  		addrDecoder = btcutil.DecodeAddress
   117  	}
   118  
   119  	addrStringer := cfg.addrStringer
   120  	if addrStringer == nil {
   121  		addrStringer = func(addr btcutil.Address, _ *chaincfg.Params) (string, error) {
   122  			return addr.String(), nil
   123  		}
   124  	}
   125  
   126  	return &electrumWallet{
   127  		log:         cfg.log,
   128  		chainParams: cfg.params,
   129  		decodeAddr:  addrDecoder,
   130  		stringAddr:  addrStringer,
   131  		wallet:      ew,
   132  		segwit:      cfg.segwit,
   133  		// TODO: remove this when all interface methods are given a Context. In
   134  		// the meantime, init with a valid sentry context until connect().
   135  		ctx: context.TODO(),
   136  		// chain is constructed after wallet connects to a server
   137  		lockedOutpoints: make(map[OutPoint]struct{}),
   138  		rpcCfg:          cfg.rpcCfg,
   139  	}
   140  }
   141  
   142  // BEGIN unimplemented asset.Wallet methods
   143  
   144  func (ew *electrumWallet) RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error) {
   145  	return nil, errors.New("not available") // and not used
   146  }
   147  
   148  // END unimplemented methods
   149  
   150  // Prefer the SSL port if set, but allow TCP if that's all it has.
   151  func bestAddr(host string, gsr *electrum.GetServersResult) (string, *tls.Config) {
   152  	if gsr.SSL != 0 {
   153  		rootCAs, _ := x509.SystemCertPool()
   154  		tlsConfig := &tls.Config{
   155  			InsecureSkipVerify: true,
   156  			RootCAs:            rootCAs,
   157  			// MinVersion:         tls.VersionTLS12,
   158  			ServerName: host,
   159  		}
   160  		port := strconv.FormatUint(uint64(gsr.SSL), 10)
   161  		return net.JoinHostPort(host, port), tlsConfig
   162  	} else if gsr.TCP != 0 {
   163  		port := strconv.FormatUint(uint64(gsr.TCP), 10)
   164  		return net.JoinHostPort(host, port), nil
   165  	}
   166  	return "", nil
   167  }
   168  
   169  // Look up the port of the active server via getservers and return a "host:port"
   170  // formatted address. A non-nil tls.Config is returned if an SSL port. A empty
   171  // host input will pick a random SSL host.
   172  func (ew *electrumWallet) connInfo(ctx context.Context, host string) (addr string, tlsConfig *tls.Config, err error) {
   173  	servers, err := ew.wallet.GetServers(ctx)
   174  	if err != nil {
   175  		return "", nil, err
   176  	}
   177  	var wsrv *electrum.GetServersResult
   178  	if host == "" { // pick a random SSL host
   179  		var sslServers []*electrum.GetServersResult
   180  		for _, srv := range servers {
   181  			if srv.SSL != 0 {
   182  				sslServers = append(sslServers, srv)
   183  			}
   184  		}
   185  		// TODO: allow non-tcp onion hosts
   186  		if len(sslServers) == 0 {
   187  			return "", nil, errors.New("no SSL servers")
   188  		}
   189  		wsrv = sslServers[rand.Intn(len(sslServers))]
   190  	} else {
   191  		for _, srv := range servers {
   192  			if srv.Host == host {
   193  				wsrv = srv
   194  				break
   195  			}
   196  		}
   197  		if wsrv == nil {
   198  			return "", nil, fmt.Errorf("Electrum wallet server %q not found in getservers result", host)
   199  		}
   200  	}
   201  	addr, tlsConfig = bestAddr(host, wsrv)
   202  	if addr == "" {
   203  		return "", nil, fmt.Errorf("no suitable address for host %v", host)
   204  	}
   205  	return addr, tlsConfig, nil
   206  }
   207  
   208  // part of btc.Wallet interface
   209  func (ew *electrumWallet) connect(ctx context.Context, wg *sync.WaitGroup) error {
   210  	// Helper to get a host:port string and connection options for a host name.
   211  	connInfo := func(host string) (addr string, srvOpts *electrum.ConnectOpts, err error) {
   212  		addr, tlsConfig, err := ew.connInfo(ctx, host)
   213  		if err != nil {
   214  			return "", nil, fmt.Errorf("no suitable address for host %q: %w", host, err)
   215  		}
   216  		srvOpts = &electrum.ConnectOpts{
   217  			// TorProxy: TODO
   218  			TLSConfig:   tlsConfig, // may be nil if not ssl host
   219  			DebugLogger: ew.log.Debugf,
   220  		}
   221  		return addr, srvOpts, nil
   222  	}
   223  
   224  	info, err := ew.wallet.GetInfo(ctx) // also initial connectivity test with the external wallet
   225  	if err != nil {
   226  		return err
   227  	}
   228  	if !info.Connected || info.Server == "" {
   229  		return errors.New("Electrum wallet has no server connections")
   230  	}
   231  
   232  	// Determine if segwit expectation is met. Request and decode an address,
   233  	// then compare with the segwit config field.
   234  	addr, err := ew.wallet.GetUnusedAddress(ctx)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	address, err := ew.decodeAddr(addr, ew.chainParams)
   239  	if err != nil {
   240  		return err
   241  	}
   242  	_, segwit := address.(interface {
   243  		WitnessVersion() byte
   244  	})
   245  	if segwit != ew.segwit {
   246  		return fmt.Errorf("segwit expectation not met: wanted segwit = %v (old wallet seed?)", ew.segwit)
   247  	}
   248  
   249  	addr, srvOpts, err := connInfo(info.Server)
   250  	if err != nil {
   251  		return fmt.Errorf("no suitable address for host %v: %w", info.Server, err)
   252  	}
   253  	chain, err := electrum.ConnectServer(ctx, addr, srvOpts)
   254  	if err != nil {
   255  		return err // maybe just try a different one if it doesn't allow multiple conns
   256  	}
   257  	ew.log.Infof("Now connected to electrum server %v.", addr)
   258  	ew.resetChain(chain)
   259  	ew.ctx = ctx // for requests via methods that lack a context arg
   260  
   261  	// This wallet may not be "protected", in which case we omit the password
   262  	// from the requests. Detect this now and flag the wallet as unlocked.
   263  	_ = ew.walletUnlock([]byte{})
   264  
   265  	// Start a goroutine to keep the chain client alive and on the same
   266  	// ElectrumX server as the external Electrum wallet if possible.
   267  	wg.Add(1)
   268  	go func() {
   269  		defer wg.Done()
   270  		defer ew.chain().Shutdown()
   271  		lastWalletServer := info.Server
   272  
   273  		failing := make(map[string]int)
   274  		const maxFails = 8
   275  
   276  		ticker := time.NewTicker(6 * time.Second) // to keep wallet and chain client on same server
   277  		defer ticker.Stop()
   278  
   279  		for {
   280  			var walletCheck bool
   281  			select {
   282  			case <-ew.chain().Done():
   283  				ew.log.Warnf("Electrum server connection lost. Reconnecting in 5 seconds...")
   284  				select {
   285  				case <-time.After(5 * time.Second):
   286  				case <-ctx.Done():
   287  					return
   288  				}
   289  
   290  			case <-ticker.C: // just checking with wallet for changes
   291  				walletCheck = true
   292  
   293  			case <-ctx.Done():
   294  				return
   295  			}
   296  
   297  			info, err := ew.wallet.GetInfo(ctx)
   298  			if err != nil {
   299  				ew.log.Errorf("Electrum wallet getinfo failed: %v", err)
   300  				continue
   301  			}
   302  			if walletCheck { // just checking if wallet's server changed
   303  				if lastWalletServer == info.Server {
   304  					continue // no change
   305  				}
   306  				delete(failing, info.Server) // clean slate now that wallet has just gotten on it
   307  				ew.log.Infof("Electrum wallet changed server to %v", info.Server)
   308  			}
   309  			lastWalletServer = info.Server
   310  
   311  			tryAddr := info.Server
   312  			if fails := failing[tryAddr]; fails > maxFails {
   313  				ew.log.Warnf("Server %q has failed to connect %d times. Trying a random one...", tryAddr, fails)
   314  				tryAddr = "" // try a random one instead
   315  			}
   316  
   317  			addr, srvOpts, err := connInfo(tryAddr)
   318  			if err != nil {
   319  				failing[tryAddr]++
   320  				ew.log.Errorf("No suitable address for host %q: %v", tryAddr, err)
   321  				continue
   322  			}
   323  
   324  			if walletCheck {
   325  				ew.chain().Shutdown()
   326  			}
   327  			ew.log.Infof("Connecting to new server %v...", addr)
   328  			chain, err := electrum.ConnectServer(ctx, addr, srvOpts)
   329  			if err != nil {
   330  				ew.log.Errorf("Failed to connect to %v: %v", addr, err)
   331  				failing[tryAddr]++
   332  				continue
   333  			}
   334  			ew.log.Infof("Chain service now connected to electrum server %v", addr)
   335  			ew.resetChain(chain)
   336  
   337  			if ctx.Err() != nil { // in case shutdown while waiting on ConnectServer
   338  				return
   339  			}
   340  		}
   341  	}()
   342  
   343  	return err
   344  }
   345  
   346  func (ew *electrumWallet) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) {
   347  	// electrumWallet only handles walletTypeElectrum.
   348  	if cfg.Type != walletTypeElectrum {
   349  		restartRequired = true
   350  		return
   351  	}
   352  
   353  	// Check the RPC configuration.
   354  	var parsedCfg RPCWalletConfig // {RPCConfig, WalletConfig} - former may not change
   355  	err = config.Unmapify(cfg.Settings, &parsedCfg)
   356  	if err != nil {
   357  		return false, fmt.Errorf("error parsing rpc wallet config: %w", err)
   358  	}
   359  	dexbtc.StandardizeRPCConf(&parsedCfg.RPCConfig.RPCConfig, "")
   360  
   361  	// Changing RPC settings is not supported without restart.
   362  	return parsedCfg.RPCConfig != *ew.rpcCfg, nil
   363  }
   364  
   365  // part of btc.Wallet interface
   366  func (ew *electrumWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) {
   367  	b, err := serializeMsgTx(tx)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  	// Add the transaction to the wallet DB before broadcasting it on the
   372  	// network, otherwise it is not immediately recorded. This is expected to
   373  	// error on non-wallet transactions such as counterparty transactions.
   374  	_, err = ew.wallet.AddLocalTx(ew.ctx, b)
   375  	if err != nil && !strings.Contains(err.Error(), "unrelated to this wallet") {
   376  		ew.log.Warnf("Failed to add tx to the wallet DB: %v", err)
   377  	}
   378  	txid, err := ew.wallet.Broadcast(ew.ctx, b)
   379  	if err != nil {
   380  		ew.tryRemoveLocalTx(ew.ctx, tx.TxHash().String())
   381  		return nil, err
   382  	}
   383  	hash, err := chainhash.NewHashFromStr(txid)
   384  	if err != nil {
   385  		return nil, err // well that sucks, it's already sent
   386  	}
   387  	ops := make([]*Output, len(tx.TxIn))
   388  	for i, txIn := range tx.TxIn {
   389  		prevOut := txIn.PreviousOutPoint
   390  		ops[i] = &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)}
   391  	}
   392  	if err = ew.lockUnspent(true, ops); err != nil {
   393  		ew.log.Errorf("Failed to unlock spent UTXOs: %v", err)
   394  	}
   395  	return hash, nil
   396  }
   397  
   398  func (ew *electrumWallet) outputIsSpent(ctx context.Context, txHash *chainhash.Hash, vout uint32, pkScript []byte) (bool, error) {
   399  	_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams)
   400  	if err != nil {
   401  		return false, fmt.Errorf("failed to decode pkScript: %w", err)
   402  	}
   403  	if len(addrs) != 1 {
   404  		return false, fmt.Errorf("pkScript encodes %d addresses, not 1", len(addrs))
   405  	}
   406  	addr, err := ew.stringAddr(addrs[0], ew.chainParams)
   407  	if err != nil {
   408  		return false, fmt.Errorf("invalid address encoding: %w", err)
   409  	}
   410  	// Now see if the unspent outputs for this address include this outpoint.
   411  	addrUnspents, err := ew.wallet.GetAddressUnspent(ctx, addr)
   412  	if err != nil {
   413  		return false, fmt.Errorf("getaddressunspent: %w", err)
   414  	}
   415  	txid := txHash.String()
   416  	for _, utxo := range addrUnspents {
   417  		if utxo.TxHash == txid && uint32(utxo.TxPos) == vout {
   418  			return false, nil // still unspent
   419  		}
   420  	}
   421  	ew.log.Infof("Output %s:%d not found in unspent output list. Searching for spending txn...",
   422  		txid, vout)
   423  	// getaddressunspent can sometimes exclude an unspent output if it is new,
   424  	// so now search for an actual spending txn, which is a more expensive
   425  	// operation so we only fall back on this.
   426  	spendTx, _, err := ew.findOutputSpender(ctx, txHash, vout)
   427  	if err != nil {
   428  		return false, fmt.Errorf("failure while checking for spending txn: %v", err)
   429  	}
   430  	return spendTx != nil, nil
   431  }
   432  
   433  // part of btc.Wallet interface
   434  func (ew *electrumWallet) getTxOut(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) {
   435  	return ew.getTxOutput(ew.ctx, txHash, vout)
   436  }
   437  
   438  func (ew *electrumWallet) getTxOutput(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.TxOut, uint32, error) {
   439  	// In case this is a wallet transaction, try the wallet DB methods first,
   440  	// then fall back to the more expensive server request.
   441  	txid := txHash.String()
   442  	txRaw, confs, err := ew.checkWalletTx(txid)
   443  	if err != nil {
   444  		txRes, err := ew.chain().GetTransaction(ctx, txid)
   445  		if err != nil {
   446  			return nil, 0, err
   447  		}
   448  		confs = uint32(txRes.Confirmations)
   449  		txRaw, err = hex.DecodeString(txRes.Hex)
   450  		if err != nil {
   451  			return nil, 0, err
   452  		}
   453  	}
   454  
   455  	msgTx, err := msgTxFromBytes(txRaw)
   456  	if err != nil {
   457  		return nil, 0, err
   458  	}
   459  	if vout >= uint32(len(msgTx.TxOut)) {
   460  		return nil, 0, fmt.Errorf("output %d of tx %v does not exists", vout, txid)
   461  	}
   462  	pkScript := msgTx.TxOut[vout].PkScript
   463  	amt := msgTx.TxOut[vout].Value
   464  
   465  	// Given the pkScript, we can query for unspent outputs to see if this one
   466  	// is unspent.
   467  	spent, err := ew.outputIsSpent(ctx, txHash, vout, pkScript)
   468  	if err != nil {
   469  		return nil, 0, err
   470  	}
   471  	if spent {
   472  		return nil, 0, nil
   473  	}
   474  
   475  	return wire.NewTxOut(amt, pkScript), confs, nil
   476  }
   477  
   478  func (ew *electrumWallet) getBlockHeaderByHeight(ctx context.Context, height int64) (*wire.BlockHeader, error) {
   479  	hdrStr, err := ew.chain().BlockHeader(ctx, uint32(height))
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	hdr := &wire.BlockHeader{}
   484  	err = hdr.Deserialize(hex.NewDecoder(strings.NewReader(hdrStr)))
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  	return hdr, nil
   489  }
   490  
   491  // part of btc.Wallet interface
   492  func (ew *electrumWallet) medianTime() (time.Time, error) {
   493  	chainHeight, err := ew.getBestBlockHeight()
   494  	if err != nil {
   495  		return time.Time{}, err
   496  	}
   497  	return ew.calcMedianTime(ew.ctx, int64(chainHeight))
   498  }
   499  
   500  func (ew *electrumWallet) calcMedianTime(ctx context.Context, height int64) (time.Time, error) {
   501  	startHeight := height - medianTimeBlocks + 1
   502  	if startHeight < 0 {
   503  		startHeight = 0
   504  	}
   505  
   506  	// TODO: check a block hash => median time cache
   507  
   508  	hdrsRes, err := ew.chain().BlockHeaders(ctx, uint32(startHeight),
   509  		uint32(height-startHeight+1))
   510  	if err != nil {
   511  		return time.Time{}, err
   512  	}
   513  
   514  	if hdrsRes.Count != medianTimeBlocks {
   515  		ew.log.Warnf("Failed to retrieve headers for %d blocks since block %v, got %d",
   516  			medianTimeBlocks, height, hdrsRes.Count)
   517  	}
   518  	if hdrsRes.Count == 0 {
   519  		return time.Time{}, errors.New("no headers retrieved")
   520  	}
   521  
   522  	hdrReader := hex.NewDecoder(strings.NewReader(hdrsRes.HexConcat))
   523  
   524  	timestamps := make([]int64, 0, hdrsRes.Count)
   525  	for i := int64(0); i < int64(hdrsRes.Count); i++ {
   526  		hdr := &wire.BlockHeader{}
   527  		err = hdr.Deserialize(hdrReader)
   528  		if err != nil {
   529  			if i > 0 {
   530  				ew.log.Errorf("Failed to deserialize header for block %d: %v",
   531  					startHeight+i, err)
   532  				break // we have at least one time stamp, work with it
   533  			}
   534  			return time.Time{}, err
   535  		}
   536  		timestamps = append(timestamps, hdr.Timestamp.Unix())
   537  	}
   538  	// Naive way fetching each header separately, if we needed to use
   539  	// btc.calcMedianTime as a chainStamper:
   540  	// for i := height; i > height-medianTimeBlocks && i > 0; i-- {
   541  	// 	hdr, err := ew.getBlockHeaderByHeight(ctx, height)
   542  	// 	if err != nil {
   543  	// 		return time.Time{}, err
   544  	// 	}
   545  	// 	timestamps = append(timestamps, hdr.Timestamp.Unix())
   546  	// }
   547  
   548  	sort.Slice(timestamps, func(i, j int) bool {
   549  		return timestamps[i] < timestamps[j]
   550  	})
   551  
   552  	medianTimestamp := timestamps[len(timestamps)/2]
   553  	return time.Unix(medianTimestamp, 0), nil
   554  }
   555  
   556  // part of btc.Wallet interface
   557  func (ew *electrumWallet) getBlockHash(height int64) (*chainhash.Hash, error) {
   558  	hdr, err := ew.getBlockHeaderByHeight(ew.ctx, height)
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  	hash := hdr.BlockHash()
   563  	return &hash, nil
   564  }
   565  
   566  // part of btc.Wallet interface
   567  func (ew *electrumWallet) getBestBlockHash() (*chainhash.Hash, error) {
   568  	inf, err := ew.wallet.GetInfo(ew.ctx)
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  	return ew.getBlockHash(inf.SyncHeight)
   573  }
   574  
   575  // part of btc.Wallet interface
   576  func (ew *electrumWallet) getBestBlockHeight() (int32, error) {
   577  	inf, err := ew.wallet.GetInfo(ew.ctx)
   578  	if err != nil {
   579  		return 0, err
   580  	}
   581  	return int32(inf.SyncHeight), nil
   582  }
   583  
   584  // part of btc.Wallet interface
   585  func (ew *electrumWallet) getBestBlockHeader() (*BlockHeader, error) {
   586  	inf, err := ew.wallet.GetInfo(ew.ctx)
   587  	if err != nil {
   588  		return nil, err
   589  	}
   590  
   591  	hdr, err := ew.getBlockHeaderByHeight(ew.ctx, inf.SyncHeight)
   592  	if err != nil {
   593  		return nil, err
   594  	}
   595  
   596  	header := &BlockHeader{
   597  		Hash:              hdr.BlockHash().String(),
   598  		Height:            inf.SyncHeight,
   599  		Confirmations:     1, // it's the head
   600  		Time:              hdr.Timestamp.Unix(),
   601  		PreviousBlockHash: hdr.PrevBlock.String(),
   602  	}
   603  	return header, nil
   604  }
   605  
   606  // part of btc.Wallet interface
   607  func (ew *electrumWallet) balances() (*GetBalancesResult, error) {
   608  	eBal, err := ew.wallet.GetBalance(ew.ctx)
   609  	if err != nil {
   610  		return nil, err
   611  	}
   612  	// NOTE: Nothing from the Electrum wallet's response indicates trusted vs.
   613  	// untrusted. To allow unconfirmed coins to be spent, we treat both
   614  	// confirmed and unconfirmed as trusted. This is like dogecoind's handling
   615  	// of balance. TODO: listunspent -> checkWalletTx(txid) -> for each
   616  	// input, checkWalletTx(prevout) and ismine(addr)
   617  	return &GetBalancesResult{
   618  		Mine: Balances{
   619  			Trusted:  eBal.Confirmed + eBal.Unconfirmed,
   620  			Immature: eBal.Immature,
   621  		},
   622  	}, nil
   623  }
   624  
   625  // part of btc.Wallet interface
   626  func (ew *electrumWallet) listUnspent() ([]*ListUnspentResult, error) {
   627  	eUnspent, err := ew.wallet.ListUnspent(ew.ctx)
   628  	if err != nil {
   629  		return nil, err
   630  	}
   631  	chainHeight, err := ew.getBestBlockHeight()
   632  	if err != nil {
   633  		return nil, err
   634  	}
   635  
   636  	// Filter out locked outpoints since listUnspent includes them.
   637  	lockedOPs := ew.listLockedOutpoints()
   638  	lockedOPMap := make(map[RPCOutpoint]bool, len(lockedOPs))
   639  	for _, pt := range lockedOPs {
   640  		lockedOPMap[*pt] = true
   641  	}
   642  
   643  	unspents := make([]*ListUnspentResult, 0, len(eUnspent))
   644  	for _, utxo := range eUnspent {
   645  		if lockedOPMap[RPCOutpoint{utxo.PrevOutHash, utxo.PrevOutIdx}] {
   646  			continue
   647  		}
   648  		addr, err := ew.decodeAddr(utxo.Address, ew.chainParams)
   649  		if err != nil {
   650  			ew.log.Warnf("Output (%v:%d) with bad address %v found: %v",
   651  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err)
   652  			continue
   653  		}
   654  		pkScript, err := txscript.PayToAddrScript(addr)
   655  		if err != nil {
   656  			ew.log.Warnf("Output (%v:%d) with bad address %v found: %v",
   657  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err)
   658  			continue
   659  		}
   660  		val, err := strconv.ParseFloat(utxo.Value, 64)
   661  		if err != nil {
   662  			ew.log.Warnf("Output (%v:%d) with bad value %v found: %v",
   663  				utxo.PrevOutHash, utxo.PrevOutIdx, val, err)
   664  			continue
   665  		}
   666  		var confs uint32
   667  		if height := int32(utxo.Height); height > 0 {
   668  			// height is non-zero, so confirmed, but if the RPCs are
   669  			// inconsistent with respect to height, avoid an underflow or
   670  			// appearing unconfirmed.
   671  			if height > chainHeight {
   672  				confs = 1
   673  			} else {
   674  				confs = uint32(chainHeight - height + 1)
   675  			}
   676  		}
   677  		redeemScript, err := hex.DecodeString(utxo.RedeemScript)
   678  		if err != nil {
   679  			ew.log.Warnf("Output (%v:%d) with bad redeemscript %v found: %v",
   680  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.RedeemScript, err)
   681  			continue
   682  		}
   683  
   684  		unspents = append(unspents, &ListUnspentResult{
   685  			TxID:          utxo.PrevOutHash,
   686  			Vout:          utxo.PrevOutIdx,
   687  			Address:       utxo.Address,
   688  			ScriptPubKey:  pkScript,
   689  			Amount:        val,
   690  			Confirmations: confs,
   691  			RedeemScript:  redeemScript,
   692  			Spendable:     true, // can electrum have unspendable?
   693  			Solvable:      true,
   694  			// Safe is unknown, leave ptr nil
   695  		})
   696  	}
   697  	return unspents, nil
   698  }
   699  
   700  // part of btc.Wallet interface
   701  func (ew *electrumWallet) lockUnspent(unlock bool, ops []*Output) error {
   702  	eUnspent, err := ew.wallet.ListUnspent(ew.ctx)
   703  	if err != nil {
   704  		return err
   705  	}
   706  	opMap := make(map[OutPoint]struct{}, len(ops))
   707  	for _, op := range ops {
   708  		opMap[op.Pt] = struct{}{}
   709  	}
   710  	// For the ones that appear in listunspent, use (un)freeze_utxo also.
   711  unspents:
   712  	for _, utxo := range eUnspent {
   713  		for op := range opMap {
   714  			if op.Vout == utxo.PrevOutIdx && op.TxHash.String() == utxo.PrevOutHash {
   715  				// FreezeUTXO and UnfreezeUTXO do not error when called
   716  				// repeatedly for the same UTXO.
   717  				if unlock {
   718  					if err = ew.wallet.UnfreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil {
   719  						ew.log.Warnf("UnfreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err)
   720  						// Maybe we lost a race somewhere. Keep going.
   721  					}
   722  					ew.lockedOutpointsMtx.Lock()
   723  					delete(ew.lockedOutpoints, op)
   724  					ew.lockedOutpointsMtx.Unlock()
   725  					delete(opMap, op)
   726  					continue unspents
   727  				}
   728  
   729  				if err = ew.wallet.FreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil {
   730  					ew.log.Warnf("FreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err)
   731  				}
   732  				// listunspent returns locked utxos, so we have to track it.
   733  				ew.lockedOutpointsMtx.Lock()
   734  				ew.lockedOutpoints[op] = struct{}{}
   735  				ew.lockedOutpointsMtx.Unlock()
   736  				delete(opMap, op)
   737  				continue unspents
   738  			}
   739  		}
   740  	}
   741  
   742  	// If not in the listunspent response, fail if trying to lock, otherwise
   743  	// just remove them from the lockedOutpoints map (unlocking spent UTXOs).
   744  	if len(opMap) > 0 && !unlock {
   745  		return fmt.Errorf("failed to lock some utxos")
   746  	}
   747  	for op := range opMap {
   748  		ew.lockedOutpointsMtx.Lock()
   749  		delete(ew.lockedOutpoints, op)
   750  		ew.lockedOutpointsMtx.Unlock()
   751  	}
   752  
   753  	return nil
   754  }
   755  
   756  func (ew *electrumWallet) listLockedOutpoints() []*RPCOutpoint {
   757  	ew.lockedOutpointsMtx.RLock()
   758  	defer ew.lockedOutpointsMtx.RUnlock()
   759  	locked := make([]*RPCOutpoint, 0, len(ew.lockedOutpoints))
   760  	for op := range ew.lockedOutpoints {
   761  		locked = append(locked, &RPCOutpoint{
   762  			TxID: op.TxHash.String(),
   763  			Vout: op.Vout,
   764  		})
   765  	}
   766  	return locked
   767  }
   768  
   769  // part of btc.Wallet interface
   770  func (ew *electrumWallet) listLockUnspent() ([]*RPCOutpoint, error) {
   771  	return ew.listLockedOutpoints(), nil
   772  }
   773  
   774  // externalAddress creates a fresh address beyond the default gap limit, so it
   775  // should be used immediately. Part of btc.Wallet interface.
   776  func (ew *electrumWallet) externalAddress() (btcutil.Address, error) {
   777  	addr, err := ew.wallet.GetUnusedAddress(ew.ctx)
   778  	if err != nil {
   779  		return nil, err
   780  	}
   781  	return ew.decodeAddr(addr, ew.chainParams)
   782  }
   783  
   784  // changeAddress creates a fresh address beyond the default gap limit, so it
   785  // should be used immediately. Part of btc.Wallet interface.
   786  func (ew *electrumWallet) changeAddress() (btcutil.Address, error) {
   787  	return ew.externalAddress() // sadly, cannot request internal addresses
   788  }
   789  
   790  // part of btc.Wallet interface
   791  func (ew *electrumWallet) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) {
   792  	// If the wallet's signtransaction RPC ever has a problem with the PSBT, we
   793  	// could attempt to sign the transaction ourselves by pulling the inputs'
   794  	// private keys and using txscript manually, but this can vary greatly
   795  	// between assets.
   796  
   797  	packet, err := psbt.NewFromUnsignedTx(inTx)
   798  	if err != nil {
   799  		return nil, err
   800  	}
   801  	psbtB64, err := packet.B64Encode()
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  
   806  	signedB, err := ew.wallet.SignTx(ew.ctx, ew.walletPass(), psbtB64)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	return msgTxFromBytes(signedB)
   811  }
   812  
   813  type hash160er interface {
   814  	Hash160() *[20]byte
   815  }
   816  
   817  type pubKeyer interface {
   818  	PubKey() *btcec.PublicKey
   819  }
   820  
   821  // part of btc.Wallet interface
   822  func (ew *electrumWallet) privKeyForAddress(addr string) (*btcec.PrivateKey, error) {
   823  	addrDec, err := ew.decodeAddr(addr, ew.chainParams)
   824  	if err != nil {
   825  		return nil, err
   826  	}
   827  	wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, ew.walletPass(), addr)
   828  	if err != nil {
   829  		return nil, err
   830  	}
   831  	wif, err := btcutil.DecodeWIF(wifStr)
   832  	if err != nil {
   833  		return nil, err
   834  	} // wif.PrivKey is the result
   835  
   836  	// Sanity check that PrivKey corresponds to the pubkey(hash).
   837  	var pkh []byte
   838  	switch addrT := addrDec.(type) {
   839  	case pubKeyer: // e.g. *btcutil.AddressPubKey:
   840  		// Get same format as wif.SerializePubKey()
   841  		var pk []byte
   842  		if wif.CompressPubKey {
   843  			pk = addrT.PubKey().SerializeCompressed()
   844  		} else {
   845  			pk = addrT.PubKey().SerializeUncompressed()
   846  		}
   847  		pkh = btcutil.Hash160(pk) // addrT.ScriptAddress() would require SetFormat(compress/uncompress)
   848  	case *btcutil.AddressScriptHash, *btcutil.AddressWitnessScriptHash:
   849  		return wif.PrivKey, nil // assume unknown redeem script references this pubkey
   850  	case hash160er: // p2pkh and p2wpkh
   851  		pkh = addrT.Hash160()[:]
   852  	}
   853  	wifPKH := btcutil.Hash160(wif.SerializePubKey())
   854  	if !bytes.Equal(pkh, wifPKH) {
   855  		return nil, errors.New("pubkey mismatch")
   856  	}
   857  	return wif.PrivKey, nil
   858  }
   859  
   860  func (ew *electrumWallet) pass() (pw string, unlocked bool) {
   861  	ew.pwMtx.RLock()
   862  	defer ew.pwMtx.RUnlock()
   863  	return ew.pw, ew.unlocked
   864  }
   865  
   866  func (ew *electrumWallet) testPass(pw []byte) error {
   867  	addr, err := ew.wallet.GetUnusedAddress(ew.ctx)
   868  	if err != nil {
   869  		return err
   870  	}
   871  	wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, string(pw), addr)
   872  	if err != nil {
   873  		// When providing a password to an unprotected wallet, and other cases,
   874  		// a cryptic error containing "incorrect padding" is returned.
   875  		if strings.Contains(strings.ToLower(err.Error()), "incorrect padding") {
   876  			return errors.New("incorrect password (no password required?)")
   877  		}
   878  		return fmt.Errorf("GetPrivateKeys: %v", err)
   879  	}
   880  	// That should be enough, but validate the returned keys in case they are
   881  	// empty or invalid.
   882  	if _, err = btcutil.DecodeWIF(wifStr); err != nil {
   883  		return fmt.Errorf("DecodeWIF: %v", err)
   884  	}
   885  	return nil
   886  }
   887  
   888  // walletLock locks the wallet. Part of the btc.Wallet interface.
   889  func (ew *electrumWallet) walletLock() error {
   890  	ew.pwMtx.Lock()
   891  	defer ew.pwMtx.Unlock()
   892  	if ew.pw == "" && ew.unlocked {
   893  		// This is an unprotected wallet (can't actually lock it). But confirm
   894  		// the password is still empty in case it changed externally.
   895  		if err := ew.testPass([]byte{}); err == nil {
   896  			return nil
   897  		} // must have changed! "lock" it!
   898  	}
   899  	ew.pw, ew.unlocked = "", false
   900  	return nil
   901  }
   902  
   903  // locked indicates if the wallet has been unlocked. Part of the btc.Wallet
   904  // interface.
   905  func (ew *electrumWallet) locked() bool {
   906  	ew.pwMtx.RLock()
   907  	defer ew.pwMtx.RUnlock()
   908  	return !ew.unlocked
   909  }
   910  
   911  // walletPass returns the wallet passphrase. Since an empty password is valid,
   912  // use pass or locked to determine if locked. This is for convenience.
   913  func (ew *electrumWallet) walletPass() string {
   914  	pw, _ := ew.pass()
   915  	return pw
   916  }
   917  
   918  // walletUnlock attempts to unlock the wallet with the provided password. On
   919  // success, the password is stored and may be accessed via pass or walletPass.
   920  // Part of the btc.Wallet interface.
   921  func (ew *electrumWallet) walletUnlock(pw []byte) error {
   922  	if err := ew.testPass(pw); err != nil {
   923  		return err
   924  	}
   925  	ew.pwMtx.Lock()
   926  	ew.pw, ew.unlocked = string(pw), true
   927  	ew.pwMtx.Unlock()
   928  	return nil
   929  }
   930  
   931  // part of the btc.Wallet interface
   932  func (ew *electrumWallet) peerCount() (uint32, error) {
   933  	if ew.chain() == nil { // must work prior to resetChain
   934  		return 0, nil
   935  	}
   936  
   937  	info, err := ew.wallet.GetInfo(ew.ctx)
   938  	if err != nil {
   939  		return 0, err
   940  	}
   941  	select {
   942  	case <-ew.chain().Done():
   943  		return 0, errors.New("electrumx server connection down")
   944  	default:
   945  	}
   946  
   947  	return uint32(info.Connections), nil
   948  }
   949  
   950  // part of the btc.Wallet interface
   951  func (ew *electrumWallet) ownsAddress(addr btcutil.Address) (bool, error) {
   952  	addrStr, err := ew.stringAddr(addr, ew.chainParams)
   953  	if err != nil {
   954  		return false, err
   955  	}
   956  	valid, mine, err := ew.wallet.CheckAddress(ew.ctx, addrStr)
   957  	if err != nil {
   958  		return false, err
   959  	}
   960  	if !valid { // maybe electrum doesn't know all encodings that btcutil does
   961  		return false, nil // an error here may prevent reconfiguring a misconfigured wallet
   962  	}
   963  	return mine, nil
   964  }
   965  
   966  // part of the btc.Wallet interface
   967  func (ew *electrumWallet) syncStatus() (*asset.SyncStatus, error) {
   968  	info, err := ew.wallet.GetInfo(ew.ctx)
   969  	if err != nil {
   970  		return nil, err
   971  	}
   972  	return &asset.SyncStatus{
   973  		Synced:       info.Connected && info.SyncHeight >= info.ServerHeight,
   974  		TargetHeight: uint64(info.ServerHeight),
   975  		Blocks:       uint64(info.SyncHeight),
   976  	}, nil
   977  }
   978  
   979  // part of the btc.Wallet interface
   980  func (ew *electrumWallet) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) {
   981  	bestHeight, err := ew.getBestBlockHeight()
   982  	if err != nil {
   983  		return nil, fmt.Errorf("error getting best block: %v", err)
   984  	}
   985  	if bestHeight < blockHeight {
   986  		return nil, nil
   987  	}
   988  	var txs []*ListTransactionsResult
   989  	from := int64(blockHeight)
   990  	to := int64(blockHeight + 2000)
   991  	for {
   992  		if to > int64(bestHeight) {
   993  			to = int64(bestHeight)
   994  		}
   995  		addTxs, err := ew.wallet.OnchainHistory(ew.ctx, from, to)
   996  		if err != nil {
   997  			return nil, fmt.Errorf("error getting onchain history: %v", err)
   998  		}
   999  		for _, tx := range addTxs {
  1000  			ltr := &ListTransactionsResult{
  1001  				BlockHeight: uint32(tx.Height),
  1002  				BlockTime:   uint64(tx.Timestamp), // Maybe?
  1003  				Send:        !tx.Incoming,
  1004  				TxID:        tx.TxID,
  1005  			}
  1006  			if tx.Fee != nil {
  1007  				f, err := strconv.ParseFloat(*tx.Fee, 64)
  1008  				if err != nil {
  1009  					return nil, fmt.Errorf("error parsing fee: %v", err)
  1010  				}
  1011  				ltr.Fee = &f
  1012  			}
  1013  			txs = append(txs, ltr)
  1014  		}
  1015  		if to == int64(bestHeight) {
  1016  			break
  1017  		}
  1018  		from = to
  1019  		to += 2000
  1020  	}
  1021  	return txs, nil
  1022  }
  1023  
  1024  // checkWalletTx will get the bytes and confirmations of a wallet transaction.
  1025  // For non-wallet transactions, it is normal to see "Exception: Transaction not
  1026  // in wallet" in Electrum's parent console, if launched from a terminal.
  1027  // Part of the walletTxChecker interface.
  1028  func (ew *electrumWallet) checkWalletTx(txid string) ([]byte, uint32, error) {
  1029  	// GetWalletTxConfs only works for wallet transactions, while
  1030  	// wallet.GetRawTransaction will try the wallet DB first, but fall back to
  1031  	// querying a server, so do GetWalletTxConfs first to prevent that.
  1032  	confs, err := ew.wallet.GetWalletTxConfs(ew.ctx, txid)
  1033  	if err != nil {
  1034  		return nil, 0, err
  1035  	}
  1036  	txRaw, err := ew.wallet.GetRawTransaction(ew.ctx, txid)
  1037  	if err != nil {
  1038  		return nil, 0, err
  1039  	}
  1040  	if confs < 0 {
  1041  		confs = 0
  1042  	}
  1043  	return txRaw, uint32(confs), nil
  1044  }
  1045  
  1046  // part of the walletTxChecker interface
  1047  func (ew *electrumWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) {
  1048  	// Try the wallet first. If it is not a wallet transaction or if it is
  1049  	// confirmed, fall back to the chain method to get the block info and time
  1050  	// fields.
  1051  	txid := txHash.String()
  1052  	txRaw, confs, err := ew.checkWalletTx(txid)
  1053  	if err == nil && confs == 0 {
  1054  		return &GetTransactionResult{
  1055  			TxID:  txid,
  1056  			Bytes: txRaw,
  1057  			// Time/TimeReceived? now? needed?
  1058  		}, nil
  1059  	} // else we have to ask a server for the verbose response with block info
  1060  
  1061  	txInfo, err := ew.chain().GetTransaction(ew.ctx, txid)
  1062  	if err != nil {
  1063  		return nil, err
  1064  	}
  1065  	txRaw, err = hex.DecodeString(txInfo.Hex)
  1066  	if err != nil {
  1067  		return nil, err
  1068  	}
  1069  	return &GetTransactionResult{
  1070  		Confirmations: uint64(txInfo.Confirmations),
  1071  		BlockHash:     txInfo.BlockHash,
  1072  		// BlockIndex unknown
  1073  		BlockTime:    uint64(txInfo.BlockTime),
  1074  		TxID:         txInfo.TxID, // txHash.String()
  1075  		Time:         uint64(txInfo.Time),
  1076  		TimeReceived: uint64(txInfo.Time),
  1077  		Bytes:        txRaw,
  1078  	}, nil
  1079  }
  1080  
  1081  func (ew *electrumWallet) fingerprint() (string, error) {
  1082  	return "", fmt.Errorf("fingerprint not implemented")
  1083  }
  1084  
  1085  // part of the walletTxChecker interface
  1086  func (ew *electrumWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) {
  1087  	// To determine if it is spent, we need the address of the output.
  1088  	var pkScript []byte
  1089  	txid := txHash.String()
  1090  	// Try the wallet first in case this is a wallet transaction (own swap).
  1091  	txRaw, confs, err := ew.checkWalletTx(txid)
  1092  	if err == nil {
  1093  		msgTx, err := msgTxFromBytes(txRaw)
  1094  		if err != nil {
  1095  			return 0, false, err
  1096  		}
  1097  		if vout >= uint32(len(msgTx.TxOut)) {
  1098  			return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1099  		}
  1100  		pkScript = msgTx.TxOut[vout].PkScript
  1101  	} else {
  1102  		// Fall back to the more expensive server request.
  1103  		txInfo, err := ew.chain().GetTransaction(ew.ctx, txid)
  1104  		if err != nil {
  1105  			return 0, false, err
  1106  		}
  1107  		confs = uint32(txInfo.Confirmations)
  1108  		if txInfo.Confirmations < 1 {
  1109  			confs = 0
  1110  		}
  1111  		if vout >= uint32(len(txInfo.Vout)) {
  1112  			return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1113  		}
  1114  		txOut := &txInfo.Vout[vout]
  1115  		pkScript, err = hex.DecodeString(txOut.PkScript.Hex)
  1116  		if err != nil {
  1117  			return 0, false, fmt.Errorf("invalid pkScript: %w", err)
  1118  		}
  1119  	}
  1120  
  1121  	spent, err = ew.outputIsSpent(ew.ctx, txHash, vout, pkScript)
  1122  	if err != nil {
  1123  		return 0, false, err
  1124  	}
  1125  	return confs, spent, nil
  1126  }
  1127  
  1128  // tryRemoveLocalTx attempts to remove a "local" transaction from the Electrum
  1129  // wallet. Such a transaction is unbroadcasted. This may be necessary if a
  1130  // broadcast of a local txn attempt failed so that the inputs are available for
  1131  // other transactions.
  1132  func (ew *electrumWallet) tryRemoveLocalTx(ctx context.Context, txid string) {
  1133  	if err := ew.wallet.RemoveLocalTx(ctx, txid); err != nil {
  1134  		ew.log.Errorf("Failed to remove local transaction %s: %v",
  1135  			txid, err)
  1136  	}
  1137  }
  1138  
  1139  func (ew *electrumWallet) outPointAddress(ctx context.Context, txid string, vout uint32) (string, error) {
  1140  	txRaw, err := ew.wallet.GetRawTransaction(ctx, txid)
  1141  	if err != nil {
  1142  		return "", err
  1143  	}
  1144  	msgTx, err := msgTxFromBytes(txRaw)
  1145  	if err != nil {
  1146  		return "", err
  1147  	}
  1148  	if vout >= uint32(len(msgTx.TxOut)) {
  1149  		return "", fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1150  	}
  1151  	pkScript := msgTx.TxOut[vout].PkScript
  1152  	_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams)
  1153  	if err != nil {
  1154  		return "", fmt.Errorf("invalid pkScript: %v", err)
  1155  	}
  1156  	if len(addrs) != 1 {
  1157  		return "", fmt.Errorf("invalid pkScript: %d addresses", len(addrs))
  1158  	}
  1159  	addrStr, err := ew.stringAddr(addrs[0], ew.chainParams)
  1160  	if err != nil {
  1161  		return "", err
  1162  	}
  1163  	return addrStr, nil
  1164  }
  1165  
  1166  func (ew *electrumWallet) findOutputSpender(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.MsgTx, uint32, error) {
  1167  	txid := txHash.String()
  1168  	addr, err := ew.outPointAddress(ctx, txid, vout)
  1169  	if err != nil {
  1170  		return nil, 0, fmt.Errorf("invalid outpoint address: %w", err)
  1171  	}
  1172  	// NOTE: Caller should already have determined the output is spent before
  1173  	// requesting the entire address history.
  1174  	hist, err := ew.wallet.GetAddressHistory(ctx, addr)
  1175  	if err != nil {
  1176  		return nil, 0, fmt.Errorf("unable to get address history: %w", err)
  1177  	}
  1178  
  1179  	sort.Slice(hist, func(i, j int) bool {
  1180  		return hist[i].Height > hist[j].Height // descending
  1181  	})
  1182  
  1183  	var outHeight int64
  1184  	for _, io := range hist {
  1185  		if io.TxHash == txid {
  1186  			outHeight = io.Height
  1187  			continue // same txn
  1188  		}
  1189  		if io.Height < outHeight {
  1190  			break // spender not before the output's txn
  1191  		}
  1192  		txRaw, err := ew.wallet.GetRawTransaction(ctx, io.TxHash)
  1193  		if err != nil {
  1194  			ew.log.Warnf("Unable to retrieve transaction %v for address %v: %v",
  1195  				io.TxHash, addr, err)
  1196  			continue
  1197  		}
  1198  		msgTx, err := msgTxFromBytes(txRaw)
  1199  		if err != nil {
  1200  			ew.log.Warnf("Unable to decode transaction %v for address %v: %v",
  1201  				io.TxHash, addr, err)
  1202  			continue
  1203  		}
  1204  		for vin, txIn := range msgTx.TxIn {
  1205  			prevOut := &txIn.PreviousOutPoint
  1206  			if vout == prevOut.Index && prevOut.Hash.IsEqual(txHash) {
  1207  				return msgTx, uint32(vin), nil
  1208  			}
  1209  		}
  1210  	}
  1211  
  1212  	return nil, 0, nil // caller should check msgTx (internal method)
  1213  }