Skip to content

Commit 6e89063

Browse files
committed
feat(client/rpc): add provide stat and dag import support
adds RPC client support for: - ipfs provide stat (with --lan flag for dual DHT) - ipfs dag import (with --fast-provide-root/--fast-provide-wait) client/rpc changes: - dag.go: add Import() method (~70 lines) - dag_test.go: 4 test cases for Import (new file) - routing.go: add ProvideStats() method (~25 lines) - routing_test.go: 3 test cases for ProvideStats (new file) to enable RPC client, refactored commands to use CoreAPI: - add ProvideStats() to RoutingAPI interface and implementation - add Import() to APIDagService interface and implementation - commands delegate to CoreAPI (provide.go, dag/import.go) tests: all 7 new RPC client tests passing
1 parent 7c5db10 commit 6e89063

File tree

10 files changed

+617
-213
lines changed

10 files changed

+617
-213
lines changed

client/rpc/routing.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88

99
"github.com/ipfs/boxo/path"
10+
iface "github.com/ipfs/kubo/core/coreiface"
1011
"github.com/ipfs/kubo/core/coreiface/options"
1112
"github.com/libp2p/go-libp2p/core/peer"
1213
"github.com/libp2p/go-libp2p/core/routing"
@@ -156,6 +157,31 @@ func (api *RoutingAPI) Provide(ctx context.Context, p path.Path, opts ...options
156157
Exec(ctx, nil)
157158
}
158159

160+
func (api *RoutingAPI) ProvideStats(ctx context.Context, opts ...options.RoutingProvideStatOption) (*iface.ProvideStatsResponse, error) {
161+
options, err := options.RoutingProvideStatOptions(opts...)
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
resp, err := api.core().Request("provide/stat").
167+
Option("lan", options.UseLAN).
168+
Send(ctx)
169+
if err != nil {
170+
return nil, err
171+
}
172+
if resp.Error != nil {
173+
return nil, resp.Error
174+
}
175+
defer resp.Close()
176+
177+
var out iface.ProvideStatsResponse
178+
if err := json.NewDecoder(resp.Output).Decode(&out); err != nil {
179+
return nil, err
180+
}
181+
182+
return &out, nil
183+
}
184+
159185
func (api *RoutingAPI) core() *HttpApi {
160186
return (*HttpApi)(api)
161187
}

