Skip to content

Commit f449e8b

Browse files
authored
fix(examples): avoid bitswap race, use ed25519 (#11282)
Reduce example-test flakiness and align with current kubo conventions. - move peer connection before ipfsA.Unixfs().Add() so bitswap's peer-connected event settles before Part IV fetches; on a slow CI runner the 1s ProvSearchDelay can expire and fail the test - use Ed25519 keys via CreateIdentity, matching `ipfs init` default - comments are concise and avoid jargon
1 parent 8cebf5c commit f449e8b

File tree

1 file changed

+42
-62
lines changed
  • docs/examples/kubo-as-a-library

1 file changed

+42
-62
lines changed

docs/examples/kubo-as-a-library/main.go

Lines changed: 42 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,26 @@ import (
1515
"github.com/ipfs/boxo/files"
1616
"github.com/ipfs/boxo/path"
1717
icore "github.com/ipfs/kubo/core/coreiface"
18+
options "github.com/ipfs/kubo/core/coreiface/options"
1819
ma "github.com/multiformats/go-multiaddr"
1920

2021
"github.com/ipfs/kubo/config"
2122
"github.com/ipfs/kubo/core"
2223
"github.com/ipfs/kubo/core/coreapi"
2324
"github.com/ipfs/kubo/core/node/libp2p"
24-
"github.com/ipfs/kubo/plugin/loader" // This package is needed so that all the preloaded plugins are loaded automatically
25+
"github.com/ipfs/kubo/plugin/loader" // registers built-in plugins
2526
"github.com/ipfs/kubo/repo/fsrepo"
2627
"github.com/libp2p/go-libp2p/core/peer"
2728
)
2829

2930
/// ------ Setting up the IPFS Repo
3031

3132
func setupPlugins(externalPluginsPath string) error {
32-
// Load any external plugins if available on externalPluginsPath
3333
plugins, err := loader.NewPluginLoader(filepath.Join(externalPluginsPath, "plugins"))
3434
if err != nil {
3535
return fmt.Errorf("error loading plugins: %s", err)
3636
}
3737

38-
// Load preloaded and external plugins
3938
if err := plugins.Initialize(); err != nil {
4039
return fmt.Errorf("error initializing plugins: %s", err)
4140
}
@@ -53,37 +52,36 @@ func createTempRepo() (string, error) {
5352
return "", fmt.Errorf("failed to get temp dir: %s", err)
5453
}
5554

56-
// Create a config with default options and a 2048 bit key
57-
cfg, err := config.Init(io.Discard, 2048)
55+
identity, err := config.CreateIdentity(io.Discard, []options.KeyGenerateOption{
56+
options.Key.Type(options.Ed25519Key),
57+
})
58+
if err != nil {
59+
return "", err
60+
}
61+
cfg, err := config.InitWithIdentity(identity)
5862
if err != nil {
5963
return "", err
6064
}
6165

62-
// Use TCP-only on loopback with random port for reliable local testing.
63-
// This matches what kubo's test harness uses (test/cli/transports_test.go).
64-
// QUIC/UDP transports are avoided because they may be throttled on CI.
66+
// TCP on loopback with a random port. QUIC/UDP is disabled because it can
67+
// be throttled on some networks; TCP is more reliable for local testing.
6568
cfg.Addresses.Swarm = []string{
6669
"/ip4/127.0.0.1/tcp/0",
6770
}
68-
69-
// Explicitly disable non-TCP transports for reliability.
7071
cfg.Swarm.Transports.Network.QUIC = config.False
7172
cfg.Swarm.Transports.Network.Relay = config.False
7273
cfg.Swarm.Transports.Network.WebTransport = config.False
7374
cfg.Swarm.Transports.Network.WebRTCDirect = config.False
7475
cfg.Swarm.Transports.Network.Websocket = config.False
7576
cfg.AutoTLS.Enabled = config.False
7677

77-
// Disable routing - we don't need DHT for direct peer connections.
78-
// Bitswap works with directly connected peers without needing DHT lookups.
78+
// No DHT: we connect peers by address, so content routing is not needed.
7979
cfg.Routing.Type = config.NewOptionalString("none")
8080

81-
// Disable bootstrap for this example - we manually connect only the peers we need.
81+
// No automatic bootstrap: we connect only the peers we need.
8282
cfg.Bootstrap = []string{}
8383

84-
// When creating the repository, you can define custom settings on the repository, such as enabling experimental
85-
// features (See experimental-features.md) or customizing the gateway endpoint.
86-
// To do such things, you should modify the variable `cfg`. For example:
84+
// Optional: enable experimental features by modifying cfg before Init, e.g.:
8785
if *flagExp {
8886
// https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-filestore
8987
cfg.Experimental.FilestoreEnabled = true
@@ -94,10 +92,8 @@ func createTempRepo() (string, error) {
9492
// https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#p2p-http-proxy
9593
cfg.Experimental.P2pHttpProxy = true
9694
// See also: https://github.com/ipfs/kubo/blob/master/docs/config.md
97-
// And: https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md
9895
}
9996

100-
// Create the repo with the config
10197
err = fsrepo.Init(repoPath, cfg)
10298
if err != nil {
10399
return "", fmt.Errorf("failed to init ephemeral node: %s", err)
@@ -108,23 +104,18 @@ func createTempRepo() (string, error) {
108104

109105
/// ------ Spawning the node
110106

111-
// Creates an IPFS node and returns its coreAPI.
107+
// createNode opens the repo at repoPath and starts an IPFS node.
112108
func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) {
113-
// Open the repo
114109
repo, err := fsrepo.Open(repoPath)
115110
if err != nil {
116111
return nil, err
117112
}
118113

119-
// Construct the node
120-
121114
nodeOptions := &core.BuildCfg{
122115
Online: true,
123-
// For this example, we use NilRouterOption (no routing) since we connect peers directly.
124-
// Bitswap works with directly connected peers without needing DHT lookups.
125-
// In production, you would typically use:
126-
// Routing: libp2p.DHTOption, // Full DHT node (stores and fetches records)
127-
// Routing: libp2p.DHTClientOption, // DHT client (only fetches records)
116+
// No routing: peers are connected directly by address.
117+
// In production use libp2p.DHTClientOption or libp2p.DHTOption
118+
// so the node can find content and peers on the wider network.
128119
Routing: libp2p.NilRouterOption,
129120
Repo: repo,
130121
}
@@ -134,7 +125,7 @@ func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) {
134125

135126
var loadPluginsOnce sync.Once
136127

137-
// Spawns a node to be used just for this run (i.e. creates a tmp repo).
128+
// spawnEphemeral creates a temporary repo, starts a node, and returns its API.
138129
func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error) {
139130
var onceErr error
140131
loadPluginsOnce.Do(func() {
@@ -144,7 +135,6 @@ func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error)
144135
return nil, nil, onceErr
145136
}
146137

147-
// Create a Temporary Repo
148138
repoPath, err := createTempRepo()
149139
if err != nil {
150140
return nil, nil, fmt.Errorf("failed to create temp repo: %s", err)
@@ -222,21 +212,12 @@ func main() {
222212
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
223213
defer cancel()
224214

225-
// Spawn a local peer using a temporary path, for testing purposes
215+
// Spawn a local peer using a temporary path, for testing purposes.
226216
ipfsA, nodeA, err := spawnEphemeral(ctx)
227217
if err != nil {
228218
panic(fmt.Errorf("failed to spawn peer node: %s", err))
229219
}
230220

231-
peerCidFile, err := ipfsA.Unixfs().Add(ctx,
232-
files.NewBytesFile([]byte("hello from ipfs 101 in Kubo")))
233-
if err != nil {
234-
panic(fmt.Errorf("could not add File: %s", err))
235-
}
236-
237-
fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String())
238-
239-
// Spawn a node using a temporary path, creating a temporary repo for the run
240221
fmt.Println("Spawning Kubo node on a temporary repo")
241222
ipfsB, _, err := spawnEphemeral(ctx)
242223
if err != nil {
@@ -245,6 +226,27 @@ func main() {
245226

246227
fmt.Println("IPFS node is running")
247228

229+
// Connect nodeB to nodeA before adding content. This lets the connection
230+
// finish its setup during the Add below, so the fetch in Part IV is fast.
231+
peerAddrs, err := ipfsA.Swarm().LocalAddrs(ctx)
232+
if err != nil {
233+
panic(fmt.Errorf("could not get peer addresses: %s", err))
234+
}
235+
peerMa := peerAddrs[0].String() + "/p2p/" + nodeA.Identity.String()
236+
fmt.Println("Connecting to peer...")
237+
if err := connectToPeers(ctx, ipfsB, []string{peerMa}); err != nil {
238+
panic(fmt.Errorf("failed to connect to peer: %s", err))
239+
}
240+
fmt.Println("Connected to peer")
241+
242+
peerCidFile, err := ipfsA.Unixfs().Add(ctx,
243+
files.NewBytesFile([]byte("hello from ipfs 101 in Kubo")))
244+
if err != nil {
245+
panic(fmt.Errorf("could not add File: %s", err))
246+
}
247+
248+
fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String())
249+
248250
/// --- Part II: Adding a file and a directory to IPFS
249251

250252
fmt.Println("\n-- Adding and getting back files & directories --")
@@ -313,29 +315,7 @@ func main() {
313315

314316
/// --- Part IV: Getting a file from another IPFS node
315317

316-
fmt.Println("\n-- Connecting to nodeA and fetching content via bitswap --")
317-
318-
// Get nodeA's actual listening address dynamically.
319-
// We configured TCP-only on 127.0.0.1 with random port, so this will be a TCP address.
320-
peerAddrs, err := ipfsA.Swarm().LocalAddrs(ctx)
321-
if err != nil {
322-
panic(fmt.Errorf("could not get peer addresses: %s", err))
323-
}
324-
peerMa := peerAddrs[0].String() + "/p2p/" + nodeA.Identity.String()
325-
326-
bootstrapNodes := []string{
327-
// In production, use real bootstrap peers like:
328-
// "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
329-
// For this example, we only connect to nodeA which has our test content.
330-
peerMa,
331-
}
332-
333-
fmt.Println("Connecting to peer...")
334-
err = connectToPeers(ctx, ipfsB, bootstrapNodes)
335-
if err != nil {
336-
panic(fmt.Errorf("failed to connect to peers: %s", err))
337-
}
338-
fmt.Println("Connected to peer")
318+
fmt.Println("\n-- Fetching content from nodeA via bitswap --")
339319

340320
exampleCIDStr := peerCidFile.RootCid().String()
341321

0 commit comments

Comments
 (0)