2021-02-23 14:50:47 +00:00
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"
2021-03-24 14:12:35 +00:00
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
2021-09-02 16:58:34 +00:00
"github.com/filecoin-project/lotus/chain/consensus/filcns"
2021-02-23 14:50:47 +00:00
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 {
2021-03-27 14:35:46 +00:00
FullNodeAPI ( ) api . FullNode
2021-03-24 14:12:35 +00:00
GetBaseFee ( ctx context . Context ) ( abi . TokenAmount , error )
// MessageForSend creates a prototype of a message based on SendParams
2021-03-27 14:35:46 +00:00
MessageForSend ( ctx context . Context , params SendParams ) ( * api . MessagePrototype , error )
2021-03-24 14:12:35 +00:00
2021-02-23 14:50:47 +00:00
// 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 )
2021-03-27 14:35:46 +00:00
RunChecksForPrototype ( ctx context . Context , prototype * api . MessagePrototype ) ( [ ] [ ] api . MessageCheckStatus , error )
2021-03-24 14:12:35 +00:00
// 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
2021-03-25 14:24:09 +00:00
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 )
2021-03-24 14:12:35 +00:00
2021-02-23 14:50:47 +00:00
// 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 {
2021-03-24 14:12:35 +00:00
api api . FullNode
2021-02-23 14:50:47 +00:00
closer jsonrpc . ClientCloser
}
2021-03-27 14:35:46 +00:00
func ( s * ServicesImpl ) FullNodeAPI ( ) api . FullNode {
return s . api
}
2021-02-23 14:50:47 +00:00
func ( s * ServicesImpl ) Close ( ) error {
if s . closer == nil {
return xerrors . Errorf ( "Services already closed" )
}
s . closer ( )
s . closer = nil
return nil
}
2021-03-24 14:12:35 +00:00
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
}
2021-02-23 14:50:47 +00:00
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
}
2021-09-02 16:58:34 +00:00
methodMeta , found := filcns . NewActorRegistry ( ) . Methods [ act . Code ] [ method ] // TODO: use remote map
2021-02-23 14:50:47 +00:00
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
}
2021-03-24 14:12:35 +00:00
type CheckInfo struct {
MessageTie cid . Cid
CurrentMessageTie bool
Check api . MessageCheckStatus
}
var ErrCheckFailed = fmt . Errorf ( "check has failed" )
2021-03-27 14:35:46 +00:00
func ( s * ServicesImpl ) RunChecksForPrototype ( ctx context . Context , prototype * api . MessagePrototype ) ( [ ] [ ] api . MessageCheckStatus , error ) {
2021-03-24 14:12:35 +00:00
var outChecks [ ] [ ] api . MessageCheckStatus
2021-04-02 14:12:34 +00:00
checks , err := s . api . MpoolCheckMessages ( ctx , [ ] * api . MessagePrototype { prototype } )
2021-03-24 14:12:35 +00:00
if err != nil {
return nil , xerrors . Errorf ( "message check: %w" , err )
}
outChecks = append ( outChecks , checks ... )
2021-03-27 14:35:46 +00:00
checks , err = s . api . MpoolCheckPendingMessages ( ctx , prototype . Message . From )
2021-03-24 14:12:35 +00:00
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 ,
2021-03-27 14:35:46 +00:00
prototype * api . MessagePrototype , force bool ) ( * types . SignedMessage , [ ] [ ] api . MessageCheckStatus , error ) {
2021-03-24 14:12:35 +00:00
2021-03-27 14:35:46 +00:00
gasedMsg , err := s . api . GasEstimateMessageGas ( ctx , & prototype . Message , nil , types . EmptyTSK )
2021-03-24 14:12:35 +00:00
if err != nil {
return nil , nil , xerrors . Errorf ( "estimating gas: %w" , err )
}
2021-03-27 14:35:46 +00:00
prototype . Message = * gasedMsg
2021-03-24 14:12:35 +00:00
if ! force {
checks , err := s . RunChecksForPrototype ( ctx , prototype )
if err != nil {
return nil , nil , xerrors . Errorf ( "running checks: %w" , err )
}
2021-03-27 14:35:46 +00:00
for _ , chks := range checks {
for _ , c := range chks {
if ! c . OK {
return nil , checks , ErrCheckFailed
}
}
2021-03-24 14:12:35 +00:00
}
}
2021-03-27 14:35:46 +00:00
if prototype . ValidNonce {
sm , err := s . api . WalletSignMessage ( ctx , prototype . Message . From , & prototype . Message )
2021-03-24 14:12:35 +00:00
if err != nil {
return nil , nil , err
}
_ , err = s . api . MpoolPush ( ctx , sm )
if err != nil {
return nil , nil , err
}
return sm , nil , nil
}
2021-03-27 14:35:46 +00:00
sm , err := s . api . MpoolPushMessage ( ctx , & prototype . Message , nil )
2021-03-24 14:12:35 +00:00
if err != nil {
return nil , nil , err
}
return sm , nil , nil
}
2021-02-23 14:50:47 +00:00
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
}
2021-03-27 14:35:46 +00:00
func ( s * ServicesImpl ) MessageForSend ( ctx context . Context , params SendParams ) ( * api . MessagePrototype , error ) {
2021-02-23 14:50:47 +00:00
if params . From == address . Undef {
defaddr , err := s . api . WalletDefaultAddress ( ctx )
if err != nil {
2021-03-24 14:12:35 +00:00
return nil , err
2021-02-23 14:50:47 +00:00
}
params . From = defaddr
}
2021-03-27 14:35:46 +00:00
msg := types . Message {
2021-02-23 14:50:47 +00:00
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
}
2021-03-27 14:35:46 +00:00
validNonce := false
2021-02-23 14:50:47 +00:00
if params . Nonce != nil {
msg . Nonce = * params . Nonce
2021-03-27 14:35:46 +00:00
validNonce = true
2021-02-23 14:50:47 +00:00
}
2021-03-27 14:35:46 +00:00
prototype := & api . MessagePrototype {
Message : msg ,
ValidNonce : validNonce ,
}
return prototype , nil
2021-02-23 14:50:47 +00:00
}
2021-03-25 14:24:09 +00:00
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
}