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 = ðereum.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 }