Skip to content

Commit

Permalink
Merge pull request #6 from sygmaprotocol/mmuftic/add-env-support
Browse files Browse the repository at this point in the history
feat: add ENV support for configuration
  • Loading branch information
MakMuftic authored Mar 20, 2024
2 parents 3e99e2b + 1757493 commit e6fa281
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

.idea

config.yml
config.json
config_*

prometheus
94 changes: 58 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,47 +39,69 @@ go test -v ./...
For local development and testing, you can run the application with:

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

## Configuration
Additionally, to load configuration from an environment variable, use the `--env` flag. Ensure the `GATEWAY_CONFIG` environment variable is set with the main configuration data.

A main YAML configuration (`config.yml`) specifies the metrics server port and multiple gateways, each with its own `.yml` configuration file:
```console
DEBUG=true go run . --env
```

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

gateways:
- config-file: "config_holesky.yml"
name: "Holesky gateway"
- config-file: "config_sepolia.yml"
name: "Sepolia gateway"
The main configuration has been updated to use JSON format (`config.json`). It specifies the metrics server port and multiple gateways, each with its own JSON configuration file:

```json
{
"metrics": {
"port": 9090
},
"port": 4000,
"gateways": [
{
"configFile": "config_holesky.json",
"name": "Holesky gateway"
},
{
"configFile": "config_sepolia.json",
"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
healthChecks:
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: # Failover order is determined by the list order
- name: "Cloudflare"
connection:
http:
url: "https://cloudflare-eth.com"
- name: "Alchemy"
connection:
http:
url: "https://alchemy.com/rpc/<apikey>"
Each JSON 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 might contain:

```json
{
"proxy": {
"port": "3000",
"upstreamTimeout": "1s"
},
"healthChecks": {
"interval": "5s",
"timeout": "1s",
"failureThreshold": 2,
"successThreshold": 1
},
"targets": [
{
"name": "Cloudflare",
"connection": {
"http": {
"url": "https://cloudflare-eth.com"
}
}
},
{
"name": "Alchemy",
"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.
---
15 changes: 15 additions & 0 deletions example_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"metrics": {
"port": 9090
},
"gateways": [
{
"configFile": "config_holesky.json",
"name": "Holesky gateway"
},
{
"configFile": "config_sepolia.json",
"name": "Sepolia gateway"
}
]
}
10 changes: 0 additions & 10 deletions example_config.yml

This file was deleted.

30 changes: 30 additions & 0 deletions example_network_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"proxy": {
"port": "3000",
"upstreamTimeout": "1s"
},
"healthChecks": {
"interval": "5s",
"timeout": "1s",
"failureThreshold": 2,
"successThreshold": 1
},
"targets": [
{
"name": "Cloudflare",
"connection": {
"http": {
"url": "https://cloudflare-eth.com"
}
}
},
{
"name": "Alchemy",
"connection": {
"http": {
"url": "https://alchemy.com/rpc/<apikey>"
}
}
}
]
}
20 changes: 0 additions & 20 deletions example_network_config.yml

This file was deleted.

2 changes: 1 addition & 1 deletion internal/metrics/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package metrics

type Config struct {
Port uint `yaml:"port"`
Port uint `json:"port"`
}
14 changes: 7 additions & 7 deletions internal/proxy/config.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package proxy

import (
"time"
"github.com/sygmaprotocol/rpc-gateway/internal/util"
)

type HealthCheckConfig struct {
Interval time.Duration `yaml:"interval"`
Timeout time.Duration `yaml:"timeout"`
FailureThreshold uint `yaml:"failureThreshold"`
SuccessThreshold uint `yaml:"successThreshold"`
Interval util.DurationUnmarshalled `json:"interval"`
Timeout util.DurationUnmarshalled `json:"timeout"`
FailureThreshold uint `json:"failureThreshold"`
SuccessThreshold uint `json:"successThreshold"`
}

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

// This struct is temporary. It's about to keep the input interface clean and simple.
Expand Down
18 changes: 11 additions & 7 deletions internal/proxy/healthchecker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync"
"time"

"github.com/sygmaprotocol/rpc-gateway/internal/util"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
Expand All @@ -21,16 +23,16 @@ type HealthCheckerConfig struct {
Logger *slog.Logger

// How often to check health.
Interval time.Duration `yaml:"healthcheckInterval"`
Interval util.DurationUnmarshalled `json:"interval"`

// How long to wait for responses before failing
Timeout time.Duration `yaml:"healthcheckTimeout"`
Timeout util.DurationUnmarshalled `json:"timeout"`

// Try FailureThreshold times before marking as unhealthy
FailureThreshold uint `yaml:"healthcheckInterval"`
FailureThreshold uint `yaml:"failureThreshold"`

// Minimum consecutive successes required to mark as healthy
SuccessThreshold uint `yaml:"healthcheckInterval"`
SuccessThreshold uint `yaml:"successThreshold"`
}

type HealthChecker struct {
Expand Down Expand Up @@ -93,6 +95,7 @@ func (h *HealthChecker) checkBlockNumber(c context.Context) (uint64, error) {
// want to perform an eth_call to make sure eth_call requests are also succeding
// as blockNumber can be either cached or routed to a different service on the
// RPC provider's side.
// nolint: unused
func (h *HealthChecker) checkGasLimit(c context.Context) (uint64, error) {
gasLimit, err := performGasLeftCall(c, h.httpClient, h.config.URL)
if err != nil {
Expand All @@ -117,7 +120,7 @@ func (h *HealthChecker) CheckAndSetHealth() {
}

func (h *HealthChecker) checkAndSetBlockNumberHealth() {
c, cancel := context.WithTimeout(context.Background(), h.config.Timeout)
c, cancel := context.WithTimeout(context.Background(), time.Duration(h.config.Timeout))
defer cancel()

// TODO
Expand All @@ -135,8 +138,9 @@ func (h *HealthChecker) checkAndSetBlockNumberHealth() {
h.blockNumber = blockNumber
}

// nolint: unused
func (h *HealthChecker) checkAndSetGasLeftHealth() {
c, cancel := context.WithTimeout(context.Background(), h.config.Timeout)
c, cancel := context.WithTimeout(context.Background(), time.Duration(h.config.Timeout))
defer cancel()

gasLimit, err := h.checkGasLimit(c)
Expand All @@ -154,7 +158,7 @@ func (h *HealthChecker) checkAndSetGasLeftHealth() {
func (h *HealthChecker) Start(c context.Context) {
h.CheckAndSetHealth()

ticker := time.NewTicker(h.config.Interval)
ticker := time.NewTicker(time.Duration(h.config.Interval))
defer ticker.Stop()

for {
Expand Down
6 changes: 4 additions & 2 deletions internal/proxy/healthchecker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"testing"
"time"

"github.com/sygmaprotocol/rpc-gateway/internal/util"

"github.com/caitlinelfring/go-env-default"
"github.com/stretchr/testify/assert"
)
Expand All @@ -18,8 +20,8 @@ func TestBasicHealthchecker(t *testing.T) {

healtcheckConfig := HealthCheckerConfig{
URL: env.GetDefault("RPC_GATEWAY_NODE_URL_1", "https://cloudflare-eth.com"),
Interval: 1 * time.Second,
Timeout: 2 * time.Second,
Interval: util.DurationUnmarshalled(1 * time.Second),
Timeout: util.DurationUnmarshalled(2 * time.Second),
FailureThreshold: 1,
SuccessThreshold: 1,
Logger: slog.New(slog.NewTextHandler(os.Stderr, nil)),
Expand Down
2 changes: 1 addition & 1 deletion internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Proxy struct {
func NewProxy(config Config) (*Proxy, error) {
proxy := &Proxy{
hcm: config.HealthcheckManager,
timeout: config.Proxy.UpstreamTimeout,
timeout: time.Duration(config.Proxy.UpstreamTimeout),
metricRequestDuration: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "zeroex_rpc_gateway_request_duration_seconds_" + config.Name,
Expand Down
4 changes: 3 additions & 1 deletion internal/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"testing"
"time"

"github.com/sygmaprotocol/rpc-gateway/internal/util"

"github.com/go-http-utils/headers"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
Expand All @@ -20,7 +22,7 @@ import (
func createConfig() Config {
return Config{
Proxy: ProxyConfig{
UpstreamTimeout: time.Second * 3,
UpstreamTimeout: util.DurationUnmarshalled(time.Second * 3),
},
HealthChecks: HealthCheckConfig{
Interval: 0,
Expand Down
10 changes: 5 additions & 5 deletions internal/rpcgateway/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
)

type RPCGatewayConfig struct { //nolint:revive
Name string `yaml:"name"`
Metrics metrics.Config `yaml:"metrics"`
Proxy proxy.ProxyConfig `yaml:"proxy"`
HealthChecks proxy.HealthCheckConfig `yaml:"healthChecks"`
Targets []proxy.NodeProviderConfig `yaml:"targets"`
Name string `json:"name"`
Metrics metrics.Config `json:"metrics"`
Proxy proxy.ProxyConfig `json:"proxy"`
HealthChecks proxy.HealthCheckConfig `json:"healthChecks"`
Targets []proxy.NodeProviderConfig `json:"targets"`
}
4 changes: 2 additions & 2 deletions internal/rpcgateway/rpcgateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func NewRPCGateway(config RPCGatewayConfig, router *chi.Mux) (*RPCGateway, error

// NewRPCGatewayFromConfigFile creates an instance of RPCGateway from provided
// configuration file.
func NewRPCGatewayFromConfigFile(fileOrURL string, router *chi.Mux) (*RPCGateway, error) {
config, err := util.LoadYamlFile[RPCGatewayConfig](fileOrURL)
func NewRPCGatewayFromConfigFile(configFile string, router *chi.Mux) (*RPCGateway, error) {
config, err := util.LoadJSONFile[RPCGatewayConfig](configFile)
if err != nil {
return nil, errors.Wrap(err, "failed to load config")
}
Expand Down
Loading

0 comments on commit e6fa281

Please sign in to comment.