diff --git a/cli/cmd.go b/cli/cmd.go index 12ea76855..53ad11bc4 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -207,6 +207,19 @@ func GetFullNodeAPI(ctx *cli.Context) (api.FullNode, jsonrpc.ClientCloser, error return client.NewFullNodeRPC(ctx.Context, addr, headers) } +func GetFullNodeServices(ctx *cli.Context) (ServicesAPI, error) { + if tn, ok := ctx.App.Metadata["test-services"]; ok { + return tn.(ServicesAPI), nil + } + + api, c, err := GetFullNodeAPI(ctx) + if err != nil { + return nil, err + } + + return &ServicesImpl{api: api, closer: c}, nil +} + type GetStorageMinerOptions struct { PreferHttp bool } diff --git a/cli/send.go b/cli/send.go index 7cdb6e2bc..d0934f6b4 100644 --- a/cli/send.go +++ b/cli/send.go @@ -1,16 +1,12 @@ package cli import ( - "bytes" "context" "encoding/hex" - "encoding/json" "fmt" - "reflect" cid "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" - cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -18,7 +14,6 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) @@ -74,14 +69,14 @@ var sendCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() ctx := ReqContext(cctx) - var params sendParams + var params SendParams params.To, err = address.NewFromString(cctx.Args().Get(0)) if err != nil { @@ -119,7 +114,7 @@ var sendCmd = &cli.Command{ params.Method = abi.MethodNum(cctx.Uint64("method")) if cctx.IsSet("params-json") { - decparams, err := decodeTypedParams(ctx, api, params.To, params.Method, cctx.String("params-json")) + decparams, err := srv.DecodeTypedParamsFromJSON(ctx, params.To, params.Method, cctx.String("params-json")) if err != nil { return fmt.Errorf("failed to decode json params: %w", err) } @@ -135,6 +130,7 @@ var sendCmd = &cli.Command{ } params.Params = decparams } + params.Force = cctx.Bool("force") if cctx.IsSet("nonce") { @@ -142,7 +138,7 @@ var sendCmd = &cli.Command{ params.Nonce.N = cctx.Uint64("nonce") } - msgCid, err := send(ctx, api, params) + msgCid, err := srv.Send(ctx, params) if err != nil { return xerrors.Errorf("executing send: %w", err) @@ -161,105 +157,3 @@ type sendAPIs interface { WalletDefaultAddress(context.Context) (address.Address, error) WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) } - -type sendParams struct { - To address.Address - From address.Address - Val abi.TokenAmount - - GasPremium abi.TokenAmount - GasFeeCap abi.TokenAmount - GasLimit int64 - - Nonce struct { - N uint64 - Set bool - } - Method abi.MethodNum - Params []byte - - Force bool -} - -func send(ctx context.Context, api sendAPIs, params sendParams) (cid.Cid, error) { - if params.From == address.Undef { - defaddr, err := api.WalletDefaultAddress(ctx) - if err != nil { - return cid.Undef, err - } - params.From = defaddr - } - - msg := &types.Message{ - From: params.From, - To: params.To, - Value: params.Val, - - GasPremium: params.GasPremium, - GasFeeCap: params.GasFeeCap, - GasLimit: params.GasLimit, - - Method: params.Method, - Params: params.Params, - } - - if !params.Force { - // Funds insufficient check - fromBalance, err := api.WalletBalance(ctx, msg.From) - if err != nil { - return cid.Undef, err - } - totalCost := types.BigAdd(types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))), msg.Value) - - if fromBalance.LessThan(totalCost) { - fmt.Printf("WARNING: From balance %s less than total cost %s\n", types.FIL(fromBalance), types.FIL(totalCost)) - return cid.Undef, fmt.Errorf("--force must be specified for this action to have an effect; you have been warned") - } - } - - if params.Nonce.Set { - msg.Nonce = params.Nonce.N - sm, err := api.WalletSignMessage(ctx, params.From, msg) - if err != nil { - return cid.Undef, err - } - - _, err = api.MpoolPush(ctx, sm) - if err != nil { - return cid.Undef, err - } - - return sm.Cid(), nil - } - - sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, err - } - - return sm.Cid(), nil -} - -func decodeTypedParams(ctx context.Context, fapi api.FullNode, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) { - act, err := fapi.StateGetActor(ctx, to, types.EmptyTSK) - if err != nil { - return nil, err - } - - methodMeta, found := stmgr.MethodsMap[act.Code][method] - if !found { - return nil, fmt.Errorf("method %d not found on actor %s", method, act.Code) - } - - p := reflect.New(methodMeta.Params.Elem()).Interface().(cbg.CBORMarshaler) - - if err := json.Unmarshal([]byte(paramstr), p); err != nil { - return nil, fmt.Errorf("unmarshaling input into params type: %w", err) - } - - buf := new(bytes.Buffer) - if err := p.MarshalCBOR(buf); err != nil { - return nil, err - } - return buf.Bytes(), nil -} diff --git a/cli/services.go b/cli/services.go new file mode 100644 index 000000000..e4fb34476 --- /dev/null +++ b/cli/services.go @@ -0,0 +1,152 @@ +package cli + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/stmgr" + types "github.com/filecoin-project/lotus/chain/types" + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" +) + +type ServicesAPI interface { + // Sends executes a send given SendParams + Send(ctx context.Context, params SendParams) (cid.Cid, error) + // DecodeTypedParamsFromJSON takes in information needed to identify a method and converts JSON + // paramaters to bytes of their CBOR encoding + DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) + + // Close ends the session of services and disconnects from RPC, using Services after Close is called + // most likely will result in an error + // Should not be called concurrently + Close() error +} + +type ServicesImpl struct { + api api.FullNode + closer jsonrpc.ClientCloser +} + +func (s *ServicesImpl) Close() error { + if s.closer == nil { + return xerrors.Errorf("Services already closed") + } + s.closer() + s.closer = nil + return nil +} + +func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) { + act, err := s.api.StateGetActor(ctx, to, types.EmptyTSK) + if err != nil { + return nil, err + } + + methodMeta, found := stmgr.MethodsMap[act.Code][method] + if !found { + return nil, fmt.Errorf("method %d not found on actor %s", method, act.Code) + } + + p := reflect.New(methodMeta.Params.Elem()).Interface().(cbg.CBORMarshaler) + + if err := json.Unmarshal([]byte(paramstr), p); err != nil { + return nil, fmt.Errorf("unmarshaling input into params type: %w", err) + } + + buf := new(bytes.Buffer) + if err := p.MarshalCBOR(buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +type SendParams struct { + To address.Address + From address.Address + Val abi.TokenAmount + + GasPremium abi.TokenAmount + GasFeeCap abi.TokenAmount + GasLimit int64 + + Nonce struct { + N uint64 + Set bool + } + Method abi.MethodNum + Params []byte + + Force bool +} + +// This is specialised Send for Send command +// There might be room for generic Send that other commands can use to send their messages +// We will see + +func (s *ServicesImpl) Send(ctx context.Context, params SendParams) (cid.Cid, error) { + if params.From == address.Undef { + defaddr, err := s.api.WalletDefaultAddress(ctx) + if err != nil { + return cid.Undef, err + } + params.From = defaddr + } + + msg := &types.Message{ + From: params.From, + To: params.To, + Value: params.Val, + + GasPremium: params.GasPremium, + GasFeeCap: params.GasFeeCap, + GasLimit: params.GasLimit, + + Method: params.Method, + Params: params.Params, + } + + if !params.Force { + // Funds insufficient check + fromBalance, err := s.api.WalletBalance(ctx, msg.From) + if err != nil { + return cid.Undef, err + } + totalCost := types.BigAdd(types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))), msg.Value) + + if fromBalance.LessThan(totalCost) { + fmt.Printf("WARNING: From balance %s less than total cost %s\n", types.FIL(fromBalance), types.FIL(totalCost)) + return cid.Undef, fmt.Errorf("--force must be specified for this action to have an effect; you have been warned") + } + } + + if params.Nonce.Set { + msg.Nonce = params.Nonce.N + sm, err := s.api.WalletSignMessage(ctx, params.From, msg) + if err != nil { + return cid.Undef, err + } + + _, err = s.api.MpoolPush(ctx, sm) + if err != nil { + return cid.Undef, err + } + + return sm.Cid(), nil + } + + sm, err := s.api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return cid.Undef, err + } + + return sm.Cid(), nil +}