Add a new StateReplay CLI/API endpoint
This commit is contained in:
parent
7826cc0bab
commit
99c07703f8
@ -315,13 +315,15 @@ type FullNode interface {
|
|||||||
|
|
||||||
// MethodGroup: State
|
// MethodGroup: State
|
||||||
// The State methods are used to query, inspect, and interact with chain state.
|
// The State methods are used to query, inspect, and interact with chain state.
|
||||||
// All methods take a TipSetKey as a parameter. The state looked up is the state at that tipset.
|
// Most methods take a TipSetKey as a parameter. The state looked up is the state at that tipset.
|
||||||
// A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used.
|
// A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used.
|
||||||
|
|
||||||
// StateCall runs the given message and returns its result without any persisted changes.
|
// StateCall runs the given message and returns its result without any persisted changes.
|
||||||
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
|
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
|
||||||
// StateTransplant returns the result of executing the indicated message, assuming it was executed in the indicated tipset.
|
// StateTransplant returns the result of executing the indicated message, assuming it was executed in the indicated tipset.
|
||||||
StateTransplant(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
StateTransplant(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
||||||
|
// StateReplay searches for where the given message was executed, and replays it in that tipset.
|
||||||
|
StateReplay(context.Context, cid.Cid) (*InvocResult, error)
|
||||||
// StateGetActor returns the indicated actor's nonce and balance.
|
// StateGetActor returns the indicated actor's nonce and balance.
|
||||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||||
// StateReadState returns the indicated actor's state.
|
// StateReadState returns the indicated actor's state.
|
||||||
|
@ -189,6 +189,7 @@ type FullNodeStruct struct {
|
|||||||
StateSectorPartition func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorLocation, error) `perm:"read"`
|
StateSectorPartition func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorLocation, error) `perm:"read"`
|
||||||
StateCall func(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) `perm:"read"`
|
StateCall func(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) `perm:"read"`
|
||||||
StateTransplant func(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) `perm:"read"`
|
StateTransplant func(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) `perm:"read"`
|
||||||
|
StateReplay func(context.Context, cid.Cid) (*api.InvocResult, error) `perm:"read"`
|
||||||
StateGetActor func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) `perm:"read"`
|
StateGetActor func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) `perm:"read"`
|
||||||
StateReadState func(context.Context, address.Address, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
StateReadState func(context.Context, address.Address, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
||||||
StateMsgGasCost func(context.Context, cid.Cid, types.TipSetKey) (*api.MsgGasCost, error) `perm:"read"`
|
StateMsgGasCost func(context.Context, cid.Cid, types.TipSetKey) (*api.MsgGasCost, error) `perm:"read"`
|
||||||
@ -884,6 +885,10 @@ func (c *FullNodeStruct) StateTransplant(ctx context.Context, tsk types.TipSetKe
|
|||||||
return c.Internal.StateTransplant(ctx, tsk, mc)
|
return c.Internal.StateTransplant(ctx, tsk, mc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FullNodeStruct) StateReplay(ctx context.Context, mc cid.Cid) (*api.InvocResult, error) {
|
||||||
|
return c.Internal.StateReplay(ctx, mc)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
|
func (c *FullNodeStruct) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
|
||||||
return c.Internal.StateGetActor(ctx, actor, tsk)
|
return c.Internal.StateGetActor(ctx, actor, tsk)
|
||||||
}
|
}
|
||||||
|
91
cli/state.go
91
cli/state.go
@ -61,6 +61,7 @@ var stateCmd = &cli.Command{
|
|||||||
stateGetActorCmd,
|
stateGetActorCmd,
|
||||||
stateLookupIDCmd,
|
stateLookupIDCmd,
|
||||||
stateTransplantCmd,
|
stateTransplantCmd,
|
||||||
|
stateReplayCmd,
|
||||||
stateSectorSizeCmd,
|
stateSectorSizeCmd,
|
||||||
stateReadStateCmd,
|
stateReadStateCmd,
|
||||||
stateListMessagesCmd,
|
stateListMessagesCmd,
|
||||||
@ -388,6 +389,16 @@ var stateTransplantCmd = &cli.Command{
|
|||||||
Name: "transplant",
|
Name: "transplant",
|
||||||
Usage: "Play a particular message within a tipset",
|
Usage: "Play a particular message within a tipset",
|
||||||
ArgsUsage: "[tipsetKey messageCid]",
|
ArgsUsage: "[tipsetKey messageCid]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "show-trace",
|
||||||
|
Usage: "print out full execution trace for given message",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "detailed-gas",
|
||||||
|
Usage: "print out detailed gas costs for given message",
|
||||||
|
},
|
||||||
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
if cctx.Args().Len() < 1 {
|
if cctx.Args().Len() < 1 {
|
||||||
fmt.Println("usage: [tipset] <message cid>")
|
fmt.Println("usage: [tipset] <message cid>")
|
||||||
@ -453,10 +464,90 @@ var stateTransplantCmd = &cli.Command{
|
|||||||
fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode)
|
fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode)
|
||||||
fmt.Printf("Return: %x\n", res.MsgRct.Return)
|
fmt.Printf("Return: %x\n", res.MsgRct.Return)
|
||||||
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
|
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
|
||||||
|
|
||||||
|
if cctx.Bool("detailed-gas") {
|
||||||
|
fmt.Printf("Base Fee Burn: %d\n", res.GasCost.BaseFeeBurn)
|
||||||
|
fmt.Printf("Overestimaton Burn: %d\n", res.GasCost.OverEstimationBurn)
|
||||||
|
fmt.Printf("Miner Penalty: %d\n", res.GasCost.MinerPenalty)
|
||||||
|
fmt.Printf("Miner Tip: %d\n", res.GasCost.MinerTip)
|
||||||
|
fmt.Printf("Refund: %d\n", res.GasCost.Refund)
|
||||||
|
}
|
||||||
|
fmt.Printf("Total Message Cost: %d\n", res.GasCost.TotalCost)
|
||||||
|
|
||||||
if res.MsgRct.ExitCode != 0 {
|
if res.MsgRct.ExitCode != 0 {
|
||||||
fmt.Printf("Error message: %q\n", res.Error)
|
fmt.Printf("Error message: %q\n", res.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cctx.Bool("show-trace") {
|
||||||
|
fmt.Printf("%s\t%s\t%s\t%d\t%x\t%d\t%x\n", res.Msg.From, res.Msg.To, res.Msg.Value, res.Msg.Method, res.Msg.Params, res.MsgRct.ExitCode, res.MsgRct.Return)
|
||||||
|
printInternalExecutions("\t", res.ExecutionTrace.Subcalls)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stateReplayCmd = &cli.Command{
|
||||||
|
Name: "replay",
|
||||||
|
Usage: "Replay a particular message",
|
||||||
|
ArgsUsage: "<messageCid>",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "show-trace",
|
||||||
|
Usage: "print out full execution trace for given message",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "detailed-gas",
|
||||||
|
Usage: "print out detailed gas costs for given message",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
if cctx.Args().Len() != 1 {
|
||||||
|
fmt.Println("must provide cid of message to replay")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mcid, err := cid.Decode(cctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("message cid was invalid: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fapi, closer, err := GetFullNodeAPI(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
|
res, err := fapi.StateReplay(ctx, mcid)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("replay call failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Replay receipt:")
|
||||||
|
fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode)
|
||||||
|
fmt.Printf("Return: %x\n", res.MsgRct.Return)
|
||||||
|
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
|
||||||
|
|
||||||
|
if cctx.Bool("detailed-gas") {
|
||||||
|
fmt.Printf("Base Fee Burn: %d\n", res.GasCost.BaseFeeBurn)
|
||||||
|
fmt.Printf("Overestimaton Burn: %d\n", res.GasCost.OverEstimationBurn)
|
||||||
|
fmt.Printf("Miner Penalty: %d\n", res.GasCost.MinerPenalty)
|
||||||
|
fmt.Printf("Miner Tip: %d\n", res.GasCost.MinerTip)
|
||||||
|
fmt.Printf("Refund: %d\n", res.GasCost.Refund)
|
||||||
|
}
|
||||||
|
fmt.Printf("Total Message Cost: %d\n", res.GasCost.TotalCost)
|
||||||
|
|
||||||
|
if res.MsgRct.ExitCode != 0 {
|
||||||
|
fmt.Printf("Error message: %q\n", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cctx.Bool("show-trace") {
|
||||||
|
fmt.Printf("%s\t%s\t%s\t%d\t%x\t%d\t%x\n", res.Msg.From, res.Msg.To, res.Msg.Value, res.Msg.Method, res.Msg.Params, res.MsgRct.ExitCode, res.MsgRct.Return)
|
||||||
|
printInternalExecutions("\t", res.ExecutionTrace.Subcalls)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -372,6 +372,46 @@ func (a *StateAPI) StateTransplant(ctx context.Context, tsk types.TipSetKey, mc
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *StateAPI) StateReplay(ctx context.Context, mc cid.Cid) (*api.InvocResult, error) {
|
||||||
|
mlkp, err := a.StateSearchMsg(ctx, mc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("searching for msg %s: %w", mc, err)
|
||||||
|
}
|
||||||
|
if mlkp == nil {
|
||||||
|
return nil, xerrors.Errorf("didn't find msg %s", mc)
|
||||||
|
}
|
||||||
|
|
||||||
|
executionTs, err := a.Chain.GetTipSetFromKey(mlkp.TipSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("loading tipset %s: %w", mlkp.TipSet, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := a.Chain.LoadTipSet(executionTs.Parents())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("loading parent tipset %s: %w", mlkp.TipSet, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, r, err := a.StateManager.Replay(ctx, ts, mlkp.Message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errstr string
|
||||||
|
if r.ActorErr != nil {
|
||||||
|
errstr = r.ActorErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.InvocResult{
|
||||||
|
MsgCid: mlkp.Message,
|
||||||
|
Msg: m,
|
||||||
|
MsgRct: &r.MessageReceipt,
|
||||||
|
GasCost: stmgr.MakeMsgGasCost(m, r),
|
||||||
|
ExecutionTrace: r.ExecutionTrace,
|
||||||
|
Error: errstr,
|
||||||
|
Duration: r.Duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func stateForTs(ctx context.Context, ts *types.TipSet, cstore *store.ChainStore, smgr *stmgr.StateManager) (*state.StateTree, error) {
|
func stateForTs(ctx context.Context, ts *types.TipSet, cstore *store.ChainStore, smgr *stmgr.StateManager) (*state.StateTree, error) {
|
||||||
if ts == nil {
|
if ts == nil {
|
||||||
ts = cstore.GetHeaviestTipSet()
|
ts = cstore.GetHeaviestTipSet()
|
||||||
|
Loading…
Reference in New Issue
Block a user