278 lines
7.7 KiB
Go
278 lines
7.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-jsonrpc"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/consensus"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
)
|
|
|
|
//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 {
|
|
FullNodeAPI() api.FullNode
|
|
|
|
GetBaseFee(ctx context.Context) (abi.TokenAmount, error)
|
|
|
|
// MessageForSend creates a prototype of a message based on SendParams
|
|
MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, 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)
|
|
|
|
RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error)
|
|
|
|
// PublishMessage takes in a message prototype and publishes it
|
|
// before publishing the message, it runs checks on the node, message and mpool to verify that
|
|
// message is valid and won't be stuck.
|
|
// if `force` is true, it skips the checks
|
|
PublishMessage(ctx context.Context, prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error)
|
|
|
|
LocalAddresses(ctx context.Context) (address.Address, []address.Address, error)
|
|
|
|
MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool, tsk types.TipSetKey) ([]*types.SignedMessage, error)
|
|
MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, 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) FullNodeAPI() api.FullNode {
|
|
return s.api
|
|
}
|
|
|
|
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) GetBaseFee(ctx context.Context) (abi.TokenAmount, error) {
|
|
// not used but useful
|
|
|
|
ts, err := s.api.ChainHead(ctx)
|
|
if err != nil {
|
|
return big.Zero(), xerrors.Errorf("getting head: %w", err)
|
|
}
|
|
return ts.MinTicketBlock().ParentBaseFee, 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 := consensus.NewActorRegistry().Methods[act.Code][method] // TODO: use remote map
|
|
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 CheckInfo struct {
|
|
MessageTie cid.Cid
|
|
CurrentMessageTie bool
|
|
|
|
Check api.MessageCheckStatus
|
|
}
|
|
|
|
var ErrCheckFailed = fmt.Errorf("check has failed")
|
|
|
|
func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error) {
|
|
var outChecks [][]api.MessageCheckStatus
|
|
checks, err := s.api.MpoolCheckMessages(ctx, []*api.MessagePrototype{prototype})
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("message check: %w", err)
|
|
}
|
|
outChecks = append(outChecks, checks...)
|
|
|
|
checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.Message.From)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("pending mpool check: %w", err)
|
|
}
|
|
outChecks = append(outChecks, checks...)
|
|
|
|
return outChecks, nil
|
|
}
|
|
|
|
// PublishMessage modifies prototype to include gas estimation
|
|
// Errors with ErrCheckFailed if any of the checks fail
|
|
// First group of checks is related to the message prototype
|
|
func (s *ServicesImpl) PublishMessage(ctx context.Context,
|
|
prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) {
|
|
|
|
gasedMsg, err := s.api.GasEstimateMessageGas(ctx, &prototype.Message, nil, types.EmptyTSK)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("estimating gas: %w", err)
|
|
}
|
|
prototype.Message = *gasedMsg
|
|
|
|
if !force {
|
|
checks, err := s.RunChecksForPrototype(ctx, prototype)
|
|
if err != nil {
|
|
return nil, nil, xerrors.Errorf("running checks: %w", err)
|
|
}
|
|
for _, chks := range checks {
|
|
for _, c := range chks {
|
|
if !c.OK {
|
|
return nil, checks, ErrCheckFailed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if prototype.ValidNonce {
|
|
sm, err := s.api.WalletSignMessage(ctx, prototype.Message.From, &prototype.Message)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
_, err = s.api.MpoolPush(ctx, sm)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return sm, nil, nil
|
|
}
|
|
|
|
sm, err := s.api.MpoolPushMessage(ctx, &prototype.Message, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return sm, nil, 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
|
|
}
|
|
|
|
func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error) {
|
|
if params.From == address.Undef {
|
|
defaddr, err := s.api.WalletDefaultAddress(ctx)
|
|
if err != nil {
|
|
return nil, 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
|
|
}
|
|
validNonce := false
|
|
if params.Nonce != nil {
|
|
msg.Nonce = *params.Nonce
|
|
validNonce = true
|
|
}
|
|
|
|
prototype := &api.MessagePrototype{
|
|
Message: msg,
|
|
ValidNonce: validNonce,
|
|
}
|
|
return prototype, nil
|
|
}
|
|
|
|
func (s *ServicesImpl) MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool,
|
|
tsk types.TipSetKey) ([]*types.SignedMessage, error) {
|
|
msgs, err := s.api.MpoolPending(ctx, types.EmptyTSK)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("getting pending messages: %w", err)
|
|
}
|
|
out := []*types.SignedMessage{}
|
|
for _, sm := range msgs {
|
|
if filter(sm) {
|
|
out = append(out, sm)
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (s *ServicesImpl) LocalAddresses(ctx context.Context) (address.Address, []address.Address, error) {
|
|
def, err := s.api.WalletDefaultAddress(ctx)
|
|
if err != nil {
|
|
return address.Undef, nil, xerrors.Errorf("getting default addr: %w", err)
|
|
}
|
|
|
|
all, err := s.api.WalletList(ctx)
|
|
if err != nil {
|
|
return address.Undef, nil, xerrors.Errorf("getting list of addrs: %w", err)
|
|
}
|
|
|
|
return def, all, nil
|
|
}
|
|
|
|
func (s *ServicesImpl) MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, error) {
|
|
checks, err := s.api.MpoolCheckPendingMessages(ctx, a)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("pending mpool check: %w", err)
|
|
}
|
|
return checks, nil
|
|
}
|