From b74d9e6b4e9177f1aa9ba57f9de8beebf5b352e1 Mon Sep 17 00:00:00 2001 From: Ivan Belyakov Date: Wed, 31 Jul 2024 08:21:11 +0200 Subject: [PATCH] feat(wallet)_: add status proxy RPC urls for blockchain providers Replace the status proxy URL for cryptocompare. --- Makefile | 5 + api/default_networks.go | 14 ++ api/defaults.go | 12 +- api/geth_backend.go | 2 +- buildinfo/apiproxystage_dev.go | 5 + buildinfo/apiproxystage_prod.go | 5 + node/get_status_node.go | 15 +- params/config.go | 40 +++-- protocol/requests/create_account.go | 25 +-- protocol/requests/login.go | 3 +- rpc/client.go | 149 +++++++++++------- rpc/client_test.go | 70 +++++++- rpc/network/network.go | 37 ++++- services/connector/api_test.go | 2 +- services/connector/service_test.go | 2 +- services/ens/api_test.go | 2 +- services/wallet/history/service_test.go | 2 +- services/wallet/router/router_v2_test.go | 2 +- .../wallet/thirdparty/cryptocompare/client.go | 6 +- services/wallet/token/token_test.go | 2 +- .../transfer/commands_sequential_test.go | 12 +- services/web3provider/api_test.go | 2 +- transactions/transactor_test.go | 2 +- 23 files changed, 309 insertions(+), 107 deletions(-) create mode 100644 buildinfo/apiproxystage_dev.go create mode 100644 buildinfo/apiproxystage_prod.go diff --git a/Makefile b/Makefile index b1f91a844..127d5689f 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,11 @@ GIT_AUTHOR := $(shell git config user.email || echo $$USER) ENABLE_METRICS ?= true BUILD_TAGS ?= gowaku_no_rln +ifdef RELEASE + BUILD_TAGS += release +endif + + BUILD_FLAGS ?= -ldflags="-X github.com/status-im/status-go/params.Version=$(RELEASE_TAG:v%=%) \ -X github.com/status-im/status-go/params.GitCommit=$(GIT_COMMIT) \ -X github.com/status-im/status-go/params.IpfsGatewayURL=$(IPFS_GATEWAY_URL) \ diff --git a/api/default_networks.go b/api/default_networks.go index 74c16f8ab..d20c5385d 100644 --- a/api/default_networks.go +++ b/api/default_networks.go @@ -1,9 +1,11 @@ package api import ( + "fmt" "strings" "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/buildinfo" "github.com/status-im/status-go/params" "github.com/status-im/status-go/protocol/requests" ) @@ -27,6 +29,8 @@ var ganacheTokenAddress = common.HexToAddress("0x8571Ddc46b10d31EF963aF49b6C7799 var mainnet = params.Network{ ChainID: mainnetChainID, ChainName: "Mainnet", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/ethereum/mainnet/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/ethereum/mainnet/", buildinfo.ApiProxyStageName), RPCURL: "https://eth-archival.rpc.grove.city/v1/", FallbackURL: "https://mainnet.infura.io/v3/", BlockExplorerURL: "https://etherscan.io/", @@ -63,6 +67,8 @@ var goerli = params.Network{ var sepolia = params.Network{ ChainID: sepoliaChainID, ChainName: "Mainnet", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/ethereum/sepolia/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/ethereum/sepolia/", buildinfo.ApiProxyStageName), RPCURL: "https://sepolia-archival.rpc.grove.city/v1/", FallbackURL: "https://sepolia.infura.io/v3/", BlockExplorerURL: "https://sepolia.etherscan.io/", @@ -81,6 +87,8 @@ var sepolia = params.Network{ var optimism = params.Network{ ChainID: optimismChainID, ChainName: "Optimism", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/optimism/mainnet/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/optimism/mainnet/", buildinfo.ApiProxyStageName), RPCURL: "https://optimism-archival.rpc.grove.city/v1/", FallbackURL: "https://optimism-mainnet.infura.io/v3/", BlockExplorerURL: "https://optimistic.etherscan.io", @@ -117,6 +125,8 @@ var optimismGoerli = params.Network{ var optimismSepolia = params.Network{ ChainID: optimismSepoliaChainID, ChainName: "Optimism", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/optimism/sepolia/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/optimism/sepolia/", buildinfo.ApiProxyStageName), RPCURL: "https://optimism-sepolia-archival.rpc.grove.city/v1/", FallbackURL: "https://optimism-sepolia.infura.io/v3/", BlockExplorerURL: "https://sepolia-optimism.etherscan.io/", @@ -135,6 +145,8 @@ var optimismSepolia = params.Network{ var arbitrum = params.Network{ ChainID: arbitrumChainID, ChainName: "Arbitrum", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/arbitrum/mainnet/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/arbitrum/mainnet/", buildinfo.ApiProxyStageName), RPCURL: "https://arbitrum-one.rpc.grove.city/v1/", FallbackURL: "https://arbitrum-mainnet.infura.io/v3/", BlockExplorerURL: "https://arbiscan.io/", @@ -171,6 +183,8 @@ var arbitrumGoerli = params.Network{ var arbitrumSepolia = params.Network{ ChainID: arbitrumSepoliaChainID, ChainName: "Arbitrum", + DefaultRPCURL: fmt.Sprintf("https://%s.api.status.im/grove/arbitrum/sepolia/", buildinfo.ApiProxyStageName), + DefaultFallbackURL: fmt.Sprintf("https://%s.api.status.im/infura/arbitrum/sepolia/", buildinfo.ApiProxyStageName), RPCURL: "https://arbitrum-sepolia-archival.rpc.grove.city/v1/", FallbackURL: "https://arbitrum-sepolia.infura.io/v3/", BlockExplorerURL: "https://sepolia-explorer.arbitrum.io/", diff --git a/api/defaults.go b/api/defaults.go index 4c404779c..628067a76 100644 --- a/api/defaults.go +++ b/api/defaults.go @@ -161,7 +161,7 @@ func SetFleet(fleet string, nodeConfig *params.NodeConfig) error { return nil } -func buildWalletConfig(request *requests.WalletSecretsConfig) params.WalletConfig { +func buildWalletConfig(request *requests.WalletSecretsConfig, statusProxyEnabled bool) params.WalletConfig { walletConfig := params.WalletConfig{ Enabled: true, AlchemyAPIKeys: make(map[uint64]string), @@ -220,6 +220,14 @@ func buildWalletConfig(request *requests.WalletSecretsConfig) params.WalletConfi if request.StatusProxyMarketPassword != "" { walletConfig.StatusProxyMarketPassword = request.StatusProxyMarketPassword } + if request.StatusProxyBlockchainUser != "" { + walletConfig.StatusProxyBlockchainUser = request.StatusProxyBlockchainUser + } + if request.StatusProxyBlockchainPassword != "" { + walletConfig.StatusProxyBlockchainPassword = request.StatusProxyBlockchainPassword + } + + walletConfig.StatusProxyEnabled = statusProxyEnabled return walletConfig } @@ -287,7 +295,7 @@ func defaultNodeConfig(installationID string, request *requests.CreateAccount, o nodeConfig.MaxPeers = DefaultMaxPeers nodeConfig.MaxPendingPeers = DefaultMaxPendingPeers - nodeConfig.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig) + nodeConfig.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled) nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true} nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true} diff --git a/api/geth_backend.go b/api/geth_backend.go index f9d2ce306..dc751c17d 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -601,7 +601,7 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error { KeycardPairingDataFile: DefaultKeycardPairingDataFile, } - defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig) + defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled) err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg) if err != nil { diff --git a/buildinfo/apiproxystage_dev.go b/buildinfo/apiproxystage_dev.go new file mode 100644 index 000000000..8144c086a --- /dev/null +++ b/buildinfo/apiproxystage_dev.go @@ -0,0 +1,5 @@ +//go:build !release + +package buildinfo + +var ApiProxyStageName = "test" diff --git a/buildinfo/apiproxystage_prod.go b/buildinfo/apiproxystage_prod.go new file mode 100644 index 000000000..56ff32e37 --- /dev/null +++ b/buildinfo/apiproxystage_prod.go @@ -0,0 +1,5 @@ +//go:build release + +package buildinfo + +var ApiProxyStageName = "prod" diff --git a/node/get_status_node.go b/node/get_status_node.go index bb2247ef6..8bbe25a86 100644 --- a/node/get_status_node.go +++ b/node/get_status_node.go @@ -314,7 +314,20 @@ func (n *StatusNode) setupRPCClient() (err error) { if err != nil { return } - n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.NetworkID, n.config.UpstreamConfig, n.config.Networks, n.appDB) + + // ProviderConfigs should be passed not in wallet secrets config on login + // but some other way, as it's not wallet specific and should not be passed with login request + // but currently there is no other way to pass it + providerConfigs := []params.ProviderConfig{ + { + Enabled: n.config.WalletConfig.StatusProxyEnabled, + Name: rpc.ProviderStatusProxy, + User: n.config.WalletConfig.StatusProxyBlockchainUser, + Password: n.config.WalletConfig.StatusProxyBlockchainPassword, + }, + } + + n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.NetworkID, n.config.UpstreamConfig, n.config.Networks, n.appDB, providerConfigs) if err != nil { return } diff --git a/params/config.go b/params/config.go index 09f5c5cd3..6c7f684d6 100644 --- a/params/config.go +++ b/params/config.go @@ -310,6 +310,21 @@ type UpstreamRPCConfig struct { URL string } +type ProviderConfig struct { + // Enabled flag specifies whether feature is enabled + Enabled bool `validate:"required"` + + // To identify provider + Name string `validate:"required"` + + // URL sets the rpc upstream host address for communication with + // a non-local infura endpoint. + User string `json:",omitempty"` + Password string `json:",omitempty"` + APIKey string `json:"APIKey,omitempty"` + APIKeySecret string `json:"APIKeySecret,omitempty"` +} + // ---------- // NodeConfig // ---------- @@ -531,6 +546,8 @@ type TokenOverride struct { type Network struct { ChainID uint64 `json:"chainId"` ChainName string `json:"chainName"` + DefaultRPCURL string `json:"defaultRpcUrl"` // proxy rpc url + DefaultFallbackURL string `json:"defaultFallbackURL"` // proxy fallback url RPCURL string `json:"rpcUrl"` OriginalRPCURL string `json:"originalRpcUrl"` FallbackURL string `json:"fallbackURL"` @@ -551,16 +568,19 @@ type Network struct { // WalletConfig extra configuration for wallet.Service. type WalletConfig struct { - Enabled bool - OpenseaAPIKey string `json:"OpenseaAPIKey"` - RaribleMainnetAPIKey string `json:"RaribleMainnetAPIKey"` - RaribleTestnetAPIKey string `json:"RaribleTestnetAPIKey"` - AlchemyAPIKeys map[uint64]string `json:"AlchemyAPIKeys"` - InfuraAPIKey string `json:"InfuraAPIKey"` - InfuraAPIKeySecret string `json:"InfuraAPIKeySecret"` - StatusProxyMarketUser string `json:"StatusProxyMarketUser"` - StatusProxyMarketPassword string `json:"StatusProxyMarketPassword"` - EnableCelerBridge bool `json:"EnableCelerBridge"` + Enabled bool + OpenseaAPIKey string `json:"OpenseaAPIKey"` + RaribleMainnetAPIKey string `json:"RaribleMainnetAPIKey"` + RaribleTestnetAPIKey string `json:"RaribleTestnetAPIKey"` + AlchemyAPIKeys map[uint64]string `json:"AlchemyAPIKeys"` + InfuraAPIKey string `json:"InfuraAPIKey"` + InfuraAPIKeySecret string `json:"InfuraAPIKeySecret"` + StatusProxyMarketUser string `json:"StatusProxyMarketUser"` + StatusProxyMarketPassword string `json:"StatusProxyMarketPassword"` + StatusProxyBlockchainUser string `json:"StatusProxyBlockchainUser"` + StatusProxyBlockchainPassword string `json:"StatusProxyBlockchainPassword"` + StatusProxyEnabled bool `json:"StatusProxyEnabled"` + EnableCelerBridge bool `json:"EnableCelerBridge"` } // LocalNotificationsConfig extra configuration for localnotifications.Service. diff --git a/protocol/requests/create_account.go b/protocol/requests/create_account.go index 6bc13840b..f989c9d47 100644 --- a/protocol/requests/create_account.go +++ b/protocol/requests/create_account.go @@ -86,6 +86,7 @@ type CreateAccount struct { KeycardInstanceUID string `json:"keycardInstanceUID"` KeycardPairingDataFile *string `json:"keycardPairingDataFile"` + StatusProxyEnabled bool `json:"statusProxyEnabled"` } type WalletSecretsConfig struct { @@ -96,17 +97,19 @@ type WalletSecretsConfig struct { RaribleMainnetAPIKey string `json:"raribleMainnetApiKey"` RaribleTestnetAPIKey string `json:"raribleTestnetApiKey"` - AlchemyEthereumMainnetToken string `json:"alchemyEthereumMainnetToken"` - AlchemyEthereumGoerliToken string `json:"alchemyEthereumGoerliToken"` - AlchemyEthereumSepoliaToken string `json:"alchemyEthereumSepoliaToken"` - AlchemyArbitrumMainnetToken string `json:"alchemyArbitrumMainnetToken"` - AlchemyArbitrumGoerliToken string `json:"alchemyArbitrumGoerliToken"` - AlchemyArbitrumSepoliaToken string `json:"alchemyArbitrumSepoliaToken"` - AlchemyOptimismMainnetToken string `json:"alchemyOptimismMainnetToken"` - AlchemyOptimismGoerliToken string `json:"alchemyOptimismGoerliToken"` - AlchemyOptimismSepoliaToken string `json:"alchemyOptimismSepoliaToken"` - StatusProxyMarketUser string `json:"statusProxyMarketUser"` - StatusProxyMarketPassword string `json:"statusProxyMarketPassword"` + AlchemyEthereumMainnetToken string `json:"alchemyEthereumMainnetToken"` + AlchemyEthereumGoerliToken string `json:"alchemyEthereumGoerliToken"` + AlchemyEthereumSepoliaToken string `json:"alchemyEthereumSepoliaToken"` + AlchemyArbitrumMainnetToken string `json:"alchemyArbitrumMainnetToken"` + AlchemyArbitrumGoerliToken string `json:"alchemyArbitrumGoerliToken"` + AlchemyArbitrumSepoliaToken string `json:"alchemyArbitrumSepoliaToken"` + AlchemyOptimismMainnetToken string `json:"alchemyOptimismMainnetToken"` + AlchemyOptimismGoerliToken string `json:"alchemyOptimismGoerliToken"` + AlchemyOptimismSepoliaToken string `json:"alchemyOptimismSepoliaToken"` + StatusProxyMarketUser string `json:"statusProxyMarketUser"` + StatusProxyMarketPassword string `json:"statusProxyMarketPassword"` + StatusProxyBlockchainUser string `json:"statusProxyBlockchainUser"` + StatusProxyBlockchainPassword string `json:"statusProxyBlockchainPassword"` // Testing GanacheURL string `json:"ganacheURL"` diff --git a/protocol/requests/login.go b/protocol/requests/login.go index 02e7fe257..98328a114 100644 --- a/protocol/requests/login.go +++ b/protocol/requests/login.go @@ -35,7 +35,8 @@ type Login struct { WalletSecretsConfig - APIConfig *APIConfig `json:"apiConfig"` + APIConfig *APIConfig `json:"apiConfig"` + StatusProxyEnabled bool `json:"statusProxyEnabled"` } func (c *Login) Validate() error { diff --git a/rpc/client.go b/rpc/client.go index fd6d265ed..d2d9f9a35 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -3,12 +3,12 @@ package rpc import ( "context" "database/sql" + "encoding/base64" "encoding/json" "errors" "fmt" "net/url" "reflect" - "strings" "sync" "time" @@ -26,6 +26,11 @@ import ( const ( // DefaultCallTimeout is a default timeout for an RPC call DefaultCallTimeout = time.Minute + + // Names of providers + providerGrove = "grove" + providerInfura = "infura" + ProviderStatusProxy = "status-proxy" ) // List of RPC client errors. @@ -67,7 +72,8 @@ type Client struct { handlers map[string]Handler // locally registered handlers log log.Logger - walletNotifier func(chainID uint64, message string) + walletNotifier func(chainID uint64, message string) + providerConfigs []params.ProviderConfig } // Is initialized in a build-tag-dependent module @@ -78,7 +84,7 @@ var verifProxyInitFn func(c *Client) // // Client is safe for concurrent use and will automatically // reconnect to the server if connection is lost. -func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.UpstreamRPCConfig, networks []params.Network, db *sql.DB) (*Client, error) { +func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.UpstreamRPCConfig, networks []params.Network, db *sql.DB, providerConfigs []params.ProviderConfig) (*Client, error) { var err error log := log.New("package", "status-go/rpc.Client") @@ -99,6 +105,7 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U rpcClients: make(map[uint64]chain.ClientInterface), limiterPerProvider: make(map[string]*chain.RPCRpsLimiter), log: log, + providerConfigs: providerConfigs, } if upstream.Enabled { @@ -133,18 +140,6 @@ func (c *Client) SetWalletNotifier(notifier func(chainID uint64, message string) c.walletNotifier = notifier } -func extractLastParamFromURL(inputURL string) (string, error) { - parsedURL, err := url.Parse(inputURL) - if err != nil { - return "", err - } - - pathSegments := strings.Split(parsedURL.Path, "/") - lastSegment := pathSegments[len(pathSegments)-1] - - return lastSegment, nil -} - func extractHostAndPortFromURL(inputURL string) (string, error) { parsedURL, err := url.Parse(inputURL) if err != nil { @@ -154,21 +149,26 @@ func extractHostAndPortFromURL(inputURL string) (string, error) { return parsedURL.Host, nil } -func (c *Client) getRPCRpsLimiter(URL string) (*chain.RPCRpsLimiter, error) { - apiKey, err := extractLastParamFromURL(URL) - if err != nil { - return nil, err - } +func (c *Client) getRPCRpsLimiter(key string) (*chain.RPCRpsLimiter, error) { c.rpsLimiterMutex.Lock() defer c.rpsLimiterMutex.Unlock() - if limiter, ok := c.limiterPerProvider[apiKey]; ok { + if limiter, ok := c.limiterPerProvider[key]; ok { return limiter, nil } limiter := chain.NewRPCRpsLimiter() - c.limiterPerProvider[apiKey] = limiter + c.limiterPerProvider[key] = limiter return limiter, nil } +func getProviderConfig(providerConfigs []params.ProviderConfig, providerName string) (params.ProviderConfig, error) { + for _, providerConfig := range providerConfigs { + if providerConfig.Name == providerName { + return providerConfig, nil + } + } + return params.ProviderConfig{}, fmt.Errorf("provider config not found for provider: %s", providerName) +} + func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) { c.rpcClientsMutex.Lock() defer c.rpcClientsMutex.Unlock() @@ -187,45 +187,9 @@ func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, err return nil, fmt.Errorf("could not find network: %d", chainID) } - rpcClient, err := gethrpc.Dial(network.RPCURL) - if err != nil { - return nil, fmt.Errorf("dial upstream server: %s", err) - } - - rpcLimiter, err := c.getRPCRpsLimiter(network.RPCURL) - if err != nil { - return nil, fmt.Errorf("get RPC limiter: %s", err) - } - - hostPortMain, err := extractHostAndPortFromURL(network.RPCURL) - if err != nil { - hostPortMain = "main" - } - - ethClients := []*chain.EthClient{ - chain.NewEthClient(ethclient.NewClient(rpcClient), rpcLimiter, rpcClient, hostPortMain), - } - - var ( - rpcFallbackClient *gethrpc.Client - rpcFallbackLimiter *chain.RPCRpsLimiter - ) - if len(network.FallbackURL) > 0 { - rpcFallbackClient, err = gethrpc.Dial(network.FallbackURL) - if err != nil { - return nil, fmt.Errorf("dial upstream server: %s", err) - } - - rpcFallbackLimiter, err = c.getRPCRpsLimiter(network.FallbackURL) - if err != nil { - return nil, fmt.Errorf("get RPC fallback limiter: %s", err) - } - hostPortFallback, err := extractHostAndPortFromURL(network.FallbackURL) - if err != nil { - hostPortFallback = "fallback" - } - - ethClients = append(ethClients, chain.NewEthClient(ethclient.NewClient(rpcFallbackClient), rpcFallbackLimiter, rpcFallbackClient, hostPortFallback)) + ethClients := c.getEthClents(network) + if len(ethClients) == 0 { + return nil, fmt.Errorf("could not find any RPC URL for chain: %d", chainID) } client := chain.NewClient(ethClients, chainID) @@ -234,6 +198,69 @@ func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, err return client, nil } +func (c *Client) getEthClents(network *params.Network) []*chain.EthClient { + urls := make(map[string]string) + keys := make([]string, 0) + authMap := make(map[string]string) + + // find proxy provider + proxyProvider, err := getProviderConfig(c.providerConfigs, ProviderStatusProxy) + if err != nil { + c.log.Warn("could not find provider config for status-proxy", "error", err) + } + + if proxyProvider.Enabled { + key := ProviderStatusProxy + keyFallback := ProviderStatusProxy + "-fallback" + urls[key] = network.DefaultRPCURL + urls[keyFallback] = network.DefaultFallbackURL + keys = []string{key, keyFallback} + authMap[key] = proxyProvider.User + ":" + proxyProvider.Password + authMap[keyFallback] = authMap[key] + } + keys = append(keys, []string{"main", "fallback"}...) + urls["main"] = network.RPCURL + urls["fallback"] = network.FallbackURL + + ethClients := make([]*chain.EthClient, 0) + for _, key := range keys { + var rpcClient *gethrpc.Client + var rpcLimiter *chain.RPCRpsLimiter + var err error + var hostPort string + url := urls[key] + + if len(url) > 0 { + // For now we only support auth for status-proxy. + authStr, ok := authMap[key] + var opts []gethrpc.ClientOption + if ok { + authEncoded := base64.StdEncoding.EncodeToString([]byte(authStr)) + opts = append(opts, gethrpc.WithHeader("Authorization", "Basic "+authEncoded)) + } + + rpcClient, err = gethrpc.DialOptions(context.Background(), url, opts...) + if err != nil { + c.log.Error("dial server "+key, "error", err) + } + + hostPort, err = extractHostAndPortFromURL(url) + if err != nil { + hostPort = key + } + + rpcLimiter, err = c.getRPCRpsLimiter(hostPort) + if err != nil { + c.log.Error("get RPC limiter "+key, "error", err) + } + + ethClients = append(ethClients, chain.NewEthClient(ethclient.NewClient(rpcClient), rpcLimiter, rpcClient, hostPort)) + } + } + + return ethClients +} + // Ethclient returns ethclient.Client per chain func (c *Client) EthClient(chainID uint64) (chain.ClientInterface, error) { client, err := c.getClientUsingCache(chainID) diff --git a/rpc/client_test.go b/rpc/client_test.go index 5ddd88fc9..daa53cfe9 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -3,17 +3,22 @@ package rpc import ( "context" "database/sql" + "encoding/base64" "fmt" + "math/big" "net/http" "net/http/httptest" + "sync" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/status-im/status-go/appdatabase" "github.com/status-im/status-go/params" "github.com/status-im/status-go/t/helpers" + "github.com/ethereum/go-ethereum/common" gethrpc "github.com/ethereum/go-ethereum/rpc" ) @@ -39,7 +44,7 @@ func TestBlockedRoutesCall(t *testing.T) { gethRPCClient, err := gethrpc.Dial(ts.URL) require.NoError(t, err) - c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) require.NoError(t, err) for _, m := range blockedMethods { @@ -78,7 +83,7 @@ func TestBlockedRoutesRawCall(t *testing.T) { gethRPCClient, err := gethrpc.Dial(ts.URL) require.NoError(t, err) - c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) require.NoError(t, err) for _, m := range blockedMethods { @@ -105,7 +110,7 @@ func TestUpdateUpstreamURL(t *testing.T) { gethRPCClient, err := gethrpc.Dial(ts.URL) require.NoError(t, err) - c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: true, URL: ts.URL}, []params.Network{}, db) + c, err := NewClient(gethRPCClient, 1, params.UpstreamRPCConfig{Enabled: true, URL: ts.URL}, []params.Network{}, db, nil) require.NoError(t, err) require.Equal(t, ts.URL, c.upstreamURL) @@ -132,3 +137,62 @@ func createTestServer(resp string) *httptest.Server { fmt.Fprintln(w, resp) })) } + +func TestGetClientsUsingCache(t *testing.T) { + db, close := setupTestNetworkDB(t) + defer close() + + providerConfig := params.ProviderConfig{ + Enabled: true, + Name: ProviderStatusProxy, + User: "user1", + Password: "pass1", + } + providerConfigs := []params.ProviderConfig{providerConfig} + + var wg sync.WaitGroup + wg.Add(2) // 2 providers + + // Create a new ServeMux + mux := http.NewServeMux() + + path1 := "/foo" + path2 := "/bar" + // Register handlers for different URL paths + mux.HandleFunc(path1, func(w http.ResponseWriter, r *http.Request) { + authToken := base64.StdEncoding.EncodeToString([]byte(providerConfig.User + ":" + providerConfig.Password)) + require.Equal(t, fmt.Sprintf("Basic %s", authToken), r.Header.Get("Authorization")) + wg.Done() + }) + + mux.HandleFunc(path2, func(w http.ResponseWriter, r *http.Request) { + authToken := base64.StdEncoding.EncodeToString([]byte(providerConfig.User + ":" + providerConfig.Password)) + require.Equal(t, fmt.Sprintf("Basic %s", authToken), r.Header.Get("Authorization")) + wg.Done() + }) + + // Create a new server with the mux as the handler + server := httptest.NewServer(mux) + defer server.Close() + + networks := []params.Network{ + { + ChainID: 1, + DefaultRPCURL: server.URL + path1, + DefaultFallbackURL: server.URL + path2, + }, + } + c, err := NewClient(nil, 1, params.UpstreamRPCConfig{}, networks, db, providerConfigs) + require.NoError(t, err) + + // Networks from DB must pick up DefaultRPCURL and DefaultFallbackURL + chainClient, err := c.getClientUsingCache(networks[0].ChainID) + require.NoError(t, err) + require.NotNil(t, chainClient) + + // Make any call to provider. If test finishes, then all handlers were called and asserts inside them passed + balance, err := chainClient.BalanceAt(context.TODO(), common.Address{0x1}, big.NewInt(1)) + assert.Error(t, err) // EOF, we dont return anything from the server, because of error iterate over all providers + assert.Nil(t, balance) + wg.Wait() +} diff --git a/rpc/network/network.go b/rpc/network/network.go index 682deee65..e4cc69419 100644 --- a/rpc/network/network.go +++ b/rpc/network/network.go @@ -169,6 +169,7 @@ func (nm *Manager) Init(networks []params.Network) error { if err != nil { errors += fmt.Sprintf("error updating network original url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error()) } + break } } @@ -230,12 +231,15 @@ func (nm *Manager) Find(chainID uint64) *params.Network { if len(networks) != 1 || err != nil { return nil } + setDefaultRPCURL(networks, nm.configuredNetworks) return networks[0] } func (nm *Manager) GetAll() ([]*params.Network, error) { query := newNetworksQuery() - return query.exec(nm.db) + networks, err := query.exec(nm.db) + setDefaultRPCURL(networks, nm.configuredNetworks) + return networks, err } func (nm *Manager) Get(onlyEnabled bool) ([]*params.Network, error) { @@ -282,6 +286,12 @@ func (nm *Manager) Get(onlyEnabled bool) ([]*params.Network, error) { continue } } + + configuredNetwork, err := findNetwork(nm.configuredNetworks, network.ChainID) + if err != nil { + addDefaultRPCURL(network, configuredNetwork) + } + results = append(results, network) } @@ -355,3 +365,28 @@ func (nm *Manager) GetActiveNetworks() ([]*params.Network, error) { return availableNetworks, nil } + +func findNetwork(networks []params.Network, chainID uint64) (params.Network, error) { + for _, network := range networks { + if network.ChainID == chainID { + return network, nil + } + } + return params.Network{}, fmt.Errorf("network not found") +} + +func addDefaultRPCURL(target *params.Network, source params.Network) { + target.DefaultRPCURL = source.DefaultRPCURL + target.DefaultFallbackURL = source.DefaultFallbackURL +} + +func setDefaultRPCURL(target []*params.Network, source []params.Network) { + for i := range target { + for j := range source { + if target[i].ChainID == source[j].ChainID { + addDefaultRPCURL(target[i], source[j]) + break + } + } + } +} diff --git a/services/connector/api_test.go b/services/connector/api_test.go index 26cae73f7..9483d5bc6 100644 --- a/services/connector/api_test.go +++ b/services/connector/api_test.go @@ -36,7 +36,7 @@ func setupTestAPI(t *testing.T) (*API, func()) { } client := gethrpc.DialInProc(server) - rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db) + rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db, nil) require.NoError(t, err) service := NewService(db, rpcClient, nil) diff --git a/services/connector/service_test.go b/services/connector/service_test.go index 859f47a2d..096c8f4a9 100644 --- a/services/connector/service_test.go +++ b/services/connector/service_test.go @@ -28,7 +28,7 @@ func TestNewService(t *testing.T) { } client := gethrpc.DialInProc(server) - rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db) + rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db, nil) require.NoError(t, err) service := NewService(db, rpcClient, rpcClient.NetworkManager) diff --git a/services/ens/api_test.go b/services/ens/api_test.go index 1dd9768be..520ca78e8 100644 --- a/services/ens/api_test.go +++ b/services/ens/api_test.go @@ -40,7 +40,7 @@ func setupTestAPI(t *testing.T) (*API, func()) { _ = client - rpcClient, err := statusRPC.NewClient(nil, 1, upstreamConfig, nil, db) + rpcClient, err := statusRPC.NewClient(nil, 1, upstreamConfig, nil, db, nil) require.NoError(t, err) // import account keys diff --git a/services/wallet/history/service_test.go b/services/wallet/history/service_test.go index e2f2c182e..24da23355 100644 --- a/services/wallet/history/service_test.go +++ b/services/wallet/history/service_test.go @@ -405,7 +405,7 @@ func Test_removeBalanceHistoryOnEventAccountRemoved(t *testing.T) { txServiceMockCtrl := gomock.NewController(t) server, _ := fake.NewTestServer(txServiceMockCtrl) client := gethrpc.DialInProc(server) - rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB) + rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB, nil) rpcClient.UpstreamChainID = chainID service := NewService(walletDB, accountsDB, &accountFeed, &walletFeed, rpcClient, nil, nil, nil) diff --git a/services/wallet/router/router_v2_test.go b/services/wallet/router/router_v2_test.go index 3ba364b74..214981883 100644 --- a/services/wallet/router/router_v2_test.go +++ b/services/wallet/router/router_v2_test.go @@ -90,7 +90,7 @@ func setupTestNetworkDB(t *testing.T) (*sql.DB, func()) { func setupRouter(t *testing.T) (*Router, func()) { db, cleanTmpDb := setupTestNetworkDB(t) - client, _ := rpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, defaultNetworks, db) + client, _ := rpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, defaultNetworks, db, nil) router := NewRouter(client, nil, nil, nil, nil, nil, nil, nil) diff --git a/services/wallet/thirdparty/cryptocompare/client.go b/services/wallet/thirdparty/cryptocompare/client.go index 84e5463fa..0fbac544d 100644 --- a/services/wallet/thirdparty/cryptocompare/client.go +++ b/services/wallet/thirdparty/cryptocompare/client.go @@ -7,13 +7,15 @@ import ( "net/url" "strings" + "github.com/status-im/status-go/buildinfo" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty/utils" ) -const baseURL = "https://min-api.cryptocompare.com" -const CryptoCompareStatusProxyURL = "https://cryptocompare.test.api.status.im" const extraParamStatus = "Status.im" +const baseURL = "https://min-api.cryptocompare.com" + +var CryptoCompareStatusProxyURL = fmt.Sprintf("https://%s.api.status.im/cryptocompare/", buildinfo.ApiProxyStageName) type HistoricalPricesContainer struct { Aggregated bool `json:"Aggregated"` diff --git a/services/wallet/token/token_test.go b/services/wallet/token/token_test.go index 1b9b1ba97..b594206be 100644 --- a/services/wallet/token/token_test.go +++ b/services/wallet/token/token_test.go @@ -331,7 +331,7 @@ func Test_removeTokenBalanceOnEventAccountRemoved(t *testing.T) { txServiceMockCtrl := gomock.NewController(t) server, _ := fake.NewTestServer(txServiceMockCtrl) client := gethrpc.DialInProc(server) - rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB) + rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB, nil) rpcClient.UpstreamChainID = chainID nm := network.NewManager(appDB) mediaServer, err := mediaserver.NewMediaServer(appDB, nil, nil, walletDB) diff --git a/services/wallet/transfer/commands_sequential_test.go b/services/wallet/transfer/commands_sequential_test.go index e85448847..43210092c 100644 --- a/services/wallet/transfer/commands_sequential_test.go +++ b/services/wallet/transfer/commands_sequential_test.go @@ -1076,7 +1076,7 @@ func setupFindBlocksCommand(t *testing.T, accountAddress common.Address, fromBlo return nil } - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) client.SetClient(tc.NetworkID(), tc) tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) tokenManager.SetTokens([]*token.Token{ @@ -1339,7 +1339,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) { currentBlock: 100, } - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) client.SetClient(tc.NetworkID(), tc) tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) @@ -1463,7 +1463,7 @@ func TestFetchNewBlocksCommand_findBlocksWithEthTransfers(t *testing.T) { currentBlock: 100, } - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) client.SetClient(tc.NetworkID(), tc) tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) @@ -1543,7 +1543,7 @@ func TestFetchNewBlocksCommand_nonceDetection(t *testing.T) { mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) require.NoError(t, err) - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) client.SetClient(tc.NetworkID(), tc) tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) @@ -1657,7 +1657,7 @@ func TestFetchNewBlocksCommand(t *testing.T) { } //tc.printPreparedData = true - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) client.SetClient(tc.NetworkID(), tc) tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) @@ -1796,7 +1796,7 @@ func TestLoadBlocksAndTransfersCommand_FiniteFinishedInfiniteRunning(t *testing. db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(t, err) - client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db) + client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) maker, _ := contracts.NewContractMaker(client) wdb := NewDB(db) diff --git a/services/web3provider/api_test.go b/services/web3provider/api_test.go index d122a9dad..692e358fb 100644 --- a/services/web3provider/api_test.go +++ b/services/web3provider/api_test.go @@ -45,7 +45,7 @@ func setupTestAPI(t *testing.T) (*API, func()) { server, _ := fake.NewTestServer(txServiceMockCtrl) client := gethrpc.DialInProc(server) - rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db) + rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db, nil) require.NoError(t, err) // import account keys diff --git a/transactions/transactor_test.go b/transactions/transactor_test.go index 28f5272fb..68bac9e39 100644 --- a/transactions/transactor_test.go +++ b/transactions/transactor_test.go @@ -55,7 +55,7 @@ func (s *TransactorSuite) SetupTest() { chainID := gethparams.AllEthashProtocolChanges.ChainID.Uint64() db, err := sqlite.OpenUnecryptedDB(sqlite.InMemoryPath) // dummy to make rpc.Client happy s.Require().NoError(err) - rpcClient, _ := rpc.NewClient(s.client, chainID, params.UpstreamRPCConfig{}, nil, db) + rpcClient, _ := rpc.NewClient(s.client, chainID, params.UpstreamRPCConfig{}, nil, db, nil) rpcClient.UpstreamChainID = chainID nodeConfig, err := utils.MakeTestNodeConfigWithDataDir("", "/tmp", chainID) s.Require().NoError(err)