Merge pull request #1089 from filecoin-project/feat/state-call-cmd
implement state call command for easy method calling
This commit is contained in:
commit
fd0bfc8777
@ -95,7 +95,7 @@ type FullNode interface {
|
|||||||
//ClientListAsks() []Ask
|
//ClientListAsks() []Ask
|
||||||
|
|
||||||
// if tipset is nil, we'll use heaviest
|
// if tipset is nil, we'll use heaviest
|
||||||
StateCall(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error)
|
StateCall(context.Context, *types.Message, *types.TipSet) (*MethodCall, error)
|
||||||
StateReplay(context.Context, *types.TipSet, cid.Cid) (*ReplayResults, error)
|
StateReplay(context.Context, *types.TipSet, cid.Cid) (*ReplayResults, error)
|
||||||
StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error)
|
StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error)
|
||||||
StateReadState(ctx context.Context, act *types.Actor, ts *types.TipSet) (*ActorState, error)
|
StateReadState(ctx context.Context, act *types.Actor, ts *types.TipSet) (*ActorState, error)
|
||||||
@ -271,6 +271,11 @@ type ReplayResults struct {
|
|||||||
Error string
|
Error string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MethodCall struct {
|
||||||
|
types.MessageReceipt
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
type ActiveSync struct {
|
type ActiveSync struct {
|
||||||
Base *types.TipSet
|
Base *types.TipSet
|
||||||
Target *types.TipSet
|
Target *types.TipSet
|
||||||
|
@ -99,7 +99,7 @@ type FullNodeStruct struct {
|
|||||||
StateMinerPeerID func(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) `perm:"read"`
|
StateMinerPeerID func(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) `perm:"read"`
|
||||||
StateMinerElectionPeriodStart func(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) `perm:"read"`
|
StateMinerElectionPeriodStart func(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) `perm:"read"`
|
||||||
StateMinerSectorSize func(context.Context, address.Address, *types.TipSet) (uint64, error) `perm:"read"`
|
StateMinerSectorSize func(context.Context, address.Address, *types.TipSet) (uint64, error) `perm:"read"`
|
||||||
StateCall func(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error) `perm:"read"`
|
StateCall func(context.Context, *types.Message, *types.TipSet) (*api.MethodCall, error) `perm:"read"`
|
||||||
StateReplay func(context.Context, *types.TipSet, cid.Cid) (*api.ReplayResults, error) `perm:"read"`
|
StateReplay func(context.Context, *types.TipSet, cid.Cid) (*api.ReplayResults, error) `perm:"read"`
|
||||||
StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"`
|
StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"`
|
||||||
StateReadState func(context.Context, *types.Actor, *types.TipSet) (*api.ActorState, error) `perm:"read"`
|
StateReadState func(context.Context, *types.Actor, *types.TipSet) (*api.ActorState, error) `perm:"read"`
|
||||||
@ -410,7 +410,7 @@ func (c *FullNodeStruct) StateMinerSectorSize(ctx context.Context, actor address
|
|||||||
return c.Internal.StateMinerSectorSize(ctx, actor, ts)
|
return c.Internal.StateMinerSectorSize(ctx, actor, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
|
func (c *FullNodeStruct) StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.MethodCall, error) {
|
||||||
return c.Internal.StateCall(ctx, msg, ts)
|
return c.Internal.StateCall(ctx, msg, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,13 +8,14 @@ import (
|
|||||||
"go.opencensus.io/trace"
|
"go.opencensus.io/trace"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/store"
|
"github.com/filecoin-project/lotus/chain/store"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/chain/vm"
|
"github.com/filecoin-project/lotus/chain/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate cid.Cid, r vm.Rand, bheight uint64) (*types.MessageReceipt, error) {
|
func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate cid.Cid, r vm.Rand, bheight uint64) (*api.MethodCall, error) {
|
||||||
ctx, span := trace.StartSpan(ctx, "statemanager.CallRaw")
|
ctx, span := trace.StartSpan(ctx, "statemanager.CallRaw")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@ -54,14 +55,19 @@ func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate
|
|||||||
return nil, xerrors.Errorf("apply message failed: %w", err)
|
return nil, xerrors.Errorf("apply message failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs string
|
||||||
if ret.ActorErr != nil {
|
if ret.ActorErr != nil {
|
||||||
|
errs = ret.ActorErr.Error()
|
||||||
log.Warnf("chain call failed: %s", ret.ActorErr)
|
log.Warnf("chain call failed: %s", ret.ActorErr)
|
||||||
}
|
}
|
||||||
return &ret.MessageReceipt, nil
|
return &api.MethodCall{
|
||||||
|
MessageReceipt: ret.MessageReceipt,
|
||||||
|
Error: errs,
|
||||||
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
|
func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.MethodCall, error) {
|
||||||
if ts == nil {
|
if ts == nil {
|
||||||
ts = sm.cs.GetHeaviestTipSet()
|
ts = sm.cs.GetHeaviestTipSet()
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,10 @@ var chainGetMsgCmd = &cli.Command{
|
|||||||
Name: "getmessage",
|
Name: "getmessage",
|
||||||
Usage: "Get and print a message by its cid",
|
Usage: "Get and print a message by its cid",
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
if !cctx.Args().Present() {
|
||||||
|
return fmt.Errorf("must pass a cid of a message to get")
|
||||||
|
}
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
213
cli/state.go
213
cli/state.go
@ -1,18 +1,24 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
actors "github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/miner"
|
"github.com/filecoin-project/lotus/miner"
|
||||||
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
"gopkg.in/urfave/cli.v2"
|
"gopkg.in/urfave/cli.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,6 +45,7 @@ var stateCmd = &cli.Command{
|
|||||||
stateReadStateCmd,
|
stateReadStateCmd,
|
||||||
stateListMessagesCmd,
|
stateListMessagesCmd,
|
||||||
stateComputeStateCmd,
|
stateComputeStateCmd,
|
||||||
|
stateCallCmd,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,3 +655,209 @@ var stateComputeStateCmd = &cli.Command{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stateCallCmd = &cli.Command{
|
||||||
|
Name: "call",
|
||||||
|
Usage: "Invoke a method on an actor locally",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "from",
|
||||||
|
Usage: "",
|
||||||
|
Value: actors.NetworkAddress.String(),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "value",
|
||||||
|
Usage: "specify value field for invocation",
|
||||||
|
Value: "0",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "ret",
|
||||||
|
Usage: "specify how to parse output (auto, raw, addr, big)",
|
||||||
|
Value: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
if cctx.Args().Len() < 2 {
|
||||||
|
return fmt.Errorf("must specify at least actor and method to invoke")
|
||||||
|
}
|
||||||
|
|
||||||
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
|
toa, err := address.NewFromString(cctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("given 'to' address %q was invalid: %w", cctx.Args().First(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
froma, err := address.NewFromString(cctx.String("from"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("given 'from' address %q was invalid: %w", cctx.String("from"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := loadTipSet(ctx, cctx, api)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
method, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("must pass method as a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := types.ParseFIL(cctx.String("value"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse 'value': %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
act, err := api.StateGetActor(ctx, toa, ts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup target actor: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params, err := parseParamsForMethod(act.Code, method, cctx.Args().Slice()[2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse params: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := api.StateCall(ctx, &types.Message{
|
||||||
|
From: froma,
|
||||||
|
To: toa,
|
||||||
|
Value: types.BigInt(value),
|
||||||
|
GasLimit: types.NewInt(10000000000),
|
||||||
|
GasPrice: types.NewInt(0),
|
||||||
|
Method: method,
|
||||||
|
Params: params,
|
||||||
|
}, ts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("state call failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.ExitCode != 0 {
|
||||||
|
return fmt.Errorf("invocation failed (exit: %d): %s", ret.ExitCode, ret.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := formatOutput(cctx.String("ret"), ret.Return)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to format output: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("return: %s\n", s)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatOutput(t string, val []byte) (string, error) {
|
||||||
|
switch t {
|
||||||
|
case "raw", "hex":
|
||||||
|
return fmt.Sprintf("%x", val), nil
|
||||||
|
case "address", "addr", "a":
|
||||||
|
a, err := address.NewFromBytes(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return a.String(), nil
|
||||||
|
case "big", "int", "bigint":
|
||||||
|
bi := types.BigFromBytes(val)
|
||||||
|
return bi.String(), nil
|
||||||
|
case "fil":
|
||||||
|
bi := types.FIL(types.BigFromBytes(val))
|
||||||
|
return bi.String(), nil
|
||||||
|
case "pid", "peerid", "peer":
|
||||||
|
pid, err := peer.IDFromBytes(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid.Pretty(), nil
|
||||||
|
case "auto":
|
||||||
|
if len(val) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := address.NewFromBytes(val)
|
||||||
|
if err == nil {
|
||||||
|
return "address: " + a.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := peer.IDFromBytes(val)
|
||||||
|
if err == nil {
|
||||||
|
return "peerID: " + pid.Pretty(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bi := types.BigFromBytes(val)
|
||||||
|
return "bigint: " + bi.String(), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unrecognized output type: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var f interface{}
|
||||||
|
switch act {
|
||||||
|
case actors.StorageMarketCodeCid:
|
||||||
|
f = actors.StorageMarketActor{}.Exports()[method]
|
||||||
|
case actors.StorageMinerCodeCid:
|
||||||
|
f = actors.StorageMinerActor{}.Exports()[method]
|
||||||
|
case actors.StoragePowerCodeCid:
|
||||||
|
f = actors.StoragePowerActor{}.Exports()[method]
|
||||||
|
case actors.MultisigCodeCid:
|
||||||
|
f = actors.MultiSigActor{}.Exports()[method]
|
||||||
|
case actors.PaymentChannelCodeCid:
|
||||||
|
f = actors.PaymentChannelActor{}.Exports()[method]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("the lazy devs didnt add support for that actor to this call yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
rf := reflect.TypeOf(f)
|
||||||
|
if rf.NumIn() != 3 {
|
||||||
|
return nil, fmt.Errorf("expected referenced method to have three arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramObj := rf.In(2).Elem()
|
||||||
|
if paramObj.NumField() != len(args) {
|
||||||
|
return nil, fmt.Errorf("not enough arguments given to call that method (expecting %d)", paramObj.NumField())
|
||||||
|
}
|
||||||
|
|
||||||
|
p := reflect.New(paramObj)
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
switch paramObj.Field(i).Type {
|
||||||
|
case reflect.TypeOf(address.Address{}):
|
||||||
|
a, err := address.NewFromString(args[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse address: %s", err)
|
||||||
|
}
|
||||||
|
p.Elem().Field(i).Set(reflect.ValueOf(a))
|
||||||
|
case reflect.TypeOf(uint64(0)):
|
||||||
|
val, err := strconv.ParseUint(args[i], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.Elem().Field(i).Set(reflect.ValueOf(val))
|
||||||
|
case reflect.TypeOf(peer.ID("")):
|
||||||
|
pid, err := peer.IDB58Decode(args[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse peer ID: %s", err)
|
||||||
|
}
|
||||||
|
p.Elem().Field(i).Set(reflect.ValueOf(pid))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported type for call (TODO): %s", paramObj.Field(i).Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := p.Interface().(cbg.CBORMarshaler)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := m.MarshalCBOR(buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal param object: %s", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
@ -108,7 +108,7 @@ func (a *StateAPI) StatePledgeCollateral(ctx context.Context, ts *types.TipSet)
|
|||||||
return types.BigFromBytes(ret.Return), nil
|
return types.BigFromBytes(ret.Return), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
|
func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.MethodCall, error) {
|
||||||
return a.StateManager.Call(ctx, msg, ts)
|
return a.StateManager.Call(ctx, msg, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ type Miner struct {
|
|||||||
|
|
||||||
type storageMinerApi interface {
|
type storageMinerApi interface {
|
||||||
// Call a read only method on actors (no interaction with the chain required)
|
// Call a read only method on actors (no interaction with the chain required)
|
||||||
StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error)
|
StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.MethodCall, error)
|
||||||
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
||||||
StateMinerElectionPeriodStart(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error)
|
StateMinerElectionPeriodStart(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error)
|
||||||
StateMinerSectors(context.Context, address.Address, *types.TipSet) ([]*api.ChainSectorInfo, error)
|
StateMinerSectors(context.Context, address.Address, *types.TipSet) ([]*api.ChainSectorInfo, error)
|
||||||
|
Loading…
Reference in New Issue
Block a user