master
go 253 lines 9.12 KB
Raw
1 package autoconf
2
3 import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "slices"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/ipfs/kubo/config"
13 "github.com/ipfs/kubo/test/cli/harness"
14 "github.com/stretchr/testify/require"
15 )
16
17 // TestAutoConfExtensibility_NewSystem verifies that the AutoConf system can be extended
18 // with new routing systems beyond the default AminoDHT and IPNI.
19 //
20 // The test verifies that:
21 // 1. New systems can be added via AutoConf's SystemRegistry
22 // 2. Native vs delegated system filtering works correctly:
23 // - Native systems (AminoDHT) provide bootstrap peers and are used for P2P routing
24 // - Delegated systems (IPNI, NewSystem) provide HTTP endpoints for delegated routing
25 //
26 // 3. The system correctly filters endpoints based on routing type
27 //
28 // Note: Only native systems contribute bootstrap peers. Delegated systems like "NewSystem"
29 // only provide HTTP routing endpoints, not P2P bootstrap peers.
30 func TestAutoConfExtensibility_NewSystem(t *testing.T) {
31 if testing.Short() {
32 t.Skip("skipping test in short mode")
33 }
34
35 // Setup mock autoconf server with NewSystem
36 var mockServer *httptest.Server
37 mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38 // Create autoconf.json with NewSystem
39 autoconfData := map[string]any{
40 "AutoConfVersion": 2025072901,
41 "AutoConfSchema": 1,
42 "AutoConfTTL": 86400,
43 "SystemRegistry": map[string]any{
44 "AminoDHT": map[string]any{
45 "URL": "https://github.com/ipfs/specs/pull/497",
46 "Description": "Public DHT swarm",
47 "NativeConfig": map[string]any{
48 "Bootstrap": []string{
49 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
50 },
51 },
52 "DelegatedConfig": map[string]any{
53 "Read": []string{"/routing/v1/providers", "/routing/v1/peers", "/routing/v1/ipns"},
54 "Write": []string{"/routing/v1/ipns"},
55 },
56 },
57 "IPNI": map[string]any{
58 "URL": "https://ipni.example.com",
59 "Description": "Network Indexer",
60 "DelegatedConfig": map[string]any{
61 "Read": []string{"/routing/v1/providers"},
62 "Write": []string{},
63 },
64 },
65 "NewSystem": map[string]any{
66 "URL": "https://example.com/newsystem",
67 "Description": "Test system for extensibility verification",
68 "NativeConfig": map[string]any{
69 "Bootstrap": []string{
70 "/ip4/127.0.0.1/tcp/9999/p2p/12D3KooWPeQ4r3v6CmVmKXoFGtqEqcr3L8P6La9yH5oEWKtoLVVa",
71 },
72 },
73 "DelegatedConfig": map[string]any{
74 "Read": []string{"/routing/v1/providers"},
75 "Write": []string{},
76 },
77 },
78 },
79 "DNSResolvers": map[string]any{
80 "eth.": []string{"https://dns.eth.limo/dns-query"},
81 },
82 "DelegatedEndpoints": map[string]any{
83 "https://ipni.example.com": map[string]any{
84 "Systems": []string{"IPNI"},
85 "Read": []string{"/routing/v1/providers"},
86 "Write": []string{},
87 },
88 mockServer.URL + "/newsystem": map[string]any{
89 "Systems": []string{"NewSystem"},
90 "Read": []string{"/routing/v1/providers"},
91 "Write": []string{},
92 },
93 },
94 }
95
96 w.Header().Set("Content-Type", "application/json")
97 w.Header().Set("Cache-Control", "max-age=300")
98 _ = json.NewEncoder(w).Encode(autoconfData)
99 }))
100 defer mockServer.Close()
101
102 // NewSystem mock server URL will be dynamically assigned
103 newSystemServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
104 // Simple mock server for NewSystem endpoint
105 response := map[string]any{"Providers": []any{}}
106 w.Header().Set("Content-Type", "application/json")
107 _ = json.NewEncoder(w).Encode(response)
108 }))
109 defer newSystemServer.Close()
110
111 // Update the autoconf to point to the correct NewSystem endpoint
112 mockServer.Close()
113 mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114 autoconfData := map[string]any{
115 "AutoConfVersion": 2025072901,
116 "AutoConfSchema": 1,
117 "AutoConfTTL": 86400,
118 "SystemRegistry": map[string]any{
119 "AminoDHT": map[string]any{
120 "URL": "https://github.com/ipfs/specs/pull/497",
121 "Description": "Public DHT swarm",
122 "NativeConfig": map[string]any{
123 "Bootstrap": []string{
124 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
125 },
126 },
127 "DelegatedConfig": map[string]any{
128 "Read": []string{"/routing/v1/providers", "/routing/v1/peers", "/routing/v1/ipns"},
129 "Write": []string{"/routing/v1/ipns"},
130 },
131 },
132 "IPNI": map[string]any{
133 "URL": "https://ipni.example.com",
134 "Description": "Network Indexer",
135 "DelegatedConfig": map[string]any{
136 "Read": []string{"/routing/v1/providers"},
137 "Write": []string{},
138 },
139 },
140 "NewSystem": map[string]any{
141 "URL": "https://example.com/newsystem",
142 "Description": "Test system for extensibility verification",
143 "NativeConfig": map[string]any{
144 "Bootstrap": []string{
145 "/ip4/127.0.0.1/tcp/9999/p2p/12D3KooWPeQ4r3v6CmVmKXoFGtqEqcr3L8P6La9yH5oEWKtoLVVa",
146 },
147 },
148 "DelegatedConfig": map[string]any{
149 "Read": []string{"/routing/v1/providers"},
150 "Write": []string{},
151 },
152 },
153 },
154 "DNSResolvers": map[string]any{
155 "eth.": []string{"https://dns.eth.limo/dns-query"},
156 },
157 "DelegatedEndpoints": map[string]any{
158 "https://ipni.example.com": map[string]any{
159 "Systems": []string{"IPNI"},
160 "Read": []string{"/routing/v1/providers"},
161 "Write": []string{},
162 },
163 newSystemServer.URL: map[string]any{
164 "Systems": []string{"NewSystem"},
165 "Read": []string{"/routing/v1/providers"},
166 "Write": []string{},
167 },
168 },
169 }
170
171 w.Header().Set("Content-Type", "application/json")
172 w.Header().Set("Cache-Control", "max-age=300")
173 _ = json.NewEncoder(w).Encode(autoconfData)
174 }))
175 defer mockServer.Close()
176
177 // Create Kubo node with autoconf pointing to mock server
178 h := harness.NewT(t)
179 node := h.NewNode().Init()
180
181 // Update config to use mock autoconf server
182 node.UpdateConfig(func(cfg *config.Config) {
183 cfg.AutoConf.URL = config.NewOptionalString(mockServer.URL)
184 cfg.AutoConf.Enabled = config.True
185 cfg.AutoConf.RefreshInterval = config.NewOptionalDuration(1 * time.Second)
186 cfg.Routing.Type = config.NewOptionalString("auto") // Should enable native AminoDHT + delegated others
187 cfg.Bootstrap = []string{"auto"}
188 cfg.Routing.DelegatedRouters = []string{"auto"}
189 })
190
191 // Start the daemon
192 daemon := node.StartDaemon()
193 defer daemon.StopDaemon()
194
195 // Give the daemon some time to initialize and make requests
196 time.Sleep(3 * time.Second)
197
198 // Test 1: Verify bootstrap includes both AminoDHT and NewSystem peers (deduplicated)
199 bootstrapResult := daemon.IPFS("bootstrap", "list", "--expand-auto")
200 bootstrapOutput := bootstrapResult.Stdout.String()
201 t.Logf("Bootstrap output: %s", bootstrapOutput)
202
203 // Should contain original DHT bootstrap peer (AminoDHT is a native system)
204 require.Contains(t, bootstrapOutput, "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", "Should contain AminoDHT bootstrap peer")
205
206 // Note: NewSystem bootstrap peers are NOT included because only native systems
207 // (AminoDHT for Routing.Type="auto") contribute bootstrap peers.
208 // Delegated systems like NewSystem only provide HTTP routing endpoints.
209
210 // Test 2: Verify delegated endpoints are filtered correctly
211 // For Routing.Type=auto, native systems=[AminoDHT], so:
212 // - AminoDHT endpoints should be filtered out
213 // - IPNI and NewSystem endpoints should be included
214
215 // Get the expanded delegated routers using --expand-auto
216 routerResult := daemon.IPFS("config", "Routing.DelegatedRouters", "--expand-auto")
217 var expandedRouters []string
218 require.NoError(t, json.Unmarshal([]byte(routerResult.Stdout.String()), &expandedRouters))
219
220 t.Logf("Expanded delegated routers: %v", expandedRouters)
221
222 // Verify we got exactly 2 delegated routers: IPNI and NewSystem
223 require.Equal(t, 2, len(expandedRouters), "Should have exactly 2 delegated routers (IPNI and NewSystem). Got %d: %v", len(expandedRouters), expandedRouters)
224
225 // Convert to URLs for checking
226 routerURLs := expandedRouters
227
228 // Should contain NewSystem endpoint (not native) - now with routing path
229 foundNewSystem := false
230 expectedNewSystemURL := newSystemServer.URL + "/routing/v1/providers" // Full URL with path, as returned by DelegatedRoutersWithAutoConf
231 if slices.Contains(routerURLs, expectedNewSystemURL) {
232 foundNewSystem = true
233 }
234 require.True(t, foundNewSystem, "Should contain NewSystem endpoint (%s) for delegated routing, got: %v", expectedNewSystemURL, routerURLs)
235
236 // Should contain ipni.example.com (IPNI is not native)
237 foundIPNI := false
238 for _, url := range routerURLs {
239 if strings.Contains(url, "ipni.example.com") {
240 foundIPNI = true
241 break
242 }
243 }
244 require.True(t, foundIPNI, "Should contain ipni.example.com endpoint for IPNI")
245
246 // Test passes - we've verified that:
247 // 1. Bootstrap peers are correctly resolved from native systems only
248 // 2. Delegated routers include both IPNI and NewSystem endpoints
249 // 3. URL format is correct (base URLs with paths)
250 // 4. AutoConf extensibility works for unknown systems
251
252 t.Log("NewSystem extensibility test passed - Kubo successfully discovered and used unknown routing system")
253 }