From 2a03591ea6f221b350069e7b41e95afd3e0a0b44 Mon Sep 17 00:00:00 2001 From: Joshua Noble Date: Mon, 17 Nov 2025 14:55:24 -0500 Subject: [PATCH] feat: accept X-PIN-ORIGINS peer hint for content retrieval --- CHANGELOG.md | 1 + handlers.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3debc05..5be4a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The following emojis are used to highlight certain changes: - Configurable routing timeouts: new options `RAINBOW_HTTP_ROUTERS_TIMEOUT` and `RAINBOW_ROUTING_TIMEOUT` (and the similar command-line flags) allow setting timeouts for routing operations. The former does it for delegated http routing requests. The latter specifies a timeout for routing requests. - Added `BITSWAP_ENABLE_DUPLICATE_BLOCK_STATS`: Controls whether bitswap duplicate block statistics are collected. This is disabled by default since it has a performance impact. - Allow specifying a DNSLink safelist: `RAINBOW_DNSLINK_GATEWAY_DOMAINS` defines which dnslink domains are allowed to use this gateway. +- ✨ Add support for pin origins via `X-PIN-ORIGINS` request header. Accepts a comma-separated list of IPFS peer multiaddresses; the gateway attempts short, best-effort libp2p connects to accelerate retrieval when peers don’t advertise via DHT/IPNI. ### Changed diff --git a/handlers.go b/handlers.go index 466b7fe..ce12065 100644 --- a/handlers.go +++ b/handlers.go @@ -9,6 +9,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/ipfs/boxo/blockstore" leveldb "github.com/ipfs/go-ds-leveldb" @@ -393,6 +394,9 @@ func setupGatewayHandler(cfg Config, nd *Node) (http.Handler, error) { // Add tracing. handler = withTracingAndDebug(handler, cfg.TracingAuthToken) + // Add pin origins support (X-PIN-ORIGINS). + handler = withPinOrigins(nd, handler) + return handler, nil } @@ -429,6 +433,51 @@ const NoBlockcacheHeader = "Rainbow-No-Blockcache" type NoBlockcache struct{} +const PinOriginsHeader = "X-PIN-ORIGINS" + +// withPinOrigins inspects the request for pin origins header(s) and, if present, +// attempts short, best-effort libp2p connects to the provided multiaddresses. +// This increases the likelihood that Bitswap can retrieve content directly +// from the specified origins even if they are not advertising on DHT/IPNI. +func withPinOrigins(nd *Node, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if nd != nil && nd.host != nil { + // Collect all header values and split by comma to support multiple origins. + rawValues := append([]string{}, r.Header.Values(PinOriginsHeader)...) + if len(rawValues) != 0 { + unique := make(map[string]struct{}) + for _, v := range rawValues { + for _, part := range strings.Split(v, ",") { + s := strings.TrimSpace(part) + if s == "" { + continue + } + unique[s] = struct{}{} + } + } + for maStr := range unique { + ai, err := peer.AddrInfoFromString(maStr) + if err != nil { + goLog.Warnw("Invalid peer origin value", "value", maStr, "err", err) + continue + } + // Fire-and-forget connects to speed up retrieval; don't block the request path. + go func(ai peer.AddrInfo) { + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + if err := nd.host.Connect(ctx, ai); err != nil { + goLog.Debugw("Failed to connect to hinted peer", "peer", ai.ID, "err", err) + return + } + goLog.Debugw("Connected to hinted peer", "peer", ai.ID) + }(*ai) + } + } + } + next.ServeHTTP(w, r) + }) +} + // MutexFractionOption allows to set runtime.SetMutexProfileFraction via HTTP // using POST request with parameter 'fraction'. func MutexFractionOption(path string, mux *http.ServeMux) *http.ServeMux {