diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b47635..67a30d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,18 @@ The following emojis are used to highlight certain changes: ### Added +- Added `/routing/v1/peers/closest/{key}` endpoint implementing [IPIP-476](https://github.com/ipfs/specs/pull/476) + - Returns DHT-closest peers to a given CID or PeerID + - Accepts both CID and legacy PeerID formats (e.g., `12D3KooW...`) + - Uses WAN DHT only for more reliable results + - Includes cached addresses in results when available + ### Changed -- [go-libp2p v0.43.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.43.0) -- [go-libp2p-kad-dht v0.34.0](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.34.0) -- [boxo v0.34.0](https://github.com/ipfs/boxo/releases/tag/v0.34.0) +- [go-libp2p v0.44.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.44.0) +- [go-libp2p-kad-dht v0.35.1](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.35.1) +- [boxo v0.35.1](https://github.com/ipfs/boxo/releases/tag/v0.35.1) +- CLI commands (`ask` subcommands) now default to `delegated-ipfs.dev` instead of `cid.contact` to ensure both IPNI and DHT results are returned without daemon running ### Removed diff --git a/client.go b/client.go index 89dcaf3..8b5679c 100644 --- a/client.go +++ b/client.go @@ -117,6 +117,47 @@ func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput b return nil } +func getClosestPeers(ctx context.Context, key cid.Cid, endpoint string, prettyOutput bool) error { + drc, err := client.New(endpoint) + if err != nil { + return err + } + + recordsIter, err := drc.GetClosestPeers(ctx, key) + if err != nil { + return err + } + defer recordsIter.Close() + + for recordsIter.Next() { + res := recordsIter.Val() + + // Check for error, but do not complain if we exceeded the timeout. We are + // expecting that to happen: we explicitly defined a timeout. + if res.Err != nil { + if !errors.Is(res.Err, context.DeadlineExceeded) { + return res.Err + } + + return nil + } + + if prettyOutput { + fmt.Fprintln(os.Stdout, res.Val.ID) + fmt.Fprintln(os.Stdout, "\tProtocols:", res.Val.Protocols) + fmt.Fprintln(os.Stdout, "\tAddresses:", res.Val.Addrs) + fmt.Fprintln(os.Stdout) + } else { + err := json.NewEncoder(os.Stdout).Encode(res.Val) + if err != nil { + return err + } + } + } + + return nil +} + func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput bool) error { drc, err := client.New(endpoint) if err != nil { diff --git a/go.mod b/go.mod index e15c7f4..2360b64 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ipfs/someguy -go 1.24.0 +go 1.24.6 require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 @@ -9,27 +9,27 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/felixge/httpsnoop v1.0.4 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/ipfs/boxo v0.34.0 + github.com/ipfs/boxo v0.35.1-0.20251017161119-ce6837eb264b github.com/ipfs/go-cid v0.5.0 - github.com/ipfs/go-log/v2 v2.8.1 - github.com/libp2p/go-libp2p v0.43.0 - github.com/libp2p/go-libp2p-kad-dht v0.34.0 + github.com/ipfs/go-log/v2 v2.8.2 + github.com/libp2p/go-libp2p v0.44.0 + github.com/libp2p/go-libp2p-kad-dht v0.35.1 github.com/libp2p/go-libp2p-record v0.3.1 github.com/multiformats/go-multiaddr v0.16.1 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 - golang.org/x/sys v0.36.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.37.0 ) require ( @@ -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 @@ -69,11 +69,11 @@ require ( github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kbucket v0.7.0 // indirect - github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect + github.com/libp2p/go-libp2p-kbucket v0.8.0 // indirect + github.com/libp2p/go-libp2p-routing-helpers v0.7.6-0.20251016083611-f098f492895e // indirect github.com/libp2p/go-libp2p-xor v0.1.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-netroute v0.2.2 // indirect + github.com/libp2p/go-netroute v0.3.0 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.0.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect @@ -89,7 +89,7 @@ require ( github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multicodec v0.9.2 // indirect github.com/multiformats/go-multistream v0.6.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect + github.com/multiformats/go-varint v0.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pion/datachannel v1.5.10 // indirect @@ -114,11 +114,11 @@ 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 - github.com/quic-go/quic-go v0.54.0 // indirect + github.com/quic-go/quic-go v0.55.0 // indirect github.com/quic-go/webtransport-go v0.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/slok/go-http-metrics v0.13.0 // indirect @@ -127,36 +127,38 @@ require ( github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/propagators/aws v1.32.0 // indirect 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.3 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // 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.10 // 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..d100fbd 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= @@ -259,17 +259,17 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ipfs/boxo v0.34.0 h1:pMP9bAsTs4xVh8R0ZmxIWviV7kjDa60U24QrlGgHb1g= -github.com/ipfs/boxo v0.34.0/go.mod h1:kzdH/ewDybtO3+M8MCVkpwnIIc/d2VISX95DFrY4vQA= -github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ= -github.com/ipfs/go-block-format v0.2.2/go.mod h1:vmuefuWU6b+9kIU0vZJgpiJt1yicQz9baHXE8qR+KB8= +github.com/ipfs/boxo v0.35.1-0.20251017161119-ce6837eb264b h1:0455cy5GFGQjPXAq6A2CMIE26zHtDbabkTxQGN+uEhE= +github.com/ipfs/boxo v0.35.1-0.20251017161119-ce6837eb264b/go.mod h1:nFzlv7VEjyWZzPEOKw6CfaR2/BmfBmB17W5/R1YICD4= +github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= +github.com/ipfs/go-block-format v0.2.3/go.mod h1:WJaQmPAKhD3LspLixqlqNFxiZ3BZ3xgqxxoSR/76pnA= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 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= @@ -277,10 +277,10 @@ github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZ github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= -github.com/ipfs/go-log/v2 v2.8.1 h1:Y/X36z7ASoLJaYIJAL4xITXgwf7RVeqb1+/25aq/Xk0= -github.com/ipfs/go-log/v2 v2.8.1/go.mod h1:NyhTBcZmh2Y55eWVjOeKf8M7e4pnJYM3yDZNxQBWEEY= -github.com/ipfs/go-test v0.2.2 h1:1yjYyfbdt1w93lVzde6JZ2einh3DIV40at4rVoyEcE8= -github.com/ipfs/go-test v0.2.2/go.mod h1:cmLisgVwkdRCnKu/CFZOk2DdhOcwghr5GsHeqwexoRA= +github.com/ipfs/go-log/v2 v2.8.2 h1:nVG4nNHUwwI/sTs9Bi5iE8sXFQwXs3AjkkuWhg7+Y2I= +github.com/ipfs/go-log/v2 v2.8.2/go.mod h1:UhIYAwMV7Nb4ZmihUxfIRM2Istw/y9cAk3xaK+4Zs2c= +github.com/ipfs/go-test v0.2.3 h1:Z/jXNAReQFtCYyn7bsv/ZqUwS6E7iIcSpJ2CuzCvnrc= +github.com/ipfs/go-test v0.2.3/go.mod h1:QW8vSKkwYvWFwIZQLGQXdkt9Ud76eQXRQ9Ao2H+cA1o= github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -342,22 +342,22 @@ github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZ github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784= github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo= -github.com/libp2p/go-libp2p v0.43.0 h1:b2bg2cRNmY4HpLK8VHYQXLX2d3iND95OjodLFymvqXU= -github.com/libp2p/go-libp2p v0.43.0/go.mod h1:IiSqAXDyP2sWH+J2gs43pNmB/y4FOi2XQPbsb+8qvzc= +github.com/libp2p/go-libp2p v0.44.0 h1:5Gtt8OrF8yiXmH+Mx4+/iBeFRMK1TY3a8OrEBDEqAvs= +github.com/libp2p/go-libp2p v0.44.0/go.mod h1:NovCojezAt4dnDd4fH048K7PKEqH0UFYYqJRjIIu8zc= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= -github.com/libp2p/go-libp2p-kad-dht v0.34.0 h1:yvJ/Vrt36GVjsqPxiGcuuwOloKuZLV9Aa7awIKyNXy0= -github.com/libp2p/go-libp2p-kad-dht v0.34.0/go.mod h1:JNbkES4W5tajS6uYivw6MPs0842cPHAwhgaPw8sQG4o= +github.com/libp2p/go-libp2p-kad-dht v0.35.1 h1:RQglhc9OxqDwlFFdhQMwKxIPBIBfGsleROnK5hqVsoE= +github.com/libp2p/go-libp2p-kad-dht v0.35.1/go.mod h1:1oCXzkkBiYh3d5cMWLpInSOZ6am2AlpC4G+GDcZFcE0= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= -github.com/libp2p/go-libp2p-kbucket v0.7.0 h1:vYDvRjkyJPeWunQXqcW2Z6E93Ywx7fX0jgzb/dGOKCs= -github.com/libp2p/go-libp2p-kbucket v0.7.0/go.mod h1:blOINGIj1yiPYlVEX0Rj9QwEkmVnz3EP8LK1dRKBC6g= +github.com/libp2p/go-libp2p-kbucket v0.8.0 h1:QAK7RzKJpYe+EuSEATAaaHYMYLkPDGC18m9jxPLnU8s= +github.com/libp2p/go-libp2p-kbucket v0.8.0/go.mod h1:JMlxqcEyKwO6ox716eyC0hmiduSWZZl6JY93mGaaqc4= github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= github.com/libp2p/go-libp2p-record v0.3.1/go.mod h1:T8itUkLcWQLCYMqtX7Th6r7SexyUJpIyPgks757td/E= -github.com/libp2p/go-libp2p-routing-helpers v0.7.5 h1:HdwZj9NKovMx0vqq6YNPTh6aaNzey5zHD7HeLJtq6fI= -github.com/libp2p/go-libp2p-routing-helpers v0.7.5/go.mod h1:3YaxrwP0OBPDD7my3D0KxfR89FlcX/IEbxDEDfAmj98= +github.com/libp2p/go-libp2p-routing-helpers v0.7.6-0.20251016083611-f098f492895e h1:6DSfN9gsAmBa1iyAKwIuk9GlEga45iH8MBmuYAuXmpU= +github.com/libp2p/go-libp2p-routing-helpers v0.7.6-0.20251016083611-f098f492895e/go.mod h1:Q1VSaOawgsvaa3hGl/PejADIhl2deiqSEsQDpB3Ggss= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-libp2p-xor v0.1.0 h1:hhQwT4uGrBcuAkUGXADuPltalOdpf9aag9kaYNT2tLA= @@ -365,8 +365,8 @@ github.com/libp2p/go-libp2p-xor v0.1.0/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQ github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= -github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= +github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= +github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= @@ -377,6 +377,8 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marcopolo/simnet v0.0.1 h1:rSMslhPz6q9IvJeFWDoMGxMIrlsbXau3NkuIXHGJxfg= +github.com/marcopolo/simnet v0.0.1/go.mod h1:WDaQkgLAjqDUEBAOXz22+1j6wXKfGlC5sD5XWt3ddOs= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -444,8 +446,8 @@ github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVO github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= +github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -522,8 +524,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 +539,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= @@ -553,13 +555,13 @@ github.com/prometheus/statsd_exporter v0.27.1 h1:tcRJOmwlA83HPfWzosAgr2+zEN5XDFv github.com/prometheus/statsd_exporter v0.27.1/go.mod h1:vA6ryDfsN7py/3JApEst6nLTJboq66XsNcJGNmC88NQ= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70= github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -668,10 +670,10 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 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/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +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 +684,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 +718,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.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 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 +740,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 +752,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-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= +golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/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 +777,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 +824,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 +849,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= @@ -903,8 +907,10 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.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 +929,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 +985,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 +1053,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 +1073,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 +1089,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.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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 dbe244e..6218994 100644 --- a/main.go +++ b/main.go @@ -17,8 +17,14 @@ import ( "github.com/urfave/cli/v2" ) +// cidContactEndpoint is the default for daemon mode (start command). +// Used for proxying provider requests in addition to results from the local DHT client. const cidContactEndpoint = "https://cid.contact" +// delegatedIPFSEndpoint is the default for CLI mode (ask command). +// Used as the only source of routing results when no local DHT is available. +const delegatedIPFSEndpoint = "https://delegated-ipfs.dev" + func main() { app := &cli.App{ Name: name, @@ -209,7 +215,7 @@ func main() { Flags: []cli.Flag{ &cli.StringFlag{ Name: "endpoint", - Value: cidContactEndpoint, + Value: delegatedIPFSEndpoint, Usage: "the Delegated Routing V1 endpoint to ask", }, &cli.BoolFlag{ @@ -251,6 +257,22 @@ func main() { return findPeers(ctx.Context, pid, ctx.String("endpoint"), ctx.Bool("pretty")) }, }, + { + Name: "getclosestpeers", + Usage: "getclosestpeers ", + UsageText: "Find DHT-closest peers to a key (CID or peer ID)", + Action: func(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.New("invalid command, see help") + } + keyStr := ctx.Args().Get(0) + c, err := parseKey(keyStr) + if err != nil { + return err + } + return getClosestPeers(ctx.Context, c, ctx.String("endpoint"), ctx.Bool("pretty")) + }, + }, { Name: "getipns", Usage: "getipns ", @@ -305,3 +327,28 @@ func printIfListConfigured(message string, list []string) { fmt.Printf(message+"%v\n", strings.Join(list, ", ")) } } + +// parseKey parses a string that can be either a CID or a PeerID. +// It accepts the following formats: +// - Arbitrary CIDs (e.g., bafkreidcd7frenco2m6ch7mny63wztgztv3q6fctaffgowkro6kljre5ei) +// - CIDv1 with libp2p-key codec (e.g., bafzaajaiaejca...) +// - Base58-encoded PeerIDs (e.g., 12D3KooW... or QmYyQ...) +// +// Returns the key as a CID. PeerIDs are converted to CIDv1 with libp2p-key codec. +func parseKey(keyStr string) (cid.Cid, error) { + // Try parsing as PeerID first using peer.Decode + // This handles legacy PeerID formats per: + // https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#string-representation + pid, pidErr := peer.Decode(keyStr) + if pidErr == nil { + return peer.ToCid(pid), nil + } + + // Fall back to parsing as CID (handles arbitrary CIDs and CIDv1 libp2p-key format) + c, cidErr := cid.Parse(keyStr) + if cidErr == nil { + return c, nil + } + + return cid.Cid{}, fmt.Errorf("unable to parse as CID or PeerID: %w", errors.Join(cidErr, pidErr)) +} diff --git a/server.go b/server.go index 8c6c993..fdd81c9 100644 --- a/server.go +++ b/server.go @@ -153,17 +153,22 @@ func start(ctx context.Context, cfg *config) error { } } - crRouters, err := getCombinedRouting(cfg.contentEndpoints, dhtRouting, cachedAddrBook, blockProviderRouters) + crRouters, err := getCombinedRouting(cfg.contentEndpoints, h, dhtRouting, cachedAddrBook, blockProviderRouters) if err != nil { return err } - prRouters, err := getCombinedRouting(cfg.peerEndpoints, dhtRouting, cachedAddrBook, nil) + prRouters, err := getCombinedRouting(cfg.peerEndpoints, h, dhtRouting, cachedAddrBook, nil) if err != nil { return err } - ipnsRouters, err := getCombinedRouting(cfg.ipnsEndpoints, dhtRouting, cachedAddrBook, nil) + ipnsRouters, err := getCombinedRouting(cfg.ipnsEndpoints, h, dhtRouting, cachedAddrBook, nil) + if err != nil { + return err + } + + dhtRouters, err := getCombinedRouting(nil, h, dhtRouting, cachedAddrBook, nil) if err != nil { return err } @@ -190,6 +195,7 @@ func start(ctx context.Context, cfg *config) error { providers: crRouters, peers: prRouters, ipns: ipnsRouters, + dht: dhtRouters, }, handlerOpts...) // Add CORS. @@ -293,14 +299,14 @@ func newHost(cfg *config) (host.Host, error) { return h, nil } -func getCombinedRouting(endpoints []string, dht routing.Routing, cachedAddrBook *cachedAddrBook, additionalRouters []router) (router, error) { +func getCombinedRouting(endpoints []string, host host.Host, dht routing.Routing, cachedAddrBook *cachedAddrBook, additionalRouters []router) (router, error) { var dhtRouter router if cachedAddrBook != nil { - cachedRouter := NewCachedRouter(libp2pRouter{routing: dht}, cachedAddrBook) + cachedRouter := NewCachedRouter(libp2pRouter{host: host, routing: dht}, cachedAddrBook) dhtRouter = sanitizeRouter{cachedRouter} } else if dht != nil { - dhtRouter = sanitizeRouter{libp2pRouter{routing: dht}} + dhtRouter = sanitizeRouter{libp2pRouter{host: host, routing: dht}} } if len(endpoints) == 0 && len(additionalRouters) == 0 { diff --git a/server_cached_router.go b/server_cached_router.go index d7e4806..7edc0b7 100644 --- a/server_cached_router.go +++ b/server_cached_router.go @@ -38,11 +38,12 @@ const ( addrCacheStateHit = "hit" addrCacheStateMiss = "miss" - // source=providers|peers indicates if query originated from provider or peer endpoint - addrQueryOriginLabel = "origin" - addrQueryOriginProviders = "providers" - addrQueryOriginPeers = "peers" - addrQueryOriginUnknown = "unknown" + // source=providers|peers|closest indicates if query originated from provider, peer, or closest peers endpoint + addrQueryOriginLabel = "origin" + addrQueryOriginProviders = "providers" + addrQueryOriginPeers = "peers" + addrQueryOriginClosestPeers = "closest" + addrQueryOriginUnknown = "unknown" DispatchedFindPeersTimeout = time.Minute ) @@ -64,7 +65,7 @@ func (r cachedRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) return nil, err } - iter := NewCacheFallbackIter(it, r, ctx) + iter := NewCacheFallbackIter(it, r, ctx, addrQueryOriginProviders) return iter, nil } @@ -88,6 +89,42 @@ func (r cachedRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (it return it, nil } +func (r cachedRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + it, err := r.router.GetClosestPeers(ctx, key) + if err != nil { + return nil, err + } + + return r.applyPeerRecordCaching(it, ctx, addrQueryOriginClosestPeers), nil +} + +// applyPeerRecordCaching applies cache fallback logic to a PeerRecord iterator +// by converting to Record iterator, applying caching, and converting back +func (r cachedRouter) applyPeerRecordCaching(it iter.ResultIter[*types.PeerRecord], ctx context.Context, queryOrigin string) iter.ResultIter[*types.PeerRecord] { + // Convert *types.PeerRecord to types.Record + recordIter := iter.Map(it, func(v iter.Result[*types.PeerRecord]) iter.Result[types.Record] { + if v.Err != nil { + return iter.Result[types.Record]{Err: v.Err} + } + return iter.Result[types.Record]{Val: v.Val} + }) + + // Apply caching + cachedIter := NewCacheFallbackIter(recordIter, r, ctx, queryOrigin) + + // Convert back to *types.PeerRecord + return iter.Map(cachedIter, func(v iter.Result[types.Record]) iter.Result[*types.PeerRecord] { + if v.Err != nil { + return iter.Result[*types.PeerRecord]{Err: v.Err} + } + peerRec, ok := v.Val.(*types.PeerRecord) + if !ok { + return iter.Result[*types.PeerRecord]{Err: errors.New("unexpected record type")} + } + return iter.Result[*types.PeerRecord]{Val: peerRec} + }) +} + // withAddrsFromCache returns the best list of addrs for specified [peer.ID]. // It will consult cache ONLY if the addrs slice passed to it is empty. func (r cachedRouter) withAddrsFromCache(queryOrigin string, pid peer.ID, addrs []types.Multiaddr) []types.Multiaddr { @@ -116,6 +153,7 @@ type cacheFallbackIter struct { current iter.Result[types.Record] findPeersResult chan types.PeerRecord router cachedRouter + queryOrigin string ctx context.Context cancel context.CancelFunc ongoingLookups atomic.Int32 @@ -123,13 +161,14 @@ type cacheFallbackIter struct { // NewCacheFallbackIter is a wrapper around a results iterator that will resolve peers with no addresses from cache and if no cached addresses, will look them up via FindPeers. // It's a bit complex because it ensures we continue iterating without blocking on the FindPeers call. -func NewCacheFallbackIter(sourceIter iter.ResultIter[types.Record], router cachedRouter, ctx context.Context) *cacheFallbackIter { +func NewCacheFallbackIter(sourceIter iter.ResultIter[types.Record], router cachedRouter, ctx context.Context, queryOrigin string) *cacheFallbackIter { // Create a cancellable context for this iterator iterCtx, cancel := context.WithCancel(ctx) iter := &cacheFallbackIter{ sourceIter: sourceIter, router: router, + queryOrigin: queryOrigin, ctx: iterCtx, cancel: cancel, findPeersResult: make(chan types.PeerRecord, 100), // Buffer to avoid drops in typical cases @@ -148,7 +187,7 @@ func (it *cacheFallbackIter) Next() bool { switch val.Val.GetSchema() { case types.SchemaPeer: if record, ok := val.Val.(*types.PeerRecord); ok { - record.Addrs = it.router.withAddrsFromCache(addrQueryOriginProviders, *record.ID, record.Addrs) + record.Addrs = it.router.withAddrsFromCache(it.queryOrigin, *record.ID, record.Addrs) if len(record.Addrs) > 0 { it.current = iter.Result[types.Record]{Val: record} return true diff --git a/server_cached_router_test.go b/server_cached_router_test.go index e0ec1c6..e43b703 100644 --- a/server_cached_router_test.go +++ b/server_cached_router_test.go @@ -151,6 +151,79 @@ func TestCachedRouter(t *testing.T) { require.Equal(t, publicAddr.String(), results[0].Addrs[0].String()) }) + t.Run("GetClosestPeers with cached addresses", func(t *testing.T) { + ctx := context.Background() + c := makeCID() + pid := peer.ID("test-peer") + + // Create mock router + mr := &mockRouter{} + mockIter := newMockResultIter([]iter.Result[*types.PeerRecord]{ + {Val: &types.PeerRecord{Schema: "peer", ID: &pid, Addrs: nil}}, + }) + mr.On("GetClosestPeers", mock.Anything, c).Return(mockIter, nil) + + // Create cached address book with test addresses + cab, err := newCachedAddrBook() + require.NoError(t, err) + + publicAddr := mustMultiaddr(t, "/ip4/137.21.14.12/tcp/4001") + cab.addrBook.AddAddrs(pid, []multiaddr.Multiaddr{publicAddr.Multiaddr}, time.Hour) + + // Create cached router + cr := NewCachedRouter(mr, cab) + + it, err := cr.GetClosestPeers(ctx, c) + require.NoError(t, err) + + results, err := iter.ReadAllResults(it) + require.NoError(t, err) + require.Len(t, results, 1) + + // Verify cached addresses were added + require.Equal(t, pid, *results[0].ID) + require.Len(t, results[0].Addrs, 1) + require.Equal(t, publicAddr.String(), results[0].Addrs[0].String()) + }) + + t.Run("GetClosestPeers with fallback to FindPeers", func(t *testing.T) { + ctx := context.Background() + c := makeCID() + pid := peer.ID("test-peer") + publicAddr := mustMultiaddr(t, "/ip4/137.21.14.12/tcp/4001") + + // Create mock router + mr := &mockRouter{} + getClosestIter := newMockResultIter([]iter.Result[*types.PeerRecord]{ + {Val: &types.PeerRecord{Schema: "peer", ID: &pid, Addrs: nil}}, + }) + mr.On("GetClosestPeers", mock.Anything, c).Return(getClosestIter, nil) + + findPeersIter := newMockResultIter([]iter.Result[*types.PeerRecord]{ + {Val: &types.PeerRecord{Schema: "peer", ID: &pid, Addrs: []types.Multiaddr{publicAddr}}}, + }) + mr.On("FindPeers", mock.Anything, pid, 1).Return(findPeersIter, nil) + + // Create cached address book with empty cache + cab, err := newCachedAddrBook() + require.NoError(t, err) + + // Create cached router + cr := NewCachedRouter(mr, cab) + + it, err := cr.GetClosestPeers(ctx, c) + require.NoError(t, err) + + results, err := iter.ReadAllResults(it) + require.NoError(t, err) + require.Len(t, results, 1) + + // Verify addresses from FindPeers fallback + require.Equal(t, pid, *results[0].ID) + require.Len(t, results[0].Addrs, 1) + require.Equal(t, publicAddr.String(), results[0].Addrs[0].String()) + }) + } func TestCacheFallbackIter(t *testing.T) { @@ -173,7 +246,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Read all results results, err := iter.ReadAllResults(fallbackIter) @@ -204,7 +277,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Read all results results, err := iter.ReadAllResults(fallbackIter) @@ -240,7 +313,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Read all results results, err := iter.ReadAllResults(fallbackIter) @@ -266,7 +339,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Cancel context before sending any values cancel() @@ -293,7 +366,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // First Next() should succeed require.True(t, fallbackIter.Next()) @@ -336,7 +409,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Cancel context during lookup cancel() @@ -364,7 +437,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Should still get a result, but with no addresses results, err := iter.ReadAllResults(fallbackIter) @@ -400,7 +473,7 @@ func TestCacheFallbackIter(t *testing.T) { cr := NewCachedRouter(mr, cab) // Create fallback iterator - fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx) + fallbackIter := NewCacheFallbackIter(sourceIter, cr, ctx, addrQueryOriginUnknown) // Should get all records with addresses results, err := iter.ReadAllResults(fallbackIter) diff --git a/server_dht_test.go b/server_dht_test.go new file mode 100644 index 0000000..3d20452 --- /dev/null +++ b/server_dht_test.go @@ -0,0 +1,382 @@ +package main + +import ( + "context" + "crypto/rand" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ipfs/boxo/ipns" + "github.com/ipfs/boxo/routing/http/server" + "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/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/routing" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +const ( + mediaTypeJSON = "application/json" + mediaTypeNDJSON = "application/x-ndjson" + + cacheControlShortTTL = "public, max-age=15, stale-while-revalidate=172800, stale-if-error=172800" + cacheControlLongTTL = "public, max-age=300, stale-while-revalidate=172800, stale-if-error=172800" +) + +func makeEd25519PeerID(t *testing.T) (crypto.PrivKey, peer.ID) { + sk, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + pid, err := peer.IDFromPrivateKey(sk) + require.NoError(t, err) + + return sk, pid +} + +func requireCloseToNow(t *testing.T, lastModified string) { + lastModifiedTime, err := time.Parse(http.TimeFormat, lastModified) + require.NoError(t, err) + require.WithinDuration(t, time.Now(), lastModifiedTime, 1*time.Minute) +} + +func makePeerRecords(t *testing.T, count int) ([]iter.Result[*types.PeerRecord], []peer.ID) { + var peerRecords []iter.Result[*types.PeerRecord] + var peerIDs []peer.ID + + for i := 0; i < count; i++ { + _, p := makeEd25519PeerID(t) + peerIDs = append(peerIDs, p) + + addr := fmt.Sprintf("/ip4/127.0.0.%d/tcp/4001", i+1) + ma, err := multiaddr.NewMultiaddr(addr) + require.NoError(t, err) + + peerRecords = append(peerRecords, iter.Result[*types.PeerRecord]{ + Val: &types.PeerRecord{ + Schema: types.SchemaPeer, + ID: &p, + Addrs: []types.Multiaddr{{Multiaddr: ma}}, + }, + }) + } + + return peerRecords, peerIDs +} + +func TestGetClosestPeersEndpoint(t *testing.T) { + t.Parallel() + + makeRequest := func(t *testing.T, router router, contentType, key string) *http.Response { + handler := server.Handler(&composableRouter{dht: router}) + srv := httptest.NewServer(handler) + t.Cleanup(srv.Close) + + urlStr := fmt.Sprintf("http://%s/routing/v1/dht/closest/peers/%s", srv.Listener.Addr().String(), key) + + req, err := http.NewRequest(http.MethodGet, urlStr, nil) + require.NoError(t, err) + if contentType != "" { + req.Header.Set("Accept", contentType) + } + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + return resp + } + + t.Run("GET /routing/v1/dht/closest/peers/{cid} returns 200 with 20 peers (JSON)", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + peerRecords, peerIDs := makePeerRecords(t, 20) + results := iter.FromSlice(peerRecords) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeJSON, resp.Header.Get("Content-Type")) + require.Equal(t, "Accept", resp.Header.Get("Vary")) + require.Equal(t, cacheControlLongTTL, resp.Header.Get("Cache-Control")) + + requireCloseToNow(t, resp.Header.Get("Last-Modified")) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + bodyStr := string(body) + require.Contains(t, bodyStr, `"Peers":[`) + // Verify all 20 peers and their addresses are present + for i, p := range peerIDs { + require.Contains(t, bodyStr, p.String()) + expectedAddr := fmt.Sprintf("/ip4/127.0.0.%d/tcp/4001", i+1) + require.Contains(t, bodyStr, expectedAddr) + } + }) + + t.Run("GET /routing/v1/dht/closest/peers/{cid} returns 200 with 20 peers (NDJSON)", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + peerRecords, peerIDs := makePeerRecords(t, 20) + results := iter.FromSlice(peerRecords) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeNDJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeNDJSON, resp.Header.Get("Content-Type")) + require.Equal(t, "Accept", resp.Header.Get("Vary")) + require.Equal(t, cacheControlLongTTL, resp.Header.Get("Cache-Control")) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + bodyStr := string(body) + // Verify all 20 peers and their addresses are present + for i, p := range peerIDs { + require.Contains(t, bodyStr, p.String()) + expectedAddr := fmt.Sprintf("/ip4/127.0.0.%d/tcp/4001", i+1) + require.Contains(t, bodyStr, expectedAddr) + } + }) + + t.Run("GET /routing/v1/dht/closest/peers/{cid} returns 200 with empty results (JSON)", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{}) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeJSON, resp.Header.Get("Content-Type")) + require.Equal(t, cacheControlShortTTL, resp.Header.Get("Cache-Control")) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, `{"Peers":null}`, string(body)) + }) + + t.Run("GET /routing/v1/dht/closest/peers/{cid} returns 200 with empty results (NDJSON)", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{}) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeNDJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeNDJSON, resp.Header.Get("Content-Type")) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "", string(body)) + }) + + t.Run("GET /routing/v1/dht/closest/peers/{cid} returns 200 when router returns ErrNotFound", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeJSON, resp.Header.Get("Content-Type")) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, `{"Peers":null}`, string(body)) + }) + + t.Run("GET /routing/v1/dht/closest/peers/{invalid-key} returns 400", func(t *testing.T) { + t.Parallel() + + mockRouter := &mockDHTRouter{} + + resp := makeRequest(t, mockRouter, mediaTypeJSON, "not-a-valid-cid") + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("GET /routing/v1/dht/closest/peers/{arbitrary-cid} returns 200", func(t *testing.T) { + t.Parallel() + + // arbitrary CID (not a PeerID) + cidStr := "bafkreidcd7frenco2m6ch7mny63wztgztv3q6fctaffgowkro6kljre5ei" + key, err := cid.Decode(cidStr) + require.NoError(t, err) + + _, pid := makeEd25519PeerID(t) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{ + {Val: &types.PeerRecord{ + Schema: types.SchemaPeer, + ID: &pid, + Addrs: []types.Multiaddr{}, + }}, + }) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeJSON, cidStr) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Contains(t, string(body), pid.String()) + }) + + t.Run("GET /routing/v1/dht/closest/peers/{peerid-as-cid} returns 200", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{ + {Val: &types.PeerRecord{ + Schema: types.SchemaPeer, + ID: &pid, + Addrs: []types.Multiaddr{}, + }}, + }) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if k.Equals(key) { + return results, nil + } + return nil, routing.ErrNotFound + }, + } + + resp := makeRequest(t, mockRouter, mediaTypeJSON, key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Contains(t, string(body), pid.String()) + }) + + t.Run("GET /routing/v1/dht/closest/peers with default Accept header returns JSON", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{}) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + return results, nil + }, + } + + resp := makeRequest(t, mockRouter, "", key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeJSON, resp.Header.Get("Content-Type")) + }) + + t.Run("GET /routing/v1/dht/closest/peers with wildcard Accept header returns JSON", func(t *testing.T) { + t.Parallel() + + _, pid := makeEd25519PeerID(t) + key := peer.ToCid(pid) + + results := iter.FromSlice([]iter.Result[*types.PeerRecord]{}) + + mockRouter := &mockDHTRouter{ + getClosestPeersFunc: func(ctx context.Context, k cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + return results, nil + }, + } + + resp := makeRequest(t, mockRouter, "text/html,*/*", key.String()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, mediaTypeJSON, resp.Header.Get("Content-Type")) + }) +} + +// mockDHTRouter implements the router interface for testing +type mockDHTRouter struct { + getClosestPeersFunc func(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) +} + +func (m *mockDHTRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) { + return nil, routing.ErrNotSupported +} + +func (m *mockDHTRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[*types.PeerRecord], error) { + return nil, routing.ErrNotSupported +} + +func (m *mockDHTRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if m.getClosestPeersFunc != nil { + return m.getClosestPeersFunc(ctx, key) + } + return nil, routing.ErrNotSupported +} + +func (m *mockDHTRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) { + return nil, routing.ErrNotSupported +} + +func (m *mockDHTRouter) PutIPNS(ctx context.Context, name ipns.Name, record *ipns.Record) error { + return routing.ErrNotSupported +} diff --git a/server_routers.go b/server_routers.go index 486951e..6947373 100644 --- a/server_routers.go +++ b/server_routers.go @@ -13,6 +13,10 @@ import ( "github.com/ipfs/boxo/routing/http/types" "github.com/ipfs/boxo/routing/http/types/iter" "github.com/ipfs/go-cid" + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p-kad-dht/dual" + "github.com/libp2p/go-libp2p-kad-dht/fullrt" + "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/routing" manet "github.com/multiformats/go-multiaddr/net" @@ -22,6 +26,7 @@ type router interface { providersRouter peersRouter ipnsRouter + dhtRouter } type providersRouter interface { @@ -37,12 +42,17 @@ type ipnsRouter interface { PutIPNS(ctx context.Context, name ipns.Name, record *ipns.Record) error } +type dhtRouter interface { + GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) +} + var _ server.ContentRouter = composableRouter{} type composableRouter struct { providers providersRouter peers peersRouter ipns ipnsRouter + dht dhtRouter } func (r composableRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) { @@ -59,6 +69,13 @@ func (r composableRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) return r.peers.FindPeers(ctx, pid, limit) } +func (r composableRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + if r.dht == nil { + return iter.ToResultIter(iter.FromSlice([]*types.PeerRecord{})), nil + } + return r.dht.GetClosestPeers(ctx, key) +} + func (r composableRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) { if r.ipns == nil { return nil, routing.ErrNotFound @@ -129,6 +146,12 @@ func find[T any](ctx context.Context, routers []router, call func(router) (iter. return newManyIter(ctx, its), nil } +func (r parallelRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + return find(ctx, r.routers, func(ri router) (iter.ResultIter[*types.PeerRecord], error) { + return ri.GetClosestPeers(ctx, key) + }) +} + type manyIter[T any] struct { ctx context.Context cancel context.CancelFunc @@ -317,6 +340,7 @@ func (r parallelRouter) ProvideBitswap(ctx context.Context, req *server.BitswapW var _ router = libp2pRouter{} type libp2pRouter struct { + host host.Host routing routing.Routing } @@ -350,6 +374,74 @@ func (d libp2pRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (it return iter.ToResultIter(iter.FromSlice([]*types.PeerRecord{rec})), nil } +func (d libp2pRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + // Per the spec, if the peer ID is empty, we should use self. + if key == cid.Undef { + key = peer.ToCid(d.host.ID()) + } + + keyStr := string(key.Hash()) + var peers []peer.ID + var err error + + switch v := d.routing.(type) { + case *dual.DHT: + // Only use WAN DHT for public HTTP Routing API. + // LAN DHT contains private network peers that should not be exposed publicly. + peers, err = v.WAN.GetClosestPeers(ctx, keyStr) + if err != nil { + return nil, err + } + case *fullrt.FullRT: + peers, err = v.GetClosestPeers(ctx, keyStr) + if err != nil { + return nil, err + } + case *dht.IpfsDHT: + peers, err = v.GetClosestPeers(ctx, keyStr) + if err != nil { + return nil, err + } + case *bundledDHT: + // bundledDHT uses either fullRT (when ready) or standard DHT + // We need to call GetClosestPeers on the active DHT + activeDHT := v.getDHT() + switch dht := activeDHT.(type) { + case *fullrt.FullRT: + peers, err = dht.GetClosestPeers(ctx, keyStr) + case *dht.IpfsDHT: + peers, err = dht.GetClosestPeers(ctx, keyStr) + default: + return nil, errors.New("bundledDHT returned unexpected DHT type") + } + if err != nil { + return nil, err + } + default: + return nil, errors.New("cannot call GetClosestPeers on DHT implementation") + } + + // We have some DHT-closest peers. Find addresses for them. + // The addresses should be in the peerstore. + var records []*types.PeerRecord + for _, p := range peers { + addrs := d.host.Peerstore().Addrs(p) + rAddrs := make([]types.Multiaddr, len(addrs)) + for i, addr := range addrs { + rAddrs[i] = types.Multiaddr{Multiaddr: addr} + } + record := types.PeerRecord{ + ID: &p, + Schema: types.SchemaPeer, + Addrs: rAddrs, + // we dont seem to care about protocol/extra infos + } + records = append(records, &record) + } + + return iter.ToResultIter(iter.FromSlice(records)), nil +} + func (d libp2pRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -487,6 +579,22 @@ func (r sanitizeRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) ( }), nil } +func (r sanitizeRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + it, err := r.router.GetClosestPeers(ctx, key) + if err != nil { + return nil, err + } + + return iter.Map(it, func(v iter.Result[*types.PeerRecord]) iter.Result[*types.PeerRecord] { + if v.Err != nil || v.Val == nil { + return v + } + + v.Val.Addrs = filterPrivateMultiaddr(v.Val.Addrs) + return v + }), nil +} + //lint:ignore SA1019 // ignore staticcheck func (r sanitizeRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) { return 0, routing.ErrNotSupported diff --git a/server_routers_test.go b/server_routers_test.go index 4a92800..8b8ebb8 100644 --- a/server_routers_test.go +++ b/server_routers_test.go @@ -41,6 +41,14 @@ func (m *mockRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (ite return args.Get(0).(iter.ResultIter[*types.PeerRecord]), args.Error(1) } +func (m *mockRouter) GetClosestPeers(ctx context.Context, key cid.Cid) (iter.ResultIter[*types.PeerRecord], error) { + args := m.Called(ctx, key) + if arg0 := args.Get(0); arg0 == nil { + return nil, args.Error(1) + } + return args.Get(0).(iter.ResultIter[*types.PeerRecord]), args.Error(1) +} + func (m *mockRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) { args := m.Called(ctx, name) if arg0 := args.Get(0); arg0 == nil { diff --git a/server_test.go b/server_test.go index a09dd6c..deb0d65 100644 --- a/server_test.go +++ b/server_test.go @@ -10,15 +10,15 @@ func TestGetCombinedRouting(t *testing.T) { t.Parallel() // Check of the result of get combined routing is a sanitize router. - v, err := getCombinedRouting(nil, &bundledDHT{}, nil, nil) + v, err := getCombinedRouting(nil, nil, &bundledDHT{}, nil, nil) require.NoError(t, err) require.IsType(t, sanitizeRouter{}, v) - v, err = getCombinedRouting([]string{"https://example.com/"}, nil, nil, nil) + v, err = getCombinedRouting([]string{"https://example.com/"}, nil, nil, nil, nil) require.NoError(t, err) require.IsType(t, parallelRouter{}, v) - v, err = getCombinedRouting([]string{"https://example.com/"}, &bundledDHT{}, nil, nil) + v, err = getCombinedRouting([]string{"https://example.com/"}, nil, &bundledDHT{}, nil, nil) require.NoError(t, err) require.IsType(t, parallelRouter{}, v) }