Skip to content

Commit 943a3bd

Browse files
committed
nodeapi + pdpwire: dial embedded Lantern over /rpc/v1 with self-minted token
Path A wiring. Rather than borrowing internal Lantern handler state or hand-rolling a TipSet synthesizer in curio-core, curio-core now talks to its embedded Lantern exactly like an external SP would: over standard Lotus-compatible JSON-RPC at /rpc/v1 with a JWT auth header. The JWT is self-issued at boot via daemon.AdminToken() (added in the matching lantern@12207e0 commit). No files on disk, no env vars; the token lifecycle is bounded by the daemon process. Changes: - internal/nodeapi/nodeapi.go: dials Lantern at d.RPCAddr()/rpc/v1 using lotus/api/client.NewFullNodeRPCV1 and the admin token. Exposes a typed *lotusapi.FullNode handle suitable for upstream curio/pdp's PDPServiceNodeApi interface (and ready for ethclient wiring next). - internal/pdpwire/pdpwire.go: when lantern is non-nil and Started, dial it via nodeapi.New and pass the FullNode handle to upstream PDPService as the PDPServiceNodeApi argument. Returns a closer for the RPC transport. - cmd/curio-core/main.go: sets RPCListen="127.0.0.1:0" on the embedded Lantern Config so an ephemeral loopback port is bound (no conflict with a standalone Lantern on the same box). Logs the resolved port for observability. defer pdpClose() releases the RPC transport on shutdown. Verified: - curio-core boots clean on Hetzner (calibration, epoch 3740991) - lantern rpc bound at http://127.0.0.1:42849/rpc/v1 (ephemeral) - Filecoin.ChainHead via direct curl returns a valid TipSet JSON - upload pipeline PUT 204 still works (no regression) Architectural win: every Filecoin.* and eth_* method Lantern adds is available to curio-core for free, same wire format as external deployments, single source of chain truth.
1 parent d94878c commit 943a3bd

5 files changed

Lines changed: 145 additions & 15 deletions

File tree

