Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for multiple gateways #1

Merged
merged 21 commits into from
Mar 13, 2024
Merged
77 changes: 0 additions & 77 deletions .github/workflows/deploy.yaml

This file was deleted.

53 changes: 0 additions & 53 deletions .github/workflows/docker.yaml

This file was deleted.

10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/rpc-gateway
/app/rpc-gateway
/main
/tags
/vendor


.idea

config.yml
config_*

prometheus
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ linters-settings:
main:
allow:
- "$gostd"
- github.com/0xProject/rpc-gateway
- github.com/sygmaprotocol/rpc-gateway
- github.com/Shopify/toxiproxy
- github.com/ethereum/go-ethereum
- github.com/gorilla/mux
Expand Down
58 changes: 39 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
> [!CAUTION]
> The rpc-gateway is in development mode, and you should not consider it
> stable yet.
# RPC Gateway

## RPC Gateway
The rpc-gateway is a failover proxy designed for node providers. It ensures high availability and reliability by automatically rerouting requests to a backup node provider when health checks indicate the primary provider is down. This process ensures uninterrupted service even in the event of node provider failures.

The rpc-gateway is a failover proxy for node providers. When health checks
fail, the rpc-gateway automatically routes requests to a backup node provider.
## Caution

> :warning: The rpc-gateway is currently in development mode. It is not considered stable and should be used with caution in production environments.

## Overview

The rpc-gateway operates by continuously performing health checks on configured node providers. If the primary node provider fails these checks, the gateway will automatically attempt to route requests to the next available provider based on a predefined failover sequence.

```mermaid
sequenceDiagram
Expand All @@ -27,39 +30,56 @@ RPC Gateway-->>Alice: {"result":[...]}

## Development

Make sure the test pass
To contribute to the development of rpc-gateway, ensure that you have Go installed and the project set up locally. Start by running tests to ensure everything is working as expected.

```console
go test -v ./...
```

To run the app locally
For local development and testing, you can run the application with:

```console
DEBUG=true go run . --config example_config.yml
DEBUG=true go run . --config config.yml
```

## Configuration

A main YAML configuration (`config.yml`) specifies the metrics server port and multiple gateways, each with its own `.yml` configuration file:

```yaml
metrics:
port: "9090" # port for prometheus metrics, served on /metrics and /
port: 9090 # Port for Prometheus metrics, served on /metrics and /

gateways:
- config-file: "config_holesky.yml"
name: "Holesky gateway"
- config-file: "config_sepolia.yml"
name: "Sepolia gateway"
```

Each `.yml` configuration file for the gateways can specify detailed settings for proxy behavior, health checks, and target node providers. Here is an example of what these individual gateway configuration files can contain:

```yaml
proxy:
port: "3000" # port for RPC gateway
upstreamTimeout: "1s" # when is a request considered timed out
port: "3000" # Port for RPC gateway
upstreamTimeout: "1s" # When is a request considered timed out

healthChecks:
interval: "5s" # how often to do healthchecks
timeout: "1s" # when should the timeout occur and considered unhealthy
failureThreshold: 2 # how many failed checks until marked as unhealthy
successThreshold: 1 # how many successes to be marked as healthy again
interval: "5s" # How often to perform health checks
timeout: "1s" # Timeout duration for health checks
failureThreshold: 2 # Failed checks until a target is marked unhealthy
successThreshold: 1 # Successes required to mark a target healthy again