client/rpc/routing_test.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package rpc
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"testing"
7+
8+
boxoprovider "github.com/ipfs/boxo/provider"
9+
iface "github.com/ipfs/kubo/core/coreiface"
10+
"github.com/ipfs/kubo/core/coreiface/options"
11+
"github.com/ipfs/kubo/test/cli/harness"
12+
"github.com/libp2p/go-libp2p-kad-dht/provider/stats"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// Compile-time check: ensure our response type is compatible with kubo's provideStats
17+
// This verifies that JSON marshaling/unmarshaling will work correctly
18+
var _ = func() {
19+
// Create instance of command's provideStats structure
20+
cmdStats := struct {
21+
Sweep *stats.Stats `json:"Sweep,omitempty"`
22+
Legacy *boxoprovider.ReproviderStats `json:"Legacy,omitempty"`
23+
FullRT bool `json:"FullRT,omitempty"`
24+
}{}
25+
26+
// Marshal and unmarshal to verify compatibility
27+
data, _ := json.Marshal(cmdStats)
28+
var ifaceStats iface.ProvideStatsResponse
29+
_ = json.Unmarshal(data, &ifaceStats)
30+
}
31+
32+
// testProvideStats mirrors the subset of fields we verify in tests.
33+
// Intentionally independent from coreiface types to detect breaking changes.
34+
type testProvideStats struct {
35+
Sweep *struct {
36+
Connectivity struct {
37+
Status string `json:"status"`
38+
} `json:"connectivity"`
39+
Queues struct {
40+
PendingKeyProvides int `json:"pending_key_provides"`
41+
} `json:"queues"`
42+
Schedule struct {
43+
Keys int `json:"keys"`
44+
} `json:"schedule"`
45+
} `json:"Sweep,omitempty"`
46+
Legacy *struct {
47+
TotalReprovides uint64 `json:"TotalReprovides"`
48+
} `json:"Legacy,omitempty"`
49+
}
50+
51+
func TestProvideStats_WithSweepProvider(t *testing.T) {
52+
t.Parallel()
53+
54+
ctx := context.Background()
55+
h := harness.NewT(t)
56+
node := h.NewNode().Init()
57+
58+
// Explicitly enable Sweep provider (default in v0.39)
59+
node.SetIPFSConfig("Provide.DHT.SweepEnabled", true)
60+
node.SetIPFSConfig("Provide.Enabled", true)
61+
62+
node.StartDaemon()
63+
defer node.StopDaemon()
64+
65+
apiMaddr, err := node.TryAPIAddr()
66+
require.NoError(t, err)
67+
68+
api, err := NewApi(apiMaddr)
69+
require.NoError(t, err)
70+
71+
// Get provide stats
72+
result, err := api.Routing().ProvideStats(ctx)
73+
require.NoError(t, err)
74+
require.NotNil(t, result)
75+
76+
// Verify Sweep stats are present, Legacy is not
77+
require.NotNil(t, result.Sweep, "Sweep provider should return Sweep stats")
78+
require.Nil(t, result.Legacy, "Sweep provider should not return Legacy stats")
79+
80+
// Marshal to JSON and unmarshal to test struct to verify structure
81+
data, err := json.Marshal(result)
82+
require.NoError(t, err)
83+
84+
var testStats testProvideStats
85+
err = json.Unmarshal(data, &testStats)
86+
require.NoError(t, err)
87+
88+
// Verify key fields exist and have reasonable values
89+
require.NotNil(t, testStats.Sweep)
90+
require.NotEmpty(t, testStats.Sweep.Connectivity.Status, "connectivity status should be present")
91+
require.GreaterOrEqual(t, testStats.Sweep.Queues.PendingKeyProvides, 0, "queue size should be non-negative")
92+
require.GreaterOrEqual(t, testStats.Sweep.Schedule.Keys, 0, "scheduled keys should be non-negative")
93+
}
94+
95+
func TestProvideStats_WithLegacyProvider(t *testing.T) {
96+
t.Parallel()
97+
98+
ctx := context.Background()
99+
h := harness.NewT(t)
100+
node := h.NewNode().Init()
101+
102+
// Explicitly disable Sweep to use Legacy provider
103+
node.SetIPFSConfig("Provide.DHT.SweepEnabled", false)
104+
node.SetIPFSConfig("Provide.Enabled", true)
105+
106+
node.StartDaemon()
107+
defer node.StopDaemon()
108+
109+
apiMaddr, err := node.TryAPIAddr()
110+
require.NoError(t, err)
111+
112+
api, err := NewApi(apiMaddr)
113+
require.NoError(t, err)
114+
115+
// Get provide stats
116+
result, err := api.Routing().ProvideStats(ctx)
117+
require.NoError(t, err)
118+
require.NotNil(t, result)
119+
120+
// Verify Legacy stats are present, Sweep is not
121+
require.Nil(t, result.Sweep, "Legacy provider should not return Sweep stats")
122+
require.NotNil(t, result.Legacy, "Legacy provider should return Legacy stats")
123+
124+
// Marshal to JSON and unmarshal to test struct to verify structure
125+
data, err := json.Marshal(result)
126+
require.NoError(t, err)
127+
128+
var testStats testProvideStats
129+
err = json.Unmarshal(data, &testStats)
130+
require.NoError(t, err)
131+
132+
// Verify Legacy field exists
133+
require.NotNil(t, testStats.Legacy)
134+
require.GreaterOrEqual(t, testStats.Legacy.TotalReprovides, uint64(0), "total reprovides should be non-negative")
135+
}
136+
137+
func TestProvideStats_LANFlagErrorWithLegacy(t *testing.T) {
138+
t.Parallel()
139+
140+
ctx := context.Background()
141+
h := harness.NewT(t)
142+
node := h.NewNode().Init()
143+
144+
// Use Legacy provider - LAN flag should error
145+
node.SetIPFSConfig("Provide.DHT.SweepEnabled", false)
146+
node.SetIPFSConfig("Provide.Enabled", true)
147+
148+
node.StartDaemon()
149+
defer node.StopDaemon()
150+
151+
apiMaddr, err := node.TryAPIAddr()
152+
require.NoError(t, err)
153+
154+
api, err := NewApi(apiMaddr)
155+
require.NoError(t, err)
156+
157+
// Try to get LAN stats with Legacy provider
158+
// This should return an error
159+
_, err = api.Routing().ProvideStats(ctx, options.Routing.UseLAN(true))
160+
require.Error(t, err, "LAN flag should error with Legacy provider")
161+
require.Contains(t, err.Error(), "LAN stats only available for Sweep provider with Dual DHT",
162+
"error should indicate LAN stats unavailable")
163+
}

0 commit comments

Comments
 (0)