Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions example_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metrics:
proxy:
port: 3000 # port for RPC gateway
upstreamTimeout: "1s" # when is a request considered timed out
enableRandomization: true

healthChecks:
interval: "5s" # how often to do healthchecks
Expand All @@ -12,14 +13,19 @@ healthChecks:
successThreshold: 1 # how many successes to be marked as healthy again

targets:
- name: "QuickNode"
- name: "Blastapi"
connection:
http: # ws is supported by default, it will be a sticky connection.
url: "https://rpc.ankr.com/eth"
url: "https://eth-mainnet.public.blastapi.io"
# compression: true # Specify if the target supports request compression
# optional ws url for Solana configuration
ws:
url: "wss://solana.ws.node"
- name: "Flashbots"
connection:
http:
url: "https://rpc.flashbots.net/"


exceptions:
# String to match in the response body
Expand Down
3 changes: 2 additions & 1 deletion internal/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ type HealthCheckConfig struct {
SuccessThreshold uint `yaml:"successThreshold"`
}

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

type TargetConnectionHTTP struct {
Expand Down
37 changes: 25 additions & 12 deletions internal/proxy/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"strconv"
"time"

"slices"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go.uber.org/zap"
Expand All @@ -17,6 +15,7 @@ type HealthcheckManagerConfig struct {
Targets []TargetConfig
Config HealthCheckConfig
Solana bool
Proxy ProxyConfig
}

type HealthcheckManager struct {
Expand All @@ -27,12 +26,15 @@ type HealthcheckManager struct {
metricResponseTime *prometheus.HistogramVec
metricRPCProviderBlockNumber *prometheus.GaugeVec
metricRPCProviderGasLimit *prometheus.GaugeVec

enableRandomization bool
}

func NewHealthcheckManager(config HealthcheckManagerConfig) *HealthcheckManager {
healthCheckers := []Healthchecker{}

healthcheckManager := &HealthcheckManager{
enableRandomization: config.Proxy.EnableRandomization,
metricRPCProviderInfo: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "zeroex_rpc_gateway_provider_info",
Expand Down Expand Up @@ -204,25 +206,36 @@ func (h *HealthcheckManager) GetNextHealthyTargetIndex() int {
}

func (h *HealthcheckManager) GetNextHealthyTargetIndexExcluding(excludedIdx []uint) int {

totalTargets := len(h.healthcheckers)
if totalTargets == 0 {
zap.L().Error("no targets")
return -1
}

idx := rand.Intn(totalTargets)
delta := 0
for delta < totalTargets {
adjustedIndex := (idx + delta) % totalTargets
target := h.healthcheckers[adjustedIndex]
if !slices.Contains(excludedIdx, uint(adjustedIndex)) && target.IsHealthy() {
return adjustedIndex
// Convert excludedIdx slice to a set for O(1) lookups
excluded := make(map[uint]struct{}, len(excludedIdx))
for _, i := range excludedIdx {
excluded[i] = struct{}{}
}

// Choose random starting index if enabled
startIdx := 0
if h.enableRandomization {
startIdx = rand.Intn(totalTargets)
}

// Iterate over all targets once
for i := 0; i < totalTargets; i++ {
idx := (startIdx + i) % totalTargets
if _, skip := excluded[uint(idx)]; skip {
continue
}
if h.healthcheckers[idx].IsHealthy() {
return idx
}
delta++
}

// no healthy targets, we down:(
zap.L().Error("no more healthy targets")
return -1
}

1 change: 1 addition & 0 deletions internal/rpcgateway/rpcgateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func NewRPCGateway(config RPCGatewayConfig) *RPCGateway {
proxy.HealthcheckManagerConfig{
Targets: config.Targets,
Config: config.HealthChecks,
Proxy: config.Proxy,
Solana: config.Solana,
})
httpFailoverProxy := proxy.NewProxy(
Expand Down