| 1 | package core |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | |
| 8 | context "context" |
| 9 | |
| 10 | "github.com/ipfs/kubo/repo" |
| 11 | |
| 12 | "github.com/ipfs/boxo/filestore" |
| 13 | "github.com/ipfs/boxo/keystore" |
| 14 | datastore "github.com/ipfs/go-datastore" |
| 15 | syncds "github.com/ipfs/go-datastore/sync" |
| 16 | config "github.com/ipfs/kubo/config" |
| 17 | "github.com/ipfs/kubo/core/node/libp2p" |
| 18 | golib "github.com/libp2p/go-libp2p" |
| 19 | ddht "github.com/libp2p/go-libp2p-kad-dht/dual" |
| 20 | "github.com/libp2p/go-libp2p-kad-dht/fullrt" |
| 21 | routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" |
| 22 | "github.com/libp2p/go-libp2p/core/host" |
| 23 | "github.com/libp2p/go-libp2p/core/peer" |
| 24 | pstore "github.com/libp2p/go-libp2p/core/peerstore" |
| 25 | mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" |
| 26 | ) |
| 27 | |
| 28 | func TestInitialization(t *testing.T) { |
| 29 | ctx := context.Background() |
| 30 | id := testIdentity |
| 31 | |
| 32 | good := []*config.Config{ |
| 33 | { |
| 34 | Identity: id, |
| 35 | Addresses: config.Addresses{ |
| 36 | Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}, |
| 37 | API: []string{"/ip4/127.0.0.1/tcp/8000"}, |
| 38 | }, |
| 39 | }, |
| 40 | |
| 41 | { |
| 42 | Identity: id, |
| 43 | Addresses: config.Addresses{ |
| 44 | Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}, |
| 45 | API: []string{"/ip4/127.0.0.1/tcp/8000"}, |
| 46 | }, |
| 47 | }, |
| 48 | } |
| 49 | |
| 50 | bad := []*config.Config{ |
| 51 | {}, |
| 52 | } |
| 53 | |
| 54 | for i, c := range good { |
| 55 | r := &repo.Mock{ |
| 56 | C: *c, |
| 57 | D: syncds.MutexWrap(datastore.NewMapDatastore()), |
| 58 | } |
| 59 | n, err := NewNode(ctx, &BuildCfg{Repo: r}) |
| 60 | if n == nil || err != nil { |
| 61 | t.Error("Should have constructed.", i, err) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | for i, c := range bad { |
| 66 | r := &repo.Mock{ |
| 67 | C: *c, |
| 68 | D: syncds.MutexWrap(datastore.NewMapDatastore()), |
| 69 | } |
| 70 | n, err := NewNode(ctx, &BuildCfg{Repo: r}) |
| 71 | if n != nil || err == nil { |
| 72 | t.Error("Should have failed to construct.", i) |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | var testIdentity = config.Identity{ |
| 78 | PeerID: "QmNgdzLieYi8tgfo2WfTUzNVH5hQK9oAYGVf6dxN12NrHt", |
| 79 | PrivKey: "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", |
| 80 | } |
| 81 | |
| 82 | // mockHostOption creates a HostOption that uses the provided mocknet. |
| 83 | // Inlined to avoid import cycle with core/mock package. |
| 84 | func mockHostOption(mn mocknet.Mocknet) libp2p.HostOption { |
| 85 | return func(id peer.ID, ps pstore.Peerstore, opts ...golib.Option) (host.Host, error) { |
| 86 | var cfg golib.Config |
| 87 | if err := cfg.Apply(opts...); err != nil { |
| 88 | return nil, err |
| 89 | } |
| 90 | |
| 91 | // The mocknet does not use the provided libp2p.Option. This options include |
| 92 | // the listening addresses we want our peer listening on. Therefore, we have |
| 93 | // to manually parse the configuration and add them here. |
| 94 | ps.AddAddrs(id, cfg.ListenAddrs, pstore.PermanentAddrTTL) |
| 95 | return mn.AddPeerWithPeerstore(id, ps) |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | func TestHasActiveDHTClient(t *testing.T) { |
| 100 | // Test 1: nil DHTClient |
| 101 | t.Run("nil DHTClient", func(t *testing.T) { |
| 102 | node := &IpfsNode{ |
| 103 | DHTClient: nil, |
| 104 | } |
| 105 | if node.HasActiveDHTClient() { |
| 106 | t.Error("Expected false for nil DHTClient") |
| 107 | } |
| 108 | }) |
| 109 | |
| 110 | // Test 2: Typed nil *ddht.DHT (common case when Routing.Type=delegated) |
| 111 | t.Run("typed nil ddht.DHT", func(t *testing.T) { |
| 112 | node := &IpfsNode{ |
| 113 | DHTClient: (*ddht.DHT)(nil), |
| 114 | } |
| 115 | if node.HasActiveDHTClient() { |
| 116 | t.Error("Expected false for typed nil *ddht.DHT") |
| 117 | } |
| 118 | }) |
| 119 | |
| 120 | // Test 3: Typed nil *fullrt.FullRT (accelerated DHT client) |
| 121 | t.Run("typed nil fullrt.FullRT", func(t *testing.T) { |
| 122 | node := &IpfsNode{ |
| 123 | DHTClient: (*fullrt.FullRT)(nil), |
| 124 | } |
| 125 | if node.HasActiveDHTClient() { |
| 126 | t.Error("Expected false for typed nil *fullrt.FullRT") |
| 127 | } |
| 128 | }) |
| 129 | |
| 130 | // Test 4: routinghelpers.Null no-op router (Routing.Type=none) |
| 131 | t.Run("routinghelpers.Null", func(t *testing.T) { |
| 132 | node := &IpfsNode{ |
| 133 | DHTClient: routinghelpers.Null{}, |
| 134 | } |
| 135 | if node.HasActiveDHTClient() { |
| 136 | t.Error("Expected false for routinghelpers.Null") |
| 137 | } |
| 138 | }) |
| 139 | |
| 140 | // Test 5: Valid standard dual DHT (Routing.Type=auto/dht/dhtclient) |
| 141 | t.Run("valid standard dual DHT", func(t *testing.T) { |
| 142 | ctx := context.Background() |
| 143 | mn := mocknet.New() |
| 144 | defer mn.Close() |
| 145 | |
| 146 | ds := syncds.MutexWrap(datastore.NewMapDatastore()) |
| 147 | c := config.Config{} |
| 148 | c.Identity = testIdentity |
| 149 | c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/4001"} |
| 150 | |
| 151 | r := &repo.Mock{ |
| 152 | C: c, |
| 153 | D: ds, |
| 154 | K: keystore.NewMemKeystore(), |
| 155 | F: filestore.NewFileManager(ds, filepath.Dir(os.TempDir())), |
| 156 | } |
| 157 | |
| 158 | node, err := NewNode(ctx, &BuildCfg{ |
| 159 | Routing: libp2p.DHTServerOption, |
| 160 | Repo: r, |
| 161 | Host: mockHostOption(mn), |
| 162 | Online: true, |
| 163 | }) |
| 164 | if err != nil { |
| 165 | t.Fatalf("Failed to create node with DHT: %v", err) |
| 166 | } |
| 167 | defer node.Close() |
| 168 | |
| 169 | // First verify test setup created the expected DHT type |
| 170 | if node.DHTClient == nil { |
| 171 | t.Fatalf("Test setup failed: DHTClient is nil") |
| 172 | } |
| 173 | |
| 174 | if _, ok := node.DHTClient.(*ddht.DHT); !ok { |
| 175 | t.Fatalf("Test setup failed: expected DHTClient to be *ddht.DHT, got %T", node.DHTClient) |
| 176 | } |
| 177 | |
| 178 | // Now verify HasActiveDHTClient() correctly identifies it as active |
| 179 | if !node.HasActiveDHTClient() { |
| 180 | t.Error("Expected true for valid dual DHT client") |
| 181 | } |
| 182 | }) |
| 183 | |
| 184 | // Test 6: Valid accelerated DHT client (Routing.Type=autoclient) |
| 185 | t.Run("valid accelerated DHT client", func(t *testing.T) { |
| 186 | ctx := context.Background() |
| 187 | mn := mocknet.New() |
| 188 | defer mn.Close() |
| 189 | |
| 190 | ds := syncds.MutexWrap(datastore.NewMapDatastore()) |
| 191 | c := config.Config{} |
| 192 | c.Identity = testIdentity |
| 193 | c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/4001"} |
| 194 | c.Routing.AcceleratedDHTClient = config.True |
| 195 | |
| 196 | r := &repo.Mock{ |
| 197 | C: c, |
| 198 | D: ds, |
| 199 | K: keystore.NewMemKeystore(), |
| 200 | F: filestore.NewFileManager(ds, filepath.Dir(os.TempDir())), |
| 201 | } |
| 202 | |
| 203 | node, err := NewNode(ctx, &BuildCfg{ |
| 204 | Routing: libp2p.DHTOption, |
| 205 | Repo: r, |
| 206 | Host: mockHostOption(mn), |
| 207 | Online: true, |
| 208 | }) |
| 209 | if err != nil { |
| 210 | t.Fatalf("Failed to create node with accelerated DHT: %v", err) |
| 211 | } |
| 212 | defer node.Close() |
| 213 | |
| 214 | // First verify test setup created the expected accelerated DHT type |
| 215 | if node.DHTClient == nil { |
| 216 | t.Fatalf("Test setup failed: DHTClient is nil") |
| 217 | } |
| 218 | |
| 219 | if _, ok := node.DHTClient.(*fullrt.FullRT); !ok { |
| 220 | t.Fatalf("Test setup failed: expected DHTClient to be *fullrt.FullRT, got %T", node.DHTClient) |
| 221 | } |
| 222 | |
| 223 | // Now verify HasActiveDHTClient() correctly identifies it as active |
| 224 | if !node.HasActiveDHTClient() { |
| 225 | t.Error("Expected true for valid accelerated DHT client") |
| 226 | } |
| 227 | }) |
| 228 | } |