diff --git a/.gitignore b/.gitignore index 0424c1f24d2..a1d152294e1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ /lotus-fountain /lotus-stats /lotus-bench +/lotus-wallet +/lotus-pcr /bench.json /lotuspond/front/node_modules /lotuspond/front/build diff --git a/chain/wallet/interface.go b/api/api_wallet.go similarity index 84% rename from chain/wallet/interface.go rename to api/api_wallet.go index 9dc95a32cce..06b00a75b8e 100644 --- a/chain/wallet/interface.go +++ b/api/api_wallet.go @@ -1,15 +1,14 @@ -package wallet +package api import ( "context" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/specs-actors/actors/crypto" ) -type Wallet interface { +type WalletAPI interface { WalletNew(context.Context, crypto.SigType) (address.Address, error) WalletHas(context.Context, address.Address) (bool, error) WalletList(context.Context) ([]address.Address, error) @@ -20,8 +19,3 @@ type Wallet interface { WalletImport(context.Context, *types.KeyInfo) (address.Address, error) WalletDelete(context.Context, address.Address) error } - -type Default interface { - GetDefault() (address.Address, error) - SetDefault(a address.Address) error -} diff --git a/api/apistruct/permissioned.go b/api/apistruct/permissioned.go index c936627334b..86902d31b29 100644 --- a/api/apistruct/permissioned.go +++ b/api/apistruct/permissioned.go @@ -36,3 +36,9 @@ func PermissionedWorkerAPI(a api.WorkerAPI) api.WorkerAPI { auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal) return &out } + +func PermissionedWalletAPI(a api.WalletAPI) api.WalletAPI { + var out WalletStruct + auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal) + return &out +} diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index dff614001f2..5eb757c7bc3 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -336,6 +336,19 @@ type WorkerStruct struct { } } +type WalletStruct struct { + Internal struct { + WalletNew func(context.Context, crypto.SigType) (address.Address, error) `perm:"write"` + WalletHas func(context.Context, address.Address) (bool, error) `perm:"write"` + WalletList func(context.Context) ([]address.Address, error) `perm:"write"` + WalletSign func(context.Context, address.Address, []byte) (*crypto.Signature, error) `perm:"sign"` + WalletSignMessage func(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) `perm:"sign"` + WalletExport func(context.Context, address.Address) (*types.KeyInfo, error) `perm:"admin"` + WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"` + WalletDelete func(context.Context, address.Address) error `perm:"write"` + } +} + // CommonStruct func (c *CommonStruct) AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) { @@ -1271,7 +1284,40 @@ func (w *WorkerStruct) Closing(ctx context.Context) (<-chan struct{}, error) { return w.Internal.Closing(ctx) } +func (c *WalletStruct) WalletNew(ctx context.Context, typ crypto.SigType) (address.Address, error) { + return c.Internal.WalletNew(ctx, typ) +} + +func (c *WalletStruct) WalletHas(ctx context.Context, addr address.Address) (bool, error) { + return c.Internal.WalletHas(ctx, addr) +} + +func (c *WalletStruct) WalletList(ctx context.Context) ([]address.Address, error) { + return c.Internal.WalletList(ctx) +} + +func (c *WalletStruct) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { + return c.Internal.WalletSign(ctx, k, msg) +} + +func (c *WalletStruct) WalletSignMessage(ctx context.Context, k address.Address, msg *types.Message) (*types.SignedMessage, error) { + return c.Internal.WalletSignMessage(ctx, k, msg) +} + +func (c *WalletStruct) WalletExport(ctx context.Context, a address.Address) (*types.KeyInfo, error) { + return c.Internal.WalletExport(ctx, a) +} + +func (c *WalletStruct) WalletImport(ctx context.Context, ki *types.KeyInfo) (address.Address, error) { + return c.Internal.WalletImport(ctx, ki) +} + +func (c *WalletStruct) WalletDelete(ctx context.Context, addr address.Address) error { + return c.Internal.WalletDelete(ctx, addr) +} + var _ api.Common = &CommonStruct{} var _ api.FullNode = &FullNodeStruct{} var _ api.StorageMiner = &StorageMinerStruct{} var _ api.WorkerAPI = &WorkerStruct{} +var _ api.WalletAPI = &WalletStruct{} diff --git a/api/client/client.go b/api/client/client.go index cd915acf049..6e624bdd3a7 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -82,3 +82,15 @@ func NewWorkerRPC(ctx context.Context, addr string, requestHeader http.Header) ( return &res, closer, err } + +func NewWalletRPC(ctx context.Context, addr string, requestHeader http.Header) (api.WalletAPI, jsonrpc.ClientCloser, error) { + var res apistruct.WalletStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + []interface{}{ + &res.Internal, + }, + requestHeader, + ) + + return &res, closer, err +} diff --git a/chain/wallet/remotewallet/remote.go b/chain/wallet/remotewallet/remote.go new file mode 100644 index 00000000000..712f270b499 --- /dev/null +++ b/chain/wallet/remotewallet/remote.go @@ -0,0 +1,51 @@ +package remotewallet + +import ( + "context" + "go.uber.org/fx" + "golang.org/x/xerrors" + "net/http" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/node/modules/helpers" +) + +type RemoteWallet struct { + api.WalletAPI +} + +func SetupRemoteWallet(url string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle) (*RemoteWallet, error) { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle) (*RemoteWallet, error) { + /*sp := strings.SplitN(env, ":", 2) + if len(sp) != 2 { + log.Warnf("invalid env(%s) value, missing token or address", envKey) + } else { + ma, err := multiaddr.NewMultiaddr(sp[1]) + if err != nil { + return APIInfo{}, xerrors.Errorf("could not parse multiaddr from env(%s): %w", envKey, err) + } + return APIInfo{ + Addr: ma, + Token: []byte(sp[0]), + }, nil + }*/ + + headers := http.Header{} + /*headers.Add("Authorization", "Bearer "+token)*/ + + wapi, closer, err := client.NewWalletRPC(mctx, url, headers) + if err != nil { + return nil, xerrors.Errorf("creating jsonrpc client: %w", err) + } + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + closer() + return nil + }, + }) + + return &RemoteWallet{wapi}, nil + } +} \ No newline at end of file diff --git a/chain/wallet/wallet.go b/chain/wallet/wallet.go index 7fdea89637c..bcc8d07b10d 100644 --- a/chain/wallet/wallet.go +++ b/chain/wallet/wallet.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures _ "github.com/filecoin-project/lotus/lib/sigs/secp" // enable secp signatures @@ -36,6 +37,11 @@ type LocalWallet struct { lk sync.Mutex } +type Default interface { + GetDefault() (address.Address, error) + SetDefault(a address.Address) error +} + func NewWallet(keystore types.KeyStore) (*LocalWallet, error) { w := &LocalWallet{ keys: make(map[address.Address]*Key), @@ -250,4 +256,4 @@ func (w *LocalWallet) WalletDelete(ctx context.Context, addr address.Address) er return nil } -var _ Wallet = &LocalWallet{} +var _ api.WalletAPI = &LocalWallet{} diff --git a/cmd/lotus-wallet/logged.go b/cmd/lotus-wallet/logged.go new file mode 100644 index 00000000000..e73daae90dc --- /dev/null +++ b/cmd/lotus-wallet/logged.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +type LoggedWallet struct { + under api.WalletAPI +} + +func (c *LoggedWallet) WalletNew(ctx context.Context, typ crypto.SigType) (address.Address, error) { + n, err := typ.Name() + if err != nil { + return address.Address{}, err + } + + log.Infow("WalletNew", "type", n) + + return c.under.WalletNew(ctx, typ) +} + +func (c *LoggedWallet) WalletHas(ctx context.Context, addr address.Address) (bool, error) { + log.Infow("WalletHas", "address", addr) + + return c.under.WalletHas(ctx, addr) +} + +func (c *LoggedWallet) WalletList(ctx context.Context) ([]address.Address, error) { + log.Infow("WalletList") + + return c.under.WalletList(ctx) +} + +func (c *LoggedWallet) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { + log.Infow("WalletSign", "address", k) + + return c.under.WalletSign(ctx, k, msg) +} + +func (c *LoggedWallet) WalletSignMessage(ctx context.Context, k address.Address, msg *types.Message) (*types.SignedMessage, error) { + log.Infow("WalletSignMessage", "address", k) + + return c.under.WalletSignMessage(ctx, k, msg) +} + +func (c *LoggedWallet) WalletExport(ctx context.Context, a address.Address) (*types.KeyInfo, error) { + log.Infow("WalletExport", "address", a) + + return c.under.WalletExport(ctx, a) +} + +func (c *LoggedWallet) WalletImport(ctx context.Context, ki *types.KeyInfo) (address.Address, error) { + log.Infow("WalletImport", "type", ki.Type) + + return c.under.WalletImport(ctx, ki) +} + +func (c *LoggedWallet) WalletDelete(ctx context.Context, addr address.Address) error { + log.Infow("WalletDelete", "address", addr) + + return c.under.WalletDelete(ctx, addr) +} \ No newline at end of file diff --git a/cmd/lotus-wallet/main.go b/cmd/lotus-wallet/main.go new file mode 100644 index 00000000000..ae3580a59c8 --- /dev/null +++ b/cmd/lotus-wallet/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "context" + "net" + "net/http" + "os" + + "github.com/gorilla/mux" + logging "github.com/ipfs/go-log/v2" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/wallet" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/lib/lotuslog" + "github.com/filecoin-project/lotus/node/repo" +) + +var log = logging.Logger("main") + +const FlagWalletRepo = "wallet-repo" + +func main() { + lotuslog.SetupLogLevels() + + local := []*cli.Command{ + runCmd, + } + + app := &cli.App{ + Name: "lotus-wallet", + Usage: "Basic external wallet", + Version: build.UserVersion(), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: FlagWalletRepo, + EnvVars: []string{"WALLET_PATH"}, + Value: "~/.lotuswallet", // TODO: Consider XDG_DATA_HOME + }, + }, + + Commands: local, + } + app.Setup() + + if err := app.Run(os.Args); err != nil { + log.Warnf("%+v", err) + return + } +} + +var runCmd = &cli.Command{ + Name: "run", + Usage: "Start lotus wallet", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "listen", + Usage: "host address and port the wallet api will listen on", + Value: "0.0.0.0:1777", + }, + }, + Action: func(cctx *cli.Context) error { + log.Info("Starting lotus wallet") + + ctx := lcli.ReqContext(cctx) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + repoPath := cctx.String(FlagWalletRepo) + r, err := repo.NewFS(repoPath) + if err != nil { + return err + } + + ok, err := r.Exists() + if err != nil { + return err + } + if !ok { + if err := r.Init(repo.Worker); err != nil { + return err + } + } + + lr, err := r.Lock(repo.Wallet) + if err != nil { + return err + } + + ks, err := lr.KeyStore() + if err != nil { + return err + } + + w, err := wallet.NewWallet(ks) + if err != nil { + return err + } + + address := cctx.String("listen") + mux := mux.NewRouter() + + log.Info("Setting up API endpoint at " + address) + + rpcServer := jsonrpc.NewServer() + rpcServer.Register("Filecoin", &LoggedWallet{under: w}) + + mux.Handle("/rpc/v0", rpcServer) + mux.PathPrefix("/").Handler(http.DefaultServeMux) // pprof + + /*ah := &auth.Handler{ + Verify: nodeApi.AuthVerify, + Next: mux.ServeHTTP, + }*/ + + srv := &http.Server{ + Handler: mux, + BaseContext: func(listener net.Listener) context.Context { + return ctx + }, + } + + 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) + } + log.Warn("Graceful shutdown successful") + }() + + nl, err := net.Listen("tcp", address) + if err != nil { + return err + } + + return srv.Serve(nl) + }, +} diff --git a/go.sum b/go.sum index 0c525579e1d..ea7b3711e63 100644 --- a/go.sum +++ b/go.sum @@ -65,9 +65,11 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -1867,6 +1869,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/node/builder.go b/node/builder.go index 7c3251df2a7..b653fa429b3 100644 --- a/node/builder.go +++ b/node/builder.go @@ -40,6 +40,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/chain/wallet/remotewallet" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/extern/sector-storage/stores" @@ -235,7 +236,7 @@ func Online() Option { Override(new(*store.ChainStore), modules.ChainStore), Override(new(*stmgr.StateManager), stmgr.NewStateManager), Override(new(*wallet.LocalWallet), wallet.NewWallet), - Override(new(wallet.Wallet), From(new(*wallet.LocalWallet))), + Override(new(api.WalletAPI), From(new(*wallet.LocalWallet))), Override(new(wallet.Default), From(new(*wallet.LocalWallet))), Override(new(dtypes.ChainGCLocker), blockstore.NewGCLocker), @@ -419,6 +420,9 @@ func ConfigFullNode(c interface{}) Option { If(cfg.Metrics.HeadNotifs, Override(HeadMetricsKey, metrics.SendHeadNotifs(cfg.Metrics.Nickname)), ), + If(cfg.Wallet.RemoteBackend != "", + Override(new(api.WalletAPI), remotewallet.SetupRemoteWallet(cfg.Wallet.RemoteBackend)), + ), ) } diff --git a/node/config/def.go b/node/config/def.go index 9fee8895af5..f5071ada57e 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -22,6 +22,7 @@ type FullNode struct { Common Client Client Metrics Metrics + Wallet Wallet } // // Common @@ -105,6 +106,10 @@ type Client struct { IpfsUseForRetrieval bool } +type Wallet struct { + RemoteBackend string +} + func defCommon() Common { return Common{ API: API{ diff --git a/node/impl/full/wallet.go b/node/impl/full/wallet.go index 22b67473737..263452cf31c 100644 --- a/node/impl/full/wallet.go +++ b/node/impl/full/wallet.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/abi/big" "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" @@ -21,7 +22,7 @@ type WalletAPI struct { StateManager *stmgr.StateManager Default wallet.Default - wallet.Wallet + api.WalletAPI } func (a *WalletAPI) WalletBalance(ctx context.Context, addr address.Address) (types.BigInt, error) { @@ -50,7 +51,7 @@ func (a *WalletAPI) WalletSignMessage(ctx context.Context, k address.Address, ms if err != nil { return nil, xerrors.Errorf("failed to resolve ID address: %w", keyAddr) } - return a.Wallet.WalletSignMessage(ctx, keyAddr, msg) + return a.WalletAPI.WalletSignMessage(ctx, keyAddr, msg) } func (a *WalletAPI) WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *crypto.Signature) bool { diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 14085d4ac2e..2b7fa9ed338 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -44,6 +44,7 @@ const ( FullNode RepoType = iota StorageMiner Worker + Wallet ) func defConfForType(t RepoType) interface{} { @@ -54,6 +55,8 @@ func defConfForType(t RepoType) interface{} { return config.DefaultStorageMiner() case Worker: return &struct{}{} + case Wallet: + return &struct{}{} default: panic(fmt.Sprintf("unknown RepoType(%d)", int(t))) } @@ -87,6 +90,12 @@ func (fsr *FsRepo) Exists() (bool, error) { notexist := os.IsNotExist(err) if notexist { err = nil + + _, err = os.Stat(filepath.Join(fsr.path, fsKeystore)) + notexist = os.IsNotExist(err) + if notexist { + err = nil + } } return !notexist, err }