@cryptotaxi247 / kubo / commits / a673c2ec9

fix: Provide according to Reprovider.Strategy (#10886)

* Provide according to strategy Updates boxo to a version with the changes from https://github.com/ipfs/boxo/pull/976, which decentralize the providing responsibilities (from a central providing.Exchange to blockstore, pinner, mfs). The changes consist in initializing the Pinner, MFS and the blockstore with the provider.System, which is created first. Since the provider.System is created first, the reproviding KeyChanFunc is set later when we can create it once we have the Pinner, MFS and the blockstore. Some additional work applies to the Add() workflow. Normally, blocks would get provided at the Blockstore or the Pinner, but when adding blocks AND a "pinned" strategy is used, the blockstore does not provide, and the pinner does not traverse the DAG (and thus doesn't provide either), so we need to provide directly from the Adder. This is resolved by wrapping the DAGService in a "providingDAGService" which provides every added block, when using the "pinned" strategy. `ipfs --offline add` when the ONLINE daemon is running will now announce blocks per the chosen strategy, where before it did not announce them. This is documented in the changelog. A couple of releases ago, adding with `ipfs --offline add` was faster, but this is no longer the case so we are not incurring in any penalties by sticking to the fact that the daemon is online and has a providing strategy that we follow. Co-authored-by: gammazero <11790789+gammazero@users.noreply.github.com> Co-authored-by: Marcin Rataj <lidel@lidel.org>

Hector Sanjuan committed Aug 8, 2025 at 10:56 UTC a673c2ec95b81b92a99bbea9d6c4c2de17472aa5
24 files changed +726 -228
client/rpc/api_test.go
+4
@@ -12,6 +12,7 @@ import (
12 "time"
13
14 "github.com/ipfs/boxo/path"
15 + "github.com/ipfs/kubo/config"
16 iface "github.com/ipfs/kubo/core/coreiface"
17 "github.com/ipfs/kubo/core/coreiface/tests"
18 "github.com/ipfs/kubo/test/cli/harness"
@@ -45,6 +46,9 @@ func (np NodeProvider) MakeAPISwarm(t *testing.T, ctx context.Context, fullIdent
46
47 c := n.ReadConfig()
48 c.Experimental.FilestoreEnabled = true
49 + // only provide things we pin. Allows to test
50 + // provide operations.
51 + c.Reprovider.Strategy = config.NewOptionalString("roots")
52 n.WriteConfig(c)
53 n.StartDaemon("--enable-pubsub-experiment", "--offline="+strconv.FormatBool(!online))
54
config/reprovider.go
+33 -1
@@ -1,15 +1,47 @@
1 package config
2
3 -import "time"
3 +import (
4 + "strings"
5 + "time"
6 +)
7
8 const (
9 DefaultReproviderInterval = time.Hour * 22 // https://github.com/ipfs/kubo/pull/9326
10 DefaultReproviderStrategy = "all"
11 )
12
13 +type ReproviderStrategy int
14 +
15 +const (
16 + ReproviderStrategyAll ReproviderStrategy = 1 << iota // 1 (0b00001)
17 + ReproviderStrategyFlat // 2 (0b00010)
18 + ReproviderStrategyPinned // 4 (0b00100)
19 + ReproviderStrategyRoots // 8 (0b01000)
20 + ReproviderStrategyMFS // 16 (0b10000)
21 +)
22 +
23 // Reprovider configuration describes how CID from local datastore are periodically re-announced to routing systems.
24 // For provide behavior of ad-hoc or newly created CIDs and their first-time announcement, see Provider.*
25 type Reprovider struct {
26 Interval *OptionalDuration `json:",omitempty"` // Time period to reprovide locally stored objects to the network
27 Strategy *OptionalString `json:",omitempty"` // Which keys to announce
28 }
29 +
30 +func ParseReproviderStrategy(s string) ReproviderStrategy {
31 + var strategy ReproviderStrategy
32 + for _, part := range strings.Split(s, "+") {
33 + switch part {
34 + case "all", "": // special case, does not mix with others
35 + return ReproviderStrategyAll
36 + case "flat":
37 + strategy |= ReproviderStrategyFlat
38 + case "pinned":
39 + strategy |= ReproviderStrategyPinned
40 + case "roots":
41 + strategy |= ReproviderStrategyRoots
42 + case "mfs":
43 + strategy |= ReproviderStrategyMFS
44 + }
45 + }
46 + return strategy
47 +}
core/core.go
+2
@@ -107,6 +107,8 @@ type IpfsNode struct {
107 Bitswap *bitswap.Bitswap `optional:"true"` // The Bitswap instance
108 Namesys namesys.NameSystem // the name system, resolves paths to hashes
109 Provider provider.System // the value provider system
110 + ProvidingStrategy config.ReproviderStrategy `optional:"true"`
111 + ProvidingKeyChanFunc provider.KeyChanFunc `optional:"true"`
112 IpnsRepub *ipnsrp.Republisher `optional:"true"`
113 ResourceManager network.ResourceManager `optional:"true"`
114
core/coreapi/coreapi.go
+7 -4
@@ -26,6 +26,7 @@ import (
26 provider "github.com/ipfs/boxo/provider"
27 offlineroute "github.com/ipfs/boxo/routing/offline"
28 ipld "github.com/ipfs/go-ipld-format"
29 + logging "github.com/ipfs/go-log/v2"
30 "github.com/ipfs/kubo/config"
31 coreiface "github.com/ipfs/kubo/core/coreiface"
32 "github.com/ipfs/kubo/core/coreiface/options"
@@ -44,6 +45,8 @@ import (
45 "github.com/ipfs/kubo/repo"
46 )
47
48 +var log = logging.Logger("coreapi")
49 +
50 type CoreAPI struct {
51 nctx context.Context
52
@@ -70,7 +73,8 @@ type CoreAPI struct {
73 ipldPathResolver pathresolver.Resolver
74 unixFSPathResolver pathresolver.Resolver
75
73 - provider provider.System
76 + provider provider.System
77 + providingStrategy config.ReproviderStrategy
78
79 pubSub *pubsub.PubSub
80
@@ -185,7 +189,8 @@ func (api *CoreAPI) WithOptions(opts ...options.ApiOption) (coreiface.CoreAPI, e
189 ipldPathResolver: n.IPLDPathResolver,
190 unixFSPathResolver: n.UnixFSPathResolver,
191
188 - provider: n.Provider,
192 + provider: n.Provider,
193 + providingStrategy: n.ProvidingStrategy,
194
195 pubSub: n.PubSub,
196
@@ -235,8 +240,6 @@ func (api *CoreAPI) WithOptions(opts ...options.ApiOption) (coreiface.CoreAPI, e
240 return nil, fmt.Errorf("error constructing namesys: %w", err)
241 }
242
238 - subAPI.provider = provider.NewNoopProvider()
239 -
243 subAPI.peerstore = nil
244 subAPI.peerHost = nil
245 subAPI.recordValidator = nil
core/coreapi/pin.go
-4
@@ -44,10 +44,6 @@ func (api *PinAPI) Add(ctx context.Context, p path.Path, opts ...caopts.PinAddOp
44 return fmt.Errorf("pin: %s", err)
45 }
46
47 - if err := api.provider.Provide(ctx, dagNode.Cid(), true); err != nil {
48 - return err
49 - }
50 -
47 return api.pinning.Flush(ctx)
48 }
49
core/coreapi/test/api_test.go
+3
@@ -70,6 +70,9 @@ func (NodeProvider) MakeAPISwarm(t *testing.T, ctx context.Context, fullIdentity
70 c.Identity = ident
71 c.Experimental.FilestoreEnabled = true
72 c.AutoTLS.Enabled = config.False // disable so no /ws listener is added
73 + // For provider tests, avoid that content gets
74 + // auto-provided without calling "provide" (unless pinned).
75 + c.Reprovider.Strategy = config.NewOptionalString("roots")
76
77 ds := syncds.MutexWrap(datastore.NewMapDatastore())
78 r := &repo.Mock{
core/coreapi/unixfs.go
+61 -8
@@ -16,6 +16,7 @@ import (
16 uio "github.com/ipfs/boxo/ipld/unixfs/io"
17 "github.com/ipfs/boxo/mfs"
18 "github.com/ipfs/boxo/path"
19 + provider "github.com/ipfs/boxo/provider"
20 cid "github.com/ipfs/go-cid"
21 cidutil "github.com/ipfs/go-cidutil"
22 ds "github.com/ipfs/go-datastore"
@@ -102,7 +103,22 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
103 bserv := blockservice.New(addblockstore, exch,
104 blockservice.WriteThrough(cfg.Datastore.WriteThrough.WithDefault(config.DefaultWriteThrough)),
105 ) // hash security 001
105 - dserv := merkledag.NewDAGService(bserv)
106 +
107 + var dserv ipld.DAGService = merkledag.NewDAGService(bserv)
108 +
109 + // wrap the DAGService in a providingDAG service which provides every block written.
110 + // note about strategies:
111 + // - "all"/"flat" gets handled directly at the blockstore so no need to provide
112 + // - "roots" gets handled in the pinner
113 + // - "mfs" gets handled in mfs
114 + // We need to provide the "pinned" cases only. Added blocks are not
115 + // going to be provided by the blockstore (wrong strategy for that),
116 + // nor by the pinner (the pinner doesn't traverse the pinned DAG itself, it only
117 + // handles roots). This wrapping ensures all blocks of pinned content get provided.
118 + if settings.Pin && !settings.OnlyHash &&
119 + (api.providingStrategy&config.ReproviderStrategyPinned) != 0 {
120 + dserv = &providingDagService{dserv, api.provider}
121 + }
122
123 // add a sync call to the DagService
124 // this ensures that data written to the DagService is persisted to the underlying datastore
@@ -126,6 +142,11 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
142 }
143 }
144
145 + // Note: the dag service gets wrapped multiple times:
146 + // 1. providingDagService (if pinned strategy) - provides blocks as they're added
147 + // 2. syncDagService - ensures data persistence
148 + // 3. batchingDagService (in coreunix.Adder) - batches operations for efficiency
149 +
150 fileAdder, err := coreunix.NewAdder(ctx, pinning, addblockstore, syncDserv)
151 if err != nil {
152 return path.ImmutablePath{}, err
@@ -183,7 +204,8 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
204 if err != nil {
205 return path.ImmutablePath{}, err
206 }
186 - mr, err := mfs.NewRoot(ctx, md, emptyDirNode, nil)
207 + // MFS root for OnlyHash mode: provider is nil since we're not storing/providing anything
208 + mr, err := mfs.NewRoot(ctx, md, emptyDirNode, nil, nil)
209 if err != nil {
210 return path.ImmutablePath{}, err
211 }
@@ -196,12 +218,6 @@ func (api *UnixfsAPI) Add(ctx context.Context, files files.Node, opts ...options
218 return path.ImmutablePath{}, err
219 }
220
199 - if !settings.OnlyHash {
200 - if err := api.provider.Provide(ctx, nd.Cid(), true); err != nil {
201 - return path.ImmutablePath{}, err
202 - }
203 - }
204 -
221 return path.FromCid(nd.Cid()), nil
222 }
223
@@ -367,3 +383,40 @@ type syncDagService struct {
383 func (s *syncDagService) Sync() error {
384 return s.syncFn()
385 }
386 +
387 +type providingDagService struct {
388 + ipld.DAGService
389 + provider provider.System
390 +}
391 +
392 +func (pds *providingDagService) Add(ctx context.Context, n ipld.Node) error {
393 + if err := pds.DAGService.Add(ctx, n); err != nil {
394 + return err
395 + }
396 + // Provider errors are logged but not propagated.
397 + // We don't want DAG operations to fail due to providing issues.
398 + // The user's data is still stored successfully even if the
399 + // announcement to the routing system fails temporarily.
400 + if err := pds.provider.Provide(ctx, n.Cid(), true); err != nil {
401 + log.Error(err)
402 + }
403 + return nil
404 +}
405 +
406 +func (pds *providingDagService) AddMany(ctx context.Context, nds []ipld.Node) error {
407 + if err := pds.DAGService.AddMany(ctx, nds); err != nil {
408 + return err
409 + }
410 + // Same error handling philosophy as Add(): log but don't fail.
411 + // Note: Provide calls are intentionally blocking here - the Provider
412 + // implementation should handle concurrency/queuing internally.
413 + for _, n := range nds {
414 + if err := pds.provider.Provide(ctx, n.Cid(), true); err != nil {
415 + log.Error(err)
416 + break
417 + }
418 + }
419 + return nil
420 +}
421 +
422 +var _ ipld.DAGService = (*providingDagService)(nil)
core/coreiface/tests/routing.go
+7
@@ -171,6 +171,13 @@ func (tp *TestSuite) TestRoutingFindProviders(t *testing.T) {
171 t.Fatal(err)
172 }
173
174 + // Pin so that it is provided, given that providing strategy is
175 + // "roots" and addTestObject does not pin.
176 + err = apis[0].Pin().Add(ctx, p)
177 + if err != nil {
178 + t.Fatal(err)
179 + }
180 +
181 time.Sleep(3 * time.Second)
182
183 out, err := apis[2].Routing().FindProviders(ctx, p, options.Routing.NumProviders(1))
core/coreunix/add.go
+4 -4
@@ -103,7 +103,7 @@ func (adder *Adder) mfsRoot() (*mfs.Root, error) {
103 }
104
105 // Note, this adds it to DAGService already.
106 - mr, err := mfs.NewEmptyRoot(adder.ctx, adder.dagService, nil, mfs.MkdirOpts{
106 + mr, err := mfs.NewEmptyRoot(adder.ctx, adder.dagService, nil, nil, mfs.MkdirOpts{
107 CidBuilder: adder.CidBuilder,
108 MaxLinks: adder.MaxDirectoryLinks,
109 MaxHAMTFanout: adder.MaxHAMTFanout,
@@ -416,7 +416,7 @@ func (adder *Adder) addFileNode(ctx context.Context, path string, file files.Nod
416 case files.Directory:
417 return adder.addDir(ctx, path, f, toplevel)
418 case *files.Symlink:
419 - return adder.addSymlink(path, f)
419 + return adder.addSymlink(ctx, path, f)
420 case files.File:
421 return adder.addFile(path, f)
422 default:
@@ -424,7 +424,7 @@ func (adder *Adder) addFileNode(ctx context.Context, path string, file files.Nod
424 }
425 }
426
427 -func (adder *Adder) addSymlink(path string, l *files.Symlink) error {
427 +func (adder *Adder) addSymlink(ctx context.Context, path string, l *files.Symlink) error {
428 sdata, err := unixfs.SymlinkData(l.Target)
429 if err != nil {
430 return err
@@ -482,7 +482,7 @@ func (adder *Adder) addDir(ctx context.Context, path string, dir files.Directory
482
483 // if we need to store mode or modification time then create a new root which includes that data
484 if toplevel && (adder.FileMode != 0 || !adder.FileMtime.IsZero()) {
485 - mr, err := mfs.NewEmptyRoot(ctx, adder.dagService, nil,
485 + mr, err := mfs.NewEmptyRoot(ctx, adder.dagService, nil, nil,
486 mfs.MkdirOpts{
487 CidBuilder: adder.CidBuilder,
488 MaxLinks: adder.MaxDirectoryLinks,
core/node/bitswap.go
-28
@@ -14,8 +14,6 @@ import (
14 "github.com/ipfs/boxo/bitswap/network/httpnet"
15 blockstore "github.com/ipfs/boxo/blockstore"
16 exchange "github.com/ipfs/boxo/exchange"
17 - "github.com/ipfs/boxo/exchange/providing"
18 - provider "github.com/ipfs/boxo/provider"
17 rpqm "github.com/ipfs/boxo/routing/providerquerymanager"
18 "github.com/ipfs/go-cid"
19 ipld "github.com/ipfs/go-ipld-format"
@@ -222,32 +220,6 @@ func OnlineExchange(isBitswapActive bool) interface{} {
220 }
221 }
222
225 -type providingExchangeIn struct {
226 - fx.In
227 -
228 - BaseExch exchange.Interface
229 - Provider provider.System
230 -}
231 -
232 -// ProvidingExchange creates a providing.Exchange with the existing exchange
233 -// and the provider.System.
234 -// We cannot do this in OnlineExchange because it causes cycles so this is for
235 -// a decorator.
236 -func ProvidingExchange(provide bool) interface{} {
237 - return func(in providingExchangeIn, lc fx.Lifecycle) exchange.Interface {
238 - exch := in.BaseExch
239 - if provide {
240 - exch = providing.New(in.BaseExch, in.Provider)
241 - lc.Append(fx.Hook{
242 - OnStop: func(ctx context.Context) error {
243 - return exch.Close()
244 - },
245 - })
246 - }
247 - return exch
248 - }
249 -}
250 -
223 type noopExchange struct {
224 closer io.Closer
225 }
core/node/core.go
+104 -64
@@ -2,6 +2,7 @@ package node
2
3 import (
4 "context"
5 + "errors"
6 "fmt"
7
8 "github.com/ipfs/boxo/blockservice"
@@ -17,6 +18,7 @@ import (
18 pathresolver "github.com/ipfs/boxo/path/resolver"
19 pin "github.com/ipfs/boxo/pinning/pinner"
20 "github.com/ipfs/boxo/pinning/pinner/dspinner"
21 + provider "github.com/ipfs/boxo/provider"
22 "github.com/ipfs/go-cid"
23 "github.com/ipfs/go-datastore"
24 format "github.com/ipfs/go-ipld-format"
@@ -47,25 +49,50 @@ func BlockService(cfg *config.Config) func(lc fx.Lifecycle, bs blockstore.Blocks
49 }
50
51 // Pinning creates new pinner which tells GC which blocks should be kept
50 -func Pinning(bstore blockstore.Blockstore, ds format.DAGService, repo repo.Repo) (pin.Pinner, error) {
51 - rootDS := repo.Datastore()
52 +func Pinning(strategy string) func(bstore blockstore.Blockstore, ds format.DAGService, repo repo.Repo, prov provider.System) (pin.Pinner, error) {
53 + // Parse strategy at function creation time (not inside the returned function)
54 + // This happens before the provider is created, which is why we pass the strategy
55 + // string and parse it here, rather than using fx-provided ProvidingStrategy.
56 + strategyFlag := config.ParseReproviderStrategy(strategy)
57 +
58 + return func(bstore blockstore.Blockstore,
59 + ds format.DAGService,
60 + repo repo.Repo,
61 + prov provider.System) (pin.Pinner, error) {
62 + rootDS := repo.Datastore()
63
53 - syncFn := func(ctx context.Context) error {
54 - if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil {
55 - return err
64 + syncFn := func(ctx context.Context) error {
65 + if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil {
66 + return err
67 + }
68 + return rootDS.Sync(ctx, filestore.FilestorePrefix)
69 + }
70 + syncDs := &syncDagService{ds, syncFn}
71 +
72 + ctx := context.TODO()
73 +
74 + var opts []dspinner.Option
75 + roots := (strategyFlag & config.ReproviderStrategyRoots) != 0
76 + pinned := (strategyFlag & config.ReproviderStrategyPinned) != 0
77 +
78 + // Important: Only one of WithPinnedProvider or WithRootsProvider should be active.
79 + // Having both would cause duplicate root advertisements since "pinned" includes all
80 + // pinned content (roots + children), while "roots" is just the root CIDs.
81 + // We prioritize "pinned" if both are somehow set (though this shouldn't happen
82 + // with proper strategy parsing).
83 + if pinned {
84 + opts = append(opts, dspinner.WithPinnedProvider(prov))
85 + } else if roots {
86 + opts = append(opts, dspinner.WithRootsProvider(prov))
87 }
57 - return rootDS.Sync(ctx, filestore.FilestorePrefix)
58 - }
59 - syncDs := &syncDagService{ds, syncFn}
88
61 - ctx := context.TODO()
89 + pinning, err := dspinner.New(ctx, rootDS, syncDs, opts...)
90 + if err != nil {
91 + return nil, err
92 + }
93
63 - pinning, err := dspinner.New(ctx, rootDS, syncDs)
64 - if err != nil {
65 - return nil, err
94 + return pinning, nil
95 }
67 -
68 - return pinning, nil
96 }
97
98 var (
@@ -152,63 +179,76 @@ func Dag(bs blockservice.BlockService) format.DAGService {
179 }
180
181 // Files loads persisted MFS root
155 -func Files(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.DAGService, bs blockstore.Blockstore) (*mfs.Root, error) {
156 - dsk := datastore.NewKey("/local/filesroot")
157 - pf := func(ctx context.Context, c cid.Cid) error {
158 - rootDS := repo.Datastore()
159 - if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil {
160 - return err
161 - }
162 - if err := rootDS.Sync(ctx, filestore.FilestorePrefix); err != nil {
163 - return err
164 - }
165 -
166 - if err := rootDS.Put(ctx, dsk, c.Bytes()); err != nil {
167 - return err
182 +func Files(strategy string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.DAGService, bs blockstore.Blockstore, prov provider.System) (*mfs.Root, error) {
183 + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.DAGService, bs blockstore.Blockstore, prov provider.System) (*mfs.Root, error) {
184 + dsk := datastore.NewKey("/local/filesroot")
185 + pf := func(ctx context.Context, c cid.Cid) error {
186 + rootDS := repo.Datastore()
187 + if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil {
188 + return err
189 + }
190 + if err := rootDS.Sync(ctx, filestore.FilestorePrefix); err != nil {
191 + return err
192 + }
193 +
194 + if err := rootDS.Put(ctx, dsk, c.Bytes()); err != nil {
195 + return err
196 + }
197 + return rootDS.Sync(ctx, dsk)
198 }
169 - return rootDS.Sync(ctx, dsk)
170 - }
171 -
172 - var nd *merkledag.ProtoNode
173 - ctx := helpers.LifecycleCtx(mctx, lc)
174 - val, err := repo.Datastore().Get(ctx, dsk)
199
176 - switch {
177 - case err == datastore.ErrNotFound || val == nil:
178 - nd = unixfs.EmptyDirNode()
179 - err := dag.Add(ctx, nd)
180 - if err != nil {
181 - return nil, fmt.Errorf("failure writing filesroot to dagstore: %s", err)
182 - }
183 - case err == nil:
184 - c, err := cid.Cast(val)
185 - if err != nil {
200 + var nd *merkledag.ProtoNode
201 + ctx := helpers.LifecycleCtx(mctx, lc)
202 + val, err := repo.Datastore().Get(ctx, dsk)
203 +
204 + switch {
205 + case errors.Is(err, datastore.ErrNotFound):
206 + nd = unixfs.EmptyDirNode()
207 + err := dag.Add(ctx, nd)
208 + if err != nil {
209 + return nil, fmt.Errorf("failure writing filesroot to dagstore: %s", err)
210 + }
211 + case err == nil:
212 + c, err := cid.Cast(val)
213 + if err != nil {
214 + return nil, err
215 + }
216 +
217 + offineDag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
218 + rnd, err := offineDag.Get(ctx, c)
219 + if err != nil {
220 + return nil, fmt.Errorf("error loading filesroot from dagservice: %s", err)
221 + }
222 +
223 + pbnd, ok := rnd.(*merkledag.ProtoNode)
224 + if !ok {
225 + return nil, merkledag.ErrNotProtobuf
226 + }
227 +
228 + nd = pbnd
229 + default:
230 return nil, err
231 }
232
189 - offineDag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
190 - rnd, err := offineDag.Get(ctx, c)
191 - if err != nil {
192 - return nil, fmt.Errorf("error loading filesroot from dagservice: %s", err)
233 + // MFS (Mutable File System) provider integration:
234 + // Only pass the provider to MFS when the strategy includes "mfs".
235 + // MFS will call Provide() on every DAGService.Add() operation,
236 + // which is sufficient for the "mfs" strategy - it ensures all
237 + // MFS content gets announced as it's added or modified.
238 + // For non-mfs strategies, we set provider to nil to avoid unnecessary providing.
239 + strategyFlag := config.ParseReproviderStrategy(strategy)
240 + if strategyFlag&config.ReproviderStrategyMFS == 0 {
241 + prov = nil
242 }
243
195 - pbnd, ok := rnd.(*merkledag.ProtoNode)
196 - if !ok {
197 - return nil, merkledag.ErrNotProtobuf
198 - }
199 -
200 - nd = pbnd
201 - default:
202 - return nil, err
203 - }
244 + root, err := mfs.NewRoot(ctx, dag, nd, pf, prov)
245
205 - root, err := mfs.NewRoot(ctx, dag, nd, pf)
206 -
207 - lc.Append(fx.Hook{
208 - OnStop: func(ctx context.Context) error {
209 - return root.Close()
210 - },
211 - })
246 + lc.Append(fx.Hook{
247 + OnStop: func(ctx context.Context) error {
248 + return root.Close()
249 + },
250 + })
251
213 - return root, err
252 + return root, err
253 + }
254 }
core/node/groups.go
+10 -5
@@ -250,7 +250,12 @@ func Storage(bcfg *BuildCfg, cfg *config.Config) fx.Option {
250 return fx.Options(
251 fx.Provide(RepoConfig),
252 fx.Provide(Datastore),
253 - fx.Provide(BaseBlockstoreCtor(cacheOpts, cfg.Datastore.HashOnRead, cfg.Datastore.WriteThrough.WithDefault(config.DefaultWriteThrough))),
253 + fx.Provide(BaseBlockstoreCtor(
254 + cacheOpts,
255 + cfg.Datastore.HashOnRead,
256 + cfg.Datastore.WriteThrough.WithDefault(config.DefaultWriteThrough),
257 + cfg.Reprovider.Strategy.WithDefault(config.DefaultReproviderStrategy),
258 + )),
259 finalBstore,
260 )
261 }
@@ -350,8 +355,6 @@ func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
355 fx.Provide(BitswapOptions(cfg)),
356 fx.Provide(Bitswap(isBitswapServerEnabled, isBitswapLibp2pEnabled, isHTTPRetrievalEnabled)),
357 fx.Provide(OnlineExchange(isBitswapLibp2pEnabled)),
353 - // Replace our Exchange with a Providing exchange!
354 - fx.Decorate(ProvidingExchange(isProviderEnabled && isBitswapServerEnabled)),
358 fx.Provide(DNSResolver),
359 fx.Provide(Namesys(ipnsCacheSize, cfg.Ipns.MaxCacheTTL.WithDefault(config.DefaultIpnsMaxCacheTTL))),
360 fx.Provide(Peering),
@@ -391,8 +394,6 @@ var Core = fx.Options(
394 fx.Provide(Dag),
395 fx.Provide(FetcherConfig),
396 fx.Provide(PathResolverConfig),
394 - fx.Provide(Pinning),
395 - fx.Provide(Files),
397 )
398
399 func Networked(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.PartialLimitConfig) fx.Option {
@@ -442,6 +443,8 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
443 uio.HAMTShardingSize = int(shardSingThresholdInt)
444 uio.DefaultShardWidth = int(shardMaxFanout)
445
446 + providerStrategy := cfg.Reprovider.Strategy.WithDefault(config.DefaultReproviderStrategy)
447 +
448 return fx.Options(
449 bcfgOpts,
450
@@ -450,6 +453,8 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
453 IPNS,
454 Networked(bcfg, cfg, userResourceOverrides),
455 fx.Provide(BlockService(cfg)),
456 + fx.Provide(Pinning(providerStrategy)),
457 + fx.Provide(Files(providerStrategy)),
458 Core,
459 )
460 }
core/node/provider.go
+152 -70
@@ -10,9 +10,12 @@ import (
10 "github.com/ipfs/boxo/fetcher"
11 "github.com/ipfs/boxo/mfs"
12 pin "github.com/ipfs/boxo/pinning/pinner"
13 + "github.com/ipfs/boxo/pinning/pinner/dspinner"
14 provider "github.com/ipfs/boxo/provider"
15 "github.com/ipfs/go-cid"
16 "github.com/ipfs/go-datastore"
17 + "github.com/ipfs/go-datastore/query"
18 + "github.com/ipfs/kubo/config"
19 "github.com/ipfs/kubo/repo"
20 irouting "github.com/ipfs/kubo/routing"
21 "go.uber.org/fx"
@@ -26,12 +29,14 @@ const sampledBatchSize = 1000
29 // Datastore key used to store previous reprovide strategy.
30 const reprovideStrategyKey = "/reprovideStrategy"
31
29 -func ProviderSys(reprovideInterval time.Duration, acceleratedDHTClient bool, provideWorkerCount int, reprovideStrategy string) fx.Option {
30 - return fx.Provide(func(lc fx.Lifecycle, cr irouting.ProvideManyRouter, keyProvider provider.KeyChanFunc, repo repo.Repo, bs blockstore.Blockstore) (provider.System, error) {
32 +func ProviderSys(reprovideInterval time.Duration, acceleratedDHTClient bool, provideWorkerCount int) fx.Option {
33 + return fx.Provide(func(lc fx.Lifecycle, cr irouting.ProvideManyRouter, repo repo.Repo) (provider.System, error) {
34 + // Initialize provider.System first, before pinner/blockstore/etc.
35 + // The KeyChanFunc will be set later via SetKeyProvider() once we have
36 + // created the pinner, blockstore and other dependencies.
37 opts := []provider.Option{
38 provider.Online(cr),
39 provider.ReproviderInterval(reprovideInterval),
34 - provider.KeyProvider(keyProvider),
40 provider.ProvideWorkerCount(provideWorkerCount),
41 }
42 if !acceleratedDHTClient && reprovideInterval > 0 {
@@ -50,16 +55,20 @@ func ProviderSys(reprovideInterval time.Duration, acceleratedDHTClient bool, pro
55 defer cancel()
56
57 // FIXME: I want a running counter of blocks so size of blockstore can be an O(1) lookup.
53 - ch, err := bs.AllKeysChan(ctx)
58 + // Note: talk to datastore directly, as to not depend on Blockstore here.
59 + qr, err := repo.Datastore().Query(ctx, query.Query{
60 + Prefix: blockstore.BlockPrefix.String(),
61 + KeysOnly: true})
62 if err != nil {
63 logger.Errorf("fetching AllKeysChain in provider ThroughputReport: %v", err)
64 return false
65 }
66 + defer qr.Close()
67 count = 0
68 countLoop:
69 for {
70 select {
62 - case _, ok := <-ch:
71 + case _, ok := <-qr.Next():
72 if !ok {
73 break countLoop
74 }
@@ -120,34 +129,10 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#routingaccelerateddhtcli
129 }, sampledBatchSize))
130 }
131
123 - var strategyChanged bool
124 - ctx := context.Background()
125 - ds := repo.Datastore()
126 - strategyKey := datastore.NewKey(reprovideStrategyKey)
127 -
128 - prev, err := ds.Get(ctx, strategyKey)
129 - if err != nil && !errors.Is(err, datastore.ErrNotFound) {
130 - logger.Error("cannot read previous reprovide strategy", "err", err)
131 - } else if string(prev) != reprovideStrategy {
132 - strategyChanged = true
133 - }
134 -
135 - sys, err := provider.New(ds, opts...)
132 + sys, err := provider.New(repo.Datastore(), opts...)
133 if err != nil {
134 return nil, err
135 }
139 - if strategyChanged {
140 - logger.Infow("Reprovider.Strategy changed, clearing provide queue", "previous", string(prev), "current", reprovideStrategy)
141 - sys.Clear()
142 - if reprovideStrategy == "" {
143 - err = ds.Delete(ctx, strategyKey)
144 - } else {
145 - err = ds.Put(ctx, strategyKey, []byte(reprovideStrategy))
146 - }
147 - if err != nil {
148 - logger.Error("cannot update reprovide strategy", "err", err)
149 - }
150 - }
136
137 lc.Append(fx.Hook{
138 OnStop: func(ctx context.Context) error {
@@ -162,22 +147,19 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#routingaccelerateddhtcli
147 // ONLINE/OFFLINE
148
149 // OnlineProviders groups units managing provider routing records online
165 -func OnlineProviders(provide bool, reprovideStrategy string, reprovideInterval time.Duration, acceleratedDHTClient bool, provideWorkerCount int) fx.Option {
150 +func OnlineProviders(provide bool, providerStrategy string, reprovideInterval time.Duration, acceleratedDHTClient bool, provideWorkerCount int) fx.Option {
151 if !provide {
152 return OfflineProviders()
153 }
154
170 - var keyProvider fx.Option
171 - switch reprovideStrategy {
172 - case "all", "", "roots", "pinned", "mfs", "pinned+mfs", "flat":
173 - keyProvider = fx.Provide(newProvidingStrategy(reprovideStrategy))
174 - default:
175 - return fx.Error(fmt.Errorf("unknown reprovider strategy %q", reprovideStrategy))
155 + strategyFlag := config.ParseReproviderStrategy(providerStrategy)
156 + if strategyFlag == 0 {
157 + return fx.Error(fmt.Errorf("unknown reprovider strategy %q", providerStrategy))
158 }
159
160 return fx.Options(
179 - keyProvider,
180 - ProviderSys(reprovideInterval, acceleratedDHTClient, provideWorkerCount, reprovideStrategy),
161 + fx.Provide(setReproviderKeyProvider(providerStrategy)),
162 + ProviderSys(reprovideInterval, acceleratedDHTClient, provideWorkerCount),
163 )
164 }
165
@@ -215,38 +197,138 @@ func mfsRootProvider(mfsRoot *mfs.Root) provider.KeyChanFunc {
197 }
198 }
199
218 -func newProvidingStrategy(strategy string) interface{} {
219 - type input struct {
220 - fx.In
221 - Pinner pin.Pinner
222 - Blockstore blockstore.Blockstore
223 - OfflineIPLDFetcher fetcher.Factory `name:"offlineIpldFetcher"`
224 - OfflineUnixFSFetcher fetcher.Factory `name:"offlineUnixfsFetcher"`
225 - MFSRoot *mfs.Root
200 +type provStrategyIn struct {
201 + fx.In
202 + Pinner pin.Pinner
203 + Blockstore blockstore.Blockstore
204 + OfflineIPLDFetcher fetcher.Factory `name:"offlineIpldFetcher"`
205 + OfflineUnixFSFetcher fetcher.Factory `name:"offlineUnixfsFetcher"`
206 + MFSRoot *mfs.Root
207 + Provider provider.System
208 + Repo repo.Repo
209 +}
210 +
211 +type provStrategyOut struct {
212 + fx.Out
213 + ProvidingStrategy config.ReproviderStrategy
214 + ProvidingKeyChanFunc provider.KeyChanFunc
215 +}
216 +
217 +// createKeyProvider creates the appropriate KeyChanFunc based on strategy.
218 +// Each strategy has different behavior:
219 +// - "roots": Only root CIDs of pinned content
220 +// - "pinned": All pinned content (roots + children)
221 +// - "mfs": Only MFS content
222 +// - "flat": All blocks, no prioritization
223 +// - "all": Prioritized: pins first, then MFS roots, then all blocks
224 +func createKeyProvider(strategyFlag config.ReproviderStrategy, in provStrategyIn) provider.KeyChanFunc {
225 + switch strategyFlag {
226 + case config.ReproviderStrategyRoots:
227 + return provider.NewBufferedProvider(dspinner.NewPinnedProvider(true, in.Pinner, in.OfflineIPLDFetcher))
228 + case config.ReproviderStrategyPinned:
229 + return provider.NewBufferedProvider(dspinner.NewPinnedProvider(false, in.Pinner, in.OfflineIPLDFetcher))
230 + case config.ReproviderStrategyPinned | config.ReproviderStrategyMFS:
231 + return provider.NewPrioritizedProvider(
232 + provider.NewBufferedProvider(dspinner.NewPinnedProvider(false, in.Pinner, in.OfflineIPLDFetcher)),
233 + mfsProvider(in.MFSRoot, in.OfflineUnixFSFetcher),
234 + )
235 + case config.ReproviderStrategyMFS:
236 + return mfsProvider(in.MFSRoot, in.OfflineUnixFSFetcher)
237 + case config.ReproviderStrategyFlat:
238 + return in.Blockstore.AllKeysChan
239 + default: // "all", ""
240 + return createAllStrategyProvider(in)
241 + }
242 +}
243 +
244 +// createAllStrategyProvider creates the complex "all" strategy provider.
245 +// This implements a three-tier priority system:
246 +// 1. Root blocks of direct and recursive pins (highest priority)
247 +// 2. MFS root (medium priority)
248 +// 3. All other blocks in blockstore (lowest priority)
249 +func createAllStrategyProvider(in provStrategyIn) provider.KeyChanFunc {
250 + return provider.NewPrioritizedProvider(
251 + provider.NewPrioritizedProvider(
252 + provider.NewBufferedProvider(dspinner.NewPinnedProvider(true, in.Pinner, in.OfflineIPLDFetcher)),
253 + mfsRootProvider(in.MFSRoot),
254 + ),
255 + in.Blockstore.AllKeysChan,
256 + )
257 +}
258 +
259 +// detectStrategyChange checks if the reproviding strategy has changed from what's persisted.
260 +// Returns: (previousStrategy, hasChanged, error)
261 +func detectStrategyChange(ctx context.Context, strategy string, ds datastore.Datastore) (string, bool, error) {
262 + strategyKey := datastore.NewKey(reprovideStrategyKey)
263 +
264 + prev, err := ds.Get(ctx, strategyKey)
265 + if err != nil {
266 + if errors.Is(err, datastore.ErrNotFound) {
267 + return "", strategy != "", nil
268 + }
269 + return "", false, err
270 }
227 - return func(in input) provider.KeyChanFunc {
228 - switch strategy {
229 - case "roots":
230 - return provider.NewBufferedProvider(provider.NewPinnedProvider(true, in.Pinner, in.OfflineIPLDFetcher))
231 - case "pinned":
232 - return provider.NewBufferedProvider(provider.NewPinnedProvider(false, in.Pinner, in.OfflineIPLDFetcher))
233 - case "pinned+mfs":
234 - return provider.NewPrioritizedProvider(
235 - provider.NewBufferedProvider(provider.NewPinnedProvider(false, in.Pinner, in.OfflineIPLDFetcher)),
236 - mfsProvider(in.MFSRoot, in.OfflineUnixFSFetcher),
237 - )
238 - case "mfs":
239 - return mfsProvider(in.MFSRoot, in.OfflineUnixFSFetcher)
240 - case "flat":
241 - return provider.NewBlockstoreProvider(in.Blockstore)
242 - default: // "all", ""
243 - return provider.NewPrioritizedProvider(
244 - provider.NewPrioritizedProvider(
245 - provider.NewBufferedProvider(provider.NewPinnedProvider(true, in.Pinner, in.OfflineIPLDFetcher)),
246 - mfsRootProvider(in.MFSRoot),
247 - ),
248 - provider.NewBlockstoreProvider(in.Blockstore),
249 - )
271 +
272 + previousStrategy := string(prev)
273 + return previousStrategy, previousStrategy != strategy, nil
274 +}
275 +
276 +// persistStrategy saves the current reproviding strategy to the datastore.
277 +// Empty string strategies are deleted rather than stored.
278 +func persistStrategy(ctx context.Context, strategy string, ds datastore.Datastore) error {
279 + strategyKey := datastore.NewKey(reprovideStrategyKey)
280 +
281 + if strategy == "" {
282 + return ds.Delete(ctx, strategyKey)
283 + }
284 + return ds.Put(ctx, strategyKey, []byte(strategy))
285 +}
286 +
287 +// handleStrategyChange manages strategy change detection and queue clearing.
288 +// Strategy change detection: when the reproviding strategy changes,
289 +// we clear the provide queue to avoid unexpected behavior from mixing
290 +// strategies. This ensures a clean transition between different providing modes.
291 +func handleStrategyChange(strategy string, provider provider.System, ds datastore.Datastore) {
292 + ctx := context.Background()
293 +
294 + previous, changed, err := detectStrategyChange(ctx, strategy, ds)
295 + if err != nil {
296 + logger.Error("cannot read previous reprovide strategy", "err", err)
297 + return
298 + }
299 +
300 + if !changed {
301 + return
302 + }
303 +
304 + logger.Infow("Reprovider.Strategy changed, clearing provide queue", "previous", previous, "current", strategy)
305 + provider.Clear()
306 +
307 + if err := persistStrategy(ctx, strategy, ds); err != nil {
308 + logger.Error("cannot update reprovide strategy", "err", err)
309 + }
310 +}
311 +
312 +func setReproviderKeyProvider(strategy string) func(in provStrategyIn) provStrategyOut {
313 + strategyFlag := config.ParseReproviderStrategy(strategy)
314 +
315 + return func(in provStrategyIn) provStrategyOut {
316 + // Create the appropriate key provider based on strategy
317 + kcf := createKeyProvider(strategyFlag, in)
318 +
319 + // SetKeyProvider breaks the circular dependency between provider, blockstore, and pinner.
320 + // We cannot create the blockstore without the provider (it needs to provide blocks),
321 + // and we cannot determine the reproviding strategy without the pinner/blockstore.
322 + // This deferred initialization allows us to create provider.System first,
323 + // then set the actual key provider function after all dependencies are ready.
324 + in.Provider.SetKeyProvider(kcf)
325 +
326 + // Handle strategy changes (detection, queue clearing, persistence)
327 + handleStrategyChange(strategy, in.Provider, in.Repo.Datastore())
328 +
329 + return provStrategyOut{
330 + ProvidingStrategy: strategyFlag,
331 + ProvidingKeyChanFunc: kcf,
332 }
333 }
334 }
core/node/storage.go
+25 -4
@@ -2,6 +2,7 @@ package node
2
3 import (
4 blockstore "github.com/ipfs/boxo/blockstore"
5 + provider "github.com/ipfs/boxo/provider"
6 "github.com/ipfs/go-datastore"
7 config "github.com/ipfs/kubo/config"
8 "go.uber.org/fx"
@@ -27,11 +28,31 @@ func Datastore(repo repo.Repo) datastore.Datastore {
28 type BaseBlocks blockstore.Blockstore
29
30 // BaseBlockstoreCtor creates cached blockstore backed by the provided datastore
30 -func BaseBlockstoreCtor(cacheOpts blockstore.CacheOpts, hashOnRead bool, writeThrough bool) func(mctx helpers.MetricsCtx, repo repo.Repo, lc fx.Lifecycle) (bs BaseBlocks, err error) {
31 - return func(mctx helpers.MetricsCtx, repo repo.Repo, lc fx.Lifecycle) (bs BaseBlocks, err error) {
31 +func BaseBlockstoreCtor(
32 + cacheOpts blockstore.CacheOpts,
33 + hashOnRead bool,
34 + writeThrough bool,
35 + providingStrategy string,
36 +
37 +) func(mctx helpers.MetricsCtx, repo repo.Repo, prov provider.System, lc fx.Lifecycle) (bs BaseBlocks, err error) {
38 + return func(mctx helpers.MetricsCtx, repo repo.Repo, prov provider.System, lc fx.Lifecycle) (bs BaseBlocks, err error) {
39 + opts := []blockstore.Option{blockstore.WriteThrough(writeThrough)}
40 +
41 + // Blockstore providing integration:
42 + // When strategy includes "all" or "flat", the blockstore directly provides blocks as they're Put.
43 + // Important: Provide calls from blockstore are intentionally BLOCKING.
44 + // The Provider implementation (not the blockstore) should handle concurrency/queuing.
45 + // This avoids spawning unbounded goroutines for concurrent block additions.
46 + strategyFlag := config.ParseReproviderStrategy(providingStrategy)
47 + shouldProvide := config.ReproviderStrategyAll | config.ReproviderStrategyFlat
48 + if strategyFlag&shouldProvide != 0 {
49 + opts = append(opts, blockstore.Provider(prov))
50 + }
51 +
52 // hash security
33 - bs = blockstore.NewBlockstore(repo.Datastore(),
34 - blockstore.WriteThrough(writeThrough),
53 + bs = blockstore.NewBlockstore(
54 + repo.Datastore(),
55 + opts...,
56 )
57 bs = &verifbs.VerifBS{Blockstore: bs}
58 bs, err = blockstore.CachedBlockstore(helpers.LifecycleCtx(mctx, lc), bs, cacheOpts)
docs/changelogs/v0.37.md
+16 -1
@@ -2,7 +2,7 @@
2
3 <a href="https://ipshipyard.com/"><img align="right" src="https://github.com/user-attachments/assets/39ed3504-bb71-47f6-9bf8-cb9a1698f272" /></a>
4
5 -This release was brought to you by the [Interplanetary Shipyard](https://ipshipyard.com/) team.
5 +This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
6
7 - [v0.37.0](#v0370)
8
@@ -12,6 +12,7 @@ This release was brought to you by the [Interplanetary Shipyard](https://ipship
12 - [🔦 Highlights](#-highlights)
13 - [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes)
14 - [Named pins in `ipfs add` command](#-named-pins-in-ipfs-add-command)
15 + - [⚙️ `Reprovider.Strategy` is now consistently respected](#-reprovider-strategy-is-now-consistently-respected)
16 - [Removed unnecessary dependencies](#removed-unnecessary-dependencies)
17 - [Deprecated `ipfs stats reprovide`](#deprecated-ipfs-stats-reprovide)
18 - [📦️ Important dependency updates](#-important-dependency-updates)
@@ -45,6 +46,20 @@ $ ipfs pin ls --names
46 bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi recursive testname
47 ```
48
49 +#### ⚙️ `Reprovider.Strategy` is now consistently respected
50 +
51 +Prior to this version, files added, blocks received etc. were "provided" to the network (announced on the DHT) regardless of the ["reproviding strategy" setting](https://github.com/ipfs/kubo/blob/master/docs/config.md#reproviderstrategy). For example:
52 +
53 +- Strategy set to "pinned" + `ipfs add --pin=false` → file was provided regardless
54 +- Strategy set to "roots" + `ipfs pin add` → all blocks (not only the root) were provided
55 +
56 +Only the periodic "reproviding" action (runs every 22h by default) respected the strategy.
57 +
58 +This was inefficient as content that should not be provided was getting provided once. Now all operations respect `Reprovider.Strategy`. If set to "roots", no blocks other than pin roots will be provided regardless of what is fetched, added etc.
59 +
60 +> [!NOTE]
61 +> **Behavior change:** The `--offline` flag no longer affects providing behavior. Both `ipfs add` and `ipfs --offline add` now provide blocks according to the reproviding strategy when run against an online daemon (previously `--offline add` did not provide). Since `ipfs add` has been nearly as fast as offline mode [since v0.35](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.35.md#fast-ipfs-add-in-online-mode), `--offline` is rarely needed. To run truly offline operations, use `ipfs --offline daemon`.
62 +
63 #### Removed unnecessary dependencies
64
65 Kubo has been cleaned up by removing unnecessary dependencies and packages:
docs/examples/kubo-as-a-library/go.mod
+1 -1
@@ -7,7 +7,7 @@ go 1.24
7 replace github.com/ipfs/kubo => ./../../..
8
9 require (
10 - github.com/ipfs/boxo v0.33.1
10 + github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08
11 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12 github.com/libp2p/go-libp2p v0.42.1
13 github.com/multiformats/go-multiaddr v0.16.0
docs/examples/kubo-as-a-library/go.sum
+2 -2
@@ -291,8 +291,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
291 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
292 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
293 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
294 -github.com/ipfs/boxo v0.33.1 h1:89m+ksw+cYi0ecTNTJ71IRS5ZrLiovmO6XWHIOGhAEg=
295 -github.com/ipfs/boxo v0.33.1/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
294 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
295 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
296 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
297 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
298 github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
fuse/ipns/ipns_unix.go
+4 -1
@@ -108,7 +108,10 @@ func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key) (*mfs.Root
108 return nil, nil, dag.ErrNotProtobuf
109 }
110
111 - root, err := mfs.NewRoot(ctx, ipfs.Dag(), pbnode, ipnsPubFunc(ipfs, key))
111 + // We have no access to provider.System from the CoreAPI. The Routing
112 + // part offers Provide through the router so it may be slow/risky
113 + // to give that here to MFS. Therefore we leave as nil.
114 + root, err := mfs.NewRoot(ctx, ipfs.Dag(), pbnode, ipnsPubFunc(ipfs, key), nil)
115 if err != nil {
116 return nil, nil, err
117 }
go.mod
+1 -1
@@ -22,7 +22,7 @@ require (
22 github.com/hashicorp/go-version v1.7.0
23 github.com/ipfs-shipyard/nopfs v0.0.14
24 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
25 - github.com/ipfs/boxo v0.33.1
25 + github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08
26 github.com/ipfs/go-block-format v0.2.2
27 github.com/ipfs/go-cid v0.5.0
28 github.com/ipfs/go-cidutil v0.1.0
go.sum
+2 -2
@@ -358,8 +358,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
358 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
359 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
360 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
361 -github.com/ipfs/boxo v0.33.1 h1:89m+ksw+cYi0ecTNTJ71IRS5ZrLiovmO6XWHIOGhAEg=
362 -github.com/ipfs/boxo v0.33.1/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
361 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
362 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
363 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
364 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
365 github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
test/cli/harness/ipfs.go
+28
@@ -101,6 +101,34 @@ func (n *Node) IPFSAdd(content io.Reader, args ...string) string {
101 return out
102 }
103
104 +func (n *Node) IPFSBlockPut(content io.Reader, args ...string) string {
105 + log.Debugf("node %d block put with args: %v", n.ID, args)
106 + fullArgs := []string{"block", "put"}
107 + fullArgs = append(fullArgs, args...)
108 + res := n.Runner.MustRun(RunRequest{
109 + Path: n.IPFSBin,
110 + Args: fullArgs,
111 + CmdOpts: []CmdOpt{RunWithStdin(content)},
112 + })
113 + out := strings.TrimSpace(res.Stdout.String())
114 + log.Debugf("block put result: %q", out)
115 + return out
116 +}
117 +
118 +func (n *Node) IPFSDAGPut(content io.Reader, args ...string) string {
119 + log.Debugf("node %d dag put with args: %v", n.ID, args)
120 + fullArgs := []string{"dag", "put"}
121 + fullArgs = append(fullArgs, args...)
122 + res := n.Runner.MustRun(RunRequest{
123 + Path: n.IPFSBin,
124 + Args: fullArgs,
125 + CmdOpts: []CmdOpt{RunWithStdin(content)},
126 + })
127 + out := strings.TrimSpace(res.Stdout.String())
128 + log.Debugf("dag put result: %q", out)
129 + return out
130 +}
131 +
132 func (n *Node) IPFSDagImport(content io.Reader, cid string, args ...string) error {
133 log.Debugf("node %d dag import with args: %v", n.ID, args)
134 fullArgs := []string{"dag", "import", "--pin-roots=false"}
test/cli/provider_test.go
+254 -25
@@ -21,6 +21,12 @@ func TestProvider(t *testing.T) {
21 return nodes.StartDaemons().Connect()
22 }
23
24 + initNodesWithoutStart := func(t *testing.T, n int, fn func(n *harness.Node)) harness.Nodes {
25 + nodes := harness.NewT(t).NewNodes(n).Init()
26 + nodes.ForEachPar(fn)
27 + return nodes
28 + }
29 +
30 expectNoProviders := func(t *testing.T, cid string, nodes ...*harness.Node) {
31 for _, node := range nodes {
32 res := node.IPFS("routing", "findprovs", "-n=1", cid)
@@ -44,9 +50,47 @@ func TestProvider(t *testing.T) {
50 defer nodes.StopDaemons()
51
52 cid := nodes[0].IPFSAddStr(time.Now().String())
47 - // Reprovide as initialProviderDelay still ongoing
48 - res := nodes[0].IPFS("routing", "reprovide")
49 - require.NoError(t, res.Err)
53 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
54 + })
55 +
56 + t.Run("Provider.Enabled=true announces new CIDs created by ipfs add --pin=false with default strategy", func(t *testing.T) {
57 + t.Parallel()
58 +
59 + nodes := initNodes(t, 2, func(n *harness.Node) {
60 + n.SetIPFSConfig("Provider.Enabled", true)
61 + // Default strategy is "all" which should provide even unpinned content
62 + })
63 + defer nodes.StopDaemons()
64 +
65 + cid := nodes[0].IPFSAddStr(time.Now().String(), "--pin=false")
66 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
67 + })
68 +
69 + t.Run("Provider.Enabled=true announces new CIDs created by ipfs block put --pin=false with default strategy", func(t *testing.T) {
70 + t.Parallel()
71 +
72 + nodes := initNodes(t, 2, func(n *harness.Node) {
73 + n.SetIPFSConfig("Provider.Enabled", true)
74 + // Default strategy is "all" which should provide unpinned content from block put
75 + })
76 + defer nodes.StopDaemons()
77 +
78 + data := testutils.RandomBytes(256)
79 + cid := nodes[0].IPFSBlockPut(bytes.NewReader(data), "--pin=false")
80 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
81 + })
82 +
83 + t.Run("Provider.Enabled=true announces new CIDs created by ipfs dag put --pin=false with default strategy", func(t *testing.T) {
84 + t.Parallel()
85 +
86 + nodes := initNodes(t, 2, func(n *harness.Node) {
87 + n.SetIPFSConfig("Provider.Enabled", true)
88 + // Default strategy is "all" which should provide unpinned content from dag put
89 + })
90 + defer nodes.StopDaemons()
91 +
92 + dagData := `{"hello": "world", "timestamp": "` + time.Now().String() + `"}`
93 + cid := nodes[0].IPFSDAGPut(bytes.NewReader([]byte(dagData)), "--pin=false")
94 expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
95 })
96
@@ -100,7 +144,7 @@ func TestProvider(t *testing.T) {
144 })
145 defer nodes.StopDaemons()
146
103 - cid := nodes[0].IPFSAddStr(time.Now().String(), "--offline")
147 + cid := nodes[0].IPFSAddStr(time.Now().String())
148
149 expectNoProviders(t, cid, nodes[1:]...)
150
@@ -120,7 +164,7 @@ func TestProvider(t *testing.T) {
164 })
165 defer nodes.StopDaemons()
166
123 - cid := nodes[0].IPFSAddStr(time.Now().String(), "--offline")
167 + cid := nodes[0].IPFSAddStr(time.Now().String())
168
169 expectNoProviders(t, cid, nodes[1:]...)
170
@@ -131,7 +175,7 @@ func TestProvider(t *testing.T) {
175 expectNoProviders(t, cid, nodes[1:]...)
176 })
177
134 - t.Run("Reprovides with 'all' strategy", func(t *testing.T) {
178 + t.Run("Provide with 'all' strategy", func(t *testing.T) {
179 t.Parallel()
180
181 nodes := initNodes(t, 2, func(n *harness.Node) {
@@ -139,8 +183,124 @@ func TestProvider(t *testing.T) {
183 })
184 defer nodes.StopDaemons()
185
142 - cid := nodes[0].IPFSAddStr(time.Now().String(), "--local")
186 + cid := nodes[0].IPFSAddStr("all strategy")
187 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
188 + })
189 +
190 + t.Run("Provide with 'flat' strategy", func(t *testing.T) {
191 + t.Parallel()
192 +
193 + nodes := initNodes(t, 2, func(n *harness.Node) {
194 + n.SetIPFSConfig("Reprovider.Strategy", "flat")
195 + })
196 + defer nodes.StopDaemons()
197 +
198 + cid := nodes[0].IPFSAddStr("flat strategy")
199 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
200 + })
201 +
202 + t.Run("Provide with 'pinned' strategy", func(t *testing.T) {
203 + t.Parallel()
204 +
205 + nodes := initNodes(t, 2, func(n *harness.Node) {
206 + n.SetIPFSConfig("Reprovider.Strategy", "pinned")
207 + })
208 + defer nodes.StopDaemons()
209 +
210 + // Add a non-pinned CID (should not be provided)
211 + cid := nodes[0].IPFSAddStr("pinned strategy", "--pin=false")
212 + expectNoProviders(t, cid, nodes[1:]...)
213 +
214 + // Pin the CID (should now be provided)
215 + nodes[0].IPFS("pin", "add", cid)
216 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
217 + })
218 +
219 + t.Run("Provide with 'pinned+mfs' strategy", func(t *testing.T) {
220 + t.Parallel()
221 +
222 + nodes := initNodes(t, 2, func(n *harness.Node) {
223 + n.SetIPFSConfig("Reprovider.Strategy", "pinned+mfs")
224 + })
225 + defer nodes.StopDaemons()
226 +
227 + // Add a pinned CID (should be provided)
228 + cidPinned := nodes[0].IPFSAddStr("pinned content")
229 + cidUnpinned := nodes[0].IPFSAddStr("unpinned content", "--pin=false")
230 + cidMFS := nodes[0].IPFSAddStr("mfs content", "--pin=false")
231 + nodes[0].IPFS("files", "cp", "/ipfs/"+cidMFS, "/myfile")
232 +
233 + n0pid := nodes[0].PeerID().String()
234 + expectProviders(t, cidPinned, n0pid, nodes[1:]...)
235 + expectNoProviders(t, cidUnpinned, nodes[1:]...)
236 + expectProviders(t, cidMFS, n0pid, nodes[1:]...)
237 + })
238 +
239 + t.Run("Provide with 'roots' strategy", func(t *testing.T) {
240 + t.Parallel()
241 +
242 + nodes := initNodes(t, 2, func(n *harness.Node) {
243 + n.SetIPFSConfig("Reprovider.Strategy", "roots")
244 + })
245 + defer nodes.StopDaemons()
246 +
247 + // Add a root CID (should be provided)
248 + cidRoot := nodes[0].IPFSAddStr("roots strategy", "-w", "-Q")
249 + // the same without wrapping should give us a child node.
250 + cidChild := nodes[0].IPFSAddStr("root strategy", "--pin=false")
251 +
252 + expectProviders(t, cidRoot, nodes[0].PeerID().String(), nodes[1:]...)
253 + expectNoProviders(t, cidChild, nodes[1:]...)
254 + })
255 +
256 + t.Run("Provide with 'mfs' strategy", func(t *testing.T) {
257 + t.Parallel()
258 +
259 + nodes := initNodes(t, 2, func(n *harness.Node) {
260 + n.SetIPFSConfig("Reprovider.Strategy", "mfs")
261 + })
262 + defer nodes.StopDaemons()
263 +
264 + // Add a file to MFS (should be provided)
265 + data := testutils.RandomBytes(1000)
266 + cid := nodes[0].IPFSAdd(bytes.NewReader(data), "-Q")
267
268 + // not yet in MFS
269 + expectNoProviders(t, cid, nodes[1:]...)
270 +
271 + nodes[0].IPFS("files", "cp", "/ipfs/"+cid, "/myfile")
272 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
273 + })
274 +
275 + t.Run("Reprovides with 'all' strategy when strategy is '' (empty)", func(t *testing.T) {
276 + t.Parallel()
277 +
278 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
279 + n.SetIPFSConfig("Reprovider.Strategy", "")
280 + })
281 +
282 + cid := nodes[0].IPFSAddStr(time.Now().String())
283 +
284 + nodes = nodes.StartDaemons().Connect()
285 + defer nodes.StopDaemons()
286 + expectNoProviders(t, cid, nodes[1:]...)
287 +
288 + nodes[0].IPFS("routing", "reprovide")
289 +
290 + expectProviders(t, cid, nodes[0].PeerID().String(), nodes[1:]...)
291 + })
292 +
293 + t.Run("Reprovides with 'all' strategy", func(t *testing.T) {
294 + t.Parallel()
295 +
296 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
297 + n.SetIPFSConfig("Reprovider.Strategy", "all")
298 + })
299 +
300 + cid := nodes[0].IPFSAddStr(time.Now().String())
301 +
302 + nodes = nodes.StartDaemons().Connect()
303 + defer nodes.StopDaemons()
304 expectNoProviders(t, cid, nodes[1:]...)
305
306 nodes[0].IPFS("routing", "reprovide")
@@ -151,13 +311,14 @@ func TestProvider(t *testing.T) {
311 t.Run("Reprovides with 'flat' strategy", func(t *testing.T) {
312 t.Parallel()
313
154 - nodes := initNodes(t, 2, func(n *harness.Node) {
314 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
315 n.SetIPFSConfig("Reprovider.Strategy", "flat")
316 })
157 - defer nodes.StopDaemons()
317
159 - cid := nodes[0].IPFSAddStr(time.Now().String(), "--local")
318 + cid := nodes[0].IPFSAddStr(time.Now().String())
319
320 + nodes = nodes.StartDaemons().Connect()
321 + defer nodes.StopDaemons()
322 expectNoProviders(t, cid, nodes[1:]...)
323
324 nodes[0].IPFS("routing", "reprovide")
@@ -171,22 +332,31 @@ func TestProvider(t *testing.T) {
332 foo := testutils.RandomBytes(1000)
333 bar := testutils.RandomBytes(1000)
334
174 - nodes := initNodes(t, 2, func(n *harness.Node) {
335 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
336 n.SetIPFSConfig("Reprovider.Strategy", "pinned")
337 })
338 +
339 + // Add a pin while offline so it cannot be provided
340 + cidBarDir := nodes[0].IPFSAdd(bytes.NewReader(bar), "-Q", "-w")
341 +
342 + nodes = nodes.StartDaemons().Connect()
343 defer nodes.StopDaemons()
344
179 - cidFoo := nodes[0].IPFSAdd(bytes.NewReader(foo), "--offline", "--pin=false")
180 - cidBar := nodes[0].IPFSAdd(bytes.NewReader(bar), "--offline", "--pin=false")
181 - cidBarDir := nodes[0].IPFSAdd(bytes.NewReader(bar), "-Q", "--offline", "-w")
345 + // Add content without pinning while daemon line
346 + cidFoo := nodes[0].IPFSAdd(bytes.NewReader(foo), "--pin=false")
347 + cidBar := nodes[0].IPFSAdd(bytes.NewReader(bar), "--pin=false")
348
349 + // Nothing should have been provided. The pin was offline, and
350 + // the others should not be provided per the strategy.
351 expectNoProviders(t, cidFoo, nodes[1:]...)
352 expectNoProviders(t, cidBar, nodes[1:]...)
353 expectNoProviders(t, cidBarDir, nodes[1:]...)
354
355 nodes[0].IPFS("routing", "reprovide")
356
357 + // cidFoo is not pinned so should not be provided.
358 expectNoProviders(t, cidFoo, nodes[1:]...)
359 + // cidBar gets provided by being a child from cidBarDir even though we added with pin=false.
360 expectProviders(t, cidBar, nodes[0].PeerID().String(), nodes[1:]...)
361 expectProviders(t, cidBarDir, nodes[0].PeerID().String(), nodes[1:]...)
362 })
@@ -196,28 +366,87 @@ func TestProvider(t *testing.T) {
366
367 foo := testutils.RandomBytes(1000)
368 bar := testutils.RandomBytes(1000)
199 - baz := testutils.RandomBytes(1000)
369
201 - nodes := initNodes(t, 2, func(n *harness.Node) {
370 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
371 n.SetIPFSConfig("Reprovider.Strategy", "roots")
372 })
373 + n0pid := nodes[0].PeerID().String()
374 +
375 + // Add a pin. Only root should get pinned but not provided
376 + // because node not started
377 + cidBarDir := nodes[0].IPFSAdd(bytes.NewReader(bar), "-Q", "-w")
378 +
379 + nodes = nodes.StartDaemons().Connect()
380 defer nodes.StopDaemons()
381
206 - cidFoo := nodes[0].IPFSAdd(bytes.NewReader(foo), "--offline", "--pin=false")
207 - cidBar := nodes[0].IPFSAdd(bytes.NewReader(bar), "--offline", "--pin=false")
208 - cidBaz := nodes[0].IPFSAdd(bytes.NewReader(baz), "--offline")
209 - cidBarDir := nodes[0].IPFSAdd(bytes.NewReader(bar), "-Q", "--offline", "-w")
382 + cidFoo := nodes[0].IPFSAdd(bytes.NewReader(foo))
383 + cidBar := nodes[0].IPFSAdd(bytes.NewReader(bar), "--pin=false")
384
211 - expectNoProviders(t, cidFoo, nodes[1:]...)
385 + // cidFoo will get provided per the strategy but cidBar will not.
386 + expectProviders(t, cidFoo, n0pid, nodes[1:]...)
387 expectNoProviders(t, cidBar, nodes[1:]...)
213 - expectNoProviders(t, cidBarDir, nodes[1:]...)
388
389 nodes[0].IPFS("routing", "reprovide")
390
217 - expectNoProviders(t, cidFoo, nodes[1:]...)
391 + expectProviders(t, cidFoo, n0pid, nodes[1:]...)
392 expectNoProviders(t, cidBar, nodes[1:]...)
219 - expectProviders(t, cidBaz, nodes[0].PeerID().String(), nodes[1:]...)
220 - expectProviders(t, cidBarDir, nodes[0].PeerID().String(), nodes[1:]...)
393 + expectProviders(t, cidBarDir, n0pid, nodes[1:]...)
394 + })
395 +
396 + t.Run("Reprovides with 'mfs' strategy", func(t *testing.T) {
397 + t.Parallel()
398 +
399 + bar := testutils.RandomBytes(1000)
400 +
401 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
402 + n.SetIPFSConfig("Reprovider.Strategy", "mfs")
403 + })
404 + n0pid := nodes[0].PeerID().String()
405 +
406 + // add something and lets put it in MFS
407 + cidBar := nodes[0].IPFSAdd(bytes.NewReader(bar), "--pin=false", "-Q")
408 + nodes[0].IPFS("files", "cp", "/ipfs/"+cidBar, "/myfile")
409 +
410 + nodes = nodes.StartDaemons().Connect()
411 + defer nodes.StopDaemons()
412 +
413 + // cidBar is in MFS but not provided
414 + expectNoProviders(t, cidBar, nodes[1:]...)
415 +
416 + nodes[0].IPFS("routing", "reprovide")
417 +
418 + // And now is provided
419 + expectProviders(t, cidBar, n0pid, nodes[1:]...)
420 + })
421 +
422 + t.Run("Reprovides with 'pinned+mfs' strategy", func(t *testing.T) {
423 + t.Parallel()
424 +
425 + nodes := initNodesWithoutStart(t, 2, func(n *harness.Node) {
426 + n.SetIPFSConfig("Reprovider.Strategy", "pinned+mfs")
427 + })
428 + n0pid := nodes[0].PeerID().String()
429 +
430 + // Add a pinned CID (should be provided)
431 + cidPinned := nodes[0].IPFSAddStr("pinned content", "--pin=true")
432 + // Add a CID to MFS (should be provided)
433 + cidMFS := nodes[0].IPFSAddStr("mfs content")
434 + nodes[0].IPFS("files", "cp", "/ipfs/"+cidMFS, "/myfile")
435 + // Add a CID that is neither pinned nor in MFS (should not be provided)
436 + cidNeither := nodes[0].IPFSAddStr("neither content", "--pin=false")
437 +
438 + nodes = nodes.StartDaemons().Connect()
439 + defer nodes.StopDaemons()
440 +
441 + // Trigger reprovide
442 + nodes[0].IPFS("routing", "reprovide")
443 +
444 + // Check that pinned CID is provided
445 + expectProviders(t, cidPinned, n0pid, nodes[1:]...)
446 + // Check that MFS CID is provided
447 + expectProviders(t, cidMFS, n0pid, nodes[1:]...)
448 + // Check that neither CID is not provided
449 + expectNoProviders(t, cidNeither, nodes[1:]...)
450 })
451
452 t.Run("provide clear command removes items from provide queue", func(t *testing.T) {
test/dependencies/go.mod
+2 -1
@@ -31,6 +31,7 @@ require (
31 github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e // indirect
32 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
33 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
34 + github.com/Jorropo/jsync v1.0.1 // indirect
35 github.com/Masterminds/semver/v3 v3.2.1 // indirect
36 github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
37 github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
@@ -130,7 +131,7 @@ require (
131 github.com/huin/goupnp v1.3.0 // indirect
132 github.com/inconshreveable/mousetrap v1.1.0 // indirect
133 github.com/ipfs/bbloom v0.0.4 // indirect
133 - github.com/ipfs/boxo v0.33.1 // indirect
134 + github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 // indirect
135 github.com/ipfs/go-bitfield v1.1.0 // indirect
136 github.com/ipfs/go-block-format v0.2.2 // indirect
137 github.com/ipfs/go-cid v0.5.0 // indirect
test/dependencies/go.sum
+4 -2
@@ -33,6 +33,8 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rW
33 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
34 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU=
35 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao=
36 +github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU=
37 +github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ=
38 github.com/Kubuxu/gocovmerge v0.0.0-20161216165753-7ecaa51963cd h1:HNhzThEtZW714v8Eda8sWWRcu9WSzJC+oCyjRjvZgRA=
39 github.com/Kubuxu/gocovmerge v0.0.0-20161216165753-7ecaa51963cd/go.mod h1:bqoB8kInrTeEtYAwaIXoSRqdwnjQmFhsfusnzyui6yY=
40 github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
@@ -319,8 +321,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
321 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
322 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
323 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
322 -github.com/ipfs/boxo v0.33.1 h1:89m+ksw+cYi0ecTNTJ71IRS5ZrLiovmO6XWHIOGhAEg=
323 -github.com/ipfs/boxo v0.33.1/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
324 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
325 +github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
326 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
327 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
328 github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=