diff --git a/.github/workflows/gotest.yml b/.github/workflows/gotest.yml index f40d2172e6b..fe2751e4ce5 100644 --- a/.github/workflows/gotest.yml +++ b/.github/workflows/gotest.yml @@ -43,7 +43,7 @@ jobs: make test_unit && [[ ! $(jq -s -c 'map(select(.Action == "fail")) | .[]' test/unit/gotest.json) ]] - name: Upload coverage to Codecov - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 if: failure() || success() with: name: unittests diff --git a/.github/workflows/sharness.yml b/.github/workflows/sharness.yml index 357c6a1027b..67dd9cc50dd 100644 --- a/.github/workflows/sharness.yml +++ b/.github/workflows/sharness.yml @@ -55,7 +55,7 @@ jobs: # increasing parallelism beyond 10 doesn't speed up the tests much PARALLEL: ${{ github.repository == 'ipfs/kubo' && 10 || 3 }} - name: Upload coverage report - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 if: failure() || success() with: name: sharness diff --git a/.github/workflows/sync-release-assets.yml b/.github/workflows/sync-release-assets.yml index 33869f11d0a..66ef40313c2 100644 --- a/.github/workflows/sync-release-assets.yml +++ b/.github/workflows/sync-release-assets.yml @@ -26,7 +26,7 @@ jobs: with: node-version: 14 - name: Sync the latest 5 github releases - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: script: | const fs = require('fs').promises diff --git a/AGENTS.md b/AGENTS.md index 637c2dafb34..65280d3ebb0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,8 @@ make install # install to $GOPATH/bin make -O test_go_lint # run linter (use this instead of golangci-lint directly) ``` +**Always build with `make build`, never `go build`.** The Makefile injects required `-ldflags` for `CurrentCommit`, `taggedRelease`, and `buildOrigin`. + If you modify `go.mod` (add/remove/update dependencies), you must run `make mod_tidy` first, before building or testing. Use `make mod_tidy` instead of `go mod tidy` directly, as the project has multiple `go.mod` files. If you modify any `.go` files outside of `test/`, you must run `make build` before running integration tests. @@ -230,3 +232,20 @@ ipfs shutdown # graceful shutdown via API ``` Kill dangling daemons before re-running tests: `pkill -f "ipfs daemon"` + +### Testing AutoTLS Locally + +AutoTLS only requests a `*.libp2p.direct` certificate once libp2p confirms the node is publicly reachable on a TCP port. For a local test the node must be able to open that port, so enable UPnP/NAT-PMP (the `server` init profile disables it via `Swarm.DisableNatPortMap: true`): + +```bash +ipfs config --json Swarm.DisableNatPortMap false # let UPnP/NAT-PMP map the swarm port +ipfs config AutoTLS.RegistrationDelay 5s # shorten the default wait before registration +``` + +Then start the daemon and watch the relevant logs: + +```bash +GOLOG_LOG_LEVEL="error,autotls=info,nat=info" ipfs daemon +``` + +Poll `ipfs id` until a `tls/ws` address under your own peer ID appears. A `libp2p.direct` address ending in `/p2p-circuit/p2p/` is a relay path, not your own AutoTLS cert. Requires a router that actually honors UPnP/NAT-PMP; without it AutoNAT reports `Private` and no certificate is issued. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4a2c47c66..9a0cdc1948d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Kubo Changelogs +- [v0.43](docs/changelogs/v0.43.md) - [v0.42](docs/changelogs/v0.42.md) - [v0.41](docs/changelogs/v0.41.md) - [v0.40](docs/changelogs/v0.40.md) diff --git a/Dockerfile b/Dockerfile index ffc4553a3eb..7b81ca8ae49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ # The default below is what `docker build .` (without --build-arg) uses, and # `.github/workflows/docker-check.yml` lints that this default stays in sync # with go.mod. When bumping Go, update both go.mod and this default together. -ARG GO_VERSION=1.26.2 +ARG GO_VERSION=1.26.4 FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:${GO_VERSION} AS builder ARG TARGETOS TARGETARCH @@ -97,10 +97,10 @@ ENV GOLOG_LOG_LEVEL="" # tini ensures proper signal handling and zombie process cleanup ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/start_ipfs"] -# Health check verifies IPFS daemon is responsive. -# Uses empty directory CID (QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn) as test +# Health check via "ipfs diag healthy": verifies RPC + DAG pipeline, and +# fails after SIGINT/SIGTERM to catch half-shutdown states. HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD ipfs --api=/ip4/127.0.0.1/tcp/5001 dag stat /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn || exit 1 + CMD ipfs --api=/ip4/127.0.0.1/tcp/5001 diag healthy > /dev/null 2>&1 || exit 1 # Default: run IPFS daemon with auto-migration enabled CMD ["daemon", "--migrate=true", "--agent-version-suffix=docker"] diff --git a/bin/mkreleaselog b/bin/mkreleaselog index f535f2f6d53..60f1d2ea446 100755 --- a/bin/mkreleaselog +++ b/bin/mkreleaselog @@ -607,7 +607,10 @@ statsummary() { Deletions: (map(.Deletions) | add), Files: (map(.Files) | add) } - ' | jq '. + {Lines: (.Deletions + .Insertions)}' + ' | + # Drop bot accounts (e.g. dependabot[bot]); GitHub bots use the [bot] suffix + jq 'select((.GitHub | endswith("[bot]")) or (.Author | endswith("[bot]")) | not)' | + jq '. + {Lines: (.Deletions + .Insertions)}' } strip_version() { diff --git a/cmd/ipfs/Rules.mk b/cmd/ipfs/Rules.mk index 7dca2c3e9aa..873f74230b5 100644 --- a/cmd/ipfs/Rules.mk +++ b/cmd/ipfs/Rules.mk @@ -12,7 +12,7 @@ PATH := $(realpath $(d)):$(PATH) # DEPS_OO_$(d) += merkledag/pb/merkledag.pb.go namesys/pb/namesys.pb.go # DEPS_OO_$(d) += pin/internal/pb/header.pb.go unixfs/pb/unixfs.pb.go -$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag)" +$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag) -X "github.com/ipfs/kubo".buildOrigin=$(git-origin)" $(IPFS_BIN_$(d)): GOFLAGS += $(cmd/ipfs_flags) diff --git a/cmd/ipfs/kubo/add_migrations.go b/cmd/ipfs/kubo/add_migrations.go index e7cb135f7e3..97204d3b04c 100644 --- a/cmd/ipfs/kubo/add_migrations.go +++ b/cmd/ipfs/kubo/add_migrations.go @@ -36,7 +36,7 @@ func addMigrations(ctx context.Context, node *core.IpfsNode, fetcher migrations. if err != nil { return err } - case *migrations.HttpFetcher, *migrations.RetryFetcher: // https://github.com/ipfs/kubo/issues/8780 + case *migrations.HttpFetcher: // https://github.com/ipfs/kubo/issues/8780 // Add the downloaded migration files directly if migrations.DownloadDirectory != "" { var paths []string diff --git a/cmd/ipfs/kubo/daemon.go b/cmd/ipfs/kubo/daemon.go index 1af9edd338c..567da2fbe92 100644 --- a/cmd/ipfs/kubo/daemon.go +++ b/cmd/ipfs/kubo/daemon.go @@ -31,6 +31,7 @@ import ( options "github.com/ipfs/kubo/core/coreiface/options" corerepo "github.com/ipfs/kubo/core/corerepo" libp2p "github.com/ipfs/kubo/core/node/libp2p" + "github.com/ipfs/kubo/core/shutdown" nodeMount "github.com/ipfs/kubo/fuse/node" fsrepo "github.com/ipfs/kubo/repo/fsrepo" "github.com/ipfs/kubo/repo/fsrepo/migrations" @@ -432,6 +433,12 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment ipnsps = cfg.Ipns.UsePubsub.WithDefault(false) } + // Resolve graceful-shutdown timeout. The generous 12h default leaves + // normal operation unchanged while guaranteeing the daemon cannot be + // stuck indefinitely on a hung FX OnStop hook. A value of 0 opts out + // entirely and restores the legacy "wait forever" behavior. + shutdownTimeout := max(cfg.Internal.ShutdownTimeout.WithDefault(config.DefaultShutdownTimeout), 0) + // Start assembling node config ncfg := &core.BuildCfg{ Repo: repo, @@ -442,6 +449,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment "pubsub": pubsub, "ipnsps": ipnsps, }, + ShutdownTimeout: shutdownTimeout, // TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral } @@ -512,9 +520,13 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment return fmt.Errorf("unrecognized routing option: %s", routingOption) } - // Set optional agent version suffix + // Resolve agent version suffix: + // Version.AgentSuffix > --agent-version-suffix > implicit (build origin). versionSuffixFromCli, _ := req.Options[agentVersionSuffix].(string) versionSuffix := cfg.Version.AgentSuffix.WithDefault(versionSuffixFromCli) + if versionSuffix == "" { + versionSuffix = version.ImplicitAgentSuffix() + } if versionSuffix != "" { version.SetUserAgentSuffix(versionSuffix) } @@ -583,6 +595,17 @@ take effect. } defer func() { + // Watchdog: if node.Close() does not return within shutdownTimeout, + // force-exit so orchestrators can restart the daemon. + // shutdownTimeout==0 disables the watchdog (wait forever). + if shutdownTimeout > 0 { + killSwitch := time.AfterFunc(shutdownTimeout, func() { + log.Errorf("shutdown watchdog: node.Close() did not return after %s; exiting", shutdownTimeout) + os.Exit(1) + }) + defer killSwitch.Stop() + } + // We wait for the node to close first, as the node has children // that it will wait for before closing, such as the API server. node.Close() @@ -708,6 +731,7 @@ take effect. // Give the user some immediate feedback when they hit C-c go func() { <-req.Context.Done() + shutdown.MarkStarted() notifyStopping() fmt.Println("Received interrupt signal, shutting down...") fmt.Println("(Hit ctrl-c again to force-shutdown the daemon.)") diff --git a/config/internal.go b/config/internal.go index 5d1276ca1dc..3372594a3aa 100644 --- a/config/internal.go +++ b/config/internal.go @@ -1,8 +1,19 @@ package config +import "time" + const ( // DefaultMFSNoFlushLimit is the default limit for consecutive unflushed MFS operations DefaultMFSNoFlushLimit = 256 + + // DefaultShutdownTimeout caps how long graceful shutdown is allowed to + // take before the daemon force-exits with status 1. Set generously so + // it does not change existing kubo behavior in practice but guarantees + // Docker / kubernetes infrastructure can never be stuck indefinitely + // on a hung FX OnStop hook. Smaller than the 22h DHT reprovide cycle, + // so a hung daemon recovers before missing more than one cycle. + // Set Internal.ShutdownTimeout to 0 to opt out and wait forever. + DefaultShutdownTimeout = 12 * time.Hour ) type Internal struct { @@ -18,6 +29,11 @@ type Internal struct { // This is an EXPERIMENTAL feature and may change or be removed in future releases. // See https://github.com/ipfs/kubo/issues/10842 MFSNoFlushLimit *OptionalInteger `json:",omitempty"` + // ShutdownTimeout caps how long graceful shutdown of the daemon is + // allowed to take. Defaults to DefaultShutdownTimeout. When the + // deadline expires the daemon logs which subsystem failed to close and + // exits with status 1. Set to 0 to disable the cap and wait forever. + ShutdownTimeout *OptionalDuration `json:",omitempty"` } type InternalBitswap struct { diff --git a/config/provide.go b/config/provide.go index c6c79f888cd..2cd74106f5d 100644 --- a/config/provide.go +++ b/config/provide.go @@ -29,15 +29,16 @@ const ( MinProvideBloomFPRate = 1_000_000 // DHT provider defaults - DefaultProvideDHTInterval = 22 * time.Hour // https://github.com/ipfs/kubo/pull/9326 - DefaultProvideDHTMaxWorkers = 16 // Unified default for both sweep and legacy providers - DefaultProvideDHTSweepEnabled = true - DefaultProvideDHTResumeEnabled = true - DefaultProvideDHTDedicatedPeriodicWorkers = 2 - DefaultProvideDHTDedicatedBurstWorkers = 1 - DefaultProvideDHTMaxProvideConnsPerWorker = 20 - DefaultProvideDHTKeystoreBatchSize = 1 << 14 // ~544 KiB per batch (1 multihash = 34 bytes) - DefaultProvideDHTOfflineDelay = 2 * time.Hour + DefaultProvideDHTInterval = 22 * time.Hour // https://github.com/ipfs/kubo/pull/9326 + DefaultProvideDHTMaxWorkers = 16 // Unified default for both sweep and legacy providers + DefaultProvideDHTSweepEnabled = true + DefaultProvideDHTResumeEnabled = true + DefaultProvideDHTDedicatedPeriodicWorkers = 2 + DefaultProvideDHTDedicatedBurstWorkers = 1 + DefaultProvideDHTMaxProvideConnsPerWorker = 20 + DefaultProvideDHTKeystoreBatchSize = 1 << 14 // ~544 KiB per batch (1 multihash = 34 bytes) + DefaultProvideDHTOfflineDelay = 2 * time.Hour + DefaultProvideDHTSendProviderRecordTimeout = 10 * time.Second // DefaultFastProvideTimeout is the maximum time allowed for fast-provide operations. // Prevents hanging on network issues when providing root CID. @@ -121,6 +122,13 @@ type ProvideDHT struct { // Default: DefaultProvideDHTOfflineDelay OfflineDelay *OptionalDuration `json:",omitempty"` + // SendProviderRecordTimeout sets the per-peer timeout applied to a single + // ADD_PROVIDER RPC. A peer that accepts the libp2p stream but never reads + // the request must not pin a provide worker goroutine indefinitely; this + // timeout bounds the wait (sweep mode only). + // Default: DefaultProvideDHTSendProviderRecordTimeout + SendProviderRecordTimeout *OptionalDuration `json:",omitempty"` + // ResumeEnabled controls whether the provider resumes from its previous state on restart. // When enabled, the provider persists its reprovide cycle state and provide queue to the datastore, // and restores them on restart. When disabled, the provider starts fresh on each restart. @@ -209,6 +217,19 @@ func ValidateProvideConfig(cfg *Provide) error { if interval < 0 { return fmt.Errorf("Provide.DHT.Interval must be non-negative, got %v", interval) } + // Provide.DHT.Interval=0 used to disable the entire provide system as a + // side effect. It now disables only the periodic reprovide schedule: + // new CIDs still announce via fast-provide-root and 'ipfs provide once'. + // Operators upgrading from earlier kubo versions must opt in to one of + // the two semantics by setting Provide.Enabled explicitly: + // - Provide.Enabled=false fully disables providing (the old behaviour). + // - Provide.Enabled=true keeps ad-hoc providing while disabling the + // periodic reprovide schedule. + if interval == 0 && cfg.Enabled == Default { + return fmt.Errorf("Provide.DHT.Interval=0 no longer disables the provide system on its own; set Provide.Enabled explicitly: " + + "Provide.Enabled=false to fully disable providing, or Provide.Enabled=true to keep ad-hoc 'ipfs provide once' " + + "and fast-provide-root working while skipping the periodic reprovide schedule") + } } // Validate MaxWorkers @@ -259,6 +280,14 @@ func ValidateProvideConfig(cfg *Provide) error { } } + // Validate SendProviderRecordTimeout + if !cfg.DHT.SendProviderRecordTimeout.IsDefault() { + timeout := cfg.DHT.SendProviderRecordTimeout.WithDefault(DefaultProvideDHTSendProviderRecordTimeout) + if timeout <= 0 { + return fmt.Errorf("Provide.DHT.SendProviderRecordTimeout must be positive, got %v", timeout) + } + } + return nil } diff --git a/config/provide_test.go b/config/provide_test.go index cd0f3902593..1084604efaa 100644 --- a/config/provide_test.go +++ b/config/provide_test.go @@ -155,21 +155,25 @@ func TestValidateProvideConfig_Interval(t *testing.T) { tests := []struct { name string interval time.Duration + enabled Flag wantErr bool errMsg string }{ - {"valid default (22h)", 22 * time.Hour, false, ""}, - {"valid max (48h)", 48 * time.Hour, false, ""}, - {"valid small (1h)", 1 * time.Hour, false, ""}, - {"valid zero (disabled)", 0, false, ""}, - {"invalid over limit (49h)", 49 * time.Hour, true, "must be less than or equal to DHT provider record validity"}, - {"invalid over limit (72h)", 72 * time.Hour, true, "must be less than or equal to DHT provider record validity"}, - {"invalid negative", -1 * time.Hour, true, "must be non-negative"}, + {"valid default (22h)", 22 * time.Hour, Default, false, ""}, + {"valid max (48h)", 48 * time.Hour, Default, false, ""}, + {"valid small (1h)", 1 * time.Hour, Default, false, ""}, + {"valid zero with explicit Enabled=true", 0, True, false, ""}, + {"valid zero with explicit Enabled=false", 0, False, false, ""}, + {"invalid zero without explicit Provide.Enabled", 0, Default, true, "set Provide.Enabled explicitly"}, + {"invalid over limit (49h)", 49 * time.Hour, Default, true, "must be less than or equal to DHT provider record validity"}, + {"invalid over limit (72h)", 72 * time.Hour, Default, true, "must be less than or equal to DHT provider record validity"}, + {"invalid negative", -1 * time.Hour, Default, true, "must be non-negative"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := &Provide{ + Enabled: tt.enabled, DHT: ProvideDHT{ Interval: NewOptionalDuration(tt.interval), }, diff --git a/core/builder.go b/core/builder.go index 4c54ddf8c5e..e226cef47cb 100644 --- a/core/builder.go +++ b/core/builder.go @@ -93,9 +93,21 @@ func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) { var stopErr error n.stop = func() error { once.Do(func() { - stopErr = app.Stop(context.Background()) + // Bound app.Stop with a deadline so an FX OnStop hook that + // never returns cannot hang the daemon. ShutdownTimeout==0 + // opts out of the cap entirely and restores the legacy + // behavior of waiting forever for hooks to complete. The + // daemon's watchdog in cmd/ipfs/kubo/daemon.go fires at the + // same deadline and is the unconditional os.Exit fallback. + stopCtx := context.Background() + if cfg.ShutdownTimeout > 0 { + var stopCancel context.CancelFunc + stopCtx, stopCancel = context.WithTimeout(stopCtx, cfg.ShutdownTimeout) + defer stopCancel() + } + stopErr = app.Stop(stopCtx) if stopErr != nil { - log.Error("failure on stop: ", stopErr) + log.Errorf("failure on stop: %v", stopErr) } // Cancel the context _after_ the app has stopped. cancel() diff --git a/core/commands/add.go b/core/commands/add.go index 1cd063120ce..3d6725822ba 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -13,7 +13,7 @@ import ( "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/ipfs/kubo/core/commands/cmdutils" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" uio "github.com/ipfs/boxo/ipld/unixfs/io" mfs "github.com/ipfs/boxo/mfs" @@ -76,6 +76,11 @@ const ( const ( adderOutChanSize = 8 + + // pb/v3 template used before the upload total is known: only the + // running byte counter and current speed. Swapped for + // cmdenv.ProgressBarFullTemplate once size discovery reports. + progressBarInitTemplate = `{{counters . }} {{speed . "%s/s" "?/s"}}` ) var AddCmd = &cmds.Command{ @@ -252,7 +257,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import cmds.BoolOption(quietOptionName, "q", "Write minimal output."), cmds.BoolOption(quieterOptionName, "Q", "Write only final hash."), cmds.BoolOption(silentOptionName, "Write no output."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), // Basic Add Behavior cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk."), cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object."), @@ -292,10 +297,10 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import silent, _ := req.Options[silentOptionName].(bool) if !quiet && !silent { - // ipfs cli progress bar defaults to true unless quiet or silent is used + // default to showing progress only when stderr is a terminal _, found := req.Options[progressOptionName].(bool) if !found { - req.Options[progressOptionName] = true + req.Options[progressOptionName] = cmdenv.IsTerminal(os.Stderr) } } @@ -732,11 +737,8 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import var bar *pb.ProgressBar if progress { - bar = pb.New64(0).SetUnits(pb.U_BYTES) - bar.ManualUpdate = true - bar.ShowTimeLeft = false - bar.ShowPercent = false - bar.Output = os.Stderr + bar = pb.New64(0).Set(pb.Bytes, true).Set(pb.Static, true).SetWriter(os.Stderr) + bar.SetTemplateString(progressBarInitTemplate) bar.Start() } @@ -786,18 +788,17 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import } lastBytes = output.Bytes delta := prevFiles + lastBytes - totalProgress - totalProgress = bar.Add64(delta) + bar.Add64(delta) + totalProgress = bar.Current() } if progress { - bar.Update() + bar.Write() } case size := <-sizeChan: if progress { - bar.Total = size - bar.ShowPercent = true - bar.ShowBar = true - bar.ShowTimeLeft = true + bar.SetTotal(size) + bar.SetTemplateString(cmdenv.ProgressBarFullTemplate) } case <-req.Context.Done(): // don't set or print error here, that happens in the goroutine below @@ -805,12 +806,19 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import } } - if progress && bar.Total == 0 && bar.Get() != 0 { - bar.Total = bar.Get() - bar.ShowPercent = true - bar.ShowBar = true - bar.ShowTimeLeft = true - bar.Update() + if progress { + // If size discovery never reported, treat the + // observed bytes as the total so the final frame + // renders the bar and percent. + if bar.Total() == 0 && bar.Current() != 0 { + bar.SetTotal(bar.Current()) + bar.SetTemplateString(cmdenv.ProgressBarFullTemplate) + } + // Finish first so the speed element switches to + // the absolute-rate branch (total/elapsed) when + // EWMA never accumulated a sample on fast adds. + bar.Finish() + bar.Write() } } diff --git a/core/commands/cat.go b/core/commands/cat.go index 38a3e8dfaf6..97a7a3f556f 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -3,13 +3,14 @@ package commands import ( "context" "errors" + "fmt" "io" "os" "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/ipfs/kubo/core/commands/cmdutils" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" cmds "github.com/ipfs/go-ipfs-cmds" iface "github.com/ipfs/kubo/core/coreiface" @@ -33,7 +34,7 @@ var CatCmd = &cmds.Command{ Options: []cmds.Option{ cmds.Int64Option(offsetOptionName, "o", "Byte offset to begin reading from."), cmds.Int64Option(lengthOptionName, "l", "Maximum number of bytes to read."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data.").WithDefault(true), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { api, err := cmdenv.GetApi(env, req) @@ -100,9 +101,7 @@ var CatCmd = &cmds.Command{ case io.Reader: reader := val - req := res.Request() - progress, _ := req.Options[progressOptionName].(bool) - if progress { + if cmdenv.ShouldShowProgress(res.Request(), progressOptionName) { var bar *pb.ProgressBar bar, reader = progressBarForReader(os.Stderr, val, int64(res.Length())) bar.Start() @@ -158,7 +157,11 @@ func cat(ctx context.Context, api iface.CoreAPI, paths []string, offset int64, m continue } - count, err := file.Seek(offset, io.SeekStart) + seeker, ok := file.(io.Seeker) + if !ok { + return nil, 0, fmt.Errorf("file does not support seeking") + } + count, err := seeker.Seek(offset, io.SeekStart) if err != nil { return nil, 0, err } diff --git a/core/commands/cmdenv/env.go b/core/commands/cmdenv/env.go index 2b601b9ffa9..4a6e2d56423 100644 --- a/core/commands/cmdenv/env.go +++ b/core/commands/cmdenv/env.go @@ -148,9 +148,6 @@ func ExecuteFastProvideRoot( case !cfg.Provide.Enabled.WithDefault(config.DefaultProvideEnabled): log.Debugw("fast-provide-root: skipped", "reason", "Provide.Enabled is false") return nil - case cfg.Provide.DHT.Interval.WithDefault(config.DefaultProvideDHTInterval) == 0: - log.Debugw("fast-provide-root: skipped", "reason", "Provide.DHT.Interval is 0") - return nil case !ipfsNode.HasActiveDHTClient(): log.Debugw("fast-provide-root: skipped", "reason", "DHT not available") return nil diff --git a/core/commands/cmdenv/progress.go b/core/commands/cmdenv/progress.go new file mode 100644 index 00000000000..a4b32d4b4c9 --- /dev/null +++ b/core/commands/cmdenv/progress.go @@ -0,0 +1,24 @@ +package cmdenv + +import ( + "os" + + cmds "github.com/ipfs/go-ipfs-cmds" +) + +// ProgressBarFullTemplate is the pb/v3 template used by transfer +// commands once the total byte count is known: byte counter, bar, +// speed, percent, and ETA. Explicit format args override pb's +// defaults so the rate renders as "MiB/s" (not "MiB p/s") and the +// remaining time falls back to "ETA ?" while speed is unknown. +const ProgressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` + +// ShouldShowProgress reports whether a progress bar should be rendered +// based on a boolean option. An explicit `--=true|false` always +// wins; when unset, it defaults to whether stderr is a terminal. +func ShouldShowProgress(req *cmds.Request, flag string) bool { + if v, ok := req.Options[flag].(bool); ok { + return v + } + return IsTerminal(os.Stderr) +} diff --git a/core/commands/cmdenv/progress_test.go b/core/commands/cmdenv/progress_test.go new file mode 100644 index 00000000000..c33409ad0c5 --- /dev/null +++ b/core/commands/cmdenv/progress_test.go @@ -0,0 +1,46 @@ +package cmdenv + +import ( + "os" + "testing" + + cmds "github.com/ipfs/go-ipfs-cmds" +) + +func TestShouldShowProgress(t *testing.T) { + const flag = "progress" + makeReq := func(opts map[string]any) *cmds.Request { + if opts == nil { + opts = map[string]any{} + } + return &cmds.Request{Options: opts} + } + + t.Run("explicit true wins regardless of TTY", func(t *testing.T) { + if !ShouldShowProgress(makeReq(map[string]any{flag: true}), flag) { + t.Error("expected true for --progress=true") + } + }) + + t.Run("explicit false wins regardless of TTY", func(t *testing.T) { + if ShouldShowProgress(makeReq(map[string]any{flag: false}), flag) { + t.Error("expected false for --progress=false") + } + }) + + t.Run("unset defaults to IsTerminal(stderr)", func(t *testing.T) { + got := ShouldShowProgress(makeReq(nil), flag) + want := IsTerminal(os.Stderr) + if got != want { + t.Errorf("ShouldShowProgress(unset) = %v, want IsTerminal(os.Stderr) = %v", got, want) + } + }) + + t.Run("non-bool value treated as unset", func(t *testing.T) { + got := ShouldShowProgress(makeReq(map[string]any{flag: "yes"}), flag) + want := IsTerminal(os.Stderr) + if got != want { + t.Errorf("ShouldShowProgress(non-bool) = %v, want IsTerminal(os.Stderr) = %v", got, want) + } + }) +} diff --git a/core/commands/cmdenv/tty.go b/core/commands/cmdenv/tty.go new file mode 100644 index 00000000000..5bf232dd0aa --- /dev/null +++ b/core/commands/cmdenv/tty.go @@ -0,0 +1,14 @@ +package cmdenv + +import ( + "os" + + "github.com/mattn/go-isatty" +) + +// IsTerminal reports whether f is connected to a terminal, +// including MSYS/Cygwin-style terminals on Windows. +func IsTerminal(f *os.File) bool { + fd := f.Fd() + return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) +} diff --git a/core/commands/commands_test.go b/core/commands/commands_test.go index 1326f5eb69c..22df7d5105c 100644 --- a/core/commands/commands_test.go +++ b/core/commands/commands_test.go @@ -81,6 +81,7 @@ func TestCommands(t *testing.T) { "/diag/datastore/count", "/diag/datastore/get", "/diag/datastore/put", + "/diag/healthy", "/diag/profile", "/diag/sys", "/files", @@ -173,6 +174,7 @@ func TestCommands(t *testing.T) { "/ping", "/provide", "/provide/clear", + "/provide/once", "/provide/stat", "/pubsub", "/pubsub/ls", diff --git a/core/commands/dag/dag.go b/core/commands/dag/dag.go index 0e737ec6c11..ec042af2794 100644 --- a/core/commands/dag/dag.go +++ b/core/commands/dag/dag.go @@ -24,6 +24,7 @@ const ( fastProvideRootOptionName = "fast-provide-root" fastProvideDAGOptionName = "fast-provide-dag" fastProvideWaitOptionName = "fast-provide-wait" + localOnlyOptionName = "local-only" ) // DagCmd provides a subset of commands for interacting with ipld dag objects @@ -193,6 +194,10 @@ Note: currently present in the blockstore does not represent a complete DAG, pinning of that individual root will fail. + Use --local-only to import a partial CAR (e.g. from 'dag export + --local-only'). --local-only implies --pin-roots=false because a partial + CAR has no full DAG to pin. + FAST PROVIDE OPTIMIZATION: Root CIDs from CAR headers are immediately provided to the DHT in addition @@ -213,7 +218,8 @@ Specification of CAR formats: https://ipld.io/specs/transport/car/ cmds.FileArg("path", true, true, "The path of a .car file.").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption(pinRootsOptionName, "Pin optional roots listed in the .car headers after importing.").WithDefault(true), + cmds.BoolOption(pinRootsOptionName, "Pin optional roots listed in the .car headers after importing. Default: true."), + cmds.BoolOption(localOnlyOptionName, "Import a partial CAR (e.g. from 'dag export --local-only'). Implies --pin-roots=false."), cmds.BoolOption(silentOptionName, "No output."), cmds.BoolOption(statsOptionName, "Output stats."), cmds.BoolOption(fastProvideRootOptionName, "Immediately provide root CIDs to DHT in addition to regular queue, for faster discovery. Default: Import.FastProvideRoot"), @@ -277,6 +283,10 @@ var DagExportCmd = &cmds.Command{ Note that at present only single root selections / .car files are supported. The output of blocks happens in strict DAG-traversal, first-seen, order. CAR file follows the CARv1 format: https://ipld.io/specs/transport/car/carv1/ + +Use --local-only for a best-effort export from the local blockstore: blocks +that are missing or unreadable locally (and their subtrees) are skipped, so +the resulting CAR is partial. --local-only implies --offline. `, HTTP: &cmds.HTTPHelpText{ ResponseContentType: "application/vnd.ipld.car", @@ -286,7 +296,8 @@ CAR file follows the CARv1 format: https://ipld.io/specs/transport/car/carv1/ cmds.StringArg("root", true, false, "CID of a root to recursively export").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption(progressOptionName, "p", "Display progress on CLI. Defaults to true when STDERR is a TTY."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), + cmds.BoolOption(localOnlyOptionName, "Best-effort export of locally-available blocks; missing or unreadable blocks (and their subtrees) are skipped. Implies --offline."), }, Run: dagExport, PostRun: cmds.PostRunMap{ @@ -352,7 +363,7 @@ Note: This command skips duplicate blocks in reporting both size and the number cmds.StringArg("root", true, true, "CID of a DAG root to get statistics for").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption(progressOptionName, "p", "Show progress on stderr. Auto-detected if stderr is a terminal."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, Run: dagStat, Type: DagStatSummary{}, diff --git a/core/commands/dag/export.go b/core/commands/dag/export.go index 48223f86083..a79136e3c2b 100644 --- a/core/commands/dag/export.go +++ b/core/commands/dag/export.go @@ -8,18 +8,29 @@ import ( "os" "time" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" + blockstore "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/dag/walker" cid "github.com/ipfs/go-cid" cmds "github.com/ipfs/go-ipfs-cmds" ipld "github.com/ipfs/go-ipld-format" "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/ipfs/kubo/core/commands/cmdutils" iface "github.com/ipfs/kubo/core/coreiface" + "github.com/ipfs/kubo/core/coreiface/options" gocar "github.com/ipld/go-car/v2" + carstorage "github.com/ipld/go-car/v2/storage" cidlink "github.com/ipld/go-ipld-prime/linking/cid" selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" ) +// pb/v3 template for `ipfs dag export`: byte counter, speed, and +// elapsed time. No bar/percent/ETA because the total size of the +// CAR stream is not known up front. The explicit "%s/s" speed +// format overrides pb's default "p/s" suffix so the rate renders +// as "MiB/s". +const progressBarTemplate = `{{counters . }} {{speed . "%s/s" "?/s"}} {{etime . }}` + func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { // Accept CID or a content path p, err := cmdutils.PathOrCidPath(req.Arguments[0]) @@ -27,10 +38,28 @@ func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment return err } + localOnly, _ := req.Options[localOnlyOptionName].(bool) + if localOnly { + // --local-only and --offline=false contradict each other. + if offline, set := req.Options["offline"].(bool); set && !offline { + return fmt.Errorf("--%s implies --offline and cannot be combined with --offline=false; please drop one of them", localOnlyOptionName) + } + } + api, err := cmdenv.GetApi(env, req) if err != nil { return err } + if localOnly { + // --local-only implies --offline so api.Block().Stat below cannot + // reach out for path resolution. The DAG walk itself uses the raw + // blockstore via walker (see exportPartialCAR) and is local by + // construction regardless of this setting. + api, err = api.WithOptions(options.Api.Offline(true)) + if err != nil { + return err + } + } // Resolve path and confirm the root block is available, fail fast if not b, err := api.Block().Stat(req.Context, p) @@ -39,6 +68,15 @@ func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment } c := b.Path().RootCid() + var bs blockstore.Blockstore + if localOnly { + node, err := cmdenv.GetNode(env) + if err != nil { + return err + } + bs = node.Blockstore + } + pipeR, pipeW := io.Pipe() errCh := make(chan error, 2) // we only report the 1st error @@ -50,6 +88,13 @@ func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment close(errCh) }() + if localOnly { + if err := exportPartialCAR(req.Context, bs, c, pipeW); err != nil { + errCh <- err + } + return + } + lsys := cidlink.DefaultLinkSystem() lsys.SetReadStorage(&dagStore{dag: api.Dag(), ctx: req.Context}) @@ -98,29 +143,63 @@ func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment return err } -func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { - var showProgress bool - val, specified := res.Request().Options[progressOptionName] - if !specified { - // default based on TTY availability - errStat, _ := os.Stderr.Stat() - if (errStat.Mode() & os.ModeCharDevice) != 0 { - showProgress = true +// exportPartialCAR is the best-effort engine behind `dag export --local-only`. +// It walks the DAG rooted at root and writes the visited blocks to w as a +// CARv1 stream. +// +// The walker reads from the raw blockstore directly (not via the kubo +// CoreAPI or DAGService), so it is structurally incapable of triggering a +// network fetch. Any block missing or unreadable locally, plus its entire +// subtree, is silently skipped: the resulting CAR is partial by design. +// +// Errors writing the CAR itself (emit failures) are surfaced: those are +// output problems, not local-availability problems. +// +// This mirrors the MFS+unique provider in core/node/provider.go. +func exportPartialCAR(ctx context.Context, bs blockstore.Blockstore, root cid.Cid, w io.Writer) error { + writable, err := carstorage.NewWritable(w, []cid.Cid{root}, gocar.WriteAsCarV1(true)) + if err != nil { + return err + } + + // Capture the first emit (write-side) error so the walk stops cleanly. + var emitErr error + emit := func(k cid.Cid) bool { + blk, err := bs.Get(ctx, k) + if err != nil { + // Any read error after locality passed (e.g. GC race or + // corruption) is treated as "not available locally": skip + // the block and keep streaming the rest of the partial CAR. + return true + } + if err := writable.Put(ctx, k.KeyString(), blk.RawData()); err != nil { + emitErr = err + return false } - } else if val.(bool) { - showProgress = true + return true } - // simple passthrough, no progress - if !showProgress { + // Both the locality check (bs.Has) and the link fetcher read straight + // from the blockstore, so the walk cannot reach the network. Errors + // inside walker (locality, fetch) are skip-and-log, matching the + // best-effort semantics here. + if err := walker.WalkDAG(ctx, root, + walker.LinksFetcherFromBlockstore(bs), + emit, + walker.WithLocality(func(ctx context.Context, k cid.Cid) (bool, error) { return bs.Has(ctx, k) }), + ); err != nil { + return err + } + return emitErr +} + +func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { + if !cmdenv.ShouldShowProgress(res.Request(), progressOptionName) { return cmds.Copy(re, res) } - bar := pb.New64(0).SetUnits(pb.U_BYTES) - bar.Output = os.Stderr - bar.ShowSpeed = true - bar.ShowElapsedTime = true - bar.RefreshRate = 500 * time.Millisecond + bar := pb.New64(0).Set(pb.Bytes, true).SetWriter(os.Stderr).SetRefreshRate(500 * time.Millisecond) + bar.SetTemplateString(progressBarTemplate) bar.Start() var processedOneResponse bool @@ -194,7 +273,7 @@ func cidFromBinString(key string) (cid.Cid, error) { return cid.Undef, fmt.Errorf("dagStore: key was not a cid: %w", err) } if l != len(key) { - return cid.Undef, fmt.Errorf("dagSore: key was not a cid: had %d bytes leftover", len(key)-l) + return cid.Undef, fmt.Errorf("dagStore: key was not a cid: had %d bytes leftover", len(key)-l) } return k, nil } diff --git a/core/commands/dag/import.go b/core/commands/dag/import.go index 472533be3c1..5ec9b0a29e5 100644 --- a/core/commands/dag/import.go +++ b/core/commands/dag/import.go @@ -48,8 +48,24 @@ func dagImport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment return err } - doPinRoots, _ := req.Options[pinRootsOptionName].(bool) + pinRootsVal, pinRootsSet := req.Options[pinRootsOptionName].(bool) + localOnly, _ := req.Options[localOnlyOptionName].(bool) + + // --pin-roots defaults to true; the default is applied here (not via + // .WithDefault) so we can tell apart "user explicitly passed true" from + // "no value provided". + doPinRoots := true + if pinRootsSet { + doPinRoots = pinRootsVal + } + if localOnly { + if pinRootsSet && pinRootsVal { + return fmt.Errorf("--%s implies --%s=false and cannot be combined with --%s=true; please drop one of them", localOnlyOptionName, pinRootsOptionName, pinRootsOptionName) + } + // --local-only implies --pin-roots=false: a partial CAR has no full DAG to pin. + doPinRoots = false + } fastProvideRoot, fastProvideRootSet := req.Options[fastProvideRootOptionName].(bool) fastProvideDAG, fastProvideDAGSet := req.Options[fastProvideDAGOptionName].(bool) fastProvideWait, fastProvideWaitSet := req.Options[fastProvideWaitOptionName].(bool) diff --git a/core/commands/dag/stat.go b/core/commands/dag/stat.go index 0ce1e4246f4..6602789bdb8 100644 --- a/core/commands/dag/stat.go +++ b/core/commands/dag/stat.go @@ -95,17 +95,7 @@ func dagStat(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) } func finishCLIStat(res cmds.Response, re cmds.ResponseEmitter) error { - // Determine whether to show progress based on TTY detection or explicit flag - var showProgress bool - val, specified := res.Request().Options[progressOptionName] - if !specified { - // Auto-detect: show progress only if stderr is a TTY - if errStat, err := os.Stderr.Stat(); err == nil { - showProgress = (errStat.Mode() & os.ModeCharDevice) != 0 - } - } else { - showProgress = val.(bool) - } + showProgress := cmdenv.ShouldShowProgress(res.Request(), progressOptionName) var dagStats *DagStatSummary for { diff --git a/core/commands/diag.go b/core/commands/diag.go index c8a48e90c58..09d69d2fc00 100644 --- a/core/commands/diag.go +++ b/core/commands/diag.go @@ -5,16 +5,26 @@ import ( "errors" "fmt" "io" + "time" + "github.com/ipfs/boxo/path" + cid "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/mount" "github.com/ipfs/go-datastore/query" cmds "github.com/ipfs/go-ipfs-cmds" oldcmds "github.com/ipfs/kubo/commands" + "github.com/ipfs/kubo/core/commands/cmdenv" node "github.com/ipfs/kubo/core/node" + "github.com/ipfs/kubo/core/shutdown" fsrepo "github.com/ipfs/kubo/repo/fsrepo" ) +// diagHealthyProbeCIDStr is the well-known empty UnixFS directory, +// built into every kubo node. Fetching it succeeds regardless of peers, +// DHT, or user content, so it isolates the DAG/blockstore pipeline. +const diagHealthyProbeCIDStr = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" + var DiagCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Generate diagnostic reports.", @@ -25,6 +35,41 @@ var DiagCmd = &cmds.Command{ "cmds": ActiveReqsCmd, "profile": sysProfileCmd, "datastore": diagDatastoreCmd, + "healthy": diagHealthyCmd, + }, +} + +// diagHealthyCmd is a container-healthcheck probe. It fails when shutdown +// has been initiated (even if the RPC API still answers) or when the DAG +// pipeline cannot resolve a built-in CID. +var diagHealthyCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Report whether the daemon is operational.", + ShortDescription: ` +Exits 0 if the daemon is running and can resolve the well-known empty +UnixFS directory. Exits non-zero if shutdown has started or the DAG +pipeline is broken. Intended for container healthchecks. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + if t := shutdown.StartedAt(); !t.IsZero() { + return fmt.Errorf("daemon is shutting down (started %s ago)", time.Since(t).Round(time.Second)) + } + api, err := cmdenv.GetApi(env, req) + if err != nil { + return err + } + probeCID, err := cid.Decode(diagHealthyProbeCIDStr) + if err != nil { + return fmt.Errorf("invalid probe CID: %w", err) + } + if _, _, err := api.ResolvePath(req.Context, path.FromCid(probeCID)); err != nil { + return fmt.Errorf("probe resolve: %w", err) + } + if _, err := api.Dag().Get(req.Context, probeCID); err != nil { + return fmt.Errorf("probe fetch: %w", err) + } + return cmds.EmitOnce(res, "ok") }, } diff --git a/core/commands/get.go b/core/commands/get.go index 804836b9afd..f5c7160331d 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -16,7 +16,7 @@ import ( "github.com/ipfs/kubo/core/commands/cmdutils" "github.com/ipfs/kubo/core/commands/e" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" "github.com/ipfs/boxo/tar" cmds "github.com/ipfs/go-ipfs-cmds" @@ -58,7 +58,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. cmds.BoolOption(archiveOptionName, "a", "Output a TAR archive."), cmds.BoolOption(compressOptionName, "C", "Compress the output with GZIP compression."), cmds.IntOption(compressionLevelOptionName, "l", "The level of compression (1-9)."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data.").WithDefault(true), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, PreRun: func(req *cmds.Request, env cmds.Environment) error { _, err := getCompressOptions(req) @@ -140,7 +140,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. } archive, _ := req.Options[archiveOptionName].(bool) - progress, _ := req.Options[progressOptionName].(bool) + showProgress := cmdenv.ShouldShowProgress(req, progressOptionName) gw := getWriter{ Out: os.Stdout, @@ -148,7 +148,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. Archive: archive, Compression: cmplvl, Size: int64(res.Length()), - Progress: progress, + Progress: showProgress, } return gw.Write(outReader, outPath) @@ -177,19 +177,7 @@ func progressBarForReader(out io.Writer, r io.Reader, l int64) (*pb.ProgressBar, } func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { - // setup bar reader - // TODO: get total length of files - bar := pb.New64(l).SetUnits(pb.U_BYTES) - bar.Output = out - - // the progress bar lib doesn't give us a way to get the width of the output, - // so as a hack we just use a callback to measure the output, then get rid of it - bar.Callback = func(line string) { - terminalWidth := len(line) - bar.Callback = nil - log.Infof("terminal width: %v\n", terminalWidth) - } - return bar + return pb.New64(l).Set(pb.Bytes, true).SetTemplateString(cmdenv.ProgressBarFullTemplate).SetWriter(out) } func getOutPath(req *cmds.Request) string { @@ -260,8 +248,8 @@ func (gw *getWriter) writeExtracted(r io.Reader, fpath string) error { bar := makeProgressBar(gw.Err, gw.Size) bar.Start() defer bar.Finish() - defer bar.Set64(gw.Size) - progressCb = bar.Add64 + defer bar.SetCurrent(gw.Size) + progressCb = func(n int64) int64 { bar.Add64(n); return bar.Current() } } extractor := &tar.Extractor{Path: fpath, Progress: progressCb} diff --git a/core/commands/provide.go b/core/commands/provide.go index 3e72ea389f2..e6c38689dac 100644 --- a/core/commands/provide.go +++ b/core/commands/provide.go @@ -5,15 +5,19 @@ import ( "errors" "fmt" "io" + "os" "strings" "text/tabwriter" "time" "unicode/utf8" humanize "github.com/dustin/go-humanize" + "github.com/ipfs/boxo/dag/walker" + dag "github.com/ipfs/boxo/ipld/merkledag" boxoprovider "github.com/ipfs/boxo/provider" cid "github.com/ipfs/go-cid" cmds "github.com/ipfs/go-ipfs-cmds" + "github.com/ipfs/kubo/config" "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/libp2p/go-libp2p-kad-dht/fullrt" "github.com/libp2p/go-libp2p-kad-dht/provider" @@ -23,6 +27,7 @@ import ( routing "github.com/libp2p/go-libp2p/core/routing" "github.com/probe-lab/go-libdht/kad/key" "golang.org/x/exp/constraints" + "golang.org/x/term" ) const ( @@ -52,10 +57,9 @@ Control providing operations. OVERVIEW: -The provider system advertises content by publishing provider records, -allowing other nodes to discover which peers have specific content. -Content is reprovided periodically (every Provide.DHT.Interval) -according to Provide.Strategy. +The provide system publishes provider records so other peers can discover +which nodes hold each CID. Content is reprovided periodically (every +Provide.DHT.Interval) according to Provide.Strategy. CONFIGURATION: @@ -63,12 +67,13 @@ Learn more: https://github.com/ipfs/kubo/blob/master/docs/config.md#provide SEE ALSO: -For ad-hoc one-time provide, see 'ipfs routing provide' +For ad-hoc immediate announcements, see 'ipfs provide once'. `, }, Subcommands: map[string]*cmds.Command{ "clear": provideClearCmd, + "once": provideOnceCmd, "stat": provideStatCmd, }, } @@ -78,20 +83,14 @@ var provideClearCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Clear all CIDs from the provide queue.", ShortDescription: ` -Clear all CIDs pending to be provided for the first time. +Clears the provide queue: CIDs waiting to be advertised to the DHT for the +first time. Does not affect content that is already being reprovided on +schedule. -BEHAVIOR: +Kubo also clears the queue automatically on restart when it detects a +change of Provide.Strategy. -This command removes CIDs from the provide queue that are waiting to be -advertised to the DHT for the first time. It does not affect content that -is already being reprovided on schedule. - -AUTOMATIC CLEARING: - -Kubo will automatically clear the queue when it detects a change of -Provide.Strategy upon a restart. - -Learn: https://github.com/ipfs/kubo/blob/master/docs/config.md#providestrategy +See: https://github.com/ipfs/kubo/blob/master/docs/config.md#providestrategy `, }, Options: []cmds.Option{ @@ -130,6 +129,211 @@ Learn: https://github.com/ipfs/kubo/blob/master/docs/config.md#providestrategy }, } +// ProvideOnceEvent is emitted once per CID announced by 'ipfs provide once'. +type ProvideOnceEvent struct { + Queued string +} + +var provideOnceCmd = &cmds.Command{ + Status: cmds.Experimental, + Helptext: cmds.HelpText{ + Tagline: "Announce CIDs to the routing system on demand.", + ShortDescription: ` +Publishes provider records for the given CIDs once. The periodic +reprovide schedule (driven by Provide.Strategy and Provide.DHT.Interval) +is left unchanged: CIDs announced here are NOT added to the schedule. +CIDs can be passed as arguments or streamed from stdin (one per line). + +The default sweep provider (Provide.DHT.SweepEnabled=true) submits the CIDs +to its burst-provide queue and returns as each CID is queued; dedicated +burst workers publish the records to the DHT. Use 'ipfs provide stat' to +monitor progress. + +The legacy provider (Provide.DHT.SweepEnabled=false) queues the CIDs for +its serial worker pool, which publishes one CID at a time and may take +significantly longer to complete. + +Use --recursive to walk the DAG and announce every reachable block. With +the default Provide.Strategy=all, every block is already announced, so -r +is only useful with selective strategies like 'roots' or 'pinned+entities'. + +CIDs must already exist in the local blockstore. + +CIDs are deduplicated across arguments, stdin, and DAG walks. Dedup uses +a bloom filter, so at very large scale a small fraction of CIDs may be +skipped (default rate ~1 in 4.75M). + +OUTPUT: + +Output is streamed as each CID is queued. With --enc=json, one +{"Queued": ""} object is emitted per line. With the text encoder +(default) on a terminal, a single line shows the running count; on a pipe, +a final count is printed at the end. +`, + }, + Arguments: []cmds.Argument{ + cmds.StringArg("cid", true, true, "The CID(s) to announce.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption(recursiveOptionName, "r", "Recursively announce the entire DAG."), + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + nd, err := cmdenv.GetNode(env) + if err != nil { + return err + } + if !nd.IsOnline { + return ErrNotOnline + } + cfg, err := nd.Repo.Config() + if err != nil { + return err + } + if !cfg.Provide.Enabled.WithDefault(config.DefaultProvideEnabled) { + return errors.New("cannot provide: Provide.Enabled is false") + } + if len(nd.PeerHost.Network().Conns()) == 0 && !cfg.HasHTTPProviderConfigured() { + return errors.New("cannot provide: no connected peers") + } + + recursive, _ := req.Options[recursiveOptionName].(bool) + + // seen deduplicates across all roots and recursive walks, so a CID + // shared by multiple roots (or repeated in argv/stdin) is announced + // exactly once per invocation. The bloom autoscales as more CIDs + // arrive, keeping memory bounded for arbitrarily large inputs at + // the cost of a small false-positive rate (default ~1 in 4.75M) + // that may cause an occasional CID to be skipped. + seen, err := walker.NewBloomTracker(walker.MinBloomCapacity, walker.DefaultBloomFPRate) + if err != nil { + return err + } + + // announce queues a single CID into the provide system and emits one + // event for it. Uses ProvideOnce so the CID is published without + // being added to the keystore: the periodic reprovide schedule + // (driven by Provide.Strategy) is unaffected. Errors propagate to + // the caller. + announce := func(c cid.Cid) error { + if err := nd.Provider.ProvideOnce(c.Hash()); err != nil { + return err + } + return res.Emit(&ProvideOnceEvent{Queued: c.String()}) + } + + // processRoot validates a root CID against the local blockstore and + // announces either just that CID or every block reachable from it. + processRoot := func(arg string) error { + c, err := cid.Decode(arg) + if err != nil { + return fmt.Errorf("invalid CID %q: %w", arg, err) + } + has, err := nd.Blockstore.Has(req.Context, c) + if err != nil { + return err + } + if !has { + return fmt.Errorf("block %s not found locally, cannot provide", c) + } + + if !recursive { + if !seen.Visit(c) { + return nil + } + return announce(c) + } + + // Stream per-block: visit emits as it walks. Cancel the walk on + // the first announce error so we don't keep fetching DAG nodes + // after we've already failed. + ctx, cancel := context.WithCancel(req.Context) + defer cancel() + var visitErr error + walkErr := dag.Walk(ctx, dag.GetLinksDirect(nd.DAG), c, func(child cid.Cid) bool { + // Skip subtrees we've already walked from a previous root or + // argument: returning false stops descent into this node. + if !seen.Visit(child) { + return false + } + if err := announce(child); err != nil { + visitErr = err + cancel() + return false + } + return true + }) + if visitErr != nil { + return visitErr + } + return walkErr + } + + args := argumentIterator{req.Arguments, req.BodyArgs()} + for { + arg, ok := args.next() + if !ok { + break + } + if err := processRoot(arg); err != nil { + return err + } + } + return args.err() + }, + PostRun: cmds.PostRunMap{ + cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error { + // In text mode we render the running counter and final summary + // directly to stderr/stdout, bypassing the encoder so the TTY + // redraw works. For other encoders (json, xml) we must let the + // encoder serialize each event, so forward the stream as-is. + if enc, _ := res.Request().Options[cmds.EncLong].(string); enc != "" && enc != cmds.Text { + return cmds.Copy(re, res) + } + + // Text mode: render directly to stderr/stdout below. Do not + // call re.Emit from this branch, or output will race with the + // running counter. + isTTY := term.IsTerminal(int(os.Stderr.Fd())) + var count int + for { + v, err := res.Next() + if err == io.EOF { + break + } + if err != nil { + if isTTY && count > 0 { + fmt.Fprintln(os.Stderr) + } + return err + } + if _, ok := v.(*ProvideOnceEvent); !ok { + log.Errorf("provide once postrun: received unexpected type %T", v) + continue + } + count++ + if isTTY { + fmt.Fprintf(os.Stderr, "\rqueued %d CID(s) for immediate provide", count) + } + } + if isTTY && count > 0 { + fmt.Fprintln(os.Stderr) + } else { + fmt.Fprintf(os.Stdout, "queued %d CID(s) for immediate provide\n", count) + } + return nil + }, + }, + Type: ProvideOnceEvent{}, + Encoders: cmds.EncoderMap{ + // Used when PostRun is not invoked (HTTP API consumers in text mode). + // One CID per line keeps the stream pipe-friendly. + cmds.Text: cmds.MakeTypedEncoder(func(_ *cmds.Request, w io.Writer, e *ProvideOnceEvent) error { + _, err := fmt.Fprintf(w, "%s\n", e.Queued) + return err + }), + }, +} + type provideStats struct { Sweep *stats.Stats Legacy *boxoprovider.ReproviderStats @@ -159,30 +363,27 @@ func extractSweepingProvider(prov any, useLAN bool) *provider.SweepingProvider { var provideStatCmd = &cmds.Command{ Status: cmds.Experimental, Helptext: cmds.HelpText{ - Tagline: "Show statistics about the provider system", + Tagline: "Show statistics about the provide system", ShortDescription: ` -Returns statistics about the node's provider system. +Returns statistics about the node's provide system. OVERVIEW: -The provide system advertises content to the DHT by publishing provider -records that map CIDs to your peer ID. These records expire after a fixed -TTL to account for node churn, so content must be reprovided periodically -to stay discoverable. +The provide system publishes provider records mapping CIDs to your peer +ID. Records expire after a fixed TTL, so the system reprovides them on a +schedule to keep content discoverable. Two provider types exist: -- Sweep provider: Divides the DHT keyspace into regions and systematically - sweeps through them over the reprovide interval. Batches CIDs allocated +- Sweep provider (default): divides the DHT keyspace into regions and + sweeps through them over the reprovide interval. Batches CIDs that map to the same DHT servers, reducing lookups from N (one per CID) to a - small static number based on DHT size (~3k for 10k DHT servers). Spreads - work evenly over time to prevent resource spikes and ensure announcements - happen just before records expire. + small constant based on DHT size (~3k for 10k DHT servers). Spreads work + evenly over time and announces records just before they expire. -- Legacy provider: Processes each CID individually with separate DHT - lookups. Attempts to reprovide all content as quickly as possible at the - start of each cycle. Works well for small datasets but struggles with - large collections. +- Legacy provider: announces each CID with a separate DHT lookup. Tries + to reprovide all content as fast as possible at each cycle start. Fine + for small datasets, slow past a few thousand CIDs. Learn more: - Config: https://github.com/ipfs/kubo/blob/master/docs/config.md#provide @@ -267,7 +468,10 @@ NOTES: return fmt.Errorf("stats not available with current routing system %T", nd.Provider) } - s := sweepingProvider.Stats() + s, err := sweepingProvider.Stats(req.Context) + if err != nil { + return err + } return res.Emit(provideStats{Sweep: &s}) }, Encoders: cmds.EncoderMap{ diff --git a/core/commands/routing.go b/core/commands/routing.go index 280b14174ce..0722e043b45 100644 --- a/core/commands/routing.go +++ b/core/commands/routing.go @@ -142,9 +142,27 @@ const ( ) var provideRefRoutingCmd = &cmds.Command{ - Status: cmds.Experimental, + Status: cmds.Deprecated, Helptext: cmds.HelpText{ - Tagline: "Announce to the network that you are providing given values.", + Tagline: "Deprecated, use 'ipfs provide once' instead.", + ShortDescription: ` +'ipfs routing provide' has moved to 'ipfs provide once'. This command keeps +its existing behavior so existing scripts continue to work, but will be +removed in a future release. + +Compared to 'ipfs provide once', this command: + +- Buffers all CIDs from arguments and stdin before doing any work, + instead of streaming them as they arrive. +- Emits no per-CID output: there is no JSON event stream and the -v + flag's per-peer events do not actually propagate to the encoder. +- With -r, re-walks subtrees shared between roots and re-announces + shared blocks; 'ipfs provide once' deduplicates across all inputs. +- Issues an extra synchronous DHT lookup per CID on top of the + provider system, which defeats sweep batching. + +Prefer 'ipfs provide once' for new scripts and any large input. +`, }, Arguments: []cmds.Argument{ @@ -271,14 +289,14 @@ var provideRefRoutingCmd = &cmds.Command{ var reprovideRoutingCmd = &cmds.Command{ Status: cmds.Deprecated, Helptext: cmds.HelpText{ - Tagline: "Trigger reprovider (legacy provider only).", + Tagline: "Trigger a reprovide cycle (legacy provider only).", ShortDescription: ` -Trigger reprovider to announce our data to network. +Forces the legacy provider to reprovide all locally stored CIDs that match +Provide.Strategy. -Only available with the legacy provider (Provide.DHT.SweepEnabled=false). -Returns an error when Provide.DHT.SweepEnabled=true (the default). -The sweep provider reprovides automatically on schedule. -Use 'ipfs provide stat -a' to monitor reprovide progress. +Only works when Provide.DHT.SweepEnabled=false. With the default sweep +provider, reproviding is continuous and scheduled, so this command returns +an error. Use 'ipfs provide stat --all' to monitor sweep progress. `, }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { @@ -303,7 +321,7 @@ Use 'ipfs provide stat -a' to monitor reprovide progress. } provideSys, ok := nd.Provider.(provider.Reprovider) if !ok { - err := errors.New("invalid configuration: manual reprovide not available with sweep provider (Provide.DHT.SweepEnabled=true), use 'ipfs provide stat -a' to monitor automatic reprovide progress") + err := errors.New("manual reprovide is not available with the sweep provider; set Provide.DHT.SweepEnabled=false to use the legacy provider, or run 'ipfs provide stat --all' to monitor the sweep schedule") log.Error(err) return err } diff --git a/core/coreiface/tests/unixfs.go b/core/coreiface/tests/unixfs.go index 7b277236950..608475b4808 100644 --- a/core/coreiface/tests/unixfs.go +++ b/core/coreiface/tests/unixfs.go @@ -938,9 +938,14 @@ func (tp *TestSuite) TestGetSeek(t *testing.T) { t.Fatal("not a file") } + fSeeker, ok := f.(io.Seeker) + if !ok { + t.Fatal("file does not support seeking") + } + test := func(offset int64, whence int, read int, expect int64, shouldEof bool) { t.Run(fmt.Sprintf("seek%d+%d-r%d-%d", whence, offset, read, expect), func(t *testing.T) { - n, err := f.Seek(offset, whence) + n, err := fSeeker.Seek(offset, whence) if err != nil { t.Fatal(err) } diff --git a/core/node/bitswap.go b/core/node/bitswap.go index f58ca495d12..d525dc39c6f 100644 --- a/core/node/bitswap.go +++ b/core/node/bitswap.go @@ -26,6 +26,7 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/kubo/core/node/helpers" + "github.com/ipfs/kubo/core/shutdown" ) // Docs: https://github.com/ipfs/kubo/blob/master/docs/config.md#internalbitswap @@ -197,7 +198,7 @@ func Bitswap(serverEnabled, libp2pEnabled, httpEnabled bool) any { lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return bs.Close() + return shutdown.CloseWithCtx(ctx, "bitswap", bs.Close) }, }) return bs, nil @@ -213,7 +214,7 @@ func OnlineExchange(isBitswapActive bool) any { } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return in.Close() + return shutdown.CloseWithCtx(ctx, "bitswap-exchange", in.Close) }, }) return in diff --git a/core/node/builder.go b/core/node/builder.go index 4014308f5c2..146f24d6836 100644 --- a/core/node/builder.go +++ b/core/node/builder.go @@ -4,12 +4,14 @@ import ( "context" "crypto/rand" "encoding/base64" + "time" "go.uber.org/fx" "github.com/ipfs/boxo/autoconf" "github.com/ipfs/kubo/core/node/helpers" "github.com/ipfs/kubo/core/node/libp2p" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" ds "github.com/ipfs/go-datastore" @@ -37,6 +39,11 @@ type BuildCfg struct { Routing libp2p.RoutingOption Host libp2p.HostOption Repo repo.Repo + + // ShutdownTimeout caps how long node.Close()'s call to app.Stop is + // allowed to take. Zero disables the cap (app.Stop runs with no + // deadline, matching the legacy "wait forever" behavior). + ShutdownTimeout time.Duration } func (cfg *BuildCfg) getOpt(key string) bool { @@ -77,7 +84,7 @@ func (cfg *BuildCfg) options(ctx context.Context) (fx.Option, *cfg.Config) { repoOption := fx.Provide(func(lc fx.Lifecycle) repo.Repo { lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return cfg.Repo.Close() + return shutdown.CloseWithCtx(ctx, "repo", cfg.Repo.Close) }, }) diff --git a/core/node/core.go b/core/node/core.go index f3f8e84a5bb..7774b807ee7 100644 --- a/core/node/core.go +++ b/core/node/core.go @@ -27,6 +27,7 @@ import ( "github.com/ipfs/kubo/config" "github.com/ipfs/kubo/core/node/helpers" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" ) @@ -42,7 +43,7 @@ func BlockService(cfg *config.Config) func(lc fx.Lifecycle, bs blockstore.Blocks lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return bsvc.Close() + return shutdown.CloseWithCtx(ctx, "blockservice", bsvc.Close) }, }) @@ -50,11 +51,19 @@ func BlockService(cfg *config.Config) func(lc fx.Lifecycle, bs blockstore.Blocks } } -// Pinning creates new pinner which tells GC which blocks should be kept -func Pinning(strategy string) func(bstore blockstore.Blockstore, ds format.DAGService, repo repo.Repo, prov DHTProvider) (pin.Pinner, error) { +// Pinning builds the pinner that GC uses to decide which blocks to keep. +// +// An fx OnStop hook closes the pinner before the repo (and its +// datastore). The order matters: in-flight pinner operations hold a +// reference to the datastore, and some datastores (pebble) panic on +// use after Close. Pinner.Close cancels those operations and waits +// for them to return. See +// [github.com/ipfs/boxo/pinning/pinner.Pinner.Close]. +func Pinning(strategy string) func(lc fx.Lifecycle, bstore blockstore.Blockstore, ds format.DAGService, repo repo.Repo, prov DHTProvider) (pin.Pinner, error) { strategyFlag := config.MustParseProvideStrategy(strategy) - return func(bstore blockstore.Blockstore, + return func(lc fx.Lifecycle, + bstore blockstore.Blockstore, ds format.DAGService, repo repo.Repo, prov DHTProvider, @@ -91,6 +100,21 @@ func Pinning(strategy string) func(bstore blockstore.Blockstore, ds format.DAGSe return nil, err } + // fx runs OnStop hooks in reverse registration order. The + // repo provider registers its close hook earlier (in + // builder.go), so this hook runs first and the repo hook + // runs after, without an explicit dependency between them. + // + // Wrapped with CloseWithCtx because the boxo Pinner.Close + // contract notes that an in-flight op which ignores its ctx + // (a downstream bug) can block Close; the host must bound it + // at the call site so the shutdown deadline is honored. + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return shutdown.CloseWithCtx(ctx, "pinner", pinning.Close) + }, + }) + return pinning, nil } } @@ -258,7 +282,7 @@ func Files(strategy string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return root.Close() + return shutdown.CloseWithCtx(ctx, "mfs-root", root.Close) }, }) diff --git a/core/node/groups.go b/core/node/groups.go index d9c7f1ffb6f..43bd09dcf21 100644 --- a/core/node/groups.go +++ b/core/node/groups.go @@ -201,6 +201,7 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part maybeProvide(libp2p.P2PForgeCertMgr(bcfg.Repo.Path(), cfg.AutoTLS, atlsLog), enableAutoTLS), maybeInvoke(libp2p.StartP2PAutoTLS, enableAutoTLS), fx.Provide(libp2p.AddrFilters(cfg.Swarm.AddrFilters)), + fx.Invoke(libp2p.MonitorDeadListeners(cfg.Addresses.Swarm, cfg.Swarm.AddrFilters, cfg.Addresses.NoAnnounce)), fx.Provide(libp2p.AddrsFactory(cfg.Addresses.Announce, cfg.Addresses.AppendAnnounce, cfg.Addresses.NoAnnounce)), fx.Provide(libp2p.SmuxTransport(cfg.Swarm.Transports)), fx.Provide(libp2p.RelayTransport(enableRelayTransport)), @@ -348,9 +349,11 @@ func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part isBitswapServerEnabled := cfg.Bitswap.ServerEnabled.WithDefault(config.DefaultBitswapServerEnabled) isHTTPRetrievalEnabled := cfg.HTTPRetrieval.Enabled.WithDefault(config.DefaultHTTPRetrievalEnabled) - // The Provide system handles both new CID announcements and periodic re-announcements. - // Disabling is controlled by Provide.Enabled=false or setting Interval to 0. - isProviderEnabled := cfg.Provide.Enabled.WithDefault(config.DefaultProvideEnabled) && cfg.Provide.DHT.Interval.WithDefault(config.DefaultProvideDHTInterval) != 0 + // The Provide system handles both new CID announcements and periodic + // re-announcements. Provide.Enabled=false fully disables it. + // Provide.DHT.Interval=0 disables only the periodic reprovide schedule; + // new CIDs still announce via fast-provide-root and 'ipfs provide once'. + isProviderEnabled := cfg.Provide.Enabled.WithDefault(config.DefaultProvideEnabled) return fx.Options( fx.Provide(BitswapOptions(cfg)), diff --git a/core/node/libp2p/addrs.go b/core/node/libp2p/addrs.go index 1b428ec0e82..9670f787fcc 100644 --- a/core/node/libp2p/addrs.go +++ b/core/node/libp2p/addrs.go @@ -12,9 +12,11 @@ import ( "github.com/ipfs/kubo/config" p2pforge "github.com/ipshipyard/p2p-forge/client" "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" p2pbhost "github.com/libp2p/go-libp2p/p2p/host/basic" ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" mamask "github.com/whyrusleeping/multiaddr-filter" "github.com/caddyserver/certmagic" @@ -36,6 +38,233 @@ func AddrFilters(filters []string) func() (*ma.Filters, Libp2pOpts, error) { } } +// Sources for deadListenerFinding.Source. +const ( + deadListenerSourceAddrFilters = "Swarm.AddrFilters" + deadListenerSourceNoAnnounce = "Addresses.NoAnnounce" +) + +// deadListenerFinding is one resolved listener whose IP falls inside a +// CIDR in `Swarm.AddrFilters` (gater RSTs inbound) or +// `Addresses.NoAnnounce` (listener never advertised). +type deadListenerFinding struct { + Listener string // resolved listen multiaddr (interface-bound) + Rule string // matching CIDR rule from Source + Source string // deadListenerSourceAddrFilters or deadListenerSourceNoAnnounce + Explicit bool // true when the listener IP+port was bound by a specific-IP entry in `Addresses.Swarm`, not a wildcard expansion +} + +// findDeadListeners returns one finding per (listener, rule, source) +// triple whose IP component falls inside a CIDR in addrFilters or +// noAnnounce. +// +// listenAddrs must be already-resolved interface addresses (output of +// `host.Network().InterfaceListenAddresses()`). +// +// swarmListen is the raw `Addresses.Swarm` config. A finding is marked +// `Explicit` when the resolved listener shares its IP and port with a +// specific-IP entry in `swarmListen`, and non-explicit when it came from a +// wildcard listen (`/ip4/0.0.0.0`, `/ip6/::`) expanding onto a per-interface +// address. See explicitListens for why the match is on IP+port rather than +// the full multiaddr string. +// +// Callers route findings to log levels based on Source + Explicit: +// +// - AddrFilters + Explicit: ERROR. The whole listener is unreachable. +// - AddrFilters + wildcard: DEBUG. Other interfaces still serve. +// - NoAnnounce: DEBUG. Operator intent, but useful when tracing why +// an interface address never reaches identify / DHT records. +// +// Unparseable rules (including exact-match multiaddrs in NoAnnounce) +// and listeners without an IP component are skipped silently. +func findDeadListeners(listenAddrs []ma.Multiaddr, swarmListen, addrFilters, noAnnounce []string) []deadListenerFinding { + explicit := explicitListens(swarmListen) + check := func(source string, rules []string) []deadListenerFinding { + var out []deadListenerFinding + for _, r := range rules { + mask, err := mamask.NewMask(r) + if err != nil { + continue + } + f := ma.NewFilters() + f.AddFilter(*mask, ma.ActionDeny) + for _, l := range listenAddrs { + if !f.AddrBlocked(l) { + continue + } + isExplicit := false + if ep, ok := listenEndpoint(l); ok { + _, isExplicit = explicit[ep] + } + out = append(out, deadListenerFinding{ + Listener: l.String(), + Rule: r, + Source: source, + Explicit: isExplicit, + }) + } + } + return out + } + findings := check(deadListenerSourceAddrFilters, addrFilters) + findings = append(findings, check(deadListenerSourceNoAnnounce, noAnnounce)...) + return findings +} + +// listenEndpoint returns a key identifying m's bound socket: its IP value, +// transport (tcp or udp), and port. The bool is false when m has no specific +// IP (a wildcard such as `/ip4/0.0.0.0`, or an IP-less `/dns...` listen) or +// no TCP/UDP port, since such an address cannot name a single socket. +// +// The key intentionally drops everything after the port. The same socket is +// reported under different multiaddrs depending on transport: the WebSocket +// listener canonicalizes `/wss` to `/tls/ws`, and WebTransport appends a +// `/certhash/...` component for its self-signed certificate. Comparing on +// IP+transport+port keeps a specific-IP listen recognizable across those +// rewrites, where a full-string comparison would not. +// +// The transport is part of the key because TCP and QUIC routinely share a +// port number (Kubo defaults to 4001 for both) yet are distinct sockets. The +// IP is matched by value: an `/ip6zone` qualifier is dropped, so a zoneless +// config entry still matches the resolved interface address. +func listenEndpoint(m ma.Multiaddr) (string, bool) { + ip, err := manet.ToIP(m) + if err != nil || ip.IsUnspecified() { + return "", false + } + if port, err := m.ValueForProtocol(ma.P_TCP); err == nil { + return ip.String() + "/tcp/" + port, true + } + if port, err := m.ValueForProtocol(ma.P_UDP); err == nil { + return ip.String() + "/udp/" + port, true + } + return "", false +} + +// explicitListens returns the set of network endpoints (IP+port), keyed by +// listenEndpoint, that `Addresses.Swarm` binds to a specific interface. +// Wildcard listens (`/ip4/0.0.0.0`, `/ip6/::`) and entries without an IP +// component (`/dns...`) are skipped: they do not pin a single interface. +// +// A resolved listener counts as explicit when its endpoint is in this set. +// A wildcard listen expands to per-interface addresses whose IPs never +// appear here, so endpoint membership separates a deliberately-bound +// listener from an incidental wildcard expansion onto a filtered interface, +// even when the two share an IP (their transport or port differs). +// +// A `/tcp/0` (OS-assigned port) listen is stored with port "0", which no +// resolved listener reports, so it falls back to non-explicit (DEBUG). The +// reverse-proxy misconfiguration this routing exists to flag always pins a +// fixed port, so the best-effort gap costs nothing in practice. +func explicitListens(swarmListen []string) map[string]struct{} { + set := make(map[string]struct{}, len(swarmListen)) + for _, s := range swarmListen { + m, err := ma.NewMultiaddr(s) + if err != nil { + continue + } + if ep, ok := listenEndpoint(m); ok { + set[ep] = struct{}{} + } + } + return set +} + +// logDeadListenerFinding writes one log line per finding, naming the +// listener, the matching CIDR rule, and where to remove it from. The +// log level depends on the finding's Source and whether the operator +// explicitly bound the listener IP. See findDeadListeners. +func logDeadListenerFinding(f deadListenerFinding) { + switch { + case f.Source == deadListenerSourceAddrFilters && f.Explicit: + log.Errorf( + "Addresses.Swarm listener %q matches Swarm.AddrFilters rule %q, "+ + "so Kubo rejects every incoming connection to it. Remove %q "+ + "from Swarm.AddrFilters to allow connections to this listener.", + f.Listener, f.Rule, f.Rule, + ) + case f.Source == deadListenerSourceAddrFilters: + log.Debugf( + "Swarm.AddrFilters rule %q blocks resolved listener %q (from a "+ + "wildcard listen). Other interfaces unaffected.", + f.Rule, f.Listener, + ) + case f.Source == deadListenerSourceNoAnnounce: + log.Debugf( + "Addresses.NoAnnounce rule %q strips listener %q from "+ + "announcements (identify, DHT self-record).", + f.Rule, f.Listener, + ) + } +} + +// MonitorDeadListeners runs findDeadListeners at startup and on every +// EvtLocalAddressesUpdated. Listen addresses change at runtime (NAT +// mapping, new interface, AutoTLS cert), so a one-shot check would +// miss listeners that appear later. +// +// Findings are deduplicated against the previous run: a stable +// misconfiguration is logged once. +// +// If subscribing to the event bus fails, the runtime monitor is +// disabled and only the startup check runs. The check is diagnostic +// and must never abort node startup. +func MonitorDeadListeners(swarmListen, addrFilters, noAnnounce []string) func(fx.Lifecycle, host.Host) error { + return func(lc fx.Lifecycle, h host.Host) error { + seen := make(map[deadListenerFinding]struct{}) + runCheck := func() { + listenAddrs, err := h.Network().InterfaceListenAddresses() + if err != nil { + log.Warnf("dead-listener check: read InterfaceListenAddresses: %s", err) + return + } + next := make(map[deadListenerFinding]struct{}) + for _, f := range findDeadListeners(listenAddrs, swarmListen, addrFilters, noAnnounce) { + next[f] = struct{}{} + if _, ok := seen[f]; ok { + continue + } + logDeadListenerFinding(f) + } + seen = next + } + + // Startup check, always runs even if the runtime monitor below + // cannot be wired up. + runCheck() + + sub, err := h.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated)) + if err != nil { + log.Errorf("dead-listener check: subscribe to EvtLocalAddressesUpdated failed (%s); runtime monitor disabled, startup check already ran", err) + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + lc.Append(fx.Hook{ + OnStop: func(_ context.Context) error { + cancel() + return nil + }, + }) + + go func() { + defer sub.Close() + for { + select { + case <-ctx.Done(): + return + case _, ok := <-sub.Out(): + if !ok { + return + } + runCheck() + } + } + }() + return nil + } +} + func makeAddrsFactory(announce []string, appendAnnounce []string, noAnnounce []string) (p2pbhost.AddrsFactory, error) { var err error // To assign to the slice in the for loop existing := make(map[string]bool) // To avoid duplicates @@ -88,6 +317,14 @@ func makeAddrsFactory(announce []string, appendAnnounce []string, noAnnounce []s var out []ma.Multiaddr for _, maddr := range addrs { + // Drop empty multiaddrs. Since go-multiaddr v0.15 made + // Multiaddr a slice type, a zero-value Multiaddr encodes to + // zero bytes and would otherwise reach the host's signed peer + // record, where peers render it as "/" and reject the address. + // See https://github.com/libp2p/js-libp2p/issues/3478#issuecomment-4322093929 + if len(maddr) == 0 { + continue + } // check for exact matches ok := noAnnAddrs[string(maddr.Bytes())] // check for /ipcidr matches diff --git a/core/node/libp2p/addrs_test.go b/core/node/libp2p/addrs_test.go new file mode 100644 index 00000000000..f18da91b2ca --- /dev/null +++ b/core/node/libp2p/addrs_test.go @@ -0,0 +1,295 @@ +package libp2p + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +// mustMultiaddrs parses a list of multiaddr strings or fails the test. +func mustMultiaddrs(t *testing.T, addrs ...string) []ma.Multiaddr { + t.Helper() + out := make([]ma.Multiaddr, 0, len(addrs)) + for _, s := range addrs { + m, err := ma.NewMultiaddr(s) + require.NoError(t, err, "parse %q", s) + out = append(out, m) + } + return out +} + +func TestFindDeadListeners(t *testing.T) { + cases := []struct { + name string + listenAddrs []ma.Multiaddr + swarmListen []string + addrFilters []string + noAnnounce []string + want []deadListenerFinding + }{ + { + name: "empty config produces no findings", + listenAddrs: mustMultiaddrs(t, "/ip4/192.168.1.5/tcp/4001"), + swarmListen: []string{"/ip4/192.168.1.5/tcp/4001"}, + }, + { + name: "explicit loopback listen with loopback AddrFilters: explicit AddrFilters finding (reverse-proxy gotcha)", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + name: "wildcard listen resolves to loopback: non-explicit AddrFilters finding", + listenAddrs: mustMultiaddrs(t, + "/ip4/127.0.0.1/tcp/4001", + "/ip4/1.2.3.4/tcp/4001", + ), + swarmListen: []string{"/ip4/0.0.0.0/tcp/4001"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/4001", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: false}, + }, + }, + { + name: "explicit loopback listen with loopback NoAnnounce: non-explicit NoAnnounce finding (debug trace)", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + noAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceNoAnnounce, Explicit: true}, + }, + }, + { + name: "wildcard listen resolves to loopback with NoAnnounce: non-explicit NoAnnounce finding", + listenAddrs: mustMultiaddrs(t, + "/ip4/127.0.0.1/tcp/4001", + "/ip4/1.2.3.4/tcp/4001", + ), + swarmListen: []string{"/ip4/0.0.0.0/tcp/4001"}, + noAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/4001", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceNoAnnounce, Explicit: false}, + }, + }, + { + name: "loopback in both AddrFilters and NoAnnounce on explicit listen: one finding per source", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + noAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceNoAnnounce, Explicit: true}, + }, + }, + { + name: "wildcard IPv6 listen resolves to ULA with `fc00::/7` AddrFilters: non-explicit AddrFilters finding", + listenAddrs: mustMultiaddrs(t, + "/ip6/fd7d:54ce:fe4::1/tcp/4001", + "/ip6/2604:2dc0:200:484::1/tcp/4001", + ), + swarmListen: []string{"/ip6/::/tcp/4001"}, + addrFilters: []string{"/ip6/fc00::/ipcidr/7"}, + want: []deadListenerFinding{ + {Listener: "/ip6/fd7d:54ce:fe4::1/tcp/4001", Rule: "/ip6/fc00::/ipcidr/7", Source: deadListenerSourceAddrFilters, Explicit: false}, + }, + }, + { + name: "explicit Docker bridge listen with matching private CIDR: explicit AddrFilters finding", + listenAddrs: mustMultiaddrs(t, "/ip4/172.17.0.1/tcp/4001"), + swarmListen: []string{"/ip4/172.17.0.1/tcp/4001"}, + addrFilters: []string{"/ip4/172.16.0.0/ipcidr/12"}, + want: []deadListenerFinding{ + {Listener: "/ip4/172.17.0.1/tcp/4001", Rule: "/ip4/172.16.0.0/ipcidr/12", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + name: "globally-routable IPv6 explicit listen is not matched by `::/3`", + listenAddrs: mustMultiaddrs(t, "/ip6/2604:2dc0:200:484::1/tcp/4001"), + swarmListen: []string{"/ip6/2604:2dc0:200:484::1/tcp/4001"}, + addrFilters: []string{"/ip6/::/ipcidr/3"}, + }, + { + name: "DNS listener has no IP component: no finding", + listenAddrs: mustMultiaddrs(t, "/dns/example.com/tcp/443/wss"), + swarmListen: []string{"/dns/example.com/tcp/443/wss"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + }, + { + name: "exact-match NoAnnounce multiaddr is not a CIDR: skipped", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + noAnnounce: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + }, + { + name: "malformed AddrFilters entry: skipped, valid filters still match", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/ws"}, + addrFilters: []string{"garbage", "/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + name: "server-profile bootstrapper mix: explicit reverse-proxy listen flagged ERROR, wildcard-resolved interfaces DEBUG", + listenAddrs: mustMultiaddrs(t, + "/ip4/147.135.44.132/tcp/4001", + "/ip4/127.0.0.1/tcp/4001", // loopback expansion of /ip4/0.0.0.0 + "/ip4/127.0.0.1/tcp/8081/ws", + "/ip6/2604:2dc0:200:484::1/tcp/4001", + "/ip6/::1/tcp/4001", + ), + swarmListen: []string{ + "/ip4/0.0.0.0/tcp/4001", + "/ip4/127.0.0.1/tcp/8081/ws", + "/ip6/::/tcp/4001", + }, + addrFilters: []string{ + "/ip4/127.0.0.0/ipcidr/8", + "/ip6/::/ipcidr/3", + }, + noAnnounce: []string{ + "/ip4/127.0.0.0/ipcidr/8", + "/ip6/::/ipcidr/3", + }, + // The /ip4/127.0.0.1/tcp/4001 loopback shares its IP with the + // explicit /ws listener but came from the /ip4/0.0.0.0 wildcard, + // so it stays non-explicit (DEBUG); only the /ws listener is ERROR. + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + {Listener: "/ip4/127.0.0.1/tcp/4001", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: false}, + {Listener: "/ip6/::1/tcp/4001", Rule: "/ip6/::/ipcidr/3", Source: deadListenerSourceAddrFilters, Explicit: false}, + {Listener: "/ip4/127.0.0.1/tcp/8081/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceNoAnnounce, Explicit: true}, + {Listener: "/ip4/127.0.0.1/tcp/4001", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceNoAnnounce, Explicit: false}, + {Listener: "/ip6/::1/tcp/4001", Rule: "/ip6/::/ipcidr/3", Source: deadListenerSourceNoAnnounce, Explicit: false}, + }, + }, + // A listener is reported under a different multiaddr than its + // Addresses.Swarm entry once a transport rewrites trailing + // components. Matching on IP+port keeps the explicit listener + // recognizable across these rewrites. + { + // A WebTransport listener reports the current and next cert + // hashes, so InterfaceListenAddresses surfaces two /certhash + // components the config entry never had. + name: "explicit WebTransport listen reported with /certhash: explicit AddrFilters finding", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/uEiAkH5a4DPGKUuOBjYw0CgwjLa2R_RF71v86aVxlqdKNOQ/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg"), + swarmListen: []string{"/ip4/127.0.0.1/udp/4001/quic-v1/webtransport"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/uEiAkH5a4DPGKUuOBjYw0CgwjLa2R_RF71v86aVxlqdKNOQ/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + // TCP and QUIC share port 4001 in stock Kubo. A pinned QUIC + // listener must not promote the TCP wildcard's expansion onto + // the same IP to ERROR: they are different sockets. + name: "pinned QUIC listener leaves same-port TCP wildcard expansion non-explicit", + listenAddrs: mustMultiaddrs(t, + "/ip4/172.17.0.1/tcp/4001", // from the /ip4/0.0.0.0/tcp/4001 wildcard + "/ip4/172.17.0.1/udp/4001/quic-v1", // the explicit QUIC listener + ), + swarmListen: []string{ + "/ip4/0.0.0.0/tcp/4001", + "/ip4/172.17.0.1/udp/4001/quic-v1", + }, + addrFilters: []string{"/ip4/172.16.0.0/ipcidr/12"}, + want: []deadListenerFinding{ + {Listener: "/ip4/172.17.0.1/tcp/4001", Rule: "/ip4/172.16.0.0/ipcidr/12", Source: deadListenerSourceAddrFilters, Explicit: false}, + {Listener: "/ip4/172.17.0.1/udp/4001/quic-v1", Rule: "/ip4/172.16.0.0/ipcidr/12", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + name: "explicit wss listen reported as /tls/ws: explicit AddrFilters finding", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/8081/tls/ws"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/8081/wss"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/8081/tls/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + // The wildcard expansion onto loopback (/tcp/4001) and the + // explicit reverse-proxy wss listener (/tcp/8081) share an IP + // but differ in port, so only the explicit port is ERROR. + name: "wildcard expansion shares loopback IP with explicit wss listener on another port", + listenAddrs: mustMultiaddrs(t, + "/ip4/127.0.0.1/tcp/4001", // from the /ip4/0.0.0.0 wildcard + "/ip4/127.0.0.1/tcp/8081/tls/ws", // the explicit wss listener, as reported + ), + swarmListen: []string{ + "/ip4/0.0.0.0/tcp/4001", + "/ip4/127.0.0.1/tcp/8081/wss", + }, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/4001", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: false}, + {Listener: "/ip4/127.0.0.1/tcp/8081/tls/ws", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + // Uppercase IPv6 in config plus a wss->/tls/ws rewrite at the + // listener: matching needs both IP canonicalization and the + // transport-independent endpoint key. + name: "explicit IPv6 wss listen configured in uppercase matches resolved lowercase /tls/ws", + listenAddrs: mustMultiaddrs(t, "/ip6/fd7d:54ce:fe4::1/tcp/8081/tls/ws"), + swarmListen: []string{"/ip6/FD7D:54CE:FE4::1/tcp/8081/wss"}, + addrFilters: []string{"/ip6/fc00::/ipcidr/7"}, + want: []deadListenerFinding{ + {Listener: "/ip6/fd7d:54ce:fe4::1/tcp/8081/tls/ws", Rule: "/ip6/fc00::/ipcidr/7", Source: deadListenerSourceAddrFilters, Explicit: true}, + }, + }, + { + // /tcp/0 binds an OS-assigned port that the config entry cannot + // name, so the listener cannot be matched back and stays DEBUG. + name: "explicit /tcp/0 listen resolves to an assigned port: falls back to non-explicit", + listenAddrs: mustMultiaddrs(t, "/ip4/127.0.0.1/tcp/54321"), + swarmListen: []string{"/ip4/127.0.0.1/tcp/0"}, + addrFilters: []string{"/ip4/127.0.0.0/ipcidr/8"}, + want: []deadListenerFinding{ + {Listener: "/ip4/127.0.0.1/tcp/54321", Rule: "/ip4/127.0.0.0/ipcidr/8", Source: deadListenerSourceAddrFilters, Explicit: false}, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := findDeadListeners(tc.listenAddrs, tc.swarmListen, tc.addrFilters, tc.noAnnounce) + require.ElementsMatch(t, tc.want, got) + }) + } +} + +// makeAddrsFactory must drop empty multiaddrs from the input list. +// A zero-component Multiaddr would otherwise reach the host's signed +// peer record and propagate to peers as "/" when they decode the wire +// bytes. +// +// See https://github.com/libp2p/js-libp2p/issues/3478#issuecomment-4322093929 +func TestMakeAddrsFactoryDropsEmptyMultiaddrs(t *testing.T) { + factory, err := makeAddrsFactory(nil, nil, nil) + if err != nil { + t.Fatal(err) + } + + good, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/4001") + if err != nil { + t.Fatal(err) + } + + in := []ma.Multiaddr{nil, good, {}, good} + out := factory(in) + + if len(out) != 2 { + t.Fatalf("expected 2 addrs after factory filter, got %d: %v", len(out), out) + } + for i, a := range out { + if len(a) == 0 { + t.Fatalf("factory returned an empty multiaddr at index %d", i) + } + } +} diff --git a/core/node/libp2p/host.go b/core/node/libp2p/host.go index 0cb85f454b9..44a37a2d475 100644 --- a/core/node/libp2p/host.go +++ b/core/node/libp2p/host.go @@ -13,6 +13,7 @@ import ( "github.com/ipfs/kubo/config" "github.com/ipfs/kubo/core/node/helpers" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" "go.uber.org/fx" @@ -104,7 +105,10 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (out P2PHo lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return out.Host.Close() + // Host.Close() does not accept a ctx and can block draining + // peer connections on busy nodes. CloseWithCtx returns when + // either the close finishes or the shutdown deadline expires. + return shutdown.CloseWithCtx(ctx, "libp2p-host", out.Host.Close) }, }) diff --git a/core/node/libp2p/peerstore.go b/core/node/libp2p/peerstore.go index b77637a34a3..1a50cff436c 100644 --- a/core/node/libp2p/peerstore.go +++ b/core/node/libp2p/peerstore.go @@ -3,6 +3,7 @@ package libp2p import ( "context" + "github.com/ipfs/kubo/core/shutdown" "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" "go.uber.org/fx" @@ -15,7 +16,7 @@ func Peerstore(lc fx.Lifecycle) (peerstore.Peerstore, error) { } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return pstore.Close() + return shutdown.CloseWithCtx(ctx, "peerstore", pstore.Close) }, }) diff --git a/core/node/libp2p/rcmgr.go b/core/node/libp2p/rcmgr.go index 491b3d21864..3c013438953 100644 --- a/core/node/libp2p/rcmgr.go +++ b/core/node/libp2p/rcmgr.go @@ -10,6 +10,7 @@ import ( "github.com/ipfs/kubo/config" "github.com/ipfs/kubo/core/node/helpers" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" logging "github.com/ipfs/go-log/v2" @@ -124,8 +125,8 @@ filled in with autocomputed defaults.`) opts.Opts = append(opts.Opts, libp2p.ResourceManager(manager)) lc.Append(fx.Hook{ - OnStop: func(_ context.Context) error { - return manager.Close() + OnStop: func(ctx context.Context) error { + return shutdown.CloseWithCtx(ctx, "resource-manager", manager.Close) }, }) diff --git a/core/node/libp2p/routing.go b/core/node/libp2p/routing.go index 305ca903380..167ab397337 100644 --- a/core/node/libp2p/routing.go +++ b/core/node/libp2p/routing.go @@ -24,6 +24,7 @@ import ( config "github.com/ipfs/kubo/config" "github.com/ipfs/kubo/core/node/helpers" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" irouting "github.com/ipfs/kubo/routing" ) @@ -71,7 +72,7 @@ func BaseRouting(cfg *config.Config) any { lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return dualDHT.Close() + return shutdown.CloseWithCtx(ctx, "dht-dual", dualDHT.Close) }, }) } @@ -82,7 +83,7 @@ func BaseRouting(cfg *config.Config) any { dualDHT = dht lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return dualDHT.Close() + return shutdown.CloseWithCtx(ctx, "dht-dual-composable", dualDHT.Close) }, }) break @@ -116,7 +117,7 @@ func BaseRouting(cfg *config.Config) any { lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return fullRTClient.Close() + return shutdown.CloseWithCtx(ctx, "dht-fullrt", fullRTClient.Close) }, }) @@ -337,10 +338,18 @@ func autoRelayFeeder(cfgPeering config.Peering, peerChan chan<- peer.AddrInfo) f }() lc.Append(fx.Hook{ - OnStop: func(_ context.Context) error { + OnStop: func(ctx context.Context) error { cancel() - <-done - return nil + // Wait for the feeder goroutine to exit but bound by + // the shutdown deadline so a stuck DHT call (downstream + // bug ignoring ctx) cannot block fx.Stop. Mirrors the + // reprovideAlert pattern in provider.go. + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } }, }) }) diff --git a/core/node/libp2p/routingopt.go b/core/node/libp2p/routingopt.go index e9ba01494c3..1d5d6c23220 100644 --- a/core/node/libp2p/routingopt.go +++ b/core/node/libp2p/routingopt.go @@ -340,7 +340,9 @@ var _ confirmedAddrsHost = (*basichost.BasicHost)(nil) // Resolution logic: // - If Announce is set, use it as a static override (no dynamic resolution). // - Otherwise, prefer AutoNAT V2 confirmed reachable addresses when available, -// falling back to static Swarm addresses (filtered by NoAnnounce). +// falling back to host.Addrs() which resolves 0.0.0.0/:: Swarm binds to +// concrete interface addresses and applies the libp2p AddrsFactory +// (Addresses.NoAnnounce CIDR filters and Swarm.AddrFilters). // - AppendAnnounce addresses are always appended. func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Multiaddr { appendAddrs := parseMultiaddrs(cfgAddrs.AppendAnnounce) @@ -351,23 +353,6 @@ func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Mult return func() []ma.Multiaddr { return staticAddrs } } - // Precompute fallback: Swarm minus NoAnnounce plus AppendAnnounce. - fallbackStrs := cfgAddrs.Swarm - if len(cfgAddrs.NoAnnounce) > 0 { - noAnnounce := map[string]struct{}{} - for _, a := range cfgAddrs.NoAnnounce { - noAnnounce[a] = struct{}{} - } - filtered := make([]string, 0, len(fallbackStrs)) - for _, a := range fallbackStrs { - if _, skip := noAnnounce[a]; !skip { - filtered = append(filtered, a) - } - } - fallbackStrs = filtered - } - fallbackResult := slices.Concat(parseMultiaddrs(fallbackStrs), appendAddrs) - ch, hasConfirmed := h.(confirmedAddrsHost) return func() []ma.Multiaddr { if hasConfirmed { @@ -379,7 +364,14 @@ func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Mult return slices.Concat(reachable, appendAddrs) } } - return fallbackResult + // Fallback: host.Addrs() resolves wildcard binds (0.0.0.0, ::) to + // concrete interface addresses and applies the libp2p AddrsFactory, + // which is where Addresses.NoAnnounce CIDR filtering happens. + hostAddrs := h.Addrs() + if len(appendAddrs) == 0 { + return hostAddrs + } + return slices.Concat(hostAddrs, appendAddrs) } } diff --git a/core/node/libp2p/routingopt_test.go b/core/node/libp2p/routingopt_test.go index 3342170a533..2681e6714d9 100644 --- a/core/node/libp2p/routingopt_test.go +++ b/core/node/libp2p/routingopt_test.go @@ -206,10 +206,12 @@ func TestEndpointCapabilitiesReadWriteLogic(t *testing.T) { } // stubHost is a minimal host.Host stub for testing httpRouterAddrFunc. -// Only the methods checked via type assertion (confirmedAddrsHost) matter; -// all other methods panic if called. +// reachable mocks ConfirmedAddrs (AutoNAT V2 result); hostAddrs mocks +// Addrs(), which in a real host returns wildcard-resolved interface +// addresses after the AddrsFactory filters (NoAnnounce/AddrFilters). type stubHost struct { reachable []ma.Multiaddr + hostAddrs []ma.Multiaddr } func (h *stubHost) ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multiaddr) { @@ -217,7 +219,7 @@ func (h *stubHost) ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multia } func (h *stubHost) ID() peer.ID { panic("unused") } -func (h *stubHost) Addrs() []ma.Multiaddr { panic("unused") } +func (h *stubHost) Addrs() []ma.Multiaddr { return h.hostAddrs } func (h *stubHost) Peerstore() peerstore.Peerstore { panic("unused") } func (h *stubHost) Network() network.Network { panic("unused") } func (h *stubHost) Mux() protocol.Switch { panic("unused") } @@ -235,44 +237,65 @@ func (h *stubHost) ConnManager() connmgr.ConnManager { panic("unused") } func (h *stubHost) EventBus() event.Bus { panic("unused") } func TestHttpRouterAddrFunc(t *testing.T) { + // hostAddrs simulates what host.Addrs() returns in a running daemon: + // wildcard Swarm binds resolved to concrete interfaces, with the + // libp2p AddrsFactory (NoAnnounce/AddrFilters) already applied. + resolvedAddrs := []string{ + "/ip4/192.168.1.10/tcp/4001", + "/ip4/192.168.1.10/udp/4001/quic-v1", + } + tests := []struct { name string reachable []string // autonat confirmed addrs (nil = none) + hostAddrs []string // host.Addrs() output (nil = none) cfg config.Addresses want []string }{ { - name: "prefers autonat confirmed reachable addrs over swarm fallback", + name: "prefers autonat confirmed reachable addrs over host.Addrs fallback", reachable: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"}, + hostAddrs: resolvedAddrs, cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}}, want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"}, }, { - name: "falls back to swarm when autonat has no confirmed addrs", - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}}, - want: []string{"/ip4/0.0.0.0/tcp/4001"}, + name: "falls back to host.Addrs when autonat has no confirmed addrs", + hostAddrs: resolvedAddrs, + cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}}, + want: resolvedAddrs, }, { - name: "Announce overrides autonat and swarm", + name: "Announce overrides autonat and host.Addrs", reachable: []string{"/ip4/1.2.3.4/tcp/4001"}, + hostAddrs: resolvedAddrs, cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, Announce: []string{"/ip4/5.6.7.8/tcp/4001"}}, want: []string{"/ip4/5.6.7.8/tcp/4001"}, }, { name: "AppendAnnounce added to autonat addrs", reachable: []string{"/ip4/1.2.3.4/tcp/4001"}, + hostAddrs: resolvedAddrs, cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}}, want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/10.0.0.1/tcp/4001"}, }, { - name: "AppendAnnounce added to swarm fallback", - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}}, - want: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/10.0.0.1/tcp/4001"}, + name: "AppendAnnounce added to host.Addrs fallback", + hostAddrs: resolvedAddrs, + cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}}, + want: append(append([]string{}, resolvedAddrs...), "/ip4/10.0.0.1/tcp/4001"), }, { - name: "NoAnnounce filters swarm fallback", - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}, NoAnnounce: []string{"/ip4/0.0.0.0/tcp/4001"}}, - want: []string{"/ip4/0.0.0.0/udp/4001/quic-v1"}, + // NoAnnounce (including server profile CIDR ranges) is applied by the + // libp2p AddrsFactory before host.Addrs() returns, so httpRouterAddrFunc + // itself performs no filtering on the fallback. + name: "NoAnnounce filtering happens upstream in host.Addrs", + hostAddrs: []string{"/ip4/192.168.1.10/tcp/4001"}, // already filtered by addrFactory + cfg: config.Addresses{ + Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, + NoAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"}, + }, + want: []string{"/ip4/192.168.1.10/tcp/4001"}, }, { name: "AppendAnnounce added to Announce", @@ -282,7 +305,10 @@ func TestHttpRouterAddrFunc(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - h := &stubHost{reachable: parseMultiaddrs(tt.reachable)} + h := &stubHost{ + reachable: parseMultiaddrs(tt.reachable), + hostAddrs: parseMultiaddrs(tt.hostAddrs), + } fn := httpRouterAddrFunc(h, tt.cfg) assert.Equal(t, parseMultiaddrs(tt.want), fn()) }) diff --git a/core/node/peering.go b/core/node/peering.go index d6b56383526..16b7bf1a1cc 100644 --- a/core/node/peering.go +++ b/core/node/peering.go @@ -4,6 +4,7 @@ import ( "context" "github.com/ipfs/boxo/peering" + "github.com/ipfs/kubo/core/shutdown" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "go.uber.org/fx" @@ -17,9 +18,11 @@ func Peering(lc fx.Lifecycle, host host.Host) *peering.PeeringService { OnStart: func(context.Context) error { return ps.Start() }, - OnStop: func(context.Context) error { - ps.Stop() - return nil + OnStop: func(ctx context.Context) error { + return shutdown.CloseWithCtx(ctx, "peering", func() error { + ps.Stop() + return nil + }) }, }) return ps diff --git a/core/node/provider.go b/core/node/provider.go index 36179b425bf..6c87d8af6bc 100644 --- a/core/node/provider.go +++ b/core/node/provider.go @@ -23,6 +23,7 @@ import ( "github.com/ipfs/go-datastore/query" log "github.com/ipfs/go-log/v2" "github.com/ipfs/kubo/config" + "github.com/ipfs/kubo/core/shutdown" "github.com/ipfs/kubo/repo" "github.com/ipfs/kubo/repo/fsrepo" irouting "github.com/ipfs/kubo/routing" @@ -321,7 +322,7 @@ Learn more: https://github.com/ipfs/kubo/blob/master/docs/config.md#provide`, } lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - return sys.Close() + return shutdown.CloseWithCtx(ctx, "legacy-provider", sys.Close) }, }) @@ -599,6 +600,11 @@ func purgeOrphanedKeystoreData(ctx context.Context, ds datastore.Batching) error func SweepingProviderOpt(cfg *config.Config) fx.Option { reprovideInterval := cfg.Provide.DHT.Interval.WithDefault(config.DefaultProvideDHTInterval) + // noScheduleMode is true when the user disabled the periodic reprovide + // schedule (Provide.DHT.Interval=0). In this mode the keystore is + // inert: kad-dht's burst-only path (ProvideOnce, StartProviding) does + // not Put or Delete keys, and no reprovide loop runs to read them. + noScheduleMode := reprovideInterval == 0 type providerInput struct { fx.In DHT routing.Routing `name:"dhtc"` @@ -625,9 +631,9 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { if err := validateKeystoreSuffix(suffix); err != nil { return nil, err } - // When no datastore spec is configured (e.g., test/mock repos), - // fall back to an in-memory datastore. - if rootSpec == nil { + // In-memory datastore in no-schedule mode (keystore is inert) + // or when no datastore spec is configured (test/mock repos). + if noScheduleMode || rootSpec == nil { return datastore.NewMapDatastore(), nil } if err := os.MkdirAll(keystoreBasePath, 0o755); err != nil { @@ -645,10 +651,25 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { if err := validateKeystoreSuffix(suffix); err != nil { return err } + if noScheduleMode { + return nil + } providerLog.Infow("provider keystore: removing datastore from disk", "suffix", suffix, "path", filepath.Join(keystoreBasePath, suffix)) return os.RemoveAll(filepath.Join(keystoreBasePath, suffix)) } + // In no-schedule mode the on-disk keystore is never used. If a + // previous run was in schedule mode it may have left data behind; + // purge it once on startup to free disk. + if noScheduleMode { + if _, statErr := os.Stat(keystoreBasePath); statErr == nil { + providerLog.Infow("provider keystore: purging on-disk data (Provide.DHT.Interval=0)", "path", keystoreBasePath) + if rmErr := os.RemoveAll(keystoreBasePath); rmErr != nil { + providerLog.Warnw("provider keystore: purge failed", "path", keystoreBasePath, "err", rmErr) + } + } + } + // One-time cleanup of stale keystore data left by older Kubo in the // shared repo datastore under /provider/keystore/. New code stores // bulk key data in separate filesystem datastores under @@ -745,6 +766,7 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { ddhtprovider.WithMaxReprovideDelay(time.Hour), ddhtprovider.WithOfflineDelay(cfg.Provide.DHT.OfflineDelay.WithDefault(config.DefaultProvideDHTOfflineDelay)), ddhtprovider.WithConnectivityCheckOnlineInterval(1*time.Minute), + ddhtprovider.WithSendProviderRecordTimeout(cfg.Provide.DHT.SendProviderRecordTimeout.WithDefault(config.DefaultProvideDHTSendProviderRecordTimeout)), ddhtprovider.WithMaxWorkers(int(cfg.Provide.DHT.MaxWorkers.WithDefault(config.DefaultProvideDHTMaxWorkers))), ddhtprovider.WithDedicatedPeriodicWorkers(int(cfg.Provide.DHT.DedicatedPeriodicWorkers.WithDefault(config.DefaultProvideDHTDedicatedPeriodicWorkers))), @@ -790,6 +812,7 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { dhtprovider.WithMaxReprovideDelay(time.Hour), dhtprovider.WithOfflineDelay(cfg.Provide.DHT.OfflineDelay.WithDefault(config.DefaultProvideDHTOfflineDelay)), dhtprovider.WithConnectivityCheckOnlineInterval(1 * time.Minute), + dhtprovider.WithSendProviderRecordTimeout(cfg.Provide.DHT.SendProviderRecordTimeout.WithDefault(config.DefaultProvideDHTSendProviderRecordTimeout)), dhtprovider.WithMaxWorkers(int(cfg.Provide.DHT.MaxWorkers.WithDefault(config.DefaultProvideDHTMaxWorkers))), dhtprovider.WithDedicatedPeriodicWorkers(int(cfg.Provide.DHT.DedicatedPeriodicWorkers.WithDefault(config.DefaultProvideDHTDedicatedPeriodicWorkers))), @@ -817,6 +840,12 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { if _, ok := in.Provider.(*NoopProvider); ok { return } + // In no-schedule mode no reprovide loop runs, so there is no + // reader for the keystore and no need to sync it. The zero + // interval would also panic the periodic sync ticker. + if noScheduleMode { + return + } var ( cancel context.CancelFunc @@ -851,11 +880,13 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { strategy := cfg.Provide.Strategy.WithDefault(config.DefaultProvideStrategy) providerLog.Infow("provider keystore sync started", "strategy", strategy) if err := syncKeystore(ctx); err != nil { - // ErrClosed means the keystore was closed by the shutdown - // hook while this goroutine was still in flight: the - // OnStart ctx is not cancelled yet, so we classify the - // failure as shutdown explicitly. - if ctx.Err() != nil || errors.Is(err, keystore.ErrClosed) { + // Shutdown can race ahead of ctx.Err() becoming + // visible here: ResetCids returns ctx.Err() + // straight from its own ctx-done select, and the + // keystore can also close mid-sync (ErrClosed) + // before the OnStart ctx is cancelled. Classify + // both as shutdown. + if ctx.Err() != nil || errors.Is(err, context.Canceled) || errors.Is(err, keystore.ErrClosed) { providerLog.Debugw("provider keystore sync interrupted by shutdown", "err", err, "strategy", strategy) } else { providerLog.Errorw("provider keystore sync failed", "err", err, "strategy", strategy) @@ -879,7 +910,11 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { return case <-ticker.C: if err := syncKeystore(gcCtx); err != nil { - if gcCtx.Err() != nil || errors.Is(err, keystore.ErrClosed) { + // See classifier note on the startup-sync + // branch above: context.Canceled can + // arrive ahead of gcCtx.Err() becoming + // visible to this goroutine. + if gcCtx.Err() != nil || errors.Is(err, context.Canceled) || errors.Is(err, keystore.ErrClosed) { providerLog.Debugw("provider keystore sync interrupted by shutdown", "err", err) } else { providerLog.Errorw("provider keystore sync failed", "err", err) @@ -927,14 +962,15 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { - // Close provider first - waits for all worker goroutines to exit. - // This ensures no code can access keystore after this returns. - if err := in.Provider.Close(); err != nil { + // Close provider first; waits for all worker goroutines + // to exit so nothing can access the keystore after this + // returns. If ctx fires before provider drains, the + // keystore close below sees an expired ctx and returns + // immediately; the watchdog is the ultimate backstop. + if err := shutdown.CloseWithCtx(ctx, "dht-provider", in.Provider.Close); err != nil { providerLog.Errorw("error closing provider during shutdown", "error", err) } - - // Close keystore - safe now, provider is fully shut down - return in.Keystore.Close() + return shutdown.CloseWithCtx(ctx, "keystore", in.Keystore.Close) }, }) }) @@ -995,7 +1031,16 @@ func SweepingProviderOpt(cfg *config.Config) fx.Option { case <-ticker.C: } - stats := prov.Stats() + statsCtx, statsCancel := context.WithTimeout(gcCtx, time.Minute) + stats, err := prov.Stats(statsCtx) + statsCancel() + if err != nil { + if gcCtx.Err() != nil { + return + } + providerLog.Debugw("provider stats unavailable for reprovide alert", "err", err) + continue + } queuedWorkers = stats.Workers.QueuedPeriodic > 0 queueSize = int64(stats.Queues.PendingRegionReprovides) diff --git a/core/shutdown/close.go b/core/shutdown/close.go new file mode 100644 index 00000000000..fde603838c1 --- /dev/null +++ b/core/shutdown/close.go @@ -0,0 +1,31 @@ +package shutdown + +import ( + "context" + "fmt" + "time" + + logging "github.com/ipfs/go-log/v2" +) + +var closeLog = logging.Logger("shutdown") + +// CloseWithCtx runs close in a goroutine and returns when it finishes or +// when ctx is done, whichever comes first. If ctx fires before close +// returns, the goroutine is leaked intentionally; the process is about to +// exit, so the leak is bounded by process lifetime. Logs at ERROR which +// subsystem failed to close in time so operators see it in journal/docker +// logs. +func CloseWithCtx(ctx context.Context, name string, close func() error) error { + done := make(chan error, 1) + start := time.Now() + go func() { done <- close() }() + select { + case err := <-done: + return err + case <-ctx.Done(): + closeLog.Errorf("subsystem %q failed to close within shutdown deadline (after %s): %s", + name, time.Since(start), ctx.Err()) + return fmt.Errorf("%s close: %w", name, ctx.Err()) + } +} diff --git a/core/shutdown/close_test.go b/core/shutdown/close_test.go new file mode 100644 index 00000000000..19d7e02f712 --- /dev/null +++ b/core/shutdown/close_test.go @@ -0,0 +1,63 @@ +package shutdown + +import ( + "context" + "errors" + "testing" + "testing/synctest" + "time" +) + +const ( + // testFinishDeadline is the ctx deadline for the happy-path tests: + // long enough that the close callback returns first. + testFinishDeadline = time.Second + // testTimeoutDeadline is the ctx deadline for the timeout test. Any + // positive value works because the test runs under synctest's fake + // clock; the choice only affects the exact-elapsed assertion below. + testTimeoutDeadline = 50 * time.Millisecond +) + +func TestCloseWithCtx_finishesBeforeDeadline(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testFinishDeadline) + defer cancel() + if err := CloseWithCtx(ctx, "fast", func() error { return nil }); err != nil { + t.Fatal(err) + } +} + +func TestCloseWithCtx_propagatesCloseError(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testFinishDeadline) + defer cancel() + want := errors.New("close failed") + err := CloseWithCtx(ctx, "bad", func() error { return want }) + if !errors.Is(err, want) { + t.Fatalf("want %v, got %v", want, err) + } +} + +func TestCloseWithCtx_timesOut(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testTimeoutDeadline) + defer cancel() + // release lets the simulated close exit after we've asserted on + // CloseWithCtx. Without it, synctest panics with "blocked + // goroutines remain" because production-side CloseWithCtx + // intentionally leaks the goroutine when the deadline fires. + release := make(chan struct{}) + start := time.Now() + err := CloseWithCtx(ctx, "slow", func() error { + <-release + return nil + }) + if elapsed := time.Since(start); elapsed != testTimeoutDeadline { + t.Fatalf("want elapsed == %s, got %s", testTimeoutDeadline, elapsed) + } + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("want DeadlineExceeded, got %v", err) + } + close(release) + }) +} diff --git a/core/shutdown/state.go b/core/shutdown/state.go new file mode 100644 index 00000000000..05f5a1d63a5 --- /dev/null +++ b/core/shutdown/state.go @@ -0,0 +1,35 @@ +// Package shutdown tracks daemon-wide graceful shutdown state. The daemon +// command marks shutdown started when SIGTERM/SIGINT is received; the +// "ipfs diag healthy" subcommand checks this state for Dockerfile +// HEALTHCHECK and other monitoring. +package shutdown + +import ( + "sync/atomic" + "time" +) + +// startedAt holds the unix-nano timestamp when shutdown began. +// Zero means shutdown has not started. +var startedAt atomic.Int64 + +// MarkStarted records that graceful shutdown has begun. Safe to call +// multiple times concurrently; only the first call wins. Returns true on +// the first call, false on subsequent calls. +func MarkStarted() bool { + return startedAt.CompareAndSwap(0, time.Now().UnixNano()) +} + +// StartedAt returns when shutdown began, or the zero time if not started. +func StartedAt() time.Time { + n := startedAt.Load() + if n == 0 { + return time.Time{} + } + return time.Unix(0, n) +} + +// InProgress reports whether shutdown has been initiated. +func InProgress() bool { + return startedAt.Load() != 0 +} diff --git a/core/shutdown/state_test.go b/core/shutdown/state_test.go new file mode 100644 index 00000000000..871f2debba4 --- /dev/null +++ b/core/shutdown/state_test.go @@ -0,0 +1,77 @@ +package shutdown + +import ( + "sync/atomic" + "testing" + "time" +) + +// resetForTest clears the package-level state. Tests in this file mutate +// global state, so they cannot run in parallel. +func resetForTest(t *testing.T) { + t.Helper() + startedAt.Store(0) +} + +func TestInProgressInitiallyFalse(t *testing.T) { + resetForTest(t) + if InProgress() { + t.Fatal("InProgress() should be false before MarkStarted") + } + if !StartedAt().IsZero() { + t.Fatal("StartedAt() should be zero time before MarkStarted") + } +} + +func TestMarkStartedFirstCallWins(t *testing.T) { + resetForTest(t) + if !MarkStarted() { + t.Fatal("first MarkStarted() should return true") + } + if MarkStarted() { + t.Fatal("second MarkStarted() should return false") + } + if !InProgress() { + t.Fatal("InProgress() should be true after MarkStarted") + } + if StartedAt().IsZero() { + t.Fatal("StartedAt() should be non-zero after MarkStarted") + } +} + +func TestMarkStartedPreservesFirstTimestamp(t *testing.T) { + resetForTest(t) + MarkStarted() + first := StartedAt() + // Sleep is intentional: it forces time.Now() to advance between the + // two MarkStarted calls so a regression that replaces the CAS with a + // plain Store would change StartedAt() and fail the assertion below. + // Without the gap, both calls could land in the same nanosecond on + // coarse-resolution clocks and mask the bug. + time.Sleep(2 * time.Millisecond) + MarkStarted() // second call must not overwrite + if !StartedAt().Equal(first) { + t.Fatalf("StartedAt() changed after second MarkStarted: %v != %v", StartedAt(), first) + } +} + +func TestMarkStartedConcurrent(t *testing.T) { + resetForTest(t) + const goroutines = 64 + var winners atomic.Int32 + done := make(chan struct{}) + for range goroutines { + go func() { + if MarkStarted() { + winners.Add(1) + } + done <- struct{}{} + }() + } + for range goroutines { + <-done + } + if got := winners.Load(); got != 1 { + t.Fatalf("expected exactly 1 winner across %d goroutines, got %d", goroutines, got) + } +} diff --git a/docs/README.md b/docs/README.md index 348e9d7359b..2cf1a071c28 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,6 +47,7 @@ If you're experiencing an issue with IPFS, please [file an issue](https://github ## Production - [Reverse proxy setup](production/reverse-proxy.md) +- [Firewall setup (ufw)](production/firewall.md) ## Specifications diff --git a/docs/changelogs/v0.41.md b/docs/changelogs/v0.41.md index e4ac4213e39..4350133cdec 100644 --- a/docs/changelogs/v0.41.md +++ b/docs/changelogs/v0.41.md @@ -229,6 +229,9 @@ The command is idempotent. See the [`server` profile docs](https://github.com/ip > [!WARNING] > The `server` profile disables local peer discovery ([`Discovery.MDNS`](https://github.com/ipfs/kubo/blob/master/docs/config.md#discoverymdns) off, loopback filtered), so co-located daemons on the same host and peers on the same LAN will no longer find each other automatically. Apply only on public-internet nodes where that is intended. +> [!CAUTION] +> If a manually configured libp2p listener (for example `/ip4/127.0.0.1/tcp/.../ws` fronted by a local nginx or Caddy reverse proxy) terminates inbound on `127.0.0.1`, the new loopback entry in [`Swarm.AddrFilters`](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmaddrfilters) makes the gater RST every inbound from the proxy before the libp2p handshake. Remove `/ip4/127.0.0.0/ipcidr/8` (and `/ip6/::1/ipcidr/128`, `/ip6/::/ipcidr/3` if the proxy uses IPv6 loopback) from `Swarm.AddrFilters` only; keep them in [`Addresses.NoAnnounce`](https://github.com/ipfs/kubo/blob/master/docs/config.md#addressesnoannounce) so the loopback addresses are still stripped from identify and DHT records. + #### 🐹 Go 1.26, Once More with Feeling Kubo first shipped with [Go 1.26](https://go.dev/doc/go1.26) in v0.40.0, but [v0.40.1](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.40.md#v0401) had to downgrade to Go 1.25 because of a Windows crash in Go's overlapped I/O layer ([#11214](https://github.com/ipfs/kubo/issues/11214)). Go 1.26.2 fixes that regression upstream ([golang/go#78041](https://github.com/golang/go/issues/78041)), so Kubo is back on Go 1.26 across all platforms. diff --git a/docs/changelogs/v0.42.md b/docs/changelogs/v0.42.md index 2c05134f19f..bcb1a033f7c 100644 --- a/docs/changelogs/v0.42.md +++ b/docs/changelogs/v0.42.md @@ -10,7 +10,16 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. - [Overview](#overview) - [🔦 Highlights](#-highlights) + - [🎯 Announce CIDs on demand with `ipfs provide once`](#-announce-cids-on-demand-with-ipfs-provide-once) + - [🧩 Export and import partial CARs with `--local-only`](#-export-and-import-partial-cars-with---local-only) + - [⚙️ `Provide.DHT.Interval=0` no longer disables providing](#%EF%B8%8F-providedhtinterval0-no-longer-disables-providing) - [🐛 Fixed pin operations hanging under pinned reprovide strategies](#-fixed-pin-operations-hanging-under-pinned-reprovide-strategies) + - [🐛 Smoother first-run upgrades from very old repos](#-smoother-first-run-upgrades-from-very-old-repos) + - [🐛 Reliable shutdown and container health checks](#-reliable-shutdown-and-container-health-checks) + - [🚨 ERROR log for explicit listeners blocked by `Swarm.AddrFilters`](#-error-log-for-explicit-listeners-blocked-by-swarmaddrfilters) + - [📊 OpenTelemetry: scope info now exposed as labels](#-opentelemetry-scope-info-now-exposed-as-labels) + - [🔧 Cleaner progress bars](#-cleaner-progress-bars) + - [📦️ Dependency updates](#-dependency-updates) - [📝 Changelog](#-changelog) - [👨‍👩‍👧‍👦 Contributors](#-contributors) @@ -18,12 +27,259 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. ### 🔦 Highlights +#### 🎯 Announce CIDs on demand with `ipfs provide once` + +`ipfs provide once ...` announces CIDs to the routing system immediately, without waiting for the next scheduled reprovide. Use it when you want fine-grained control over when specific CIDs are announced. + +CIDs can be streamed in on stdin, so you can pipe arbitrarily large lists without growing daemon memory: + +```sh +# Announce every locally pinned CID. +ipfs pin ls | awk '{print $1}' | ipfs provide once +``` + +```sh +# Announce every block reachable from a root (here, ~350 GiB of Wikipedia). +ipfs refs -r bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze | ipfs provide once +``` + +In a terminal, the command shows a running count of queued CIDs. With `--enc=json` it emits one `{"Queued":""}` line per CID, so downstream scripts can consume events as they arrive. + +`ipfs routing provide` keeps working but is deprecated. See `ipfs provide once --help` for usage and migration notes. + +#### 🧩 Export and import partial CARs with `--local-only` + +`ipfs dag export --local-only` writes a CAR with only the blocks you have locally; any missing blocks (and their subtrees) are skipped instead of failing the export. `ipfs dag import --local-only` reads such a partial CAR without trying to pin its roots. + +This is useful when: + +- you want to share part of a DAG (for example an MFS tree) that is only partly cached locally +- you fetched a partial CAR from a gateway that supports [IPIP-0402](https://specs.ipfs.tech/ipips/ipip-0402/) and want to add what you got to your local store + +`--local-only` sets the matching companion flag automatically: on export it implies `--offline`; on import it implies `--pin-roots=false`. See `ipfs dag export --help` and `ipfs dag import --help` for details. + +#### ⚙️ `Provide.DHT.Interval=0` no longer disables providing + +`Provide.DHT.Interval=0` now disables only the periodic reprovide schedule. New CIDs still announce via fast-provide-root and `ipfs provide once`. To fully disable providing, set [`Provide.Enabled=false`](https://github.com/ipfs/kubo/blob/master/docs/config.md#provideenabled). + +> [!IMPORTANT] +> The daemon now refuses to start when `Provide.DHT.Interval=0` is set without an explicit [`Provide.Enabled`](https://github.com/ipfs/kubo/blob/master/docs/config.md#provideenabled). Operators upgrading from an earlier kubo version must opt in to one of the two semantics: +> +> - `Provide.Enabled=false` to fully disable providing (the previous behaviour of `Interval=0`). +> - `Provide.Enabled=true` to keep ad-hoc providing while skipping the periodic reprovide schedule. +> +> The startup error names both options. Pick the one that matches your intent. + #### 🐛 Fixed pin operations hanging under pinned reprovide strategies `ipfs pin ls`, `ipfs add`, and other pin-touching operations could block for hours on nodes running with [`Provide.Strategy`](https://github.com/ipfs/kubo/blob/master/docs/config.md#providestrategy) set to `pinned`, `roots`, or `pinned+mfs` (including `+unique` / `+entities` variants). The pin index held a read lock for the entire reprovide cycle, which on large pinsets takes many hours. Any pin operation issued during that window blocked, and further `pin ls` / `ipfs add` calls piled up behind it until the cycle finished. The pinner now snapshots the index under the read lock and releases it before the reprovider starts, so pin operations are no longer blocked by the reprovide cycle. The default `Provide.Strategy=all` was not affected. +#### 🐛 Smoother first-run upgrades from very old repos + +The one-time migration for repos from `go-ipfs` or Kubo older than v0.27 now retries across several gateways with HTTP timeouts, so a single slow or blocked gateway no longer hangs the daemon. Set [`Migration.DownloadSources`](https://github.com/ipfs/kubo/blob/master/docs/config.md#migrationdownloadsources) to use your own gateway list. + +#### 🐛 Reliable shutdown and container health checks + +Sending `SIGTERM` or `SIGINT` to kubo could leave the daemon stuck "half-shutdown": internal subsystems had stopped, but the process kept running and answering the RPC API. Docker and Kubernetes health checks reported the node as healthy while it had quietly stopped serving content. Recovery required a manual `docker restart`. Separately, the pinner could log a `pebble: closed` panic trace when the datastore closed before ongoing pin operations finished. + +What changed: + +- **Bounded shutdown.** A new [`Internal.ShutdownTimeout`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalshutdowntimeout) caps how long a stuck shutdown can run, so a zombie daemon recovers instead of staying half-alive. Routine shutdowns finish in seconds; this is a belt-and-suspenders ceiling against unknown bugs and future regressions. The 12-hour default is high enough that no real-world deployment hits it and low enough to recycle a stuck node well before its DHT provider records expire (22 hours). On expiry, the daemon logs which subsystem failed and exits with status `1`. Set `0` to disable. + +- **`ipfs diag healthy` subcommand.** Returns non-zero as soon as shutdown begins, even if the RPC API still answers. The kubo Docker image's `HEALTHCHECK` now uses it, so under `--restart=on-failure` or a Kubernetes liveness probe a half-shutdown daemon is recycled within seconds. + +- **Pinner shuts down cleanly.** The pinner cancels and waits for ongoing pin work before the datastore closes, removing the `pebble: closed` panic trace from shutdown logs. + +- **DHT provider deadlines.** `ipfs provide stat` now returns promptly when the caller cancels, instead of blocking on a slow keystore lookup (previously seen at over an hour). Each provider record sent to a peer is capped by [`Provide.DHT.SendProviderRecordTimeout`](https://github.com/ipfs/kubo/blob/master/docs/config.md#providedhtsendproviderrecordtimeout), so an unresponsive peer cannot stall a reprovide cycle. + +#### 🚨 ERROR log for explicit listeners blocked by `Swarm.AddrFilters` + +If you list a specific address in [`Addresses.Swarm`](https://github.com/ipfs/kubo/blob/master/docs/config.md#addressesswarm) and a rule in [`Swarm.AddrFilters`](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmaddrfilters) blocks it, no incoming connection reaches that listener. Kubo now logs one ERROR per such listener, naming the listener, the matching rule, and the field to remove the rule from. + +The common trigger: a `/ip4/127.0.0.1/tcp/.../ws` listener fronted by nginx or Caddy on a [`server`-profile](https://github.com/ipfs/kubo/blob/master/docs/config.md#server-profile) node. The profile adds `/ip4/127.0.0.0/ipcidr/8` to `Swarm.AddrFilters`, which rejects every proxy connection over loopback. See the [reverse-proxy override row](https://github.com/ipfs/kubo/blob/master/docs/config.md#overriding-specific-entries) for the fix. + +Wildcard listens (`/ip4/0.0.0.0`, `/ip6/::`) stay out of the ERROR log. Even if their interface expansion lands inside a filtered CIDR, the listener still accepts traffic on the interfaces outside that CIDR, so the filter is working as intended. These matches log at DEBUG instead, so you can still trace which interfaces an AddrFilters rule strips when you need to. + +[`Addresses.NoAnnounce`](https://github.com/ipfs/kubo/blob/master/docs/config.md#addressesnoannounce) matches also log at DEBUG. Hiding addresses there is the point of the field, but the log line helps when you ask "why isn't this interface in my identify or DHT records?" and the answer is a CIDR rule you forgot you set. + +#### 📊 OpenTelemetry: scope info now exposed as labels + +The Prometheus endpoint no longer emits the `otel_scope_info` metric. Each metric now carries `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels identifying the instrumentation library that produced it. Update dashboards or queries that read `otel_scope_info` to consume these labels instead. See [`docs/metrics.md`](https://github.com/ipfs/kubo/blob/master/docs/metrics.md) for details. + +#### 🔧 Cleaner progress bars + +`ipfs add`, `ipfs cat`, and `ipfs get` now hide their progress bar when stderr is piped or redirected, so a command like `ipfs add file 2> log.txt` no longer fills the log with progress-bar noise. Pass `--progress=true` to force the bar on, or `--progress=false` to hide it. + +`ipfs dag export` and `ipfs dag stat` now correctly recognize MSYS2 and Git Bash terminals on Windows. Previously the bar was suppressed there even when running interactively. + +#### 📦️ Dependency updates + +- update Go to [1.26.4](https://go.dev/doc/devel/release#go1.26.4) (incl. [1.26.3](https://go.dev/doc/devel/release#go1.26.3)) +- update `go-libp2p-pubsub` to [v0.16.0](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.16.0) +- update `go-libp2p-kad-dht` to [v0.40.0](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.40.0) (incl. [v0.39.2](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.39.2)) +- update `go-fuse/v2` to [v2.10.1](https://github.com/hanwen/go-fuse/releases/tag/v2.10.1) (incl. [v2.10.0](https://github.com/hanwen/go-fuse/releases/tag/v2.10)) +- update `cheggaaa/pb` to [v3.1.7](https://github.com/cheggaaa/pb/releases/tag/v3.1.7) +- update `boxo` to [v0.40.0](https://github.com/ipfs/boxo/releases/tag/v0.40.0) +- update `p2p-forge/client` to [v0.9.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.9.0) (incl. [v0.8.1](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.8.1)) + ### 📝 Changelog +
Full Changelog + +- github.com/ipfs/kubo: + - docs: move p2p-forge bump to v0.42 changelog + - docs: note AutoTLS local testing needs UPnP + - chore: go 1.26.4 (#11350) ([ipfs/kubo#11350](https://github.com/ipfs/kubo/pull/11350)) + - fix(libp2p): quieter dead-listener check (#11342) ([ipfs/kubo#11342](https://github.com/ipfs/kubo/pull/11342)) + - feat: derive AgentSuffix from build origin (#11341) ([ipfs/kubo#11341](https://github.com/ipfs/kubo/pull/11341)) + - chore(deps): bump p2p-forge to 0.9.0 (#11336) ([ipfs/kubo#11336](https://github.com/ipfs/kubo/pull/11336)) + - chore: set version to v0.42.0-rc1 + - chore: upgrade to Boxo v0.40.0 (#11338) ([ipfs/kubo#11338](https://github.com/ipfs/kubo/pull/11338)) + - feat(dag): add --local-only to dag export and import (#11229) ([ipfs/kubo#11229](https://github.com/ipfs/kubo/pull/11229)) + - chore: bump go-libp2p-kad-dht to v0.40.0 (#11334) ([ipfs/kubo#11334](https://github.com/ipfs/kubo/pull/11334)) + - refactor: migrate away from cheggaaa/pb v1 (#11322) ([ipfs/kubo#11322](https://github.com/ipfs/kubo/pull/11322)) + - docs: firewall (ufw) walkthrough for port 4001 (#11332) ([ipfs/kubo#11332](https://github.com/ipfs/kubo/pull/11332)) + - fix: migration fetcher robustness (#11305) ([ipfs/kubo#11305](https://github.com/ipfs/kubo/pull/11305)) + - chore: update boxo to remove io.Seeker from files.File (#11254) ([ipfs/kubo#11254](https://github.com/ipfs/kubo/pull/11254)) + - feat(provide): add `ipfs provide once` and support Interval=0 mode (#11321) ([ipfs/kubo#11321](https://github.com/ipfs/kubo/pull/11321)) + - feat: bound graceful shutdown, add diag healthy (#11329) ([ipfs/kubo#11329](https://github.com/ipfs/kubo/pull/11329)) + - feat(pinner): close pinner before repo on shutdown (#11296) ([ipfs/kubo#11296](https://github.com/ipfs/kubo/pull/11296)) + - chore(deps): bump go-libp2p-kad-dht to v0.39.2 (#11323) ([ipfs/kubo#11323](https://github.com/ipfs/kubo/pull/11323)) + - docs: clarify denylist scope vs routing layer (#11320) ([ipfs/kubo#11320](https://github.com/ipfs/kubo/pull/11320)) + - chore(deps): align deps with ipfs/boxo#1152 (#11313) ([ipfs/kubo#11313](https://github.com/ipfs/kubo/pull/11313)) + - feat(config): dead listener check (#11299) ([ipfs/kubo#11299](https://github.com/ipfs/kubo/pull/11299)) + - fix(libp2p): drop empty addrs in AddrsFactory (#11302) ([ipfs/kubo#11302](https://github.com/ipfs/kubo/pull/11302)) + - docs: clarify blockstore cache sizing and flatfs sharding (#11303) ([ipfs/kubo#11303](https://github.com/ipfs/kubo/pull/11303)) + - fix(test): mock GitHub API in TestUpdate (#11300) ([ipfs/kubo#11300](https://github.com/ipfs/kubo/pull/11300)) + - fix: resolve wildcard swarm in http provides (#11297) ([ipfs/kubo#11297](https://github.com/ipfs/kubo/pull/11297)) + - Merge release v0.41.0 ([ipfs/kubo#11295](https://github.com/ipfs/kubo/pull/11295)) + - fix(pins): snapshot index before emitting pins (#11290) ([ipfs/kubo#11290](https://github.com/ipfs/kubo/pull/11290)) + - Upgrade to Boxo v0.39.0 (#11294) ([ipfs/kubo#11294](https://github.com/ipfs/kubo/pull/11294)) + - fix(log): scope provide logs to "provider" subsystem (#11289) ([ipfs/kubo#11289](https://github.com/ipfs/kubo/pull/11289)) + - fix(defaultServerFilters): strip loopback and non-public (#11286) ([ipfs/kubo#11286](https://github.com/ipfs/kubo/pull/11286)) + - chore: bump p2p-forge to v0.8.0 (#11285) ([ipfs/kubo#11285](https://github.com/ipfs/kubo/pull/11285)) + - fix: queryevent addrinfo race in kad-dht (#11288) ([ipfs/kubo#11288](https://github.com/ipfs/kubo/pull/11288)) + - fix(examples): avoid bitswap race, use ed25519 (#11282) ([ipfs/kubo#11282](https://github.com/ipfs/kubo/pull/11282)) + - fix(fuse): accurate `st_blocks` and `st_blksize` (#11280) ([ipfs/kubo#11280](https://github.com/ipfs/kubo/pull/11280)) + - chore: start v0.42.0 dev cycle +- github.com/ipfs/boxo (v0.39.0 -> v0.40.0): + - Release v0.40.0 ([ipfs/boxo#1161](https://github.com/ipfs/boxo/pull/1161)) + - upgrade to go-libp2p-kad-dht v0.40.0 ([ipfs/boxo#1159](https://github.com/ipfs/boxo/pull/1159)) + - Typos ([ipfs/boxo#1158](https://github.com/ipfs/boxo/pull/1158)) + - fix(routing/http): cap records after filtering (#1157) ([ipfs/boxo#1157](https://github.com/ipfs/boxo/pull/1157)) + - feat(path/resolver): populate retrieval state (#1153) ([ipfs/boxo#1153](https://github.com/ipfs/boxo/pull/1153)) + - feat(pqm): opt-in findpeer fallback on dial fail (#1156) ([ipfs/boxo#1156](https://github.com/ipfs/boxo/pull/1156)) + - chore: go-libp2p-kad-dht v0.39.2 (#1155) ([ipfs/boxo#1155](https://github.com/ipfs/boxo/pull/1155)) + - fix(files): remove io.Seeker from File interface (#1128) ([ipfs/boxo#1128](https://github.com/ipfs/boxo/pull/1128)) + - feat(pinner): add Close with ErrClosed lifecycle (#1150) ([ipfs/boxo#1150](https://github.com/ipfs/boxo/pull/1150)) + - fix(files): support js/wasm and wasip1/wasm builds (#935) ([ipfs/boxo#935](https://github.com/ipfs/boxo/pull/935)) +- github.com/ipfs/go-ds-dynamodb (v0.2.2 -> v0.3.0): + - feat!: migrate to aws-sdk-go-v2 (#22) ([ipfs/go-ds-dynamodb#22](https://github.com/ipfs/go-ds-dynamodb/pull/22)) +- github.com/ipfs/go-ds-pebble (v0.5.10 -> v0.5.11): + - update version for release v0.5.11 (#87) ([ipfs/go-ds-pebble#87](https://github.com/ipfs/go-ds-pebble/pull/87)) +- github.com/ipfs/go-ipfs-cmds (v0.16.0 -> v0.16.1): + - new version (#339) ([ipfs/go-ipfs-cmds#339](https://github.com/ipfs/go-ipfs-cmds/pull/339)) + - fix spelling errors (#336) ([ipfs/go-ipfs-cmds#336](https://github.com/ipfs/go-ipfs-cmds/pull/336)) +- github.com/ipfs/go-log/v2 (v2.9.1 -> v2.9.2): + - v2.9.2 bump (#184) ([ipfs/go-log#184](https://github.com/ipfs/go-log/pull/184)) + - chore: flag WithGroup as a known no-op + - fix: resolve slog LogValuer in zap bridge + - fix: inline top-level empty-key slog.Group + - fix: support slog.Group in the zap bridge +- github.com/ipfs/go-unixfsnode (v1.10.3 -> v1.10.4): + - updete version ([ipfs/go-unixfsnode#96](https://github.com/ipfs/go-unixfsnode/pull/96)) +- github.com/ipld/go-car/v2 (v2.16.0 -> v2.16.1-0.20260428045700-c4b9f366f20c): + - feat(cmd): Make "extract" arguments more closely mirror common archive tools (#653) ([ipld/go-car#653](https://github.com/ipld/go-car/pull/653)) + - fix: use %w when wrapping errors to preserve error chain (#658) ([ipld/go-car#658](https://github.com/ipld/go-car/pull/658)) + - chore(release): modernise goreleaser config + - fix: remove broken trailing data check in "inspect --full" for CARv1 (#654) ([ipld/go-car#654](https://github.com/ipld/go-car/pull/654)) + - Allow the car binary to provide a version. (#645) ([ipld/go-car#645](https://github.com/ipld/go-car/pull/645)) + - Add `car put-block` command (#629) ([ipld/go-car#629](https://github.com/ipld/go-car/pull/629)) +- github.com/ipshipyard/p2p-forge (v0.8.0 -> v0.9.0): + - chore: release v0.9.0 (#89) ([ipshipyard/p2p-forge#89](https://github.com/ipshipyard/p2p-forge/pull/89)) + - refactor: bump deps and migrate to aws-sdk-go-v2 (#88) ([ipshipyard/p2p-forge#88](https://github.com/ipshipyard/p2p-forge/pull/88)) + - feat(client): add WithHTTPClient option (#87) ([ipshipyard/p2p-forge#87](https://github.com/ipshipyard/p2p-forge/pull/87)) + - feat: run Corefile check before run (#77) ([ipshipyard/p2p-forge#77](https://github.com/ipshipyard/p2p-forge/pull/77)) +- github.com/libp2p/go-libp2p-kad-dht (v0.39.1 -> v0.40.0): + - chore: release v0.40.0 (#1257) ([libp2p/go-libp2p-kad-dht#1257](https://github.com/libp2p/go-libp2p-kad-dht/pull/1257)) + - fix(ResettableKeystore): speed up reset process and keep worker responsive (#1256) ([libp2p/go-libp2p-kad-dht#1256](https://github.com/libp2p/go-libp2p-kad-dht/pull/1256)) + - fix(provider): hold cycleStatsLk in batchReprovide defer (#1255) ([libp2p/go-libp2p-kad-dht#1255](https://github.com/libp2p/go-libp2p-kad-dht/pull/1255)) + - fix(provider): per-peer timeout on ADD_PROVIDER sends (#1252) ([libp2p/go-libp2p-kad-dht#1252](https://github.com/libp2p/go-libp2p-kad-dht/pull/1252)) + - fix(provider)!: bound keystore.Size in Stats with a timeout (#1251) ([libp2p/go-libp2p-kad-dht#1251](https://github.com/libp2p/go-libp2p-kad-dht/pull/1251)) + - chore: release v0.39.2 (#1249) ([libp2p/go-libp2p-kad-dht#1249](https://github.com/libp2p/go-libp2p-kad-dht/pull/1249)) + - test: fix flaky TestStartProvidingSingle (#1247) ([libp2p/go-libp2p-kad-dht#1247](https://github.com/libp2p/go-libp2p-kad-dht/pull/1247)) + - feat(provider): allow WithReprovideInterval(0) for burst-only mode (#1246) ([libp2p/go-libp2p-kad-dht#1246](https://github.com/libp2p/go-libp2p-kad-dht/pull/1246)) +- github.com/libp2p/go-libp2p-pubsub (v0.15.0 -> v0.16.0): + - Release v0.16.0 (#693) ([libp2p/go-libp2p-pubsub#693](https://github.com/libp2p/go-libp2p-pubsub/pull/693)) + - fix: properly log topic string (#694) ([libp2p/go-libp2p-pubsub#694](https://github.com/libp2p/go-libp2p-pubsub/pull/694)) + - rename {Add/Remove}Peer to On{New/Closed}OutboundStream + - Fix leaked state with OnNewIncomingStream/onClosedIncomingStream + - test: Add failing test demonstrating leak + - partialmessages: init fanout if empty (#690) ([libp2p/go-libp2p-pubsub#690](https://github.com/libp2p/go-libp2p-pubsub/pull/690)) + - log on error when handling rpc in extensions (#668) ([libp2p/go-libp2p-pubsub#668](https://github.com/libp2p/go-libp2p-pubsub/pull/668)) + - feat: add WithMessageFilter option to filter messages early in the notification pipeline (#678) ([libp2p/go-libp2p-pubsub#678](https://github.com/libp2p/go-libp2p-pubsub/pull/678)) + - partialmsgs: remove unused struct + - partialmsgs: PeerRequestsPartial means PeerRequestsPartial + - partialmsgs: Change Gossip Callback to have application call PublishPartial + - feat: FanoutOnly topic option (#676) ([libp2p/go-libp2p-pubsub#676](https://github.com/libp2p/go-libp2p-pubsub/pull/676)) + - Partial Messages API refactor (#671) ([libp2p/go-libp2p-pubsub#671](https://github.com/libp2p/go-libp2p-pubsub/pull/671)) + - Revert "Refactor parts metadata to an interface (#669)" + - Add threadsafe dynamic direct peer handling to GossipSub (#673) ([libp2p/go-libp2p-pubsub#673](https://github.com/libp2p/go-libp2p-pubsub/pull/673)) + - Refactor parts metadata to an interface (#669) ([libp2p/go-libp2p-pubsub#669](https://github.com/libp2p/go-libp2p-pubsub/pull/669)) + - Add Rpc.From() + - partialmsgs: Implement Partial Message gossip + - partialmsgs: Add explicit EagerPartialMessageBytes method + - partialmsgs: remove outdated comment + - partialmessages: Send fewer partsMetadata messages + - partialmessages: Change EagerPush to EagerPushWithParts + - partialmessages: PublishPartial to peers in group state as well + - fix stale comment in partialmessages + - fix logic of omitting IDONTWANT to peers that support partial messages + - fix bug of not sending full messages to peers that requested partial + - rename PartialMessagesExtension for consistency + - partialmessages: rename field to reflect use + - partialmessages: add per peer bounds on peer initiated groups + - partialmessages: Add pairwise interaction test + - add test for SupportsPartial subscribe option + - Add support for `supportsPartial` + - pb: add `supportsPartial` field in SubOpts + - support fanout topics for partial messages + - limit the number of peer initiated group states we track + - nit: rename method from old form + - Add ability to update peer scores if using partial messages + - refactor score methods to accept topic string + - set write deadline for outgoing messages + - ensure that the Hello Packet is the first rpc sent + - add todo + - don't send IDONTWANT to partial message peers + - Add documentation for the RequestPartialMessages topic option + - partialmessages: add explicit MergePartsMetadata function + - partialmessages: add basic bitmap package + - add partial messages to gossipsub router + - implement Partial Messages + - add structured rpc logging + - update protobufs for partial messages + +
+ ### 👨‍👩‍👧‍👦 Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| [@lidel](https://github.com/lidel) | 42 | +7059/-920 | 188 | +| [@MarcoPolo](https://github.com/MarcoPolo) | 43 | +5818/-2113 | 122 | +| [@guillaumemichel](https://github.com/guillaumemichel) | 8 | +1422/-165 | 17 | +| [@ChayanDass](https://github.com/ChayanDass) | 2 | +421/-18 | 10 | +| [@parkan](https://github.com/parkan) | 1 | +339/-0 | 3 | +| [@gammazero](https://github.com/gammazero) | 12 | +142/-135 | 28 | +| [@Vinayak9769](https://github.com/Vinayak9769) | 1 | +145/-78 | 10 | +| [@laciferin2024](https://github.com/laciferin2024) | 1 | +209/-0 | 3 | +| [@rvagg](https://github.com/rvagg) | 4 | +160/-18 | 6 | +| [@Wondertan](https://github.com/Wondertan) | 1 | +154/-4 | 4 | +| [@cortze](https://github.com/cortze) | 1 | +125/-19 | 5 | +| [@sukunrt](https://github.com/sukunrt) | 3 | +58/-27 | 5 | +| [@davidebeatrici](https://github.com/davidebeatrici) | 1 | +55/-30 | 4 | +| [@hsanjuan](https://github.com/hsanjuan) | 1 | +33/-15 | 5 | +| [@willscott](https://github.com/willscott) | 1 | +10/-2 | 2 | diff --git a/docs/changelogs/v0.43.md b/docs/changelogs/v0.43.md new file mode 100644 index 00000000000..4bda4586540 --- /dev/null +++ b/docs/changelogs/v0.43.md @@ -0,0 +1,25 @@ +# Kubo changelog v0.43 + + + +This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. + +- [v0.43.0](#v0430) + +## v0.43.0 + +- [Overview](#overview) +- [🔦 Highlights](#-highlights) + - [📦️ Dependency updates](#-dependency-updates) +- [📝 Changelog](#-changelog) +- [👨‍👩‍👧‍👦 Contributors](#-contributors) + +### Overview + +### 🔦 Highlights + +#### 📦️ Dependency updates + +### 📝 Changelog + +### 👨‍👩‍👧‍👦 Contributors diff --git a/docs/config.md b/docs/config.md index 8604d7082e9..6aa27375987 100644 --- a/docs/config.md +++ b/docs/config.md @@ -104,6 +104,7 @@ config file at runtime. - [`Internal.Bitswap.BroadcastControl.MaxRandomPeers`](#internalbitswapbroadcastcontrolmaxrandompeers) - [`Internal.Bitswap.BroadcastControl.SendToPendingPeers`](#internalbitswapbroadcastcontrolsendtopendingpeers) - [`Internal.UnixFSShardingSizeThreshold`](#internalunixfsshardingsizethreshold) + - [`Internal.ShutdownTimeout`](#internalshutdowntimeout) - [`Ipns`](#ipns) - [`Ipns.RepublishPeriod`](#ipnsrepublishperiod) - [`Ipns.RecordLifetime`](#ipnsrecordlifetime) @@ -144,6 +145,7 @@ config file at runtime. - [`Provide.DHT.MaxProvideConnsPerWorker`](#providedhtmaxprovideconnsperworker) - [`Provide.DHT.KeystoreBatchSize`](#providedhtkeystorebatchsize) - [`Provide.DHT.OfflineDelay`](#providedhtofflinedelay) + - [`Provide.DHT.SendProviderRecordTimeout`](#providedhtsendproviderrecordtimeout) - [`Provide.BloomFPRate`](#providebloomfprate) - [`Provider`](#provider) - [`Provider.Enabled`](#providerenabled) @@ -356,7 +358,8 @@ Supported Transports: > [!IMPORTANT] > Make sure your firewall rules allow incoming connections on both TCP and UDP ports defined here. -> See [Security section](#security) for network exposure considerations. +> See [`docs/production/firewall.md`](./production/firewall.md) for a `ufw` walkthrough, +> and the [Security section](#security) below for wider network exposure considerations. Note that quic (Draft-29) used to be supported with the format `/ipN/.../udp/.../quic`, but has since been [removed](https://github.com/libp2p/go-libp2p/releases/tag/v0.30.0). @@ -966,25 +969,116 @@ Type: `bool` ### `Datastore.BloomFilterSize` -A number representing the size in bytes of the blockstore's [bloom -filter](https://en.wikipedia.org/wiki/Bloom_filter). A value of zero represents -the feature is disabled. - -This site generates useful graphs for various bloom filter values: - You may use it to find a -preferred optimal value, where `m` is `BloomFilterSize` in bits. Remember to -convert the value `m` from bits, into bytes for use as `BloomFilterSize` in the -config file. For example, for 1,000,000 blocks, expecting a 1% false-positive -rate, you'd end up with a filter size of 9592955 bits, so for `BloomFilterSize` -we'd want to use 1199120 bytes. As of writing, [7 hash -functions](https://github.com/ipfs/go-ipfs-blockstore/blob/547442836ade055cc114b562a3cc193d4e57c884/caching.go#L22) -are used, so the constant `k` is 7 in the formula. - -Enabling the BloomFilter can provide performance improvements specially when -responding to many requests for inexistent blocks. It however requires a full -sweep of all the datastore keys on daemon start. On very large datastores this -can be a very taxing operation, particularly if the datastore does not support -querying existing keys without reading their values at the same time (blocks). +The size in **bytes** of the blockstore's [bloom filter](https://en.wikipedia.org/wiki/Bloom_filter). +A value of `0` disables the feature. + +The bloom filter answers "does the blockstore *not* have this CID?" from RAM +without touching the datastore. A negative answer is exact (no false +negatives, so blocks are never falsely reported missing); a positive answer +is probabilistic and falls through to the underlying blockstore for +verification. The chance of a false "maybe present" is the filter's +**false-positive rate (FPR)**. A false positive costs one wasted datastore +lookup; it never causes data loss or incorrect retrieval. The lower the FPR, +the more `Has()` calls the filter answers from RAM alone. + +This cache pays off most on nodes that field many requests for content they +don't host: public gateways, mirrors, and peers asked to serve +opportunistically-cached blocks. + +The complementary cache for the *positive* path (block exists, look up its +size) is [`Datastore.BlockKeyCacheSize`](#datastoreblockkeycachesize). + +#### How kubo's bloom filter is sized + +Kubo wires the underlying [`ipfs/bbloom`](https://github.com/ipfs/bbloom) +filter with `k=7` hash positions. Two kubo-specific behaviors matter for +sizing: + +1. **Power-of-two bit-count rounding.** bbloom rounds the requested bit + count up to the next power of two, so a `BloomFilterSize` value that is + not itself a power of two in bits silently allocates more memory than + configured. For example, `BloomFilterSize: 1199120` (~1.14 MiB) + actually allocates a `16,777,216`-bit (= 2 MiB) filter internally. For + predictable behavior, pick `BloomFilterSize` values that are + power-of-two byte counts: 1 MiB, 2 MiB, 4 MiB, ..., 256 MiB, 512 MiB, + 1 GiB. +2. **Fixed `k=7`.** With seven hash positions, FPR for a filter of `m` + bits and `n` inserted entries is `(1 - exp(-7n/m))^7`. To hit a + target FPR, budget roughly ~1.8 bytes per entry at ~1% FPR, ~2.8 + bytes per entry at ~0.1% FPR, and ~4.2 bytes per entry at ~0.01% + FPR. These figures already include the average ~1.5x penalty from + the power-of-two rounding above; the worst case is ~2x. + +#### Reference sizing + +Power-of-two `BloomFilterSize` values for common blockset sizes, with the +FPR you can expect at the design point and at 2× growth: + +| Expected blocks (`n`) | `BloomFilterSize` | FPR at `n` | FPR at 2× `n` | +|---:|---:|---:|---:| +| 10,000,000 | `16777216` (16 MiB) | ~0.18% | ~5% | +| 25,000,000 | `33554432` (32 MiB) | ~0.58% | ~11% | +| 50,000,000 | `67108864` (64 MiB) | ~0.58% | ~11% | +| 100,000,000 | `134217728` (128 MiB) | ~0.58% | ~11% | +| 200,000,000 | `268435456` (256 MiB) | ~0.58% | ~11% | + +For a tighter FPR at the design point, step up to the next power of two. + +The [hur.st/bloomfilter](https://hur.st/bloomfilter/?n=10e6&p=0.01&m=&k=7) +calculator works as a reference for exploring `(n, p, m)` combinations +(remember kubo uses `k=7`); just keep in mind that the `m` it suggests +is the optimal-fit value, while bbloom rounds up to the next power of +two on top of that. + +#### Saturation as the repo grows + +A bloom filter is fixed-size after creation. As more CIDs are inserted +past its design `n`, the false-positive rate climbs steeply. Rough +behavior with a filter sized for ~0.6% FPR at its design point: + +- At `n`: ~0.6% FPR. Every "definitely not" reliably saves a datastore + lookup. +- At ~`2 × n`: ~11% FPR. Most negatives still save lookups, but tail + latency rises because each "maybe" still hits the datastore. +- At ~`4 × n`: ~58% FPR. Most "maybe" answers fall through. The filter + is mostly paying CPU and RAM cost without short-circuiting much. +- At ~`8 × n` or more: above ~95% FPR. Effectively saturated. The + filter answers "maybe" for nearly every CID and provides no benefit. + +Size for **expected steady-state, not today's count**, and re-tune after +crossing the design point. Bloom filters cannot grow in place; raising +`BloomFilterSize` and restarting the daemon rebuilds the filter from +scratch. + +#### Risks of an undersized filter + +A poorly-sized filter is **never a correctness issue**. Bloom filters +have no false negatives, so blocks are never falsely reported missing. +The risks are operational: + +- **Wasted RAM and CPU.** Every `Has()` still runs all seven hash + positions. Once the filter saturates, those cycles return nothing. +- **Silent regression as the pinset grows.** A filter sized for last + year's data can drift past saturation without warning; the + negative-`Has` short-circuit benefit just quietly disappears. +- **Recurring startup tax.** The filter rebuilds on every daemon + restart (see below). On slow disks this means minutes of + `AllKeysChan` walking, paid in full even when the resulting filter + is too small to help. + +Quick health check: divide `BloomFilterSize` by your current block count. +Below ~`1` byte/block the filter is past its design point; below +~`0.5` bytes/block it is effectively saturated. + +#### Startup cost + +The filter is not persisted across restarts. Every daemon start rebuilds it +by walking all datastore keys (`AllKeysChan`). On very large blockstores or +slow disks this can take many minutes, during which `Has()` falls through +to the datastore and the filter provides no benefit. Datastores that cannot +enumerate keys without reading values (block content) pay even more here; +flatfs and pebble both support keys-only iteration, so the rebuild cost +scales with the keyset, not data volume. Default: `0` (disabled) @@ -1011,16 +1105,38 @@ Type: `bool` ### `Datastore.BlockKeyCacheSize` -A number representing the maximum size in bytes of the blockstore's Two-Queue -cache, which caches block-cids and their block-sizes. Use `0` to disable. +The maximum **number of entries** held in the blockstore's Two-Queue cache. The +cache stores per-CID metadata (existence and block size) but never block +content. Use `0` to disable. + +A cache hit answers `Has` and `GetSize` from RAM and skips the underlying +datastore lookup. This includes the per-block `os.Stat` flatfs does to learn a +block's size, which is the dominant cost on bitswap servers responding to peer +wantlists. + +The cache uses a [Two-Queue (2Q) replacement policy](https://pkg.go.dev/github.com/hashicorp/golang-lru/v2#TwoQueueCache): +an entry must be touched twice before it is promoted to the frequently-used +tier. A long one-shot scan (reprovider, GC, `ipfs repo verify`) therefore +does not evict the hot entries that bitswap repeatedly serves. -This cache, once primed, can greatly speed up operations like `ipfs repo stat` -as there is no need to read full blocks to know their sizes. Size should be -adjusted depending on the number of CIDs on disk (`NumObjects in`ipfs repo stat`). +#### Sizing -Default: `65536` (64KiB) +Memory usage is roughly the entry count times the per-entry overhead, which +combines 2Q bookkeeping, the multihash key bytes, and the cached value. As a +rough estimate, budget ~200 bytes per entry, so `1048576` (1M entries) is on +the order of ~200 MB resident. The cache only needs to cover the **hot +working set** of CIDs (the ones repeatedly hit by inbound bitswap, gateway, +or DAG-resolution traffic), not the entire blockstore. -Type: `optionalInteger` (non-negative, bytes) +The default of `65536` is sized for small dev/desktop nodes. Operators +running public gateways, pinning clusters, or any node serving non-trivial +bitswap traffic should size this against the active working set. See +[`Datastore.BloomFilterSize`](#datastorebloomfiltersize) for the +complementary negative-`Has()` short-circuit that pairs well with this cache. + +Default: `65536` (entries) + +Type: `optionalInteger` (non-negative, number of entries) ### `Datastore.Spec` @@ -1796,6 +1912,28 @@ Type: `optionalInteger` (0 disables the limit, strongly discouraged) **Note:** This is an EXPERIMENTAL feature and may change or be removed in future releases. See [#10842](https://github.com/ipfs/kubo/issues/10842) for more information. +### `Internal.ShutdownTimeout` + +Caps how long graceful shutdown is allowed to take. If `node.Close()` does +not return within this duration, the daemon logs which subsystem failed +and exits with status `1`. Set to `0` to wait forever (legacy behavior). + +The default `12h` guarantees the daemon cannot be stuck indefinitely on a +hung close hook, which matters for container orchestrators that otherwise +see a half-shutdown process as `healthy`. The value is smaller than the +22h DHT reprovide cycle, so a hung daemon recovers before missing more +than one cycle. + +Tune down for fast-restart environments. When tuning, raise the +orchestrator grace period (`--stop-timeout` for Docker, +`terminationGracePeriodSeconds` for Kubernetes) to at least this value so +the daemon exits gracefully before the orchestrator escalates to +`SIGKILL`. + +Default: `12h` + +Type: `optionalDuration` (`0` disables the cap) + ## `Ipns` ### `Ipns.RepublishPeriod` @@ -2243,14 +2381,21 @@ interval (common with large datasets), the next cycle is skipped and provider records may expire. - If unset, it uses the implicit safe default. -- If set to the value `"0"` it will disable content reproviding to DHT. +- If set to `"0"`, the periodic reprovide schedule is disabled. New CIDs are + still announced immediately via fast-provide-root and `ipfs provide once`. > [!CAUTION] -> Disabling this will prevent other nodes from discovering your content via the DHT. -> Your node will stop announcing data to the DHT, making it -> inaccessible unless peers connect to you directly. Since provider -> records expire after `amino.DefaultProvideValidity`, your content will become undiscoverable -> after this period. +> `Interval=0` disables only the periodic refresh, not announcements of new +> content. Once provider records expire after `amino.DefaultProvideValidity`, +> the affected CIDs become undiscoverable to peers that did not retrieve them +> within that window. To fully disable providing, set +> [`Provide.Enabled=false`](#provideenabled) instead. + +> [!IMPORTANT] +> When `Interval=0`, [`Provide.Enabled`](#provideenabled) must be set +> explicitly. The daemon refuses to start otherwise. This prevents silent +> behaviour change on upgrade for operators who previously relied on +> `Interval=0` as a master kill-switch. Default: `22h` @@ -2446,7 +2591,7 @@ Number of workers dedicated to burst provides. Only applies when `Provide.DHT.Sw Burst provides are triggered by: -- Manual provide commands (`ipfs routing provide`) +- Manual provide commands (`ipfs provide once`) - New content matching your `Provide.Strategy` (blocks from `ipfs add`, bitswap, or trustless gateway requests) - Catch-up reprovides after being disconnected/offline for a while @@ -2527,6 +2672,26 @@ Default: `2h` Type: `optionalDuration` +#### `Provide.DHT.SendProviderRecordTimeout` + +Per-peer timeout applied to a single `ADD_PROVIDER` RPC sent during a provide +or reprovide operation. A peer that accepts the libp2p stream but never reads +the request can otherwise pin a provide worker goroutine until the connection +is dropped by the transport layer; this option bounds that wait. + +Healthy peers complete the round-trip in well under a second. The default +leaves significant headroom for slow links while keeping a hung peer from +stalling a worker. + +> [!NOTE] +> Lowering this value can speed up reprovide cycles when a non-trivial +> fraction of peers are slow or unresponsive, at the cost of giving up on +> genuinely slow but healthy peers. + +Default: `10s` + +Type: `optionalDuration` (positive) + ### `Provide.BloomFPRate` Target false positive rate for the bloom filter used by the [`+unique` and @@ -2793,7 +2958,12 @@ Controls how your node discovers content and peers on the network. when reachable from the public internet. - **`autoclient`**: Same as `auto`, but never runs a DHT server. - Use this if your node is behind a firewall or NAT. + Use this if your node is behind a firewall or NAT, or if you run a + [content denylist](https://github.com/ipfs/kubo/blob/master/docs/content-blocking.md) + and do not want to store or serve routing records (provider records, + IPNS records) for denied keys on behalf of other peers. See + [Scope of denylists](https://github.com/ipfs/kubo/blob/master/docs/content-blocking.md#scope-of-denylists) + for why this matters. - **`dht`**: Uses only the Amino DHT (no HTTP routers). Automatically switches between client and server mode based on reachability. @@ -3112,6 +3282,9 @@ so that a range is neither advertised nor dialed. > [`server` profile](#server-profile) section for the full list and for > optional entries operators may add manually. +> [!CAUTION] +> If an [`Addresses.Swarm`](#addressesswarm) listener (for example a manually configured `/ip4/127.0.0.1/tcp/.../ws` fronted by a local nginx or Caddy reverse proxy) is covered by an entry in this list, Kubo rejects every incoming connection to it, so the proxy cannot reach Kubo. Kubo logs an ERROR at startup naming the offending rule. Remove the rule from `Swarm.AddrFilters` to allow the listener; keep it in [`Addresses.NoAnnounce`](#addressesnoannounce) if you still want to suppress its announcement. + Default: `[]` Type: `array[string]` @@ -4080,12 +4253,12 @@ other peers version for detecting when there is time to update. Optional suffix to the AgentVersion presented by `ipfs id` and exposed via [libp2p identify protocol](https://github.com/libp2p/specs/blob/master/identify/README.md#agentversion). -The value from config takes precedence over value passed via `ipfs daemon --agent-version-suffix`. +The value from config takes precedence over value passed via `ipfs daemon --agent-version-suffix`. When both are empty, kubo derives an implicit suffix from the build origin (`git remote get-url origin`, or `debug.ReadBuildInfo` for `go install` builds), stripping public forge hostnames so a fork hosted at `github.com/myorg/kubo` becomes `myorg`. Set this option to override the implicit value. > [!NOTE] > Setting a custom version suffix helps with ecosystem analysis, such as Amino DHT reports published at -Default: `""` (no suffix, or value from `ipfs daemon --agent-version-suffix=`) +Default: implicit suffix from build origin, or `""` for upstream builds and when `ipfs daemon --agent-version-suffix=` is empty. Type: `optionalString` @@ -4187,6 +4360,7 @@ Or skip the profile and populate those fields manually. | Link-local IPv6 peering | `/ip6/fe80::/ipcidr/10` | | Multiple daemons peering over `127.0.0.1` | `/ip4/127.0.0.0/ipcidr/8` | | Multiple daemons peering over IPv6 loopback `::1` | `/ip6/::1/ipcidr/128` and `/ip6/::/ipcidr/3` | +| Local reverse proxy fronting a `/ws` (or other libp2p) listener on `127.0.0.1` | `/ip4/127.0.0.0/ipcidr/8` from `Swarm.AddrFilters` only (keep it in `Addresses.NoAnnounce`); also drop `/ip6/::1/ipcidr/128` and `/ip6/::/ipcidr/3` from `Swarm.AddrFilters` if the proxy uses IPv6 loopback | | [Yggdrasil] mesh peering (`200::/8`, `300::/8`) | `/ip6/::/ipcidr/3` | | NAT64 (`64:ff9b::/96`) reachability | `/ip6/::/ipcidr/3` | @@ -4438,7 +4612,7 @@ Several configuration options expose TCP or UDP ports that can make your Kubo no - Keep admin services ([`Addresses.API`](#addressesapi)) bound to localhost unless authentication ([`API.Authorizations`](#apiauthorizations)) is configured - Use [`Gateway.NoFetch`](#gatewaynofetch) to prevent arbitrary CID retrieval if Kubo is acting as a public gateway available to anyone -- Configure firewall rules to restrict access to exposed ports. Note that [`Addresses.Swarm`](#addressesswarm) is special - all incoming traffic to swarm ports should be allowed to ensure proper P2P connectivity +- Configure firewall rules to restrict access to exposed ports. Note that [`Addresses.Swarm`](#addressesswarm) is special - all incoming traffic to swarm ports should be allowed to ensure proper P2P connectivity. See [`docs/production/firewall.md`](./production/firewall.md) for a `ufw` walkthrough. - Control which public-facing addresses are announced to other peers using [`Addresses.NoAnnounce`](#addressesnoannounce), [`Addresses.Announce`](#addressesannounce), and [`Addresses.AppendAnnounce`](#addressesappendannounce) - Consider using the [`server` profile](#server-profile) for production deployments diff --git a/docs/content-blocking.md b/docs/content-blocking.md index f02e01a7ace..3745b466029 100644 --- a/docs/content-blocking.md +++ b/docs/content-blocking.md @@ -39,6 +39,33 @@ End user is not informed about the exact reason, see [How to debug](#how-to-debug) if you need to find out which line of which denylist caused the request to be blocked. +## Scope of denylists + +Denylists apply to **content retrieval and serving** by your local node: + +- Bitswap: your node neither requests blocked blocks from peers nor serves them to peers. +- Gateway and CLI: requests for a denied CID return an error (HTTP 410 Gone from the gateway). +- IPNS resolution: your node refuses to resolve a denied IPNS name locally. + +Denylists do **not** apply to the routing system. If your node runs as a DHT server (the default with `Routing.Type=auto` once your node is publicly reachable), it can still: + +- Accept and store provider records (`ADD_PROVIDER`) for denied CIDs from other peers, and return them on `GET_PROVIDERS`. +- Accept and store IPNS records for denied names from other peers, and serve them on `GetValue`. +- Forward IPNS records over pubsub when [`Ipns.UsePubsub`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsusepubsub) is enabled. +- Surface those records over the [`/routing/v1/`](https://specs.ipfs.tech/routing/http-routing-v1/) HTTP API when [`Gateway.ExposeRoutingAPI`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi) is enabled. + +In short, your node will not fetch or serve the content itself, but as a DHT server it still helps other peers discover providers and resolve names for that content. + +### How to stop facilitating routing for blocked content + +Set [`Routing.Type`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingtype) to `autoclient`: + +```sh +$ ipfs config Routing.Type autoclient +``` + +In `autoclient` mode your node only acts as a DHT client. It never runs a DHT server, so it does not store or serve provider records or IPNS records on behalf of other peers. + ## Denylist file format [NOpfs](https://github.com/ipfs-shipyard/nopfs) supports the format from [IPIP-383](https://specs.ipfs.tech/ipips/ipip-0383/). diff --git a/docs/datastores.md b/docs/datastores.md index 3af06d24e35..d56de8ec8e3 100644 --- a/docs/datastores.md +++ b/docs/datastores.md @@ -16,7 +16,9 @@ Stores each key-value pair as a file on the filesystem. The shardFunc is prefixed with `/repo/flatfs/shard/v1` then followed by a descriptor of the sharding strategy. Some example values are: - `/repo/flatfs/shard/v1/next-to-last/2` - - Shards on the two next to last characters of the key + - Shards on the two next-to-last base32 characters of the key (~1024 directories) +- `/repo/flatfs/shard/v1/next-to-last/3` + - Shards on the three next-to-last base32 characters of the key (~32,768 directories) - `/repo/flatfs/shard/v1/prefix/2` - Shards based on the two-character prefix of the key @@ -33,6 +35,35 @@ The shardFunc is prefixed with `/repo/flatfs/shard/v1` then followed by a descri NOTE: flatfs must only be used as a block store (mounted at `/blocks`) as it only partially implements the datastore interface. You can mount flatfs for /blocks only using the mount datastore (described below). +### Choosing a `shardFunc` for large blockstores + +The `next-to-last/N` shard depth controls how many directories the blockstore +is spread across. Each shard becomes a single directory under `blocks/`, and +every block file lives directly inside its shard. The cost of any operation +that does a `readdir` or per-file `stat` on a shard scales with the number of +files in that shard. + +Two depths in common use: + +| `shardFunc` | Shard count | At 60M blocks | Notes | +|--------------------------|------------:|----------------:|---------------------------------------------| +| `next-to-last/2` | ~1,024 | ~58k files/dir | default; fine for small/medium nodes | +| `next-to-last/3` | ~32,768 | ~1.8k files/dir | recommended for large pinning/gateway nodes | + +For nodes expected to grow past a few million blocks (most pinning clusters, +public gateways, mirrors), prefer `next-to-last/3`. The deeper sharding keeps +per-directory file counts in a range modern filesystems handle well, and it +significantly reduces the per-operation cost of `Stat`, `readdir`, and bulk +enumeration (used by GC, [`Datastore.BloomFilterSize`](config.md#datastorebloomfiltersize) +rebuild on startup, and `Provide.Strategy=all` reprovide cycles). On nodes +backed by rotational disks the difference can be the gap between healthy +operation and IOPS-saturated iowait. + +The shard depth is fixed at `ipfs init` time. Kubo ships no in-place +re-sharding tool, so switching depth on an existing repo means exporting +and re-importing the blockstore. Pick conservatively for the expected +steady state of the node. + ## levelds Uses a [leveldb](https://github.com/syndtr/goleveldb) database to store key-value diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index b9289162a3a..b758c71dc40 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -1,13 +1,13 @@ module github.com/ipfs/kubo/examples/kubo-as-a-library -go 1.26.2 +go 1.26.4 // Used to keep this in sync with the current version of kubo. You should remove // this if you copy this example. replace github.com/ipfs/kubo => ./../../.. require ( - github.com/ipfs/boxo v0.39.0 + github.com/ipfs/boxo v0.40.0 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.48.0 github.com/multiformats/go-multiaddr v0.16.1 @@ -26,8 +26,8 @@ require ( github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/caddyserver/certmagic v0.23.0 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/caddyserver/certmagic v0.25.3 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/ceramicnetwork/go-dag-jose v0.1.1 // indirect @@ -35,7 +35,7 @@ require ( github.com/cockroachdb/crlib v0.0.0-20241112164430-1264a2edc35b // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble/v2 v2.1.4 // indirect + github.com/cockroachdb/pebble/v2 v2.1.5 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect @@ -51,12 +51,12 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gammazero/chanqueue v1.1.2 // indirect github.com/gammazero/deque v1.2.1 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -68,7 +68,7 @@ require ( github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/guillaumemichel/reservedpool v0.3.0 // indirect - github.com/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c // indirect + github.com/hanwen/go-fuse/v2 v2.10.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huin/goupnp v1.3.0 // indirect @@ -84,10 +84,10 @@ require ( github.com/ipfs/go-ds-flatfs v0.6.0 // indirect github.com/ipfs/go-ds-leveldb v0.5.2 // indirect github.com/ipfs/go-ds-measure v0.2.2 // indirect - github.com/ipfs/go-ds-pebble v0.5.10 // indirect + github.com/ipfs/go-ds-pebble v0.5.11 // indirect github.com/ipfs/go-dsqueue v0.2.0 // indirect github.com/ipfs/go-fs-lock v0.1.1 // indirect - github.com/ipfs/go-ipfs-cmds v0.16.0 // indirect + github.com/ipfs/go-ipfs-cmds v0.16.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect github.com/ipfs/go-ipfs-pq v0.0.4 // indirect github.com/ipfs/go-ipfs-redirects-file v0.1.2 // indirect @@ -96,31 +96,31 @@ require ( github.com/ipfs/go-ipld-git v0.1.1 // indirect github.com/ipfs/go-ipld-legacy v0.3.0 // indirect github.com/ipfs/go-libdht v0.5.0 // indirect - github.com/ipfs/go-log/v2 v2.9.1 // indirect + github.com/ipfs/go-log/v2 v2.9.2 // indirect github.com/ipfs/go-metrics-interface v0.3.0 // indirect github.com/ipfs/go-peertaskqueue v0.8.3 // indirect github.com/ipfs/go-test v0.3.0 // indirect - github.com/ipfs/go-unixfsnode v1.10.3 // indirect - github.com/ipld/go-car/v2 v2.16.0 // indirect + github.com/ipfs/go-unixfsnode v1.10.4 // indirect + github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c // indirect github.com/ipld/go-codec-dagpb v1.7.0 // indirect github.com/ipld/go-ipld-prime v0.23.0 // indirect - github.com/ipshipyard/p2p-forge v0.8.0 // indirect + github.com/ipshipyard/p2p-forge v0.9.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/koron/go-ssdp v0.0.6 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/libdns/libdns v1.0.0-beta.1 // indirect + github.com/libdns/libdns v1.1.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-doh-resolver v0.5.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-kad-dht v0.39.1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.40.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.8.0 // indirect - github.com/libp2p/go-libp2p-pubsub v0.15.0 // indirect + github.com/libp2p/go-libp2p-pubsub v0.16.0 // indirect github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect @@ -131,8 +131,8 @@ require ( github.com/libp2p/go-yamux/v5 v5.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mholt/acmez/v3 v3.1.2 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -192,39 +192,39 @@ require ( github.com/zeebo/blake3 v0.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/sdk v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // 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/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap v1.28.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.45.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/grpc v1.79.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index ad8bad36592..9a54cfe11e2 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0= filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI= @@ -84,10 +86,10 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 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/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A= +github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -118,8 +120,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA= -github.com/cockroachdb/pebble/v2 v2.1.4 h1:j9wPgMDbkErFdAKYFGhsoCcvzcjR+6zrJ4jhKtJ6bOk= -github.com/cockroachdb/pebble/v2 v2.1.4/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= +github.com/cockroachdb/pebble/v2 v2.1.5 h1:1ziHpaSau6qCXnFpQX3EBOH14yPHA8W66vKxsyrgXgs= +github.com/cockroachdb/pebble/v2 v2.1.5/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI= @@ -157,8 +159,8 @@ github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70d github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -186,8 +188,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gammazero/chanqueue v1.1.2 h1:dZEsxlyANZMyeTRemABqZF8QM9BnE4NBI43Oh3y5fIU= @@ -205,8 +207,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -314,8 +316,8 @@ github.com/guillaumemichel/reservedpool v0.3.0 h1:eqqO/QvTllLBrit7LVtVJBqw4cD0Wd github.com/guillaumemichel/reservedpool v0.3.0/go.mod h1:sXSDIaef81TFdAJglsCFCMfgF5E5Z5xK1tFhjDhvbUc= 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/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c h1:m4bneA0dtaIhyTOJZCvcka670ZwDEiSomj5EARK1Jxc= -github.com/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= +github.com/hanwen/go-fuse/v2 v2.10.1 h1:QAqZuc9+aBtTou+OPruU/hkYQYCkgPtQd2QaepHkTTs= +github.com/hanwen/go-fuse/v2 v2.10.1/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -352,8 +354,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU= github.com/ipfs/bbloom v0.1.0 h1:nIWwfIE3AaG7RCDQIsrUonGCOTp7qSXzxH7ab/ss964= github.com/ipfs/bbloom v0.1.0/go.mod h1:lDy3A3i6ndgEW2z1CaRFvDi5/ZTzgM1IxA/pkL7Wgts= -github.com/ipfs/boxo v0.39.0 h1:u9jLf5pLx5SWROXjHtj8VMvv+iDlMbiTyZ/vVTQ4VhI= -github.com/ipfs/boxo v0.39.0/go.mod h1:k9YCvMjytFguMHndEiGdCGMMj4b7CkdOT44vtgAxOdk= +github.com/ipfs/boxo v0.40.0 h1:AXbum7U+aVWqSdZ5RSvKFAmrwUSxOCHyDYEIkpmb23g= +github.com/ipfs/boxo v0.40.0/go.mod h1:LBlAkRBsiiADMdfhcY5CbX/gLBHla4K89RfROjj31NI= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -382,14 +384,14 @@ github.com/ipfs/go-ds-leveldb v0.5.2 h1:6nmxlQ2zbp4LCNdJVsmHfs9GP0eylfBNxpmY1csp github.com/ipfs/go-ds-leveldb v0.5.2/go.mod h1:2fAwmcvD3WoRT72PzEekHBkQmBDhc39DJGoREiuGmYo= github.com/ipfs/go-ds-measure v0.2.2 h1:4kwvBGbbSXNYe4ANlg7qTIYoZU6mNlqzQHdVqICkqGI= github.com/ipfs/go-ds-measure v0.2.2/go.mod h1:b/87ak0jMgH9Ylt7oH0+XGy4P8jHx9KG09Qz+pOeTIs= -github.com/ipfs/go-ds-pebble v0.5.10 h1:MsSPrq4ubtaWGaIvdE5+L227wEaoxs7nWEb6+lKojNE= -github.com/ipfs/go-ds-pebble v0.5.10/go.mod h1:ShbyLsills0WD9TJavOHu7uEDj/LwDW1WW91G4+W4X8= +github.com/ipfs/go-ds-pebble v0.5.11 h1:ennESxgtR6pXCByAQUsa+IrfPf+59Xb6zaZwrJCnFx0= +github.com/ipfs/go-ds-pebble v0.5.11/go.mod h1:fAwqo8m42YghourN3LQLNNDzp7M+DyJzCK8fpWr6XW8= github.com/ipfs/go-dsqueue v0.2.0 h1:MBi9w3oSiX98Xc+Y7NuJ9G8MI6mAT4IGdO9dHEMCZzU= github.com/ipfs/go-dsqueue v0.2.0/go.mod h1:8FfNQC4DMF/KkzBXRNB9Rb3MKDW0Sh98HMtXYl1mLQE= github.com/ipfs/go-fs-lock v0.1.1 h1:TecsP/Uc7WqYYatasreZQiP9EGRy4ZnKoG4yXxR33nw= github.com/ipfs/go-fs-lock v0.1.1/go.mod h1:2goSXMCw7QfscHmSe09oXiR34DQeUdm+ei+dhonqly0= -github.com/ipfs/go-ipfs-cmds v0.16.0 h1:Oq39Gzz3pWrPwP25SjbfBQugFjKyjFdsHlAMIXdzYzM= -github.com/ipfs/go-ipfs-cmds v0.16.0/go.mod h1:iRNtY9ipM/vH0Yr+/0FY+JfT+trZDQIztDoesmmoTo4= +github.com/ipfs/go-ipfs-cmds v0.16.1 h1:O3xV6v2LN52wL0odvXX6jqlt7G2scuHzQYl80OJ+TOA= +github.com/ipfs/go-ipfs-cmds v0.16.1/go.mod h1:UkHLmJ2MlbLPuUJ0wmuF1R91+DGnwKvcCoEW3MR5CNg= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= @@ -412,18 +414,18 @@ github.com/ipfs/go-ipld-legacy v0.3.0/go.mod h1:Ukef9ARQiX+RVetwH2XiReLgJvQDEXcU github.com/ipfs/go-libdht v0.5.0 h1:ZN+eCqwahZvUeT0e4DsIxRtm78Mc9UR5tmZUiMsrGjQ= github.com/ipfs/go-libdht v0.5.0/go.mod h1:L3YiuFXecLeZZFuuVRM0hjg1GgVhARzUdahFsuqSa7w= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= -github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= -github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= +github.com/ipfs/go-log/v2 v2.9.2 h1:O/5BB0elpkRILvT24rCJ5976wWd7u0nJ436T3rdYdc4= +github.com/ipfs/go-log/v2 v2.9.2/go.mod h1:RziRwwXWhndlk8L75RnEe0zeAYaq2heKtEMc3jqUov0= github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU= github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= github.com/ipfs/go-peertaskqueue v0.8.3 h1:tBPpGJy+A92RqtRFq5amJn0Uuj8Pw8tXi0X3eHfHM8w= github.com/ipfs/go-peertaskqueue v0.8.3/go.mod h1:OqVync4kPOcXEGdj/LKvox9DCB5mkSBeXsPczCxLtYA= github.com/ipfs/go-test v0.3.0 h1:0Y4Uve3tp9HI+2lIJjfOliOrOgv/YpXg/l1y3P4DEYE= github.com/ipfs/go-test v0.3.0/go.mod h1:JK+U8pRpATZb7lsYNSJlCj3WYB3cFfWIbI6nWRM/GFk= -github.com/ipfs/go-unixfsnode v1.10.3 h1:c8sJjuGNkxXAQH75P+f5ngPda/9T+DrboVA0TcDGvGI= -github.com/ipfs/go-unixfsnode v1.10.3/go.mod h1:2Jlc7DoEwr12W+7l8Hr6C7XF4NHST3gIkqSArLhGSxU= -github.com/ipld/go-car/v2 v2.16.0 h1:LWe0vmN/QcQmUU4tr34W5Nv5mNraW+G6jfN2s+ndBco= -github.com/ipld/go-car/v2 v2.16.0/go.mod h1:RqFGWN9ifcXVmCrTAVnfnxiWZk1+jIx67SYhenlmL34= +github.com/ipfs/go-unixfsnode v1.10.4 h1:cMmMyOrSjQkPVQbQvt8trErIn6jhayNf9pBA9oOwfxY= +github.com/ipfs/go-unixfsnode v1.10.4/go.mod h1:Vu1e/s7ToALBBRo38sJ8DwUVWmSeQMTdxk5/rcHl7d0= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c h1:ZFONxHSj6bzzB9eKIu+yS2AazTJe7j9FPesfy4sZSE0= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c/go.mod h1:/4HY8tFZ1q42Mw54ILLPQfjkUqMJxFKqY1yMDKHlYko= github.com/ipld/go-codec-dagpb v1.7.0 h1:hpuvQjCSVSLnTnHXn+QAMR0mLmb1gA6wl10LExo2Ts0= github.com/ipld/go-codec-dagpb v1.7.0/go.mod h1:rD3Zg+zub9ZnxcLwfol/OTQRVjaLzXypgy4UqHQvilM= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= @@ -431,8 +433,8 @@ github.com/ipld/go-ipld-prime v0.23.0 h1:csqdPZH60BsTC+AZrv7fpa27v+09I/oTqyHYYYE github.com/ipld/go-ipld-prime v0.23.0/go.mod h1:46YCFSFNFBJHPjB0pfMuv7Ly7df2eChpkpyPo5SE0bA= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714 h1:cqNk8PEwHnK0vqWln+U/YZhQc9h2NB3KjUjDPZo5Q2s= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714/go.mod h1:ZEUdra3CoqRVRYgAX/jAJO9aZGz6SKtKEG628fHHktY= -github.com/ipshipyard/p2p-forge v0.8.0 h1:yMg3UcAIVV0FDBMMbyZxlUnE6TYew0I+tBPzX6Y2OWM= -github.com/ipshipyard/p2p-forge v0.8.0/go.mod h1:XIyBqyuMGFDYO8wJ6wcbylLvsXbMnTgYPFkydkrVgQM= +github.com/ipshipyard/p2p-forge v0.9.0 h1:Mp/bZ8BX7sxNTyzN5BXbYpOPbggrUbn+Dr5XnJ2kj0s= +github.com/ipshipyard/p2p-forge v0.9.0/go.mod h1:1keK1MRRCu5oNe9uFKfNIIZXOFEF9hgD1iK1DUsjsXQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= @@ -454,8 +456,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -474,8 +476,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.1 h1:oKHx3lgN4e5Nno2LKTMrVx+b+NkDptkO9aDireiBDGE= +github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -494,14 +500,14 @@ github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl9 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.39.1 h1:9RzUBc7zywT4ZNamRSgEvPZmVlK3Y6xdlCYfXXvlR/Q= -github.com/libp2p/go-libp2p-kad-dht v0.39.1/go.mod h1:Po2JugFEkDq9Vig/JXtc153ntOi0q58o4j7IuITCOVs= +github.com/libp2p/go-libp2p-kad-dht v0.40.0 h1:as8U7Y1RX9CTKCBiFBHWKZ6tSS+rE+6WNz+H1+M+wbo= +github.com/libp2p/go-libp2p-kad-dht v0.40.0/go.mod h1:iLUjII47u3/HjxyhucI2lhsl29lrzlAs/ym16+H40jE= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= 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-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o= -github.com/libp2p/go-libp2p-pubsub v0.15.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= +github.com/libp2p/go-libp2p-pubsub v0.16.0 h1:j7G2C8kJwkcAQqYR7Wmq3d75d3Sgw/N0Hhiv0dVx7OY= +github.com/libp2p/go-libp2p-pubsub v0.16.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= github.com/libp2p/go-libp2p-pubsub-router v0.6.0 h1:D30iKdlqDt5ZmLEYhHELCMRj8b4sFAqrUcshIUvVP/s= github.com/libp2p/go-libp2p-pubsub-router v0.6.0/go.mod h1:FY/q0/RBTKsLA7l4vqC2cbRbOvyDotg8PJQ7j8FDudE= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= @@ -537,11 +543,11 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= @@ -832,10 +838,10 @@ 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.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.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= @@ -844,14 +850,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -861,18 +867,20 @@ go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -892,8 +900,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= 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= @@ -904,8 +912,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-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= 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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -934,8 +942,8 @@ golang.org/x/mod v0.4.2/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.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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= @@ -983,8 +991,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1077,10 +1085,10 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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= @@ -1097,13 +1105,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1163,8 +1171,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 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= @@ -1244,10 +1252,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1268,8 +1276,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= 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= diff --git a/docs/experimental-features.md b/docs/experimental-features.md index d025fa89d79..76cbc82435d 100644 --- a/docs/experimental-features.md +++ b/docs/experimental-features.md @@ -507,7 +507,7 @@ ones. This heuristic approach can significantly speed up the process, resulting When it is enabled: - Amino DHT provide operations should complete much faster than with it disabled -- This can be tested with commands such as `ipfs routing provide` +- This can be tested with commands such as `ipfs provide once` **Tradeoffs** diff --git a/docs/metrics.md b/docs/metrics.md index ce287dbaf4a..99a4b9242d7 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -121,5 +121,5 @@ These metrics are automatically added to Gateway handlers, Hostname Gateway, Lib Kubo uses Prometheus for metrics collection for historical reasons, but OpenTelemetry metrics are automatically exposed through the same Prometheus endpoint. These metadata metrics provide context about the instrumentation: -- `otel_scope_info` - Information about instrumentation libraries producing metrics +- `otel_scope_name`, `otel_scope_version`, `otel_scope_schema_url` - Per-metric labels identifying the instrumentation library that produced each metric - `target_info` - Service metadata including version and instance information \ No newline at end of file diff --git a/docs/production/firewall.md b/docs/production/firewall.md new file mode 100644 index 00000000000..79dfb4ab948 --- /dev/null +++ b/docs/production/firewall.md @@ -0,0 +1,207 @@ +# Firewall Setup for Kubo + +By default, kubo's libp2p swarm listens on **port 4001** over both TCP and +UDP. Open both so peers can reach you: + +- **TCP/4001** carries the plain TCP transport (and the optional WebSocket `/ws`). +- **UDP/4001** carries QUIC, WebTransport, and WebRTC-Direct. + +Block either one and kubo falls back to slower relayed or hole-punched +connections. + +The defaults come from [`Addresses.Swarm`](../config.md#addressesswarm): + +```json +[ + "/ip4/0.0.0.0/tcp/4001", + "/ip6/::/tcp/4001", + "/ip4/0.0.0.0/udp/4001/webrtc-direct", + "/ip4/0.0.0.0/udp/4001/quic-v1", + "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport", + "/ip6/::/udp/4001/webrtc-direct", + "/ip6/::/udp/4001/quic-v1", + "/ip6/::/udp/4001/quic-v1/webtransport" +] +``` + +The examples below use [`ufw`](https://help.ubuntu.com/community/UFW), the +default firewall tool on Debian and Ubuntu. The same rules translate to +`firewalld`, `nftables`, or cloud security groups. + +## Check what rules you have + +List active rules: + +```bash +sudo ufw status verbose +``` + +Or with line numbers, useful when deleting one later: + +```bash +sudo ufw status numbered +``` + +A typical SSH-only host looks like this: + +``` +Status: active +Logging: off +Default: deny (incoming), allow (outgoing), disabled (routed) + +To Action From +-- ------ ---- +22/tcp ALLOW IN Anywhere +22/tcp (v6) ALLOW IN Anywhere (v6) +``` + +You want 4001 in that list, on both TCP and UDP. + +## Open port 4001 + +The short way opens both TCP and UDP at once: + +```bash +sudo ufw allow 4001 comment 'ipfs/libp2p swarm' +``` + +One rule per protocol reads more clearly later: + +```bash +sudo ufw allow 4001/tcp comment 'ipfs/libp2p tcp+http+ws' +sudo ufw allow 4001/udp comment 'ipfs/libp2p quic+webtransport+webrtc' +``` + +`ufw` covers IPv4 and IPv6 together when `IPV6=yes` is set in +`/etc/default/ufw` (the default on Ubuntu). + +To limit a rule to one interface or source range: + +```bash +sudo ufw allow in on eth0 to any port 4001 proto tcp +sudo ufw allow in on eth0 to any port 4001 proto udp +sudo ufw allow from 203.0.113.0/24 to any port 4001 +``` + +> [!NOTE] +> A public IPFS node needs to be reachable by anyone. Restrict by source IP +> only on private deployments. + +Check the result: + +```bash +sudo ufw status verbose +``` + +You should see `4001/tcp` and `4001/udp` (and the matching `(v6)` lines). + +## Optional: a `Kubo` application profile + +When you run kubo across many hosts, a `ufw` "application profile" lets you +allow it by name. Create `/etc/ufw/applications.d/kubo`: + +```ini +[Kubo] +title=Kubo +description=ipfs kubo swarm ports +ports=4001/tcp|4001/udp +``` + +Allow it by name: + +```bash +sudo ufw allow Kubo +``` + +Inspect the profile: + +```bash +sudo ufw app info Kubo +``` + +If you later edit the `ports=` line in the profile, push the new ports +into the existing rule with: + +```bash +sudo ufw app update Kubo +``` + +## Different ports? + +If you changed [`Addresses.Swarm`](../config.md#addressesswarm) (for example, +when running several kubo nodes on one host), open the port you chose. Open +both TCP and UDP unless you explicitly disabled a transport in +[`Swarm.Transports.Network`](../config.md#swarmtransportsnetwork). + +## Remove a rule + +Find the rule number: + +```bash +sudo ufw status numbered +``` + +Numbers shift after each delete, so list again between deletes: + +```bash +sudo ufw delete +``` + +Or delete by spec: + +```bash +sudo ufw delete allow 4001/tcp +sudo ufw delete allow 4001/udp +``` + +## Is the daemon healthy? + +To confirm kubo is running and the local block pipeline works: + +```bash +ipfs diag healthy +``` + +It exits 0 when the daemon is up. Use it for container healthchecks. It +only checks local state; for reachability from outside, see the next +section. + +## Can peers reach you? + +`ipfs id` shows the addresses your node advertises. To test them from +outside, ask AutoNAT V2: + +```bash +ipfs swarm addrs autonat +``` + +Look for `Reachability: Public`. The `Reachable` and `Unreachable` lists +break things down by address, so you can see at a glance which protocol is +blocked upstream. + +If you stay `Private` even with `ufw` open, something upstream is blocking +you. Common next steps: + +- **Behind a home or office router (NAT):** let kubo ask the router to + forward the port. Keep + [`Swarm.DisableNatPortMap`](../config.md#swarmdisablenatportmap) at `false` + (the default; this is UPnP / NAT-PMP). The `server` profile disables it, + so if you applied that profile but you are behind a router, set it back + to `false`. +- **No control over the upstream NAT (CGNAT, mobile, locked-down corporate + networks):** keep + [`Swarm.EnableHolePunching`](../config.md#swarmenableholepunching) on + (the default). Peers will then reach you through a relay using DCUtR + (direct connection upgrade through relay). + +More background: the +[libp2p AutoNAT V2 spec](https://github.com/libp2p/specs/blob/master/autonat/autonat-v2.md). + +## Related + +- [`Addresses.Swarm`](../config.md#addressesswarm): the addresses kubo + listens on. +- [`Swarm.Transports.Network`](../config.md#swarmtransportsnetwork): which + transports are enabled. +- [Security section in `config.md`](../config.md#security): port and + exposure guidance for the API, Gateway, and swarm. diff --git a/go.mod b/go.mod index 8036ed3a035..d27cc9bfe91 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,27 @@ module github.com/ipfs/kubo -go 1.26.2 +go 1.26.4 require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 github.com/blang/semver/v4 v4.0.0 - github.com/caddyserver/certmagic v0.23.0 + github.com/caddyserver/certmagic v0.25.3 github.com/cenkalti/backoff/v4 v4.3.0 github.com/ceramicnetwork/go-dag-jose v0.1.1 - github.com/cheggaaa/pb v1.0.29 - github.com/cockroachdb/pebble/v2 v2.1.4 + github.com/cheggaaa/pb/v3 v3.1.7 + github.com/cockroachdb/pebble/v2 v2.1.5 github.com/coreos/go-systemd/v22 v22.7.0 github.com/dustin/go-humanize v1.0.1 github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 - github.com/fsnotify/fsnotify v1.9.0 + github.com/fsnotify/fsnotify v1.10.1 github.com/google/uuid v1.6.0 - github.com/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c + github.com/hanwen/go-fuse/v2 v2.10.1 github.com/hashicorp/go-version v1.9.0 github.com/ipfs-shipyard/nopfs v0.0.14 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 - github.com/ipfs/boxo v0.39.0 + github.com/ipfs/boxo v0.40.0 github.com/ipfs/go-block-format v0.2.3 github.com/ipfs/go-cid v0.6.1 github.com/ipfs/go-cidutil v0.1.1 @@ -31,35 +31,36 @@ require ( github.com/ipfs/go-ds-flatfs v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.2 github.com/ipfs/go-ds-measure v0.2.2 - github.com/ipfs/go-ds-pebble v0.5.10 + github.com/ipfs/go-ds-pebble v0.5.11 github.com/ipfs/go-fs-lock v0.1.1 - github.com/ipfs/go-ipfs-cmds v0.16.0 + github.com/ipfs/go-ipfs-cmds v0.16.1 github.com/ipfs/go-ipld-cbor v0.2.1 github.com/ipfs/go-ipld-format v0.6.3 github.com/ipfs/go-ipld-git v0.1.1 github.com/ipfs/go-ipld-legacy v0.3.0 - github.com/ipfs/go-log/v2 v2.9.1 + github.com/ipfs/go-log/v2 v2.9.2 github.com/ipfs/go-metrics-interface v0.3.0 github.com/ipfs/go-metrics-prometheus v0.1.0 github.com/ipfs/go-test v0.3.0 - github.com/ipfs/go-unixfsnode v1.10.3 - github.com/ipld/go-car/v2 v2.16.0 + github.com/ipfs/go-unixfsnode v1.10.4 + github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c github.com/ipld/go-codec-dagpb v1.7.0 github.com/ipld/go-ipld-prime v0.23.0 - github.com/ipshipyard/p2p-forge v0.8.0 + github.com/ipshipyard/p2p-forge v0.9.0 github.com/jbenet/go-temp-err-catcher v0.1.0 github.com/julienschmidt/httprouter v1.3.0 github.com/libp2p/go-doh-resolver v0.5.0 github.com/libp2p/go-libp2p v0.48.0 github.com/libp2p/go-libp2p-http v0.5.0 - github.com/libp2p/go-libp2p-kad-dht v0.39.1 + github.com/libp2p/go-libp2p-kad-dht v0.40.0 github.com/libp2p/go-libp2p-kbucket v0.8.0 - github.com/libp2p/go-libp2p-pubsub v0.15.0 + github.com/libp2p/go-libp2p-pubsub v0.16.0 github.com/libp2p/go-libp2p-pubsub-router v0.6.0 github.com/libp2p/go-libp2p-record v0.3.1 github.com/libp2p/go-libp2p-routing-helpers v0.7.5 github.com/libp2p/go-libp2p-testing v0.12.0 github.com/libp2p/go-socket-activation v0.1.1 + github.com/mattn/go-isatty v0.0.22 github.com/miekg/dns v1.1.72 github.com/multiformats/go-multiaddr v0.16.1 github.com/multiformats/go-multiaddr-dns v0.5.0 @@ -77,21 +78,22 @@ require ( github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 - go.opentelemetry.io/contrib/propagators/autoprop v0.46.1 - go.opentelemetry.io/otel v1.42.0 - go.opentelemetry.io/otel/exporters/prometheus v0.56.0 - go.opentelemetry.io/otel/sdk v1.42.0 - go.opentelemetry.io/otel/sdk/metric v1.42.0 - go.opentelemetry.io/otel/trace v1.42.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 + go.opentelemetry.io/contrib/propagators/autoprop v0.68.0 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/exporters/prometheus v0.65.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/metric v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 go.uber.org/dig v1.19.0 go.uber.org/fx v1.24.0 - go.uber.org/zap v1.27.1 - golang.org/x/crypto v0.50.0 - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f - golang.org/x/mod v0.35.0 + go.uber.org/zap v1.28.0 + golang.org/x/crypto v0.51.0 + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a + golang.org/x/mod v0.36.0 golang.org/x/sync v0.20.0 - golang.org/x/sys v0.43.0 + golang.org/x/sys v0.45.0 + golang.org/x/term v0.43.0 google.golang.org/protobuf v1.36.11 ) @@ -103,11 +105,12 @@ require ( github.com/Jorropo/jsync v1.0.1 // indirect github.com/RaduBerinde/axisds v0.1.0 // indirect github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -124,9 +127,8 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/ristretto v0.0.2 // indirect - github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dunglas/httpsfv v1.1.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect @@ -134,7 +136,7 @@ require ( github.com/gammazero/chanqueue v1.1.2 // indirect github.com/gammazero/deque v1.2.1 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -160,12 +162,12 @@ require ( github.com/ipfs/go-libdht v0.5.0 // indirect github.com/ipfs/go-peertaskqueue v0.8.3 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/koron/go-ssdp v0.0.6 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/libdns/libdns v1.0.0-beta.1 // indirect + github.com/libdns/libdns v1.1.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.3.0 // indirect @@ -178,11 +180,10 @@ require ( github.com/libp2p/go-yamux/v5 v5.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/mholt/acmez/v3 v3.1.2 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/minlz v1.0.1-0.20250507153514-87eb42fe8882 // indirect @@ -218,12 +219,13 @@ require ( github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.20.1 // indirect github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/quic-go/webtransport-go v0.10.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/slok/go-http-metrics v0.13.0 // indirect @@ -240,33 +242,32 @@ require ( github.com/wlynxg/anet v0.0.5 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/propagators/aws v1.21.1 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.21.1 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.21.1 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.43.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.43.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.uber.org/mock v0.5.2 // indirect + go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.45.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/grpc v1.79.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // 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 52db29c1247..06473279b37 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -58,6 +60,8 @@ github.com/RaduBerinde/axisds v0.1.0 h1:YItk/RmU5nvlsv/awo2Fjx97Mfpt4JfgtEVAGPrL github.com/RaduBerinde/axisds v0.1.0/go.mod h1:UHGJonU9z4YYGKJxSaC6/TNcLOBptpmM5m2Cksbnw0Y= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 h1:bsU8Tzxr/PNz75ayvCnxKZWEYdLMPDkUgticP4a4Bvk= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54/go.mod h1:0tr7FllbE9gJkHq7CVeeDDFAFKQVy5RnCSSNBOvdqbc= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -97,10 +101,10 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 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/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A= +github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -116,8 +120,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -135,8 +139,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA= -github.com/cockroachdb/pebble/v2 v2.1.4 h1:j9wPgMDbkErFdAKYFGhsoCcvzcjR+6zrJ4jhKtJ6bOk= -github.com/cockroachdb/pebble/v2 v2.1.4/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= +github.com/cockroachdb/pebble/v2 v2.1.5 h1:1ziHpaSau6qCXnFpQX3EBOH14yPHA8W66vKxsyrgXgs= +github.com/cockroachdb/pebble/v2 v2.1.5/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI= @@ -175,8 +179,8 @@ github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrV github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -194,9 +198,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= @@ -211,8 +214,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gammazero/chanqueue v1.1.2 h1:dZEsxlyANZMyeTRemABqZF8QM9BnE4NBI43Oh3y5fIU= @@ -230,8 +233,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -352,8 +355,8 @@ github.com/guillaumemichel/reservedpool v0.3.0 h1:eqqO/QvTllLBrit7LVtVJBqw4cD0Wd github.com/guillaumemichel/reservedpool v0.3.0/go.mod h1:sXSDIaef81TFdAJglsCFCMfgF5E5Z5xK1tFhjDhvbUc= 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/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c h1:m4bneA0dtaIhyTOJZCvcka670ZwDEiSomj5EARK1Jxc= -github.com/hanwen/go-fuse/v2 v2.9.1-0.20260323175136-8b5aa92e8e7c/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= +github.com/hanwen/go-fuse/v2 v2.10.1 h1:QAqZuc9+aBtTou+OPruU/hkYQYCkgPtQd2QaepHkTTs= +github.com/hanwen/go-fuse/v2 v2.10.1/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -392,8 +395,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU= github.com/ipfs/bbloom v0.1.0 h1:nIWwfIE3AaG7RCDQIsrUonGCOTp7qSXzxH7ab/ss964= github.com/ipfs/bbloom v0.1.0/go.mod h1:lDy3A3i6ndgEW2z1CaRFvDi5/ZTzgM1IxA/pkL7Wgts= -github.com/ipfs/boxo v0.39.0 h1:u9jLf5pLx5SWROXjHtj8VMvv+iDlMbiTyZ/vVTQ4VhI= -github.com/ipfs/boxo v0.39.0/go.mod h1:k9YCvMjytFguMHndEiGdCGMMj4b7CkdOT44vtgAxOdk= +github.com/ipfs/boxo v0.40.0 h1:AXbum7U+aVWqSdZ5RSvKFAmrwUSxOCHyDYEIkpmb23g= +github.com/ipfs/boxo v0.40.0/go.mod h1:LBlAkRBsiiADMdfhcY5CbX/gLBHla4K89RfROjj31NI= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -422,14 +425,14 @@ github.com/ipfs/go-ds-leveldb v0.5.2 h1:6nmxlQ2zbp4LCNdJVsmHfs9GP0eylfBNxpmY1csp github.com/ipfs/go-ds-leveldb v0.5.2/go.mod h1:2fAwmcvD3WoRT72PzEekHBkQmBDhc39DJGoREiuGmYo= github.com/ipfs/go-ds-measure v0.2.2 h1:4kwvBGbbSXNYe4ANlg7qTIYoZU6mNlqzQHdVqICkqGI= github.com/ipfs/go-ds-measure v0.2.2/go.mod h1:b/87ak0jMgH9Ylt7oH0+XGy4P8jHx9KG09Qz+pOeTIs= -github.com/ipfs/go-ds-pebble v0.5.10 h1:MsSPrq4ubtaWGaIvdE5+L227wEaoxs7nWEb6+lKojNE= -github.com/ipfs/go-ds-pebble v0.5.10/go.mod h1:ShbyLsills0WD9TJavOHu7uEDj/LwDW1WW91G4+W4X8= +github.com/ipfs/go-ds-pebble v0.5.11 h1:ennESxgtR6pXCByAQUsa+IrfPf+59Xb6zaZwrJCnFx0= +github.com/ipfs/go-ds-pebble v0.5.11/go.mod h1:fAwqo8m42YghourN3LQLNNDzp7M+DyJzCK8fpWr6XW8= github.com/ipfs/go-dsqueue v0.2.0 h1:MBi9w3oSiX98Xc+Y7NuJ9G8MI6mAT4IGdO9dHEMCZzU= github.com/ipfs/go-dsqueue v0.2.0/go.mod h1:8FfNQC4DMF/KkzBXRNB9Rb3MKDW0Sh98HMtXYl1mLQE= github.com/ipfs/go-fs-lock v0.1.1 h1:TecsP/Uc7WqYYatasreZQiP9EGRy4ZnKoG4yXxR33nw= github.com/ipfs/go-fs-lock v0.1.1/go.mod h1:2goSXMCw7QfscHmSe09oXiR34DQeUdm+ei+dhonqly0= -github.com/ipfs/go-ipfs-cmds v0.16.0 h1:Oq39Gzz3pWrPwP25SjbfBQugFjKyjFdsHlAMIXdzYzM= -github.com/ipfs/go-ipfs-cmds v0.16.0/go.mod h1:iRNtY9ipM/vH0Yr+/0FY+JfT+trZDQIztDoesmmoTo4= +github.com/ipfs/go-ipfs-cmds v0.16.1 h1:O3xV6v2LN52wL0odvXX6jqlt7G2scuHzQYl80OJ+TOA= +github.com/ipfs/go-ipfs-cmds v0.16.1/go.mod h1:UkHLmJ2MlbLPuUJ0wmuF1R91+DGnwKvcCoEW3MR5CNg= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= @@ -452,8 +455,8 @@ github.com/ipfs/go-ipld-legacy v0.3.0/go.mod h1:Ukef9ARQiX+RVetwH2XiReLgJvQDEXcU github.com/ipfs/go-libdht v0.5.0 h1:ZN+eCqwahZvUeT0e4DsIxRtm78Mc9UR5tmZUiMsrGjQ= github.com/ipfs/go-libdht v0.5.0/go.mod h1:L3YiuFXecLeZZFuuVRM0hjg1GgVhARzUdahFsuqSa7w= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= -github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= -github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= +github.com/ipfs/go-log/v2 v2.9.2 h1:O/5BB0elpkRILvT24rCJ5976wWd7u0nJ436T3rdYdc4= +github.com/ipfs/go-log/v2 v2.9.2/go.mod h1:RziRwwXWhndlk8L75RnEe0zeAYaq2heKtEMc3jqUov0= github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU= github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= github.com/ipfs/go-metrics-prometheus v0.1.0 h1:bApWOHkrH3VTBHzTHrZSfq4n4weOZDzZFxUXv+HyKcA= @@ -462,10 +465,10 @@ github.com/ipfs/go-peertaskqueue v0.8.3 h1:tBPpGJy+A92RqtRFq5amJn0Uuj8Pw8tXi0X3e github.com/ipfs/go-peertaskqueue v0.8.3/go.mod h1:OqVync4kPOcXEGdj/LKvox9DCB5mkSBeXsPczCxLtYA= github.com/ipfs/go-test v0.3.0 h1:0Y4Uve3tp9HI+2lIJjfOliOrOgv/YpXg/l1y3P4DEYE= github.com/ipfs/go-test v0.3.0/go.mod h1:JK+U8pRpATZb7lsYNSJlCj3WYB3cFfWIbI6nWRM/GFk= -github.com/ipfs/go-unixfsnode v1.10.3 h1:c8sJjuGNkxXAQH75P+f5ngPda/9T+DrboVA0TcDGvGI= -github.com/ipfs/go-unixfsnode v1.10.3/go.mod h1:2Jlc7DoEwr12W+7l8Hr6C7XF4NHST3gIkqSArLhGSxU= -github.com/ipld/go-car/v2 v2.16.0 h1:LWe0vmN/QcQmUU4tr34W5Nv5mNraW+G6jfN2s+ndBco= -github.com/ipld/go-car/v2 v2.16.0/go.mod h1:RqFGWN9ifcXVmCrTAVnfnxiWZk1+jIx67SYhenlmL34= +github.com/ipfs/go-unixfsnode v1.10.4 h1:cMmMyOrSjQkPVQbQvt8trErIn6jhayNf9pBA9oOwfxY= +github.com/ipfs/go-unixfsnode v1.10.4/go.mod h1:Vu1e/s7ToALBBRo38sJ8DwUVWmSeQMTdxk5/rcHl7d0= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c h1:ZFONxHSj6bzzB9eKIu+yS2AazTJe7j9FPesfy4sZSE0= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c/go.mod h1:/4HY8tFZ1q42Mw54ILLPQfjkUqMJxFKqY1yMDKHlYko= github.com/ipld/go-codec-dagpb v1.7.0 h1:hpuvQjCSVSLnTnHXn+QAMR0mLmb1gA6wl10LExo2Ts0= github.com/ipld/go-codec-dagpb v1.7.0/go.mod h1:rD3Zg+zub9ZnxcLwfol/OTQRVjaLzXypgy4UqHQvilM= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= @@ -473,8 +476,8 @@ github.com/ipld/go-ipld-prime v0.23.0 h1:csqdPZH60BsTC+AZrv7fpa27v+09I/oTqyHYYYE github.com/ipld/go-ipld-prime v0.23.0/go.mod h1:46YCFSFNFBJHPjB0pfMuv7Ly7df2eChpkpyPo5SE0bA= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714 h1:cqNk8PEwHnK0vqWln+U/YZhQc9h2NB3KjUjDPZo5Q2s= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714/go.mod h1:ZEUdra3CoqRVRYgAX/jAJO9aZGz6SKtKEG628fHHktY= -github.com/ipshipyard/p2p-forge v0.8.0 h1:yMg3UcAIVV0FDBMMbyZxlUnE6TYew0I+tBPzX6Y2OWM= -github.com/ipshipyard/p2p-forge v0.8.0/go.mod h1:XIyBqyuMGFDYO8wJ6wcbylLvsXbMnTgYPFkydkrVgQM= +github.com/ipshipyard/p2p-forge v0.9.0 h1:Mp/bZ8BX7sxNTyzN5BXbYpOPbggrUbn+Dr5XnJ2kj0s= +github.com/ipshipyard/p2p-forge v0.9.0/go.mod h1:1keK1MRRCu5oNe9uFKfNIIZXOFEF9hgD1iK1DUsjsXQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= @@ -503,8 +506,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -525,8 +528,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.1 h1:oKHx3lgN4e5Nno2LKTMrVx+b+NkDptkO9aDireiBDGE= +github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -549,14 +556,14 @@ github.com/libp2p/go-libp2p-gostream v0.6.0 h1:QfAiWeQRce6pqnYfmIVWJFXNdDyfiR/qk github.com/libp2p/go-libp2p-gostream v0.6.0/go.mod h1:Nywu0gYZwfj7Jc91PQvbGU8dIpqbQQkjWgDuOrFaRdA= github.com/libp2p/go-libp2p-http v0.5.0 h1:+x0AbLaUuLBArHubbbNRTsgWz0RjNTy6DJLOxQ3/QBc= github.com/libp2p/go-libp2p-http v0.5.0/go.mod h1:glh87nZ35XCQyFsdzZps6+F4HYI6DctVFY5u1fehwSg= -github.com/libp2p/go-libp2p-kad-dht v0.39.1 h1:9RzUBc7zywT4ZNamRSgEvPZmVlK3Y6xdlCYfXXvlR/Q= -github.com/libp2p/go-libp2p-kad-dht v0.39.1/go.mod h1:Po2JugFEkDq9Vig/JXtc153ntOi0q58o4j7IuITCOVs= +github.com/libp2p/go-libp2p-kad-dht v0.40.0 h1:as8U7Y1RX9CTKCBiFBHWKZ6tSS+rE+6WNz+H1+M+wbo= +github.com/libp2p/go-libp2p-kad-dht v0.40.0/go.mod h1:iLUjII47u3/HjxyhucI2lhsl29lrzlAs/ym16+H40jE= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= 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-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o= -github.com/libp2p/go-libp2p-pubsub v0.15.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= +github.com/libp2p/go-libp2p-pubsub v0.16.0 h1:j7G2C8kJwkcAQqYR7Wmq3d75d3Sgw/N0Hhiv0dVx7OY= +github.com/libp2p/go-libp2p-pubsub v0.16.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4= github.com/libp2p/go-libp2p-pubsub-router v0.6.0 h1:D30iKdlqDt5ZmLEYhHELCMRj8b4sFAqrUcshIUvVP/s= github.com/libp2p/go-libp2p-pubsub-router v0.6.0/go.mod h1:FY/q0/RBTKsLA7l4vqC2cbRbOvyDotg8PJQ7j8FDudE= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= @@ -591,25 +598,20 @@ github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8 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= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= @@ -795,6 +797,8 @@ github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= 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= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -813,8 +817,8 @@ github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRC github.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI= github.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -954,38 +958,38 @@ 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.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.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/contrib/propagators/autoprop v0.46.1 h1:cXTYcMjY0dsYokAuo8LbNBQxpF8VgTHdiHJJ1zlIXl4= -go.opentelemetry.io/contrib/propagators/autoprop v0.46.1/go.mod h1:WZxgny1/6+j67B1s72PLJ4bGjidoWFzSmLNfJKVt2bo= -go.opentelemetry.io/contrib/propagators/aws v1.21.1 h1:uQIQIDWb0gzyvon2ICnghpLAf9w7ADOCUiIiwCQgR2o= -go.opentelemetry.io/contrib/propagators/aws v1.21.1/go.mod h1:kCcto3ACQxm+VrkQX/NK/TkDmAd99MQhvffzyTKhzL4= -go.opentelemetry.io/contrib/propagators/b3 v1.21.1 h1:WPYiUgmw3+b7b3sQ1bFBFAf0q+Di9dvNc3AtYfnT4RQ= -go.opentelemetry.io/contrib/propagators/b3 v1.21.1/go.mod h1:EmzokPoSqsYMBVK4nRnhsfm5mbn8J1eDuz/U1UaQaWg= -go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k= -go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY= -go.opentelemetry.io/contrib/propagators/ot v1.21.1 h1:3TN5vkXjKYWp0YdMcnUEC/A+pBPvqz9V3nCS2xmcurk= -go.opentelemetry.io/contrib/propagators/ot v1.21.1/go.mod h1:oy0MYCbS/b3cqUDW37wBWtlwBIsutngS++Lklpgh+fc= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/contrib/propagators/autoprop v0.68.0 h1:wLGFvNBPqQhzBn0QRBZjrriH8lZ9gqtTz8ufHEjLg7k= +go.opentelemetry.io/contrib/propagators/autoprop v0.68.0/go.mod h1:evWK9nCqCzH8nhclTlpkdUzmxrmJQ2mrWCdKIvyOYec= +go.opentelemetry.io/contrib/propagators/aws v1.43.0 h1:EwnsB3cXRLAh7/Nr/9rMuGw73nfb3z6uAvVDjRrbeUg= +go.opentelemetry.io/contrib/propagators/aws v1.43.0/go.mod h1:CJjTym6F87tEdm61Qvnz5xrV8vKlH4C92djiqcn62k8= +go.opentelemetry.io/contrib/propagators/b3 v1.43.0 h1:CETqV3QLLPTy5yNrqyMr41VnAOOD4lsRved7n4QG00A= +go.opentelemetry.io/contrib/propagators/b3 v1.43.0/go.mod h1:Q4mCiCdziYzpNR0g+6UqVotAlCDZdzz6L8jwY4knOrw= +go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 h1:peiLMz1+aqJE+3L4mOVtR9wlmv+yh/JVYXCBjqmzJJE= +go.opentelemetry.io/contrib/propagators/jaeger v1.43.0/go.mod h1:Agvif+4A8p/3UtZzJ0MCcDEuQwgtrzM71DueU41DCs8= +go.opentelemetry.io/contrib/propagators/ot v1.43.0 h1:Hh1HahlGc81AOE7siqi1tVOlbanY/UxMMWedpb0d5oQ= +go.opentelemetry.io/contrib/propagators/ot v1.43.0/go.mod h1:58MlyS7lghzYvAm5LN9gGmZpCMQEMB5vpZp9SRgOyE4= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -995,18 +999,20 @@ go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1026,8 +1032,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= 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= @@ -1038,8 +1044,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-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= 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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1068,8 +1074,8 @@ golang.org/x/mod v0.4.2/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.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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= @@ -1121,8 +1127,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1226,20 +1232,19 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1251,13 +1256,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1317,8 +1322,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 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= @@ -1398,10 +1403,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1422,8 +1427,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= 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= diff --git a/mk/git.mk b/mk/git.mk index 9474c2d3364..62d48a4b1d7 100644 --- a/mk/git.mk +++ b/mk/git.mk @@ -10,3 +10,12 @@ ifeq ($(findstring dirty,$(git-hash)),) else git-tag:= endif + +# Normalize `origin` to `host/org/repo` for runtime fork detection via +# Version.AgentSuffix. Handles ssh and https forms, strips `.git`, drops +# userinfo. Empty when no git, no `origin`, or git is unavailable. +git-origin:=$(shell git remote get-url origin 2>/dev/null \ + | sed -E -e 's|^git@([^:]+):|\1/|' \ + -e 's|^[a-z]+://||' \ + -e 's|^[^/]+@||' \ + -e 's|\.git$$||') diff --git a/repo/fsrepo/migrations/fetch_test.go b/repo/fsrepo/migrations/fetch_test.go index 9236eb655a9..b7a14e496ef 100644 --- a/repo/fsrepo/migrations/fetch_test.go +++ b/repo/fsrepo/migrations/fetch_test.go @@ -3,11 +3,17 @@ package migrations import ( "bufio" "bytes" + "context" + "errors" "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "runtime" "strings" + "sync" + "sync/atomic" "testing" ) @@ -158,6 +164,93 @@ func TestFetchBinary(t *testing.T) { } } +// TestHttpFetcherUserAgent guards against a regression where NewHttpFetcher +// accepts a userAgent parameter but forgets to store it on the struct, +// silently sending Go's default "Go-http-client/1.1" instead of the +// migration agent string. +func TestHttpFetcherUserAgent(t *testing.T) { + const wantUA = "kubo/migration" + + var gotUA string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUA = r.Header.Get("User-Agent") + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + fetcher := NewHttpFetcher("/ipfs/bafyreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", srv.URL, wantUA, 0) + _, _ = fetcher.Fetch(t.Context(), "/anything") + + if gotUA != wantUA { + t.Fatalf("User-Agent: got %q, want %q", gotUA, wantUA) + } +} + +// TestMigrationDownloadSourcesFailover is an end-to-end check that two +// gateways listed in Migration.DownloadSources (here passed straight into +// GetMigrationFetcher, the same path ReadMigrationConfig feeds) cooperate via +// MultiFetcher: when the first gateway either errors with 404 or returns +// bytes that don't parse as a CAR, the second gateway is attempted and the +// migration data flows through. +func TestMigrationDownloadSourcesFailover(t *testing.T) { + ctx := t.Context() + + t.Run("first gateway returns 404", func(t *testing.T) { + var badHits atomic.Int64 + bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + badHits.Add(1) + http.Error(w, "not found", http.StatusNotFound) + })) + defer bad.Close() + + // Migration.DownloadSources order: bad first, real test gateway second. + fetcher, err := GetMigrationFetcher([]string{bad.URL, testServer.URL}, testIpfsDist, nil) + if err != nil { + t.Fatalf("GetMigrationFetcher: %v", err) + } + defer fetcher.Close() + + out, err := fetcher.Fetch(ctx, "/kubo/versions") + if err != nil { + t.Fatalf("expected failover to second gateway, got: %v", err) + } + if len(out) < 6 { + t.Fatalf("second gateway should have served the versions file, got %d bytes", len(out)) + } + if badHits.Load() == 0 { + t.Fatal("first gateway was never tried; the failover path did not actually run") + } + }) + + t.Run("first gateway returns invalid CAR bytes", func(t *testing.T) { + var badHits atomic.Int64 + bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + badHits.Add(1) + w.Header().Set("Content-Type", "application/vnd.ipld.car") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("this is definitely not a valid CAR file")) + })) + defer bad.Close() + + fetcher, err := GetMigrationFetcher([]string{bad.URL, testServer.URL}, testIpfsDist, nil) + if err != nil { + t.Fatalf("GetMigrationFetcher: %v", err) + } + defer fetcher.Close() + + out, err := fetcher.Fetch(ctx, "/kubo/versions") + if err != nil { + t.Fatalf("expected failover to second gateway, got: %v", err) + } + if len(out) < 6 { + t.Fatalf("second gateway should have served the versions file, got %d bytes", len(out)) + } + if badHits.Load() == 0 { + t.Fatal("first gateway was never tried; the failover path did not actually run") + } + }) +} + func TestMultiFetcher(t *testing.T) { ctx := t.Context() @@ -175,3 +268,185 @@ func TestMultiFetcher(t *testing.T) { fmt.Println("unexpected more data") } } + +// TestMultiFetcherQuarantine verifies that a fetcher which fails once is +// moved to the back of the rotation on subsequent calls, so a dead gateway +// does not cost the full HTTP timeout on every parallel migration download. +func TestMultiFetcherQuarantine(t *testing.T) { + ctx := t.Context() + + tracker := &countingFetcher{} + good := NewHttpFetcher(testIpfsDist, testServer.URL, "", 0) + mf := NewMultiFetcher(tracker, good) + + // First call: tracker is healthy, gets tried first, fails. good takes over. + if _, err := mf.Fetch(ctx, "/kubo/versions"); err != nil { + t.Fatalf("first fetch: %v", err) + } + if got := tracker.calls.Load(); got != 1 { + t.Fatalf("expected tracker to be tried once, got %d", got) + } + + // Second call: tracker is quarantined and must not be tried while good + // is still healthy. + if _, err := mf.Fetch(ctx, "/kubo/versions"); err != nil { + t.Fatalf("second fetch: %v", err) + } + if got := tracker.calls.Load(); got != 1 { + t.Fatalf("expected tracker to stay quarantined, got %d calls", got) + } +} + +// TestMultiFetcherQuarantineReset verifies that when every fetcher fails in a +// single Fetch call, the quarantine resets so the next call retries all +// fetchers from scratch rather than inheriting a fully poisoned set. +func TestMultiFetcherQuarantineReset(t *testing.T) { + ctx := t.Context() + + a := &countingFetcher{} + b := &countingFetcher{} + mf := NewMultiFetcher(a, b) + + if _, err := mf.Fetch(ctx, "/anything"); err == nil { + t.Fatal("expected error when all fetchers fail") + } + if ca, cb := a.calls.Load(), b.calls.Load(); ca != 1 || cb != 1 { + t.Fatalf("first call: expected each fetcher tried once, got a=%d b=%d", ca, cb) + } + + if _, err := mf.Fetch(ctx, "/anything"); err == nil { + t.Fatal("expected error when all fetchers fail") + } + // After the total wipeout, both should have been retried fresh, not + // skipped as quarantined. + if ca, cb := a.calls.Load(), b.calls.Load(); ca != 2 || cb != 2 { + t.Fatalf("second call after reset: expected each fetcher tried again, got a=%d b=%d", ca, cb) + } +} + +// TestMultiFetcherExhaustionCap verifies the MultiFetcher gives up after +// maxMultiFetcherFullLoopFailures full rotations, returning +// ErrMultiFetcherExhausted without trying inner fetchers again. +func TestMultiFetcherExhaustionCap(t *testing.T) { + ctx := t.Context() + + a := &countingFetcher{} + b := &countingFetcher{} + mf := NewMultiFetcher(a, b) + + // Three failed full rotations should latch the breaker. + for i := range maxMultiFetcherFullLoopFailures { + if _, err := mf.Fetch(ctx, "/x"); err == nil { + t.Fatalf("rotation %d: expected error", i+1) + } + } + + expectedCalls := int64(maxMultiFetcherFullLoopFailures) + if ca, cb := a.calls.Load(), b.calls.Load(); ca != expectedCalls || cb != expectedCalls { + t.Fatalf("after %d rotations: expected each fetcher called %d times, got a=%d b=%d", + maxMultiFetcherFullLoopFailures, expectedCalls, ca, cb) + } + + // Subsequent calls must hard-error with ErrMultiFetcherExhausted and + // must not invoke the inner fetchers again. + _, err := mf.Fetch(ctx, "/x") + if !errors.Is(err, ErrMultiFetcherExhausted) { + t.Fatalf("expected ErrMultiFetcherExhausted, got %v", err) + } + if ca, cb := a.calls.Load(), b.calls.Load(); ca != expectedCalls || cb != expectedCalls { + t.Fatalf("inner fetchers called after exhaustion: a=%d b=%d", ca, cb) + } +} + +// TestMultiFetcherConcurrent exercises the locking paths under -race by +// hammering one MultiFetcher from many goroutines, mirroring how +// fetchMigrations spawns parallel downloads against a shared fetcher. +func TestMultiFetcherConcurrent(t *testing.T) { + ctx := t.Context() + + bad := &countingFetcher{} + good := NewHttpFetcher(testIpfsDist, testServer.URL, "", 0) + mf := NewMultiFetcher(bad, good) + + const goroutines = 16 + const callsPerGoroutine = 8 + + var wg sync.WaitGroup + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + for range callsPerGoroutine { + if _, err := mf.Fetch(ctx, "/kubo/versions"); err != nil { + t.Errorf("unexpected fetch error: %v", err) + return + } + } + }() + } + wg.Wait() +} + +// TestMultiFetcherSuccessResetsCounter verifies that any successful Fetch +// resets the loop-failure counter, so transient blips during a long session +// don't accumulate toward the exhaustion cap. +func TestMultiFetcherSuccessResetsCounter(t *testing.T) { + ctx := t.Context() + + bad := &countingFetcher{} + good := NewHttpFetcher(testIpfsDist, testServer.URL, "", 0) + mf := NewMultiFetcher(bad, good) + + // Many alternating success calls must not trip the breaker. + for i := range maxMultiFetcherFullLoopFailures * 3 { + if _, err := mf.Fetch(ctx, "/kubo/versions"); err != nil { + t.Fatalf("call %d: %v", i+1, err) + } + } + if err := mf.exhaustedErr(); err != nil { + t.Fatalf("breaker tripped despite repeated successes: %v", err) + } +} + +// TestMultiFetcherContextCancelled verifies that a cancelled context exits +// the rotation early without quarantining every fetcher or counting toward +// the exhaustion cap. Otherwise three user Ctrl-Cs in a row would latch +// ErrMultiFetcherExhausted on a perfectly healthy gateway list. +func TestMultiFetcherContextCancelled(t *testing.T) { + a := &countingFetcher{} + b := &countingFetcher{} + mf := NewMultiFetcher(a, b) + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + for i := range maxMultiFetcherFullLoopFailures + 2 { + _, err := mf.Fetch(ctx, "/x") + if !errors.Is(err, context.Canceled) { + t.Fatalf("call %d: expected context.Canceled, got %v", i+1, err) + } + } + + // Each call should have exited after the first fetcher returned the + // cancellation error, so b is never tried and the breaker never latches. + if got := b.calls.Load(); got != 0 { + t.Fatalf("second fetcher should not be tried after cancellation, got %d calls", got) + } + if err := mf.exhaustedErr(); err != nil { + t.Fatalf("breaker latched on cancelled-context loops: %v", err) + } +} + +// countingFetcher always errors and records how many times it was called. +// The counter is atomic so the fetcher is safe to share across goroutines +// during -race tests. +type countingFetcher struct { + calls atomic.Int64 +} + +func (c *countingFetcher) Fetch(ctx context.Context, _ string) ([]byte, error) { + c.calls.Add(1) + return nil, fmt.Errorf("countingFetcher always fails") +} + +func (c *countingFetcher) Close() error { return nil } diff --git a/repo/fsrepo/migrations/fetcher.go b/repo/fsrepo/migrations/fetcher.go index cc48a3b779b..a5c374698f7 100644 --- a/repo/fsrepo/migrations/fetcher.go +++ b/repo/fsrepo/migrations/fetcher.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "sync" ) const ( @@ -16,8 +17,20 @@ const ( // Distribution environ variable. envIpfsDistPath = "IPFS_DIST_PATH" + + // maxMultiFetcherFullLoopFailures caps how many times a MultiFetcher + // may exhaust every fetcher before it gives up for the rest of its + // lifetime. Without a cap, a fully unreachable network would keep + // every Fetch call paying the full per-gateway timeout once per call. + maxMultiFetcherFullLoopFailures = 3 ) +// ErrMultiFetcherExhausted is returned by MultiFetcher.Fetch after every +// fetcher has failed maxMultiFetcherFullLoopFailures full rotations in a row. +// The message points the user at the recovery path: replacing the gateway +// list via Migration.DownloadSources in the Kubo config. +var ErrMultiFetcherExhausted = errors.New("migration download exhausted: every configured gateway failed; add a reachable HTTPS gateway to Migration.DownloadSources in your Kubo config and retry") + type Fetcher interface { // Fetch attempts to fetch the file at the given ipfs path. Fetch(ctx context.Context, filePath string) ([]byte, error) @@ -25,10 +38,25 @@ type Fetcher interface { Close() error } -// MultiFetcher holds multiple Fetchers and provides a Fetch that tries each -// until one succeeds. +// MultiFetcher tries each Fetcher in turn until one succeeds. A fetcher that +// has already failed in this MultiFetcher's lifetime moves to the back of the +// rotation; if every healthy fetcher fails, the quarantined ones run as a +// fallback so a transient outage can self-heal. If every fetcher fails in a +// single call, the quarantine resets and the next call starts fresh, but +// only up to maxMultiFetcherFullLoopFailures times: after that the +// MultiFetcher returns ErrMultiFetcherExhausted without trying again. +// +// This acts as a session-scoped circuit breaker: when migrations issue many +// parallel downloads through one MultiFetcher, the first failure drops a +// dead gateway from rotation for the rest of the session instead of charging +// every goroutine the full HTTP timeout against it. type MultiFetcher struct { fetchers []Fetcher + + mu sync.Mutex + failed map[int]struct{} + loopFailures int + exhausted error } type limitReadCloser struct { @@ -41,25 +69,104 @@ type limitReadCloser struct { func NewMultiFetcher(f ...Fetcher) *MultiFetcher { mf := &MultiFetcher{ fetchers: make([]Fetcher, len(f)), + failed: make(map[int]struct{}), } copy(mf.fetchers, f) return mf } -// Fetch attempts to fetch the file at each of its fetchers until one succeeds. +// Fetch tries each fetcher until one succeeds. Fetchers that have already +// failed in this session are tried last. Once every fetcher has failed +// maxMultiFetcherFullLoopFailures full loops in a row, Fetch returns +// ErrMultiFetcherExhausted without further attempts. func (f *MultiFetcher) Fetch(ctx context.Context, ipfsPath string) ([]byte, error) { + if err := f.exhaustedErr(); err != nil { + return nil, err + } + var errs []error - for _, fetcher := range f.fetchers { - out, err := fetcher.Fetch(ctx, ipfsPath) + for _, i := range f.tryOrder() { + out, err := f.fetchers[i].Fetch(ctx, ipfsPath) if err == nil { + f.markOutcome(i, true) return out, nil } + // A cancelled or timed-out context is not the gateway's fault. + // Returning early avoids quarantining every fetcher and bumping + // the loop-failure counter, which could latch the exhaustion + // breaker after a few user-initiated cancellations. + if ctxErr := ctx.Err(); ctxErr != nil { + return nil, ctxErr + } fmt.Printf("Error fetching: %s\n", err.Error()) errs = append(errs, err) + f.markOutcome(i, false) + } + + // Every fetcher failed in this call. Bump the loop-failure counter + // and decide whether to give up entirely or let the next call retry. + if err := f.recordFullLoopFailure(errs); err != nil { + return nil, err } return nil, errors.Join(errs...) } +// tryOrder returns the indices of all fetchers in the order they should be +// tried this call: never-failed fetchers first, previously-failed ones last, +// each group keeping its original order. +func (f *MultiFetcher) tryOrder() []int { + f.mu.Lock() + defer f.mu.Unlock() + order := make([]int, 0, len(f.fetchers)) + var quarantined []int + for i := range f.fetchers { + if _, bad := f.failed[i]; bad { + quarantined = append(quarantined, i) + } else { + order = append(order, i) + } + } + return append(order, quarantined...) +} + +// markOutcome records the result of a single fetcher attempt: a success +// clears any quarantine bit on i and resets the loop-failure streak, while a +// failure puts i in quarantine. Both operations are idempotent. +func (f *MultiFetcher) markOutcome(i int, success bool) { + f.mu.Lock() + defer f.mu.Unlock() + if success { + delete(f.failed, i) + f.loopFailures = 0 + return + } + f.failed[i] = struct{}{} +} + +// recordFullLoopFailure increments the full-loop counter. If the cap is +// reached, the MultiFetcher latches into the exhausted state and returns +// ErrMultiFetcherExhausted (wrapping the last batch of errors). Otherwise +// the quarantine is cleared so the next call retries every fetcher fresh. +func (f *MultiFetcher) recordFullLoopFailure(errs []error) error { + f.mu.Lock() + defer f.mu.Unlock() + f.loopFailures++ + if f.loopFailures >= maxMultiFetcherFullLoopFailures { + f.exhausted = fmt.Errorf("%w: %w", ErrMultiFetcherExhausted, errors.Join(errs...)) + return f.exhausted + } + clear(f.failed) + return nil +} + +// exhaustedErr returns the latched exhaustion error, or nil if the +// MultiFetcher is still in service. +func (f *MultiFetcher) exhaustedErr() error { + f.mu.Lock() + defer f.mu.Unlock() + return f.exhausted +} + func (f *MultiFetcher) Close() error { var errs error for _, fetcher := range f.fetchers { diff --git a/repo/fsrepo/migrations/httpfetcher.go b/repo/fsrepo/migrations/httpfetcher.go index 2ae180b1ea2..3c69714f547 100644 --- a/repo/fsrepo/migrations/httpfetcher.go +++ b/repo/fsrepo/migrations/httpfetcher.go @@ -5,9 +5,11 @@ import ( "errors" "fmt" "io" + "net" "net/http" gopath "path" "strings" + "time" "github.com/ipfs/boxo/blockservice" "github.com/ipfs/boxo/blockstore" @@ -23,18 +25,73 @@ import ( "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" "github.com/ipfs/go-unixfsnode" + config "github.com/ipfs/kubo/config" gocarv2 "github.com/ipld/go-car/v2" dagpb "github.com/ipld/go-codec-dagpb" madns "github.com/multiformats/go-multiaddr-dns" ) const ( - // default is different name than ipfs.io which is being blocked by some ISPs + // defaultGatewayURL is used when no gateway is configured. Some ISPs + // block ipfs.io, so we use a different hostname. defaultGatewayURL = "https://trustless-gateway.link" // Default maximum download size. defaultFetchLimit = 1024 * 1024 * 512 + + // Sized for users on slow / high-latency networks (e.g. VPNs through + // congested links): a 3-RTT TLS handshake at 1-2s RTT plus packet + // loss can legitimately approach 10s. 15s leaves headroom while + // still failing fast against truly dead gateways. + dialTimeout = 15 * time.Second + tlsHandshakeTimeout = 15 * time.Second ) +// defaultMigrationGateways is a last-resort fallback used when +// Migration.DownloadSources expands the "HTTPS" alias. The first entry +// (trustless-gateway.link) serves nearly all users; the rest are tried +// only when it is blocked or unreachable. +// +// Including third-party gateways is safe: each block is fetched as CAR +// and verified against the requested CID's multihash, so a malicious +// operator cannot substitute different content. +// +// TODO: replace this static list with a dynamic source, either the public +// gateway checker list at +// https://github.com/ipfs/public-gateway-checker/raw/refs/heads/main/gateways.json +// or AutoConf. Not done yet because this code path only runs for repos +// from go-ipfs or Kubo older than v0.27 (roughly 2020 vintage). Modern +// Kubo ships embedded migrations and never reaches it, so the impact and +// risk of leaving the list hard-coded are both low. +var defaultMigrationGateways = []string{ + defaultGatewayURL, + "https://gateway.pinata.cloud", + "https://ipfs.filebase.io", + "https://4everland.io", + "https://dget.top", +} + +// migrationHTTPClient is the HTTP client for migration downloads. Its timeouts +// fail fast on unreachable or stalled gateways so MultiFetcher can rotate. +var migrationHTTPClient = &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: dialTimeout, + }).DialContext, + TLSHandshakeTimeout: tlsHandshakeTimeout, + // ResponseHeaderTimeout matches boxo's server-side + // DefaultRetrievalTimeout (re-exported via Kubo config): a healthy + // gateway returns the first byte within this budget or 504s. + // Mirroring it avoids drift if boxo retunes. + ResponseHeaderTimeout: config.DefaultRetrievalTimeout, + IdleConnTimeout: 90 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + ForceAttemptHTTP2: true, + }, + // No overall Timeout: cancellation flows from the request context, so + // streaming bodies run to completion under context control. +} + // HttpFetcher fetches files over HTTP using verifiable CAR archives. type HttpFetcher struct { //nolint distPath string @@ -52,9 +109,10 @@ var _ Fetcher = (*HttpFetcher)(nil) // Specifying 0 for fetchLimit sets the default, -1 means no limit. func NewHttpFetcher(distPath, gateway, userAgent string, fetchLimit int64) *HttpFetcher { //nolint f := &HttpFetcher{ - distPath: LatestIpfsDist, - gateway: defaultGatewayURL, - limit: defaultFetchLimit, + distPath: LatestIpfsDist, + gateway: defaultGatewayURL, + limit: defaultFetchLimit, + userAgent: userAgent, } if distPath != "" { @@ -86,7 +144,7 @@ func (f *HttpFetcher) Fetch(ctx context.Context, filePath string) ([]byte, error return nil, fmt.Errorf("path could not be resolved: %w", err) } - rc, err := f.httpRequest(ctx, imPath, "application/vnd.ipld.car") + rc, err := f.httpRequest(ctx, imPath, "application/vnd.ipld.car", "car") if err != nil { return nil, fmt.Errorf("failed to fetch CAR: %w", err) } @@ -123,10 +181,11 @@ func (f *HttpFetcher) resolvePath(ctx context.Context, pathStr string) (path.Imm } func (f *HttpFetcher) resolveIPNS(ctx context.Context, name ipns.Name) (path.Path, error) { - rc, err := f.httpRequest(ctx, name.AsPath(), "application/vnd.ipfs.ipns-record") + rc, err := f.httpRequest(ctx, name.AsPath(), "application/vnd.ipfs.ipns-record", "ipns-record") if err != nil { return path.ImmutablePath{}, err } + defer rc.Close() rc = NewLimitReadCloser(rc, int64(ipns.MaxRecordSize)) rawRecord, err := io.ReadAll(rc) @@ -156,8 +215,15 @@ func (f *HttpFetcher) resolveDNSLink(ctx context.Context, p path.Path) (path.Pat return res.Path, nil } -func (f *HttpFetcher) httpRequest(ctx context.Context, p path.Path, accept string) (io.ReadCloser, error) { +func (f *HttpFetcher) httpRequest(ctx context.Context, p path.Path, accept, format string) (io.ReadCloser, error) { url := f.gateway + p.String() + // Pass the format hint as both an Accept header and a ?format= query + // parameter. The trustless gateway spec defines both as valid + // signaling mechanisms, and some gateway implementations honor one + // but not the other; sending both maximizes compatibility. + if format != "" { + url += "?format=" + format + } fmt.Printf("Fetching with HTTP: %q\n", url) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -169,9 +235,9 @@ func (f *HttpFetcher) httpRequest(ctx context.Context, p path.Path, accept strin req.Header.Set("User-Agent", f.userAgent) } - resp, err := http.DefaultClient.Do(req) + resp, err := migrationHTTPClient.Do(req) if err != nil { - return nil, fmt.Errorf("http.DefaultClient.Do error: %w", err) + return nil, fmt.Errorf("migration http request error: %w", err) } if resp.StatusCode >= 400 { diff --git a/repo/fsrepo/migrations/migrations.go b/repo/fsrepo/migrations/migrations.go index c5b23a17d6d..4c9a48df7a0 100644 --- a/repo/fsrepo/migrations/migrations.go +++ b/repo/fsrepo/migrations/migrations.go @@ -157,21 +157,26 @@ func ReadMigrationConfig(repoRoot string, userConfigFile string) (*config.Migrat return &cfg.Migration, nil } -// GetMigrationFetcher creates one or more fetchers according to -// downloadSources. +// GetMigrationFetcher creates one or more fetchers from downloadSources. +// Multiple fetchers are wrapped in a MultiFetcher that rotates to the next +// gateway when one errors and quarantines failed gateways for the session. // // Deprecated: This function is used by legacy migration downloads and will be removed // in a future version. Use RunHybridMigrations or RunEmbeddedMigrations instead. func GetMigrationFetcher(downloadSources []string, distPath string, newIpfsFetcher func(string) Fetcher) (Fetcher, error) { const httpUserAgent = "kubo/migration" - const numTriesPerHTTP = 3 var fetchers []Fetcher for _, src := range downloadSources { src := strings.TrimSpace(src) switch src { case "HTTPS", "https", "HTTP", "http": - fetchers = append(fetchers, &RetryFetcher{NewHttpFetcher(distPath, "", httpUserAgent, 0), numTriesPerHTTP}) + // Expand the alias into the full ordered list of trustless + // community-provided gateways so migration survives a + // single-gateway outage. + for _, gw := range defaultMigrationGateways { + fetchers = append(fetchers, NewHttpFetcher(distPath, gw, httpUserAgent, 0)) + } case "IPFS", "ipfs": return nil, errors.New("IPFS downloads are not supported for legacy migrations (repo versions <16). Please use only HTTPS in Migration.DownloadSources") case "": @@ -188,7 +193,7 @@ func GetMigrationFetcher(downloadSources []string, distPath string, newIpfsFetch default: return nil, errors.New("bad gateway address: url scheme must be http or https") } - fetchers = append(fetchers, &RetryFetcher{NewHttpFetcher(distPath, u.String(), httpUserAgent, 0), numTriesPerHTTP}) + fetchers = append(fetchers, NewHttpFetcher(distPath, u.String(), httpUserAgent, 0)) } } diff --git a/repo/fsrepo/migrations/migrations_test.go b/repo/fsrepo/migrations/migrations_test.go index 90712a41ea1..25ae3395f1b 100644 --- a/repo/fsrepo/migrations/migrations_test.go +++ b/repo/fsrepo/migrations/migrations_test.go @@ -310,10 +310,8 @@ func TestGetMigrationFetcher(t *testing.T) { if err != nil { t.Fatal(err) } - if rf, ok := f.(*RetryFetcher); !ok { - t.Fatal("expected RetryFetcher") - } else if _, ok := rf.Fetcher.(*HttpFetcher); !ok { - t.Fatal("expected HttpFetcher") + if _, ok := f.(*HttpFetcher); !ok { + t.Fatalf("expected HttpFetcher, got %T", f) } downloadSources = []string{"ipfs"} @@ -327,10 +325,12 @@ func TestGetMigrationFetcher(t *testing.T) { if err != nil { t.Fatal(err) } - if rf, ok := f.(*RetryFetcher); !ok { - t.Fatal("expected RetryFetcher") - } else if _, ok := rf.Fetcher.(*HttpFetcher); !ok { - t.Fatal("expected HttpFetcher") + mf, ok := f.(*MultiFetcher) + if !ok { + t.Fatal("expected MultiFetcher for HTTPS alias expansion") + } + if mf.Len() != len(defaultMigrationGateways) { + t.Fatalf("expected %d fetchers from HTTPS alias, got %d", len(defaultMigrationGateways), mf.Len()) } downloadSources = []string{"IPFS", "HTTPS"} @@ -344,12 +344,12 @@ func TestGetMigrationFetcher(t *testing.T) { if err != nil { t.Fatal(err) } - mf, ok := f.(*MultiFetcher) + mf, ok = f.(*MultiFetcher) if !ok { t.Fatal("expected MultiFetcher") } - if mf.Len() != 2 { - t.Fatal("expected 2 fetchers in MultiFetcher") + if mf.Len() != len(defaultMigrationGateways)+1 { + t.Fatalf("expected %d fetchers in MultiFetcher, got %d", len(defaultMigrationGateways)+1, mf.Len()) } downloadSources = nil diff --git a/repo/fsrepo/migrations/retryfetcher.go b/repo/fsrepo/migrations/retryfetcher.go deleted file mode 100644 index 81415bb6756..00000000000 --- a/repo/fsrepo/migrations/retryfetcher.go +++ /dev/null @@ -1,33 +0,0 @@ -package migrations - -import ( - "context" - "fmt" -) - -type RetryFetcher struct { - Fetcher - MaxTries int -} - -var _ Fetcher = (*RetryFetcher)(nil) - -func (r *RetryFetcher) Fetch(ctx context.Context, filePath string) ([]byte, error) { - var lastErr error - for i := 0; i < r.MaxTries; i++ { - out, err := r.Fetcher.Fetch(ctx, filePath) - if err == nil { - return out, nil - } - - if ctx.Err() != nil { - return nil, ctx.Err() - } - lastErr = err - } - return nil, fmt.Errorf("exceeded number of retries. last error was %w", lastErr) -} - -func (r *RetryFetcher) Close() error { - return r.Fetcher.Close() -} diff --git a/test/cli/dag_test.go b/test/cli/dag_test.go index 7e28435f364..5daadc18cfb 100644 --- a/test/cli/dag_test.go +++ b/test/cli/dag_test.go @@ -2,8 +2,11 @@ package cli import ( "encoding/json" + "fmt" "io" "os" + "path/filepath" + "strings" "testing" "time" @@ -344,3 +347,268 @@ func TestDagImportFastProvide(t *testing.T) { require.Contains(t, daemonLog, "fast-provide-root: skipped") }) } + +// dagRefs returns root plus recursive ref CIDs from "ipfs refs -r --unique root". +func dagRefs(node *harness.Node, root string) []string { + refsRes := node.IPFS("refs", "-r", "--unique", root) + refs := []string{root} + for _, line := range testutils.SplitLines(strings.TrimSpace(refsRes.Stdout.String())) { + if line != "" { + refs = append(refs, line) + } + } + return refs +} + +// countCARBlocks imports the CAR at carPath onto a fresh node and returns the +// number of blocks reported by `dag import --stats`. The fresh node guarantees +// the count reflects what is in the CAR, not what was already in the store. +func countCARBlocks(t *testing.T, carPath string) int { + t.Helper() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + car, err := os.Open(carPath) + require.NoError(t, err) + defer car.Close() + + res := node.Runner.Run(harness.RunRequest{ + Path: node.IPFSBin, + Args: []string{"dag", "import", "--pin-roots=false", "--stats"}, + CmdOpts: []harness.CmdOpt{harness.RunWithStdin(car)}, + }) + require.Equal(t, 0, res.ExitCode(), "dag import --stats failed: %s", res.Stderr.String()) + + var n int + for _, line := range testutils.SplitLines(res.Stdout.String()) { + if _, err := fmt.Sscanf(line, "Imported %d blocks", &n); err == nil { + break + } + } + require.Greater(t, n, 0, "expected 'Imported N blocks' in stdout: %q", res.Stdout.String()) + return n +} + +// shallowDAGArgs are the `ipfs add` args used by the partial-DAG helpers +// below. Chunker and max-file-links are pinned so the resulting DAG shape +// (root + 2 raw leaves) is independent of changes to Import.* defaults or +// applied profiles. +var shallowDAGArgs = []string{"--raw-leaves", "--chunker=size-262144", "--max-file-links=174"} + +// makePartialDAG adds a 300 KiB file with shallowDAGArgs (yielding root + 2 +// raw leaves) and then deletes the first leaf so the node holds a DAG with +// one missing block. Returns the root CID and the CID that was removed. +func makePartialDAG(t *testing.T, node *harness.Node, seed string, addArgs ...string) (root, removed string) { + t.Helper() + root = node.IPFSAddDeterministic("300KiB", seed, append(shallowDAGArgs, addArgs...)...) + refs := dagRefs(node, root) + require.Equal(t, 3, len(refs), "expected exactly root + 2 raw leaves with pinned chunker/max-links, got %v", refs) + require.Equal(t, 0, node.RunIPFS("pin", "rm", root).ExitCode()) + require.Equal(t, 0, node.RunIPFS("block", "rm", refs[1]).ExitCode()) + return root, refs[1] +} + +// TestDagExportLocalOnly verifies the core promise of --local-only: a DAG +// with a single missing leaf can still be exported as a partial CAR, and +// the partial CAR contains exactly the full DAG minus the removed block. +func TestDagExportLocalOnly(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + // Snapshot the full DAG to a CAR before the block is removed, so we + // have a baseline block count to compare against. + root := node.IPFSAddDeterministic("300KiB", "dag-export-local-only", shallowDAGArgs...) + fullCarPath := filepath.Join(node.Dir, "full.car") + require.NoError(t, node.IPFSDagExport(root, fullCarPath)) + fullCount := countCARBlocks(t, fullCarPath) + require.Equal(t, 3, fullCount, "expected root + 2 raw leaves (full=%d)", fullCount) + + // Drop one leaf so the local DAG is partial. + refs := dagRefs(node, root) + require.Equal(t, 0, node.RunIPFS("pin", "rm", root).ExitCode()) + require.Equal(t, 0, node.RunIPFS("block", "rm", refs[1]).ExitCode()) + + // Sanity: plain --offline (without --local-only) must fail loudly + // when a block is missing. This guards the existing behavior. + res := node.Runner.Run(harness.RunRequest{ + Path: node.IPFSBin, + Args: []string{"dag", "export", "--offline", root}, + CmdOpts: []harness.CmdOpt{harness.RunWithStdout(io.Discard)}, + }) + require.NotEqual(t, 0, res.ExitCode(), "dag export --offline must fail when a block is missing") + require.Contains(t, res.Stderr.String(), "block was not found locally") + + // --local-only must succeed and produce a CAR with exactly the + // full DAG minus the one removed leaf. + partialCarPath := filepath.Join(node.Dir, "partial.car") + require.NoError(t, node.IPFSDagExport(root, partialCarPath, "--local-only", "--offline")) + partialCount := countCARBlocks(t, partialCarPath) + + require.Equal(t, fullCount-1, partialCount, + "partial CAR should be exactly the full DAG minus the one removed leaf (full=%d, partial=%d)", + fullCount, partialCount) +} + +// TestDagExportLocalOnlyImpliesOffline verifies that --local-only on its own +// makes a partial-DAG export succeed: it implies --offline so the user does +// not have to pass both flags. +func TestDagExportLocalOnlyImpliesOffline(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + root, _ := makePartialDAG(t, node, "dag-export-local-only-implies") + + // Export with only --local-only (no --offline) and confirm the + // resulting CAR has the right number of blocks (full DAG minus one). + partialCarPath := filepath.Join(node.Dir, "partial.car") + require.NoError(t, node.IPFSDagExport(root, partialCarPath, "--local-only")) + + // 300KiB --raw-leaves yields root + 2 leaves, so removing one leaf + // leaves 2 blocks. Asserting the exact count proves --offline was + // actually applied (without it, the export would either fetch the + // missing block or fail differently). + require.Equal(t, 2, countCARBlocks(t, partialCarPath)) +} + +// TestDagExportLocalOnlySkipsSubtree verifies that when a non-leaf block is +// missing, --local-only skips the entire subtree under it, not just the +// missing block. Uses a small chunk size to force a depth>1 DAG so removing +// an intermediate prunes many descendant blocks. +func TestDagExportLocalOnlySkipsSubtree(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + // chunker=size-256 + 64 KiB → 256 leaves; max-file-links=174 forces + // at least one intermediate dag-pb layer between root and leaves + // (256 > 174). Both values are pinned so the DAG shape (and the + // counts below) survives any change to Import.* defaults or profiles. + root := node.IPFSAddDeterministic("64KiB", "dag-export-local-only-subtree", + "--raw-leaves", "--chunker=size-256", "--max-file-links=174") + fullCarPath := filepath.Join(node.Dir, "full.car") + require.NoError(t, node.IPFSDagExport(root, fullCarPath)) + fullCount := countCARBlocks(t, fullCarPath) + // 1 root + 2 intermediates (174 + 82 children) + 256 leaves = 259. + require.Equal(t, 259, fullCount, "expected root + 2 intermediates + 256 leaves, got %d", fullCount) + + // Find the first intermediate ref: a non-leaf whose codec is dag-pb. + // "ipfs refs -r --unique" lists CIDs depth-first; the root's first + // child in a balanced UnixFS DAG with >174 leaves is an intermediate. + refs := dagRefs(node, root) + intermediate := refs[1] + intermediateChildren := dagRefs(node, intermediate) + require.Greater(t, len(intermediateChildren), 10, + "expected refs[1] to be a non-leaf with many children, got %d", len(intermediateChildren)) + + // Remove the intermediate. Its subtree blocks remain locally, but + // without the intermediate the walker cannot reach them, so they + // must be skipped along with it. + require.Equal(t, 0, node.RunIPFS("pin", "rm", root).ExitCode()) + require.Equal(t, 0, node.RunIPFS("block", "rm", intermediate).ExitCode()) + + partialCarPath := filepath.Join(node.Dir, "partial.car") + require.NoError(t, node.IPFSDagExport(root, partialCarPath, "--local-only")) + partialCount := countCARBlocks(t, partialCarPath) + + expectedDropped := len(intermediateChildren) // includes the intermediate itself + require.Equal(t, fullCount-expectedDropped, partialCount, + "removing intermediate %s should drop it and its %d descendants (full=%d, partial=%d)", + intermediate, expectedDropped-1, fullCount, partialCount) +} + +// TestDagExportLocalOnlyConflictsWithOnline verifies that explicitly asking +// for online mode together with --local-only is rejected, since the two +// settings contradict each other. +func TestDagExportLocalOnlyConflictsWithOnline(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + root := node.IPFSAddDeterministic("300KiB", "dag-export-local-only-online", "--raw-leaves") + + res := node.RunIPFS("dag", "export", "--local-only", "--offline=false", root) + require.NotEqual(t, 0, res.ExitCode(), "dag export --local-only --offline=false should be rejected") + stderr := res.Stderr.String() + require.Contains(t, stderr, "--local-only") + require.Contains(t, stderr, "--offline") +} + +// TestDagImportPartialCAR is the round-trip happy path: a partial CAR from +// --local-only can be imported on a fresh node with default flags (the +// IPFSDagImport harness helper passes --pin-roots=false). The helper also +// confirms the root resolves offline on the receiver. +func TestDagImportPartialCAR(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + root, _ := makePartialDAG(t, node, "dag-import-partial") + + partialCarPath := filepath.Join(node.Dir, "partial.car") + require.NoError(t, node.IPFSDagExport(root, partialCarPath, "--local-only", "--offline")) + + imp := harness.NewT(t).NewNode().Init().StartDaemon() + defer imp.StopDaemon() + partialCAR, err := os.Open(partialCarPath) + require.NoError(t, err) + defer partialCAR.Close() + require.NoError(t, imp.IPFSDagImport(partialCAR, root)) +} + +// TestDagImportLocalOnlyImpliesNoPin verifies that --local-only on its own +// makes a partial-CAR import succeed: it implies --pin-roots=false so the +// user does not have to pass both flags. +func TestDagImportLocalOnlyImpliesNoPin(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + root, _ := makePartialDAG(t, node, "dag-import-local-only-implies") + partialCarPath := filepath.Join(node.Dir, "partial.car") + require.NoError(t, node.IPFSDagExport(root, partialCarPath, "--local-only", "--offline")) + + imp := harness.NewT(t).NewNode().Init().StartDaemon() + defer imp.StopDaemon() + partialCAR, err := os.Open(partialCarPath) + require.NoError(t, err) + defer partialCAR.Close() + + // Import with only --local-only (no --pin-roots=false). Should + // succeed because --local-only implies --pin-roots=false, and the + // receiver must not attempt to pin (pin would fail on a partial DAG). + res := imp.Runner.Run(harness.RunRequest{ + Path: imp.IPFSBin, + Args: []string{"dag", "import", "--local-only"}, + CmdOpts: []harness.CmdOpt{harness.RunWithStdin(partialCAR)}, + }) + require.Equal(t, 0, res.ExitCode(), + "dag import --local-only on a partial CAR should succeed; stderr: %s", res.Stderr.String()) + require.NotContains(t, res.Stdout.String(), "Pinned root", + "import must not pin when --local-only is set") +} + +// TestDagImportLocalOnlyPinRootsConflict verifies that --local-only is +// rejected when combined with an explicit --pin-roots=true. The two are +// mutually exclusive: --local-only is for partial CARs (no full DAG to pin). +func TestDagImportLocalOnlyPinRootsConflict(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init().StartDaemon() + defer node.StopDaemon() + + r, err := os.Open(fixtureFile) + require.NoError(t, err) + defer r.Close() + + res := node.Runner.Run(harness.RunRequest{ + Path: node.IPFSBin, + Args: []string{"dag", "import", "--local-only", "--pin-roots=true"}, + CmdOpts: []harness.CmdOpt{harness.RunWithStdin(r)}, + }) + + require.NotEqual(t, 0, res.ExitCode()) + stderr := res.Stderr.String() + require.Contains(t, stderr, "--local-only") + require.Contains(t, stderr, "--pin-roots") +} diff --git a/test/cli/delegated_routing_v1_http_client_test.go b/test/cli/delegated_routing_v1_http_client_test.go index a23ab32db58..cfa347565f7 100644 --- a/test/cli/delegated_routing_v1_http_client_test.go +++ b/test/cli/delegated_routing_v1_http_client_test.go @@ -274,4 +274,27 @@ func TestHTTPDelegatedRoutingProviderAddrs(t *testing.T) { require.NotEmpty(t, addrs, "provider record should contain addresses") assert.Contains(t, addrs, "/ip4/5.6.7.8/tcp/4001", "AppendAnnounce address should be present") }) + + t.Run("provider records resolve 0.0.0.0 Swarm bind to interface addresses", func(t *testing.T) { + t.Parallel() + srv, getAddrs := captureProviderAddrs(t) + + // Default Addresses.Swarm binds to /ip4/0.0.0.0/... If httpRouterAddrFunc + // forwards those verbatim, HTTP routers receive useless unroutable entries. + // See https://github.com/ipfs/kubo/issues/11213. + node := harness.NewT(t).NewNode().Init() + node.SetIPFSConfig("Routing", customRoutingConf(srv.URL)) + node.StartDaemon() + defer node.StopDaemon() + + cidStr := node.IPFSAddStr(time.Now().String()) + node.IPFS("routing", "provide", cidStr) + + addrs := getAddrs() + require.NotEmpty(t, addrs, "provider record should contain addresses") + for _, a := range addrs { + assert.NotContains(t, a, "/ip4/0.0.0.0/", "unresolved 0.0.0.0 in provider record: %s", a) + assert.NotContains(t, a, "/ip6/::/", "unresolved :: in provider record: %s", a) + } + }) } diff --git a/test/cli/harness/ipfs.go b/test/cli/harness/ipfs.go index d7470b4e764..637b316867b 100644 --- a/test/cli/harness/ipfs.go +++ b/test/cli/harness/ipfs.go @@ -162,17 +162,19 @@ func (n *Node) IPFSDagImport(content io.Reader, cid string, args ...string) erro } // IPFSDagExport exports a DAG rooted at cid to a CAR file at carPath. -func (n *Node) IPFSDagExport(cid string, carPath string) error { - log.Debugf("node %d dag export of %s to %q", n.ID, cid, carPath) +func (n *Node) IPFSDagExport(cid string, carPath string, args ...string) error { + log.Debugf("node %d dag export of %s to %q with args: %v", n.ID, cid, carPath, args) car, err := os.Create(carPath) if err != nil { return err } defer car.Close() + fullArgs := append([]string{"dag", "export"}, args...) + fullArgs = append(fullArgs, cid) res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, - Args: []string{"dag", "export", cid}, + Args: fullArgs, CmdOpts: []CmdOpt{RunWithStdout(car)}, }) return res.Err diff --git a/test/cli/provide_stats_test.go b/test/cli/provide_stats_test.go index 6511f2da706..7207c0e39b5 100644 --- a/test/cli/provide_stats_test.go +++ b/test/cli/provide_stats_test.go @@ -506,7 +506,7 @@ func TestProvideStatDisabledConfig(t *testing.T) { assert.Contains(t, res.Stderr.String(), "stats not available") }) - t.Run("Provide.Enabled=true with Provide.DHT.Interval=0 returns error stats not available", func(t *testing.T) { + t.Run("Provide.Enabled=true with Provide.DHT.Interval=0 returns stats with zero schedule fields", func(t *testing.T) { t.Parallel() h := harness.NewT(t) @@ -517,8 +517,11 @@ func TestProvideStatDisabledConfig(t *testing.T) { node.StartDaemon() defer node.StopDaemon() + // Interval=0 disables only the periodic schedule; the provider + // is still wired and 'provide stat' returns valid stats with + // the schedule-related timing fields zeroed out. res := node.RunIPFS("provide", "stat") - assert.Error(t, res.Err) - assert.Contains(t, res.Stderr.String(), "stats not available") + assert.Equal(t, 0, res.ExitCode()) + assert.NotContains(t, res.Stderr.String(), "stats not available") }) } diff --git a/test/cli/provider_test.go b/test/cli/provider_test.go index d174447c9d2..565db743525 100644 --- a/test/cli/provider_test.go +++ b/test/cli/provider_test.go @@ -235,19 +235,241 @@ func runProviderSuite(t *testing.T, sweep bool, apply cfgApplier, awaitReprovide assert.Equal(t, 0, res.ExitCode(), "Should succeed with exit code 0") }) - // Right now Provide and Reprovide are tied together - t.Run("Reprovide.Interval=0 disables announcement of new CID too", func(t *testing.T) { + t.Run("ipfs provide once works when Provide.DHT.Interval=0", func(t *testing.T) { t.Parallel() nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + // No periodic reprovide schedule; provide once is the only + // way new content reaches peers in this configuration. n.SetIPFSConfig("Provide.DHT.Interval", "0") + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + cid := publisher.IPFSAddStr(uniq("interval=0"), "--pin=false") + expectNoProviders(t, cid, nodes[1:]...) + + res := publisher.RunIPFS("provide", "once", cid) + assert.Equal(t, 0, res.ExitCode(), "provide once should succeed with Interval=0") + expectProviders(t, cid, publisher.PeerID().String(), nodes[1:]...) + }) + + t.Run("Provide.Enabled=false disables ipfs provide once", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", false) }) defer nodes.StopDaemons() cid := nodes[0].IPFSAddStr(time.Now().String()) + res := nodes[0].RunIPFS("provide", "once", cid) + assert.Contains(t, res.Stderr.Trimmed(), "cannot provide: Provide.Enabled is false") + assert.Equal(t, 1, res.ExitCode()) + expectNoProviders(t, cid, nodes[1:]...) }) + t.Run("ipfs provide once announces a CID and finds providers", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + // "roots" so add-time providing is skipped and we know the + // announcement comes from `provide once`, not from ipfs add. + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + cid := nodes[0].IPFSAddStr(uniq("provide once"), "--pin=false") + expectNoProviders(t, cid, nodes[1:]...) + + res := nodes[0].RunIPFS("provide", "once", cid) + assert.Equal(t, 0, res.ExitCode(), "provide once should succeed") + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...) + }) + + t.Run("ipfs provide once errors when CID is not in local blockstore", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 1, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + }) + defer nodes.StopDaemons() + + // CID for content the node has never seen. + missing := "bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy" + res := nodes[0].RunIPFS("provide", "once", missing) + assert.Contains(t, res.Stderr.Trimmed(), "not found locally, cannot provide") + assert.Equal(t, 1, res.ExitCode()) + }) + + t.Run("ipfs provide once --recursive announces every block in the DAG", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + // Selective strategy + --pin=false below means nothing is + // auto-provided; everything findable comes from `provide once`. + n.SetIPFSConfig("Provide.Strategy", "roots") + // 1 MiB chunks so a 2 MiB file produces multiple leaf blocks. + n.SetIPFSConfig("Import.UnixFSChunker", "size-1048576") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + data := random.Bytes(2 * 1024 * 1024) + cidRoot := publisher.IPFSAdd(bytes.NewReader(data), "-Q", "--pin=false") + + // Discover a chunk CID via the root's DAG links. + dagOut := publisher.IPFS("dag", "get", cidRoot) + var dagNode struct { + Links []struct { + Hash map[string]string `json:"Hash"` + } `json:"Links"` + } + require.NoError(t, json.Unmarshal(dagOut.Stdout.Bytes(), &dagNode)) + require.Greater(t, len(dagNode.Links), 1, "2 MiB file with 1 MiB chunker should have multiple chunks") + cidChunk := dagNode.Links[0].Hash["/"] + require.NotEmpty(t, cidChunk) + + // Recursive provide should announce both the root and every chunk. + res := publisher.RunIPFS("provide", "once", "-r", cidRoot) + assert.Equal(t, 0, res.ExitCode(), "provide once -r should succeed") + expectProviders(t, cidRoot, publisher.PeerID().String(), nodes[1:]...) + expectProviders(t, cidChunk, publisher.PeerID().String(), nodes[1:]...) + }) + + t.Run("ipfs provide once accepts multiple CIDs and reports count", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + c1 := publisher.IPFSAddStr(uniq("multi 1"), "--pin=false") + c2 := publisher.IPFSAddStr(uniq("multi 2"), "--pin=false") + c3 := publisher.IPFSAddStr(uniq("multi 3"), "--pin=false") + + res := publisher.RunIPFS("provide", "once", c1, c2, c3) + assert.Equal(t, 0, res.ExitCode(), "provide once with multiple CIDs should succeed") + assert.Contains(t, res.Stdout.Trimmed(), "queued 3 CID(s) for immediate provide") + + expectProviders(t, c1, publisher.PeerID().String(), nodes[1:]...) + expectProviders(t, c2, publisher.PeerID().String(), nodes[1:]...) + expectProviders(t, c3, publisher.PeerID().String(), nodes[1:]...) + }) + + t.Run("ipfs provide once reads CIDs streamed from stdin", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + c1 := publisher.IPFSAddStr(uniq("stdin 1"), "--pin=false") + c2 := publisher.IPFSAddStr(uniq("stdin 2"), "--pin=false") + c3 := publisher.IPFSAddStr(uniq("stdin 3"), "--pin=false") + + res := publisher.Runner.Run(harness.RunRequest{ + Path: publisher.IPFSBin, + Args: []string{"provide", "once"}, + CmdOpts: []harness.CmdOpt{ + harness.RunWithStdinStr(c1 + "\n" + c2 + "\n" + c3 + "\n"), + }, + }) + assert.Equal(t, 0, res.ExitCode(), "provide once with stdin should succeed") + assert.Contains(t, res.Stdout.Trimmed(), "queued 3 CID(s) for immediate provide") + + expectProviders(t, c1, publisher.PeerID().String(), nodes[1:]...) + expectProviders(t, c2, publisher.PeerID().String(), nodes[1:]...) + expectProviders(t, c3, publisher.PeerID().String(), nodes[1:]...) + }) + + t.Run("ipfs provide once deduplicates repeated CIDs", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 1, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + c1 := publisher.IPFSAddStr(uniq("dedup 1"), "--pin=false") + c2 := publisher.IPFSAddStr(uniq("dedup 2"), "--pin=false") + + // 4 args, 2 unique CIDs. The repeated ones should not produce + // extra events on the wire. + res := publisher.RunIPFS("provide", "once", "--enc=json", c1, c2, c1, c2) + assert.Equal(t, 0, res.ExitCode()) + + var queued []string + for line := range strings.Lines(res.Stdout.String()) { + line = strings.TrimSpace(line) + if line == "" { + continue + } + var ev struct{ Queued string } + require.NoError(t, json.Unmarshal([]byte(line), &ev)) + queued = append(queued, ev.Queued) + } + assert.ElementsMatch(t, []string{c1, c2}, queued, "duplicates should be filtered") + }) + + t.Run("ipfs provide once --enc=json streams one event per CID", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 1, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) + n.SetIPFSConfig("Provide.Strategy", "roots") + }) + defer nodes.StopDaemons() + + publisher := nodes[0] + c1 := publisher.IPFSAddStr(uniq("json 1"), "--pin=false") + c2 := publisher.IPFSAddStr(uniq("json 2"), "--pin=false") + + res := publisher.RunIPFS("provide", "once", "--enc=json", c1, c2) + assert.Equal(t, 0, res.ExitCode(), "provide once --enc=json should succeed") + + // Parse one JSON object per non-empty line. + var queued []string + for line := range strings.Lines(res.Stdout.String()) { + line = strings.TrimSpace(line) + if line == "" { + continue + } + var ev struct{ Queued string } + require.NoError(t, json.Unmarshal([]byte(line), &ev), "each line must parse as JSON: %q", line) + queued = append(queued, ev.Queued) + } + assert.ElementsMatch(t, []string{c1, c2}, queued) + }) + + t.Run("Provide.DHT.Interval=0 keeps announcing new CIDs (fast-provide-root)", func(t *testing.T) { + t.Parallel() + + nodes := initNodes(t, 2, func(n *harness.Node) { + // Required: Interval=0 alone is rejected by the validator + // since the new semantic only disables the schedule. + n.SetIPFSConfig("Provide.Enabled", true) + n.SetIPFSConfig("Provide.DHT.Interval", "0") + }) + defer nodes.StopDaemons() + + cid := nodes[0].IPFSAddStr(time.Now().String()) + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...) + }) + // `routing reprovide` is only available with the legacy provider. // Sweep provider reprovides automatically on schedule. if !sweep { @@ -255,19 +477,14 @@ func runProviderSuite(t *testing.T, sweep bool, apply cfgApplier, awaitReprovide t.Parallel() nodes := initNodes(t, 2, func(n *harness.Node) { + n.SetIPFSConfig("Provide.Enabled", true) n.SetIPFSConfig("Provide.DHT.Interval", "0") }) defer nodes.StopDaemons() - cid := nodes[0].IPFSAddStr(time.Now().String()) - - expectNoProviders(t, cid, nodes[1:]...) - res := nodes[0].RunIPFS("routing", "reprovide") assert.Contains(t, res.Stderr.Trimmed(), "invalid configuration: Provide.DHT.Interval is set to '0'") assert.Equal(t, 1, res.ExitCode()) - - expectNoProviders(t, cid, nodes[1:]...) }) t.Run("Manual Reprovide trigger does not work when Provide system is disabled", func(t *testing.T) { diff --git a/test/cli/shutdown_timeout_test.go b/test/cli/shutdown_timeout_test.go new file mode 100644 index 00000000000..da8f7b19a6d --- /dev/null +++ b/test/cli/shutdown_timeout_test.go @@ -0,0 +1,74 @@ +package cli + +import ( + "testing" + "time" + + "github.com/ipfs/kubo/config" + "github.com/ipfs/kubo/test/cli/harness" + "github.com/stretchr/testify/require" +) + +const ( + // testShutdownTimeout overrides DefaultShutdownTimeout so the test + // runs in seconds rather than the production default. + testShutdownTimeout = 10 * time.Second + // testShutdownCompletionBound is a soft upper bound for StopDaemon in + // this test. StopDaemon escalates SIGTERM, SIGTERM, SIGQUIT, SIGKILL + // itself (see harness/node.go), so anything close to this bound + // indicates kubo's own bounded-shutdown logic failed. + testShutdownCompletionBound = testShutdownTimeout + 5*time.Second +) + +// TestShutdownTimeoutHonored exercises the bounded-shutdown logic end-to-end +// for the common case (no hung subsystems): the daemon must shut down +// cleanly well within the configured ShutdownTimeout, and pinned/MFS data +// must survive across the restart. +func TestShutdownTimeoutHonored(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + node.UpdateConfig(func(cfg *config.Config) { + cfg.Internal.ShutdownTimeout = config.NewOptionalDuration(testShutdownTimeout) + }) + node.StartDaemon() + + // Real data-path work that must survive shutdown. + addCID := node.PipeStrToIPFS("survives shutdown", "add", "-q").Stdout.Trimmed() + node.IPFS("files", "mkdir", "/persisted") + + // "diag healthy" must succeed while the daemon is running normally. + require.Equal(t, 0, node.RunIPFS("diag", "healthy").ExitCode(), + "diag healthy should succeed before shutdown is initiated") + + start := time.Now() + node.StopDaemon() + require.Less(t, time.Since(start), testShutdownCompletionBound, + "graceful shutdown should complete well within the configured ShutdownTimeout") + + // Restart and verify data survived. + node.StartDaemon() + require.Contains(t, node.IPFS("pin", "ls").Stdout.String(), addCID, + "pinned CID should survive shutdown+restart") + require.Contains(t, node.IPFS("files", "ls").Stdout.String(), "persisted", + "MFS content should survive shutdown+restart") +} + +// TestShutdownTimeoutDisabled verifies that ShutdownTimeout=0 opts out of +// the bounded-shutdown logic and behaves like legacy kubo (no watchdog, +// no app.Stop deadline). The daemon must still shut down cleanly because +// no subsystem is actually hung. +func TestShutdownTimeoutDisabled(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + node.UpdateConfig(func(cfg *config.Config) { + cfg.Internal.ShutdownTimeout = config.NewOptionalDuration(0) + }) + node.StartDaemon() + + start := time.Now() + node.StopDaemon() + require.Less(t, time.Since(start), testShutdownCompletionBound, + "graceful shutdown should still complete in reasonable time with ShutdownTimeout=0") +} diff --git a/test/cli/update_test.go b/test/cli/update_test.go index 02ce49b1dd1..182e6ce0f1b 100644 --- a/test/cli/update_test.go +++ b/test/cli/update_test.go @@ -21,16 +21,20 @@ import ( "github.com/stretchr/testify/require" ) -// TestUpdate exercises the built-in "ipfs update" command tree against -// the real GitHub Releases API. Network access is required. +// TestUpdate exercises the built-in "ipfs update" command tree. // -// The node is created without Init or daemon, so install/revert error -// paths that don't depend on a running daemon can be tested. +// A local httptest server replaces GitHub Releases so the test does not +// depend on network reachability or rate limits. The node is created +// without Init or daemon, so install/revert error paths that don't +// depend on a running daemon can be tested. func TestUpdate(t *testing.T) { t.Parallel() h := harness.NewT(t) node := h.NewNode() + srv := newMockGitHubReleases(t) + node.Runner.Env["TEST_KUBO_UPDATE_GITHUB_URL"] = srv.URL + t.Run("help text describes the command", func(t *testing.T) { t.Parallel() res := node.IPFS("update", "--help") @@ -131,9 +135,17 @@ func TestUpdate(t *testing.T) { // (check, versions) work while the IPFS daemon holds the repo lock. // These commands only query the GitHub API and never touch the repo, // so they must succeed regardless of daemon state. +// +// A local httptest server replaces GitHub so the test does not depend +// on network reachability or GitHub rate limits. The locking behavior +// under test is independent of which endpoint serves the release JSON. func TestUpdateWhileDaemonRuns(t *testing.T) { t.Parallel() - node := harness.NewT(t).NewNode().Init().StartDaemon() + + srv := newMockGitHubReleases(t) + node := harness.NewT(t).NewNode() + node.Runner.Env["TEST_KUBO_UPDATE_GITHUB_URL"] = srv.URL + node.Init().StartDaemon() defer node.StopDaemon() t.Run("check succeeds with daemon running", func(t *testing.T) { @@ -463,6 +475,43 @@ func TestUpdateClean(t *testing.T) { // --- test helpers --- +// newMockGitHubReleases returns an httptest server that mimics the GitHub +// Releases listing API with a single stable release at v0.99.0 carrying +// a binary asset for the current GOOS/GOARCH. This is enough to drive +// "ipfs update check" and "ipfs update versions" without touching the +// real api.github.com. +// +// The asset name follows the same convention used by real kubo releases +// (see https://github.com/ipfs/kubo/releases): kubo__-., +// where ext is "zip" on Windows and "tar.gz" everywhere else. This must +// match what assetNameForPlatformTag produces in core/commands/update_github.go, +// otherwise findReleaseAsset cannot locate the binary and reports +// "no release found with a binary for /". +func newMockGitHubReleases(t *testing.T) *httptest.Server { + t.Helper() + ext := "tar.gz" + if runtime.GOOS == "windows" { + ext = "zip" + } + asset := fmt.Sprintf("kubo_v0.99.0_%s-%s.%s", runtime.GOOS, runtime.GOARCH, ext) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Both `update check` and `update versions` call + // GET /releases?per_page=N. One stable release with a matching + // platform asset exercises both paths. + rels := []map[string]any{{ + "tag_name": "v0.99.0", + "prerelease": false, + "assets": []map[string]any{{ + "name": asset, + }}, + }} + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(rels) + })) + t.Cleanup(srv.Close) + return srv +} + // copyBuiltBinary copies the built ipfs binary (cmd/ipfs/ipfs) to dst. // It locates the project root the same way the test harness does. func copyBuiltBinary(t *testing.T, dst string) { diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 08d1846f784..97509f90a98 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -1,6 +1,6 @@ module github.com/ipfs/kubo/test/dependencies -go 1.26.2 +go 1.26.4 replace github.com/ipfs/kubo => ../../ @@ -8,7 +8,7 @@ require ( github.com/Kubuxu/gocovmerge v0.0.0-20161216165753-7ecaa51963cd github.com/golangci/golangci-lint v1.64.8 github.com/ipfs/go-cidutil v0.1.1 - github.com/ipfs/go-log/v2 v2.9.1 + github.com/ipfs/go-log/v2 v2.9.2 github.com/ipfs/go-test v0.3.0 github.com/ipfs/hang-fds v0.1.0 github.com/ipfs/iptb v1.4.1 @@ -54,8 +54,8 @@ require ( github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/caddyserver/certmagic v0.23.0 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/caddyserver/certmagic v0.25.3 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/catenacyber/perfsprint v0.8.2 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -65,7 +65,7 @@ require ( github.com/cockroachdb/crlib v0.0.0-20241112164430-1264a2edc35b // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble/v2 v2.1.4 // indirect + github.com/cockroachdb/pebble/v2 v2.1.5 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect @@ -87,7 +87,7 @@ require ( github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/flynn/noise v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gammazero/chanqueue v1.1.2 // indirect @@ -135,24 +135,24 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.1.0 // indirect - github.com/ipfs/boxo v0.39.0 // indirect + github.com/ipfs/boxo v0.40.0 // indirect github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-block-format v0.2.3 // indirect github.com/ipfs/go-cid v0.6.1 // indirect github.com/ipfs/go-datastore v0.9.1 // indirect github.com/ipfs/go-dsqueue v0.2.0 // indirect - github.com/ipfs/go-ipfs-cmds v0.16.0 // indirect + github.com/ipfs/go-ipfs-cmds v0.16.1 // indirect github.com/ipfs/go-ipfs-redirects-file v0.1.2 // indirect github.com/ipfs/go-ipld-cbor v0.2.1 // indirect github.com/ipfs/go-ipld-format v0.6.3 // indirect github.com/ipfs/go-ipld-legacy v0.3.0 // indirect github.com/ipfs/go-metrics-interface v0.3.0 // indirect - github.com/ipfs/go-unixfsnode v1.10.3 // indirect + github.com/ipfs/go-unixfsnode v1.10.4 // indirect github.com/ipfs/kubo v0.31.0 // indirect - github.com/ipld/go-car/v2 v2.16.0 // indirect + github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c // indirect github.com/ipld/go-codec-dagpb v1.7.0 // indirect github.com/ipld/go-ipld-prime v0.23.0 // indirect - github.com/ipshipyard/p2p-forge v0.8.0 // indirect + github.com/ipshipyard/p2p-forge v0.9.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect @@ -162,7 +162,7 @@ require ( github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/koron/go-ssdp v0.0.6 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -176,14 +176,14 @@ require ( github.com/ldez/tagliatelle v0.7.1 // indirect github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/libdns/libdns v1.0.0-beta.1 // indirect + github.com/libdns/libdns v1.1.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-doh-resolver v0.5.0 // indirect github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p v0.48.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.39.1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.40.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.8.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect @@ -196,11 +196,11 @@ require ( github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mgechev/revive v1.7.0 // indirect - github.com/mholt/acmez/v3 v3.1.2 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/minio/minlz v1.0.1-0.20250507153514-87eb42fe8882 // indirect github.com/minio/sha256-simd v1.0.1 // indirect @@ -313,28 +313,28 @@ require ( go-simpler.org/musttag v0.13.0 // indirect go-simpler.org/sloglint v0.9.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap v1.28.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.45.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index 006abf1a347..1a5ff40ac8a 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -40,6 +40,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0= filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI= @@ -128,10 +130,10 @@ github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A= +github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= @@ -162,8 +164,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA= -github.com/cockroachdb/pebble/v2 v2.1.4 h1:j9wPgMDbkErFdAKYFGhsoCcvzcjR+6zrJ4jhKtJ6bOk= -github.com/cockroachdb/pebble/v2 v2.1.4/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= +github.com/cockroachdb/pebble/v2 v2.1.5 h1:1ziHpaSau6qCXnFpQX3EBOH14yPHA8W66vKxsyrgXgs= +github.com/cockroachdb/pebble/v2 v2.1.5/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI= @@ -233,8 +235,8 @@ github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= @@ -257,6 +259,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -452,8 +456,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/bbloom v0.1.0 h1:nIWwfIE3AaG7RCDQIsrUonGCOTp7qSXzxH7ab/ss964= github.com/ipfs/bbloom v0.1.0/go.mod h1:lDy3A3i6ndgEW2z1CaRFvDi5/ZTzgM1IxA/pkL7Wgts= -github.com/ipfs/boxo v0.39.0 h1:u9jLf5pLx5SWROXjHtj8VMvv+iDlMbiTyZ/vVTQ4VhI= -github.com/ipfs/boxo v0.39.0/go.mod h1:k9YCvMjytFguMHndEiGdCGMMj4b7CkdOT44vtgAxOdk= +github.com/ipfs/boxo v0.40.0 h1:AXbum7U+aVWqSdZ5RSvKFAmrwUSxOCHyDYEIkpmb23g= +github.com/ipfs/boxo v0.40.0/go.mod h1:LBlAkRBsiiADMdfhcY5CbX/gLBHla4K89RfROjj31NI= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= @@ -470,8 +474,8 @@ github.com/ipfs/go-ds-leveldb v0.5.2 h1:6nmxlQ2zbp4LCNdJVsmHfs9GP0eylfBNxpmY1csp github.com/ipfs/go-ds-leveldb v0.5.2/go.mod h1:2fAwmcvD3WoRT72PzEekHBkQmBDhc39DJGoREiuGmYo= github.com/ipfs/go-dsqueue v0.2.0 h1:MBi9w3oSiX98Xc+Y7NuJ9G8MI6mAT4IGdO9dHEMCZzU= github.com/ipfs/go-dsqueue v0.2.0/go.mod h1:8FfNQC4DMF/KkzBXRNB9Rb3MKDW0Sh98HMtXYl1mLQE= -github.com/ipfs/go-ipfs-cmds v0.16.0 h1:Oq39Gzz3pWrPwP25SjbfBQugFjKyjFdsHlAMIXdzYzM= -github.com/ipfs/go-ipfs-cmds v0.16.0/go.mod h1:iRNtY9ipM/vH0Yr+/0FY+JfT+trZDQIztDoesmmoTo4= +github.com/ipfs/go-ipfs-cmds v0.16.1 h1:O3xV6v2LN52wL0odvXX6jqlt7G2scuHzQYl80OJ+TOA= +github.com/ipfs/go-ipfs-cmds v0.16.1/go.mod h1:UkHLmJ2MlbLPuUJ0wmuF1R91+DGnwKvcCoEW3MR5CNg= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-pq v0.0.4 h1:U7jjENWJd1jhcrR8X/xHTaph14PTAK9O+yaLJbjqgOw= @@ -484,32 +488,32 @@ github.com/ipfs/go-ipld-format v0.6.3 h1:9/lurLDTotJpZSuL++gh3sTdmcFhVkCwsgx2+rA github.com/ipfs/go-ipld-format v0.6.3/go.mod h1:74ilVN12NXVMIV+SrBAyC05UJRk0jVvGqdmrcYZvCBk= github.com/ipfs/go-ipld-legacy v0.3.0 h1:7XhFKkRyCvP5upOlQfKUFIqL3S5DEZnbUE4bQmQ/tNE= github.com/ipfs/go-ipld-legacy v0.3.0/go.mod h1:Ukef9ARQiX+RVetwH2XiReLgJvQDEXcUPszrZ1KRjKI= -github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= -github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= +github.com/ipfs/go-log/v2 v2.9.2 h1:O/5BB0elpkRILvT24rCJ5976wWd7u0nJ436T3rdYdc4= +github.com/ipfs/go-log/v2 v2.9.2/go.mod h1:RziRwwXWhndlk8L75RnEe0zeAYaq2heKtEMc3jqUov0= github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU= github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= github.com/ipfs/go-peertaskqueue v0.8.3 h1:tBPpGJy+A92RqtRFq5amJn0Uuj8Pw8tXi0X3eHfHM8w= github.com/ipfs/go-peertaskqueue v0.8.3/go.mod h1:OqVync4kPOcXEGdj/LKvox9DCB5mkSBeXsPczCxLtYA= github.com/ipfs/go-test v0.3.0 h1:0Y4Uve3tp9HI+2lIJjfOliOrOgv/YpXg/l1y3P4DEYE= github.com/ipfs/go-test v0.3.0/go.mod h1:JK+U8pRpATZb7lsYNSJlCj3WYB3cFfWIbI6nWRM/GFk= -github.com/ipfs/go-unixfsnode v1.10.3 h1:c8sJjuGNkxXAQH75P+f5ngPda/9T+DrboVA0TcDGvGI= -github.com/ipfs/go-unixfsnode v1.10.3/go.mod h1:2Jlc7DoEwr12W+7l8Hr6C7XF4NHST3gIkqSArLhGSxU= +github.com/ipfs/go-unixfsnode v1.10.4 h1:cMmMyOrSjQkPVQbQvt8trErIn6jhayNf9pBA9oOwfxY= +github.com/ipfs/go-unixfsnode v1.10.4/go.mod h1:Vu1e/s7ToALBBRo38sJ8DwUVWmSeQMTdxk5/rcHl7d0= github.com/ipfs/hang-fds v0.1.0 h1:deBiFlWHsVGzJ0ZMaqscEqRM1r2O1rFZ59UiQXb1Xko= github.com/ipfs/hang-fds v0.1.0/go.mod h1:29VLWOn3ftAgNNgXg/al7b11UzuQ+w7AwtCGcTaWkbM= github.com/ipfs/iptb v1.4.1 h1:faXd3TKGPswbHyZecqqg6UfbES7RDjTKQb+6VFPKDUo= github.com/ipfs/iptb v1.4.1/go.mod h1:nTsBMtVYFEu0FjC5DgrErnABm3OG9ruXkFXGJoTV5OA= github.com/ipfs/iptb-plugins v0.5.1 h1:11PNTNEt2+SFxjUcO5qpyCTXqDj6T8Tx9pU/G4ytCIQ= github.com/ipfs/iptb-plugins v0.5.1/go.mod h1:mscJAjRnu4g16QK6oUBn9RGpcp8ueJmLfmPxIG/At78= -github.com/ipld/go-car/v2 v2.16.0 h1:LWe0vmN/QcQmUU4tr34W5Nv5mNraW+G6jfN2s+ndBco= -github.com/ipld/go-car/v2 v2.16.0/go.mod h1:RqFGWN9ifcXVmCrTAVnfnxiWZk1+jIx67SYhenlmL34= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c h1:ZFONxHSj6bzzB9eKIu+yS2AazTJe7j9FPesfy4sZSE0= +github.com/ipld/go-car/v2 v2.16.1-0.20260428045700-c4b9f366f20c/go.mod h1:/4HY8tFZ1q42Mw54ILLPQfjkUqMJxFKqY1yMDKHlYko= github.com/ipld/go-codec-dagpb v1.7.0 h1:hpuvQjCSVSLnTnHXn+QAMR0mLmb1gA6wl10LExo2Ts0= github.com/ipld/go-codec-dagpb v1.7.0/go.mod h1:rD3Zg+zub9ZnxcLwfol/OTQRVjaLzXypgy4UqHQvilM= github.com/ipld/go-ipld-prime v0.23.0 h1:csqdPZH60BsTC+AZrv7fpa27v+09I/oTqyHYYYE27eE= github.com/ipld/go-ipld-prime v0.23.0/go.mod h1:46YCFSFNFBJHPjB0pfMuv7Ly7df2eChpkpyPo5SE0bA= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714 h1:cqNk8PEwHnK0vqWln+U/YZhQc9h2NB3KjUjDPZo5Q2s= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20250821084354-a425e60cd714/go.mod h1:ZEUdra3CoqRVRYgAX/jAJO9aZGz6SKtKEG628fHHktY= -github.com/ipshipyard/p2p-forge v0.8.0 h1:yMg3UcAIVV0FDBMMbyZxlUnE6TYew0I+tBPzX6Y2OWM= -github.com/ipshipyard/p2p-forge v0.8.0/go.mod h1:XIyBqyuMGFDYO8wJ6wcbylLvsXbMnTgYPFkydkrVgQM= +github.com/ipshipyard/p2p-forge v0.9.0 h1:Mp/bZ8BX7sxNTyzN5BXbYpOPbggrUbn+Dr5XnJ2kj0s= +github.com/ipshipyard/p2p-forge v0.9.0/go.mod h1:1keK1MRRCu5oNe9uFKfNIIZXOFEF9hgD1iK1DUsjsXQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -535,8 +539,8 @@ github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= @@ -568,8 +572,12 @@ github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.1 h1:oKHx3lgN4e5Nno2LKTMrVx+b+NkDptkO9aDireiBDGE= +github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= @@ -582,8 +590,8 @@ github.com/libp2p/go-libp2p v0.48.0 h1:h2BrLAgrj7X8bEN05K7qmrjpNHYA+6tnsGRdprjTn github.com/libp2p/go-libp2p v0.48.0/go.mod h1:Q1fBZNdmC2Hf82husCTfkKJVfHm2we5zk+NWmOGEmWk= 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-kad-dht v0.39.1 h1:9RzUBc7zywT4ZNamRSgEvPZmVlK3Y6xdlCYfXXvlR/Q= -github.com/libp2p/go-libp2p-kad-dht v0.39.1/go.mod h1:Po2JugFEkDq9Vig/JXtc153ntOi0q58o4j7IuITCOVs= +github.com/libp2p/go-libp2p-kad-dht v0.40.0 h1:as8U7Y1RX9CTKCBiFBHWKZ6tSS+rE+6WNz+H1+M+wbo= +github.com/libp2p/go-libp2p-kad-dht v0.40.0/go.mod h1:iLUjII47u3/HjxyhucI2lhsl29lrzlAs/ym16+H40jE= 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-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= @@ -621,8 +629,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -630,8 +638,8 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= -github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= @@ -985,18 +993,18 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 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.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= @@ -1006,18 +1014,20 @@ go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1032,8 +1042,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= 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= @@ -1044,8 +1054,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-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= @@ -1082,8 +1092,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1128,8 +1138,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1217,10 +1227,10 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1229,8 +1239,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1245,13 +1255,13 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1319,8 +1329,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= diff --git a/test/sharness/t0119-prometheus-data/prometheus_metrics b/test/sharness/t0119-prometheus-data/prometheus_metrics index 1099032d7b2..78f82583e84 100644 --- a/test/sharness/t0119-prometheus-data/prometheus_metrics +++ b/test/sharness/t0119-prometheus-data/prometheus_metrics @@ -240,7 +240,6 @@ libp2p_relaysvc_status libp2p_swarm_dial_ranking_delay_seconds_bucket libp2p_swarm_dial_ranking_delay_seconds_count libp2p_swarm_dial_ranking_delay_seconds_sum -otel_scope_info process_cpu_seconds_total process_max_fds process_network_receive_bytes_total diff --git a/version.go b/version.go index 84663f7a5a9..5493fa363dd 100644 --- a/version.go +++ b/version.go @@ -3,6 +3,8 @@ package ipfs import ( "fmt" "runtime" + "runtime/debug" + "strings" "github.com/ipfs/kubo/core/commands/cmdutils" ) @@ -16,8 +18,17 @@ var CurrentCommit string // already identifies the exact source. var taggedRelease string +// buildOrigin is the Makefile-injected `host/org/repo` form of +// `git remote get-url origin`. ImplicitAgentSuffix turns a non-upstream +// value into the Version.AgentSuffix default so fork builds self-identify. +var buildOrigin string + +// upstreamModulePath is the canonical upstream module path. Builds whose +// origin matches it contribute no implicit suffix. +const upstreamModulePath = "github.com/ipfs/kubo" + // CurrentVersionNumber is the current application's version literal. -const CurrentVersionNumber = "0.41.0" +const CurrentVersionNumber = "0.42.0" const ApiVersion = "/kubo/" + CurrentVersionNumber + "/" //nolint @@ -49,6 +60,52 @@ func SetUserAgentSuffix(suffix string) { userAgentSuffix = cmdutils.CleanAndTrim(suffix) } +// ImplicitAgentSuffix returns a Version.AgentSuffix default derived from +// the build origin. It prefers the Makefile-injected URL (covers forks +// that keep the upstream `module` line) and falls back to +// debug.ReadBuildInfo's main module path (covers `go install` and forks +// that renamed their module). Returns "" for upstream builds. +func ImplicitAgentSuffix() string { + if s := suffixFromForkPath(buildOrigin); s != "" { + return s + } + if bi, ok := debug.ReadBuildInfo(); ok { + return suffixFromForkPath(bi.Main.Path) + } + return "" +} + +// knownForges lists public git hosts whose hostname is dropped from the +// implicit suffix; other hosts are kept so the origin stays identifiable. +var knownForges = map[string]struct{}{ + "github.com": {}, + "gitlab.com": {}, + "codeberg.org": {}, + "bitbucket.org": {}, +} + +// suffixFromForkPath turns a normalized `host/org/repo` into the implicit +// Version.AgentSuffix. Returns "" for upstream and empty inputs. +func suffixFromForkPath(p string) string { + p = strings.Trim(p, "/") + if p == "" || p == upstreamModulePath { + return "" + } + parts := strings.Split(p, "/") + // Only normalize canonical `host/org/repo`; shorter inputs pass through + // so operators can still identify them. + if len(parts) < 3 { + return p + } + if _, ok := knownForges[parts[0]]; ok { + parts = parts[1:] + } + if parts[len(parts)-1] == "kubo" { + parts = parts[:len(parts)-1] + } + return strings.Join(parts, "/") +} + type VersionInfo struct { Version string Commit string diff --git a/version_test.go b/version_test.go index cb9b7e4a132..f06f4650532 100644 --- a/version_test.go +++ b/version_test.go @@ -6,6 +6,51 @@ import ( "github.com/stretchr/testify/assert" ) +func TestSuffixFromForkPath(t *testing.T) { + tests := []struct { + name string + path string + expected string + }{ + {name: "empty", path: "", expected: ""}, + {name: "upstream", path: "github.com/ipfs/kubo", expected: ""}, + {name: "github fork", path: "github.com/myorg/kubo", expected: "myorg"}, + {name: "gitlab fork", path: "gitlab.com/myorg/kubo", expected: "myorg"}, + {name: "codeberg fork", path: "codeberg.org/myorg/kubo", expected: "myorg"}, + {name: "bitbucket fork", path: "bitbucket.org/myorg/kubo", expected: "myorg"}, + {name: "github renamed repo", path: "github.com/myorg/kubo-experimental", expected: "myorg/kubo-experimental"}, + {name: "unknown host canonical repo", path: "git.example.com/team/kubo", expected: "git.example.com/team"}, + {name: "unknown host renamed repo", path: "git.example.com/team/kubo-fork", expected: "git.example.com/team/kubo-fork"}, + {name: "unknown host nested path", path: "git.example.com/group/sub/kubo", expected: "git.example.com/group/sub"}, + {name: "trailing slash", path: "github.com/myorg/kubo/", expected: "myorg"}, + {name: "leading slash", path: "/github.com/myorg/kubo", expected: "myorg"}, + {name: "single segment", path: "kubo", expected: "kubo"}, + {name: "two segment fork on known host", path: "github.com/kubo", expected: "github.com/kubo"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, suffixFromForkPath(tt.path)) + }) + } +} + +func TestImplicitAgentSuffix_PrefersBuildOrigin(t *testing.T) { + orig := buildOrigin + t.Cleanup(func() { buildOrigin = orig }) + + buildOrigin = "github.com/myorg/kubo" + assert.Equal(t, "myorg", ImplicitAgentSuffix()) + + // Falls through to BuildInfo when origin matches upstream or is empty; + // BuildInfo.Main.Path is "github.com/ipfs/kubo" during `go test` of this + // package, so the implicit suffix is empty. + buildOrigin = "" + assert.Equal(t, "", ImplicitAgentSuffix()) + + buildOrigin = upstreamModulePath + assert.Equal(t, "", ImplicitAgentSuffix()) +} + // TestGetUserAgentVersion verifies the user agent string used in libp2p // identify and HTTP requests. Tagged release builds (where the commit matches // the tag) skip the commit hash from the agent version, since the version