lotus/cmd/curio/rpc/rpc.go

288 lines
7.9 KiB
Go
Raw Normal View History

// Package rpc provides all direct access to this node.
2023-12-03 06:40:01 +00:00
package rpc
import (
"context"
"encoding/base64"
"encoding/json"
"net"
2023-12-03 06:40:01 +00:00
"net/http"
2024-01-20 14:52:38 +00:00
"net/url"
"os"
"time"
2023-12-03 06:40:01 +00:00
"github.com/gbrlsnchs/jwt/v3"
2023-12-03 06:40:01 +00:00
"github.com/gorilla/mux"
logging "github.com/ipfs/go-log/v2"
2024-02-11 13:08:54 +00:00
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"go.opencensus.io/tag"
2023-12-12 05:16:57 +00:00
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
2023-12-03 06:40:01 +00:00
2024-01-20 14:52:38 +00:00
"github.com/filecoin-project/go-address"
2023-12-03 06:40:01 +00:00
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-jsonrpc/auth"
"github.com/filecoin-project/lotus/api"
2024-02-11 13:08:54 +00:00
"github.com/filecoin-project/lotus/api/client"
cliutil "github.com/filecoin-project/lotus/cli/util"
"github.com/filecoin-project/lotus/cmd/curio/deps"
"github.com/filecoin-project/lotus/curiosrc/market"
"github.com/filecoin-project/lotus/curiosrc/web"
2023-12-03 06:40:01 +00:00
"github.com/filecoin-project/lotus/lib/rpcenc"
"github.com/filecoin-project/lotus/metrics"
2023-12-03 06:40:01 +00:00
"github.com/filecoin-project/lotus/metrics/proxy"
2024-02-11 13:08:54 +00:00
"github.com/filecoin-project/lotus/node/repo"
"github.com/filecoin-project/lotus/storage/paths"
2024-02-11 13:08:54 +00:00
"github.com/filecoin-project/lotus/storage/sealer/fsutil"
"github.com/filecoin-project/lotus/storage/sealer/storiface"
2023-12-03 06:40:01 +00:00
)
var log = logging.Logger("curio/rpc")
var permissioned = os.Getenv("LOTUS_DISABLE_AUTH_PERMISSIONED") != "1"
func CurioHandler(
2023-12-03 06:40:01 +00:00
authv func(ctx context.Context, token string) ([]auth.Permission, error),
remote http.HandlerFunc,
a api.Curio,
2023-12-03 06:40:01 +00:00
permissioned bool) http.Handler {
mux := mux.NewRouter()
readerHandler, readerServerOpt := rpcenc.ReaderParamDecoder()
rpcServer := jsonrpc.NewServer(jsonrpc.WithServerErrors(api.RPCErrors), readerServerOpt)
wapi := proxy.MetricedAPI[api.Curio, api.CurioStruct](a)
2023-12-03 06:40:01 +00:00
if permissioned {
wapi = api.PermissionedAPI[api.Curio, api.CurioStruct](wapi)
2023-12-03 06:40:01 +00:00
}
rpcServer.Register("Filecoin", wapi)
rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover")
mux.Handle("/rpc/v0", rpcServer)
mux.Handle("/rpc/streams/v0/push/{uuid}", readerHandler)
mux.PathPrefix("/remote").HandlerFunc(remote)
mux.PathPrefix("/").Handler(http.DefaultServeMux) // pprof
if !permissioned {
return mux
}
ah := &auth.Handler{
Verify: authv,
Next: mux.ServeHTTP,
}
return ah
}
type CurioAPI struct {
*deps.Deps
2024-02-08 15:28:34 +00:00
paths.SectorIndex
ShutdownChan chan struct{}
}
func (p *CurioAPI) Version(context.Context) (api.Version, error) {
return api.CurioAPIVersion0, nil
}
func (p *CurioAPI) StorageDetachLocal(ctx context.Context, path string) error {
2024-02-08 15:28:34 +00:00
path, err := homedir.Expand(path)
if err != nil {
return xerrors.Errorf("expanding local path: %w", err)
}
// check that we have the path opened
lps, err := p.LocalStore.Local(ctx)
if err != nil {
return xerrors.Errorf("getting local path list: %w", err)
}
var localPath *storiface.StoragePath
for _, lp := range lps {
if lp.LocalPath == path {
lp := lp // copy to make the linter happy
localPath = &lp
break
}
}
if localPath == nil {
return xerrors.Errorf("no local paths match '%s'", path)
}
// drop from the persisted storage.json
var found bool
if err := p.LocalPaths.SetStorage(func(sc *storiface.StorageConfig) {
out := make([]storiface.LocalPath, 0, len(sc.StoragePaths))
for _, storagePath := range sc.StoragePaths {
if storagePath.Path != path {
out = append(out, storagePath)
continue
}
found = true
}
sc.StoragePaths = out
}); err != nil {
return xerrors.Errorf("set storage config: %w", err)
}
if !found {
// maybe this is fine?
return xerrors.Errorf("path not found in storage.json")
}
// unregister locally, drop from sector index
return p.LocalStore.ClosePath(ctx, localPath.ID)
}
func (p *CurioAPI) StorageLocal(ctx context.Context) (map[storiface.ID]string, error) {
2024-02-08 15:28:34 +00:00
ps, err := p.LocalStore.Local(ctx)
if err != nil {
return nil, err
}
var out = make(map[storiface.ID]string)
for _, path := range ps {
out[path.ID] = path.LocalPath
}
return out, nil
}
func (p *CurioAPI) StorageStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) {
2024-02-14 14:27:34 +00:00
return p.Stor.FsStat(ctx, id)
2024-02-08 15:28:34 +00:00
}
func (p *CurioAPI) AllocatePieceToSector(ctx context.Context, maddr address.Address, piece api.PieceDealInfo, rawSize int64, source url.URL, header http.Header) (api.SectorOffset, error) {
di := market.NewPieceIngester(p.Deps.DB, p.Deps.Full)
2024-01-20 14:52:38 +00:00
return di.AllocatePieceToSector(ctx, maddr, piece, rawSize, source, header)
}
// Trigger shutdown
func (p *CurioAPI) Shutdown(context.Context) error {
close(p.ShutdownChan)
return nil
}
func (p *CurioAPI) StorageAddLocal(ctx context.Context, path string) error {
path, err := homedir.Expand(path)
if err != nil {
return xerrors.Errorf("expanding local path: %w", err)
}
if err := p.LocalStore.OpenPath(ctx, path); err != nil {
return xerrors.Errorf("opening local path: %w", err)
}
if err := p.LocalPaths.SetStorage(func(sc *storiface.StorageConfig) {
sc.StoragePaths = append(sc.StoragePaths, storiface.LocalPath{Path: path})
}); err != nil {
return xerrors.Errorf("get storage config: %w", err)
}
return nil
}
func (p *CurioAPI) LogList(ctx context.Context) ([]string, error) {
return logging.GetSubsystems(), nil
}
func (p *CurioAPI) LogSetLevel(ctx context.Context, subsystem, level string) error {
return logging.SetLogLevel(subsystem, level)
}
func ListenAndServe(ctx context.Context, dependencies *deps.Deps, shutdownChan chan struct{}) error {
fh := &paths.FetchHandler{Local: dependencies.LocalStore, PfHandler: &paths.DefaultPartialFileHandler{}}
remoteHandler := func(w http.ResponseWriter, r *http.Request) {
if !auth.HasPerm(r.Context(), nil, api.PermAdmin) {
w.WriteHeader(401)
_ = json.NewEncoder(w).Encode(struct{ Error string }{"unauthorized: missing admin permission"})
return
}
fh.ServeHTTP(w, r)
}
var authVerify func(context.Context, string) ([]auth.Permission, error)
{
privateKey, err := base64.StdEncoding.DecodeString(dependencies.Cfg.Apis.StorageRPCSecret)
if err != nil {
return xerrors.Errorf("decoding storage rpc secret: %w", err)
}
authVerify = func(ctx context.Context, token string) ([]auth.Permission, error) {
var payload deps.JwtPayload
if _, err := jwt.Verify([]byte(token), jwt.NewHS256(privateKey), &payload); err != nil {
return nil, xerrors.Errorf("JWT Verification failed: %w", err)
}
return payload.Allow, nil
}
}
// Serve the RPC.
srv := &http.Server{
Handler: CurioHandler(
authVerify,
remoteHandler,
&CurioAPI{dependencies, dependencies.Si, shutdownChan},
permissioned),
ReadHeaderTimeout: time.Minute * 3,
BaseContext: func(listener net.Listener) context.Context {
ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "lotus-worker"))
return ctx
},
2023-12-12 00:30:39 +00:00
Addr: dependencies.ListenAddr,
}
2023-12-12 05:16:57 +00:00
log.Infof("Setting up RPC server at %s", dependencies.ListenAddr)
eg := errgroup.Group{}
eg.Go(srv.ListenAndServe)
2023-12-12 05:16:57 +00:00
if dependencies.Cfg.Subsystems.EnableWebGui {
web, err := web.GetSrv(ctx, dependencies)
if err != nil {
return err
2023-12-12 05:16:57 +00:00
}
go func() {
<-ctx.Done()
log.Warn("Shutting down...")
if err := srv.Shutdown(context.TODO()); err != nil {
log.Errorf("shutting down RPC server failed: %s", err)
}
if err := web.Shutdown(context.Background()); err != nil {
log.Errorf("shutting down web server failed: %s", err)
}
log.Warn("Graceful shutdown successful")
}()
feat: curio: web based config edit (#11822) * cfg edit 1 * jsonschema deps * feat: lp mig - first few steps * lp mig: default tasks * code comments * docs * lp-mig-progress * shared * comments and todos * fix: curio: rename lotus-provider to curio (#11645) * rename provider to curio * install gotext * fix lint errors, mod tidy * fix typo * fix API_INFO and add gotext to circleCI * add back gotext * add gotext after remerge * lp: channels doc * finish easy-migration TODOs * out generate * merging and more renames * avoid make-all * minor doc stuff * cu: make gen * make gen fix * make gen * tryfix * go mod tidy * minor ez migration fixes * ez setup - ui cleanups * better error message * guided setup colors * better path to saveconfigtolayer * loadconfigwithupgrades fix * readMiner oops * guided - homedir * err if miner is running * prompt error should exit * process already running, miner_id sectors in migration * dont prompt for language a second time * check miner stopped * unlock repo * render and sql oops * curio easyMig - some fixes * easyMigration runs successfully * lint * part 2 of last * message * merge addtl * fixing guided setup for myself * warn-on-no-post * EditorLoads * cleanups and styles * create info * fix tests * make gen * change layout, add help button * Duration custom json * mjs naming --------- Co-authored-by: LexLuthr <88259624+LexLuthr@users.noreply.github.com> Co-authored-by: LexLuthr <lexluthr@protocol.ai> Co-authored-by: LexLuthr <lexluthr@curiostorage.org>
2024-04-16 14:30:27 +00:00
uiAddress := dependencies.Cfg.Subsystems.GuiAddress
if uiAddress == "" || uiAddress[0] == ':' {
uiAddress = "localhost" + uiAddress
}
log.Infof("GUI: http://%s", uiAddress)
eg.Go(web.ListenAndServe)
}
2023-12-12 05:16:57 +00:00
return eg.Wait()
}
func GetCurioAPI(ctx *cli.Context) (api.Curio, jsonrpc.ClientCloser, error) {
addr, headers, err := cliutil.GetRawAPI(ctx, repo.Curio, "v0")
if err != nil {
return nil, nil, err
}
u, err := url.Parse(addr)
if err != nil {
return nil, nil, xerrors.Errorf("parsing miner api URL: %w", err)
}
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
}
addr = u.String()
return client.NewCurioRpc(ctx.Context, addr, headers)
}