decred.org/dcrdex@v1.0.5/client/asset/eth/multirpc.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  // https://ethereumnodes.com/ for RPC providers
     5  
     6  package eth
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"math/big"
    14  	"math/rand"
    15  	"net"
    16  	"net/url"
    17  	"os"
    18  	"path/filepath"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"decred.org/dcrdex/client/asset"
    27  	"decred.org/dcrdex/dex"
    28  	"decred.org/dcrdex/dex/networks/erc20"
    29  	dexeth "decred.org/dcrdex/dex/networks/eth"
    30  	"github.com/ethereum/go-ethereum"
    31  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    32  	"github.com/ethereum/go-ethereum/common"
    33  	"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
    34  	"github.com/ethereum/go-ethereum/core/txpool"
    35  	"github.com/ethereum/go-ethereum/core/types"
    36  	"github.com/ethereum/go-ethereum/eth/ethconfig"
    37  	"github.com/ethereum/go-ethereum/ethclient"
    38  	"github.com/ethereum/go-ethereum/params"
    39  	"github.com/ethereum/go-ethereum/rpc"
    40  )
    41  
    42  const (
    43  	// failQuarantine is how long we will wait after a failed request before
    44  	// trying a provider again.
    45  	failQuarantine = time.Minute
    46  	// headerCheckInterval is the time between header checks. Slightly less
    47  	// than the fail quarantine to ensure providers with old headers stay
    48  	// quarantined.
    49  	headerCheckInterval = time.Second * 50
    50  	// receiptCacheExpiration is how long we will track a receipt after the
    51  	// last request. There is no persistent storage, so all receipts are cached
    52  	// in-memory.
    53  	receiptCacheExpiration       = time.Hour
    54  	unconfirmedReceiptExpiration = time.Minute
    55  	tipCapSuggestionExpiration   = time.Hour
    56  	brickedFailCount             = 100
    57  	providerDelimiter            = " "
    58  	// Infura and Rivet (basic plans) seem to have a 15 second delay for 1)
    59  	// initializing websocket connection, or 2) the first eth_chainId request on
    60  	// HTTPS, but not for other requests.
    61  	// TODO: Keep a file mapping provider URL to retrieved chain IDs, and skip
    62  	// the eth_chainId request after verified for the first time?
    63  	defaultRequestTimeout = time.Second * 10
    64  )
    65  
    66  var (
    67  	// nonceProviderStickiness is the minimum amount of time that must pass
    68  	// between requests to DIFFERENT nonce providers. If we use a provider for a
    69  	// nonce-sensitive (NS) operation, and later have another NS operation, we
    70  	// will use the same provider if < nonceProviderStickiness has passed.
    71  	nonceProviderStickiness = time.Minute
    72  	// By default, connectProviders will attempt to get a WebSockets endpoint
    73  	// when given an HTTP(S) provider URL. Can be disabled for testing
    74  	// ((*MRPCTest).TestRPC).
    75  	forceTryWS = true
    76  	// https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113
    77  	// ultimately results in a minimum fee rate by the filter applied at
    78  	// https://github.com/ethereum/go-ethereum/blob/4ebeca19d739a243dc0549bcaf014946cde95c4f/core/tx_pool.go#L626
    79  	minGasPrice = ethconfig.Defaults.Miner.GasPrice
    80  )
    81  
    82  // TODO: Handle rate limiting? From the docs:
    83  // When you are rate limited, your JSON-RPC responses have HTTP Status code 429.
    84  // I don't think we have access to these codes through ethclient.Client, but
    85  // I haven't verified that.
    86  
    87  // The suggested tip cap is expected to be very-slowly changing. We'll only
    88  // update once per tipCapSuggestionExpiration.
    89  type cachedTipCap struct {
    90  	cap   *big.Int
    91  	stamp time.Time
    92  }
    93  
    94  type combinedRPCClient struct {
    95  	*ethclient.Client
    96  	rpc *rpc.Client
    97  }
    98  
    99  type provider struct {
   100  	// host is the domain and tld of the provider, and is used as a identifier
   101  	// in logs and as a unique, path- and subdomain-independent ID for e.g. map
   102  	// keys.
   103  	host         string
   104  	endpointAddr string
   105  	ec           *combinedRPCClient
   106  	ws           bool
   107  	chainID      *big.Int
   108  	net          dex.Network
   109  	tipCapV      atomic.Value // *cachedTipCap
   110  	stop         func()
   111  
   112  	// tip tracks the best known header as well as any error encountered
   113  	tip struct {
   114  		sync.RWMutex
   115  		header       *types.Header
   116  		headerStamp  time.Time
   117  		failStamp    time.Time
   118  		failCount    int
   119  		wsHeaderSeen atomic.Bool
   120  	}
   121  }
   122  
   123  // String returns the provider host name.
   124  func (p *provider) String() string {
   125  	return p.host
   126  }
   127  
   128  func (p *provider) shutdown() {
   129  	p.stop()
   130  	p.ec.Close()
   131  }
   132  
   133  func (p *provider) setTip(header *types.Header, log dex.Logger) {
   134  	p.tip.Lock()
   135  	p.tip.header = header
   136  	p.tip.headerStamp = time.Now()
   137  	p.tip.failStamp = time.Time{}
   138  	unfailed := p.tip.failCount != 0
   139  	p.tip.failCount = 0
   140  	p.tip.Unlock()
   141  	if unfailed {
   142  		log.Debugf("Provider at %s was failed but is now useable again.", p.host)
   143  	}
   144  }
   145  
   146  // cachedTip retrieves the last known best header.
   147  func (p *provider) cachedTip() *types.Header {
   148  	stale := time.Second * 10
   149  	if p.tip.wsHeaderSeen.Load() {
   150  		// We want to avoid requests, and we expect that our notification feed
   151  		// is working. Setting this too low would result in unnecessary requests
   152  		// when notifications are working right. Setting this too high will
   153  		// inevitably result in long tip change intervals if notifications fail.
   154  		stale = time.Minute
   155  	}
   156  
   157  	p.tip.RLock()
   158  	defer p.tip.RUnlock()
   159  
   160  	if time.Since(p.tip.failStamp) < failQuarantine || time.Since(p.tip.headerStamp) > stale {
   161  		return nil
   162  	}
   163  	return p.tip.header
   164  }
   165  
   166  // setFailed should be called after a failed request, the provider is considered
   167  // failed for failQuarantine.
   168  func (p *provider) setFailed() {
   169  	p.tip.Lock()
   170  	p.tip.failStamp = time.Now()
   171  	p.tip.failCount++
   172  	p.tip.Unlock()
   173  }
   174  
   175  // failed will be true if setFailed has been called in the last failQuarantine.
   176  func (p *provider) failed() bool {
   177  	p.tip.Lock()
   178  	defer p.tip.Unlock()
   179  	return p.tip.failCount > brickedFailCount || time.Since(p.tip.failStamp) < failQuarantine
   180  }
   181  
   182  // bestHeader get the best known header from the provider, cached if available,
   183  // otherwise a new RPC call is made.
   184  func (p *provider) bestHeader(ctx context.Context, log dex.Logger) (*types.Header, error) {
   185  	// Check if we have a cached header.
   186  	if tip := p.cachedTip(); tip != nil {
   187  		log.Tracef("Using cached header from %q", p.host)
   188  		return tip, nil
   189  	}
   190  
   191  	log.Tracef("Fetching fresh header from %q", p.host)
   192  	hdr, err := p.ec.HeaderByNumber(ctx, nil /* latest */)
   193  	if err != nil {
   194  		p.setFailed()
   195  		return nil, fmt.Errorf("HeaderByNumber error: %w", err)
   196  	}
   197  	timeDiff := time.Now().Unix() - int64(hdr.Time)
   198  	if timeDiff > dexeth.MaxBlockInterval && p.net != dex.Simnet {
   199  		p.setFailed()
   200  		return nil, fmt.Errorf("time since last block (%d sec) exceeds %d sec. "+
   201  			"Assuming provider %s is not in sync. Ensure your computer's system clock "+
   202  			"is correct", timeDiff, dexeth.MaxBlockInterval, p.host)
   203  	}
   204  	p.setTip(hdr, log)
   205  	return hdr, nil
   206  }
   207  
   208  func (p *provider) headerByHash(ctx context.Context, h common.Hash) (*types.Header, error) {
   209  	hdr, err := p.ec.HeaderByHash(ctx, h)
   210  	if err != nil {
   211  		p.setFailed()
   212  		return nil, fmt.Errorf("HeaderByHash error: %w", err)
   213  	}
   214  	return hdr, nil
   215  }
   216  
   217  // suggestTipCap returns a tip cap suggestion, cached if available, otherwise a
   218  // new RPC call is made.
   219  func (p *provider) suggestTipCap(ctx context.Context, log dex.Logger) *big.Int {
   220  	if cachedV := p.tipCapV.Load(); cachedV != nil {
   221  		rec := cachedV.(*cachedTipCap)
   222  		if time.Since(rec.stamp) < tipCapSuggestionExpiration {
   223  			return rec.cap
   224  		}
   225  	}
   226  	tipCap, err := p.ec.SuggestGasTipCap(ctx)
   227  	if err != nil {
   228  		p.setFailed()
   229  		log.Errorf("error getting tip cap suggestion from %q: %v", p.host, err)
   230  		return dexeth.GweiToWei(dexeth.MinGasTipCap)
   231  	}
   232  
   233  	minGasTipCapWei := dexeth.GweiToWei(dexeth.MinGasTipCap)
   234  	if tipCap.Cmp(minGasTipCapWei) < 0 {
   235  		return tipCap.Set(minGasTipCapWei)
   236  	}
   237  
   238  	p.tipCapV.Store(&cachedTipCap{
   239  		cap:   tipCap,
   240  		stamp: time.Now(),
   241  	})
   242  
   243  	return tipCap
   244  }
   245  
   246  // refreshHeader fetches a header every headerCheckInterval. This keeps the
   247  // cached header up to date or fails the provider if there is a problem getting
   248  // the header.
   249  func (p *provider) refreshHeader(ctx context.Context, log dex.Logger) {
   250  	log.Tracef("handling header refreshes for %q", p.host)
   251  	ticker := time.NewTicker(headerCheckInterval)
   252  	defer ticker.Stop()
   253  	for {
   254  		select {
   255  		case <-ticker.C:
   256  			// Fetching the best header will check that either the
   257  			// provider's cached header is not too old or that a
   258  			// newly fetched header is not too old. If it is too
   259  			// old that indicates the provider is not in sync and
   260  			// should not be used.
   261  			innerCtx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
   262  			if _, err := p.bestHeader(innerCtx, log); err != nil {
   263  				log.Warnf("Problem getting best header from provider %s: %s.", p.host, err)
   264  			}
   265  			cancel()
   266  		case <-ctx.Done():
   267  			return
   268  		}
   269  	}
   270  }
   271  
   272  // subscribeHeaders starts a listening loop for header updates for a provider.
   273  // The Subscription and header chan are passed in, because error-free
   274  // instantiation of these variable is necessary to accepting that a websocket
   275  // connection is valid, so they are generated early in connectProviders.
   276  func (p *provider) subscribeHeaders(ctx context.Context, sub ethereum.Subscription, h chan *types.Header, log dex.Logger) {
   277  	defer func() {
   278  		// If a provider does not respond to an unsubscribe request, the unsubscribe function
   279  		// will never return because geth does not use a timeout.
   280  		doneUnsubbing := make(chan struct{})
   281  		go func() {
   282  			sub.Unsubscribe()
   283  			close(doneUnsubbing)
   284  		}()
   285  		select {
   286  		case <-doneUnsubbing:
   287  		case <-time.After(defaultRequestTimeout):
   288  			log.Errorf("Timed out waiting to unsubscribe from %q", p.host)
   289  		}
   290  	}()
   291  
   292  	var lastWarning time.Time
   293  	newSub := func() (ethereum.Subscription, error) {
   294  		for {
   295  			var err error
   296  			sub, err = p.ec.SubscribeNewHead(ctx, h)
   297  			if err == nil {
   298  				return sub, nil
   299  			}
   300  			if time.Since(lastWarning) > 5*time.Minute {
   301  				log.Warnf("can't resubscribe to %q headers: %v", p.host, err)
   302  			}
   303  			select {
   304  			case <-time.After(time.Second * 30):
   305  				log.Debugf("attempting to resubscribe to websocket headers from %s", p.host)
   306  			case <-ctx.Done():
   307  				return nil, context.Canceled
   308  			}
   309  		}
   310  	}
   311  
   312  	// I thought the filter logs might catch some transactions we could cache
   313  	// to avoid rpc calls, but in testing, I get nothing in the channel. May
   314  	// revisit later.
   315  	// logs := make(chan types.Log, 128)
   316  	// newAcctSub := func(retryTimeout time.Duration) ethereum.Subscription {
   317  	// 	config := ethereum.FilterQuery{
   318  	// 		Addresses: []common.Address{addr},
   319  	// 	}
   320  
   321  	// 	acctSub, err := p.ec.SubscribeFilterLogs(ctx, config, logs)
   322  	// 	if err != nil {
   323  	// 		log.Errorf("failed to subscribe to filter logs: %v", err)
   324  	// 		return newRetrySubscription(ctx, retryTimeout)
   325  	// 	}
   326  	// 	return acctSub
   327  	// }
   328  
   329  	// // If we fail the first time, don't try again.
   330  	// acctSub := newAcctSub(time.Hour * 24 * 365)
   331  	// defer acctSub.Unsubscribe()
   332  
   333  	// Start the background filtering
   334  	log.Tracef("handling websocket subscriptions for %q", p.host)
   335  
   336  	for {
   337  		select {
   338  		case hdr := <-h:
   339  			log.Tracef("%q reported new tip at height %s (%s)", p.host, hdr.Number, hdr.Hash())
   340  			p.setTip(hdr, log)
   341  			p.tip.wsHeaderSeen.Store(true)
   342  		case err, ok := <-sub.Err():
   343  			if !ok {
   344  				// Subscription cancelled
   345  				return
   346  			}
   347  			if ctx.Err() != nil || err == nil { // Both conditions indicate normal close
   348  				return
   349  			}
   350  			log.Errorf("%q header subscription error: %v", p.host, err)
   351  			log.Infof("Attempting to resubscribe to %q block headers", p.host)
   352  			sub, err = newSub()
   353  			if err != nil { // context cancelled
   354  				return
   355  			}
   356  		// case l := <-logs:
   357  		// 	log.Tracef("%q log reported: %+v", p.host, l)
   358  		// case err, ok := <-acctSub.Err():
   359  		// 	if err != nil && !errors.Is(err, retryError) {
   360  		// 		log.Errorf("%q log subscription error: %v", p.host, err)
   361  		// 	}
   362  		// 	if ok {
   363  		// 		acctSub = newAcctSub(time.Minute * 5)
   364  		// 	}
   365  		case <-ctx.Done():
   366  			return
   367  		}
   368  	}
   369  }
   370  
   371  // receiptRecord is a cached receipt and its last-access time. Receipts are
   372  // stored in-memory for up to receiptCacheExpiration.
   373  type receiptRecord struct {
   374  	r          *types.Receipt
   375  	lastAccess time.Time
   376  	confirmed  bool
   377  }
   378  
   379  // multiRPCClient is an ethFetcher backed by one or more public RPC providers.
   380  type multiRPCClient struct {
   381  	cfg     *params.ChainConfig
   382  	creds   *accountCredentials
   383  	log     dex.Logger
   384  	chainID *big.Int
   385  	net     dex.Network
   386  
   387  	finalizeConfs uint64
   388  
   389  	providerMtx sync.RWMutex
   390  	endpoints   []string
   391  	providers   []*provider
   392  
   393  	// When we send transactions close together, we'll want to use the same
   394  	// provider.
   395  	lastProvider struct {
   396  		sync.Mutex
   397  		*provider
   398  		stamp time.Time
   399  	}
   400  
   401  	receipts struct {
   402  		sync.RWMutex
   403  		cache     map[common.Hash]*receiptRecord
   404  		lastClean time.Time
   405  	}
   406  }
   407  
   408  var _ ethFetcher = (*multiRPCClient)(nil)
   409  
   410  func newMultiRPCClient(
   411  	dir string,
   412  	endpoints []string,
   413  	log dex.Logger,
   414  	cfg *params.ChainConfig,
   415  	finalizeConfs uint64,
   416  	net dex.Network,
   417  ) (*multiRPCClient, error) {
   418  	walletDir := getWalletDir(dir, net)
   419  	creds, err := pathCredentials(filepath.Join(walletDir, "keystore"))
   420  	if err != nil {
   421  		return nil, fmt.Errorf("error parsing credentials from %q: %w", dir, err)
   422  	}
   423  
   424  	m := &multiRPCClient{
   425  		net:           net,
   426  		cfg:           cfg,
   427  		log:           log,
   428  		creds:         creds,
   429  		chainID:       cfg.ChainID,
   430  		endpoints:     endpoints,
   431  		finalizeConfs: finalizeConfs,
   432  	}
   433  	m.receipts.cache = make(map[common.Hash]*receiptRecord)
   434  	m.receipts.lastClean = time.Now()
   435  
   436  	return m, nil
   437  }
   438  
   439  // connectProviders attempts to connect to the list of endpoints, returning a
   440  // list of providers that were successfully connected. It is not an error for a
   441  // connection to fail, unless all endpoints fail. The caller can infer failed
   442  // connections from the length and contents of the returned provider list.
   443  func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, chainID *big.Int, net dex.Network) ([]*provider, error) {
   444  	providers := make([]*provider, 0, len(endpoints))
   445  	var success bool
   446  
   447  	defer func() {
   448  		if !success {
   449  			for _, p := range providers {
   450  				p.shutdown()
   451  			}
   452  		}
   453  	}()
   454  
   455  	// We'll connect concurrently, and cancel the context on the first error
   456  	// received.
   457  	ctx, cancel := context.WithCancel(ctx)
   458  	defer cancel()
   459  
   460  	// addEndpoint only returns errors that should be propagated immediately.
   461  	addEndpoint := func(endpoint string) (*provider, error) {
   462  		// Give ourselves a limited time to resolve a connection.
   463  		timedCtx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
   464  		defer cancel()
   465  		// First try to get a websocket connection. WebSockets have a header
   466  		// feed, so are much preferred to http connections. So much so, that
   467  		// we'll do some path inspection here and make an attempt to find a
   468  		// websocket server, even if the user requested http.
   469  		var ec *ethclient.Client
   470  		var rpcClient *rpc.Client
   471  		var sub ethereum.Subscription
   472  		var wsSubscribed bool
   473  		var h chan *types.Header
   474  		host := providerIPC
   475  		var scratchURL *url.URL
   476  		isIPC := strings.HasSuffix(endpoint, ".ipc")
   477  		if !isIPC {
   478  			var err error
   479  			scratchURL, err = url.Parse(endpoint)
   480  			if err != nil {
   481  				return nil, fmt.Errorf("failed to parse url %q: %w", endpoint, err)
   482  			}
   483  			host = scratchURL.Host
   484  		}
   485  		if forceTryWS && !isIPC {
   486  			wsURL := scratchURL
   487  			ogScheme := wsURL.Scheme
   488  			switch ogScheme {
   489  			case "https":
   490  				wsURL.Scheme = "wss"
   491  			case "http":
   492  				wsURL.Scheme = "ws"
   493  			case "ws", "wss":
   494  			default:
   495  				return nil, fmt.Errorf("unknown scheme for endpoint %q: %q, expected any of: ws(s)/http(s)",
   496  					endpoint, wsURL.Scheme)
   497  			}
   498  			replaced := ogScheme != wsURL.Scheme
   499  
   500  			// Handle known paths.
   501  			switch {
   502  			case strings.Contains(wsURL.String(), "infura.io/v3"):
   503  				if replaced {
   504  					wsURL.Path = "/ws" + wsURL.Path
   505  				}
   506  			case strings.Contains(wsURL.Host, "rpc.rivet.cloud"):
   507  				// subdomain contains API key, so can't simply replace.
   508  				wsURL.Host = strings.Replace(wsURL.Host, ".rpc.", ".ws.", 1)
   509  				host = providerRivetCloud
   510  			}
   511  
   512  			// Some providers appear to meter websocket connections.
   513  			var err error
   514  			startTime := time.Now()
   515  			rpcClient, err = rpc.DialWebsocket(timedCtx, wsURL.String(), "")
   516  			log.Tracef("%s to connect to %s", time.Since(startTime), wsURL)
   517  			if err == nil {
   518  				ec = ethclient.NewClient(rpcClient)
   519  				h = make(chan *types.Header, 8)
   520  				sub, err = ec.SubscribeNewHead(timedCtx, h)
   521  				if err != nil {
   522  					rpcClient.Close()
   523  					ec = nil
   524  					if replaced {
   525  						log.Debugf("Connected to websocket, but headers subscription not supported. Trying HTTP")
   526  					} else {
   527  						log.Errorf("Connected to websocket, but headers subscription not supported. Attempting HTTP fallback")
   528  					}
   529  				} else {
   530  					wsSubscribed = true
   531  				}
   532  			} else {
   533  				if replaced {
   534  					log.Debugf("couldn't get a websocket connection for %q (original scheme: %q) (OK)", wsURL, ogScheme)
   535  				} else {
   536  					log.Errorf("failed to get websocket connection to %q. attempting http(s) fallback: error = %v", endpoint, err)
   537  				}
   538  			}
   539  		}
   540  		// Weren't able to get a websocket connection. Try HTTP now. Dial does
   541  		// path discrimination, so I won't even try to validate the protocol.
   542  		if ec == nil {
   543  			var err error
   544  			startTime := time.Now()
   545  			rpcClient, err = rpc.DialContext(timedCtx, endpoint)
   546  			log.Tracef("%s to connect to %s", time.Since(startTime), endpoint)
   547  			if err != nil {
   548  				log.Errorf("error creating http client for %q: %v", endpoint, err)
   549  				return nil, nil
   550  			}
   551  			ec = ethclient.NewClient(rpcClient)
   552  		}
   553  
   554  		// Chain ID seems to be metered for some providers.
   555  		startTime := time.Now()
   556  		reportedChainID, err := ec.ChainID(timedCtx)
   557  		log.Tracef("%s to fetch chain ID for verification at %s", time.Since(startTime), host)
   558  		if err != nil {
   559  			// If we can't get a header, don't use this provider.
   560  			ec.Close()
   561  			log.Errorf("Failed to get chain ID from %q: %v", endpoint, err)
   562  			return nil, nil
   563  		}
   564  		if chainID.Cmp(reportedChainID) != 0 {
   565  			ec.Close()
   566  			log.Errorf("%q reported wrong chain ID. expected %d, got %d", endpoint, chainID, reportedChainID)
   567  			return nil, nil
   568  		}
   569  
   570  		hdr, err := ec.HeaderByNumber(timedCtx, nil /* latest */)
   571  		if err != nil {
   572  			// If we can't get a header, don't use this provider.
   573  			ec.Close()
   574  			log.Errorf("Failed to get header from %q: %v", endpoint, err)
   575  			return nil, nil
   576  		}
   577  
   578  		p := &provider{
   579  			chainID:      chainID,
   580  			host:         host,
   581  			endpointAddr: endpoint,
   582  			ws:           wsSubscribed,
   583  			net:          net,
   584  			ec: &combinedRPCClient{
   585  				Client: ec,
   586  				rpc:    rpcClient,
   587  			},
   588  		}
   589  		p.setTip(hdr, log)
   590  
   591  		ctx, cancel := context.WithCancel(ctx)
   592  		var wg sync.WaitGroup
   593  
   594  		// Start websocket listen loop.
   595  		if wsSubscribed {
   596  			wg.Add(1)
   597  			go func() {
   598  				p.subscribeHeaders(ctx, sub, h, log)
   599  				wg.Done()
   600  			}()
   601  		}
   602  		wg.Add(1)
   603  		go func() {
   604  			p.refreshHeader(ctx, log)
   605  			wg.Done()
   606  		}()
   607  
   608  		p.stop = func() {
   609  			cancel()
   610  			wg.Wait()
   611  		}
   612  
   613  		return p, nil
   614  	}
   615  
   616  	type addResult struct {
   617  		provider *provider
   618  		err      error
   619  		endpoint string
   620  	}
   621  	resultChan := make(chan *addResult)
   622  	for _, endpoint := range endpoints {
   623  		go func(endpoint string) {
   624  			p, err := addEndpoint(endpoint)
   625  			resultChan <- &addResult{p, err, endpoint}
   626  		}(endpoint)
   627  	}
   628  	var resCount int
   629  	var err error
   630  out:
   631  	for {
   632  		select {
   633  		case res := <-resultChan:
   634  			resCount++
   635  			if res.provider != nil {
   636  				providers = append(providers, res.provider)
   637  			}
   638  			if res.err != nil && err == nil {
   639  				err = res.err
   640  				cancel()
   641  			}
   642  			if resCount == len(endpoints) {
   643  				break out
   644  			}
   645  		case <-ctx.Done():
   646  		}
   647  	}
   648  
   649  	if err != nil {
   650  		return nil, err
   651  	}
   652  
   653  	if len(providers) == 0 {
   654  		return nil, fmt.Errorf("failed to connect to even a single provider among: %s",
   655  			failedProviders(providers, endpoints))
   656  	}
   657  
   658  	log.Debugf("Connected with %d of %d RPC providers", len(providers), len(endpoints))
   659  
   660  	success = true
   661  	return providers, nil
   662  }
   663  
   664  func (m *multiRPCClient) connect(ctx context.Context) (err error) {
   665  	providers, err := connectProviders(ctx, m.endpoints, m.log, m.chainID, m.net)
   666  	if err != nil {
   667  		return err
   668  	}
   669  
   670  	m.providerMtx.Lock()
   671  	m.providers = providers
   672  	m.providerMtx.Unlock()
   673  
   674  	var connections int
   675  	for _, p := range m.providerList() {
   676  		if _, err := p.bestHeader(ctx, m.log); err != nil {
   677  			m.log.Errorf("Failed to synchrnoize header from %s: %v", p.host, err)
   678  		} else {
   679  			connections++
   680  		}
   681  	}
   682  	// TODO: Require at least two if all connections are non-local.
   683  	if connections == 0 {
   684  		return fmt.Errorf("no connections established")
   685  	}
   686  
   687  	go func() {
   688  		<-ctx.Done()
   689  		for _, p := range m.providerList() {
   690  			p.shutdown()
   691  		}
   692  	}()
   693  
   694  	return nil
   695  }
   696  
   697  // createAndCheckProviders creates and connects to providers. It checks that
   698  // unknown providers have a sufficient api to trade and saves good providers to
   699  // file. One bad provider or connect problem will cause this to error.
   700  func createAndCheckProviders(ctx context.Context, walletDir string, endpoints []string, chainID *big.Int,
   701  	compat *CompatibilityData, net dex.Network, log dex.Logger, allProvidersMustConnect bool) error {
   702  
   703  	var localCP map[string]bool
   704  	path := filepath.Join(walletDir, "compliant-providers.json")
   705  	b, err := os.ReadFile(path)
   706  	if err == nil {
   707  		if err := json.Unmarshal(b, &localCP); err != nil {
   708  			log.Warnf("Couldn't parse compliant providers file: %v", err)
   709  		}
   710  	} else if !errors.Is(err, os.ErrNotExist) {
   711  		log.Warnf("Error reading providers file: %v", err)
   712  	}
   713  	if localCP == nil {
   714  		localCP = make(map[string]bool)
   715  	}
   716  
   717  	var writeLocalCP bool
   718  
   719  	var unknownEndpoints []string
   720  	for _, p := range endpoints {
   721  		d, err := domain(p)
   722  		if err != nil {
   723  			log.Warnf("unable to parse domain for endpoint %s: %v", p, err)
   724  			unknownEndpoints = append(unknownEndpoints, p)
   725  			continue
   726  		}
   727  		if localCP[d] {
   728  			continue
   729  		}
   730  		writeLocalCP = true
   731  		localCP[d] = true
   732  		if _, known := compliantProviders[d]; !known {
   733  			unknownEndpoints = append(unknownEndpoints, p)
   734  		}
   735  	}
   736  
   737  	if len(unknownEndpoints) > 0 {
   738  		providers, err := connectProviders(ctx, unknownEndpoints, log, chainID, net)
   739  		if err != nil {
   740  			return fmt.Errorf("expected to successfully connect to at least 1 of these unfamiliar providers: %s",
   741  				failedProviders(providers, unknownEndpoints))
   742  		}
   743  		defer func() {
   744  			for _, p := range providers {
   745  				p.shutdown()
   746  			}
   747  		}()
   748  		if allProvidersMustConnect && len(providers) != len(unknownEndpoints) {
   749  			return fmt.Errorf("expected to successfully connect to all of these unfamiliar providers: %s",
   750  				failedProviders(providers, unknownEndpoints))
   751  		}
   752  		providers, err = checkProvidersCompliance(ctx, providers, compat, dex.Disabled /* logger is for testing only */, allProvidersMustConnect)
   753  		if err != nil {
   754  			return err
   755  		}
   756  		if len(providers) == 0 {
   757  			return fmt.Errorf("no compliant providers found among: %s", unknownEndpoints)
   758  		}
   759  	}
   760  
   761  	if writeLocalCP {
   762  		// All unknown providers were checked.
   763  		b, err := json.Marshal(localCP)
   764  		if err != nil {
   765  			return err
   766  		}
   767  		if err := os.WriteFile(path, b, 0644); err != nil {
   768  			log.Errorf("Failed to write compliant providers file: %v", err)
   769  		}
   770  	}
   771  	return nil
   772  }
   773  
   774  // failedProviders builds string message that describes provider endpoints we
   775  // tried to connect to but didn't succeed.
   776  func failedProviders(succeeded []*provider, tried []string) string {
   777  	ok := make(map[string]bool)
   778  	for _, p := range succeeded {
   779  		ok[p.endpointAddr] = true
   780  	}
   781  	notOK := make([]string, 0, len(tried)-len(succeeded))
   782  	for _, endpoint := range tried {
   783  		if !ok[endpoint] {
   784  			if d, err := domain(endpoint); err == nil {
   785  				endpoint = d
   786  			}
   787  			notOK = append(notOK, endpoint)
   788  		}
   789  	}
   790  	return strings.Join(notOK, " ")
   791  }
   792  
   793  func (m *multiRPCClient) reconfigure(ctx context.Context, endpoints []string, compat *CompatibilityData, walletDir string, defaultProviders bool) error {
   794  	if err := createAndCheckProviders(ctx, walletDir, endpoints, m.chainID, compat, m.net, m.log, !defaultProviders); err != nil {
   795  		return fmt.Errorf("create and check providers: %v", err)
   796  	}
   797  
   798  	// TODO: If endpoints haven't change, do nothing.
   799  	providers, err := connectProviders(ctx, endpoints, m.log, m.chainID, m.net)
   800  	if err != nil {
   801  		return err
   802  	}
   803  
   804  	m.providerMtx.Lock()
   805  	oldProviders := m.providers
   806  	m.providers = providers
   807  	m.endpoints = endpoints
   808  
   809  	m.providerMtx.Unlock()
   810  	for _, p := range oldProviders {
   811  		p.shutdown()
   812  	}
   813  	return nil
   814  }
   815  
   816  func (m *multiRPCClient) cachedReceipt(txHash common.Hash) *types.Receipt {
   817  	m.receipts.Lock()
   818  	defer m.receipts.Unlock()
   819  
   820  	cached := m.receipts.cache[txHash]
   821  
   822  	// Periodically clean up the receipts.
   823  	if time.Since(m.receipts.lastClean) > time.Minute*20 {
   824  		m.receipts.lastClean = time.Now()
   825  		defer func() {
   826  			for txHash, rec := range m.receipts.cache {
   827  				if time.Since(rec.lastAccess) > receiptCacheExpiration {
   828  					delete(m.receipts.cache, txHash)
   829  				}
   830  			}
   831  		}()
   832  	}
   833  
   834  	// If confirmed or if it was just fetched, return it as is.
   835  	if cached != nil {
   836  		// If the cached receipt has the requisite confirmations, it's always
   837  		// considered good and we'll just update the lastAccess stamp so we don't
   838  		// delete it from the map.
   839  		// If it's not confirmed, we never update the lastAccess stamp, which just
   840  		// serves to age out the receipt so a new one can be requested and
   841  		// confirmations checked again.
   842  		if cached.confirmed {
   843  			cached.lastAccess = time.Now()
   844  		}
   845  		if time.Since(cached.lastAccess) < unconfirmedReceiptExpiration {
   846  			return cached.r
   847  		}
   848  	}
   849  	return nil
   850  }
   851  
   852  func (m *multiRPCClient) transactionReceipt(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) {
   853  	if r = m.cachedReceipt(txHash); r != nil {
   854  		return r, nil
   855  	}
   856  	if err := m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
   857  		r, err = p.ec.TransactionReceipt(ctx, txHash)
   858  		return err
   859  	}); err != nil {
   860  		if isNotFoundError(err) {
   861  			return nil, asset.CoinNotFoundError
   862  		}
   863  		return nil, err
   864  	}
   865  	var confs uint64
   866  	if r.BlockNumber != nil {
   867  		hdr, err := m.bestHeader(ctx)
   868  		if err != nil {
   869  			return nil, fmt.Errorf("error getting best header: %v", err)
   870  		}
   871  		if tip := hdr.Number.Uint64(); tip >= r.BlockNumber.Uint64() {
   872  			confs = tip - r.BlockNumber.Uint64() + 1
   873  		}
   874  	}
   875  
   876  	m.receipts.Lock()
   877  	m.receipts.cache[txHash] = &receiptRecord{
   878  		r:          r,
   879  		lastAccess: time.Now(),
   880  		confirmed:  confs > m.finalizeConfs,
   881  	}
   882  	m.receipts.Unlock()
   883  
   884  	return r, nil
   885  
   886  }
   887  
   888  func (m *multiRPCClient) transactionAndReceipt(ctx context.Context, txHash common.Hash) (r *types.Receipt, tx *types.Transaction, err error) {
   889  	if tx, _, err = m.getTransaction(ctx, txHash); err != nil {
   890  		return nil, nil, err
   891  	}
   892  
   893  	r, err = m.transactionReceipt(ctx, txHash)
   894  	return r, tx, err
   895  }
   896  
   897  // nonce gets the best next nonce for the account.
   898  func (m *multiRPCClient) nonce(ctx context.Context) (confirmed, pending *big.Int, _ error) {
   899  	confirmed, pending = new(big.Int), new(big.Int)
   900  	return confirmed, pending, m.withAll(ctx, func(ctx context.Context, p *provider) error {
   901  		confirmedAt, err := p.ec.NonceAt(ctx, m.creds.addr, nil)
   902  		if err == nil && confirmed.Uint64() < confirmedAt {
   903  			confirmed.SetUint64(confirmedAt)
   904  		}
   905  		pendingAt, err := p.ec.PendingNonceAt(ctx, m.creds.addr)
   906  		if err == nil && pending.Uint64() < pendingAt {
   907  			pending.SetUint64(pendingAt)
   908  		}
   909  		return err
   910  	})
   911  }
   912  
   913  type rpcTransaction struct {
   914  	tx *types.Transaction
   915  	txExtraDetail
   916  }
   917  
   918  type txExtraDetail struct {
   919  	BlockNumber *string         `json:"blockNumber,omitempty"`
   920  	BlockHash   *common.Hash    `json:"blockHash,omitempty"`
   921  	From        *common.Address `json:"from,omitempty"`
   922  }
   923  
   924  func (tx *rpcTransaction) UnmarshalJSON(b []byte) error {
   925  	if err := json.Unmarshal(b, &tx.tx); err != nil {
   926  		return err
   927  	}
   928  	return json.Unmarshal(b, &tx.txExtraDetail)
   929  }
   930  
   931  func getRPCTransaction(ctx context.Context, p *provider, txHash common.Hash) (*rpcTransaction, error) {
   932  	var resp *rpcTransaction
   933  	err := p.ec.rpc.CallContext(ctx, &resp, "eth_getTransactionByHash", txHash)
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  	if resp == nil {
   938  		return nil, asset.CoinNotFoundError
   939  	}
   940  	// Just copying geth with this one.
   941  	if _, r, _ := resp.tx.RawSignatureValues(); r == nil {
   942  		return nil, fmt.Errorf("server returned transaction without signature")
   943  	}
   944  	return resp, nil
   945  }
   946  
   947  func (m *multiRPCClient) getTransaction(ctx context.Context, txHash common.Hash) (tx *types.Transaction, h int64, err error) {
   948  	return tx, h, m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
   949  		resp, err := getRPCTransaction(ctx, p, txHash)
   950  		if err != nil {
   951  			if isNotFoundError(err) {
   952  				return asset.CoinNotFoundError
   953  			}
   954  			return err
   955  		}
   956  		tx = resp.tx
   957  		if resp.BlockNumber == nil {
   958  			h = -1
   959  		} else {
   960  			bigH, ok := new(big.Int).SetString(*resp.BlockNumber, 0 /* must start with 0x */)
   961  			if !ok {
   962  				return fmt.Errorf("couldn't parse hex number %q", *resp.BlockNumber)
   963  			}
   964  			h = bigH.Int64()
   965  		}
   966  		return nil
   967  	})
   968  }
   969  
   970  func (m *multiRPCClient) getConfirmedNonce(ctx context.Context) (n uint64, err error) {
   971  	return n, m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
   972  		n, err = p.ec.PendingNonceAt(ctx, m.address())
   973  		return err
   974  	})
   975  }
   976  
   977  func (m *multiRPCClient) providerList() []*provider {
   978  	m.providerMtx.RLock()
   979  	defer m.providerMtx.RUnlock()
   980  
   981  	providers := make([]*provider, len(m.providers))
   982  	copy(providers, m.providers)
   983  	return providers
   984  }
   985  
   986  // acceptabilityFilter: When running a pick-a-provider function (withOne,
   987  // withAny, withPreferred), sometimes errors will need special handling
   988  // depending on what they are. Zero or more acceptabilityFilters can be added
   989  // to provide extra control.
   990  //
   991  //	 discard: If a filter indicates discard = true, the error will be discarded,
   992  //	   provider iteration will end immediately and a nil error will be returned.
   993  //	propagate: If a filter indicates propagate = true, provider iteration will
   994  //	  be ended and the error will be returned immediately.
   995  //	fail: If a filter indicates fail = true, the provider will be quarantined
   996  //	  and provider iteration will continue
   997  //
   998  // If false is returned for all three for all filters, the error is logged and
   999  // provider iteration will continue.
  1000  type acceptabilityFilter func(error) (discard, propagate, fail bool)
  1001  
  1002  func allRPCErrorsAreFails(err error) (discard, propagate, fail bool) {
  1003  	return false, false, true
  1004  }
  1005  
  1006  func errorFilter(err error, matches ...any) bool {
  1007  	errStr := err.Error()
  1008  	for _, mi := range matches {
  1009  		var s string
  1010  		switch m := mi.(type) {
  1011  		case string:
  1012  			s = m
  1013  		case error:
  1014  			if errors.Is(err, m) {
  1015  				return true
  1016  			}
  1017  			s = m.Error()
  1018  		}
  1019  		if strings.Contains(errStr, s) {
  1020  			return true
  1021  		}
  1022  	}
  1023  	return false
  1024  }
  1025  
  1026  // withOne runs the provider function against the providers in order until one
  1027  // succeeds or all have failed. The context used to run functions has a time
  1028  // limit equal to defaultRequestTimeout for all requests to return. If
  1029  // operations are expected to run longer than that the calling function should
  1030  // not use the altered context.
  1031  func (m *multiRPCClient) withOne(ctx context.Context, providers []*provider, f func(context.Context, *provider) error, acceptabilityFilters ...acceptabilityFilter) (superError error) {
  1032  	readyProviders := make([]*provider, 0, len(providers))
  1033  	for _, p := range providers {
  1034  		if !p.failed() {
  1035  			readyProviders = append(readyProviders, p)
  1036  		}
  1037  	}
  1038  	if len(readyProviders) == 0 {
  1039  		// Just try them all.
  1040  		m.log.Tracef("all providers in a failed state, so acting like none are")
  1041  		readyProviders = providers
  1042  	}
  1043  	for _, p := range readyProviders {
  1044  		ctx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
  1045  		err := f(ctx, p)
  1046  		cancel()
  1047  		if err == nil {
  1048  			return nil
  1049  		}
  1050  		if superError == nil {
  1051  			superError = err
  1052  		} else if err.Error() != superError.Error() {
  1053  			superError = fmt.Errorf("%v: %w", superError, err)
  1054  		}
  1055  		for _, f := range acceptabilityFilters { // use case for more than one? is it just variadic to allow 0?
  1056  			discard, propagate, fail := f(err)
  1057  			if discard {
  1058  				return nil
  1059  			}
  1060  			if propagate {
  1061  				return err
  1062  			}
  1063  			if fail {
  1064  				p.setFailed()
  1065  			}
  1066  		}
  1067  	}
  1068  	if superError == nil {
  1069  		return errors.New("all providers in a failed state")
  1070  	}
  1071  	return
  1072  }
  1073  
  1074  // withAll runs the provider function against all known providers in order of
  1075  // freshness, with any non-stale nonce provider first. This is similar to
  1076  // withPreferred, except that it does not stop after the first success. However,
  1077  // if an acceptability filter indicates to "propagate" the error (hard stop), it
  1078  // will not try all providers. withAll should only be used for actions that are
  1079  // safe to repeat, such as broadcasting a transaction or getting results for a
  1080  // read-only operation.
  1081  func (m *multiRPCClient) withAll(
  1082  	ctx context.Context,
  1083  	f func(context.Context, *provider) error,
  1084  	acceptabilityFilters ...acceptabilityFilter,
  1085  ) error {
  1086  
  1087  	var atLeastOne bool
  1088  	var errs []string
  1089  	providers := m.nonceProviderList()
  1090  	for _, p := range providers {
  1091  		if p.failed() {
  1092  			continue
  1093  		}
  1094  
  1095  		ctx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
  1096  		err := f(ctx, p)
  1097  		cancel()
  1098  		if err == nil {
  1099  			atLeastOne = true // return nil err unless a later "propagated" error says to
  1100  			continue
  1101  		}
  1102  		var discarded bool
  1103  		for i, f := range acceptabilityFilters {
  1104  			discard, propagate, fail := f(err)
  1105  			discarded = discard && (discarded || i == 0)
  1106  			if discard {
  1107  				m.log.Tracef("non-fatal provider error: %v (%T / %T)", err, err, errors.Unwrap(err))
  1108  				continue // or maybe break since what is the use case of conflicting filters?
  1109  			}
  1110  			if fail {
  1111  				p.setFailed()
  1112  			}
  1113  			if propagate {
  1114  				return err
  1115  			}
  1116  		}
  1117  		if discarded {
  1118  			atLeastOne = true
  1119  		} else {
  1120  			errs = append(errs, err.Error())
  1121  			m.log.Warnf("Failed request from %q: %v", p, err)
  1122  		}
  1123  	}
  1124  
  1125  	if atLeastOne {
  1126  		return nil
  1127  	}
  1128  
  1129  	// err will be nil if all providers were already in a failed state or if
  1130  	// atLeastOne is false.
  1131  	if errs == nil {
  1132  		return errors.New("all providers in a failed state")
  1133  	}
  1134  
  1135  	if len(errs) > 0 {
  1136  		return errors.New(strings.Join(errs, "\n"))
  1137  	}
  1138  
  1139  	return nil
  1140  }
  1141  
  1142  // withAny runs the provider function against known providers in random order
  1143  // until one succeeds or all have failed.
  1144  func (m *multiRPCClient) withAny(ctx context.Context, f func(context.Context, *provider) error, acceptabilityFilters ...acceptabilityFilter) error {
  1145  	providers := m.providerList()
  1146  	shuffleProviders(providers)
  1147  	return m.withOne(ctx, providers, f, acceptabilityFilters...)
  1148  }
  1149  
  1150  // withFreshest runs the provider function against known providers in order of
  1151  // best header time until one succeeds or all have failed.
  1152  func (m *multiRPCClient) withFreshest(ctx context.Context, f func(context.Context, *provider) error, acceptabilityFilters ...acceptabilityFilter) error {
  1153  	providers := m.freshnessSortedProviders()
  1154  	return m.withOne(ctx, providers, f, acceptabilityFilters...)
  1155  }
  1156  
  1157  // withPreferred is like withAny, but will prioritize recently used nonce
  1158  // providers.
  1159  func (m *multiRPCClient) withPreferred(ctx context.Context, f func(context.Context, *provider) error, acceptabilityFilters ...acceptabilityFilter) error {
  1160  	return m.withOne(ctx, m.nonceProviderList(), f, acceptabilityFilters...)
  1161  }
  1162  
  1163  // freshnessSortedProviders generates a list of providers sorted by their header
  1164  // times, newest first.
  1165  func (m *multiRPCClient) freshnessSortedProviders() []*provider {
  1166  	unsorted := m.providerList()
  1167  	type stampedProvider struct {
  1168  		stamp time.Time
  1169  		p     *provider
  1170  	}
  1171  	sps := make([]*stampedProvider, len(unsorted))
  1172  	for i, p := range unsorted {
  1173  		p.tip.RLock()
  1174  		stamp := p.tip.headerStamp
  1175  		p.tip.RUnlock()
  1176  		sps[i] = &stampedProvider{
  1177  			stamp: stamp,
  1178  			p:     p,
  1179  		}
  1180  	}
  1181  	sort.Slice(sps, func(i, j int) bool { return sps[i].stamp.Before(sps[j].stamp) })
  1182  	providers := make([]*provider, len(sps))
  1183  	for i, sp := range sps {
  1184  		providers[i] = sp.p
  1185  	}
  1186  	return providers
  1187  }
  1188  
  1189  // nonceProviderList returns the freshness-sorted provider list, but with any recent
  1190  // nonce provider inserted in the first position.
  1191  func (m *multiRPCClient) nonceProviderList() []*provider {
  1192  	var lastProvider *provider
  1193  	m.lastProvider.Lock()
  1194  	if time.Since(m.lastProvider.stamp) < nonceProviderStickiness {
  1195  		lastProvider = m.lastProvider.provider
  1196  	}
  1197  	m.lastProvider.Unlock()
  1198  
  1199  	freshProviders := m.freshnessSortedProviders()
  1200  
  1201  	providers := make([]*provider, 0, len(m.providers))
  1202  	for _, p := range freshProviders {
  1203  		if lastProvider != nil && lastProvider.host == p.host {
  1204  			continue // adding lastProvider below, as preferred provider
  1205  		}
  1206  		providers = append(providers, p)
  1207  	}
  1208  
  1209  	if lastProvider != nil {
  1210  		providers = append([]*provider{lastProvider}, providers...)
  1211  	}
  1212  
  1213  	return providers
  1214  }
  1215  
  1216  func (m *multiRPCClient) address() common.Address {
  1217  	return m.creds.addr
  1218  }
  1219  
  1220  func (m *multiRPCClient) addressBalance(ctx context.Context, addr common.Address) (bal *big.Int, err error) {
  1221  	return bal, m.withFreshest(ctx, func(ctx context.Context, p *provider) error {
  1222  		bal, err = p.ec.BalanceAt(ctx, addr, nil /* latest */)
  1223  		return err
  1224  	})
  1225  }
  1226  
  1227  func (m *multiRPCClient) bestHeader(ctx context.Context) (hdr *types.Header, err error) {
  1228  	// Check for an unexpired cached header first.
  1229  	var bestHeader *types.Header
  1230  	for _, p := range m.providerList() {
  1231  		h := p.cachedTip()
  1232  		if h == nil {
  1233  			continue
  1234  		}
  1235  		if bestHeader == nil ||
  1236  			// In fact, we should be comparing the total terminal difficulty of
  1237  			// the blocks. We don't have the TTD, even though it is sent by RPC,
  1238  			// because ethclient strips it from header data and the header
  1239  			// subscriptions may or may not send the ttd (Infura docs do not
  1240  			// show it in message), but it doesn't come through the geth client
  1241  			// subscription machinery regardless.
  1242  			h.Number.Cmp(bestHeader.Number) > 0 {
  1243  
  1244  			bestHeader = h
  1245  		}
  1246  	}
  1247  	if bestHeader != nil {
  1248  		return bestHeader, nil
  1249  	}
  1250  
  1251  	return hdr, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1252  		hdr, err = p.bestHeader(ctx, m.log)
  1253  		return err
  1254  	}, allRPCErrorsAreFails)
  1255  }
  1256  
  1257  func (m *multiRPCClient) headerByHash(ctx context.Context, h common.Hash) (hdr *types.Header, err error) {
  1258  	return hdr, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1259  		hdr, err = p.headerByHash(ctx, h)
  1260  		return err
  1261  	})
  1262  }
  1263  
  1264  func (m *multiRPCClient) chainConfig() *params.ChainConfig {
  1265  	return m.cfg
  1266  }
  1267  
  1268  func (m *multiRPCClient) peerCount() (c uint32) {
  1269  	m.providerMtx.RLock()
  1270  	defer m.providerMtx.RUnlock()
  1271  	for _, p := range m.providers {
  1272  		if !p.failed() {
  1273  			c++
  1274  		}
  1275  	}
  1276  	return
  1277  }
  1278  
  1279  func (m *multiRPCClient) contractBackend() bind.ContractBackend {
  1280  	return m
  1281  }
  1282  
  1283  func (m *multiRPCClient) lock() error {
  1284  	return m.creds.ks.Lock(m.creds.addr)
  1285  }
  1286  
  1287  func (m *multiRPCClient) locked() bool {
  1288  	status, _ := m.creds.wallet.Status()
  1289  	return status != "Unlocked"
  1290  }
  1291  
  1292  func (m *multiRPCClient) shutdown() {
  1293  	for _, p := range m.providerList() {
  1294  		p.shutdown()
  1295  	}
  1296  }
  1297  
  1298  func allowAlreadyKnownFilter(err error) (discard, propagate, fail bool) {
  1299  	// NOTE: err never hits errors.Is(err, txpool.ErrAlreadyKnown) because
  1300  	// err is a *rpc.jsonError, but it does have a Message that matches.
  1301  	return errorFilter(err, txpool.ErrAlreadyKnown, "known transaction"), false, false
  1302  }
  1303  
  1304  func (m *multiRPCClient) sendSignedTransaction(ctx context.Context, tx *types.Transaction, filts ...acceptabilityFilter) error {
  1305  	var lastProvider *provider
  1306  	if err := m.withAll(ctx, func(ctx context.Context, p *provider) error {
  1307  		lastProvider = p
  1308  		m.log.Tracef("Sending signed tx via %q", p.host)
  1309  		return p.ec.SendTransaction(ctx, tx)
  1310  	}, filts...); err != nil {
  1311  		return err
  1312  	}
  1313  	m.lastProvider.Lock()
  1314  	m.lastProvider.provider = lastProvider
  1315  	m.lastProvider.stamp = time.Now()
  1316  	m.lastProvider.Unlock()
  1317  	return nil
  1318  }
  1319  
  1320  func (m *multiRPCClient) sendTransaction(ctx context.Context, txOpts *bind.TransactOpts, to common.Address, data []byte, filts ...acceptabilityFilter) (*types.Transaction, error) {
  1321  	tx, err := m.creds.ks.SignTx(*m.creds.acct, types.NewTx(&types.DynamicFeeTx{
  1322  		To:        &to,
  1323  		ChainID:   m.chainID,
  1324  		Nonce:     txOpts.Nonce.Uint64(),
  1325  		Gas:       txOpts.GasLimit,
  1326  		GasFeeCap: txOpts.GasFeeCap,
  1327  		GasTipCap: txOpts.GasTipCap,
  1328  		Value:     txOpts.Value,
  1329  		Data:      data,
  1330  	}), m.chainID)
  1331  
  1332  	if err != nil {
  1333  		return nil, fmt.Errorf("signing error: %v", err)
  1334  	}
  1335  
  1336  	return tx, m.sendSignedTransaction(ctx, tx, filts...)
  1337  }
  1338  
  1339  func (m *multiRPCClient) signData(data []byte) (sig, pubKey []byte, err error) {
  1340  	return signData(m.creds, data)
  1341  }
  1342  
  1343  // syncProgress: Current and Highest blocks are not very useful for the caller,
  1344  // but the best header's time in seconds can be used to determine if the
  1345  // provider is out of sync.
  1346  func (m *multiRPCClient) syncProgress(ctx context.Context) (prog *ethereum.SyncProgress, tipTime uint64, err error) {
  1347  	return prog, tipTime, m.withFreshest(ctx, func(ctx context.Context, p *provider) error {
  1348  		tip, err := p.bestHeader(ctx, m.log)
  1349  		if err != nil {
  1350  			return err
  1351  		}
  1352  		tipTime = tip.Time
  1353  
  1354  		prog = &ethereum.SyncProgress{
  1355  			CurrentBlock: tip.Number.Uint64(),
  1356  			HighestBlock: tip.Number.Uint64(),
  1357  		}
  1358  		return nil
  1359  	}, allRPCErrorsAreFails)
  1360  }
  1361  
  1362  func (m *multiRPCClient) transactionConfirmations(ctx context.Context, txHash common.Hash) (confs uint32, err error) {
  1363  	var r *types.Receipt
  1364  	var tip *types.Header
  1365  	if err := m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
  1366  		r, err = p.ec.TransactionReceipt(ctx, txHash)
  1367  		if err != nil {
  1368  			return err
  1369  		}
  1370  		tip, err = p.bestHeader(ctx, m.log)
  1371  		return err
  1372  	}); err != nil {
  1373  		if isNotFoundError(err) {
  1374  			return 0, asset.CoinNotFoundError
  1375  		}
  1376  		return 0, err
  1377  	}
  1378  	if r.BlockNumber != nil && tip.Number != nil {
  1379  		bigConfs := new(big.Int).Sub(tip.Number, r.BlockNumber)
  1380  		if bigConfs.Sign() < 0 { // avoid potential overflow
  1381  			return 0, nil
  1382  		}
  1383  		bigConfs.Add(bigConfs, big.NewInt(1))
  1384  		if bigConfs.IsInt64() {
  1385  			return uint32(bigConfs.Int64()), nil
  1386  		}
  1387  	}
  1388  	return 0, nil
  1389  }
  1390  
  1391  // txOpts creates transaction options and sets the passed nonce if supplied. If
  1392  // nonce is nil the next nonce will be fetched and the passed argument altered.
  1393  // txOpts can be called with either one or both of maxFeeRate or tipRate, but
  1394  // if either is nil, as many as two RPC calls may be made to establish the
  1395  // missing values. If the maxFeeRate is not specified, the standard 2*base+tip
  1396  // formula is used.
  1397  func (m *multiRPCClient) txOpts(ctx context.Context, val, maxGas uint64, maxFeeRate, tipRate, nonce *big.Int) (_ *bind.TransactOpts, err error) {
  1398  	if maxFeeRate == nil || tipRate == nil {
  1399  		baseRate, newTipRate, err := m.currentFees(ctx)
  1400  		if err != nil {
  1401  			return nil, err
  1402  		}
  1403  		if tipRate == nil {
  1404  			tipRate = newTipRate
  1405  		}
  1406  		maxFeeRate = new(big.Int).Add(tipRate, new(big.Int).Mul(baseRate, big.NewInt(2)))
  1407  	}
  1408  
  1409  	txOpts := newTxOpts(ctx, m.creds.addr, val, maxGas, maxFeeRate, tipRate)
  1410  
  1411  	// If nonce is not nil, this indicates that we are trying to re-send an
  1412  	// old transaction with higher fee in order to ensure it is mined.
  1413  	if nonce == nil {
  1414  		_, nonce, err = m.nonce(ctx)
  1415  		if err != nil {
  1416  			return nil, fmt.Errorf("error getting nonce: %v", err)
  1417  		}
  1418  	}
  1419  	txOpts.Nonce = nonce
  1420  
  1421  	txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
  1422  		return m.creds.wallet.SignTx(*m.creds.acct, tx, m.chainID)
  1423  	}
  1424  
  1425  	return txOpts, nil
  1426  
  1427  }
  1428  
  1429  func (m *multiRPCClient) currentFees(ctx context.Context) (baseFees, tipCap *big.Int, err error) {
  1430  	return baseFees, tipCap, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1431  		hdr, err := p.bestHeader(ctx, m.log)
  1432  		if err != nil {
  1433  			return err
  1434  		}
  1435  
  1436  		baseFees = eip1559.CalcBaseFee(m.cfg, hdr)
  1437  
  1438  		if baseFees.Cmp(minGasPrice) < 0 {
  1439  			baseFees.Set(minGasPrice)
  1440  		}
  1441  
  1442  		tipCap = p.suggestTipCap(ctx, m.log)
  1443  
  1444  		return nil
  1445  	})
  1446  }
  1447  
  1448  func (m *multiRPCClient) unlock(pw string) error {
  1449  	return m.creds.ks.TimedUnlock(*m.creds.acct, pw, 0)
  1450  }
  1451  
  1452  // Methods below implement bind.ContractBackend
  1453  
  1454  var _ bind.ContractBackend = (*multiRPCClient)(nil)
  1455  
  1456  func (m *multiRPCClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (code []byte, err error) {
  1457  	return code, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1458  		code, err = p.ec.CodeAt(ctx, contract, blockNumber)
  1459  		return err
  1460  	})
  1461  }
  1462  
  1463  func (m *multiRPCClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) (res []byte, err error) {
  1464  	return res, m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
  1465  		res, err = p.ec.CallContract(ctx, call, blockNumber)
  1466  		return err
  1467  	})
  1468  }
  1469  
  1470  func (m *multiRPCClient) HeaderByNumber(ctx context.Context, number *big.Int) (hdr *types.Header, err error) {
  1471  	return hdr, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1472  		hdr, err = p.ec.HeaderByNumber(ctx, number)
  1473  		return err
  1474  	})
  1475  }
  1476  
  1477  func (m *multiRPCClient) PendingCodeAt(ctx context.Context, account common.Address) (code []byte, err error) {
  1478  	return code, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1479  		code, err = p.ec.PendingCodeAt(ctx, account)
  1480  		return err
  1481  	})
  1482  }
  1483  
  1484  func (m *multiRPCClient) PendingNonceAt(ctx context.Context, account common.Address) (nonce uint64, err error) {
  1485  	return nonce, m.withPreferred(ctx, func(ctx context.Context, p *provider) error {
  1486  		nonce, err = p.ec.PendingNonceAt(ctx, account)
  1487  		return err
  1488  	})
  1489  }
  1490  
  1491  func (m *multiRPCClient) SuggestGasPrice(ctx context.Context) (price *big.Int, err error) {
  1492  	return price, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1493  		price, err = p.ec.SuggestGasPrice(ctx)
  1494  		return err
  1495  	})
  1496  }
  1497  
  1498  func (m *multiRPCClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) {
  1499  	return tipCap, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1500  		tipCap = p.suggestTipCap(ctx, m.log)
  1501  		return nil
  1502  	})
  1503  }
  1504  
  1505  func (m *multiRPCClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
  1506  	return gas, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1507  		gas, err = p.ec.EstimateGas(ctx, call)
  1508  		return err
  1509  	}, func(err error) (discard, propagate, fail bool) {
  1510  		// Assume this one will be the same all around.
  1511  		return false, errorFilter(err, "gas required exceeds allowance"), false
  1512  	})
  1513  }
  1514  
  1515  func (m *multiRPCClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
  1516  	return m.sendSignedTransaction(ctx, tx)
  1517  }
  1518  
  1519  func (m *multiRPCClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) (logs []types.Log, err error) {
  1520  	return logs, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1521  		logs, err = p.ec.FilterLogs(ctx, query)
  1522  		return err
  1523  	})
  1524  }
  1525  
  1526  func (m *multiRPCClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (sub ethereum.Subscription, err error) {
  1527  	return sub, m.withAny(ctx, func(ctx context.Context, p *provider) error {
  1528  		sub, err = p.ec.SubscribeFilterLogs(ctx, query, ch)
  1529  		return err
  1530  	})
  1531  }
  1532  
  1533  const (
  1534  	// Compliant providers
  1535  	providerIPC         = "IPC"
  1536  	providerLinkPool    = "linkpool.io"
  1537  	providerMewAPI      = "mewapi.io"
  1538  	providerFlashBots   = "flashbots.net"
  1539  	providerMyCryptoAPI = "mycryptoapi.com"
  1540  	providerRunOnFlux   = "runonflux.io"
  1541  	providerInfura      = "infura.io"
  1542  	providerRivetCloud  = "rivet.cloud"
  1543  	providerAlchemy     = "alchemy.com"
  1544  	providerAnkr        = "ankr.com"
  1545  	providerdRPC        = "drpc.org"
  1546  
  1547  	// Non-compliant providers
  1548  	// providerCloudflareETH = "cloudflare-eth.com" // "SuggestGasTipCap" error: Method not found
  1549  )
  1550  
  1551  var compliantProviders = map[string]struct{}{
  1552  	providerLinkPool:    {},
  1553  	providerMewAPI:      {},
  1554  	providerFlashBots:   {},
  1555  	providerMyCryptoAPI: {},
  1556  	providerRunOnFlux:   {},
  1557  	providerInfura:      {},
  1558  	providerRivetCloud:  {},
  1559  	providerAlchemy:     {},
  1560  	providerAnkr:        {},
  1561  	providerdRPC:        {},
  1562  }
  1563  
  1564  type rpcTest struct {
  1565  	name string
  1566  	f    func(context.Context, *provider) error
  1567  }
  1568  
  1569  // newCompatibilityTests returns a list of RPC tests to run to determine API
  1570  // compatibility.
  1571  // NOTE: The logger is intended for use the execution of the compatibility
  1572  // tests, and it will generally be dex.Disabled in production.
  1573  func newCompatibilityTests(cb bind.ContractBackend, compat *CompatibilityData, log dex.Logger) []*rpcTest {
  1574  	return []*rpcTest{
  1575  		{
  1576  			name: "HeaderByNumber",
  1577  			f: func(ctx context.Context, p *provider) error {
  1578  				_, err := p.ec.HeaderByNumber(ctx, nil /* latest */)
  1579  				return err
  1580  			},
  1581  		},
  1582  		{
  1583  			name: "HeaderByHash",
  1584  			f: func(ctx context.Context, p *provider) error {
  1585  				_, err := p.ec.HeaderByHash(ctx, compat.BlockHash)
  1586  				return err
  1587  			},
  1588  		},
  1589  		{
  1590  			name: "TransactionReceipt",
  1591  			f: func(ctx context.Context, p *provider) error {
  1592  				_, err := p.ec.TransactionReceipt(ctx, compat.TxHash)
  1593  				return err
  1594  			},
  1595  		},
  1596  		{
  1597  			name: "PendingNonceAt",
  1598  			f: func(ctx context.Context, p *provider) error {
  1599  				_, err := p.ec.PendingNonceAt(ctx, compat.Addr)
  1600  				return err
  1601  			},
  1602  		},
  1603  		{
  1604  			name: "SuggestGasTipCap",
  1605  			f: func(ctx context.Context, p *provider) error {
  1606  				tipCap, err := p.ec.SuggestGasTipCap(ctx)
  1607  				if err != nil {
  1608  					return err
  1609  				}
  1610  				log.Debugf("#### Retrieved tip cap: %d gwei", dexeth.WeiToGweiCeil(tipCap))
  1611  				return nil
  1612  			},
  1613  		},
  1614  		{
  1615  			name: "BalanceAt",
  1616  			f: func(ctx context.Context, p *provider) error {
  1617  				bal, err := p.ec.BalanceAt(ctx, compat.Addr, nil)
  1618  				if err != nil {
  1619  					return err
  1620  				}
  1621  				log.Debugf("#### Balance retrieved: %.9f", float64(dexeth.WeiToGweiCeil(bal))/1e9)
  1622  				return nil
  1623  			},
  1624  		},
  1625  		{
  1626  			name: "CodeAt",
  1627  			f: func(ctx context.Context, p *provider) error {
  1628  				if compat.TokenAddr == (common.Address{}) {
  1629  					log.Debug("#### Skipping CodeAt. No token address provided")
  1630  					return nil
  1631  				}
  1632  				code, err := p.ec.CodeAt(ctx, compat.TokenAddr, nil)
  1633  				if err != nil {
  1634  					return err
  1635  				}
  1636  				log.Debugf("#### %d bytes of USDC contract retrieved", len(code))
  1637  				return nil
  1638  			},
  1639  		},
  1640  		{
  1641  			name: "CallContract(balanceOf)",
  1642  			f: func(ctx context.Context, p *provider) error {
  1643  				if compat.TokenAddr == (common.Address{}) {
  1644  					log.Debug("#### Skipping CallContract. No token address provided")
  1645  					return nil
  1646  				}
  1647  				caller, err := erc20.NewIERC20(compat.TokenAddr, cb)
  1648  				if err != nil {
  1649  					return err
  1650  				}
  1651  				bal, err := caller.BalanceOf(&bind.CallOpts{
  1652  					From:    compat.Addr,
  1653  					Context: ctx,
  1654  				}, compat.Addr)
  1655  				if err != nil {
  1656  					return err
  1657  				}
  1658  				// I guess we would need to unpack the results. I don't really
  1659  				// know how to interpret these, but I'm really just looking for
  1660  				// a request error.
  1661  				log.Debug("#### ERC20 token balanceOf result:", dexeth.WeiToGwei(bal), "gwei")
  1662  				return nil
  1663  			},
  1664  		},
  1665  		{
  1666  			name: "ChainID",
  1667  			f: func(ctx context.Context, p *provider) error {
  1668  				chainID, err := p.ec.ChainID(ctx)
  1669  				if err != nil {
  1670  					return err
  1671  				}
  1672  				log.Debugf("#### Chain ID: %d", chainID)
  1673  				return nil
  1674  			},
  1675  		},
  1676  		{
  1677  			name: "getRPCTransaction",
  1678  			f: func(ctx context.Context, p *provider) error {
  1679  				rpcTx, err := getRPCTransaction(ctx, p, compat.TxHash)
  1680  				if err != nil {
  1681  					return err
  1682  				}
  1683  				var h string
  1684  				if rpcTx.BlockNumber != nil {
  1685  					h = *rpcTx.BlockNumber
  1686  				}
  1687  				log.Debugf("#### RPC Tx is nil? %t, block number: %q", rpcTx.tx == nil, h)
  1688  				return nil
  1689  			},
  1690  		},
  1691  	}
  1692  }
  1693  
  1694  // domain accepts an url, ip, or file path and returns the domain:port if they
  1695  // exist. Returns just the domain if no port. Returns a cleaned file path if a
  1696  // file with .ipc suffix, otherwise returns the address as is if no errors were
  1697  // encountered.
  1698  func domain(addr string) (string, error) {
  1699  	addr = strings.TrimSpace(addr)
  1700  	if addr == "" {
  1701  		return "", errors.New("address is an empty string")
  1702  	}
  1703  	if strings.HasSuffix(addr, ".ipc") {
  1704  		return dex.CleanAndExpandPath(addr), nil // ipc file
  1705  	}
  1706  	const missingPort = "missing port in address"
  1707  	host, port, splitErr := net.SplitHostPort(addr)
  1708  	_, portErr := strconv.ParseUint(port, 10, 16)
  1709  
  1710  	removeSubdomain := func(addr string) string {
  1711  		parts := strings.Split(host, ".")
  1712  		n := len(parts)
  1713  		if n <= 2 {
  1714  			return addr
  1715  		}
  1716  		// Possibly an ipv4 address with no subdomain.
  1717  		if ip := net.ParseIP(addr); ip != nil {
  1718  			return addr
  1719  		}
  1720  		// Top level domains such as .ne.jp or .co.uk exist.
  1721  		if n >= 3 && len(parts[n-2]) == 2 {
  1722  			return parts[n-3] + "." + parts[n-2] + "." + parts[n-1]
  1723  		}
  1724  		// Otherwise assume domain.topleveldomain at the end.
  1725  		return parts[n-2] + "." + parts[n-1]
  1726  	}
  1727  
  1728  	// net.SplitHostPort will error on anything not in the format
  1729  	// string:string or :string or if a colon is in an unexpected position,
  1730  	// such as in the scheme.
  1731  	// If the port isn't a port, it must also be parsed.
  1732  	if splitErr != nil || portErr != nil {
  1733  		// Any address with no colons is appended with the default port.
  1734  		var addrErr *net.AddrError
  1735  		if errors.As(splitErr, &addrErr) && addrErr.Err == missingPort {
  1736  			if host == "" {
  1737  				// address was either an ip with no port like
  1738  				// [::1] or a file path with no .ipc at the end
  1739  				return addr, nil
  1740  			}
  1741  			return removeSubdomain(host), nil // no port
  1742  		}
  1743  		// These are addresses with at least one colon in an unexpected
  1744  		// position.
  1745  		a, err := url.Parse(addr)
  1746  		// This address is of an unknown format.
  1747  		if err != nil {
  1748  			return "", fmt.Errorf("addr %s of unknown format %v", addr, err)
  1749  		}
  1750  		host, port = a.Hostname(), a.Port()
  1751  		if port == "" {
  1752  			return removeSubdomain(host), nil // no port
  1753  		}
  1754  	}
  1755  	if host == "" {
  1756  		return "", fmt.Errorf("no domain found for %s", addr)
  1757  	}
  1758  	return net.JoinHostPort(removeSubdomain(host), port), nil
  1759  }
  1760  
  1761  // checkProvidersCompliance verifies that providers support the API that DEX
  1762  // requires by sending a series of requests and verifying the responses. If a
  1763  // provider is found to be compliant, their domain name is added to a list and
  1764  // stored in a file on disk so that future checks can be short-circuited.
  1765  func checkProvidersCompliance(ctx context.Context, providers []*provider, compat *CompatibilityData, log dex.Logger, errOnNonCompliant bool) ([]*provider, error) {
  1766  	compliantProviders := make([]*provider, 0, len(providers))
  1767  	for _, p := range providers {
  1768  		// Need to run API tests on this endpoint.
  1769  		for _, t := range newCompatibilityTests(p.ec, compat, log) {
  1770  			ctx, cancel := context.WithTimeout(ctx, defaultRequestTimeout)
  1771  			err := t.f(ctx, p)
  1772  			cancel()
  1773  			if err == nil {
  1774  				compliantProviders = append(compliantProviders, p)
  1775  				continue
  1776  			}
  1777  
  1778  			if errOnNonCompliant {
  1779  				return nil, fmt.Errorf("%s: RPC Provider @ %q has a non-compliant API: %v", t.name, p.host, err)
  1780  			} else {
  1781  				log.Errorf("%s: RPC Provider @ %q has a non-compliant API: %v", t.name, p.host, err)
  1782  			}
  1783  		}
  1784  	}
  1785  
  1786  	return compliantProviders, nil
  1787  }
  1788  
  1789  // shuffleProviders shuffles the provider slice in-place.
  1790  func shuffleProviders(p []*provider) {
  1791  	rand.Shuffle(len(p), func(i, j int) {
  1792  		p[i], p[j] = p[j], p[i]
  1793  	})
  1794  }
  1795  
  1796  func isNotFoundError(err error) bool {
  1797  	return strings.Contains(err.Error(), "not found")
  1798  }