From 429ed66e820fdf61871386b30d0ee17267125641 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:28:38 -1000 Subject: [PATCH 01/16] Use autoconf to configure settings - autoconf bootstrap peers --- autoconf.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 32 +++++++++++++++++ server.go | 26 ++++++++++++-- server_dht.go | 6 ++-- 4 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 autoconf.go diff --git a/autoconf.go b/autoconf.go new file mode 100644 index 0000000..11263af --- /dev/null +++ b/autoconf.go @@ -0,0 +1,95 @@ +package main + +import ( + "path/filepath" + "time" + + autoconf "github.com/ipfs/boxo/autoconf" + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" +) + +// autoConfConfig contains the configuration for the autoconf subsystem +type autoConfConfig struct { + // enabled determines whether to use autoconf + // Default: true + enabled bool + + // url is the HTTP(S) URL to fetch the autoconf.json from + // Default: https://conf.ipfs-mainnet.org/autoconf.json + url string + + // refreshInterval is how often to refresh autoconf data + // Default: 24h + refreshInterval time.Duration + + // cacheDir is the directory to cache autoconf data + // Default: $RAINBOW_DATADIR/.autoconf-cache + cacheDir string +} + +func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.AddrInfo { + if autoConf != nil { + nativeSystems := getNativeSystems(cfg.dhtType) + return stringsToPeerAddrInfos(autoConf.GetBootstrapPeers(nativeSystems...)) + } + // Fallback to hard-coded bootstrappers. + return dht.GetDefaultBootstrapPeerAddrInfos() +} + +func stringsToPeerAddrInfos(addrs []string) []peer.AddrInfo { + addrInfos := make([]peer.AddrInfo, 0, len(addrs)) + + for _, s := range addrs { + ma, err := multiaddr.NewMultiaddr(s) + if err != nil { + logger.Error("bad multiaddr in bootstrapper autoconf data", "err", err) + continue + } + + info, err := peer.AddrInfoFromP2pAddr(ma) + if err != nil { + logger.Errorw("failed to convert bootstrapper address to peer addr info", "address", ma.String(), err, "err") + continue + } + addrInfos = append(addrInfos, *info) + } + + return addrInfos +} + +// createAutoConfClient creates an autoconf client with the given configuration +func createAutoConfClient(config autoConfConfig) (*autoconf.Client, error) { + if config.cacheDir == "" { + config.cacheDir = filepath.Join(".", ".autoconf-cache") + } + if config.refreshInterval == 0 { + config.refreshInterval = autoconf.DefaultRefreshInterval + } + if config.url == "" { + config.url = autoconf.MainnetAutoConfURL + } + + return autoconf.NewClient( + autoconf.WithCacheDir(config.cacheDir), + autoconf.WithUserAgent("someguy/"+version), + autoconf.WithCacheSize(autoconf.DefaultCacheSize), + autoconf.WithTimeout(autoconf.DefaultTimeout), + autoconf.WithURL(config.url), + autoconf.WithRefreshInterval(config.refreshInterval), + ) +} + +// getNativeSystems returns the list of systems that should be used natively based on routing type +func getNativeSystems(routingType string) []string { + switch routingType { + case "dht", "accelerated", "standard", "auto": + return []string{autoconf.SystemAminoDHT} + case "disabled", "off", "none", "custom": + return []string{} + default: + logger.Warnf("getNativeSystems: unknown routing type %q, assuming no native systems", routingType) + return []string{} + } +} diff --git a/main.go b/main.go index dbe244e..7bd29d5 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "os" + "path/filepath" "strings" "time" @@ -145,6 +146,30 @@ func main() { EnvVars: []string{"SOMEGUY_SAMPLING_FRACTION"}, Usage: "Rate at which to sample gateway requests. Does not include requests with traceheaders which will always sample", }, + &cli.StringFlag{ + Name: "datadir", + Value: "", + EnvVars: []string{"SOMEGUY_DATADIR"}, + Usage: "Directory for persistent data (autoconf cache)", + }, + &cli.BoolFlag{ + Name: "autoconf", + Value: true, + EnvVars: []string{"SOMEGUY_AUTOCONF"}, + Usage: "Enable autoconf for bootstrap, DNS resolvers, and HTTP routers", + }, + &cli.StringFlag{ + Name: "autoconf-url", + Value: "https://conf.ipfs-mainnet.org/autoconf.json", + EnvVars: []string{"SOMEGUY_AUTOCONF_URL"}, + Usage: "URL to fetch autoconf data from", + }, + &cli.DurationFlag{ + Name: "autoconf-refresh", + Value: 24 * time.Hour, + EnvVars: []string{"SOMEGUY_AUTOCONF_REFRESH"}, + Usage: "How often to refresh autoconf data", + }, }, Action: func(ctx *cli.Context) error { cfg := &config{ @@ -169,6 +194,13 @@ func main() { tracingAuth: ctx.String("tracing-auth"), samplingFraction: ctx.Float64("sampling-fraction"), + + autoConf: autoConfConfig{ + enabled: ctx.Bool("autoconf"), + url: ctx.String("autoconf-url"), + refreshInterval: ctx.Duration("autoconf-refresh"), + cacheDir: filepath.Join(ctx.String("datadir"), ".autoconf-cache"), + }, } fmt.Printf("Starting %s %s\n", name, version) diff --git a/server.go b/server.go index 8c6c993..fa08cf4 100644 --- a/server.go +++ b/server.go @@ -20,6 +20,7 @@ import ( "github.com/CAFxX/httpcompression" sddaemon "github.com/coreos/go-systemd/v22/daemon" "github.com/felixge/httpsnoop" + autoconf "github.com/ipfs/boxo/autoconf" drclient "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/server" logging "github.com/ipfs/go-log/v2" @@ -87,6 +88,8 @@ type config struct { tracingAuth string samplingFraction float64 + + autoConf autoConfConfig } func start(ctx context.Context, cfg *config) error { @@ -95,17 +98,36 @@ func start(ctx context.Context, cfg *config) error { return err } + // Setup autoconf + var autoConf *autoconf.Config + if cfg.autoConf.enabled && cfg.autoConf.url != "" { + client, err := createAutoConfClient(cfg.autoConf) + if err != nil { + logger.Errorf("Failed to create autoconf client: %v", err) + } else { + // Start primes cache and starts background updater + // Note: Start() always returns a config (using fallback if needed) + autoConf, err = client.Start(ctx) + if err != nil { + logger.Errorf("Failed to start autoconf updater: %v", err) + // Continue with the config we got (likely fallback) + } + } + } + + bootstrapAddrInfos := getBootstrapPeerAddrInfos(cfg, autoConf) + fmt.Printf("Someguy libp2p host listening on %v\n", h.Addrs()) var dhtRouting routing.Routing switch cfg.dhtType { case "accelerated": - wrappedDHT, err := newBundledDHT(ctx, h) + wrappedDHT, err := newBundledDHT(ctx, h, bootstrapAddrInfos) if err != nil { return err } dhtRouting = wrappedDHT case "standard": - standardDHT, err := dht.New(ctx, h, dht.Mode(dht.ModeClient), dht.BootstrapPeers(dht.GetDefaultBootstrapPeerAddrInfos()...)) + standardDHT, err := dht.New(ctx, h, dht.Mode(dht.ModeClient), dht.BootstrapPeers(bootstrapAddrInfos...)) if err != nil { return err } diff --git a/server_dht.go b/server_dht.go index 70cf291..7f42aab 100644 --- a/server_dht.go +++ b/server_dht.go @@ -18,8 +18,8 @@ type bundledDHT struct { fullRT *fullrt.FullRT } -func newBundledDHT(ctx context.Context, h host.Host) (routing.Routing, error) { - standardDHT, err := dht.New(ctx, h, dht.Mode(dht.ModeClient), dht.BootstrapPeers(dht.GetDefaultBootstrapPeerAddrInfos()...)) +func newBundledDHT(ctx context.Context, h host.Host, bootstrapAddrInfos []peer.AddrInfo) (routing.Routing, error) { + standardDHT, err := dht.New(ctx, h, dht.Mode(dht.ModeClient), dht.BootstrapPeers(bootstrapAddrInfos...)) if err != nil { return nil, err } @@ -31,7 +31,7 @@ func newBundledDHT(ctx context.Context, h host.Host) (routing.Routing, error) { "pk": record.PublicKeyValidator{}, "ipns": ipns.Validator{}, }), - dht.BootstrapPeers(dht.GetDefaultBootstrapPeerAddrInfos()...), + dht.BootstrapPeers(bootstrapAddrInfos...), dht.Mode(dht.ModeClient), )) if err != nil { From 8cb78892d21eaec0e4fa2f9e8b47ed9ea82c8ae1 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Sun, 21 Sep 2025 21:38:18 -1000 Subject: [PATCH 02/16] autoconf delegated routing endpoints --- autoconf.go | 64 +++++++++++++++++++++++++++++++++++++-- main.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++++---- server.go | 22 ++++---------- 3 files changed, 148 insertions(+), 25 deletions(-) diff --git a/autoconf.go b/autoconf.go index 11263af..224e6c2 100644 --- a/autoconf.go +++ b/autoconf.go @@ -1,10 +1,14 @@ package main import ( + "context" + "fmt" "path/filepath" + "slices" + "strings" "time" - autoconf "github.com/ipfs/boxo/autoconf" + "github.com/ipfs/boxo/autoconf" dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" @@ -25,10 +29,27 @@ type autoConfConfig struct { refreshInterval time.Duration // cacheDir is the directory to cache autoconf data - // Default: $RAINBOW_DATADIR/.autoconf-cache + // Default: $SOMEGUY_DATADIR/.autoconf-cache cacheDir string } +func startAutoConf(ctx context.Context, cfg *config) (*autoconf.Config, error) { + var autoConf *autoconf.Config + if cfg.autoConf.enabled && cfg.autoConf.url != "" { + client, err := createAutoConfClient(cfg.autoConf) + if err != nil { + return nil, fmt.Errorf("failed to create autoconf client: %w", err) + } + // Start primes cache and starts background updater + // Note: Start() always returns a config (using fallback if needed) + autoConf, err = client.Start(ctx) + if err != nil { + return nil, fmt.Errorf("failed to start autoconf updater: %w", err) + } + } + return autoConf, nil +} + func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.AddrInfo { if autoConf != nil { nativeSystems := getNativeSystems(cfg.dhtType) @@ -38,6 +59,43 @@ func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.Ad return dht.GetDefaultBootstrapPeerAddrInfos() } +func expandContentEndpoints(cfg *config, autoConf *autoconf.Config) error { + if !cfg.autoConf.enabled { + if slices.Contains(cfg.contentEndpoints, autoconf.AutoPlaceholder) { + return autoconfDisabledError("HTTP routers", "SOMEGUY_HTTP_ROUTERS", "--http-routers") + } + return nil + } + + nativeSystems := getNativeSystems(cfg.dhtType) + + // Someguy only uses read-only endpoints for providers, peers, and IPNS + cfg.contentEndpoints = autoconf.ExpandDelegatedEndpoints(cfg.contentEndpoints, autoConf, nativeSystems, + autoconf.RoutingV1ProvidersPath, + autoconf.RoutingV1PeersPath, + autoconf.RoutingV1IPNSPath) + + // Need to remove "/routing/v1/providers" because routing.FindProviders adds it back on. + for i, ep := range cfg.contentEndpoints { + ep = strings.TrimSuffix(ep, autoconf.RoutingV1ProvidersPath) + ep = strings.TrimSuffix(ep, autoconf.RoutingV1PeersPath) + ep = strings.TrimSuffix(ep, autoconf.RoutingV1IPNSPath) + cfg.contentEndpoints[i] = ep + } + + // Remove duplicates. + slices.Sort(cfg.contentEndpoints) + cfg.contentEndpoints = slices.Compact(cfg.contentEndpoints) + + return nil +} + +// autoconfDisabledError returns a consistent error message when auto placeholder is found but autoconf is disabled +func autoconfDisabledError(configType, envVar, flag string) error { + return fmt.Errorf("'auto' placeholder found in %s but autoconf is disabled. Set explicit %s with %s or %s, or re-enable autoconf", + configType, configType, envVar, flag) +} + func stringsToPeerAddrInfos(addrs []string) []peer.AddrInfo { addrInfos := make([]peer.AddrInfo, 0, len(addrs)) @@ -86,7 +144,7 @@ func getNativeSystems(routingType string) []string { switch routingType { case "dht", "accelerated", "standard", "auto": return []string{autoconf.SystemAminoDHT} - case "disabled", "off", "none", "custom": + case "disabled", "off", "none", "delegated", "custom": return []string{} default: logger.Warnf("getNativeSystems: unknown routing type %q, assuming no native systems", routingType) diff --git a/main.go b/main.go index 7bd29d5..da3b8ce 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/ipfs/boxo/autoconf" "github.com/ipfs/boxo/ipns" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" @@ -18,8 +19,6 @@ import ( "github.com/urfave/cli/v2" ) -const cidContactEndpoint = "https://cid.contact" - func main() { app := &cli.App{ Name: name, @@ -62,7 +61,7 @@ func main() { }, &cli.StringSliceFlag{ Name: "provider-endpoints", - Value: cli.NewStringSlice(cidContactEndpoint), + Value: cli.NewStringSlice(autoconf.AutoPlaceholder), EnvVars: []string{"SOMEGUY_PROVIDER_ENDPOINTS"}, Usage: "other Delegated Routing V1 endpoints to proxy provider requests to", }, @@ -241,7 +240,7 @@ func main() { Flags: []cli.Flag{ &cli.StringFlag{ Name: "endpoint", - Value: cidContactEndpoint, + Value: autoconf.AutoPlaceholder, Usage: "the Delegated Routing V1 endpoint to ask", }, &cli.BoolFlag{ @@ -249,6 +248,30 @@ func main() { Value: false, Usage: "output data in a prettier format that may convey less information", }, + &cli.StringFlag{ + Name: "datadir", + Value: "", + EnvVars: []string{"SOMEGUY_DATADIR"}, + Usage: "Directory for persistent data (autoconf cache)", + }, + &cli.BoolFlag{ + Name: "autoconf", + Value: true, + EnvVars: []string{"SOMEGUY_AUTOCONF"}, + Usage: "Enable autoconf for bootstrap, DNS resolvers, and HTTP routers", + }, + &cli.StringFlag{ + Name: "autoconf-url", + Value: "https://conf.ipfs-mainnet.org/autoconf.json", + EnvVars: []string{"SOMEGUY_AUTOCONF_URL"}, + Usage: "URL to fetch autoconf data from", + }, + &cli.DurationFlag{ + Name: "autoconf-refresh", + Value: 24 * time.Hour, + EnvVars: []string{"SOMEGUY_AUTOCONF_REFRESH"}, + Usage: "How often to refresh autoconf data", + }, }, Subcommands: []*cli.Command{ { @@ -264,7 +287,33 @@ func main() { if err != nil { return err } - return findProviders(ctx.Context, c, ctx.String("endpoint"), ctx.Bool("pretty")) + + cfg := &config{ + dhtType: "none", + contentEndpoints: []string{ctx.String("endpoint")}, + autoConf: autoConfConfig{ + enabled: ctx.Bool("autoconf"), + url: ctx.String("autoconf-url"), + refreshInterval: ctx.Duration("autoconf-refresh"), + cacheDir: filepath.Join(ctx.String("datadir"), ".autoconf-cache"), + }, + } + + autoConf, err := startAutoConf(ctx.Context, cfg) + if err != nil { + logger.Error(err.Error()) + } + + if err = expandContentEndpoints(cfg, autoConf); err != nil { + return err + } + if len(cfg.contentEndpoints) == 0 { + return errors.New("No delegated routing endpoint configured. Use --endpoint to specify") + } + + endPoint := cfg.contentEndpoints[0] + + return findProviders(ctx.Context, c, endPoint, ctx.Bool("pretty")) }, }, { @@ -280,7 +329,33 @@ func main() { if err != nil { return err } - return findPeers(ctx.Context, pid, ctx.String("endpoint"), ctx.Bool("pretty")) + cfg := &config{ + dhtType: "none", + contentEndpoints: []string{ctx.String("endpoint")}, + autoConf: autoConfConfig{ + enabled: ctx.Bool("autoconf"), + url: ctx.String("autoconf-url"), + refreshInterval: ctx.Duration("autoconf-refresh"), + cacheDir: filepath.Join(ctx.String("datadir"), ".autoconf-cache"), + }, + } + + autoConf, err := startAutoConf(ctx.Context, cfg) + if err != nil { + logger.Error(err.Error()) + } + + if err = expandContentEndpoints(cfg, autoConf); err != nil { + return err + } + if len(cfg.contentEndpoints) == 0 { + return errors.New("No delegated routing endpoint configured. Use --endpoint to specify") + } + + endPoint := cfg.contentEndpoints[0] + fmt.Println("---> endpoints:", cfg.contentEndpoints) + + return findPeers(ctx.Context, pid, endPoint, ctx.Bool("pretty")) }, }, { diff --git a/server.go b/server.go index fa08cf4..0682b34 100644 --- a/server.go +++ b/server.go @@ -20,7 +20,6 @@ import ( "github.com/CAFxX/httpcompression" sddaemon "github.com/coreos/go-systemd/v22/daemon" "github.com/felixge/httpsnoop" - autoconf "github.com/ipfs/boxo/autoconf" drclient "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/server" logging "github.com/ipfs/go-log/v2" @@ -98,24 +97,15 @@ func start(ctx context.Context, cfg *config) error { return err } - // Setup autoconf - var autoConf *autoconf.Config - if cfg.autoConf.enabled && cfg.autoConf.url != "" { - client, err := createAutoConfClient(cfg.autoConf) - if err != nil { - logger.Errorf("Failed to create autoconf client: %v", err) - } else { - // Start primes cache and starts background updater - // Note: Start() always returns a config (using fallback if needed) - autoConf, err = client.Start(ctx) - if err != nil { - logger.Errorf("Failed to start autoconf updater: %v", err) - // Continue with the config we got (likely fallback) - } - } + autoConf, err := startAutoConf(ctx, cfg) + if err != nil { + logger.Error(err.Error()) } bootstrapAddrInfos := getBootstrapPeerAddrInfos(cfg, autoConf) + if err = expandContentEndpoints(cfg, autoConf); err != nil { + return err + } fmt.Printf("Someguy libp2p host listening on %v\n", h.Addrs()) var dhtRouting routing.Routing From ce3591960f5fb9383bf9d3c1ae420ecfeced30f5 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:19:03 -1000 Subject: [PATCH 03/16] no delegated endpoint filtering --- client.go | 4 +- go.mod | 56 +++++++++++++------------- go.sum | 116 ++++++++++++++++++++++++++++-------------------------- main.go | 7 ++-- 4 files changed, 95 insertions(+), 88 deletions(-) diff --git a/client.go b/client.go index 89dcaf3..2c087c6 100644 --- a/client.go +++ b/client.go @@ -16,7 +16,7 @@ import ( ) func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint) + drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true)) if err != nil { return err } @@ -77,7 +77,7 @@ func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutp } func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint) + drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true)) if err != nil { return err } diff --git a/go.mod b/go.mod index e15c7f4..08a50a1 100644 --- a/go.mod +++ b/go.mod @@ -19,16 +19,16 @@ require ( github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multihash v0.2.3 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 - github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_golang v1.23.2 github.com/rs/cors v1.11.0 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v2 v2.27.7 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 go.opentelemetry.io/contrib/propagators/autoprop v0.57.0 - go.opentelemetry.io/otel v1.37.0 - go.opentelemetry.io/otel/sdk v1.37.0 - go.opentelemetry.io/otel/trace v1.37.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 golang.org/x/sys v0.36.0 ) @@ -37,7 +37,7 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -55,10 +55,10 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/huin/goupnp v1.3.0 // indirect - github.com/ipfs/go-datastore v0.8.3 // indirect + github.com/ipfs/go-datastore v0.9.0 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect @@ -114,7 +114,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect @@ -132,31 +132,33 @@ require ( go.opentelemetry.io/contrib/propagators/b3 v1.32.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.32.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/zipkin v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/text v0.28.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/tools v0.37.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index 8a5f4ca..46825df 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -243,8 +243,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -268,8 +268,8 @@ github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= -github.com/ipfs/go-datastore v0.8.3 h1:z391GsQyGKUIUof2tPoaZVeDknbt7fNHs6Gqjcw5Jo4= -github.com/ipfs/go-datastore v0.8.3/go.mod h1:raxQ/CreIy9L6MxT71ItfMX12/ASN6EhXJoUFjICQ2M= +github.com/ipfs/go-datastore v0.9.0 h1:WocriPOayqalEsueHv6SdD4nPVl4rYMfYGLD4bqCZ+w= +github.com/ipfs/go-datastore v0.9.0/go.mod h1:uT77w/XEGrvJWwHgdrMr8bqCN6ZTW9gzmi+3uK+ouHg= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= @@ -522,8 +522,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -537,8 +537,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -670,8 +670,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/propagators/autoprop v0.57.0 h1:bNPJOdT5154XxzeFmrh8R+PXnV4t3TZEczy8gHEpcpg= go.opentelemetry.io/contrib/propagators/autoprop v0.57.0/go.mod h1:Tb0j0mK+QatKdCxCKPN7CSzc7kx/q34/KaohJx/N96s= go.opentelemetry.io/contrib/propagators/aws v1.32.0 h1:NELzr8bW7a7aHVZj5gaep1PfkvoSCGx+1qNGZx/uhhU= @@ -682,28 +682,28 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.32.0 h1:K/fOyTMD6GELKTIJBaJ9k3 go.opentelemetry.io/contrib/propagators/jaeger v1.32.0/go.mod h1:ISE6hda//MTWvtngG7p4et3OCngsrTVfl7c6DjN17f8= go.opentelemetry.io/contrib/propagators/ot v1.32.0 h1:Poy02A4wOZubHyd2hpHPDgZW+rn6EIq0vCwTZJ6Lmu8= go.opentelemetry.io/contrib/propagators/ot v1.32.0/go.mod h1:cbhaURV+VR3NIMarzDYZU1RDEkXG1fNd1WMP1XCcGkY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= -go.opentelemetry.io/otel/exporters/zipkin v1.37.0 h1:Z2apuaRnHEjzDAkpbWNPiksz1R0/FCIrJSjiMA43zwI= -go.opentelemetry.io/otel/exporters/zipkin v1.37.0/go.mod h1:ofGu/7fG+bpmjZoiPUUmYDJ4vXWxMT57HmGoegx49uw= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/exporters/zipkin v1.38.0 h1:0rJ2TmzpHDG+Ib9gPmu3J3cE0zXirumQcKS4wCoZUa0= +go.opentelemetry.io/otel/exporters/zipkin v1.38.0/go.mod h1:Su/nq/K5zRjDKKC3Il0xbViE3juWgG3JDoqLumFx5G0= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= @@ -716,6 +716,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -736,8 +738,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -748,8 +750,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -773,8 +775,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -820,8 +822,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -845,8 +847,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -905,6 +907,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -923,8 +927,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -979,8 +983,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1047,10 +1051,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1067,8 +1071,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1083,8 +1087,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index da3b8ce..cc7a9ee 100644 --- a/main.go +++ b/main.go @@ -308,10 +308,11 @@ func main() { return err } if len(cfg.contentEndpoints) == 0 { - return errors.New("No delegated routing endpoint configured. Use --endpoint to specify") + return errors.New("no delegated routing endpoint configured, use --endpoint to specify") } endPoint := cfg.contentEndpoints[0] + logger.Debugf("delegated routing endpoint: %s", endPoint) return findProviders(ctx.Context, c, endPoint, ctx.Bool("pretty")) }, @@ -349,11 +350,11 @@ func main() { return err } if len(cfg.contentEndpoints) == 0 { - return errors.New("No delegated routing endpoint configured. Use --endpoint to specify") + return errors.New("no delegated routing endpoint configured, use --endpoint to specify") } endPoint := cfg.contentEndpoints[0] - fmt.Println("---> endpoints:", cfg.contentEndpoints) + logger.Debugf("delegated routing endpoint: %s", endPoint) return findPeers(ctx.Context, pid, endPoint, ctx.Bool("pretty")) }, From 90f0b70f3886e14975515ee3f8c9d39b07f5fa18 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:24:55 -1000 Subject: [PATCH 04/16] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b47635..1016a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ The following emojis are used to highlight certain changes: ### Added +- Added automatic configuration (autoconf) of bootstrap nodes and delegated routing endpoints. Autoconf can be disabled and autoconf endpoints can be manually supplied to provide alternate configuration data. Delegated routing endpoints can also be manually supplied, overriding auto-configured values. + ### Changed - [go-libp2p v0.43.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.43.0) From 52b48eeb0d91cc27c392cc8913a5eb41597dd383 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:56:31 -1000 Subject: [PATCH 05/16] - Add autoconftest - Set delegated routing endpoint using environ var - Fix autoconf disabled error message --- autoconf.go | 2 +- autoconf_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 7 ++-- 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 autoconf_test.go diff --git a/autoconf.go b/autoconf.go index 224e6c2..514cced 100644 --- a/autoconf.go +++ b/autoconf.go @@ -62,7 +62,7 @@ func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.Ad func expandContentEndpoints(cfg *config, autoConf *autoconf.Config) error { if !cfg.autoConf.enabled { if slices.Contains(cfg.contentEndpoints, autoconf.AutoPlaceholder) { - return autoconfDisabledError("HTTP routers", "SOMEGUY_HTTP_ROUTERS", "--http-routers") + return autoconfDisabledError("endpoint option", "SOMEGUY_DELEGATED_ENDPOINT", "--endpoint") } return nil } diff --git a/autoconf_test.go b/autoconf_test.go new file mode 100644 index 0000000..8c0523a --- /dev/null +++ b/autoconf_test.go @@ -0,0 +1,92 @@ +package main + +import ( + "testing" + + autoconf "github.com/ipfs/boxo/autoconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestGetNativeSystems verifies that routing types are correctly mapped to native systems +func TestGetNativeSystems(t *testing.T) { + tests := []struct { + name string + routingType string + expectedSystems []string + }{ + { + name: "DHT routing", + routingType: "dht", + expectedSystems: []string{autoconf.SystemAminoDHT}, + }, + { + name: "Accelerated routing", + routingType: "accelerated", + expectedSystems: []string{autoconf.SystemAminoDHT}, + }, + { + name: "Standard routing", + routingType: "standard", + expectedSystems: []string{autoconf.SystemAminoDHT}, + }, + { + name: "Auto routing", + routingType: "auto", + expectedSystems: []string{autoconf.SystemAminoDHT}, + }, + { + name: "Off routing", + routingType: "off", + expectedSystems: []string{}, + }, + { + name: "None routing", + routingType: "none", + expectedSystems: []string{}, + }, + { + name: "Unknown routing type", + routingType: "custom", + expectedSystems: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + systems := getNativeSystems(tt.routingType) + assert.Equal(t, tt.expectedSystems, systems) + }) + } +} + +// TestExpandContentEndpoints verifies endpoint expansion behavior when autoconf is disabled +func TestExpandContentEndpoints(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: false, + }, + contentEndpoints: []string{autoconf.AutoPlaceholder}, + } + + t.Run("auto placeholder errors when autoconf disabled", func(t *testing.T) { + err := expandContentEndpoints(&cfg, nil) + require.Error(t, err, "should error when 'auto' is used with autoconf disabled") + assert.Contains(t, err.Error(), "'auto' placeholder found in endpoint option", "error should mention bootstrap peers") + assert.Contains(t, err.Error(), "SOMEGUY_DELEGATED_ENDPOINT", "error should mention how to fix") + }) + + t.Run("custom endpoint preserved when autoconf disabled", func(t *testing.T) { + custom := []string{"https://example.com"} + cfg.contentEndpoints = custom + err := expandContentEndpoints(&cfg, nil) + require.NoError(t, err, "custom endpoint should not error") + assert.Equal(t, custom, cfg.contentEndpoints, "custom endpoint should be preserved") + }) + + t.Run("mixed auto and custom errors when autoconf disabled", func(t *testing.T) { + cfg.contentEndpoints = []string{autoconf.AutoPlaceholder, "https://example.com"} + err := expandContentEndpoints(&cfg, nil) + require.Error(t, err, "should error when 'auto' is mixed with custom values") + }) +} diff --git a/main.go b/main.go index cc7a9ee..5c9c952 100644 --- a/main.go +++ b/main.go @@ -239,9 +239,10 @@ func main() { Name: "ask", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "endpoint", - Value: autoconf.AutoPlaceholder, - Usage: "the Delegated Routing V1 endpoint to ask", + Name: "endpoint", + Value: autoconf.AutoPlaceholder, + EnvVars: []string{"SOMEGUY_DELEGATED_ENDPOINT"}, + Usage: "the Delegated Routing V1 endpoint to ask", }, &cli.BoolFlag{ Name: "pretty", From 674300e3ac16ba0f49f1de24c3e0a0aca4d21004 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:59:19 -1000 Subject: [PATCH 06/16] git ignore .autoconf-cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b89b1ab..5e0b07f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ someguy +.autoconf-cache From a350234c2a293e42493a012807f3c295ef7a580c Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 22 Sep 2025 18:11:42 -1000 Subject: [PATCH 07/16] Update README and changelog for autoconf --- CHANGELOG.md | 6 ++++++ README.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1016a59..cdca6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ The following emojis are used to highlight certain changes: ### Added - Added automatic configuration (autoconf) of bootstrap nodes and delegated routing endpoints. Autoconf can be disabled and autoconf endpoints can be manually supplied to provide alternate configuration data. Delegated routing endpoints can also be manually supplied, overriding auto-configured values. +- AutoConf support with `auto` placeholders for bootstrap peers and delegated routing endpoints ([Autoconf #123](https://github.com/ipfs/someguy/pull/123)) + - Configuration flags: + - `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable automatic configuration expansion (default: `true`) + - `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch autoconf data from (default: `https://conf.ipfs-mainnet.org/autoconf.json`) + - `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: Interval for refreshing autoconf data (default: `24h`) + - When autoconf is disabled, `auto` placeholders will cause an error, requiring explicit values ### Changed diff --git a/README.md b/README.md index aea245b..d944494 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,20 @@ If you don't want to run a server yourself, but want to query some other server, For more details run `someguy ask --help`. +### AutoConf + +Someguy supports automatic configuration of bootstrap peers and delegated routing endpoints through the autoconf feature. When enabled (default), Someguy will fetch configuration from a remote URL and automatically expand the special `auto` placeholder value with network-appropriate defaults. + +This feature can be configured via: +- `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable autoconf (default: `true`) +- `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch configuration from (default: `https://conf.ipfs-mainnet.org/autoconf.json`) +- `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: How often to refresh configuration (default: `24h`) + +The `auto` placeholder can be used in: +- `--endpoing` / `SOMEGUY_DELEGATED_ENDPOINT`: the Delegated Routing V1 endpoint to ask + +**Note:** When autoconf is disabled (`--autoconf=false`), using the `auto` placeholder will cause an error. You must provide explicit values for these configurations when autoconf is disabled. + ## Deployment Suggested method for self-hosting is to run a [prebuilt Docker image](#docker). From 3833366a35c34b806485d1d448a6139e1aa29e9d Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 17 Nov 2025 17:40:27 +0100 Subject: [PATCH 08/16] fix(autoconf): add fallback configuration ensures autoconf client uses mainnet fallback config when server is unreachable or cache is empty --- autoconf.go | 1 + 1 file changed, 1 insertion(+) diff --git a/autoconf.go b/autoconf.go index 514cced..2189535 100644 --- a/autoconf.go +++ b/autoconf.go @@ -136,6 +136,7 @@ func createAutoConfClient(config autoConfConfig) (*autoconf.Client, error) { autoconf.WithTimeout(autoconf.DefaultTimeout), autoconf.WithURL(config.url), autoconf.WithRefreshInterval(config.refreshInterval), + autoconf.WithFallback(autoconf.GetMainnetFallbackConfig), ) } From 08e82e21d557b6e1cda9d38333be64011fea690f Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 17 Nov 2025 18:05:00 +0100 Subject: [PATCH 09/16] fix(autoconf): use autoconf.FallbackBootstrapPeers for fallback replaces dht.GetDefaultBootstrapPeerAddrInfos with autoconf.FallbackBootstrapPeers to align with kubo's approach --- autoconf.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/autoconf.go b/autoconf.go index 2189535..233d841 100644 --- a/autoconf.go +++ b/autoconf.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ipfs/boxo/autoconf" - dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" ) @@ -55,8 +54,8 @@ func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.Ad nativeSystems := getNativeSystems(cfg.dhtType) return stringsToPeerAddrInfos(autoConf.GetBootstrapPeers(nativeSystems...)) } - // Fallback to hard-coded bootstrappers. - return dht.GetDefaultBootstrapPeerAddrInfos() + // Fallback to autoconf fallback bootstrappers. + return stringsToPeerAddrInfos(autoconf.FallbackBootstrapPeers) } func expandContentEndpoints(cfg *config, autoConf *autoconf.Config) error { From a174b0632b27bba2eb49760afce42fea969214c8 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 17 Nov 2025 18:06:55 +0100 Subject: [PATCH 10/16] docs: fix typo in README changes '--endpoing' to '--endpoint' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d944494..38dccac 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ This feature can be configured via: - `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: How often to refresh configuration (default: `24h`) The `auto` placeholder can be used in: -- `--endpoing` / `SOMEGUY_DELEGATED_ENDPOINT`: the Delegated Routing V1 endpoint to ask +- `--endpoint` / `SOMEGUY_DELEGATED_ENDPOINT`: the Delegated Routing V1 endpoint to ask **Note:** When autoconf is disabled (`--autoconf=false`), using the `auto` placeholder will cause an error. You must provide explicit values for these configurations when autoconf is disabled. From 06005bd6b39a8db57c19430e30600e444954ad1f Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 19 Nov 2025 00:06:39 +0100 Subject: [PATCH 11/16] refactor(autoconf): implement path-based routing for delegated endpoints - set all three endpoint types (provider, peer, IPNS) to default to 'auto' - expand and validate endpoint URLs per flag to support custom configurations - accept both base URLs (https://example.com) and full URLs with routing paths - strip routing paths after validation to get base URLs for HTTP client - validate path matches flag type, error on mismatches with helpful messages - update stdout to show routing paths explicitly (/routing/v1/providers) - add spec references to Routing V1 and IPFS Mainnet in docs - simplify documentation following godoc style This ensures endpoints only receive requests they support (e.g., cid.contact only gets provider requests, not peers or IPNS). --- CHANGELOG.md | 10 +- README.md | 23 ++-- autoconf.go | 138 +++++++++++++++++++--- autoconf_test.go | 216 ++++++++++++++++++++++++++++++---- docs/environment-variables.md | 45 ++++++- main.go | 15 ++- server.go | 15 ++- 7 files changed, 393 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bae990..7b2722b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,9 @@ The following emojis are used to highlight certain changes: ### Added -- Added automatic configuration (autoconf) of bootstrap nodes and delegated routing endpoints. Autoconf can be disabled and autoconf endpoints can be manually supplied to provide alternate configuration data. Delegated routing endpoints can also be manually supplied, overriding auto-configured values. -- AutoConf support with `auto` placeholders for bootstrap peers and delegated routing endpoints ([Autoconf #123](https://github.com/ipfs/someguy/pull/123)) - - Configuration flags: - - `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable automatic configuration expansion (default: `true`) - - `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch autoconf data from (default: `https://conf.ipfs-mainnet.org/autoconf.json`) - - `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: Interval for refreshing autoconf data (default: `24h`) - - When autoconf is disabled, `auto` placeholders will cause an error, requiring explicit values +- AutoConf support: automatic configuration of bootstrap peers and delegated routing endpoints ([#123](https://github.com/ipfs/someguy/pull/123)). When enabled (default), the `auto` placeholder is replaced with network-recommended values. + - All endpoint flags (`--provider-endpoints`, `--peer-endpoints`, `--ipns-endpoints`) default to `auto` + - See [environment-variables.md](docs/environment-variables.md#someguy_autoconf) for configuration details ### Changed diff --git a/README.md b/README.md index 38dccac..7ba550a 100644 --- a/README.md +++ b/README.md @@ -65,17 +65,24 @@ For more details run `someguy ask --help`. ### AutoConf -Someguy supports automatic configuration of bootstrap peers and delegated routing endpoints through the autoconf feature. When enabled (default), Someguy will fetch configuration from a remote URL and automatically expand the special `auto` placeholder value with network-appropriate defaults. +Automatic configuration of bootstrap peers and delegated routing endpoints. When enabled (default), the `auto` placeholder is replaced with network-recommended values fetched from a remote URL. -This feature can be configured via: -- `--autoconf` / `SOMEGUY_AUTOCONF`: Enable/disable autoconf (default: `true`) -- `--autoconf-url` / `SOMEGUY_AUTOCONF_URL`: URL to fetch configuration from (default: `https://conf.ipfs-mainnet.org/autoconf.json`) -- `--autoconf-refresh` / `SOMEGUY_AUTOCONF_REFRESH`: How often to refresh configuration (default: `24h`) +Configuration: +- `--autoconf` / [`SOMEGUY_AUTOCONF`](docs/environment-variables.md#someguy_autoconf) +- `--autoconf-url` / [`SOMEGUY_AUTOCONF_URL`](docs/environment-variables.md#someguy_autoconf_url) +- `--autoconf-refresh` / [`SOMEGUY_AUTOCONF_REFRESH`](docs/environment-variables.md#someguy_autoconf_refresh) -The `auto` placeholder can be used in: -- `--endpoint` / `SOMEGUY_DELEGATED_ENDPOINT`: the Delegated Routing V1 endpoint to ask +Endpoint flags (default to `auto`): +- `--provider-endpoints` / [`SOMEGUY_PROVIDER_ENDPOINTS`](docs/environment-variables.md#someguy_provider_endpoints) +- `--peer-endpoints` / [`SOMEGUY_PEER_ENDPOINTS`](docs/environment-variables.md#someguy_peer_endpoints) +- `--ipns-endpoints` / [`SOMEGUY_IPNS_ENDPOINTS`](docs/environment-variables.md#someguy_ipns_endpoints) -**Note:** When autoconf is disabled (`--autoconf=false`), using the `auto` placeholder will cause an error. You must provide explicit values for these configurations when autoconf is disabled. +To use custom endpoints instead of `auto`: +```bash +someguy start --ipns-endpoints https://example.com +``` + +See [environment-variables.md](docs/environment-variables.md) for URL formats and configuration details. ## Deployment diff --git a/autoconf.go b/autoconf.go index 233d841..e02e3ac 100644 --- a/autoconf.go +++ b/autoconf.go @@ -13,7 +13,7 @@ import ( "github.com/multiformats/go-multiaddr" ) -// autoConfConfig contains the configuration for the autoconf subsystem +// autoConfConfig contains the configuration for the autoconf subsystem. type autoConfConfig struct { // enabled determines whether to use autoconf // Default: true @@ -58,37 +58,141 @@ func getBootstrapPeerAddrInfos(cfg *config, autoConf *autoconf.Config) []peer.Ad return stringsToPeerAddrInfos(autoconf.FallbackBootstrapPeers) } -func expandContentEndpoints(cfg *config, autoConf *autoconf.Config) error { +// normalizeEndpointURL validates and normalizes a single endpoint URL for a specific routing type. +// Returns the base URL (with routing path stripped if present) and an error if the URL has a mismatched path. +func normalizeEndpointURL(url, expectedPath, flagName string) (string, error) { + // "auto" placeholder passes through unchanged + if url == autoconf.AutoPlaceholder { + return url, nil + } + + // Check if URL has the expected routing path + if strings.HasSuffix(url, expectedPath) { + // Strip the expected path to get base URL + return strings.TrimSuffix(url, expectedPath), nil + } + + // Check if URL has a different routing path (potential misconfiguration) + routingPaths := []string{ + autoconf.RoutingV1ProvidersPath, + autoconf.RoutingV1PeersPath, + autoconf.RoutingV1IPNSPath, + } + for _, path := range routingPaths { + if strings.HasSuffix(url, path) { + return "", fmt.Errorf("URL %q has path %q which doesn't match %s (expected %q or no path)", url, path, flagName, expectedPath) + } + } + + // URL has no routing path or unknown path - treat as base URL + return url, nil +} + +// validateEndpointURLs validates and normalizes a list of endpoint URLs for a specific routing type +func validateEndpointURLs(urls []string, expectedPath, flagName, envVar string) ([]string, error) { + normalized := make([]string, 0, len(urls)) + for _, url := range urls { + baseURL, err := normalizeEndpointURL(url, expectedPath, flagName) + if err != nil { + return nil, fmt.Errorf("%s: %w. Use %s or %s to fix", envVar, err, envVar, flagName) + } + normalized = append(normalized, baseURL) + } + return normalized, nil +} + +// expandDelegatedRoutingEndpoints expands autoconf placeholders and categorizes endpoints by path +func expandDelegatedRoutingEndpoints(cfg *config, autoConf *autoconf.Config) error { + // Validate and normalize each flag's URLs separately + normalizedProviders, err := validateEndpointURLs(cfg.contentEndpoints, autoconf.RoutingV1ProvidersPath, "--provider-endpoints", "SOMEGUY_PROVIDER_ENDPOINTS") + if err != nil { + return err + } + + normalizedPeers, err := validateEndpointURLs(cfg.peerEndpoints, autoconf.RoutingV1PeersPath, "--peer-endpoints", "SOMEGUY_PEER_ENDPOINTS") + if err != nil { + return err + } + + normalizedIPNS, err := validateEndpointURLs(cfg.ipnsEndpoints, autoconf.RoutingV1IPNSPath, "--ipns-endpoints", "SOMEGUY_IPNS_ENDPOINTS") + if err != nil { + return err + } + if !cfg.autoConf.enabled { - if slices.Contains(cfg.contentEndpoints, autoconf.AutoPlaceholder) { - return autoconfDisabledError("endpoint option", "SOMEGUY_DELEGATED_ENDPOINT", "--endpoint") + // Check for "auto" placeholder when autoconf is disabled + if slices.Contains(normalizedProviders, autoconf.AutoPlaceholder) || + slices.Contains(normalizedPeers, autoconf.AutoPlaceholder) || + slices.Contains(normalizedIPNS, autoconf.AutoPlaceholder) { + return autoconfDisabledError("endpoint option", "SOMEGUY_PROVIDER_ENDPOINTS/SOMEGUY_PEER_ENDPOINTS/SOMEGUY_IPNS_ENDPOINTS", "--provider-endpoints/--peer-endpoints/--ipns-endpoints") } + // No autoconf, keep normalized endpoints as configured + cfg.contentEndpoints = deduplicateEndpoints(normalizedProviders) + cfg.peerEndpoints = deduplicateEndpoints(normalizedPeers) + cfg.ipnsEndpoints = deduplicateEndpoints(normalizedIPNS) return nil } nativeSystems := getNativeSystems(cfg.dhtType) - // Someguy only uses read-only endpoints for providers, peers, and IPNS - cfg.contentEndpoints = autoconf.ExpandDelegatedEndpoints(cfg.contentEndpoints, autoConf, nativeSystems, + // Expand each routing type separately to maintain category information + expandedProviders := autoconf.ExpandDelegatedEndpoints( + normalizedProviders, + autoConf, + nativeSystems, autoconf.RoutingV1ProvidersPath, + ) + + expandedPeers := autoconf.ExpandDelegatedEndpoints( + normalizedPeers, + autoConf, + nativeSystems, autoconf.RoutingV1PeersPath, - autoconf.RoutingV1IPNSPath) + ) - // Need to remove "/routing/v1/providers" because routing.FindProviders adds it back on. - for i, ep := range cfg.contentEndpoints { - ep = strings.TrimSuffix(ep, autoconf.RoutingV1ProvidersPath) - ep = strings.TrimSuffix(ep, autoconf.RoutingV1PeersPath) - ep = strings.TrimSuffix(ep, autoconf.RoutingV1IPNSPath) - cfg.contentEndpoints[i] = ep - } + expandedIPNS := autoconf.ExpandDelegatedEndpoints( + normalizedIPNS, + autoConf, + nativeSystems, + autoconf.RoutingV1IPNSPath, + ) + + // Strip routing paths from expanded URLs to get base URLs + cfg.contentEndpoints = stripRoutingPaths(expandedProviders, autoconf.RoutingV1ProvidersPath) + cfg.peerEndpoints = stripRoutingPaths(expandedPeers, autoconf.RoutingV1PeersPath) + cfg.ipnsEndpoints = stripRoutingPaths(expandedIPNS, autoconf.RoutingV1IPNSPath) - // Remove duplicates. - slices.Sort(cfg.contentEndpoints) - cfg.contentEndpoints = slices.Compact(cfg.contentEndpoints) + logger.Debugf("expanded endpoints - providers: %v, peers: %v, IPNS: %v", + cfg.contentEndpoints, cfg.peerEndpoints, cfg.ipnsEndpoints) return nil } +// stripRoutingPaths strips the routing path from URLs and deduplicates +// URLs without the expected path are kept as base URLs (from custom config) +func stripRoutingPaths(urls []string, expectedPath string) []string { + result := make([]string, 0, len(urls)) + for _, url := range urls { + if strings.HasSuffix(url, expectedPath) { + // Autoconf-expanded URL with path - strip it + result = append(result, strings.TrimSuffix(url, expectedPath)) + } else { + // Custom base URL without path - keep as-is + result = append(result, url) + } + } + return deduplicateEndpoints(result) +} + +// deduplicateEndpoints removes duplicate endpoints from a list +func deduplicateEndpoints(endpoints []string) []string { + if len(endpoints) == 0 { + return endpoints + } + slices.Sort(endpoints) + return slices.Compact(endpoints) +} + // autoconfDisabledError returns a consistent error message when auto placeholder is found but autoconf is disabled func autoconfDisabledError(configType, envVar, flag string) error { return fmt.Errorf("'auto' placeholder found in %s but autoconf is disabled. Set explicit %s with %s or %s, or re-enable autoconf", diff --git a/autoconf_test.go b/autoconf_test.go index 8c0523a..68b25ff 100644 --- a/autoconf_test.go +++ b/autoconf_test.go @@ -60,33 +60,205 @@ func TestGetNativeSystems(t *testing.T) { } } -// TestExpandContentEndpoints verifies endpoint expansion behavior when autoconf is disabled -func TestExpandContentEndpoints(t *testing.T) { - cfg := config{ - autoConf: autoConfConfig{ - enabled: false, - }, - contentEndpoints: []string{autoconf.AutoPlaceholder}, - } - +// TestExpandDelegatedRoutingEndpoints verifies endpoint expansion and path categorization +func TestExpandDelegatedRoutingEndpoints(t *testing.T) { t.Run("auto placeholder errors when autoconf disabled", func(t *testing.T) { - err := expandContentEndpoints(&cfg, nil) + cfg := config{ + autoConf: autoConfConfig{ + enabled: false, + }, + contentEndpoints: []string{autoconf.AutoPlaceholder}, + } + err := expandDelegatedRoutingEndpoints(&cfg, nil) require.Error(t, err, "should error when 'auto' is used with autoconf disabled") - assert.Contains(t, err.Error(), "'auto' placeholder found in endpoint option", "error should mention bootstrap peers") - assert.Contains(t, err.Error(), "SOMEGUY_DELEGATED_ENDPOINT", "error should mention how to fix") + assert.Contains(t, err.Error(), "'auto' placeholder found in endpoint option") + assert.Contains(t, err.Error(), "SOMEGUY_PROVIDER_ENDPOINTS") + }) + + t.Run("custom endpoints without paths preserved", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: false, + }, + contentEndpoints: []string{"https://example.com"}, + } + err := expandDelegatedRoutingEndpoints(&cfg, nil) + require.NoError(t, err) + // Without autoconf, endpoints pass through unchanged + assert.Equal(t, []string{"https://example.com"}, cfg.contentEndpoints) + }) + + t.Run("separate flags with matching paths", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", // no native systems to exclude + contentEndpoints: []string{ + "https://provider-only.example.com/routing/v1/providers", + "https://all-in-one.example.com/routing/v1/providers", + }, + peerEndpoints: []string{ + "https://peer-only.example.com/routing/v1/peers", + "https://all-in-one.example.com/routing/v1/peers", + }, + ipnsEndpoints: []string{ + "https://ipns-only.example.com/routing/v1/ipns", + "https://all-in-one.example.com/routing/v1/ipns", + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // Verify paths stripped to base URLs + assert.ElementsMatch(t, []string{ + "https://all-in-one.example.com", + "https://provider-only.example.com", + }, cfg.contentEndpoints, "provider endpoints should have paths stripped") + + assert.ElementsMatch(t, []string{ + "https://all-in-one.example.com", + "https://peer-only.example.com", + }, cfg.peerEndpoints, "peer endpoints should have paths stripped") + + assert.ElementsMatch(t, []string{ + "https://all-in-one.example.com", + "https://ipns-only.example.com", + }, cfg.ipnsEndpoints, "IPNS endpoints should have paths stripped") }) - t.Run("custom endpoint preserved when autoconf disabled", func(t *testing.T) { - custom := []string{"https://example.com"} - cfg.contentEndpoints = custom - err := expandContentEndpoints(&cfg, nil) - require.NoError(t, err, "custom endpoint should not error") - assert.Equal(t, custom, cfg.contentEndpoints, "custom endpoint should be preserved") + t.Run("base URLs and unknown paths accepted", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + "https://example.com/routing/v1/providers", + "https://example.com/custom/path", + "https://example.com", + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // All URLs accepted: known path stripped, unknown path and base URL kept + assert.ElementsMatch(t, []string{ + "https://example.com", + "https://example.com/custom/path", + }, cfg.contentEndpoints) + assert.Empty(t, cfg.peerEndpoints) + assert.Empty(t, cfg.ipnsEndpoints) }) - t.Run("mixed auto and custom errors when autoconf disabled", func(t *testing.T) { - cfg.contentEndpoints = []string{autoconf.AutoPlaceholder, "https://example.com"} - err := expandContentEndpoints(&cfg, nil) - require.Error(t, err, "should error when 'auto' is mixed with custom values") + t.Run("deduplication works", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + "https://example.com/routing/v1/providers", + "https://example.com/routing/v1/providers", // duplicate + "https://example.com", // duplicate after path stripping + }, + peerEndpoints: []string{ + "https://example.com/routing/v1/peers", + "https://example.com", // duplicate after path stripping + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // Duplicates removed, paths stripped + assert.Equal(t, []string{"https://example.com"}, cfg.contentEndpoints) + assert.Equal(t, []string{"https://example.com"}, cfg.peerEndpoints) + }) + + t.Run("mismatched path errors", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + "https://example.com/routing/v1/peers", // wrong path for provider endpoints + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.Error(t, err) + assert.Contains(t, err.Error(), "/routing/v1/peers") + assert.Contains(t, err.Error(), "--provider-endpoints") + }) + + t.Run("mixing auto with custom URLs", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + autoconf.AutoPlaceholder, + "https://custom-provider.example.com", + }, + peerEndpoints: []string{ + "https://custom-peer.example.com", + }, + ipnsEndpoints: []string{ + autoconf.AutoPlaceholder, + }, + } + + // Empty autoConf means "auto" expands to nothing, custom URLs preserved + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // Custom URLs should be preserved + assert.Equal(t, []string{"https://custom-provider.example.com"}, cfg.contentEndpoints) + assert.Equal(t, []string{"https://custom-peer.example.com"}, cfg.peerEndpoints) + assert.Empty(t, cfg.ipnsEndpoints) // auto expanded to nothing + }) + + t.Run("multiple custom URLs in one flag", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + "https://a.example.com", + "https://b.example.com/routing/v1/providers", + "https://c.example.com", + }, + peerEndpoints: []string{ + "https://peer1.example.com/routing/v1/peers", + "https://peer2.example.com", + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // All URLs should be processed (paths stripped where present) + assert.ElementsMatch(t, []string{ + "https://a.example.com", + "https://b.example.com", + "https://c.example.com", + }, cfg.contentEndpoints) + + assert.ElementsMatch(t, []string{ + "https://peer1.example.com", + "https://peer2.example.com", + }, cfg.peerEndpoints) }) } diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 823a7c1..a61394b 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -11,6 +11,9 @@ - [`SOMEGUY_PROVIDER_ENDPOINTS`](#someguy_provider_endpoints) - [`SOMEGUY_PEER_ENDPOINTS`](#someguy_peer_endpoints) - [`SOMEGUY_IPNS_ENDPOINTS`](#someguy_ipns_endpoints) + - [`SOMEGUY_AUTOCONF`](#someguy_autoconf) + - [`SOMEGUY_AUTOCONF_URL`](#someguy_autoconf_url) + - [`SOMEGUY_AUTOCONF_REFRESH`](#someguy_autoconf_refresh) - [`SOMEGUY_HTTP_BLOCK_PROVIDER_ENDPOINTS`](#someguy_http_block_provider_endpoints) - [`SOMEGUY_HTTP_BLOCK_PROVIDER_PEERIDS`](#someguy_http_block_provider_peerids) - [`SOMEGUY_LIBP2P_LISTEN_ADDRS`](#someguy_libp2p_listen_addrs) @@ -62,21 +65,51 @@ Default: `true` ### `SOMEGUY_PROVIDER_ENDPOINTS` -Comma-separated list of other Delegated Routing V1 endpoints to proxy provider requests to. +Comma-separated list of [Delegated Routing V1](https://specs.ipfs.tech/routing/http-routing-v1/) endpoints for provider lookups. -Default: `https://cid.contact` +Supports two URL formats: +- Base URL without path: `https://example.com` +- Full URL with path: `https://example.com/routing/v1/providers` + +When using the `auto` placeholder (default), endpoints are automatically configured from the network configuration at [`SOMEGUY_AUTOCONF_URL`](#someguy_autoconf_url). + +Default: `auto` ### `SOMEGUY_PEER_ENDPOINTS` -Comma-separated list of other Delegated Routing V1 endpoints to proxy peer requests to. +Comma-separated list of Delegated Routing V1 endpoints for peer routing. -Default: none +URL formats: same as [`SOMEGUY_PROVIDER_ENDPOINTS`](#someguy_provider_endpoints) (use `/routing/v1/peers` path). + +Default: `auto` ### `SOMEGUY_IPNS_ENDPOINTS` -Comma-separated list of other Delegated Routing V1 endpoints to proxy IPNS requests to. +Comma-separated list of Delegated Routing V1 endpoints for IPNS records. -Default: none +URL formats: same as [`SOMEGUY_PROVIDER_ENDPOINTS`](#someguy_provider_endpoints) (use `/routing/v1/ipns` path). + +Default: `auto` + +### `SOMEGUY_AUTOCONF` + +Enable or disable automatic configuration (autoconf) of delegated routing endpoints and bootstrap peers. + +When enabled, the `auto` placeholder in endpoint configuration is replaced with network-recommended values fetched from the autoconf URL. + +Default: `true` + +### `SOMEGUY_AUTOCONF_URL` + +URL to fetch autoconf data from. Defaults to the service that provides configuration for [IPFS Mainnet](https://docs.ipfs.tech/concepts/glossary/#mainnet). + +Default: `https://conf.ipfs-mainnet.org/autoconf.json` + +### `SOMEGUY_AUTOCONF_REFRESH` + +How often to refresh the autoconf data. The configuration is cached and updated at this interval. + +Default: `24h` ### `SOMEGUY_HTTP_BLOCK_PROVIDER_ENDPOINTS` diff --git a/main.go b/main.go index 5c9c952..3c8f7d4 100644 --- a/main.go +++ b/main.go @@ -79,13 +79,13 @@ func main() { }, &cli.StringSliceFlag{ Name: "peer-endpoints", - Value: cli.NewStringSlice(), + Value: cli.NewStringSlice(autoconf.AutoPlaceholder), EnvVars: []string{"SOMEGUY_PEER_ENDPOINTS"}, Usage: "other Delegated Routing V1 endpoints to proxy peer requests to", }, &cli.StringSliceFlag{ Name: "ipns-endpoints", - Value: cli.NewStringSlice(), + Value: cli.NewStringSlice(autoconf.AutoPlaceholder), EnvVars: []string{"SOMEGUY_IPNS_ENDPOINTS"}, Usage: "other Delegated Routing V1 endpoints to proxy IPNS requests to", }, @@ -239,10 +239,9 @@ func main() { Name: "ask", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "endpoint", - Value: autoconf.AutoPlaceholder, - EnvVars: []string{"SOMEGUY_DELEGATED_ENDPOINT"}, - Usage: "the Delegated Routing V1 endpoint to ask", + Name: "endpoint", + Value: autoconf.AutoPlaceholder, + Usage: "the Delegated Routing V1 endpoint to ask", }, &cli.BoolFlag{ Name: "pretty", @@ -305,7 +304,7 @@ func main() { logger.Error(err.Error()) } - if err = expandContentEndpoints(cfg, autoConf); err != nil { + if err = expandDelegatedRoutingEndpoints(cfg, autoConf); err != nil { return err } if len(cfg.contentEndpoints) == 0 { @@ -347,7 +346,7 @@ func main() { logger.Error(err.Error()) } - if err = expandContentEndpoints(cfg, autoConf); err != nil { + if err = expandDelegatedRoutingEndpoints(cfg, autoConf); err != nil { return err } if len(cfg.contentEndpoints) == 0 { diff --git a/server.go b/server.go index a8c615d..b8e97bc 100644 --- a/server.go +++ b/server.go @@ -113,10 +113,23 @@ func start(ctx context.Context, cfg *config) error { } bootstrapAddrInfos := getBootstrapPeerAddrInfos(cfg, autoConf) - if err = expandContentEndpoints(cfg, autoConf); err != nil { + + // Expand delegated routing endpoints and categorize by path + if err = expandDelegatedRoutingEndpoints(cfg, autoConf); err != nil { return err } + // Print delegated routing endpoints + if len(cfg.contentEndpoints) > 0 { + fmt.Printf("Delegated routing endpoints for /routing/v1/providers: %v\n", cfg.contentEndpoints) + } + if len(cfg.peerEndpoints) > 0 { + fmt.Printf("Delegated routing endpoints for /routing/v1/peers: %v\n", cfg.peerEndpoints) + } + if len(cfg.ipnsEndpoints) > 0 { + fmt.Printf("Delegated routing endpoints for /routing/v1/ipns: %v\n", cfg.ipnsEndpoints) + } + fmt.Printf("Someguy libp2p host listening on %v\n", h.Addrs()) var dhtRouting routing.Routing switch cfg.dhtType { From 7a095400cce052838aa95739f7dffd2bef1b0447 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 19 Nov 2025 00:52:56 +0100 Subject: [PATCH 12/16] refactor(autoconf): deduplicate HTTP clients per base URL when the same base URL appears in multiple endpoint configs (provider/peer/ipns), create only ONE HTTP client instead of three separate clients with duplicate connection pools this was not an issue with cid.contact as it only supported /providers, but is necessary to future-proof if autoconf provides more routing systems in the future implementation: - client_delegated_routing.go: new file for delegated routing client logic - collectEndpoints: deduplicates URLs and aggregates capabilities - createDelegatedHTTPRouters: creates one client per unique base URL and registers metrics once for all delegated HTTP clients - combineRouters: simplified to combine routers without client creation - clientRouter: moved from server_routers.go to co-locate with client creation same client instance is reused across routing types when base URL matches, significantly reducing memory and connection overhead --- client_delegated_routing.go | 127 ++++++++++++++++++++++++++++++++++++ server.go | 49 ++++---------- server_routers.go | 15 ----- server_test.go | 18 ++--- 4 files changed, 150 insertions(+), 59 deletions(-) create mode 100644 client_delegated_routing.go diff --git a/client_delegated_routing.go b/client_delegated_routing.go new file mode 100644 index 0000000..b6fac33 --- /dev/null +++ b/client_delegated_routing.go @@ -0,0 +1,127 @@ +package main + +import ( + "context" + "fmt" + + drclient "github.com/ipfs/boxo/routing/http/client" + "github.com/ipfs/boxo/routing/http/types" + "github.com/ipfs/boxo/routing/http/types/iter" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "go.opencensus.io/stats/view" +) + +// clientRouter wraps an HTTP delegated routing client to implement the router interface +var _ router = clientRouter{} + +type clientRouter struct { + *drclient.Client +} + +func (d clientRouter) FindProviders(ctx context.Context, cid cid.Cid, limit int) (iter.ResultIter[types.Record], error) { + return d.Client.FindProviders(ctx, cid) +} + +func (d clientRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[*types.PeerRecord], error) { + return d.Client.FindPeers(ctx, pid) +} + +// endpointConfig tracks which routing capabilities a base URL should provide +type endpointConfig struct { + baseURL string + providers bool // FindProviders capability + peers bool // FindPeer capability + ipns bool // GetIPNS/PutIPNS capability +} + +// collectEndpoints deduplicates base URLs across all endpoint types and +// aggregates their capabilities. This ensures we create only one HTTP client +// per unique base URL, even if it appears in multiple endpoint configurations. +func collectEndpoints(cfg *config) []endpointConfig { + capabilities := make(map[string]*endpointConfig) + + // Collect provider endpoints + for _, url := range cfg.contentEndpoints { + if caps := capabilities[url]; caps != nil { + caps.providers = true + } else { + capabilities[url] = &endpointConfig{baseURL: url, providers: true} + } + } + + // Collect peer endpoints + for _, url := range cfg.peerEndpoints { + if caps := capabilities[url]; caps != nil { + caps.peers = true + } else { + capabilities[url] = &endpointConfig{baseURL: url, peers: true} + } + } + + // Collect IPNS endpoints + for _, url := range cfg.ipnsEndpoints { + if caps := capabilities[url]; caps != nil { + caps.ipns = true + } else { + capabilities[url] = &endpointConfig{baseURL: url, ipns: true} + } + } + + // Convert map to slice + result := make([]endpointConfig, 0, len(capabilities)) + for _, caps := range capabilities { + result = append(result, *caps) + } + + return result +} + +// createDelegatedHTTPRouters creates deduplicated HTTP routing clients. +// It ensures that each unique base URL gets exactly one HTTP client, even if +// that URL appears in multiple endpoint configurations (provider/peer/ipns). +// The same client instance is added to multiple router lists based on its +// aggregated capabilities. +func createDelegatedHTTPRouters(cfg *config) (providers, peers, ipns []router, err error) { + endpoints := collectEndpoints(cfg) + + var providerRouters, peerRouters, ipnsRouters []router + + for _, endpoint := range endpoints { + // Create ONE HTTP client per unique base URL + client, err := drclient.New( + endpoint.baseURL, + drclient.WithUserAgent("someguy/"+buildVersion()), + // override default filters, we want all results from remote endpoint + drclient.WithProtocolFilter([]string{}), + drclient.WithDisabledLocalFiltering(true), + ) + if err != nil { + return nil, nil, nil, err + } + + // Wrap in clientRouter - this implements all routing interfaces + router := clientRouter{Client: client} + + // Add the same router instance to appropriate lists based on capabilities + if endpoint.providers { + providerRouters = append(providerRouters, router) + } + if endpoint.peers { + peerRouters = append(peerRouters, router) + } + if endpoint.ipns { + ipnsRouters = append(ipnsRouters, router) + } + } + + // Register delegated routing client metrics only once for all HTTP clients. + // We must avoid registering multiple times since view.Register() is a global operation. + if len(providerRouters) > 0 || len(peerRouters) > 0 || len(ipnsRouters) > 0 { + if err := view.Register(drclient.OpenCensusViews...); err != nil { + return nil, nil, nil, fmt.Errorf("registering HTTP delegated routing views: %w", err) + } + } + + return providerRouters, peerRouters, ipnsRouters, nil +} diff --git a/server.go b/server.go index b8e97bc..330cc18 100644 --- a/server.go +++ b/server.go @@ -18,7 +18,6 @@ import ( "github.com/CAFxX/httpcompression" sddaemon "github.com/coreos/go-systemd/v22/daemon" "github.com/felixge/httpsnoop" - drclient "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/server" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p" @@ -188,20 +187,16 @@ func start(ctx context.Context, cfg *config) error { } } - crRouters, err := getCombinedRouting(cfg.contentEndpoints, dhtRouting, cachedAddrBook, blockProviderRouters) + // Create deduplicated HTTP routers - one client per unique base URL + providerHTTPRouters, peerHTTPRouters, ipnsHTTPRouters, err := createDelegatedHTTPRouters(cfg) if err != nil { return err } - prRouters, err := getCombinedRouting(cfg.peerEndpoints, dhtRouting, cachedAddrBook, nil) - if err != nil { - return err - } - - ipnsRouters, err := getCombinedRouting(cfg.ipnsEndpoints, dhtRouting, cachedAddrBook, nil) - if err != nil { - return err - } + // Combine HTTP routers with DHT and additional routers + crRouters := combineRouters(dhtRouting, cachedAddrBook, providerHTTPRouters, blockProviderRouters) + prRouters := combineRouters(dhtRouting, cachedAddrBook, peerHTTPRouters, nil) + ipnsRouters := combineRouters(dhtRouting, cachedAddrBook, ipnsHTTPRouters, nil) _, port, err := net.SplitHostPort(cfg.listenAddress) if err != nil { @@ -328,7 +323,9 @@ func newHost(cfg *config) (host.Host, error) { return h, nil } -func getCombinedRouting(endpoints []string, dht routing.Routing, cachedAddrBook *cachedAddrBook, additionalRouters []router) (router, error) { +// combineRouters combines delegated HTTP routers with DHT and additional routers. +// It no longer creates HTTP clients (that's done in createDelegatedHTTPRouters). +func combineRouters(dht routing.Routing, cachedAddrBook *cachedAddrBook, delegatedRouters, additionalRouters []router) router { var dhtRouter router if cachedAddrBook != nil { @@ -338,31 +335,11 @@ func getCombinedRouting(endpoints []string, dht routing.Routing, cachedAddrBook dhtRouter = sanitizeRouter{libp2pRouter{routing: dht}} } - if len(endpoints) == 0 && len(additionalRouters) == 0 { + if len(delegatedRouters) == 0 && len(additionalRouters) == 0 { if dhtRouter == nil { - return composableRouter{}, nil + return composableRouter{} } - return dhtRouter, nil - } - - var delegatedRouters []router - - for _, endpoint := range endpoints { - drclient, err := drclient.New(endpoint, - drclient.WithUserAgent("someguy/"+buildVersion()), - // override default filters, we want all results from remote endpoint, then someguy's user can use IPIP-484 to narrow them down - drclient.WithProtocolFilter([]string{}), - drclient.WithDisabledLocalFiltering(true), - ) - if err != nil { - return nil, err - } - delegatedRouters = append(delegatedRouters, clientRouter{Client: drclient}) - } - - // setup delegated routing client metrics - if err := view.Register(drclient.OpenCensusViews...); err != nil { - return nil, fmt.Errorf("registering HTTP delegated routing views: %w", err) + return dhtRouter } var routers []router @@ -374,7 +351,7 @@ func getCombinedRouting(endpoints []string, dht routing.Routing, cachedAddrBook return parallelRouter{ routers: routers, - }, nil + } } func withTracingAndDebug(next http.Handler, authToken string) http.Handler { diff --git a/server_routers.go b/server_routers.go index 486951e..607e971 100644 --- a/server_routers.go +++ b/server_routers.go @@ -8,7 +8,6 @@ import ( "time" "github.com/ipfs/boxo/ipns" - "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/server" "github.com/ipfs/boxo/routing/http/types" "github.com/ipfs/boxo/routing/http/types/iter" @@ -412,20 +411,6 @@ func (it *peerChanIter) Close() error { return nil } -var _ router = clientRouter{} - -type clientRouter struct { - *client.Client -} - -func (d clientRouter) FindProviders(ctx context.Context, cid cid.Cid, limit int) (iter.ResultIter[types.Record], error) { - return d.Client.FindProviders(ctx, cid) -} - -func (d clientRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[*types.PeerRecord], error) { - return d.Client.FindPeers(ctx, pid) -} - var _ server.ContentRouter = sanitizeRouter{} type sanitizeRouter struct { diff --git a/server_test.go b/server_test.go index a09dd6c..e4ffbb9 100644 --- a/server_test.go +++ b/server_test.go @@ -6,19 +6,21 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetCombinedRouting(t *testing.T) { +func TestCombineRouters(t *testing.T) { t.Parallel() - // Check of the result of get combined routing is a sanitize router. - v, err := getCombinedRouting(nil, &bundledDHT{}, nil, nil) - require.NoError(t, err) + // Mock router for testing + mockRouter := composableRouter{} + + // Check that combineRouters with DHT only returns sanitizeRouter + v := combineRouters(&bundledDHT{}, nil, nil, nil) require.IsType(t, sanitizeRouter{}, v) - v, err = getCombinedRouting([]string{"https://example.com/"}, nil, nil, nil) - require.NoError(t, err) + // Check that combineRouters with delegated routers only returns parallelRouter + v = combineRouters(nil, nil, []router{mockRouter}, nil) require.IsType(t, parallelRouter{}, v) - v, err = getCombinedRouting([]string{"https://example.com/"}, &bundledDHT{}, nil, nil) - require.NoError(t, err) + // Check that combineRouters with both DHT and delegated routers returns parallelRouter + v = combineRouters(&bundledDHT{}, nil, []router{mockRouter}, nil) require.IsType(t, parallelRouter{}, v) } From 5d595a69b78056f43af671014e50e07aa456a80f Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 19 Nov 2025 01:21:43 +0100 Subject: [PATCH 13/16] chore: improve test coverage and documentation - add file-level godoc to autoconf.go - fix parameter shadowing in createAutoConfClient - add validation for empty strings in collectEndpoints - add tests for collectEndpoints deduplication logic - add trailing slash normalization tests --- autoconf.go | 46 ++-- autoconf_test.go | 398 +++++++++++++++++++++++++++++++ client_delegated_routing.go | 14 +- client_delegated_routing_test.go | 116 +++++++++ 4 files changed, 559 insertions(+), 15 deletions(-) create mode 100644 client_delegated_routing_test.go diff --git a/autoconf.go b/autoconf.go index e02e3ac..95447d7 100644 --- a/autoconf.go +++ b/autoconf.go @@ -1,3 +1,18 @@ +// autoconf.go implements automatic configuration for someguy. +// +// Autoconf fetches network configuration from a remote JSON endpoint to automatically +// configure bootstrap peers and delegated routing endpoints. +// +// The autoconf system: +// - Fetches configuration from a remote URL (configurable) +// - Caches configuration locally and refreshes periodically +// - Falls back to embedded defaults if fetching fails +// - Expands "auto" placeholder in endpoint configuration +// - Filters out endpoints for systems running natively (e.g., DHT) +// - Validates and normalizes endpoint URLs +// +// See https://github.com/ipfs/someguy/blob/main/docs/environment-variables.md +// for configuration options and defaults. package main import ( @@ -170,15 +185,18 @@ func expandDelegatedRoutingEndpoints(cfg *config, autoConf *autoconf.Config) err // stripRoutingPaths strips the routing path from URLs and deduplicates // URLs without the expected path are kept as base URLs (from custom config) +// Handles trailing slashes by normalizing before comparison func stripRoutingPaths(urls []string, expectedPath string) []string { result := make([]string, 0, len(urls)) for _, url := range urls { - if strings.HasSuffix(url, expectedPath) { + // Trim trailing slash for comparison + normalized := strings.TrimSuffix(url, "/") + if strings.HasSuffix(normalized, expectedPath) { // Autoconf-expanded URL with path - strip it - result = append(result, strings.TrimSuffix(url, expectedPath)) + result = append(result, strings.TrimSuffix(normalized, expectedPath)) } else { - // Custom base URL without path - keep as-is - result = append(result, url) + // Custom base URL without path - keep normalized (no trailing slash) + result = append(result, normalized) } } return deduplicateEndpoints(result) @@ -221,24 +239,24 @@ func stringsToPeerAddrInfos(addrs []string) []peer.AddrInfo { } // createAutoConfClient creates an autoconf client with the given configuration -func createAutoConfClient(config autoConfConfig) (*autoconf.Client, error) { - if config.cacheDir == "" { - config.cacheDir = filepath.Join(".", ".autoconf-cache") +func createAutoConfClient(cfg autoConfConfig) (*autoconf.Client, error) { + if cfg.cacheDir == "" { + cfg.cacheDir = filepath.Join(".", ".autoconf-cache") } - if config.refreshInterval == 0 { - config.refreshInterval = autoconf.DefaultRefreshInterval + if cfg.refreshInterval == 0 { + cfg.refreshInterval = autoconf.DefaultRefreshInterval } - if config.url == "" { - config.url = autoconf.MainnetAutoConfURL + if cfg.url == "" { + cfg.url = autoconf.MainnetAutoConfURL } return autoconf.NewClient( - autoconf.WithCacheDir(config.cacheDir), + autoconf.WithCacheDir(cfg.cacheDir), autoconf.WithUserAgent("someguy/"+version), autoconf.WithCacheSize(autoconf.DefaultCacheSize), autoconf.WithTimeout(autoconf.DefaultTimeout), - autoconf.WithURL(config.url), - autoconf.WithRefreshInterval(config.refreshInterval), + autoconf.WithURL(cfg.url), + autoconf.WithRefreshInterval(cfg.refreshInterval), autoconf.WithFallback(autoconf.GetMainnetFallbackConfig), ) } diff --git a/autoconf_test.go b/autoconf_test.go index 68b25ff..b7557c9 100644 --- a/autoconf_test.go +++ b/autoconf_test.go @@ -261,4 +261,402 @@ func TestExpandDelegatedRoutingEndpoints(t *testing.T) { "https://peer2.example.com", }, cfg.peerEndpoints) }) + + t.Run("trailing slashes handled consistently", func(t *testing.T) { + cfg := config{ + autoConf: autoConfConfig{ + enabled: true, + }, + dhtType: "none", + contentEndpoints: []string{ + "https://example.com/routing/v1/providers/", // with trailing slash + "https://another.com/routing/v1/providers", // without trailing slash + "https://base.com/", // base URL with trailing slash + "https://clean.com", // base URL without trailing slash + }, + } + + mockAutoConf := &autoconf.Config{} + err := expandDelegatedRoutingEndpoints(&cfg, mockAutoConf) + require.NoError(t, err) + + // Verify trailing slashes are normalized (removed) consistently + // Paths should be stripped and trailing slashes removed + assert.ElementsMatch(t, []string{ + "https://another.com", + "https://base.com", // trailing slash removed + "https://clean.com", + "https://example.com", // path stripped and trailing slash removed + }, cfg.contentEndpoints) + }) +} + +// TestNormalizeEndpointURL verifies URL normalization and path handling +func TestNormalizeEndpointURL(t *testing.T) { + tests := []struct { + name string + url string + expectedPath string + flagName string + want string + wantErr bool + errContains string + }{ + { + name: "auto placeholder passes through", + url: autoconf.AutoPlaceholder, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: autoconf.AutoPlaceholder, + wantErr: false, + }, + { + name: "URL with expected path stripped", + url: "https://example.com/routing/v1/providers", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "https://example.com", + wantErr: false, + }, + { + name: "URL with trailing slash and expected path passes through", + url: "https://example.com/routing/v1/providers/", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "https://example.com/routing/v1/providers/", + wantErr: false, + }, + { + name: "base URL without path", + url: "https://example.com", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "https://example.com", + wantErr: false, + }, + { + name: "base URL with trailing slash", + url: "https://example.com/", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "https://example.com/", + wantErr: false, + }, + { + name: "peers path in provider flag errors with mismatch message", + url: "https://example.com/routing/v1/peers", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/peers\" which doesn't match --provider-endpoints (expected \"/routing/v1/providers\"", + }, + { + name: "IPNS path in provider flag errors with mismatch message", + url: "https://example.com/routing/v1/ipns", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/ipns\" which doesn't match --provider-endpoints (expected \"/routing/v1/providers\"", + }, + { + name: "provider path in peer flag errors with mismatch message", + url: "https://example.com/routing/v1/providers", + expectedPath: autoconf.RoutingV1PeersPath, + flagName: "--peer-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/providers\" which doesn't match --peer-endpoints (expected \"/routing/v1/peers\"", + }, + { + name: "provider path in IPNS flag errors with mismatch message", + url: "https://example.com/routing/v1/providers", + expectedPath: autoconf.RoutingV1IPNSPath, + flagName: "--ipns-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/providers\" which doesn't match --ipns-endpoints (expected \"/routing/v1/ipns\"", + }, + { + name: "peer path in IPNS flag errors with mismatch message", + url: "https://example.com/routing/v1/peers", + expectedPath: autoconf.RoutingV1IPNSPath, + flagName: "--ipns-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/peers\" which doesn't match --ipns-endpoints (expected \"/routing/v1/ipns\"", + }, + { + name: "IPNS path in peer flag errors with mismatch message", + url: "https://example.com/routing/v1/ipns", + expectedPath: autoconf.RoutingV1PeersPath, + flagName: "--peer-endpoints", + want: "", + wantErr: true, + errContains: "has path \"/routing/v1/ipns\" which doesn't match --peer-endpoints (expected \"/routing/v1/peers\"", + }, + { + name: "custom path accepted", + url: "https://example.com/custom/path", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "https://example.com/custom/path", + wantErr: false, + }, + { + name: "empty URL passes through", + url: "", + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + want: "", + wantErr: false, + }, + { + name: "peer path works for peer endpoints", + url: "https://example.com/routing/v1/peers", + expectedPath: autoconf.RoutingV1PeersPath, + flagName: "--peer-endpoints", + want: "https://example.com", + wantErr: false, + }, + { + name: "IPNS path works for IPNS endpoints", + url: "https://example.com/routing/v1/ipns", + expectedPath: autoconf.RoutingV1IPNSPath, + flagName: "--ipns-endpoints", + want: "https://example.com", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := normalizeEndpointURL(tt.url, tt.expectedPath, tt.flagName) + if tt.wantErr { + require.Error(t, err) + if tt.errContains != "" { + assert.Contains(t, err.Error(), tt.errContains) + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} + +// TestValidateEndpointURLs verifies batch URL validation with proper error messages +func TestValidateEndpointURLs(t *testing.T) { + tests := []struct { + name string + urls []string + expectedPath string + flagName string + envVar string + want []string + wantErr bool + errContains []string + }{ + { + name: "valid URLs pass validation", + urls: []string{"https://a.com", "https://b.com/routing/v1/providers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: []string{"https://a.com", "https://b.com"}, + wantErr: false, + }, + { + name: "auto placeholder passes validation", + urls: []string{autoconf.AutoPlaceholder}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: []string{autoconf.AutoPlaceholder}, + wantErr: false, + }, + { + name: "mixed auto and custom URLs", + urls: []string{autoconf.AutoPlaceholder, "https://custom.com"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: []string{autoconf.AutoPlaceholder, "https://custom.com"}, + wantErr: false, + }, + { + name: "mismatched path error includes flag and env var", + urls: []string{"https://example.com/routing/v1/peers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: nil, + wantErr: true, + errContains: []string{"SOMEGUY_PROVIDER_ENDPOINTS", "--provider-endpoints", "/routing/v1/peers"}, + }, + { + name: "empty URLs list", + urls: []string{}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: []string{}, + wantErr: false, + }, + { + name: "URLs with trailing slashes pass through", + urls: []string{"https://a.com/routing/v1/providers/", "https://b.com/"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + flagName: "--provider-endpoints", + envVar: "SOMEGUY_PROVIDER_ENDPOINTS", + want: []string{"https://a.com/routing/v1/providers/", "https://b.com/"}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateEndpointURLs(tt.urls, tt.expectedPath, tt.flagName, tt.envVar) + if tt.wantErr { + require.Error(t, err) + for _, contains := range tt.errContains { + assert.Contains(t, err.Error(), contains) + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} + +// TestStripRoutingPaths verifies path stripping and deduplication logic +func TestStripRoutingPaths(t *testing.T) { + tests := []struct { + name string + urls []string + expectedPath string + want []string + }{ + { + name: "strips expected paths", + urls: []string{"https://a.com/routing/v1/providers", "https://b.com/routing/v1/providers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "normalizes trailing slashes", + urls: []string{"https://a.com/routing/v1/providers/", "https://b.com/routing/v1/providers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "preserves base URLs without paths", + urls: []string{"https://a.com", "https://b.com"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "preserves custom paths", + urls: []string{"https://a.com/custom/path", "https://b.com"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com/custom/path", "https://b.com"}, + }, + { + name: "deduplicates after stripping", + urls: []string{"https://a.com/routing/v1/providers", "https://a.com", "https://a.com/routing/v1/providers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com"}, + }, + { + name: "mixed URLs with and without paths", + urls: []string{"https://a.com/routing/v1/providers", "https://b.com", "https://c.com/custom"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com", "https://c.com/custom"}, + }, + { + name: "removes trailing slashes from base URLs", + urls: []string{"https://a.com/", "https://b.com"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "deduplicates URLs with and without trailing slashes", + urls: []string{"https://a.com/", "https://a.com", "https://b.com/"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "empty URLs list", + urls: []string{}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{}, + }, + { + name: "single URL with path", + urls: []string{"https://example.com/routing/v1/providers"}, + expectedPath: autoconf.RoutingV1ProvidersPath, + want: []string{"https://example.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := stripRoutingPaths(tt.urls, tt.expectedPath) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestDeduplicateEndpoints verifies deduplication and sorting behavior +func TestDeduplicateEndpoints(t *testing.T) { + tests := []struct { + name string + input []string + want []string + }{ + { + name: "empty slice", + input: []string{}, + want: []string{}, + }, + { + name: "no duplicates", + input: []string{"https://a.com", "https://b.com"}, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "with duplicates", + input: []string{"https://a.com", "https://b.com", "https://a.com"}, + want: []string{"https://a.com", "https://b.com"}, + }, + { + name: "all duplicates", + input: []string{"https://a.com", "https://a.com", "https://a.com"}, + want: []string{"https://a.com"}, + }, + { + name: "unsorted input gets sorted", + input: []string{"https://c.com", "https://a.com", "https://b.com"}, + want: []string{"https://a.com", "https://b.com", "https://c.com"}, + }, + { + name: "duplicates with unsorted input", + input: []string{"https://c.com", "https://a.com", "https://b.com", "https://a.com", "https://c.com"}, + want: []string{"https://a.com", "https://b.com", "https://c.com"}, + }, + { + name: "single element", + input: []string{"https://example.com"}, + want: []string{"https://example.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := deduplicateEndpoints(tt.input) + assert.Equal(t, tt.want, got) + }) + } } diff --git a/client_delegated_routing.go b/client_delegated_routing.go index b6fac33..1389c79 100644 --- a/client_delegated_routing.go +++ b/client_delegated_routing.go @@ -12,7 +12,10 @@ import ( "go.opencensus.io/stats/view" ) -// clientRouter wraps an HTTP delegated routing client to implement the router interface +// clientRouter wraps an HTTP delegated routing client to implement the router interface. +// Only FindProviders and FindPeers are explicitly implemented to adapt the signature +// (our interface includes a limit parameter). The IPNS methods (GetIPNS/PutIPNS) are +// inherited from the embedded drclient.Client as their signatures already match. var _ router = clientRouter{} type clientRouter struct { @@ -43,6 +46,9 @@ func collectEndpoints(cfg *config) []endpointConfig { // Collect provider endpoints for _, url := range cfg.contentEndpoints { + if url == "" { + continue // skip empty strings + } if caps := capabilities[url]; caps != nil { caps.providers = true } else { @@ -52,6 +58,9 @@ func collectEndpoints(cfg *config) []endpointConfig { // Collect peer endpoints for _, url := range cfg.peerEndpoints { + if url == "" { + continue // skip empty strings + } if caps := capabilities[url]; caps != nil { caps.peers = true } else { @@ -61,6 +70,9 @@ func collectEndpoints(cfg *config) []endpointConfig { // Collect IPNS endpoints for _, url := range cfg.ipnsEndpoints { + if url == "" { + continue // skip empty strings + } if caps := capabilities[url]; caps != nil { caps.ipns = true } else { diff --git a/client_delegated_routing_test.go b/client_delegated_routing_test.go new file mode 100644 index 0000000..78acd08 --- /dev/null +++ b/client_delegated_routing_test.go @@ -0,0 +1,116 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCollectEndpoints(t *testing.T) { + t.Run("deduplicates same URL across multiple endpoint types", func(t *testing.T) { + cfg := &config{ + contentEndpoints: []string{"https://example.com"}, + peerEndpoints: []string{"https://example.com"}, + ipnsEndpoints: []string{"https://example.com"}, + } + + endpoints := collectEndpoints(cfg) + + require.Len(t, endpoints, 1, "should have exactly one endpoint") + assert.Equal(t, "https://example.com", endpoints[0].baseURL) + assert.True(t, endpoints[0].providers, "should support providers") + assert.True(t, endpoints[0].peers, "should support peers") + assert.True(t, endpoints[0].ipns, "should support ipns") + }) + + t.Run("handles different URLs separately", func(t *testing.T) { + cfg := &config{ + contentEndpoints: []string{"https://a.com", "https://b.com"}, + peerEndpoints: []string{"https://b.com", "https://c.com"}, + } + + endpoints := collectEndpoints(cfg) + + require.Len(t, endpoints, 3, "should have three separate endpoints") + + // Convert to map for easier testing + urlMap := make(map[string]endpointConfig) + for _, ep := range endpoints { + urlMap[ep.baseURL] = ep + } + + // Verify a.com (providers only) + assert.True(t, urlMap["https://a.com"].providers) + assert.False(t, urlMap["https://a.com"].peers) + assert.False(t, urlMap["https://a.com"].ipns) + + // Verify b.com (providers and peers) + assert.True(t, urlMap["https://b.com"].providers) + assert.True(t, urlMap["https://b.com"].peers) + assert.False(t, urlMap["https://b.com"].ipns) + + // Verify c.com (peers only) + assert.False(t, urlMap["https://c.com"].providers) + assert.True(t, urlMap["https://c.com"].peers) + assert.False(t, urlMap["https://c.com"].ipns) + }) + + t.Run("skips empty strings", func(t *testing.T) { + cfg := &config{ + contentEndpoints: []string{"https://example.com", "", "https://another.com"}, + peerEndpoints: []string{""}, + } + + endpoints := collectEndpoints(cfg) + + require.Len(t, endpoints, 2, "should skip empty strings") + + urlMap := make(map[string]endpointConfig) + for _, ep := range endpoints { + urlMap[ep.baseURL] = ep + } + + assert.Contains(t, urlMap, "https://example.com") + assert.Contains(t, urlMap, "https://another.com") + assert.NotContains(t, urlMap, "") + }) + + t.Run("handles all three endpoint types for different URLs", func(t *testing.T) { + cfg := &config{ + contentEndpoints: []string{"https://provider.com"}, + peerEndpoints: []string{"https://peer.com"}, + ipnsEndpoints: []string{"https://ipns.com"}, + } + + endpoints := collectEndpoints(cfg) + + require.Len(t, endpoints, 3) + + urlMap := make(map[string]endpointConfig) + for _, ep := range endpoints { + urlMap[ep.baseURL] = ep + } + + // Each URL should have only one capability enabled + assert.True(t, urlMap["https://provider.com"].providers) + assert.False(t, urlMap["https://provider.com"].peers) + assert.False(t, urlMap["https://provider.com"].ipns) + + assert.False(t, urlMap["https://peer.com"].providers) + assert.True(t, urlMap["https://peer.com"].peers) + assert.False(t, urlMap["https://peer.com"].ipns) + + assert.False(t, urlMap["https://ipns.com"].providers) + assert.False(t, urlMap["https://ipns.com"].peers) + assert.True(t, urlMap["https://ipns.com"].ipns) + }) + + t.Run("empty config returns empty list", func(t *testing.T) { + cfg := &config{} + + endpoints := collectEndpoints(cfg) + + assert.Empty(t, endpoints) + }) +} From e40a4435c73d471c1792c89c7f64bec5677e6356 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 19 Nov 2025 02:32:41 +0100 Subject: [PATCH 14/16] refactor: improve HTTP client creation and file organization - inline autoconfDisabledError (single use function) - add newDelegatedRoutingClient helper for consistent HTTP client creation - use helper in both server and CLI code to ensure consistent options - rename client_delegated_routing.go to server_delegated_routing.go - add file-level documentation explaining server-side delegated routing --- autoconf.go | 8 +---- client.go | 9 +++--- ..._routing.go => server_delegated_routing.go | 29 ++++++++++++++----- ...est.go => server_delegated_routing_test.go | 0 4 files changed, 27 insertions(+), 19 deletions(-) rename client_delegated_routing.go => server_delegated_routing.go (80%) rename client_delegated_routing_test.go => server_delegated_routing_test.go (100%) diff --git a/autoconf.go b/autoconf.go index 95447d7..70aed3b 100644 --- a/autoconf.go +++ b/autoconf.go @@ -139,7 +139,7 @@ func expandDelegatedRoutingEndpoints(cfg *config, autoConf *autoconf.Config) err if slices.Contains(normalizedProviders, autoconf.AutoPlaceholder) || slices.Contains(normalizedPeers, autoconf.AutoPlaceholder) || slices.Contains(normalizedIPNS, autoconf.AutoPlaceholder) { - return autoconfDisabledError("endpoint option", "SOMEGUY_PROVIDER_ENDPOINTS/SOMEGUY_PEER_ENDPOINTS/SOMEGUY_IPNS_ENDPOINTS", "--provider-endpoints/--peer-endpoints/--ipns-endpoints") + return fmt.Errorf("'auto' placeholder found in endpoint option but autoconf is disabled. Set explicit endpoint option with SOMEGUY_PROVIDER_ENDPOINTS/SOMEGUY_PEER_ENDPOINTS/SOMEGUY_IPNS_ENDPOINTS or --provider-endpoints/--peer-endpoints/--ipns-endpoints, or re-enable autoconf") } // No autoconf, keep normalized endpoints as configured cfg.contentEndpoints = deduplicateEndpoints(normalizedProviders) @@ -211,12 +211,6 @@ func deduplicateEndpoints(endpoints []string) []string { return slices.Compact(endpoints) } -// autoconfDisabledError returns a consistent error message when auto placeholder is found but autoconf is disabled -func autoconfDisabledError(configType, envVar, flag string) error { - return fmt.Errorf("'auto' placeholder found in %s but autoconf is disabled. Set explicit %s with %s or %s, or re-enable autoconf", - configType, configType, envVar, flag) -} - func stringsToPeerAddrInfos(addrs []string) []peer.AddrInfo { addrInfos := make([]peer.AddrInfo, 0, len(addrs)) diff --git a/client.go b/client.go index 2c087c6..b83db7f 100644 --- a/client.go +++ b/client.go @@ -9,14 +9,13 @@ import ( "time" "github.com/ipfs/boxo/ipns" - "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/types" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" ) func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true)) + drc, err := newDelegatedRoutingClient(endpoint) if err != nil { return err } @@ -77,7 +76,7 @@ func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutp } func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint, client.WithDisabledLocalFiltering(true)) + drc, err := newDelegatedRoutingClient(endpoint) if err != nil { return err } @@ -118,7 +117,7 @@ func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput b } func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint) + drc, err := newDelegatedRoutingClient(endpoint) if err != nil { return err } @@ -172,7 +171,7 @@ func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput } func putIPNS(ctx context.Context, name ipns.Name, record []byte, endpoint string) error { - drc, err := client.New(endpoint) + drc, err := newDelegatedRoutingClient(endpoint) if err != nil { return err } diff --git a/client_delegated_routing.go b/server_delegated_routing.go similarity index 80% rename from client_delegated_routing.go rename to server_delegated_routing.go index 1389c79..d30b631 100644 --- a/client_delegated_routing.go +++ b/server_delegated_routing.go @@ -1,3 +1,14 @@ +// server_delegated_routing.go implements HTTP delegated routing for the server. +// +// This file contains code for creating and managing HTTP clients that talk to +// remote delegated routing endpoints (e.g., cid.contact, delegated-ipfs.dev). +// The server uses these HTTP clients to perform content, peer, and IPNS lookups +// when delegated routing is enabled. +// +// Key components: +// - newDelegatedRoutingClient: creates HTTP client with consistent options +// - collectEndpoints: deduplicates URLs and aggregates capabilities +// - createDelegatedHTTPRouters: creates one client per unique base URL package main import ( @@ -30,6 +41,16 @@ func (d clientRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (it return d.Client.FindPeers(ctx, pid) } +// newDelegatedRoutingClient creates an HTTP delegated routing client with consistent options +func newDelegatedRoutingClient(endpoint string) (*drclient.Client, error) { + return drclient.New( + endpoint, + drclient.WithUserAgent("someguy/"+buildVersion()), + drclient.WithProtocolFilter([]string{}), + drclient.WithDisabledLocalFiltering(true), + ) +} + // endpointConfig tracks which routing capabilities a base URL should provide type endpointConfig struct { baseURL string @@ -101,13 +122,7 @@ func createDelegatedHTTPRouters(cfg *config) (providers, peers, ipns []router, e for _, endpoint := range endpoints { // Create ONE HTTP client per unique base URL - client, err := drclient.New( - endpoint.baseURL, - drclient.WithUserAgent("someguy/"+buildVersion()), - // override default filters, we want all results from remote endpoint - drclient.WithProtocolFilter([]string{}), - drclient.WithDisabledLocalFiltering(true), - ) + client, err := newDelegatedRoutingClient(endpoint.baseURL) if err != nil { return nil, nil, nil, err } diff --git a/client_delegated_routing_test.go b/server_delegated_routing_test.go similarity index 100% rename from client_delegated_routing_test.go rename to server_delegated_routing_test.go From a76ff08733ddf04c692e2f674b7a3d63096f2b04 Mon Sep 17 00:00:00 2001 From: Andrew Gillis <11790789+gammazero@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:43:26 -0800 Subject: [PATCH 15/16] Update autoconf.go --- autoconf.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/autoconf.go b/autoconf.go index 70aed3b..b5571ef 100644 --- a/autoconf.go +++ b/autoconf.go @@ -191,13 +191,8 @@ func stripRoutingPaths(urls []string, expectedPath string) []string { for _, url := range urls { // Trim trailing slash for comparison normalized := strings.TrimSuffix(url, "/") - if strings.HasSuffix(normalized, expectedPath) { - // Autoconf-expanded URL with path - strip it - result = append(result, strings.TrimSuffix(normalized, expectedPath)) - } else { - // Custom base URL without path - keep normalized (no trailing slash) - result = append(result, normalized) - } + // Strip path from autoconf-expanded URL or keep normalized base URL. + result = append(result, strings.TrimSuffix(normalized, expectedPath)) } return deduplicateEndpoints(result) } From 70cf45160bc7230d6e1f34d11204ef863bff080c Mon Sep 17 00:00:00 2001 From: Andrew Gillis <11790789+gammazero@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:43:32 -0800 Subject: [PATCH 16/16] Update autoconf.go --- autoconf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoconf.go b/autoconf.go index b5571ef..c193b61 100644 --- a/autoconf.go +++ b/autoconf.go @@ -88,7 +88,7 @@ func normalizeEndpointURL(url, expectedPath, flagName string) (string, error) { } // Check if URL has a different routing path (potential misconfiguration) - routingPaths := []string{ + routingPaths := [...]string{ autoconf.RoutingV1ProvidersPath, autoconf.RoutingV1PeersPath, autoconf.RoutingV1IPNSPath,