-
Notifications
You must be signed in to change notification settings - Fork 249
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(config)_: extract rpc_provider_persistence + tests
* 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
Showing
14 changed files
with
1,729 additions
and
31 deletions.
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
appdatabase/migrations/sql/1733400346_add_rpc_providers.up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.