decred.org/dcrdex@v1.0.5/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  	hdrStr, err := ew.chain().BlockHeader(ew.ctx, uint32(height))
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  
   563  	fullHeader, err := hex.DecodeString(hdrStr)
   564  	if err != nil {
   565  		return nil, err
   566  	}
   567  
   568  	hash := chainhash.DoubleHashH(fullHeader)
   569  
   570  	return &hash, nil
   571  }
   572  
   573  // part of btc.Wallet interface
   574  func (ew *electrumWallet) GetBestBlockHash() (*chainhash.Hash, error) {
   575  	inf, err := ew.wallet.GetInfo(ew.ctx)
   576  	if err != nil {
   577  		return nil, err
   578  	}
   579  	return ew.GetBlockHash(inf.SyncHeight)
   580  }
   581  
   582  // part of btc.Wallet interface
   583  func (ew *electrumWallet) GetBestBlockHeight() (int32, error) {
   584  	inf, err := ew.wallet.GetInfo(ew.ctx)
   585  	if err != nil {
   586  		return 0, err
   587  	}
   588  	return int32(inf.SyncHeight), nil
   589  }
   590  
   591  // part of btc.Wallet interface
   592  func (ew *electrumWallet) GetBestBlockHeader() (*BlockHeader, error) {
   593  	inf, err := ew.wallet.GetInfo(ew.ctx)
   594  	if err != nil {
   595  		return nil, err
   596  	}
   597  
   598  	hdr, err := ew.getBlockHeaderByHeight(ew.ctx, inf.SyncHeight)
   599  	if err != nil {
   600  		return nil, err
   601  	}
   602  
   603  	header := &BlockHeader{
   604  		Hash:              hdr.BlockHash().String(),
   605  		Height:            inf.SyncHeight,
   606  		Confirmations:     1, // it's the head
   607  		Time:              hdr.Timestamp.Unix(),
   608  		PreviousBlockHash: hdr.PrevBlock.String(),
   609  	}
   610  	return header, nil
   611  }
   612  
   613  // part of btc.Wallet interface
   614  func (ew *electrumWallet) Balances() (*GetBalancesResult, error) {
   615  	eBal, err := ew.wallet.GetBalance(ew.ctx)
   616  	if err != nil {
   617  		return nil, err
   618  	}
   619  	// NOTE: Nothing from the Electrum wallet's response indicates trusted vs.
   620  	// untrusted. To allow unconfirmed coins to be spent, we treat both
   621  	// confirmed and unconfirmed as trusted. This is like dogecoind's handling
   622  	// of balance. TODO: listunspent -> checkWalletTx(txid) -> for each
   623  	// input, checkWalletTx(prevout) and ismine(addr)
   624  	return &GetBalancesResult{
   625  		Mine: Balances{
   626  			Trusted:  eBal.Confirmed + eBal.Unconfirmed,
   627  			Immature: eBal.Immature,
   628  		},
   629  	}, nil
   630  }
   631  
   632  // part of btc.Wallet interface
   633  func (ew *electrumWallet) ListUnspent() ([]*ListUnspentResult, error) {
   634  	eUnspent, err := ew.wallet.ListUnspent(ew.ctx)
   635  	if err != nil {
   636  		return nil, err
   637  	}
   638  	chainHeight, err := ew.GetBestBlockHeight()
   639  	if err != nil {
   640  		return nil, err
   641  	}
   642  
   643  	// Filter out locked outpoints since listUnspent includes them.
   644  	lockedOPs := ew.listLockedOutpoints()
   645  	lockedOPMap := make(map[RPCOutpoint]bool, len(lockedOPs))
   646  	for _, pt := range lockedOPs {
   647  		lockedOPMap[*pt] = true
   648  	}
   649  
   650  	unspents := make([]*ListUnspentResult, 0, len(eUnspent))
   651  	for _, utxo := range eUnspent {
   652  		if lockedOPMap[RPCOutpoint{utxo.PrevOutHash, utxo.PrevOutIdx}] {
   653  			continue
   654  		}
   655  		addr, err := ew.decodeAddr(utxo.Address, ew.chainParams)
   656  		if err != nil {
   657  			ew.log.Warnf("Output (%v:%d) with bad address %v found: %v",
   658  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err)
   659  			continue
   660  		}
   661  		pkScript, err := txscript.PayToAddrScript(addr)
   662  		if err != nil {
   663  			ew.log.Warnf("Output (%v:%d) with bad address %v found: %v",
   664  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err)
   665  			continue
   666  		}
   667  		val, err := strconv.ParseFloat(utxo.Value, 64)
   668  		if err != nil {
   669  			ew.log.Warnf("Output (%v:%d) with bad value %v found: %v",
   670  				utxo.PrevOutHash, utxo.PrevOutIdx, val, err)
   671  			continue
   672  		}
   673  		var confs uint32
   674  		if height := int32(utxo.Height); height > 0 {
   675  			// height is non-zero, so confirmed, but if the RPCs are
   676  			// inconsistent with respect to height, avoid an underflow or
   677  			// appearing unconfirmed.
   678  			if height > chainHeight {
   679  				confs = 1
   680  			} else {
   681  				confs = uint32(chainHeight - height + 1)
   682  			}
   683  		}
   684  		redeemScript, err := hex.DecodeString(utxo.RedeemScript)
   685  		if err != nil {
   686  			ew.log.Warnf("Output (%v:%d) with bad redeemscript %v found: %v",
   687  				utxo.PrevOutHash, utxo.PrevOutIdx, utxo.RedeemScript, err)
   688  			continue
   689  		}
   690  
   691  		unspents = append(unspents, &ListUnspentResult{
   692  			TxID:          utxo.PrevOutHash,
   693  			Vout:          utxo.PrevOutIdx,
   694  			Address:       utxo.Address,
   695  			ScriptPubKey:  pkScript,
   696  			Amount:        val,
   697  			Confirmations: confs,
   698  			RedeemScript:  redeemScript,
   699  			Spendable:     true, // can electrum have unspendable?
   700  			Solvable:      true,
   701  			// Safe is unknown, leave ptr nil
   702  		})
   703  	}
   704  	return unspents, nil
   705  }
   706  
   707  // part of btc.Wallet interface
   708  func (ew *electrumWallet) LockUnspent(unlock bool, ops []*Output) error {
   709  	eUnspent, err := ew.wallet.ListUnspent(ew.ctx)
   710  	if err != nil {
   711  		return err
   712  	}
   713  	opMap := make(map[OutPoint]struct{}, len(ops))
   714  	for _, op := range ops {
   715  		opMap[op.Pt] = struct{}{}
   716  	}
   717  	// For the ones that appear in listunspent, use (un)freeze_utxo also.
   718  unspents:
   719  	for _, utxo := range eUnspent {
   720  		for op := range opMap {
   721  			if op.Vout == utxo.PrevOutIdx && op.TxHash.String() == utxo.PrevOutHash {
   722  				// FreezeUTXO and UnfreezeUTXO do not error when called
   723  				// repeatedly for the same UTXO.
   724  				if unlock {
   725  					if err = ew.wallet.UnfreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil {
   726  						ew.log.Warnf("UnfreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err)
   727  						// Maybe we lost a race somewhere. Keep going.
   728  					}
   729  					ew.lockedOutpointsMtx.Lock()
   730  					delete(ew.lockedOutpoints, op)
   731  					ew.lockedOutpointsMtx.Unlock()
   732  					delete(opMap, op)
   733  					continue unspents
   734  				}
   735  
   736  				if err = ew.wallet.FreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil {
   737  					ew.log.Warnf("FreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err)
   738  				}
   739  				// listunspent returns locked utxos, so we have to track it.
   740  				ew.lockedOutpointsMtx.Lock()
   741  				ew.lockedOutpoints[op] = struct{}{}
   742  				ew.lockedOutpointsMtx.Unlock()
   743  				delete(opMap, op)
   744  				continue unspents
   745  			}
   746  		}
   747  	}
   748  
   749  	// If not in the listunspent response, fail if trying to lock, otherwise
   750  	// just remove them from the lockedOutpoints map (unlocking spent UTXOs).
   751  	if len(opMap) > 0 && !unlock {
   752  		return fmt.Errorf("failed to lock some utxos")
   753  	}
   754  	for op := range opMap {
   755  		ew.lockedOutpointsMtx.Lock()
   756  		delete(ew.lockedOutpoints, op)
   757  		ew.lockedOutpointsMtx.Unlock()
   758  	}
   759  
   760  	return nil
   761  }
   762  
   763  func (ew *electrumWallet) listLockedOutpoints() []*RPCOutpoint {
   764  	ew.lockedOutpointsMtx.RLock()
   765  	defer ew.lockedOutpointsMtx.RUnlock()
   766  	locked := make([]*RPCOutpoint, 0, len(ew.lockedOutpoints))
   767  	for op := range ew.lockedOutpoints {
   768  		locked = append(locked, &RPCOutpoint{
   769  			TxID: op.TxHash.String(),
   770  			Vout: op.Vout,
   771  		})
   772  	}
   773  	return locked
   774  }
   775  
   776  // part of btc.Wallet interface
   777  func (ew *electrumWallet) ListLockUnspent() ([]*RPCOutpoint, error) {
   778  	return ew.listLockedOutpoints(), nil
   779  }
   780  
   781  // ExternalAddress creates a fresh address beyond the default gap limit, so it
   782  // should be used immediately. Part of btc.Wallet interface.
   783  func (ew *electrumWallet) ExternalAddress() (btcutil.Address, error) {
   784  	addr, err := ew.wallet.GetUnusedAddress(ew.ctx)
   785  	if err != nil {
   786  		return nil, err
   787  	}
   788  	return ew.decodeAddr(addr, ew.chainParams)
   789  }
   790  
   791  // ChangeAddress creates a fresh address beyond the default gap limit, so it
   792  // should be used immediately. Part of btc.Wallet interface.
   793  func (ew *electrumWallet) ChangeAddress() (btcutil.Address, error) {
   794  	return ew.ExternalAddress() // sadly, cannot request internal addresses
   795  }
   796  
   797  // part of btc.Wallet interface
   798  func (ew *electrumWallet) SignTx(inTx *wire.MsgTx) (*wire.MsgTx, error) {
   799  	// If the wallet's signtransaction RPC ever has a problem with the PSBT, we
   800  	// could attempt to sign the transaction ourselves by pulling the inputs'
   801  	// private keys and using txscript manually, but this can vary greatly
   802  	// between assets.
   803  
   804  	packet, err := psbt.NewFromUnsignedTx(inTx)
   805  	if err != nil {
   806  		return nil, err
   807  	}
   808  	psbtB64, err := packet.B64Encode()
   809  	if err != nil {
   810  		return nil, err
   811  	}
   812  
   813  	signedB, err := ew.wallet.SignTx(ew.ctx, ew.walletPass(), psbtB64)
   814  	if err != nil {
   815  		return nil, err
   816  	}
   817  	return msgTxFromBytes(signedB)
   818  }
   819  
   820  type hash160er interface {
   821  	Hash160() *[20]byte
   822  }
   823  
   824  type pubKeyer interface {
   825  	PubKey() *btcec.PublicKey
   826  }
   827  
   828  // part of btc.Wallet interface
   829  func (ew *electrumWallet) PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) {
   830  	addrDec, err := ew.decodeAddr(addr, ew.chainParams)
   831  	if err != nil {
   832  		return nil, err
   833  	}
   834  	wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, ew.walletPass(), addr)
   835  	if err != nil {
   836  		return nil, err
   837  	}
   838  	wif, err := btcutil.DecodeWIF(wifStr)
   839  	if err != nil {
   840  		return nil, err
   841  	} // wif.PrivKey is the result
   842  
   843  	// Sanity check that PrivKey corresponds to the pubkey(hash).
   844  	var pkh []byte
   845  	switch addrT := addrDec.(type) {
   846  	case pubKeyer: // e.g. *btcutil.AddressPubKey:
   847  		// Get same format as wif.SerializePubKey()
   848  		var pk []byte
   849  		if wif.CompressPubKey {
   850  			pk = addrT.PubKey().SerializeCompressed()
   851  		} else {
   852  			pk = addrT.PubKey().SerializeUncompressed()
   853  		}
   854  		pkh = btcutil.Hash160(pk) // addrT.ScriptAddress() would require SetFormat(compress/uncompress)
   855  	case *btcutil.AddressScriptHash, *btcutil.AddressWitnessScriptHash:
   856  		return wif.PrivKey, nil // assume unknown redeem script references this pubkey
   857  	case hash160er: // p2pkh and p2wpkh
   858  		pkh = addrT.Hash160()[:]
   859  	}
   860  	wifPKH := btcutil.Hash160(wif.SerializePubKey())
   861  	if !bytes.Equal(pkh, wifPKH) {
   862  		return nil, errors.New("pubkey mismatch")
   863  	}
   864  	return wif.PrivKey, nil
   865  }
   866  
   867  func (ew *electrumWallet) pass() (pw string, unlocked bool) {
   868  	ew.pwMtx.RLock()
   869  	defer ew.pwMtx.RUnlock()
   870  	return ew.pw, ew.unlocked
   871  }
   872  
   873  func (ew *electrumWallet) testPass(pw []byte) error {
   874  	addr, err := ew.wallet.GetUnusedAddress(ew.ctx)
   875  	if err != nil {
   876  		return err
   877  	}
   878  	wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, string(pw), addr)
   879  	if err != nil {
   880  		// When providing a password to an unprotected wallet, and other cases,
   881  		// a cryptic error containing "incorrect padding" is returned.
   882  		if strings.Contains(strings.ToLower(err.Error()), "incorrect padding") {
   883  			return errors.New("incorrect password (no password required?)")
   884  		}
   885  		return fmt.Errorf("GetPrivateKeys: %v", err)
   886  	}
   887  	// That should be enough, but validate the returned keys in case they are
   888  	// empty or invalid.
   889  	if _, err = btcutil.DecodeWIF(wifStr); err != nil {
   890  		return fmt.Errorf("DecodeWIF: %v", err)
   891  	}
   892  	return nil
   893  }
   894  
   895  // WalletLock locks the wallet. Part of the btc.Wallet interface.
   896  func (ew *electrumWallet) WalletLock() error {
   897  	ew.pwMtx.Lock()
   898  	defer ew.pwMtx.Unlock()
   899  	if ew.pw == "" && ew.unlocked {
   900  		// This is an unprotected wallet (can't actually lock it). But confirm
   901  		// the password is still empty in case it changed externally.
   902  		if err := ew.testPass([]byte{}); err == nil {
   903  			return nil
   904  		} // must have changed! "lock" it!
   905  	}
   906  	ew.pw, ew.unlocked = "", false
   907  	return nil
   908  }
   909  
   910  // Locked indicates if the wallet has been unlocked. Part of the btc.Wallet
   911  // interface.
   912  func (ew *electrumWallet) Locked() bool {
   913  	ew.pwMtx.RLock()
   914  	defer ew.pwMtx.RUnlock()
   915  	return !ew.unlocked
   916  }
   917  
   918  // walletPass returns the wallet passphrase. Since an empty password is valid,
   919  // use pass or locked to determine if locked. This is for convenience.
   920  func (ew *electrumWallet) walletPass() string {
   921  	pw, _ := ew.pass()
   922  	return pw
   923  }
   924  
   925  // WalletUnlock attempts to unlock the wallet with the provided password. On
   926  // success, the password is stored and may be accessed via pass or walletPass.
   927  // Part of the btc.Wallet interface.
   928  func (ew *electrumWallet) WalletUnlock(pw []byte) error {
   929  	if err := ew.testPass(pw); err != nil {
   930  		return err
   931  	}
   932  	ew.pwMtx.Lock()
   933  	ew.pw, ew.unlocked = string(pw), true
   934  	ew.pwMtx.Unlock()
   935  	return nil
   936  }
   937  
   938  // part of the btc.Wallet interface
   939  func (ew *electrumWallet) PeerCount() (uint32, error) {
   940  	if ew.chain() == nil { // must work prior to resetChain
   941  		return 0, nil
   942  	}
   943  
   944  	info, err := ew.wallet.GetInfo(ew.ctx)
   945  	if err != nil {
   946  		return 0, err
   947  	}
   948  	select {
   949  	case <-ew.chain().Done():
   950  		return 0, errors.New("electrumx server connection down")
   951  	default:
   952  	}
   953  
   954  	return uint32(info.Connections), nil
   955  }
   956  
   957  // part of the btc.Wallet interface
   958  func (ew *electrumWallet) OwnsAddress(addr btcutil.Address) (bool, error) {
   959  	addrStr, err := ew.stringAddr(addr, ew.chainParams)
   960  	if err != nil {
   961  		return false, err
   962  	}
   963  	valid, mine, err := ew.wallet.CheckAddress(ew.ctx, addrStr)
   964  	if err != nil {
   965  		return false, err
   966  	}
   967  	if !valid { // maybe electrum doesn't know all encodings that btcutil does
   968  		return false, nil // an error here may prevent reconfiguring a misconfigured wallet
   969  	}
   970  	return mine, nil
   971  }
   972  
   973  // part of the btc.Wallet interface
   974  func (ew *electrumWallet) SyncStatus() (*asset.SyncStatus, error) {
   975  	info, err := ew.wallet.GetInfo(ew.ctx)
   976  	if err != nil {
   977  		return nil, err
   978  	}
   979  	return &asset.SyncStatus{
   980  		Synced:       info.Connected && info.SyncHeight >= info.ServerHeight,
   981  		TargetHeight: uint64(info.ServerHeight),
   982  		Blocks:       uint64(info.SyncHeight),
   983  	}, nil
   984  }
   985  
   986  // part of the btc.Wallet interface
   987  func (ew *electrumWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) {
   988  	bestHeight, err := ew.GetBestBlockHeight()
   989  	if err != nil {
   990  		return nil, fmt.Errorf("error getting best block: %v", err)
   991  	}
   992  	if bestHeight < blockHeight {
   993  		return nil, nil
   994  	}
   995  	var txs []*ListTransactionsResult
   996  	from := int64(blockHeight)
   997  	to := int64(blockHeight + 2000)
   998  	for {
   999  		if to > int64(bestHeight) {
  1000  			to = int64(bestHeight)
  1001  		}
  1002  		addTxs, err := ew.wallet.OnchainHistory(ew.ctx, from, to)
  1003  		if err != nil {
  1004  			return nil, fmt.Errorf("error getting onchain history: %v", err)
  1005  		}
  1006  		for _, tx := range addTxs {
  1007  			ltr := &ListTransactionsResult{
  1008  				BlockHeight: uint32(tx.Height),
  1009  				BlockTime:   uint64(tx.Timestamp), // Maybe?
  1010  				Send:        !tx.Incoming,
  1011  				TxID:        tx.TxID,
  1012  			}
  1013  			if tx.Fee != nil {
  1014  				f, err := strconv.ParseFloat(*tx.Fee, 64)
  1015  				if err != nil {
  1016  					return nil, fmt.Errorf("error parsing fee: %v", err)
  1017  				}
  1018  				ltr.Fee = &f
  1019  			}
  1020  			txs = append(txs, ltr)
  1021  		}
  1022  		if to == int64(bestHeight) {
  1023  			break
  1024  		}
  1025  		from = to
  1026  		to += 2000
  1027  	}
  1028  	return txs, nil
  1029  }
  1030  
  1031  // checkWalletTx will get the bytes and confirmations of a wallet transaction.
  1032  // For non-wallet transactions, it is normal to see "Exception: Transaction not
  1033  // in wallet" in Electrum's parent console, if launched from a terminal.
  1034  // Part of the walletTxChecker interface.
  1035  func (ew *electrumWallet) checkWalletTx(txid string) ([]byte, uint32, error) {
  1036  	// GetWalletTxConfs only works for wallet transactions, while
  1037  	// wallet.GetRawTransaction will try the wallet DB first, but fall back to
  1038  	// querying a server, so do GetWalletTxConfs first to prevent that.
  1039  	confs, err := ew.wallet.GetWalletTxConfs(ew.ctx, txid)
  1040  	if err != nil {
  1041  		return nil, 0, err
  1042  	}
  1043  	txRaw, err := ew.wallet.GetRawTransaction(ew.ctx, txid)
  1044  	if err != nil {
  1045  		return nil, 0, err
  1046  	}
  1047  	if confs < 0 {
  1048  		confs = 0
  1049  	}
  1050  	return txRaw, uint32(confs), nil
  1051  }
  1052  
  1053  // part of the walletTxChecker interface
  1054  func (ew *electrumWallet) GetWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) {
  1055  	// Try the wallet first. If it is not a wallet transaction or if it is
  1056  	// confirmed, fall back to the chain method to get the block info and time
  1057  	// fields.
  1058  	txid := txHash.String()
  1059  	txRaw, confs, err := ew.checkWalletTx(txid)
  1060  	if err == nil && confs == 0 {
  1061  		return &GetTransactionResult{
  1062  			TxID:  txid,
  1063  			Bytes: txRaw,
  1064  			// Time/TimeReceived? now? needed?
  1065  		}, nil
  1066  	} // else we have to ask a server for the verbose response with block info
  1067  
  1068  	txInfo, err := ew.chain().GetTransaction(ew.ctx, txid)
  1069  	if err != nil {
  1070  		return nil, err
  1071  	}
  1072  	txRaw, err = hex.DecodeString(txInfo.Hex)
  1073  	if err != nil {
  1074  		return nil, err
  1075  	}
  1076  	return &GetTransactionResult{
  1077  		Confirmations: uint64(txInfo.Confirmations),
  1078  		BlockHash:     txInfo.BlockHash,
  1079  		// BlockIndex unknown
  1080  		BlockTime:    uint64(txInfo.BlockTime),
  1081  		TxID:         txInfo.TxID, // txHash.String()
  1082  		Time:         uint64(txInfo.Time),
  1083  		TimeReceived: uint64(txInfo.Time),
  1084  		Bytes:        txRaw,
  1085  	}, nil
  1086  }
  1087  
  1088  func (ew *electrumWallet) Fingerprint() (string, error) {
  1089  	return "", fmt.Errorf("fingerprint not implemented")
  1090  }
  1091  
  1092  // part of the walletTxChecker interface
  1093  func (ew *electrumWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) {
  1094  	// To determine if it is spent, we need the address of the output.
  1095  	var pkScript []byte
  1096  	txid := txHash.String()
  1097  	// Try the wallet first in case this is a wallet transaction (own swap).
  1098  	txRaw, confs, err := ew.checkWalletTx(txid)
  1099  	if err == nil {
  1100  		msgTx, err := msgTxFromBytes(txRaw)
  1101  		if err != nil {
  1102  			return 0, false, err
  1103  		}
  1104  		if vout >= uint32(len(msgTx.TxOut)) {
  1105  			return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1106  		}
  1107  		pkScript = msgTx.TxOut[vout].PkScript
  1108  	} else {
  1109  		// Fall back to the more expensive server request.
  1110  		txInfo, err := ew.chain().GetTransaction(ew.ctx, txid)
  1111  		if err != nil {
  1112  			return 0, false, err
  1113  		}
  1114  		confs = uint32(txInfo.Confirmations)
  1115  		if txInfo.Confirmations < 1 {
  1116  			confs = 0
  1117  		}
  1118  		if vout >= uint32(len(txInfo.Vout)) {
  1119  			return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1120  		}
  1121  		txOut := &txInfo.Vout[vout]
  1122  		pkScript, err = hex.DecodeString(txOut.PkScript.Hex)
  1123  		if err != nil {
  1124  			return 0, false, fmt.Errorf("invalid pkScript: %w", err)
  1125  		}
  1126  	}
  1127  
  1128  	spent, err = ew.outputIsSpent(ew.ctx, txHash, vout, pkScript)
  1129  	if err != nil {
  1130  		return 0, false, err
  1131  	}
  1132  	return confs, spent, nil
  1133  }
  1134  
  1135  // tryRemoveLocalTx attempts to remove a "local" transaction from the Electrum
  1136  // wallet. Such a transaction is unbroadcasted. This may be necessary if a
  1137  // broadcast of a local txn attempt failed so that the inputs are available for
  1138  // other transactions.
  1139  func (ew *electrumWallet) tryRemoveLocalTx(ctx context.Context, txid string) {
  1140  	if err := ew.wallet.RemoveLocalTx(ctx, txid); err != nil {
  1141  		ew.log.Errorf("Failed to remove local transaction %s: %v",
  1142  			txid, err)
  1143  	}
  1144  }
  1145  
  1146  func (ew *electrumWallet) outPointAddress(ctx context.Context, txid string, vout uint32) (string, error) {
  1147  	txRaw, err := ew.wallet.GetRawTransaction(ctx, txid)
  1148  	if err != nil {
  1149  		return "", err
  1150  	}
  1151  	msgTx, err := msgTxFromBytes(txRaw)
  1152  	if err != nil {
  1153  		return "", err
  1154  	}
  1155  	if vout >= uint32(len(msgTx.TxOut)) {
  1156  		return "", fmt.Errorf("output %d of tx %v does not exists", vout, txid)
  1157  	}
  1158  	pkScript := msgTx.TxOut[vout].PkScript
  1159  	_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams)
  1160  	if err != nil {
  1161  		return "", fmt.Errorf("invalid pkScript: %v", err)
  1162  	}
  1163  	if len(addrs) != 1 {
  1164  		return "", fmt.Errorf("invalid pkScript: %d addresses", len(addrs))
  1165  	}
  1166  	addrStr, err := ew.stringAddr(addrs[0], ew.chainParams)
  1167  	if err != nil {
  1168  		return "", err
  1169  	}
  1170  	return addrStr, nil
  1171  }
  1172  
  1173  func (ew *electrumWallet) findOutputSpender(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.MsgTx, uint32, error) {
  1174  	txid := txHash.String()
  1175  	addr, err := ew.outPointAddress(ctx, txid, vout)
  1176  	if err != nil {
  1177  		return nil, 0, fmt.Errorf("invalid outpoint address: %w", err)
  1178  	}
  1179  	// NOTE: Caller should already have determined the output is spent before
  1180  	// requesting the entire address history.
  1181  	hist, err := ew.wallet.GetAddressHistory(ctx, addr)
  1182  	if err != nil {
  1183  		return nil, 0, fmt.Errorf("unable to get address history: %w", err)
  1184  	}
  1185  
  1186  	sort.Slice(hist, func(i, j int) bool {
  1187  		return hist[i].Height > hist[j].Height // descending
  1188  	})
  1189  
  1190  	var outHeight int64
  1191  	for _, io := range hist {
  1192  		if io.TxHash == txid {
  1193  			outHeight = io.Height
  1194  			continue // same txn
  1195  		}
  1196  		if io.Height < outHeight {
  1197  			break // spender not before the output's txn
  1198  		}
  1199  		txRaw, err := ew.wallet.GetRawTransaction(ctx, io.TxHash)
  1200  		if err != nil {
  1201  			ew.log.Warnf("Unable to retrieve transaction %v for address %v: %v",
  1202  				io.TxHash, addr, err)
  1203  			continue
  1204  		}
  1205  		msgTx, err := msgTxFromBytes(txRaw)
  1206  		if err != nil {
  1207  			ew.log.Warnf("Unable to decode transaction %v for address %v: %v",
  1208  				io.TxHash, addr, err)
  1209  			continue
  1210  		}
  1211  		for vin, txIn := range msgTx.TxIn {
  1212  			prevOut := &txIn.PreviousOutPoint
  1213  			if vout == prevOut.Index && prevOut.Hash.IsEqual(txHash) {
  1214  				return msgTx, uint32(vin), nil
  1215  			}
  1216  		}
  1217  	}
  1218  
  1219  	return nil, 0, nil // caller should check msgTx (internal method)
  1220  }
  1221  
  1222  func (ew *electrumWallet) AddressUsed(addrStr string) (bool, error) {
  1223  	txs, err := ew.wallet.GetAddressHistory(ew.ctx, addrStr)
  1224  	if err != nil {
  1225  		return false, fmt.Errorf("error getting address history: %w", err)
  1226  	}
  1227  	return len(txs) > 0, nil
  1228  }