From ff07b9ca37cff79ad183cfffa024e6c31c0169ad Mon Sep 17 00:00:00 2001 From: guillaumemichel Date: Wed, 21 Oct 2026 15:58:26 +0200 Subject: [PATCH 1/5] feat(routing): opt-in persistence of the DHT routing table --- config/routing.go | 6 + core/builder.go | 12 +- core/core.go | 27 ++- core/node/libp2p/host.go | 1 + core/node/libp2p/routingopt.go | 8 + docs/changelogs/v0.44.md | 26 +++ docs/config.md | 36 +++- docs/examples/kubo-as-a-library/go.mod | 4 +- docs/examples/kubo-as-a-library/go.sum | 8 +- go.mod | 4 +- go.sum | 8 +- routing/delegated.go | 6 +- test/cli/backup_bootstrap_test.go | 3 + test/cli/routing_table_persistence_test.go | 206 +++++++++++++++++++++ test/dependencies/go.mod | 4 +- test/dependencies/go.sum | 8 +- 16 files changed, 341 insertions(+), 26 deletions(-) create mode 100644 test/cli/routing_table_persistence_test.go diff --git a/config/routing.go b/config/routing.go index c3d77237f5e..38e6637cac1 100644 --- a/config/routing.go +++ b/config/routing.go @@ -12,6 +12,7 @@ import ( const ( DefaultAcceleratedDHTClient = false DefaultLoopbackAddressesOnLanDHT = false + DefaultPersistRoutingTable = true DefaultRoutingType = "auto" CidContactRoutingURL = "https://cid.contact" PublicGoodDelegatedRoutingURL = "https://delegated-ipfs.dev" // cid.contact + amino dht (incl. IPNS PUTs) @@ -44,6 +45,11 @@ type Routing struct { LoopbackAddressesOnLanDHT Flag `json:",omitempty"` + // PersistRoutingTable saves the Amino DHT routing table to the repo and, + // on the next start, reconnects to those peers before contacting the + // bootstrap peers. It has no effect with an empty Bootstrap list. + PersistRoutingTable Flag `json:",omitempty"` + IgnoreProviders []string `json:",omitempty"` // Simplified configuration used by default when Routing.Type=auto|autoclient diff --git a/core/builder.go b/core/builder.go index e226cef47cb..253dbf8559a 100644 --- a/core/builder.go +++ b/core/builder.go @@ -143,7 +143,17 @@ func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) { return n, nil } - return n, n.Bootstrap(bootstrap.DefaultBootstrapConfig) + repoCfg, err := n.Repo.Config() + if err != nil { + return n, err + } + bootstrapCfg := bootstrap.DefaultBootstrapConfig + // A WAN DHT that persists its routing table reconnects to its saved peers + // first, and dials the bootstrap peers itself if they fall short. The + // first round here waits one Period as a backstop, instead of dialing the + // bootstrap peers right away. + bootstrapCfg.DeferFirstRound = n.persistsRoutingTable(repoCfg) + return n, n.Bootstrap(bootstrapCfg) } // Log the entire `app.Err()` but return only the innermost one to the user diff --git a/core/core.go b/core/core.go index db8c4d9a66e..1a44d49f756 100644 --- a/core/core.go +++ b/core/core.go @@ -259,7 +259,17 @@ func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error { return ps } } - if load, _ := cfg.BackupPeers(); load == nil { + repoConf, err := n.Repo.Config() + if err != nil { + return err + } + if n.persistsRoutingTable(repoConf) { + // The DHT routing table snapshot replaces the backup bootstrap peers + // saved while Routing.PersistRoutingTable was off, so drop them. + if err := n.Repo.Datastore().Delete(n.Context(), TempBootstrapPeersKey); err != nil { + log.Warnf("failed to remove the backup bootstrap peers: %s", err) + } + } else if load, _ := cfg.BackupPeers(); load == nil { save := func(ctx context.Context, peerList []peer.AddrInfo) { err := n.saveTempBootstrapPeers(ctx, peerList) if err != nil { @@ -278,10 +288,6 @@ func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error { cfg.SetBackupPeers(load, save) } - repoConf, err := n.Repo.Config() - if err != nil { - return err - } if repoConf.Internal.BackupBootstrapInterval != nil { cfg.BackupBootstrapInterval = repoConf.Internal.BackupBootstrapInterval.WithDefault(time.Hour) } @@ -290,6 +296,17 @@ func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error { return err } +// persistsRoutingTable reports whether the WAN DHT saves its routing table +// and reconnects to the saved peers on its own (Routing.PersistRoutingTable). +// Like the DHT, it treats an empty Bootstrap list as turning that off. +func (n *IpfsNode) persistsRoutingTable(cfg *config.Config) bool { + if n.DHT == nil || !cfg.Routing.PersistRoutingTable.WithDefault(config.DefaultPersistRoutingTable) { + return false + } + peers, err := cfg.BootstrapPeersWithAutoConf() + return err == nil && len(peers) > 0 +} + var TempBootstrapPeersKey = datastore.NewKey("/local/temp_bootstrap_peers") func (n *IpfsNode) loadBootstrapPeers() ([]peer.AddrInfo, error) { diff --git a/core/node/libp2p/host.go b/core/node/libp2p/host.go index 69543cf0fdd..db4b441d9d3 100644 --- a/core/node/libp2p/host.go +++ b/core/node/libp2p/host.go @@ -74,6 +74,7 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (out P2PHo OptimisticProvide: optimisticProvide, OptimisticProvideJobsPoolSize: cfg.Experimental.OptimisticProvideJobsPoolSize, LoopbackAddressesOnLanDHT: cfg.Routing.LoopbackAddressesOnLanDHT.WithDefault(config.DefaultLoopbackAddressesOnLanDHT), + PersistRoutingTable: cfg.Routing.PersistRoutingTable.WithDefault(config.DefaultPersistRoutingTable), } opts = append(opts, libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { args := routingOptArgs diff --git a/core/node/libp2p/routingopt.go b/core/node/libp2p/routingopt.go index ccb6874bd94..708efde8dba 100644 --- a/core/node/libp2p/routingopt.go +++ b/core/node/libp2p/routingopt.go @@ -30,6 +30,7 @@ type RoutingOptionArgs struct { OptimisticProvide bool OptimisticProvideJobsPoolSize int LoopbackAddressesOnLanDHT bool + PersistRoutingTable bool } type RoutingOption func(args RoutingOptionArgs) (routing.Routing, error) @@ -259,6 +260,13 @@ func constructDHTRouting(mode dht.ModeOpt) RoutingOption { wanOptions := []dht.Option{ dht.BootstrapPeers(args.BootstrapPeers...), } + // An empty Bootstrap list turns off all automatic dialing, saved + // peers included. + if args.PersistRoutingTable && len(args.BootstrapPeers) > 0 { + wanOptions = append(wanOptions, dht.BootstrapOrder(dht.PersistedFirst)) + } else { + wanOptions = append(wanOptions, dht.RoutingTablePersistence(0)) + } // In stub mode, allow loopback peers in the WAN routing // table so Provide/PutValue work with ephemeral test peers. if os.Getenv("TEST_DHT_STUB") != "" { diff --git a/docs/changelogs/v0.44.md b/docs/changelogs/v0.44.md index 3704b5caeae..626ec799743 100644 --- a/docs/changelogs/v0.44.md +++ b/docs/changelogs/v0.44.md @@ -6,6 +6,7 @@ - [Overview](#overview) - [๐Ÿ”ฆ Highlights](#-highlights) + - [๐Ÿงญ Opt-in: restarts rejoin the DHT through known peers](#-opt-in-restarts-rejoin-the-dht-through-known-peers) - [๐Ÿ“ Changelog](#-changelog) - [๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors](#-contributors) @@ -13,6 +14,31 @@ ### ๐Ÿ”ฆ Highlights +#### ๐Ÿงญ Opt-in: restarts rejoin the DHT through known peers + +With the new +[`Routing.PersistRoutingTable`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingpersistroutingtable) +option, Kubo saves its Amino DHT routing table to the repo after 10 minutes, +then every hour, and on shutdown. On the next start it reconnects to those +peers first, and contacts the +[`Bootstrap`](https://github.com/ipfs/kubo/blob/master/docs/config.md#bootstrap) +peers only if fewer than 10 of the saved peers answer within 20 seconds. +Restarts then depend less on the same small set of bootstrap peers, and a node +that knows the network can rejoin it even when those peers are unreachable. A +new node, which has no saved peers yet, still starts from the bootstrap peers. + +Saved peers are checked like any other peer before they enter the routing +table, and a node with an empty `Bootstrap` list neither saves its routing +table nor dials saved peers. With the option on, startup no longer waits for a +first bootstrap round, and the snapshot replaces the backup bootstrap peers: +the list at `/local/temp_bootstrap_peers` is deleted on startup. With +[`Routing.AcceleratedDHTClient`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingaccelerateddhtclient), +the network crawls still start from the bootstrap peers. The snapshot is stored +in the repo datastore under `/kad-dht/routing-table/`. + +To try it, run `ipfs config --json Routing.PersistRoutingTable true` and +restart the daemon. + #### ๐Ÿ“ฆ๏ธ Dependency updates - update `go-libp2p` to [v0.50.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.50.0) diff --git a/docs/config.md b/docs/config.md index 05505a53c74..27f20353ba8 100644 --- a/docs/config.md +++ b/docs/config.md @@ -173,6 +173,7 @@ config file at runtime. - [`Routing.DelegatedRouters`](#routingdelegatedrouters) - [`Routing.AcceleratedDHTClient`](#routingaccelerateddhtclient) - [`Routing.LoopbackAddressesOnLanDHT`](#routingloopbackaddressesonlandht) + - [`Routing.PersistRoutingTable`](#routingpersistroutingtable) - [`Routing.IgnoreProviders`](#routingignoreproviders) - [`Routing.Routers`](#routingrouters) - [`Routing.Routers.[name].Type`](#routingroutersnametype) @@ -923,7 +924,7 @@ The special value `"auto"` automatically uses curated, up-to-date bootstrap peer - **Automatic updates**: New bootstrap peers are added as the network evolves - **Custom control**: Add your own trusted peers alongside or instead of the defaults -An empty list turns off all bootstrap dialing, including backup peers saved from earlier runs. The node then connects only to `Peering.Peers`, peers found through mDNS, and peers you connect manually. Backup peers are dialed only after the configured peers, when those leave the node below the minimum peer count. +An empty list turns off all bootstrap dialing, including backup peers saved from earlier runs and the peers saved by [`Routing.PersistRoutingTable`](#routingpersistroutingtable). The node then connects only to `Peering.Peers`, peers found through mDNS, and peers you connect manually. Backup peers are dialed only after the configured peers, when those leave the node below the minimum peer count. Default: `["auto"]` @@ -3151,6 +3152,39 @@ Default: `false` Type: `bool` (missing means `false`) +### `Routing.PersistRoutingTable` + +Whether the node saves its Amino DHT routing table, and reconnects to those +peers when it starts instead of going through the [`Bootstrap`](#bootstrap) +peers first. + +When enabled, the peers in the routing table and their addresses are saved to +the repo after 10 minutes, then every hour, and on shutdown. On the next start, +the node dials them first. The bootstrap peers are contacted only if fewer than +10 saved peers answer within 20 seconds, for example after a long time offline. +Restarts then depend less on a small set of bootstrap peers, and keep working +when those are unreachable. + +Saved peers are checked like any other peer before they enter the routing +table. A node with an empty [`Bootstrap`](#bootstrap) list neither saves its +routing table nor dials saved peers. The setting applies to the Amino DHT used +when [`Routing.Type`](#routingtype) is `auto`, `autoclient`, `dht`, `dhtclient` +or `dhtserver`. With +[`Routing.AcceleratedDHTClient`](#routingaccelerateddhtclient), the network +crawls still start from the bootstrap peers. + +When enabled for one of these routing types, this replaces the backup bootstrap +peers that the node otherwise saves from its connected peers every hour: that +list is deleted on startup, and `Internal.BackupBootstrapInterval` has no +effect. + +When disabled, the routing table isn't saved, and every start goes through the +bootstrap peers, with the backup bootstrap peers as a fallback. + +Default: `false` + +Type: `flag` + ### `Routing.IgnoreProviders` An array of [string-encoded PeerIDs](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#string-representation). Any provider record associated to one of these peer IDs is ignored. diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 2152005ed15..124fbb967ab 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -7,7 +7,7 @@ go 1.26.5 replace github.com/ipfs/kubo => ./../../.. require ( - github.com/ipfs/boxo v0.43.0 + github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.50.0 github.com/multiformats/go-multiaddr v0.16.1 @@ -116,7 +116,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.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.42.2 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-pubsub v0.17.0 // indirect github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 543951cb701..52bee62e39d 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -266,8 +266,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.43.0 h1:nhIbfBBxgLoAxUi8upOIO5RATy++2WigMVWniRJFjnw= -github.com/ipfs/boxo v0.43.0/go.mod h1:n9HNLPLfAGexg2DGoQ5B4BMVlmj9g4+5dIcuo7w95iI= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 h1:LiSieCK2yysUDcrGbw9eCrhHDDaoznC7rv+Dja3wsLM= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56/go.mod h1:88j5vdrVF+ZacX8+73uS2489c6qi2yezKPpU8KnIIdU= 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.4 h1:pgsT9i8zB4YQkBIQRrBwqbQiXPogRCiQnfxd2bC4koI= @@ -404,8 +404,8 @@ 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.42.2 h1:5RtESYeBbjaF+KIPmU47xz2DOb2iLqvoRCZcsjxCS48= -github.com/libp2p/go-libp2p-kad-dht v0.42.2/go.mod h1:oLGZ7xq317zXmoeJe0dwgUdOeWeJ85MDJ/+fbKqFoK0= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/go.mod b/go.mod index 60a8e409216..d2581900fcf 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( 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.43.0 + github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 github.com/ipfs/go-block-format v0.2.4 github.com/ipfs/go-cid v0.6.2 github.com/ipfs/go-cidutil v0.1.2 @@ -53,7 +53,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.0 github.com/libp2p/go-libp2p v0.50.0 github.com/libp2p/go-libp2p-http v0.5.0 - github.com/libp2p/go-libp2p-kad-dht v0.42.2 + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 github.com/libp2p/go-libp2p-kbucket v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.17.0 github.com/libp2p/go-libp2p-pubsub-router v0.6.0 diff --git a/go.sum b/go.sum index d240f4c78cc..07237c9796a 100644 --- a/go.sum +++ b/go.sum @@ -338,8 +338,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.43.0 h1:nhIbfBBxgLoAxUi8upOIO5RATy++2WigMVWniRJFjnw= -github.com/ipfs/boxo v0.43.0/go.mod h1:n9HNLPLfAGexg2DGoQ5B4BMVlmj9g4+5dIcuo7w95iI= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 h1:LiSieCK2yysUDcrGbw9eCrhHDDaoznC7rv+Dja3wsLM= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56/go.mod h1:88j5vdrVF+ZacX8+73uS2489c6qi2yezKPpU8KnIIdU= 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.4 h1:pgsT9i8zB4YQkBIQRrBwqbQiXPogRCiQnfxd2bC4koI= @@ -492,8 +492,8 @@ 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.42.2 h1:5RtESYeBbjaF+KIPmU47xz2DOb2iLqvoRCZcsjxCS48= -github.com/libp2p/go-libp2p-kad-dht v0.42.2/go.mod h1:oLGZ7xq317zXmoeJe0dwgUdOeWeJ85MDJ/+fbKqFoK0= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/routing/delegated.go b/routing/delegated.go index cfa1cfefbbb..784b591c976 100644 --- a/routing/delegated.go +++ b/routing/delegated.go @@ -353,7 +353,11 @@ func createDHT(params *ExtraDHTParams, public bool, mode dht.ModeOpt) (routing.R dht.Datastore(params.Datastore), dht.ValueDatastore(DHTValueDatastore(params.Datastore)), dht.Validator(params.Validator), - dht.BootstrapPeers(params.BootstrapPeers...)) + dht.BootstrapPeers(params.BootstrapPeers...), + // Custom routers are never closed, so they would only save on a + // timer, and their address filtering differs from the default DHT. + // Keep them out of routing table persistence for now. + dht.RoutingTablePersistence(0)) return dht.New(params.Host, opts...) } diff --git a/test/cli/backup_bootstrap_test.go b/test/cli/backup_bootstrap_test.go index d6a54ddeb02..79955d7bce6 100644 --- a/test/cli/backup_bootstrap_test.go +++ b/test/cli/backup_bootstrap_test.go @@ -23,6 +23,9 @@ func TestBackupBootstrapPeers(t *testing.T) { cfg.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", harness.NewRandPort())} cfg.Discovery.MDNS.Enabled = false cfg.Internal.BackupBootstrapInterval = config.NewOptionalDuration(250 * time.Millisecond) + // Backup bootstrap peers are only saved while the routing table + // isn't. + cfg.Routing.PersistRoutingTable = config.False }) }) diff --git a/test/cli/routing_table_persistence_test.go b/test/cli/routing_table_persistence_test.go new file mode 100644 index 00000000000..4e458f2809b --- /dev/null +++ b/test/cli/routing_table_persistence_test.go @@ -0,0 +1,206 @@ +package cli + +import ( + "encoding/json" + "errors" + "testing" + "time" + + "github.com/ipfs/kubo/test/cli/harness" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +// routingTableSnapshotKey is where go-libp2p-kad-dht saves the Amino DHT +// routing table in the repo datastore. +const routingTableSnapshotKey = "/kad-dht/routing-table/ipfs/kad/1.0.0" + +// backupBootstrapPeersKey is where Kubo saves backup bootstrap peers while +// Routing.PersistRoutingTable is off. +const backupBootstrapPeersKey = "/local/temp_bootstrap_peers" + +// unreachableBootstrapPeer is a bootstrap peer nobody listens for, so every +// dial to it fails. +const unreachableBootstrapPeer = "/ip4/127.0.0.1/tcp/1/p2p/12D3KooWAr43wwcGtURU6Ck6R7cbvfii2unzaK2moqNYQmBQwWgN" + +// routingTableSnapshot mirrors the snapshot layout of go-libp2p-kad-dht, +// which pins it with a golden test. +type routingTableSnapshot struct { + Version int `json:"version"` + SavedAt time.Time `json:"savedAt"` + Peers []routingTableSnapshotPeer `json:"peers"` +} + +type routingTableSnapshotPeer struct { + ID string `json:"id"` + Addrs []string `json:"addrs"` +} + +func TestRoutingTablePersistence(t *testing.T) { + t.Parallel() + + t.Run("routing table is saved on shutdown", func(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + node.SetIPFSConfig("Routing.PersistRoutingTable", true) + h.BootstrapWithStubDHT(harness.Nodes{node}) + node.StartDaemon() + + require.Eventually(t, func() bool { return wanRoutingTableSize(node) >= 10 }, time.Minute, 200*time.Millisecond) + shutdown(t, node) + + var snapshot routingTableSnapshot + res := node.IPFS("diag", "datastore", "get", routingTableSnapshotKey) + require.NoError(t, json.Unmarshal(res.Stdout.Bytes(), &snapshot)) + require.Equal(t, 1, snapshot.Version) + require.GreaterOrEqual(t, len(snapshot.Peers), 10) + }) + + t.Run("saved peers stand in for unreachable bootstrap peers", func(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + saved := saveStubPeersAsSnapshot(t, h, node) + + node.StartDaemon() + defer node.StopDaemon() + require.Eventually(t, func() bool { return connectedToAny(node, saved) }, time.Minute, 200*time.Millisecond) + }) + + t.Run("saved peers are not dialed when disabled", func(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + saved := saveStubPeersAsSnapshot(t, h, node) + node.SetIPFSConfig("Routing.PersistRoutingTable", false) + + node.StartDaemon() + defer node.StopDaemon() + requireNeverConnected(t, node, saved) + }) + + t.Run("saved peers are not dialed with an empty Bootstrap list", func(t *testing.T) { + t.Parallel() + h := harness.NewT(t) + node := h.NewNode().Init() + saved := saveStubPeersAsSnapshot(t, h, node) + node.SetIPFSConfig("Bootstrap", []string{}) + + node.StartDaemon() + defer node.StopDaemon() + requireNeverConnected(t, node, saved) + }) + + t.Run("backup bootstrap peers are dropped", func(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init() + node.SetIPFSConfig("Routing.PersistRoutingTable", true) + node.SetIPFSConfig("Bootstrap", []string{unreachableBootstrapPeer}) + node.IPFS("diag", "datastore", "put", backupBootstrapPeersKey, `["`+unreachableBootstrapPeer+`"]`) + + node.StartDaemon() + node.StopDaemon() + res := node.RunIPFS("diag", "datastore", "get", backupBootstrapPeersKey) + require.Contains(t, res.Stderr.String(), "key not found", "backup bootstrap peers were kept") + }) +} + +// requireNeverConnected checks for ten seconds that node connects to none of +// ids. It is a plain loop rather than require.Never, which returns at its +// deadline without waiting for the last check: that check's ipfs command +// would then race the harness removing the repo. +func requireNeverConnected(t *testing.T, node *harness.Node, ids map[peer.ID]bool) { + t.Helper() + for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(500 * time.Millisecond) { + require.False(t, connectedToAny(node, ids), "a saved peer was dialed") + } +} + +// saveStubPeersAsSnapshot writes the harness's stub DHT peers to node's repo +// as a routing table snapshot, turns Routing.PersistRoutingTable on, and +// leaves node with a single, unreachable bootstrap peer. It returns the IDs of +// the saved peers. +func saveStubPeersAsSnapshot(t *testing.T, h *harness.Harness, node *harness.Node) map[peer.ID]bool { + t.Helper() + h.BootstrapWithStubDHT(harness.Nodes{node}) + + snapshot := routingTableSnapshot{Version: 1, SavedAt: time.Now().UTC()} + index := map[peer.ID]int{} + for _, s := range node.ReadConfig().Bootstrap { + ai, err := peer.AddrInfoFromString(s) + require.NoError(t, err) + i, ok := index[ai.ID] + if !ok { + i = len(snapshot.Peers) + index[ai.ID] = i + snapshot.Peers = append(snapshot.Peers, routingTableSnapshotPeer{ID: ai.ID.String()}) + } + for _, a := range ai.Addrs { + snapshot.Peers[i].Addrs = append(snapshot.Peers[i].Addrs, a.String()) + } + } + b, err := json.Marshal(snapshot) + require.NoError(t, err) + node.IPFS("diag", "datastore", "put", routingTableSnapshotKey, string(b)) + node.SetIPFSConfig("Bootstrap", []string{unreachableBootstrapPeer}) + node.SetIPFSConfig("Routing.PersistRoutingTable", true) + + saved := make(map[peer.ID]bool, len(index)) + for id := range index { + saved[id] = true + } + return saved +} + +func connectedToAny(node *harness.Node, ids map[peer.ID]bool) bool { + for _, a := range node.Peers() { + ai, err := peer.AddrInfoFromP2pAddr(a) + if err == nil && ids[ai.ID] { + return true + } + } + return false +} + +func wanRoutingTableSize(node *harness.Node) int { + res := node.RunIPFS("stats", "dht", "wan", "--enc=json") + if res.Err != nil { + return 0 + } + var stat struct { + Buckets []struct { + Peers []json.RawMessage + } + } + if json.Unmarshal(res.Stdout.Bytes(), &stat) != nil { + return 0 + } + size := 0 + for _, b := range stat.Buckets { + size += len(b.Peers) + } + return size +} + +// shutdown stops node's daemon with `ipfs shutdown`, which closes the DHT +// cleanly. StopDaemon escalates to signals that can cut that short. +func shutdown(t *testing.T, node *harness.Node) { + t.Helper() + exited := make(chan error, 1) + go func() { + state, err := node.Daemon.Cmd.Process.Wait() + if err == nil && !state.Success() { + err = errors.New(state.String()) + } + exited <- err + }() + node.IPFS("shutdown") + select { + case err := <-exited: + require.NoError(t, err) + case <-time.After(time.Minute): + _ = node.Daemon.Cmd.Process.Kill() + t.Fatal("daemon did not exit after ipfs shutdown") + } +} diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 54d38cf4aaa..5a5c08f774f 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -135,7 +135,7 @@ 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.43.0 // indirect + github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 // indirect github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-block-format v0.2.4 // indirect github.com/ipfs/go-cid v0.6.2 // indirect @@ -183,7 +183,7 @@ require ( github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p v0.50.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.42.2 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index 7104c7815b6..79d6d907cce 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -299,8 +299,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.43.0 h1:nhIbfBBxgLoAxUi8upOIO5RATy++2WigMVWniRJFjnw= -github.com/ipfs/boxo v0.43.0/go.mod h1:n9HNLPLfAGexg2DGoQ5B4BMVlmj9g4+5dIcuo7w95iI= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56 h1:LiSieCK2yysUDcrGbw9eCrhHDDaoznC7rv+Dja3wsLM= +github.com/ipfs/boxo v0.43.1-0.20261021124930-a3f9255caf56/go.mod h1:88j5vdrVF+ZacX8+73uS2489c6qi2yezKPpU8KnIIdU= 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.4 h1:pgsT9i8zB4YQkBIQRrBwqbQiXPogRCiQnfxd2bC4koI= @@ -428,8 +428,8 @@ github.com/libp2p/go-libp2p v0.50.0 h1:A0tBP6mr6GV7l5ip2Q04a/IxPqVYYaCiif5s8JuXk github.com/libp2p/go-libp2p v0.50.0/go.mod h1:RjqB+dxCWNZ33yw0yeK5Pu157oNxKuaTZY6vLed4t8w= 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.42.2 h1:5RtESYeBbjaF+KIPmU47xz2DOb2iLqvoRCZcsjxCS48= -github.com/libp2p/go-libp2p-kad-dht v0.42.2/go.mod h1:oLGZ7xq317zXmoeJe0dwgUdOeWeJ85MDJ/+fbKqFoK0= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= From 588d53cd5cd5077d7fd34341e646957fb7a27b8b Mon Sep 17 00:00:00 2001 From: guillaumemichel Date: Wed, 21 Oct 2026 16:13:58 +0200 Subject: [PATCH 2/5] refactor(routing): rename PersistRoutingTable to PersistDHTRoutingTable --- config/routing.go | 6 +++--- core/core.go | 6 +++--- core/node/libp2p/host.go | 2 +- core/node/libp2p/routingopt.go | 4 ++-- docs/changelogs/v0.44.md | 4 ++-- docs/config.md | 6 +++--- test/cli/backup_bootstrap_test.go | 2 +- test/cli/routing_table_persistence_test.go | 12 ++++++------ 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/config/routing.go b/config/routing.go index 38e6637cac1..ec956dde0d9 100644 --- a/config/routing.go +++ b/config/routing.go @@ -12,7 +12,7 @@ import ( const ( DefaultAcceleratedDHTClient = false DefaultLoopbackAddressesOnLanDHT = false - DefaultPersistRoutingTable = true + DefaultPersistDHTRoutingTable = true DefaultRoutingType = "auto" CidContactRoutingURL = "https://cid.contact" PublicGoodDelegatedRoutingURL = "https://delegated-ipfs.dev" // cid.contact + amino dht (incl. IPNS PUTs) @@ -45,10 +45,10 @@ type Routing struct { LoopbackAddressesOnLanDHT Flag `json:",omitempty"` - // PersistRoutingTable saves the Amino DHT routing table to the repo and, + // PersistDHTRoutingTable saves the Amino DHT routing table to the repo and, // on the next start, reconnects to those peers before contacting the // bootstrap peers. It has no effect with an empty Bootstrap list. - PersistRoutingTable Flag `json:",omitempty"` + PersistDHTRoutingTable Flag `json:",omitempty"` IgnoreProviders []string `json:",omitempty"` diff --git a/core/core.go b/core/core.go index 1a44d49f756..f86dc6cc54d 100644 --- a/core/core.go +++ b/core/core.go @@ -265,7 +265,7 @@ func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error { } if n.persistsRoutingTable(repoConf) { // The DHT routing table snapshot replaces the backup bootstrap peers - // saved while Routing.PersistRoutingTable was off, so drop them. + // saved while Routing.PersistDHTRoutingTable was off, so drop them. if err := n.Repo.Datastore().Delete(n.Context(), TempBootstrapPeersKey); err != nil { log.Warnf("failed to remove the backup bootstrap peers: %s", err) } @@ -297,10 +297,10 @@ func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error { } // persistsRoutingTable reports whether the WAN DHT saves its routing table -// and reconnects to the saved peers on its own (Routing.PersistRoutingTable). +// and reconnects to the saved peers on its own (Routing.PersistDHTRoutingTable). // Like the DHT, it treats an empty Bootstrap list as turning that off. func (n *IpfsNode) persistsRoutingTable(cfg *config.Config) bool { - if n.DHT == nil || !cfg.Routing.PersistRoutingTable.WithDefault(config.DefaultPersistRoutingTable) { + if n.DHT == nil || !cfg.Routing.PersistDHTRoutingTable.WithDefault(config.DefaultPersistDHTRoutingTable) { return false } peers, err := cfg.BootstrapPeersWithAutoConf() diff --git a/core/node/libp2p/host.go b/core/node/libp2p/host.go index db4b441d9d3..f5d41e337df 100644 --- a/core/node/libp2p/host.go +++ b/core/node/libp2p/host.go @@ -74,7 +74,7 @@ func Host(mctx helpers.MetricsCtx, lc fx.Lifecycle, params P2PHostIn) (out P2PHo OptimisticProvide: optimisticProvide, OptimisticProvideJobsPoolSize: cfg.Experimental.OptimisticProvideJobsPoolSize, LoopbackAddressesOnLanDHT: cfg.Routing.LoopbackAddressesOnLanDHT.WithDefault(config.DefaultLoopbackAddressesOnLanDHT), - PersistRoutingTable: cfg.Routing.PersistRoutingTable.WithDefault(config.DefaultPersistRoutingTable), + PersistDHTRoutingTable: cfg.Routing.PersistDHTRoutingTable.WithDefault(config.DefaultPersistDHTRoutingTable), } opts = append(opts, libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { args := routingOptArgs diff --git a/core/node/libp2p/routingopt.go b/core/node/libp2p/routingopt.go index 708efde8dba..c61ed3ae118 100644 --- a/core/node/libp2p/routingopt.go +++ b/core/node/libp2p/routingopt.go @@ -30,7 +30,7 @@ type RoutingOptionArgs struct { OptimisticProvide bool OptimisticProvideJobsPoolSize int LoopbackAddressesOnLanDHT bool - PersistRoutingTable bool + PersistDHTRoutingTable bool } type RoutingOption func(args RoutingOptionArgs) (routing.Routing, error) @@ -262,7 +262,7 @@ func constructDHTRouting(mode dht.ModeOpt) RoutingOption { } // An empty Bootstrap list turns off all automatic dialing, saved // peers included. - if args.PersistRoutingTable && len(args.BootstrapPeers) > 0 { + if args.PersistDHTRoutingTable && len(args.BootstrapPeers) > 0 { wanOptions = append(wanOptions, dht.BootstrapOrder(dht.PersistedFirst)) } else { wanOptions = append(wanOptions, dht.RoutingTablePersistence(0)) diff --git a/docs/changelogs/v0.44.md b/docs/changelogs/v0.44.md index 626ec799743..5874d1a4716 100644 --- a/docs/changelogs/v0.44.md +++ b/docs/changelogs/v0.44.md @@ -17,7 +17,7 @@ #### ๐Ÿงญ Opt-in: restarts rejoin the DHT through known peers With the new -[`Routing.PersistRoutingTable`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingpersistroutingtable) +[`Routing.PersistDHTRoutingTable`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingpersistdhtroutingtable) option, Kubo saves its Amino DHT routing table to the repo after 10 minutes, then every hour, and on shutdown. On the next start it reconnects to those peers first, and contacts the @@ -36,7 +36,7 @@ the list at `/local/temp_bootstrap_peers` is deleted on startup. With the network crawls still start from the bootstrap peers. The snapshot is stored in the repo datastore under `/kad-dht/routing-table/`. -To try it, run `ipfs config --json Routing.PersistRoutingTable true` and +To try it, run `ipfs config --json Routing.PersistDHTRoutingTable true` and restart the daemon. #### ๐Ÿ“ฆ๏ธ Dependency updates diff --git a/docs/config.md b/docs/config.md index 27f20353ba8..ab31bdeb767 100644 --- a/docs/config.md +++ b/docs/config.md @@ -173,7 +173,7 @@ config file at runtime. - [`Routing.DelegatedRouters`](#routingdelegatedrouters) - [`Routing.AcceleratedDHTClient`](#routingaccelerateddhtclient) - [`Routing.LoopbackAddressesOnLanDHT`](#routingloopbackaddressesonlandht) - - [`Routing.PersistRoutingTable`](#routingpersistroutingtable) + - [`Routing.PersistDHTRoutingTable`](#routingpersistdhtroutingtable) - [`Routing.IgnoreProviders`](#routingignoreproviders) - [`Routing.Routers`](#routingrouters) - [`Routing.Routers.[name].Type`](#routingroutersnametype) @@ -924,7 +924,7 @@ The special value `"auto"` automatically uses curated, up-to-date bootstrap peer - **Automatic updates**: New bootstrap peers are added as the network evolves - **Custom control**: Add your own trusted peers alongside or instead of the defaults -An empty list turns off all bootstrap dialing, including backup peers saved from earlier runs and the peers saved by [`Routing.PersistRoutingTable`](#routingpersistroutingtable). The node then connects only to `Peering.Peers`, peers found through mDNS, and peers you connect manually. Backup peers are dialed only after the configured peers, when those leave the node below the minimum peer count. +An empty list turns off all bootstrap dialing, including backup peers saved from earlier runs and the peers saved by [`Routing.PersistDHTRoutingTable`](#routingpersistdhtroutingtable). The node then connects only to `Peering.Peers`, peers found through mDNS, and peers you connect manually. Backup peers are dialed only after the configured peers, when those leave the node below the minimum peer count. Default: `["auto"]` @@ -3152,7 +3152,7 @@ Default: `false` Type: `bool` (missing means `false`) -### `Routing.PersistRoutingTable` +### `Routing.PersistDHTRoutingTable` Whether the node saves its Amino DHT routing table, and reconnects to those peers when it starts instead of going through the [`Bootstrap`](#bootstrap) diff --git a/test/cli/backup_bootstrap_test.go b/test/cli/backup_bootstrap_test.go index 79955d7bce6..d96b8bf27b5 100644 --- a/test/cli/backup_bootstrap_test.go +++ b/test/cli/backup_bootstrap_test.go @@ -25,7 +25,7 @@ func TestBackupBootstrapPeers(t *testing.T) { cfg.Internal.BackupBootstrapInterval = config.NewOptionalDuration(250 * time.Millisecond) // Backup bootstrap peers are only saved while the routing table // isn't. - cfg.Routing.PersistRoutingTable = config.False + cfg.Routing.PersistDHTRoutingTable = config.False }) }) diff --git a/test/cli/routing_table_persistence_test.go b/test/cli/routing_table_persistence_test.go index 4e458f2809b..59ca8a1164e 100644 --- a/test/cli/routing_table_persistence_test.go +++ b/test/cli/routing_table_persistence_test.go @@ -16,7 +16,7 @@ import ( const routingTableSnapshotKey = "/kad-dht/routing-table/ipfs/kad/1.0.0" // backupBootstrapPeersKey is where Kubo saves backup bootstrap peers while -// Routing.PersistRoutingTable is off. +// Routing.PersistDHTRoutingTable is off. const backupBootstrapPeersKey = "/local/temp_bootstrap_peers" // unreachableBootstrapPeer is a bootstrap peer nobody listens for, so every @@ -43,7 +43,7 @@ func TestRoutingTablePersistence(t *testing.T) { t.Parallel() h := harness.NewT(t) node := h.NewNode().Init() - node.SetIPFSConfig("Routing.PersistRoutingTable", true) + node.SetIPFSConfig("Routing.PersistDHTRoutingTable", true) h.BootstrapWithStubDHT(harness.Nodes{node}) node.StartDaemon() @@ -73,7 +73,7 @@ func TestRoutingTablePersistence(t *testing.T) { h := harness.NewT(t) node := h.NewNode().Init() saved := saveStubPeersAsSnapshot(t, h, node) - node.SetIPFSConfig("Routing.PersistRoutingTable", false) + node.SetIPFSConfig("Routing.PersistDHTRoutingTable", false) node.StartDaemon() defer node.StopDaemon() @@ -95,7 +95,7 @@ func TestRoutingTablePersistence(t *testing.T) { t.Run("backup bootstrap peers are dropped", func(t *testing.T) { t.Parallel() node := harness.NewT(t).NewNode().Init() - node.SetIPFSConfig("Routing.PersistRoutingTable", true) + node.SetIPFSConfig("Routing.PersistDHTRoutingTable", true) node.SetIPFSConfig("Bootstrap", []string{unreachableBootstrapPeer}) node.IPFS("diag", "datastore", "put", backupBootstrapPeersKey, `["`+unreachableBootstrapPeer+`"]`) @@ -118,7 +118,7 @@ func requireNeverConnected(t *testing.T, node *harness.Node, ids map[peer.ID]boo } // saveStubPeersAsSnapshot writes the harness's stub DHT peers to node's repo -// as a routing table snapshot, turns Routing.PersistRoutingTable on, and +// as a routing table snapshot, turns Routing.PersistDHTRoutingTable on, and // leaves node with a single, unreachable bootstrap peer. It returns the IDs of // the saved peers. func saveStubPeersAsSnapshot(t *testing.T, h *harness.Harness, node *harness.Node) map[peer.ID]bool { @@ -144,7 +144,7 @@ func saveStubPeersAsSnapshot(t *testing.T, h *harness.Harness, node *harness.Nod require.NoError(t, err) node.IPFS("diag", "datastore", "put", routingTableSnapshotKey, string(b)) node.SetIPFSConfig("Bootstrap", []string{unreachableBootstrapPeer}) - node.SetIPFSConfig("Routing.PersistRoutingTable", true) + node.SetIPFSConfig("Routing.PersistDHTRoutingTable", true) saved := make(map[peer.ID]bool, len(index)) for id := range index { From 4dff8afc1979a9154ac783553fbb48cc13a42fd0 Mon Sep 17 00:00:00 2001 From: guillaumemichel Date: Wed, 28 Oct 2026 10:41:04 +0100 Subject: [PATCH 3/5] bump kad-dht --- docs/examples/kubo-as-a-library/go.mod | 2 +- docs/examples/kubo-as-a-library/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- test/dependencies/go.mod | 2 +- test/dependencies/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 124fbb967ab..97683c87cce 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -116,7 +116,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.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.42.3-0.20261021123015-fbdee6e95bb4 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-pubsub v0.17.0 // indirect github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 52bee62e39d..d363b1c8985 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -404,8 +404,8 @@ 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.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/go.mod b/go.mod index d2581900fcf..3d7ce9ada1a 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.0 github.com/libp2p/go-libp2p v0.50.0 github.com/libp2p/go-libp2p-http v0.5.0 - github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 github.com/libp2p/go-libp2p-kbucket v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.17.0 github.com/libp2p/go-libp2p-pubsub-router v0.6.0 diff --git a/go.sum b/go.sum index 07237c9796a..80f95c4cce0 100644 --- a/go.sum +++ b/go.sum @@ -492,8 +492,8 @@ 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.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 5a5c08f774f..53aab4bf39d 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -183,7 +183,7 @@ require ( github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p v0.50.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index 79d6d907cce..6f4bdda88db 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -428,8 +428,8 @@ github.com/libp2p/go-libp2p v0.50.0 h1:A0tBP6mr6GV7l5ip2Q04a/IxPqVYYaCiif5s8JuXk github.com/libp2p/go-libp2p v0.50.0/go.mod h1:RjqB+dxCWNZ33yw0yeK5Pu157oNxKuaTZY6vLed4t8w= 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.42.3-0.20261021123015-fbdee6e95bb4 h1:6KwZCbIqq4y25FSkHpBujx9rgcfv0pImuo0bRB7uGls= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261021123015-fbdee6e95bb4/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= +github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= From 1c352e46105deb76075b6afa22f4d18eef3caa51 Mon Sep 17 00:00:00 2001 From: guillaumemichel Date: Wed, 28 Oct 2026 13:16:30 +0100 Subject: [PATCH 4/5] chore: kad-dht v0.43.0 --- docs/examples/kubo-as-a-library/go.mod | 2 +- docs/examples/kubo-as-a-library/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- test/dependencies/go.mod | 2 +- test/dependencies/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 97683c87cce..cd58afffb8d 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -116,7 +116,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.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.42.3-0.20261028093345-0e1414ed4fa1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.43.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-pubsub v0.17.0 // indirect github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index d363b1c8985..98ea3395df1 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -404,8 +404,8 @@ 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.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.43.0 h1:PTa4Qea87dAd+ecAIPIrRIHByFsErjKFkFuYO0rgENU= +github.com/libp2p/go-libp2p-kad-dht v0.43.0/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/go.mod b/go.mod index 3d7ce9ada1a..d9056a2e906 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/libp2p/go-doh-resolver v0.6.0 github.com/libp2p/go-libp2p v0.50.0 github.com/libp2p/go-libp2p-http v0.5.0 - github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 + github.com/libp2p/go-libp2p-kad-dht v0.43.0 github.com/libp2p/go-libp2p-kbucket v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.17.0 github.com/libp2p/go-libp2p-pubsub-router v0.6.0 diff --git a/go.sum b/go.sum index 80f95c4cce0..31383cc6956 100644 --- a/go.sum +++ b/go.sum @@ -492,8 +492,8 @@ 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.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.43.0 h1:PTa4Qea87dAd+ecAIPIrRIHByFsErjKFkFuYO0rgENU= +github.com/libp2p/go-libp2p-kad-dht v0.43.0/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 53aab4bf39d..aefa9943bfd 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -183,7 +183,7 @@ require ( github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p v0.50.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.43.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.9.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index 6f4bdda88db..226e089887b 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -428,8 +428,8 @@ github.com/libp2p/go-libp2p v0.50.0 h1:A0tBP6mr6GV7l5ip2Q04a/IxPqVYYaCiif5s8JuXk github.com/libp2p/go-libp2p v0.50.0/go.mod h1:RjqB+dxCWNZ33yw0yeK5Pu157oNxKuaTZY6vLed4t8w= 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.42.3-0.20261028093345-0e1414ed4fa1 h1:EwrPaNVY/sRUdhbFppEMOJn3vv3VCIrt5UBF8chmfKE= -github.com/libp2p/go-libp2p-kad-dht v0.42.3-0.20261028093345-0e1414ed4fa1/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= +github.com/libp2p/go-libp2p-kad-dht v0.43.0 h1:PTa4Qea87dAd+ecAIPIrRIHByFsErjKFkFuYO0rgENU= +github.com/libp2p/go-libp2p-kad-dht v0.43.0/go.mod h1:H5knPH7Fc8Qp1va9h5C7Dp09MeVB5V30fnKTX847rPs= github.com/libp2p/go-libp2p-kbucket v0.9.0 h1:9kTf74R8CHGIk3QK+gwEOnJ/t+JzuxqQSfAAASl1VhM= github.com/libp2p/go-libp2p-kbucket v0.9.0/go.mod h1:lKhHVjRq1z/Cl/bFzB2vPzYY0KCdmON6HlBwEAanqjk= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= From b6b16593f0524bd5d90ef2e309fe514b85bb9974 Mon Sep 17 00:00:00 2001 From: guillaumemichel Date: Wed, 28 Oct 2026 13:38:04 +0100 Subject: [PATCH 5/5] config: Routing.PersistDHTRoutingTable=false --- config/routing.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/config/routing.go b/config/routing.go index ec956dde0d9..dc2f79b9232 100644 --- a/config/routing.go +++ b/config/routing.go @@ -12,7 +12,7 @@ import ( const ( DefaultAcceleratedDHTClient = false DefaultLoopbackAddressesOnLanDHT = false - DefaultPersistDHTRoutingTable = true + DefaultPersistDHTRoutingTable = false DefaultRoutingType = "auto" CidContactRoutingURL = "https://cid.contact" PublicGoodDelegatedRoutingURL = "https://delegated-ipfs.dev" // cid.contact + amino dht (incl. IPNS PUTs) @@ -20,16 +20,14 @@ const ( EnvHTTPRoutersFilterProtocols = "IPFS_HTTP_ROUTERS_FILTER_PROTOCOLS" ) -var ( - // Default filter-protocols to pass along with delegated routing requests (as defined in IPIP-484) - // and also filter out locally - DefaultHTTPRoutersFilterProtocols = getEnvOrDefault(EnvHTTPRoutersFilterProtocols, []string{ - "unknown", // allow results without protocol list, we can do libp2p identify to test them - "transport-bitswap", - // http is added dynamically in routing/delegated.go. - // 'transport-ipfs-gateway-http' - }) -) +// Default filter-protocols to pass along with delegated routing requests (as defined in IPIP-484) +// and also filter out locally +var DefaultHTTPRoutersFilterProtocols = getEnvOrDefault(EnvHTTPRoutersFilterProtocols, []string{ + "unknown", // allow results without protocol list, we can do libp2p identify to test them + "transport-bitswap", + // http is added dynamically in routing/delegated.go. + // 'transport-ipfs-gateway-http' +}) // Routing defines configuration options for libp2p routing. type Routing struct {