diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index a747ec1dc..367c8ed38 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -10,7 +10,7 @@ require ( github.com/ipfs/boxo v0.35.3-0.20251202220026-0842ad274a0c github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.46.0 - github.com/multiformats/go-multiaddr v0.16.1 + github.com/stretchr/testify v1.11.1 ) require ( @@ -40,6 +40,7 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf // indirect github.com/cskr/pubsub v1.0.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgraph-io/badger v1.6.2 // indirect @@ -137,6 +138,7 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr v0.16.1 // indirect github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect @@ -169,6 +171,7 @@ require ( github.com/pion/turn/v4 v4.0.2 // indirect github.com/pion/webrtc/v4 v4.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/probe-lab/go-libdht v0.4.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect diff --git a/docs/examples/kubo-as-a-library/main.go b/docs/examples/kubo-as-a-library/main.go index 8edfa1aeb..b40c6808f 100644 --- a/docs/examples/kubo-as-a-library/main.go +++ b/docs/examples/kubo-as-a-library/main.go @@ -11,7 +11,6 @@ import ( "sync" "time" - "github.com/ipfs/boxo/bootstrap" "github.com/ipfs/boxo/files" "github.com/ipfs/boxo/path" icore "github.com/ipfs/kubo/core/coreiface" @@ -46,7 +45,7 @@ func setupPlugins(externalPluginsPath string) error { return nil } -func createTempRepo(swarmPort int) (string, error) { +func createTempRepo() (string, error) { repoPath, err := os.MkdirTemp("", "ipfs-shell") if err != nil { return "", fmt.Errorf("failed to get temp dir: %s", err) @@ -58,15 +57,16 @@ func createTempRepo(swarmPort int) (string, error) { return "", err } - // Configure custom ports to avoid conflicts with other IPFS instances. - // This demonstrates how to customize the node's network addresses. + // Use port 0 to let the OS assign random available ports. + // This avoids conflicts with other services or parallel test runs. cfg.Addresses.Swarm = []string{ - fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", swarmPort), - fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1", swarmPort), - fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1/webtransport", swarmPort), - fmt.Sprintf("/ip4/0.0.0.0/udp/%d/webrtc-direct", swarmPort), + "/ip4/127.0.0.1/tcp/0", + "/ip4/127.0.0.1/udp/0/quic-v1", } + // Enable loopback addresses on LAN DHT for local testing. + cfg.Routing.LoopbackAddressesOnLanDHT = config.True + // Disable auto-bootstrap. For this example, we manually connect only the peers we need. // In production, you'd typically keep the default bootstrap peers to join the network. cfg.Bootstrap = []string{} @@ -121,8 +121,7 @@ func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) { var loadPluginsOnce sync.Once // Spawns a node to be used just for this run (i.e. creates a tmp repo). -// The swarmPort parameter specifies the port for libp2p swarm listeners. -func spawnEphemeral(ctx context.Context, swarmPort int) (icore.CoreAPI, *core.IpfsNode, error) { +func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error) { var onceErr error loadPluginsOnce.Do(func() { onceErr = setupPlugins("") @@ -132,7 +131,7 @@ func spawnEphemeral(ctx context.Context, swarmPort int) (icore.CoreAPI, *core.Ip } // Create a Temporary Repo - repoPath, err := createTempRepo(swarmPort) + repoPath, err := createTempRepo() if err != nil { return nil, nil, fmt.Errorf("failed to create temp repo: %s", err) } @@ -176,8 +175,7 @@ func main() { defer cancel() // Spawn a local peer using a temporary path, for testing purposes - // Using port 4010 to avoid conflict with default IPFS port 4001 - ipfsA, nodeA, err := spawnEphemeral(ctx, 4010) + ipfsA, nodeA, err := spawnEphemeral(ctx) if err != nil { panic(fmt.Errorf("failed to spawn peer node: %s", err)) } @@ -190,10 +188,9 @@ func main() { fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String()) - // Spawn a node using a temporary path, creating a temporary repo for the run - // Using port 4011 (different from nodeA's port 4010) + // Spawn a second node using a temporary path fmt.Println("Spawning Kubo node on a temporary repo") - ipfsB, nodeB, err := spawnEphemeral(ctx, 4011) + ipfsB, nodeB, err := spawnEphemeral(ctx) if err != nil { panic(fmt.Errorf("failed to spawn ephemeral node: %s", err)) } @@ -270,29 +267,27 @@ func main() { fmt.Println("\n-- Connecting to nodeA and fetching content via bitswap --") + // Connect nodes bidirectionally for bitswap transfer. + // Mutual peering protects the connection from being culled by the resource manager on either side. // In production, you'd typically connect to bootstrap peers to join the IPFS network. - // autoconf.FallbackBootstrapPeers from boxo/autoconf provides well-known bootstrap peers: - // "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - // "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - // "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - // - // For this example, we only connect nodeB to nodeA to demonstrate direct bitswap. - // We use Bootstrap() instead of raw Swarm().Connect() because Bootstrap() properly - // integrates peers into the routing system and ensures bitswap learns about them. - fmt.Println("Bootstrapping nodeB to nodeA...") - nodeAPeerInfo := nodeA.Peerstore.PeerInfo(nodeA.Identity) - if err := nodeB.Bootstrap(bootstrap.BootstrapConfigWithPeers([]peer.AddrInfo{nodeAPeerInfo})); err != nil { - panic(fmt.Errorf("failed to bootstrap nodeB to nodeA: %s", err)) + nodeAPeerInfo := peer.AddrInfo{ + ID: nodeA.Identity, + Addrs: nodeA.Peerstore.Addrs(nodeA.Identity), } - fmt.Println("nodeB is now connected to nodeA") - - // Since nodeB is directly connected to nodeA, bitswap can fetch the content - // without needing DHT-based provider discovery. In a production scenario where - // nodes aren't directly connected, you'd use Routing().Provide() to announce - // content to the DHT (see docs/config.md#providedhtsweepenabled for background providing). + nodeBPeerInfo := peer.AddrInfo{ + ID: nodeB.Identity, + Addrs: nodeB.Peerstore.Addrs(nodeB.Identity), + } + fmt.Println("Connecting nodes bidirectionally...") + if err := ipfsB.Swarm().Connect(ctx, nodeAPeerInfo); err != nil { + panic(fmt.Errorf("failed to connect nodeB to nodeA: %s", err)) + } + if err := ipfsA.Swarm().Connect(ctx, nodeBPeerInfo); err != nil { + panic(fmt.Errorf("failed to connect nodeA to nodeB: %s", err)) + } + fmt.Println("Nodes connected") exampleCIDStr := peerCidFile.RootCid().String() - fmt.Printf("Fetching a file from the network with CID %s\n", exampleCIDStr) outputPath := outputBasePath + exampleCIDStr testCID := path.FromCid(peerCidFile.RootCid())