cmd/curio-core/main.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,18 @@ Flags:
274274
return fmt.Errorf("create wallet: %w", err)
275275
}
276276
lanternDaemon, err = lantern.New(lantern.Config{
277-
DataDir: *dataDir,
278-
Wallet: w,
279-
Gateway: *gateway,
280-
Network: *network,
277+
DataDir: *dataDir,
278+
Wallet: w,
279+
Gateway: *gateway,
280+
Network: *network,
281+
// Ephemeral loopback bind: embedded Lantern speaks /rpc/v1 to
282+
// in-process consumers (nodeapi, ethclient) only. We never expose
283+
// this port externally; nginx terminates client traffic at the
284+
// curio-core listener (14994) which composes /pdp/* over the
285+
// upstream PDPService that itself talks to Lantern through this
286+
// loopback. Port 0 avoids conflicts with a real standalone
287+
// Lantern on the same host.
288+
RPCListen: "127.0.0.1:0",
281289
NoLibp2p: true,
282290
EmbeddedMode: true,
283291
})
@@ -306,6 +314,9 @@ Flags:
306314
}
307315
tr := lanternDaemon.TrustedRoot()
308316
fmt.Printf(" lantern: anchored at epoch %d\n", tr.Epoch)
317+
if addr := lanternDaemon.RPCAddr(); addr != "" {
318+
fmt.Printf(" lantern: rpc at http://%s/rpc/v1 (in-process)\n", addr)
319+
}
309320
} else {
310321
fmt.Printf(" lantern: skipped (--no-lantern)\n")
311322
}
@@ -316,10 +327,12 @@ Flags:
316327
// /, /setup, /api/setup — curio-core's first-run WebUI flow
317328
pdpMux := chi.NewRouter()
318329
stashDir := filepath.Join(*dataDir, "stash")
319-
if _, err := pdpwire.Mount(rootCtx, pdpMux, eng.DB(), stashDir); err != nil {
330+
_, pdpClose, err := pdpwire.Mount(rootCtx, pdpMux, eng.DB(), stashDir, lanternDaemon)
331+
if err != nil {
320332
_ = eng.Stop()
321333
return fmt.Errorf("pdpwire.Mount: %w", err)
322334
}
335+
defer pdpClose()
323336
fmt.Printf(" pdp: /pdp/* routes mounted (stash %s)\n", stashDir)
324337
handler := pdpwire.FallbackHandler(pdpMux, setupweb.New(eng.DB()))
325338
srv := &http.Server{

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.26
44

55
require (
66
github.com/BurntSushi/toml v1.6.0
7-
github.com/Reiers/lantern v1.4.1-0.20260523140040-9f9f5097a0a4
7+
github.com/Reiers/lantern v1.4.1-0.20260523170605-12207e0bdf70
88
github.com/curiostorage/harmonyquery v1.0.2
99
github.com/ethereum/go-ethereum v1.17.2
1010
github.com/filecoin-project/curio v1.28.2-0.20260522154309-2153109777fa
@@ -14,6 +14,7 @@ require (
1414
github.com/filecoin-project/go-padreader v0.0.1
1515
github.com/filecoin-project/go-state-types v0.18.0
1616
github.com/filecoin-project/lotus v1.36.0
17+
github.com/filecoin-project/specs-actors/v2 v2.3.6
1718
github.com/georgysavva/scany/v2 v2.1.4
1819
github.com/go-chi/chi/v5 v5.2.5
1920
github.com/google/uuid v1.6.0
@@ -93,7 +94,6 @@ require (
9394
github.com/filecoin-project/go-statestore v0.2.0 // indirect
9495
github.com/filecoin-project/pubsub v1.0.0 // indirect
9596
github.com/filecoin-project/specs-actors v0.9.15 // indirect
96-
github.com/filecoin-project/specs-actors/v2 v2.3.6 // indirect
9797
github.com/filecoin-project/specs-actors/v3 v3.1.2 // indirect
9898
github.com/filecoin-project/specs-actors/v4 v4.0.2 // indirect
9999
github.com/filecoin-project/specs-actors/v5 v5.0.6 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ github.com/Reiers/lantern v1.2.2-0.20260522222332-9177a99e6581 h1:U/W2OtjHbw261Z
119119
github.com/Reiers/lantern v1.2.2-0.20260522222332-9177a99e6581/go.mod h1:EmEQBtIa/0YQY0RmbJhhqmwRelvsHmZ4079hj6WFHrk=
120120
github.com/Reiers/lantern v1.4.1-0.20260523140040-9f9f5097a0a4 h1:XyrbVO8m05TgBW6UsVuTlb5RokW+q/iMNXx7/MAqsEg=
121121
github.com/Reiers/lantern v1.4.1-0.20260523140040-9f9f5097a0a4/go.mod h1:EmEQBtIa/0YQY0RmbJhhqmwRelvsHmZ4079hj6WFHrk=
122+
github.com/Reiers/lantern v1.4.1-0.20260523170605-12207e0bdf70 h1:8trQ4ld5UGJMmJNZ7TJpESlr1Xf+YDvqWZJ9iEqWKJs=
123+
github.com/Reiers/lantern v1.4.1-0.20260523170605-12207e0bdf70/go.mod h1:EmEQBtIa/0YQY0RmbJhhqmwRelvsHmZ4079hj6WFHrk=
122124
github.com/Reiers/lotus v0.2.11-0.20260523003030-baf8b697b916 h1:U2r4SVfPwqmm3QJO5l5LCoNA7XnvQ3bks8DQmLMqHnw=
123125
github.com/Reiers/lotus v0.2.11-0.20260523003030-baf8b697b916/go.mod h1:tzFOxSSszbfgocCRMsAraa9C3Jj3iEIEiOY9Pf9CsmY=
124126
github.com/Reiers/lotus v0.2.11-0.20260523142958-39c6137c2ea8 h1:8csNjSUUbY6CJ78C4UzGPst7+LnwjwCwbKCYSdRr2gk=

internal/nodeapi/nodeapi.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Package nodeapi dials the embedded Lantern daemon over standard
2+
// Lotus-compatible JSON-RPC and exposes the parts curio-core wires
3+
// into upstream curio/pdp.
4+
//
5+
// Architecture: curio-core's embedded Lantern (pkg/daemon.Daemon)
6+
// mounts a /rpc/v1 HTTP endpoint exactly like a standalone Lantern
7+
// would. We talk to it the same way an external SP operator would
8+
// talk to a remote Lantern: through lotus/api/client with a JWT auth
9+
// header. Self-issued at boot via daemon.AdminToken().
10+
//
11+
// Benefits over hand-rolling TipSet construction or borrowing
12+
// internal handler state:
13+
//
14+
// - Single source of chain truth (no duplicate ChainHead synthesizer
15+
// that drifts from Lantern's).
16+
// - Every Filecoin.* and eth_* method Lantern adds in the future
17+
// becomes available to curio-core for free.
18+
// - Same wire format external operators consume; bugs surface
19+
// identically in both deployments.
20+
// - Standard lotus/api/client gives us a fully-typed FullNode handle.
21+
package nodeapi
22+
23+
import (
24+
"context"
25+
"fmt"
26+
"net/http"
27+
28+
lotusapi "github.com/filecoin-project/lotus/api"
29+
lotusclient "github.com/filecoin-project/lotus/api/client"
30+
"github.com/filecoin-project/go-jsonrpc"
31+
32+
lanterndaemon "github.com/Reiers/lantern/pkg/daemon"
33+
)
34+
35+
// Client is the curio-core handle to embedded Lantern's JSON-RPC.
36+
// Holds the underlying lotus FullNode RPC client + a Close function
37+
// that releases the HTTP transport.
38+
type Client struct {
39+
Full lotusapi.FullNode
40+
close jsonrpc.ClientCloser
41+
}
42+
43+
// New dials the embedded Lantern Daemon over its in-process
44+
// /rpc/v1 endpoint and self-issues an admin-scope JWT.
45+
//
46+
// Callers must call Close when done to release the HTTP transport.
47+
//
48+
// Returns an error if the Daemon hasn't mounted the RPC server yet
49+
// (Started() == false, or RPCListen was empty).
50+
func New(ctx context.Context, d *lanterndaemon.Daemon) (*Client, error) {
51+
if d == nil {
52+
return nil, fmt.Errorf("nodeapi.New: daemon is nil")
53+
}
54+
if !d.Started() {
55+
return nil, fmt.Errorf("nodeapi.New: daemon not started")
56+
}
57+
addr := d.RPCAddr()
58+
if addr == "" {
59+
return nil, fmt.Errorf("nodeapi.New: daemon has no RPC address (RPCListen empty?)")
60+
}
61+
tok := d.AdminToken()
62+
if tok == "" {
63+
return nil, fmt.Errorf("nodeapi.New: daemon admin token unavailable")
64+
}
65+
66+
hdr := http.Header{}
67+
hdr.Set("Authorization", "Bearer "+tok)
68+
69+
url := "http://" + addr + "/rpc/v1"
70+
full, closer, err := lotusclient.NewFullNodeRPCV1(ctx, url, hdr)
71+
if err != nil {
72+
return nil, fmt.Errorf("dial embedded lantern at %s: %w", url, err)
73+
}
74+
return &Client{Full: full, close: closer}, nil
75+
}
76+
77+
// Close releases the JSON-RPC transport. Safe to call multiple times.
78+
func (c *Client) Close() {
79+
if c.close != nil {
80+
c.close()
81+
c.close = nil
82+
}
83+
}
84+
85+
// FullNode returns the underlying lotus FullNode handle. Use this when
86+
// passing curio-core's nodeapi to subsystems that want the full Lotus
87+
// API surface (curio/pdp.PDPServiceNodeApi is one such consumer; it
88+
// only needs ChainHead but the upstream type expects the broader
89+
// interface).
90+
func (c *Client) FullNode() lotusapi.FullNode { return c.Full }

internal/pdpwire/pdpwire.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import (
3030

3131
"github.com/curiostorage/harmonyquery"
3232

33+
lanterndaemon "github.com/Reiers/lantern/pkg/daemon"
34+
3335
"github.com/Reiers/curio-core/internal/diskstash"
36+
"github.com/Reiers/curio-core/internal/nodeapi"
3437
)
3538

3639
// Compile-time guard: *diskstash.Store must satisfy paths.StashStore.
@@ -53,23 +56,45 @@ var _ curiopaths.StashStore = (*diskstash.Store)(nil)
5356
// proof submission) will return 5xx; the upload trio (/pdp/piece/uploads*)
5457
// works end-to-end and lands data on disk + a row in
5558
// pdp_piece_streaming_uploads.
56-
func Mount(ctx context.Context, r *chi.Mux, db harmonyquery.DBInterface, stashDir string) (*upstreampdp.PDPService, error) {
59+
// lantern is the embedded Lantern daemon. May be nil for tests or
60+
// the --no-lantern boot path; routes that need chain reads degrade
61+
// to 5xx in that mode.
62+
//
63+
// When lantern is non-nil and Started, Mount dials it over standard
64+
// /rpc/v1 with a self-minted admin token and wires the resulting
65+
// FullNode handle into upstream PDPService as the PDPServiceNodeApi.
66+
//
67+
// Returns a closer that releases the JSON-RPC transport; callers
68+
// should defer it for the lifetime of the daemon.
69+
func Mount(ctx context.Context, r *chi.Mux, db harmonyquery.DBInterface, stashDir string, lantern *lanterndaemon.Daemon) (*upstreampdp.PDPService, func(), error) {
5770
stash, err := diskstash.New(stashDir)
5871
if err != nil {
59-
return nil, err
72+
return nil, func() {}, err
73+
}
74+
var (
75+
nodeAPI upstreampdp.PDPServiceNodeApi
76+
closer = func() {}
77+
)
78+
if lantern != nil {
79+
c, err := nodeapi.New(ctx, lantern)
80+
if err != nil {
81+
return nil, func() {}, err
82+
}
83+
nodeAPI = c.FullNode()
84+
closer = c.Close
6085
}
6186
svc := upstreampdp.NewPDPService(
6287
ctx,
6388
db,
6489
stash,
65-
nil, // ethchain.EthClient — TODO: wire to embedded Lantern RPC
66-
nil, // PDPServiceNodeApi — TODO: wire to embedded Lantern RPC
67-
nil, // *message.SenderETH — TODO: calibration wallet signer
68-
nil, // *alertmanager.AlertTask — handlePing nil-checks this
69-
nil, // *ipni_provider.Provider — TODO: minimal IPNI publisher
90+
nil, // ethchain.EthClient — TODO: wire to lotus ethclient over same RPC
91+
nodeAPI, // PDPServiceNodeApi via embedded Lantern /rpc/v1
92+
nil, // *message.SenderETH — TODO: calibration wallet signer
93+
nil, // *alertmanager.AlertTask — handlePing nil-checks this
94+
nil, // *ipni_provider.Provider — TODO: minimal IPNI publisher
7095
)
7196
upstreampdp.Routes(r, svc)
72-
return svc, nil
97+
return svc, closer, nil
7398
}
7499

75100
// FallbackHandler returns an HTTP handler that serves the chi router

0 commit comments

Comments
 (0)