Skip to content

Commit

Permalink
chore(config)_: extract rpc_provider_persistence + tests
Browse files Browse the repository at this point in the history
* Add rpc_providers table, migration
* add RpcProvider type
* deprecate old rpc fields in networks, add RpcProviders list
* add persistence packages for rpc_providers, networks
* Tests

(without integration)
  • Loading branch information
friofry committed Dec 6, 2024
1 parent 9a94a82 commit 50ee868
Show file tree
Hide file tree
Showing 14 changed files with 1,729 additions and 31 deletions.
13 changes: 13 additions & 0 deletions appdatabase/migrations/sql/1733400346_add_rpc_providers.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS rpc_providers (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- Unique provider ID (sorting)
chain_id INTEGER NOT NULL CHECK (chain_id > 0), -- Chain ID for the network
name TEXT NOT NULL CHECK (LENGTH(name) > 0), -- Provider name
url TEXT NOT NULL CHECK (LENGTH(url) > 0), -- Provider URL
enable_rps_limiter BOOLEAN NOT NULL DEFAULT FALSE, -- Enable RPS limiter
type TEXT NOT NULL DEFAULT 'user', -- Provider type: embedded-proxy, embedded-direct, user
enabled BOOLEAN NOT NULL DEFAULT TRUE, -- Whether the provider is active or not
auth_type TEXT NOT NULL DEFAULT 'no-auth', -- Authentication type: no-auth, basic-auth, token-auth
auth_login TEXT, -- BasicAuth login (nullable)
auth_password TEXT, -- Password for BasicAuth (nullable)
auth_token TEXT -- Token for TokenAuth (nullable)
);
32 changes: 1 addition & 31 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"go.uber.org/zap"
validator "gopkg.in/go-playground/validator.v9"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/params"

Expand Down Expand Up @@ -514,35 +513,6 @@ type NodeConfig struct {
ProcessBackedupMessages bool
}

type TokenOverride struct {
Symbol string `json:"symbol"`
Address common.Address `json:"address"`
}

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
DefaultFallbackURL2 string `json:"defaultFallbackURL2"` // second proxy fallback url
RPCURL string `json:"rpcUrl"`
OriginalRPCURL string `json:"originalRpcUrl"`
FallbackURL string `json:"fallbackURL"`
OriginalFallbackURL string `json:"originalFallbackURL"`
BlockExplorerURL string `json:"blockExplorerUrl,omitempty"`
IconURL string `json:"iconUrl,omitempty"`
NativeCurrencyName string `json:"nativeCurrencyName,omitempty"`
NativeCurrencySymbol string `json:"nativeCurrencySymbol,omitempty"`
NativeCurrencyDecimals uint64 `json:"nativeCurrencyDecimals"`
IsTest bool `json:"isTest"`
Layer uint64 `json:"layer"`
Enabled bool `json:"enabled"`
ChainColor string `json:"chainColor"`
ShortName string `json:"shortName"`
TokenOverrides []TokenOverride `json:"tokenOverrides"`
RelatedChainID uint64 `json:"relatedChainId"`
}

