167 lines
4.1 KiB
Go
167 lines
4.1 KiB
Go
|
package cli
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"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"
|
||
|
)
|
||
|
|
||
|
//go:generate go run github.com/golang/mock/mockgen -destination=servicesmock_test.go -package=cli -self_package github.com/filecoin-project/lotus/cli . ServicesAPI
|
||
|
|
||
|
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
|
||
|
// parameters 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 *uint64
|
||
|
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
|
||
|
|
||
|
var ErrSendBalanceTooLow = errors.New("balance too low")
|
||
|
|
||
|
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,
|
||
|
|
||
|
Method: params.Method,
|
||
|
Params: params.Params,
|
||
|
}
|
||
|
|
||
|
if params.GasPremium != nil {
|
||
|
msg.GasPremium = *params.GasPremium
|
||
|
} else {
|
||
|
msg.GasPremium = types.NewInt(0)
|
||
|
}
|
||
|
if params.GasFeeCap != nil {
|
||
|
msg.GasFeeCap = *params.GasFeeCap
|
||
|
} else {
|
||
|
msg.GasFeeCap = types.NewInt(0)
|
||
|
}
|
||
|
if params.GasLimit != nil {
|
||
|
msg.GasLimit = *params.GasLimit
|
||
|
} else {
|
||
|
msg.GasLimit = 0
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
return cid.Undef, xerrors.Errorf("From balance %s less than total cost %s: %w", types.FIL(fromBalance), types.FIL(totalCost), ErrSendBalanceTooLow)
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if params.Nonce != nil {
|
||
|
msg.Nonce = *params.Nonce
|
||
|
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
|
||
|
}
|