feat(wallet)_: add status proxy RPC urls for blockchain providers

Replace the status proxy URL for cryptocompare.
This commit is contained in:
Ivan Belyakov 2024-07-31 08:21:11 +02:00 committed by IvanBelyakoff
parent 780e3e55f4
commit b74d9e6b4e
23 changed files with 309 additions and 107 deletions

View File

@ -61,6 +61,11 @@ GIT_AUTHOR := $(shell git config user.email || echo $$USER)
ENABLE_METRICS ?= true ENABLE_METRICS ?= true
BUILD_TAGS ?= gowaku_no_rln 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%=%) \ 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.GitCommit=$(GIT_COMMIT) \
-X github.com/status-im/status-go/params.IpfsGatewayURL=$(IPFS_GATEWAY_URL) \ -X github.com/status-im/status-go/params.IpfsGatewayURL=$(IPFS_GATEWAY_URL) \

View File

@ -1,9 +1,11 @@
package api package api
import ( import (
"fmt"
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "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/params"
"github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/requests"
) )
@ -27,6 +29,8 @@ var ganacheTokenAddress = common.HexToAddress("0x8571Ddc46b10d31EF963aF49b6C7799
var mainnet = params.Network{ var mainnet = params.Network{
ChainID: mainnetChainID, ChainID: mainnetChainID,
ChainName: "Mainnet", 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/", RPCURL: "https://eth-archival.rpc.grove.city/v1/",
FallbackURL: "https://mainnet.infura.io/v3/", FallbackURL: "https://mainnet.infura.io/v3/",
BlockExplorerURL: "https://etherscan.io/", BlockExplorerURL: "https://etherscan.io/",
@ -63,6 +67,8 @@ var goerli = params.Network{
var sepolia = params.Network{ var sepolia = params.Network{
ChainID: sepoliaChainID, ChainID: sepoliaChainID,
ChainName: "Mainnet", 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/", RPCURL: "https://sepolia-archival.rpc.grove.city/v1/",
FallbackURL: "https://sepolia.infura.io/v3/", FallbackURL: "https://sepolia.infura.io/v3/",
BlockExplorerURL: "https://sepolia.etherscan.io/", BlockExplorerURL: "https://sepolia.etherscan.io/",
@ -81,6 +87,8 @@ var sepolia = params.Network{
var optimism = params.Network{ var optimism = params.Network{
ChainID: optimismChainID, ChainID: optimismChainID,
ChainName: "Optimism", 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/", RPCURL: "https://optimism-archival.rpc.grove.city/v1/",
FallbackURL: "https://optimism-mainnet.infura.io/v3/", FallbackURL: "https://optimism-mainnet.infura.io/v3/",
BlockExplorerURL: "https://optimistic.etherscan.io", BlockExplorerURL: "https://optimistic.etherscan.io",
@ -117,6 +125,8 @@ var optimismGoerli = params.Network{
var optimismSepolia = params.Network{ var optimismSepolia = params.Network{
ChainID: optimismSepoliaChainID, ChainID: optimismSepoliaChainID,
ChainName: "Optimism", 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/", RPCURL: "https://optimism-sepolia-archival.rpc.grove.city/v1/",
FallbackURL: "https://optimism-sepolia.infura.io/v3/", FallbackURL: "https://optimism-sepolia.infura.io/v3/",
BlockExplorerURL: "https://sepolia-optimism.etherscan.io/", BlockExplorerURL: "https://sepolia-optimism.etherscan.io/",
@ -135,6 +145,8 @@ var optimismSepolia = params.Network{
var arbitrum = params.Network{ var arbitrum = params.Network{
ChainID: arbitrumChainID, ChainID: arbitrumChainID,
ChainName: "Arbitrum", 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/", RPCURL: "https://arbitrum-one.rpc.grove.city/v1/",
FallbackURL: "https://arbitrum-mainnet.infura.io/v3/", FallbackURL: "https://arbitrum-mainnet.infura.io/v3/",
BlockExplorerURL: "https://arbiscan.io/", BlockExplorerURL: "https://arbiscan.io/",
@ -171,6 +183,8 @@ var arbitrumGoerli = params.Network{
var arbitrumSepolia = params.Network{ var arbitrumSepolia = params.Network{
ChainID: arbitrumSepoliaChainID, ChainID: arbitrumSepoliaChainID,
ChainName: "Arbitrum", 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/", RPCURL: "https://arbitrum-sepolia-archival.rpc.grove.city/v1/",
FallbackURL: "https://arbitrum-sepolia.infura.io/v3/", FallbackURL: "https://arbitrum-sepolia.infura.io/v3/",
BlockExplorerURL: "https://sepolia-explorer.arbitrum.io/", BlockExplorerURL: "https://sepolia-explorer.arbitrum.io/",

View File

@ -161,7 +161,7 @@ func SetFleet(fleet string, nodeConfig *params.NodeConfig) error {
return nil return nil
} }
func buildWalletConfig(request *requests.WalletSecretsConfig) params.WalletConfig { func buildWalletConfig(request *requests.WalletSecretsConfig, statusProxyEnabled bool) params.WalletConfig {
walletConfig := params.WalletConfig{ walletConfig := params.WalletConfig{
Enabled: true, Enabled: true,
AlchemyAPIKeys: make(map[uint64]string), AlchemyAPIKeys: make(map[uint64]string),
@ -220,6 +220,14 @@ func buildWalletConfig(request *requests.WalletSecretsConfig) params.WalletConfi
if request.StatusProxyMarketPassword != "" { if request.StatusProxyMarketPassword != "" {
walletConfig.StatusProxyMarketPassword = 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 return walletConfig
} }
@ -287,7 +295,7 @@ func defaultNodeConfig(installationID string, request *requests.CreateAccount, o
nodeConfig.MaxPeers = DefaultMaxPeers nodeConfig.MaxPeers = DefaultMaxPeers
nodeConfig.MaxPendingPeers = DefaultMaxPendingPeers nodeConfig.MaxPendingPeers = DefaultMaxPendingPeers
nodeConfig.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig) nodeConfig.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled)
nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true} nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true} nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}

View File

@ -601,7 +601,7 @@ func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
KeycardPairingDataFile: DefaultKeycardPairingDataFile, KeycardPairingDataFile: DefaultKeycardPairingDataFile,
} }
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig) defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled)
err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg) err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg)
if err != nil { if err != nil {

View File

@ -0,0 +1,5 @@
//go:build !release
package buildinfo
var ApiProxyStageName = "test"

View File

@ -0,0 +1,5 @@
//go:build release
package buildinfo
var ApiProxyStageName = "prod"

View File

@ -314,7 +314,20 @@ func (n *StatusNode) setupRPCClient() (err error) {
if err != nil { if err != nil {
return 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 { if err != nil {
return return
} }

View File

@ -310,6 +310,21 @@ type UpstreamRPCConfig struct {
URL string 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 // NodeConfig
// ---------- // ----------
@ -531,6 +546,8 @@ type TokenOverride struct {
type Network struct { type Network struct {
ChainID uint64 `json:"chainId"` ChainID uint64 `json:"chainId"`
ChainName string `json:"chainName"` ChainName string `json:"chainName"`
DefaultRPCURL string `json:"defaultRpcUrl"` // proxy rpc url
DefaultFallbackURL string `json:"defaultFallbackURL"` // proxy fallback url
RPCURL string `json:"rpcUrl"` RPCURL string `json:"rpcUrl"`
OriginalRPCURL string `json:"originalRpcUrl"` OriginalRPCURL string `json:"originalRpcUrl"`
FallbackURL string `json:"fallbackURL"` FallbackURL string `json:"fallbackURL"`
@ -560,6 +577,9 @@ type WalletConfig struct {
InfuraAPIKeySecret string `json:"InfuraAPIKeySecret"` InfuraAPIKeySecret string `json:"InfuraAPIKeySecret"`
StatusProxyMarketUser string `json:"StatusProxyMarketUser"` StatusProxyMarketUser string `json:"StatusProxyMarketUser"`
StatusProxyMarketPassword string `json:"StatusProxyMarketPassword"` StatusProxyMarketPassword string `json:"StatusProxyMarketPassword"`
StatusProxyBlockchainUser string `json:"StatusProxyBlockchainUser"`
StatusProxyBlockchainPassword string `json:"StatusProxyBlockchainPassword"`
StatusProxyEnabled bool `json:"StatusProxyEnabled"`
EnableCelerBridge bool `json:"EnableCelerBridge"` EnableCelerBridge bool `json:"EnableCelerBridge"`
} }

View File

@ -86,6 +86,7 @@ type CreateAccount struct {
KeycardInstanceUID string `json:"keycardInstanceUID"` KeycardInstanceUID string `json:"keycardInstanceUID"`
KeycardPairingDataFile *string `json:"keycardPairingDataFile"` KeycardPairingDataFile *string `json:"keycardPairingDataFile"`
StatusProxyEnabled bool `json:"statusProxyEnabled"`
} }
type WalletSecretsConfig struct { type WalletSecretsConfig struct {
@ -107,6 +108,8 @@ type WalletSecretsConfig struct {
AlchemyOptimismSepoliaToken string `json:"alchemyOptimismSepoliaToken"` AlchemyOptimismSepoliaToken string `json:"alchemyOptimismSepoliaToken"`
StatusProxyMarketUser string `json:"statusProxyMarketUser"` StatusProxyMarketUser string `json:"statusProxyMarketUser"`
StatusProxyMarketPassword string `json:"statusProxyMarketPassword"` StatusProxyMarketPassword string `json:"statusProxyMarketPassword"`
StatusProxyBlockchainUser string `json:"statusProxyBlockchainUser"`
StatusProxyBlockchainPassword string `json:"statusProxyBlockchainPassword"`
// Testing // Testing
GanacheURL string `json:"ganacheURL"` GanacheURL string `json:"ganacheURL"`

View File

@ -36,6 +36,7 @@ type Login struct {
WalletSecretsConfig WalletSecretsConfig
APIConfig *APIConfig `json:"apiConfig"` APIConfig *APIConfig `json:"apiConfig"`
StatusProxyEnabled bool `json:"statusProxyEnabled"`
} }
func (c *Login) Validate() error { func (c *Login) Validate() error {

View File

@ -3,12 +3,12 @@ package rpc
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
@ -26,6 +26,11 @@ import (
const ( const (
// DefaultCallTimeout is a default timeout for an RPC call // DefaultCallTimeout is a default timeout for an RPC call
DefaultCallTimeout = time.Minute DefaultCallTimeout = time.Minute
// Names of providers
providerGrove = "grove"
providerInfura = "infura"
ProviderStatusProxy = "status-proxy"
) )
// List of RPC client errors. // List of RPC client errors.
@ -68,6 +73,7 @@ type Client struct {
log log.Logger 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 // 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 // Client is safe for concurrent use and will automatically
// reconnect to the server if connection is lost. // 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 var err error
log := log.New("package", "status-go/rpc.Client") 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), rpcClients: make(map[uint64]chain.ClientInterface),
limiterPerProvider: make(map[string]*chain.RPCRpsLimiter), limiterPerProvider: make(map[string]*chain.RPCRpsLimiter),
log: log, log: log,
providerConfigs: providerConfigs,
} }
if upstream.Enabled { if upstream.Enabled {
@ -133,18 +140,6 @@ func (c *Client) SetWalletNotifier(notifier func(chainID uint64, message string)
c.walletNotifier = notifier 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) { func extractHostAndPortFromURL(inputURL string) (string, error) {
parsedURL, err := url.Parse(inputURL) parsedURL, err := url.Parse(inputURL)
if err != nil { if err != nil {
@ -154,21 +149,26 @@ func extractHostAndPortFromURL(inputURL string) (string, error) {
return parsedURL.Host, nil return parsedURL.Host, nil
} }
func (c *Client) getRPCRpsLimiter(URL string) (*chain.RPCRpsLimiter, error) { func (c *Client) getRPCRpsLimiter(key string) (*chain.RPCRpsLimiter, error) {
apiKey, err := extractLastParamFromURL(URL)
if err != nil {
return nil, err
}
c.rpsLimiterMutex.Lock() c.rpsLimiterMutex.Lock()
defer c.rpsLimiterMutex.Unlock() defer c.rpsLimiterMutex.Unlock()
if limiter, ok := c.limiterPerProvider[apiKey]; ok { if limiter, ok := c.limiterPerProvider[key]; ok {
return limiter, nil return limiter, nil
} }
limiter := chain.NewRPCRpsLimiter() limiter := chain.NewRPCRpsLimiter()
c.limiterPerProvider[apiKey] = limiter c.limiterPerProvider[key] = limiter
return limiter, nil 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) { func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) {
c.rpcClientsMutex.Lock() c.rpcClientsMutex.Lock()
defer c.rpcClientsMutex.Unlock() 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) return nil, fmt.Errorf("could not find network: %d", chainID)
} }
rpcClient, err := gethrpc.Dial(network.RPCURL) ethClients := c.getEthClents(network)
if err != nil { if len(ethClients) == 0 {
return nil, fmt.Errorf("dial upstream server: %s", err) return nil, fmt.Errorf("could not find any RPC URL for chain: %d", chainID)
}
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))
} }
client := chain.NewClient(ethClients, chainID) client := chain.NewClient(ethClients, chainID)
@ -234,6 +198,69 @@ func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, err
return client, nil 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 // Ethclient returns ethclient.Client per chain
func (c *Client) EthClient(chainID uint64) (chain.ClientInterface, error) { func (c *Client) EthClient(chainID uint64) (chain.ClientInterface, error) {
client, err := c.getClientUsingCache(chainID) client, err := c.getClientUsingCache(chainID)

View File

@ -3,17 +3,22 @@ package rpc
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/base64"
"fmt" "fmt"
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/status-im/status-go/appdatabase" "github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/helpers"
"github.com/ethereum/go-ethereum/common"
gethrpc "github.com/ethereum/go-ethereum/rpc" gethrpc "github.com/ethereum/go-ethereum/rpc"
) )
@ -39,7 +44,7 @@ func TestBlockedRoutesCall(t *testing.T) {
gethRPCClient, err := gethrpc.Dial(ts.URL) gethRPCClient, err := gethrpc.Dial(ts.URL)
require.NoError(t, err) 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) require.NoError(t, err)
for _, m := range blockedMethods { for _, m := range blockedMethods {
@ -78,7 +83,7 @@ func TestBlockedRoutesRawCall(t *testing.T) {
gethRPCClient, err := gethrpc.Dial(ts.URL) gethRPCClient, err := gethrpc.Dial(ts.URL)
require.NoError(t, err) 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) require.NoError(t, err)
for _, m := range blockedMethods { for _, m := range blockedMethods {
@ -105,7 +110,7 @@ func TestUpdateUpstreamURL(t *testing.T) {
gethRPCClient, err := gethrpc.Dial(ts.URL) gethRPCClient, err := gethrpc.Dial(ts.URL)
require.NoError(t, err) 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.NoError(t, err)
require.Equal(t, ts.URL, c.upstreamURL) require.Equal(t, ts.URL, c.upstreamURL)
@ -132,3 +137,62 @@ func createTestServer(resp string) *httptest.Server {
fmt.Fprintln(w, resp) 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()
}

View File

@ -169,6 +169,7 @@ func (nm *Manager) Init(networks []params.Network) error {
if err != nil { if err != nil {
errors += fmt.Sprintf("error updating network original url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error()) errors += fmt.Sprintf("error updating network original url for ChainID: %d, %s", currentNetworks[j].ChainID, err.Error())
} }
break break
} }
} }
@ -230,12 +231,15 @@ func (nm *Manager) Find(chainID uint64) *params.Network {
if len(networks) != 1 || err != nil { if len(networks) != 1 || err != nil {
return nil return nil
} }
setDefaultRPCURL(networks, nm.configuredNetworks)
return networks[0] return networks[0]
} }
func (nm *Manager) GetAll() ([]*params.Network, error) { func (nm *Manager) GetAll() ([]*params.Network, error) {
query := newNetworksQuery() 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) { func (nm *Manager) Get(onlyEnabled bool) ([]*params.Network, error) {
@ -282,6 +286,12 @@ func (nm *Manager) Get(onlyEnabled bool) ([]*params.Network, error) {
continue continue
} }
} }
configuredNetwork, err := findNetwork(nm.configuredNetworks, network.ChainID)
if err != nil {
addDefaultRPCURL(network, configuredNetwork)
}
results = append(results, network) results = append(results, network)
} }
@ -355,3 +365,28 @@ func (nm *Manager) GetActiveNetworks() ([]*params.Network, error) {
return availableNetworks, nil 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
}
}
}
}

View File

@ -36,7 +36,7 @@ func setupTestAPI(t *testing.T) (*API, func()) {
} }
client := gethrpc.DialInProc(server) 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) require.NoError(t, err)
service := NewService(db, rpcClient, nil) service := NewService(db, rpcClient, nil)

View File

@ -28,7 +28,7 @@ func TestNewService(t *testing.T) {
} }
client := gethrpc.DialInProc(server) 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) require.NoError(t, err)
service := NewService(db, rpcClient, rpcClient.NetworkManager) service := NewService(db, rpcClient, rpcClient.NetworkManager)

View File

@ -40,7 +40,7 @@ func setupTestAPI(t *testing.T) (*API, func()) {
_ = client _ = client
rpcClient, err := statusRPC.NewClient(nil, 1, upstreamConfig, nil, db) rpcClient, err := statusRPC.NewClient(nil, 1, upstreamConfig, nil, db, nil)
require.NoError(t, err) require.NoError(t, err)
// import account keys // import account keys

View File

@ -405,7 +405,7 @@ func Test_removeBalanceHistoryOnEventAccountRemoved(t *testing.T) {
txServiceMockCtrl := gomock.NewController(t) txServiceMockCtrl := gomock.NewController(t)
server, _ := fake.NewTestServer(txServiceMockCtrl) server, _ := fake.NewTestServer(txServiceMockCtrl)
client := gethrpc.DialInProc(server) 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 rpcClient.UpstreamChainID = chainID
service := NewService(walletDB, accountsDB, &accountFeed, &walletFeed, rpcClient, nil, nil, nil) service := NewService(walletDB, accountsDB, &accountFeed, &walletFeed, rpcClient, nil, nil, nil)

View File

@ -90,7 +90,7 @@ func setupTestNetworkDB(t *testing.T) (*sql.DB, func()) {
func setupRouter(t *testing.T) (*Router, func()) { func setupRouter(t *testing.T) (*Router, func()) {
db, cleanTmpDb := setupTestNetworkDB(t) 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) router := NewRouter(client, nil, nil, nil, nil, nil, nil, nil)

View File

@ -7,13 +7,15 @@ import (
"net/url" "net/url"
"strings" "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"
"github.com/status-im/status-go/services/wallet/thirdparty/utils" "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 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 { type HistoricalPricesContainer struct {
Aggregated bool `json:"Aggregated"` Aggregated bool `json:"Aggregated"`

View File

@ -331,7 +331,7 @@ func Test_removeTokenBalanceOnEventAccountRemoved(t *testing.T) {
txServiceMockCtrl := gomock.NewController(t) txServiceMockCtrl := gomock.NewController(t)
server, _ := fake.NewTestServer(txServiceMockCtrl) server, _ := fake.NewTestServer(txServiceMockCtrl)
client := gethrpc.DialInProc(server) 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 rpcClient.UpstreamChainID = chainID
nm := network.NewManager(appDB) nm := network.NewManager(appDB)
mediaServer, err := mediaserver.NewMediaServer(appDB, nil, nil, walletDB) mediaServer, err := mediaserver.NewMediaServer(appDB, nil, nil, walletDB)

View File

@ -1076,7 +1076,7 @@ func setupFindBlocksCommand(t *testing.T, accountAddress common.Address, fromBlo
return nil 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) 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 := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db))
tokenManager.SetTokens([]*token.Token{ tokenManager.SetTokens([]*token.Token{
@ -1339,7 +1339,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) {
currentBlock: 100, 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) 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 := 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, 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) 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 := 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) mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
require.NoError(t, err) 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) 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 := 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 //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) 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 := 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{}) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) 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) maker, _ := contracts.NewContractMaker(client)
wdb := NewDB(db) wdb := NewDB(db)

View File

@ -45,7 +45,7 @@ func setupTestAPI(t *testing.T) (*API, func()) {
server, _ := fake.NewTestServer(txServiceMockCtrl) server, _ := fake.NewTestServer(txServiceMockCtrl)
client := gethrpc.DialInProc(server) 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) require.NoError(t, err)
// import account keys // import account keys

View File

@ -55,7 +55,7 @@ func (s *TransactorSuite) SetupTest() {
chainID := gethparams.AllEthashProtocolChanges.ChainID.Uint64() chainID := gethparams.AllEthashProtocolChanges.ChainID.Uint64()
db, err := sqlite.OpenUnecryptedDB(sqlite.InMemoryPath) // dummy to make rpc.Client happy db, err := sqlite.OpenUnecryptedDB(sqlite.InMemoryPath) // dummy to make rpc.Client happy
s.Require().NoError(err) 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 rpcClient.UpstreamChainID = chainID
nodeConfig, err := utils.MakeTestNodeConfigWithDataDir("", "/tmp", chainID) nodeConfig, err := utils.MakeTestNodeConfigWithDataDir("", "/tmp", chainID)
s.Require().NoError(err) s.Require().NoError(err)