From da780b231732845d26a97eba17305bc79d81c543 Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Thu, 29 May 2025 17:31:50 +0900 Subject: [PATCH 1/6] MvProxy refactoring --- README.md | 22 +++++++++++----------- config/config.go | 8 ++++---- config/default.go | 4 ++-- config/model.go | 4 ++-- config/viper.go | 4 ++-- middlewares/retry.go | 2 +- mvproxy.yaml | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c3a0e4c..4571d6c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MvProxy -MvProxy is a reverse proxy specifically designed for Tezos nodes, offering a range of features that enhance node performance, security, and manageability. +MvProxy is a reverse proxy specifically designed for Mavryk nodes, offering a range of features that enhance node performance, security, and manageability. ## Features @@ -16,9 +16,9 @@ MvProxy is a reverse proxy specifically designed for Tezos nodes, offering a ran ## How to run -Make sure that your Tezos Node is running and set its host address in the `tezos_host` configuration. +Make sure that your Mavryk Node is running and set its host address in the `mavryk_host` configuration. -If you want to test only MvProxy without a real Tezos Node, you can simulate a Tezos Node with our `flextesa.sh` script. Make sure that you have a docker. +If you want to test only MvProxy without a real Mavryk Node, you can simulate a Mavryk Node with our `flextesa.sh` script. Make sure that you have a docker. ```bash ./flextesa.sh @@ -124,9 +124,9 @@ rate_limit: redis: enabled: false host: "" -tezos_host: +mavryk_host: - 127.0.0.1:8732 -tezos_host_retry: "" +mavryk_host_retry: "" ``` ### Environment Variables @@ -135,8 +135,8 @@ You can also configure or overwrite MvProxy with environment variables, using th - `MVPROXY_DEV_MODE` is a flag to enable dev features like pretty logger. - `MVPROXY_HOST` is the host of the proxy. -- `MVPROXY_TEZOS_HOST` are the hosts of the tezos nodes. -- `MVPROXY_TEZOS_HOST_RETRY` is the host used when finding a 404 or 410. It's recommended use full or archive nodes. +- `MVPROXY_MAVRYK_HOST` are the hosts of the mavryk nodes. +- `MVPROXY_MAVRYK_HOST_RETRY` is the host used when finding a 404 or 410. It's recommended use full or archive nodes. - `MVPROXY_REDIS_HOST` is the host of the redis. - `MVPROXY_REDIS_ENABLE` is a flag to enable redis. - `MVPROXY_LOAD_BALANCER_TTL` is the time to live to keep using the same node by user IP. @@ -151,10 +151,10 @@ You can also configure or overwrite MvProxy with environment variables, using th - `MVPROXY_RATE_LIMIT_MAX` is the max of requests permitted in a period. - `MVPROXY_DENY_IPS_ENABLED` is a flag to block IP addresses. - `MVPROXY_DENY_IPS_VALUES` is the IP Address that will be blocked on the proxy. -- `MVPROXY_DENY_ROUTES_ENABLED` is a flag to block the Tezos node's routes. -- `MVPROXY_DENY_ROUTES_VALUES` is the Tezos nodes routes that will be blocked on the proxy.conf. -- `MVPROXY_ALLOW_ROUTES_ENABLED` is a flag to allow the Tezos node's routes. -- `MVPROXY_ALLOW_ROUTES_VALUES` is the Tezos nodes routes that will be allowed on the proxy.conf. +- `MVPROXY_DENY_ROUTES_ENABLED` is a flag to block the Mavryk node's routes. +- `MVPROXY_DENY_ROUTES_VALUES` is the Mavryk nodes routes that will be blocked on the proxy.conf. +- `MVPROXY_ALLOW_ROUTES_ENABLED` is a flag to allow the Mavryk node's routes. +- `MVPROXY_ALLOW_ROUTES_VALUES` is the Mavryk nodes routes that will be allowed on the proxy.conf. - `MVPROXY_METRICS_ENABLED` is the flag to enable metrics. - `MVPROXY_METRICS_PPROF` is the flag to enable pprof. - `MVPROXY_METRICS_HOST` is the host of the prometheus metrics and pprof (if enabled). diff --git a/config/config.go b/config/config.go index 8e3f4e9..22e40fe 100644 --- a/config/config.go +++ b/config/config.go @@ -30,10 +30,10 @@ func NewConfig() *Config { var targets = []*middleware.ProxyTarget{} var retryTarget *middleware.ProxyTarget = nil - if configFile.TezosHostRetry != "" { - retryTarget = hostToTarget(configFile.TezosHostRetry) + if configFile.MavrykHostRetry != "" { + retryTarget = hostToTarget(configFile.MavrykHostRetry) } - for _, host := range configFile.TezosHost { + for _, host := range configFile.MavrykHost { targets = append(targets, hostToTarget(host)) } @@ -49,7 +49,7 @@ func NewConfig() *Config { proxyConfig := middleware.ProxyConfig{ Skipper: middleware.DefaultSkipper, ContextKey: "target", - RetryCount: len(configFile.TezosHost) + 1, + RetryCount: len(configFile.MavrykHost) + 1, Balancer: balancer, RetryFilter: func(c echo.Context, err error) bool { if httpErr, ok := err.(*echo.HTTPError); ok { diff --git a/config/default.go b/config/default.go index 1206eed..3f85a59 100644 --- a/config/default.go +++ b/config/default.go @@ -3,8 +3,8 @@ package config var defaultConfig = &ConfigFile{ DevMode: false, Host: "0.0.0.0:8080", - TezosHost: []string{"127.0.0.1:8732"}, - TezosHostRetry: "", + MavrykHost: []string{"127.0.0.1:8732"}, + MavrykHostRetry: "", Redis: Redis{ Host: "", Enabled: false, diff --git a/config/model.go b/config/model.go index 0cf3361..914d4e2 100644 --- a/config/model.go +++ b/config/model.go @@ -104,6 +104,6 @@ type ConfigFile struct { CORS CORS `mapstructure:"cors"` GZIP GZIP `mapstructure:"gzip"` Host string `mapstructure:"host"` - TezosHost []string `mapstructure:"tezos_host"` - TezosHostRetry string `mapstructure:"tezos_host_retry"` + MavrykHost []string `mapstructure:"mavryk_host"` + MavrykHostRetry string `mapstructure:"mavryk_host_retry"` } diff --git a/config/viper.go b/config/viper.go index 04c697e..b772953 100644 --- a/config/viper.go +++ b/config/viper.go @@ -32,8 +32,8 @@ func initViper() *ConfigFile { // Set default values for configuration viper.SetDefault("dev_mode", false) viper.SetDefault("host", defaultConfig.Host) - viper.SetDefault("tezos_host", defaultConfig.TezosHost) - viper.SetDefault("tezos_host_retry", defaultConfig.TezosHostRetry) + viper.SetDefault("mavryk_host", defaultConfig.MavrykHost) + viper.SetDefault("mavryk_host_retry", defaultConfig.MavrykHostRetry) viper.SetDefault("redis.host", defaultConfig.Redis.Host) viper.SetDefault("redis.enabled", defaultConfig.Redis.Enabled) viper.SetDefault("load_balancer.ttl", defaultConfig.LoadBalancer.TTL) diff --git a/middlewares/retry.go b/middlewares/retry.go index 3b28618..c901b83 100644 --- a/middlewares/retry.go +++ b/middlewares/retry.go @@ -18,7 +18,7 @@ var statusCodeRegex = regexp.MustCompile(`code=(\d+)`) func Retry(config *config.Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) (err error) { - if config.ConfigFile.TezosHostRetry == "" || + if config.ConfigFile.MavrykHostRetry == "" || strings.Contains(c.Request().URL.Path, "mempool") || strings.Contains(c.Request().URL.Path, "monitor") { return next(c) diff --git a/mvproxy.yaml b/mvproxy.yaml index af831a5..a0e109a 100644 --- a/mvproxy.yaml +++ b/mvproxy.yaml @@ -76,6 +76,6 @@ rate_limit: redis: enabled: false host: "" -tezos_host: +mavryk_host: - 127.0.0.1:8732 -tezos_host_retry: "" +mavryk_host_retry: "" From 10226f965e8e4929fb3b91c4d4ccd496ae202732 Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Wed, 4 Jun 2025 17:28:24 +0900 Subject: [PATCH 2/6] Better CORS management --- README.md | 8 ++++++++ config/default.go | 3 +++ config/model.go | 5 ++++- middlewares/cors.go | 6 +++--- mvproxy.yaml | 8 ++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4571d6c..91fa04d 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ cache: ttl: 5 cors: enabled: true + allow_headers: + - '*' + allow_origins: + - '*' + allow_methods: + - GET + - POST + - OPTIONS deny_ips: enabled: false values: [] diff --git a/config/default.go b/config/default.go index 3f85a59..5575380 100644 --- a/config/default.go +++ b/config/default.go @@ -88,5 +88,8 @@ var defaultConfig = &ConfigFile{ }, CORS: CORS{ Enabled: true, + AllowHeaders: []string{"*"}, + AllowOrigins: []string{"*"}, + AllowMethods: []string{"*"}, }, } diff --git a/config/model.go b/config/model.go index 914d4e2..73907b5 100644 --- a/config/model.go +++ b/config/model.go @@ -73,7 +73,10 @@ type GC struct { } type CORS struct { - Enabled bool `mapstructure:"enabled"` + Enabled bool `mapstructure:"enabled"` + AllowHeaders []string `mapstructure:"allow_headers"` + AllowOrigins []string `mapstructure:"allow_origins"` + AllowMethods []string `mapstructure:"allow_methods"` } type GZIP struct { diff --git a/middlewares/cors.go b/middlewares/cors.go index 990450a..6e126e7 100644 --- a/middlewares/cors.go +++ b/middlewares/cors.go @@ -11,8 +11,8 @@ func CORS(config *config.Config) echo.MiddlewareFunc { Skipper: func(c echo.Context) bool { return !config.ConfigFile.CORS.Enabled }, - AllowHeaders: []string{"*"}, - AllowOrigins: []string{"*"}, - AllowMethods: []string{"*"}, + AllowHeaders: config.ConfigFile.CORS.AllowHeaders, + AllowOrigins: config.ConfigFile.CORS.AllowOrigins, + AllowMethods: config.ConfigFile.CORS.AllowMethods, }) } diff --git a/mvproxy.yaml b/mvproxy.yaml index a0e109a..49959f4 100644 --- a/mvproxy.yaml +++ b/mvproxy.yaml @@ -36,6 +36,14 @@ cache: ttl: 5 cors: enabled: true + allow_headers: + - '*' + allow_origins: + - '*' + allow_methods: + - GET + - POST + - OPTIONS deny_ips: enabled: false values: [] From 6328ac47dc8ef5ea171d29e5fb398aa3aa3b2df8 Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Tue, 22 Jul 2025 16:27:13 +0200 Subject: [PATCH 3/6] DNS Discovery enabled --- balancers/iphash.go | 6 ++++++ config/config.go | 7 +++---- main.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/balancers/iphash.go b/balancers/iphash.go index 09dcc37..276dd2f 100644 --- a/balancers/iphash.go +++ b/balancers/iphash.go @@ -53,6 +53,12 @@ func (b *ipHashBalancer) RemoveTarget(name string) bool { return false } +func (b *ipHashBalancer) SetTargets(targets []*middleware.ProxyTarget) { + b.mutex.Lock() + defer b.mutex.Unlock() + b.targets = targets +} + func (b *ipHashBalancer) Next(c echo.Context) *middleware.ProxyTarget { b.mutex.Lock() defer b.mutex.Unlock() diff --git a/config/config.go b/config/config.go index 22e40fe..c970eab 100644 --- a/config/config.go +++ b/config/config.go @@ -31,10 +31,10 @@ func NewConfig() *Config { var targets = []*middleware.ProxyTarget{} var retryTarget *middleware.ProxyTarget = nil if configFile.MavrykHostRetry != "" { - retryTarget = hostToTarget(configFile.MavrykHostRetry) + retryTarget = HostToTarget(configFile.MavrykHostRetry) } for _, host := range configFile.MavrykHost { - targets = append(targets, hostToTarget(host)) + targets = append(targets, HostToTarget(host)) } var redisClient *redis.Client @@ -171,7 +171,7 @@ func buildLogger(devMode bool) zerolog.Logger { return log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) } -func hostToTarget(host string) *middleware.ProxyTarget { +func HostToTarget(host string) *middleware.ProxyTarget { hostWithScheme := host if !strings.Contains(host, "http") { hostWithScheme = "http://" + host @@ -180,6 +180,5 @@ func hostToTarget(host string) *middleware.ProxyTarget { if err != nil { log.Fatal().Err(err).Msg("unable to parse host") } - return &middleware.ProxyTarget{URL: targetURL} } diff --git a/main.go b/main.go index fd3f33d..1551c27 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,13 @@ import ( "context" "net/http" "net/http/pprof" + "net" + "strings" "os" "os/signal" "runtime/debug" "time" + "log" "github.com/fraidev/echo-contrib/echoprometheus" "github.com/labstack/echo/v4" @@ -43,6 +46,38 @@ func main() { // Start metrics server startMetricsServer(config) + // Start dynamic DNS resolver for mavryk_host + go func() { + interval := 30 * time.Second + for { + var newTargets []*middleware.ProxyTarget + for _, host := range config.ConfigFile.MavrykHost { + if net.ParseIP(host) == nil && !strings.Contains(host, ":") { + // Host is a DNS name, resolve all A records + ips, err := net.LookupHost(host) + if err == nil { + for _, ip := range ips { + newTargets = append(newTargets, config.HostToTarget(ip+":8732")) // default port, adjust as needed + } + } + } else { + newTargets = append(newTargets, config.HostToTarget(host)) + } + } + if len(newTargets) > 0 { + var urls []string + for _, t := range newTargets { + urls = append(urls, t.URL.String()) + } + log.Printf("[mvproxy] Updating balancer targets: %v", urls) + if balancer, ok := config.ProxyConfig.Balancer.(interface{ SetTargets([]*middleware.ProxyTarget) }); ok { + balancer.SetTargets(newTargets) + } + } + time.Sleep(interval) + } + }() + // Start proxy startProxyWithGracefulShutdown(e, config) } From bd4661c88d347e7843d32af1629e782e69dcc250 Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Tue, 22 Jul 2025 16:28:26 +0200 Subject: [PATCH 4/6] DNS Discovery enabled --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3c356c4..fb4879e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,6 +6,9 @@ on: - "main" tags: - "v*.*.*" + pull_request: + branches: + - 'main' jobs: docker: From 7de8b716184716c3ab4425f2c86cdca05e972146 Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Tue, 22 Jul 2025 16:29:30 +0200 Subject: [PATCH 5/6] DNS Discovery enabled --- .github/workflows/docker.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fb4879e..3c356c4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,9 +6,6 @@ on: - "main" tags: - "v*.*.*" - pull_request: - branches: - - 'main' jobs: docker: From f4bc44739b2fbe5d92c239a55b2defb03c82502e Mon Sep 17 00:00:00 2001 From: Tristan Allaire Date: Wed, 23 Jul 2025 09:42:48 +0200 Subject: [PATCH 6/6] mvproxy loadbalancing improved --- balancers/iphash.go | 52 ++++++++++++++++++++++++++++++++++++++++----- main.go | 12 +++++------ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/balancers/iphash.go b/balancers/iphash.go index 276dd2f..95356ab 100644 --- a/balancers/iphash.go +++ b/balancers/iphash.go @@ -8,6 +8,7 @@ import ( echocache "github.com/fraidev/go-echo-cache" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "log" ) type ipHashBalancer struct { @@ -17,6 +18,7 @@ type ipHashBalancer struct { random *rand.Rand store echocache.Cache TTL int + activeReqs sync.Map // map[string]int, backend address -> active requests } func NewIPHashBalancer(targets []*middleware.ProxyTarget, retryTarget *middleware.ProxyTarget, ttl int, store echocache.Cache) middleware.ProxyBalancer { @@ -66,21 +68,61 @@ func (b *ipHashBalancer) Next(c echo.Context) *middleware.ProxyTarget { if len(b.targets) == 0 { return nil } else if len(b.targets) == 1 && b.retryTarget == nil { + log.Printf("[mvproxy] %s -> %s %s", c.RealIP(), b.targets[0].URL.String(), c.Request().URL.Path) + b.incActive(b.targets[0].URL.Host) return b.targets[0] } if c.Get("retry") != nil { + log.Printf("[mvproxy] %s -> %s (retry) %s", c.RealIP(), b.retryTarget.URL.String(), c.Request().URL.Path) + b.incActive(b.retryTarget.URL.Host) return b.retryTarget } ctx := c.Request().Context() ip := []byte(c.RealIP()) got, err := b.store.Get(ctx, ip) - if err != nil { - i := b.random.Intn(len(b.targets)) - b.store.Set(ctx, ip, []byte{byte(i)}, b.TTL) - return b.targets[i] + var target *middleware.ProxyTarget + if err == nil && len(got) > 0 { + backendAddr := string(got) + for _, t := range b.targets { + if t.URL.Host == backendAddr { + target = t + break + } + } } + if target == nil { + // Least-connections: pick backend with fewest active requests + minReqs := int(^uint(0) >> 1) // max int + for _, t := range b.targets { + v, _ := b.activeReqs.LoadOrStore(t.URL.Host, 0) + if reqs, ok := v.(int); ok && reqs < minReqs { + minReqs = reqs + target = t + } + } + if target == nil { + i := b.random.Intn(len(b.targets)) + target = b.targets[i] + } + b.store.Set(ctx, ip, []byte(target.URL.Host), b.TTL) + } + + b.incActive(target.URL.Host) + log.Printf("[mvproxy] %s -> %s %s", c.RealIP(), target.URL.String(), c.Request().URL.Path) + return target +} + +// Call this when a request is done (in a middleware after proxying) +func (b *ipHashBalancer) Done(host string) { + v, _ := b.activeReqs.LoadOrStore(host, 0) + if reqs, ok := v.(int); ok && reqs > 0 { + b.activeReqs.Store(host, reqs-1) + } +} - return b.targets[int(got[0])] +func (b *ipHashBalancer) incActive(host string) { + v, _ := b.activeReqs.LoadOrStore(host, 0) + b.activeReqs.Store(host, v.(int)+1) } diff --git a/main.go b/main.go index 1551c27..61f1376 100644 --- a/main.go +++ b/main.go @@ -15,13 +15,13 @@ import ( "github.com/fraidev/echo-contrib/echoprometheus" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" - "github.com/mavryk-network/mvproxy/config" + configpkg "github.com/mavryk-network/mvproxy/config" "github.com/mavryk-network/mvproxy/middlewares" "github.com/ziflex/lecho/v3" ) func main() { - config := config.NewConfig() + config := configpkg.NewConfig() debug.SetGCPercent(config.ConfigFile.GC.Percent) @@ -57,11 +57,11 @@ func main() { ips, err := net.LookupHost(host) if err == nil { for _, ip := range ips { - newTargets = append(newTargets, config.HostToTarget(ip+":8732")) // default port, adjust as needed + newTargets = append(newTargets, configpkg.HostToTarget(ip+":8732")) // default port, adjust as needed } } } else { - newTargets = append(newTargets, config.HostToTarget(host)) + newTargets = append(newTargets, configpkg.HostToTarget(host)) } } if len(newTargets) > 0 { @@ -82,7 +82,7 @@ func main() { startProxyWithGracefulShutdown(e, config) } -func startMetricsServer(config *config.Config) { +func startMetricsServer(config *configpkg.Config) { if config.ConfigFile.Metrics.Enabled { go func() { metrics := echo.New() @@ -107,7 +107,7 @@ func startMetricsServer(config *config.Config) { } -func startProxyWithGracefulShutdown(e *echo.Echo, config *config.Config) { +func startProxyWithGracefulShutdown(e *echo.Echo, config *configpkg.Config) { go func() { if err := e.Start(config.ConfigFile.Host); err != nil && err != http.ErrServerClosed { e.Logger.Fatal("Shutting down the server")