// WalletConfig extra configuration for wallet.Service.
type WalletConfig struct {
Enabled bool
Expand Down Expand Up @@ -598,7 +568,7 @@ type MailserversConfig struct {
Enabled bool
}

// ProviderConfig extra configuration for provider.Service
// ProviderAuthConfig extra configuration for provider.Service
type Web3ProviderConfig struct {
Enabled bool
}
Expand Down
95 changes: 95 additions & 0 deletions params/network_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package params

import "github.com/ethereum/go-ethereum/common"

// RpcProviderAuthType defines the different types of authentication for RPC providers
type RpcProviderAuthType string

const (
NoAuth RpcProviderAuthType = "no-auth" // No authentication
BasicAuth RpcProviderAuthType = "basic-auth" // HTTP Header "Authorization: Basic base64(username:password)"
TokenAuth RpcProviderAuthType = "token-auth" // URL Token-based authentication "https://api.example.com/YOUR_TOKEN"
)

// RpcProviderType defines the type of RPC provider
type RpcProviderType string

const (
EmbeddedProxyProviderType RpcProviderType = "embedded-proxy" // Proxy-based RPC provider
EmbeddedDirectProviderType RpcProviderType = "embedded-direct" // Direct RPC provider
UserProviderType RpcProviderType = "user" // User-defined RPC provider
)

// RpcProvider represents an RPC provider configuration with various options
type RpcProvider struct {
ID int64 `json:"id" validate:"omitempty"` // Auto-increment ID (for sorting order)
ChainID uint64 `json:"chainId" validate:"required,gt=0"` // Chain ID of the network
Name string `json:"name" validate:"required,min=1"` // Provider name for identification
URL string `json:"url" validate:"required,url"` // Current Provider URL
EnableRPSLimiter bool `json:"enableRpsLimiter"` // Enable RPC rate limiting for this provider
Type RpcProviderType `json:"type" validate:"required,oneof=embedded-proxy embedded-direct user"`
Enabled bool `json:"enabled"` // Whether the provider is enabled
// Authentication
AuthType RpcProviderAuthType `json:"authType" validate:"required,oneof=no-auth basic-auth token-auth"` // Type of authentication
AuthLogin string `json:"authLogin" validate:"omitempty,min=1"` // Login for BasicAuth (empty string if not used)
AuthPassword string `json:"authPassword" validate:"omitempty,min=1"` // Password for BasicAuth (empty string if not used)
AuthToken string `json:"authToken" validate:"omitempty,min=1"` // Token for TokenAuth (empty string if not used)
}

type TokenOverride struct {
Symbol string `json:"symbol"`
Address common.Address `json:"address"`
}

type Network struct {
ChainID uint64 `json:"chainId" validate:"required,gt=0"`
ChainName string `json:"chainName" validate:"required,min=1"`
RpcProviders []RpcProvider `json:"rpcProviders" validate:"dive,required"` // List of RPC providers, in the order in which they are accessed

// Deprecated fields (kept for backward compatibility)
// FIXME: remove deprecated fields (keeping until client integrate this). TODO: add ticket URL
DefaultRPCURL string `json:"defaultRpcUrl" validate:"omitempty,url"` // Deprecated: proxy rpc url
DefaultFallbackURL string `json:"defaultFallbackURL" validate:"omitempty,url"` // Deprecated: proxy fallback url
DefaultFallbackURL2 string `json:"defaultFallbackURL2" validate:"omitempty,url"` // Deprecated: second proxy fallback url
RPCURL string `json:"rpcUrl" validate:"omitempty,url"` // Deprecated: direct rpc url
OriginalRPCURL string `json:"originalRpcUrl" validate:"omitempty,url"` // Deprecated: direct rpc url if user overrides RPCURL
FallbackURL string `json:"fallbackURL" validate:"omitempty,url"` // Deprecated
OriginalFallbackURL string `json:"originalFallbackURL" validate:"omitempty,url"` // Deprecated

BlockExplorerURL string `json:"blockExplorerUrl,omitempty" validate:"omitempty,url"`
IconURL string `json:"iconUrl,omitempty" validate:"omitempty"`
NativeCurrencyName string `json:"nativeCurrencyName,omitempty" validate:"omitempty,min=1"`
NativeCurrencySymbol string `json:"nativeCurrencySymbol,omitempty" validate:"omitempty,min=1"`
NativeCurrencyDecimals uint64 `json:"nativeCurrencyDecimals" validate:"omitempty"`
IsTest bool `json:"isTest"`
Layer uint64 `json:"layer" validate:"omitempty"`
Enabled bool `json:"enabled"`
ChainColor string `json:"chainColor" validate:"omitempty"`
ShortName string `json:"shortName" validate:"omitempty,min=1"`
TokenOverrides []TokenOverride `json:"tokenOverrides" validate:"omitempty,dive"`
RelatedChainID uint64 `json:"relatedChainId" validate:"omitempty"`
}

func newRpcProvider(chainID uint64, name, url string, enableRpsLimiter bool, providerType RpcProviderType) *RpcProvider {
return &RpcProvider{
ChainID: chainID,
Name: name,
URL: url,
EnableRPSLimiter: enableRpsLimiter,
Type: providerType,
Enabled: true,
AuthType: NoAuth,
}
}

func NewUserProvider(chainID uint64, name, url string, enableRpsLimiter bool) *RpcProvider {
return newRpcProvider(chainID, name, url, enableRpsLimiter, UserProviderType)
}

func NewProxyProvider(chainID uint64, name, url string, enableRpsLimiter bool) *RpcProvider {
return newRpcProvider(chainID, name, url, enableRpsLimiter, EmbeddedProxyProviderType)
}

func NewDirectProvider(chainID uint64, name, url string, enableRpsLimiter bool) *RpcProvider {
return newRpcProvider(chainID, name, url, enableRpsLimiter, EmbeddedDirectProviderType)
}
184 changes: 184 additions & 0 deletions params/networkhelper/provider_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package networkhelper

import (
"net/url"
"strings"

"github.com/status-im/status-go/params"
)

// MergeProvidersPreservingUsersAndEnabledState merges new embedded providers with the current ones,
// preserving user-defined providers and maintaining the Enabled state.
func MergeProvidersPreservingUsersAndEnabledState(currentProviders, newProviders []params.RpcProvider) []params.RpcProvider {
// Create a map for quick lookup of the Enabled state by Name
enabledState := make(map[string]bool)
for _, provider := range currentProviders {
enabledState[provider.Name] = provider.Enabled
}

// Update the Enabled field in newProviders if the Name matches
for i := range newProviders {
if enabled, exists := enabledState[newProviders[i].Name]; exists {
newProviders[i].Enabled = enabled
}
}

// Retain current providers of type UserProviderType and add them to the beginning of the list
var mergedProviders []params.RpcProvider
for _, provider := range currentProviders {
if provider.Type == params.UserProviderType {
mergedProviders = append(mergedProviders, provider)
}
}

// Add the updated newProviders
mergedProviders = append(mergedProviders, newProviders...)

return mergedProviders
}

// ToggleUserProviders enables or disables all user-defined providers and disables other types.
func ToggleUserProviders(providers []params.RpcProvider, enabled bool) []params.RpcProvider {
for i := range providers {
if providers[i].Type == params.UserProviderType {
providers[i].Enabled = enabled
} else {
providers[i].Enabled = !enabled
}
}
return providers
}

// GetEmbeddedProviders returns the embedded providers from the list.
func GetEmbeddedProviders(providers []params.RpcProvider) []params.RpcProvider {
var embeddedProviders []params.RpcProvider
for _, provider := range providers {
if provider.Type != params.UserProviderType {
embeddedProviders = append(embeddedProviders, provider)
}
}
return embeddedProviders
}

// GetUserProviders returns the user-defined providers from the list.
func GetUserProviders(providers []params.RpcProvider) []params.RpcProvider {
var userProviders []params.RpcProvider
for _, provider := range providers {
if provider.Type == params.UserProviderType {
userProviders = append(userProviders, provider)
}
}
return userProviders
}

// ReplaceUserProviders replaces user-defined providers with new ones, retaining the rest of the providers.
func ReplaceUserProviders(currentProviders, newUserProviders []params.RpcProvider) []params.RpcProvider {
// Extract embedded providers from the current list
embeddedProviders := GetEmbeddedProviders(currentProviders)
userProviders := GetUserProviders(newUserProviders)

// Combine new user providers with the existing embedded providers
return append(userProviders, embeddedProviders...)
}

// ReplaceEmbeddedProviders replaces embedded providers with new ones, retaining user-defined providers.
func ReplaceEmbeddedProviders(currentProviders, newEmbeddedProviders []params.RpcProvider) []params.RpcProvider {
// Extract user-defined providers from the current list
userProviders := GetUserProviders(currentProviders)
embeddedProviders := GetEmbeddedProviders(newEmbeddedProviders)

// Combine existing user-defined providers with the new embedded providers
return append(userProviders, embeddedProviders...)
}

// OverrideEmbeddedProxyProviders updates all embedded-proxy providers in the given networks.
// It sets the `Enabled` flag and configures the `AuthLogin` and `AuthPassword` for each provider.
func OverrideEmbeddedProxyProviders(networks []params.Network, enabled bool, user, password string) []params.Network {
updatedNetworks := make([]params.Network, len(networks))
for i, network := range networks {
// Deep copy the network to avoid mutating the input slice
updatedNetwork := network
updatedProviders := make([]params.RpcProvider, len(network.RpcProviders))

// Update the embedded-proxy providers
for j, provider := range network.RpcProviders {
if provider.Type == params.EmbeddedProxyProviderType {
provider.Enabled = enabled
provider.AuthLogin = user
provider.AuthPassword = password
}
updatedProviders[j] = provider
}

updatedNetwork.RpcProviders = updatedProviders
updatedNetworks[i] = updatedNetwork
}

return updatedNetworks
}

func OverrideDirectProvidersAuth(networks []params.Network, authTokens map[string]string) []params.Network {
updatedNetworks := make([]params.Network, len(networks))
for i, network := range networks {
updatedNetwork := network
updatedProviders := make([]params.RpcProvider, len(network.RpcProviders))

for j, provider := range network.RpcProviders {
updatedProvider := provider

if provider.Type == params.EmbeddedDirectProviderType {
host, err := extractHost(provider.URL)
if err == nil {
for suffix, token := range authTokens {
if strings.HasSuffix(host, suffix) && token != "" {
updatedProvider.AuthType = params.TokenAuth
updatedProvider.AuthToken = token
break
}
}
}
}

updatedProviders[j] = updatedProvider
}

updatedNetwork.RpcProviders = updatedProviders
updatedNetworks[i] = updatedNetwork
}
return updatedNetworks
}

func OverrideGanacheToken(networks []params.Network, ganacheURL string, chainID uint64, tokenOverride params.TokenOverride) []params.Network {
updatedNetworks := make([]params.Network, len(networks))
for i, network := range networks {
updatedNetwork := network

if network.ChainID == chainID {
updatedProviders := make([]params.RpcProvider, len(network.RpcProviders))

for j, provider := range network.RpcProviders {
updatedProvider := provider
if ganacheURL != "" {
updatedProvider.URL = ganacheURL
}
updatedProviders[j] = updatedProvider
}

updatedNetwork.RpcProviders = updatedProviders
updatedNetwork.TokenOverrides = []params.TokenOverride{
tokenOverride,
}
}

updatedNetworks[i] = updatedNetwork
}
return updatedNetworks
}

func extractHost(providerURL string) (string, error) {
parsedURL, err := url.Parse(providerURL)
if err != nil {
return "", err
}
return parsedURL.Host, nil
}
Loading

0 comments on commit 50ee868

Please sign in to comment.