targets: # the order here determines the failover order
targets: # Failover order is determined by the list order
- name: "Cloudflare"
connection:
http: # ws is supported by default, it will be a sticky connection.
http:
url: "https://cloudflare-eth.com"
- name: "Alchemy"
connection:
http: # ws is supported by default, it will be a sticky connection.
http:
url: "https://alchemy.com/rpc/<apikey>"
```

Any of these configuration files can be also loaded from URL if one is provided as path.
---
25 changes: 5 additions & 20 deletions example_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,8 @@
metrics:
port: 9090 # port for prometheus metrics, served on /metrics and /

proxy:
port: 3000 # port for RPC gateway
upstreamTimeout: "1s" # when is a request considered timed out

healthChecks:
interval: "5s" # how often to do healthchecks
timeout: "1s" # when should the timeout occur and considered unhealthy
failureThreshold: 2 # how many failed checks until marked as unhealthy
successThreshold: 1 # how many successes to be marked as healthy again

targets:
- name: "Ankr"
connection:
http: # ws is supported by default, it will be a sticky connection.
url: "https://rpc.ankr.com/eth"
# compression: true # Specify if the target supports request compression
- name: "Cloudflare"
connection:
http:
url: "https://cloudflare-eth.com"
gateways:
- configFile: "config_holesky.yml"
name: "Holesky gateway"
- configFile: "config_sepolia.yml"
name: "Sepolia gateway"
20 changes: 20 additions & 0 deletions example_network_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

proxy:
port: "3000" # port for RPC gateway
upstreamTimeout: "1s" # when is a request considered timed out

healthChecks:
interval: "5s" # how often to do healthchecks
timeout: "1s" # when should the timeout occur and considered unhealthy
failureThreshold: 2 # how many failed checks until marked as unhealthy
successThreshold: 1 # how many successes to be marked as healthy again

targets: # the order here determines the failover order
- name: "Cloudflare"
connection:
http: # ws is supported by default, it will be a sticky connection.
url: "https://cloudflare-eth.com"
- name: "Alchemy"
connection:
http: # ws is supported by default, it will be a sticky connection.
url: "https://alchemy.com/rpc/<apikey>"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/0xProject/rpc-gateway
module github.com/sygmaprotocol/rpc-gateway

go 1.21

Expand Down
2 changes: 1 addition & 1 deletion internal/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (s *Server) Stop() error {
func NewServer(config Config) *Server {
r := chi.NewRouter()

r.Use(middleware.Heartbeat("/healthz"))
r.Use(middleware.Heartbeat("/health"))
r.Handle("/metrics", promhttp.Handler())

return &Server{
Expand Down
3 changes: 2 additions & 1 deletion internal/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type HealthCheckConfig struct {
}

type ProxyConfig struct { // nolint:revive
Port string `yaml:"port"`
Path string `yaml:"path"`
UpstreamTimeout time.Duration `yaml:"upstreamTimeout"`
}

Expand All @@ -22,4 +22,5 @@ type Config struct {
Targets []NodeProviderConfig
HealthChecks HealthCheckConfig
HealthcheckManager *HealthCheckManager
Name string
}
10 changes: 5 additions & 5 deletions internal/proxy/healthcheckmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,35 @@ type HealthCheckManager struct {
metricRPCProviderGasLimit *prometheus.GaugeVec
}

func NewHealthCheckManager(config HealthCheckManagerConfig) (*HealthCheckManager, error) {
func NewHealthCheckManager(config HealthCheckManagerConfig, name string) (*HealthCheckManager, error) {
hcm := &HealthCheckManager{
logger: config.Logger,
metricRPCProviderInfo: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "zeroex_rpc_gateway_provider_info",
Name: "zeroex_rpc_gateway_provider_info_" + name,
Help: "Gas limit of a given provider",
}, []string{
"index",
"provider",
}),
metricRPCProviderStatus: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "zeroex_rpc_gateway_provider_status",
Name: "zeroex_rpc_gateway_provider_status_" + name,
Help: "Current status of a given provider by type. Type can be either healthy or tainted.",
}, []string{
"provider",
"type",
}),
metricRPCProviderBlockNumber: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "zeroex_rpc_gateway_provider_block_number",
Name: "zeroex_rpc_gateway_provider_block_number_" + name,
Help: "Block number of a given provider",
}, []string{
"provider",
}),
metricRPCProviderGasLimit: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "zeroex_rpc_gateway_provider_gasLimit_number",
Name: "zeroex_rpc_gateway_provider_gasLimit_number_" + name,
Help: "Gas limit of a given provider",
}, []string{
"provider",
Expand Down
2 changes: 1 addition & 1 deletion internal/proxy/nodeprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"net/http/httputil"
"strings"

"github.com/0xProject/rpc-gateway/internal/middleware"
"github.com/go-http-utils/headers"
"github.com/sygmaprotocol/rpc-gateway/internal/middleware"
)

type NodeProviderConnectionHTTPConfig struct {
Expand Down
4 changes: 2 additions & 2 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func NewProxy(config Config) (*Proxy, error) {
timeout: config.Proxy.UpstreamTimeout,
metricRequestDuration: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "zeroex_rpc_gateway_request_duration_seconds",
Name: "zeroex_rpc_gateway_request_duration_seconds_" + config.Name,
Help: "Histogram of response time for Gateway in seconds",
Buckets: []float64{
.025,
Expand All @@ -50,7 +50,7 @@ func NewProxy(config Config) (*Proxy, error) {
}),
metricRequestErrors: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "zeroex_rpc_gateway_request_errors_handled_total",
Name: "zeroex_rpc_gateway_request_errors_handled_total_" + config.Name,
Help: "The total number of request errors handled by gateway",
}, []string{
"provider",
Expand Down
Loading
Loading