Skip to content

Commit 4ce979d

Browse files
committed
fix: IPFS peering never persists, and bitswap hangs even when peered
Two separate bugs were still causing shared storage downloads to hang/time out intermittently even after the earlier peering fix (#359): 1. swarm/peering/add returns success but never actually writes to the on-disk Peering.Peers config - verified directly by checking the config immediately after calling it. It only registers the peer with the running daemon's in-memory peering service for that session. Since first-time setup restarts the IPFS containers partway through (to apply finalized config), any peering set up before that restart was silently lost, leaving nodes to fall back on unreliable mDNS. Switched to POST /api/v0/config to persist Peering.Peers for real; verified it survives a full container restart with both nodes reconnecting automatically. 2. Even once genuinely peered, content fetches still hang: this is a long-standing upstream Kubo/bitswap bug (ipfs/kubo#8346, open since 2021) where in a small private network, bitswap discovers a provider via the DHT but never sends it a WANT message if the swarm connection to that peer already existed - reproduced directly (bitswap stat showed 0 partners despite an active, bitswap-protocol-negotiated swarm connection). Routing.Type is now set to "none" instead of "dht": with no DHT to depend on, bitswap has no choice but to broadcast wants directly to its connected peers, which is all a small private swarm needs anyway. Verified 5/5 sequential cross-node fetches plus the HTTP gateway path all succeed reliably with this change. Signed-off-by: Enrique Lacal <enrique.lacal@kaleido.io>
1 parent 4bfec00 commit 4ce979d

2 files changed

Lines changed: 41 additions & 10 deletions

File tree

internal/stacks/ipfs_config.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,22 @@ func GenerateSwarmKey() (string, error) {
3434
// disables Kubo's AutoConf/public-network defaults, which are incompatible
3535
// with a private swarm (swarm.key / LIBP2P_FORCE_PNET) and otherwise prevent
3636
// the daemon from starting or from ever peering with other members.
37+
//
38+
// Routing.Type is set to "none" rather than "dht": a small private swarm hits
39+
// a long-standing upstream bug (https://github.com/ipfs/kubo/issues/8346)
40+
// where bitswap finds a provider via the DHT but never sends it a WANT
41+
// message if the swarm connection to that peer already existed, so content
42+
// fetches hang even though the peers are connected. With routing disabled,
43+
// bitswap has no choice but to broadcast wants directly to its connected
44+
// peers, which is all a 2+ node private swarm actually needs.
3745
func GenerateIPFSPrivateNetInitScript() string {
3846
return `#!/bin/sh
3947
ipfs config --json AutoConf.Enabled false
4048
ipfs config --json Bootstrap '[]'
4149
ipfs config --json DNS.Resolvers '{}'
4250
ipfs config --json Routing.DelegatedRouters '[]'
4351
ipfs config --json Ipns.DelegatedPublishers '[]'
44-
ipfs config Routing.Type dht
52+
ipfs config Routing.Type none
4553
ipfs config --json AutoTLS.Enabled false
4654
ipfs config --json Swarm.Transports.Network.Websocket false
4755
`

internal/stacks/stack_manager.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"net"
2424
"net/http"
25+
"net/url"
2526
"os"
2627
"os/exec"
2728
"path"
@@ -614,24 +615,46 @@ func (s *StackManager) peerIPFSNodes() error {
614615
peerIDs[member.ID] = idResp.ID
615616
}
616617

618+
type peeringPeer struct {
619+
ID string `json:"ID"`
620+
Addrs []string `json:"Addrs"`
621+
}
622+
617623
for _, member := range s.Stack.Members {
624+
peers := []peeringPeer{}
618625
for _, other := range s.Stack.Members {
619626
if member.ID == other.ID {
620627
continue
621628
}
622-
addr := fmt.Sprintf("/dns4/ipfs_%s/tcp/4001/p2p/%s", other.ID, peerIDs[other.ID])
629+
peers = append(peers, peeringPeer{
630+
ID: peerIDs[other.ID],
631+
Addrs: []string{fmt.Sprintf("/dns4/ipfs_%s/tcp/4001", other.ID)},
632+
})
633+
}
634+
peersJSON, err := json.Marshal(peers)
635+
if err != nil {
636+
return err
637+
}
623638

624-
// swarm/peering/add only registers the peer with Kubo's background
625-
// reconnect service - it returns success even when the peer is
626-
// unreachable, so it does not confirm a connection was made.
627-
peeringURL := fmt.Sprintf("http://127.0.0.1:%d/api/v0/swarm/peering/add?arg=%s", member.ExposedIPFSApiPort, addr)
628-
if err := core.RequestWithRetry(s.ctx, http.MethodPost, peeringURL, nil, nil); err != nil {
629-
return fmt.Errorf("failed to peer IPFS node %s with %s: %w", member.ID, other.ID, err)
630-
}
639+
// swarm/peering/add only registers the peer with the *running*
640+
// daemon's in-memory peering service and returns success even when
641+
// the peer is unreachable - it does not persist to Peering.Peers in
642+
// the on-disk config, so the pairing is lost on the next restart.
643+
// Writing through /api/v0/config persists it for real, and Kubo
644+
// picks up the change live (no restart needed).
645+
configURL := fmt.Sprintf("http://127.0.0.1:%d/api/v0/config?arg=Peering.Peers&arg=%s&json=true", member.ExposedIPFSApiPort, url.QueryEscape(string(peersJSON)))
646+
if err := core.RequestWithRetry(s.ctx, http.MethodPost, configURL, nil, nil); err != nil {
647+
return fmt.Errorf("failed to persist IPFS peering config for member %s: %w", member.ID, err)
648+
}
631649

650+
for _, other := range s.Stack.Members {
651+
if member.ID == other.ID {
652+
continue
653+
}
654+
addr := fmt.Sprintf("/dns4/ipfs_%s/tcp/4001/p2p/%s", other.ID, peerIDs[other.ID])
632655
// swarm/connect dials synchronously and errors if the connection
633656
// fails, so retrying it confirms the nodes are actually connected
634-
// rather than just registered to reconnect in the background.
657+
// now rather than waiting on the peering service's own timing.
635658
connectURL := fmt.Sprintf("http://127.0.0.1:%d/api/v0/swarm/connect?arg=%s", member.ExposedIPFSApiPort, addr)
636659
if err := core.RequestWithRetry(s.ctx, http.MethodPost, connectURL, nil, nil); err != nil {
637660
return fmt.Errorf("failed to connect IPFS node %s to %s: %w", member.ID, other.ID, err)

0 commit comments

Comments
 (0)