1949 lines
44 KiB
Go
1949 lines
44 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/ipfs/go-cid"
|
|
cbor "github.com/ipfs/go-ipld-cbor"
|
|
"github.com/multiformats/go-multiaddr"
|
|
"github.com/urfave/cli/v2"
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
actorstypes "github.com/filecoin-project/go-state-types/actors"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
"github.com/filecoin-project/go-state-types/exitcode"
|
|
"github.com/filecoin-project/go-state-types/network"
|
|
"github.com/filecoin-project/lotus/api"
|
|
lapi "github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/api/v0api"
|
|
"github.com/filecoin-project/lotus/blockstore"
|
|
"github.com/filecoin-project/lotus/build"
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
|
"github.com/filecoin-project/lotus/chain/consensus"
|
|
"github.com/filecoin-project/lotus/chain/state"
|
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
cliutil "github.com/filecoin-project/lotus/cli/util"
|
|
)
|
|
|
|
var StateCmd = &cli.Command{
|
|
Name: "state",
|
|
Usage: "Interact with and query filecoin chain state",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset to call method on (pass comma separated array of cids)",
|
|
},
|
|
},
|
|
Subcommands: []*cli.Command{
|
|
StatePowerCmd,
|
|
StateSectorsCmd,
|
|
StateActiveSectorsCmd,
|
|
StateListActorsCmd,
|
|
StateListMinersCmd,
|
|
StateCircSupplyCmd,
|
|
StateSectorCmd,
|
|
StateGetActorCmd,
|
|
StateLookupIDCmd,
|
|
StateReplayCmd,
|
|
StateSectorSizeCmd,
|
|
StateReadStateCmd,
|
|
StateListMessagesCmd,
|
|
StateComputeStateCmd,
|
|
StateCallCmd,
|
|
StateGetDealSetCmd,
|
|
StateWaitMsgCmd,
|
|
StateSearchMsgCmd,
|
|
StateMinerInfo,
|
|
StateMarketCmd,
|
|
StateExecTraceCmd,
|
|
StateNtwkVersionCmd,
|
|
StateMinerProvingDeadlineCmd,
|
|
StateSysActorCIDsCmd,
|
|
},
|
|
}
|
|
|
|
var StateMinerProvingDeadlineCmd = &cli.Command{
|
|
Name: "miner-proving-deadline",
|
|
Usage: "Retrieve information about a given miner's proving deadline",
|
|
ArgsUsage: "[minerAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cd, err := api.StateMinerProvingDeadline(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting miner info: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Period Start:\t%s\n", cd.PeriodStart)
|
|
fmt.Printf("Index:\t\t%d\n", cd.Index)
|
|
fmt.Printf("Open:\t\t%s\n", cd.Open)
|
|
fmt.Printf("Close:\t\t%s\n", cd.Close)
|
|
fmt.Printf("Challenge:\t%s\n", cd.Challenge)
|
|
fmt.Printf("FaultCutoff:\t%s\n", cd.FaultCutoff)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateMinerInfo = &cli.Command{
|
|
Name: "miner-info",
|
|
Usage: "Retrieve miner information",
|
|
ArgsUsage: "[minerAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
availableBalance, err := api.StateMinerAvailableBalance(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting miner available balance: %w", err)
|
|
}
|
|
fmt.Printf("Available Balance: %s\n", types.FIL(availableBalance))
|
|
fmt.Printf("Owner:\t%s\n", mi.Owner)
|
|
fmt.Printf("Worker:\t%s\n", mi.Worker)
|
|
for i, controlAddress := range mi.ControlAddresses {
|
|
fmt.Printf("Control %d: \t%s\n", i, controlAddress)
|
|
}
|
|
if mi.Beneficiary != address.Undef {
|
|
fmt.Printf("Beneficiary:\t%s\n", mi.Beneficiary)
|
|
if mi.Beneficiary != mi.Owner {
|
|
fmt.Printf("Beneficiary Quota:\t%s\n", mi.BeneficiaryTerm.Quota)
|
|
fmt.Printf("Beneficiary Used Quota:\t%s\n", mi.BeneficiaryTerm.UsedQuota)
|
|
fmt.Printf("Beneficiary Expiration:\t%s\n", mi.BeneficiaryTerm.Expiration)
|
|
}
|
|
}
|
|
if mi.PendingBeneficiaryTerm != nil {
|
|
fmt.Printf("Pending Beneficiary Term:\n")
|
|
fmt.Printf("New Beneficiary:\t%s\n", mi.PendingBeneficiaryTerm.NewBeneficiary)
|
|
fmt.Printf("New Quota:\t%s\n", mi.PendingBeneficiaryTerm.NewQuota)
|
|
fmt.Printf("New Expiration:\t%s\n", mi.PendingBeneficiaryTerm.NewExpiration)
|
|
fmt.Printf("Approved By Beneficiary:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByBeneficiary)
|
|
fmt.Printf("Approved By Nominee:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByNominee)
|
|
}
|
|
|
|
fmt.Printf("PeerID:\t%s\n", mi.PeerId)
|
|
fmt.Printf("Multiaddrs:\t")
|
|
for _, addr := range mi.Multiaddrs {
|
|
a, err := multiaddr.NewMultiaddrBytes(addr)
|
|
if err != nil {
|
|
return xerrors.Errorf("undecodable listen address: %w", err)
|
|
}
|
|
fmt.Printf("%s ", a)
|
|
}
|
|
fmt.Println()
|
|
fmt.Printf("Consensus Fault End:\t%d\n", mi.ConsensusFaultElapsed)
|
|
|
|
fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize)
|
|
pow, err := api.StateMinerPower(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n",
|
|
color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)),
|
|
types.SizeStr(pow.TotalPower.RawBytePower),
|
|
types.BigDivFloat(
|
|
types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)),
|
|
pow.TotalPower.RawBytePower,
|
|
),
|
|
)
|
|
|
|
fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n",
|
|
color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)),
|
|
types.DeciStr(pow.TotalPower.QualityAdjPower),
|
|
types.BigDivFloat(
|
|
types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)),
|
|
pow.TotalPower.QualityAdjPower,
|
|
),
|
|
)
|
|
|
|
fmt.Println()
|
|
|
|
cd, err := api.StateMinerProvingDeadline(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting miner info: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Proving Period Start:\t%s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.PeriodStart))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func ParseTipSetString(ts string) ([]cid.Cid, error) {
|
|
strs := strings.Split(ts, ",")
|
|
|
|
var cids []cid.Cid
|
|
for _, s := range strs {
|
|
c, err := cid.Parse(strings.TrimSpace(s))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cids = append(cids, c)
|
|
}
|
|
|
|
return cids, nil
|
|
}
|
|
|
|
type TipSetResolver interface {
|
|
ChainHead(context.Context) (*types.TipSet, error)
|
|
ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error)
|
|
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
|
|
}
|
|
|
|
// LoadTipSet gets the tipset from the context, or the head from the API.
|
|
//
|
|
// It always gets the head from the API so commands use a consistent tipset even if time pases.
|
|
func LoadTipSet(ctx context.Context, cctx *cli.Context, api TipSetResolver) (*types.TipSet, error) {
|
|
tss := cctx.String("tipset")
|
|
if tss == "" {
|
|
return api.ChainHead(ctx)
|
|
}
|
|
|
|
return ParseTipSetRef(ctx, api, tss)
|
|
}
|
|
|
|
func ParseTipSetRef(ctx context.Context, api TipSetResolver, tss string) (*types.TipSet, error) {
|
|
if tss[0] == '@' {
|
|
if tss == "@head" {
|
|
return api.ChainHead(ctx)
|
|
}
|
|
|
|
var h uint64
|
|
if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil {
|
|
return nil, xerrors.Errorf("parsing height tipset ref: %w", err)
|
|
}
|
|
|
|
return api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(h), types.EmptyTSK)
|
|
}
|
|
|
|
cids, err := ParseTipSetString(tss)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(cids) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
k := types.NewTipSetKey(cids...)
|
|
ts, err := api.ChainGetTipSet(ctx, k)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ts, nil
|
|
}
|
|
|
|
func ParseTipSetRefOffline(ctx context.Context, cs *store.ChainStore, tss string) (*types.TipSet, error) {
|
|
switch {
|
|
|
|
case tss == "" || tss == "@head":
|
|
return cs.GetHeaviestTipSet(), nil
|
|
|
|
case tss[0] != '@':
|
|
cids, err := ParseTipSetString(tss)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to parse tipset (%q): %w", tss, err)
|
|
}
|
|
return cs.LoadTipSet(ctx, types.NewTipSetKey(cids...))
|
|
|
|
default:
|
|
var h uint64
|
|
if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil {
|
|
return nil, xerrors.Errorf("parsing height tipset ref: %w", err)
|
|
}
|
|
return cs.GetTipsetByHeight(ctx, abi.ChainEpoch(h), cs.GetHeaviestTipSet(), true)
|
|
}
|
|
}
|
|
|
|
var StatePowerCmd = &cli.Command{
|
|
Name: "power",
|
|
Usage: "Query network or miner power",
|
|
ArgsUsage: "[<minerAddress> (optional)]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var maddr address.Address
|
|
if cctx.Args().Present() {
|
|
maddr, err = address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ma, err := api.StateGetActor(ctx, maddr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !builtin.IsStorageMinerActor(ma.Code) {
|
|
return xerrors.New("provided address does not correspond to a miner actor")
|
|
}
|
|
}
|
|
|
|
power, err := api.StateMinerPower(ctx, maddr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tp := power.TotalPower
|
|
if cctx.Args().Present() {
|
|
mp := power.MinerPower
|
|
fmt.Printf(
|
|
"%s(%s) / %s(%s) ~= %0.4f%%\n",
|
|
mp.QualityAdjPower.String(), types.SizeStr(mp.QualityAdjPower),
|
|
tp.QualityAdjPower.String(), types.SizeStr(tp.QualityAdjPower),
|
|
types.BigDivFloat(
|
|
types.BigMul(mp.QualityAdjPower, big.NewInt(100)),
|
|
tp.QualityAdjPower,
|
|
),
|
|
)
|
|
} else {
|
|
fmt.Printf("%s(%s)\n", tp.QualityAdjPower.String(), types.SizeStr(tp.QualityAdjPower))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateSectorsCmd = &cli.Command{
|
|
Name: "sectors",
|
|
Usage: "Query the sector set of a miner",
|
|
ArgsUsage: "[minerAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
maddr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sectors, err := api.StateMinerSectors(ctx, maddr, nil, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, s := range sectors {
|
|
fmt.Printf("%d: %s\n", s.SectorNumber, s.SealedCID)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateActiveSectorsCmd = &cli.Command{
|
|
Name: "active-sectors",
|
|
Usage: "Query the active sector set of a miner",
|
|
ArgsUsage: "[minerAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
maddr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sectors, err := api.StateMinerActiveSectors(ctx, maddr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, s := range sectors {
|
|
fmt.Printf("%d: %s\n", s.SectorNumber, s.SealedCID)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateExecTraceCmd = &cli.Command{
|
|
Name: "exec-trace",
|
|
Usage: "Get the execution trace of a given message",
|
|
ArgsUsage: "<messageCid>",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
mcid, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("message cid was invalid: %s", err)
|
|
}
|
|
|
|
capi, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
msg, err := capi.ChainGetMessage(ctx, mcid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lookup, err := capi.StateSearchMsg(ctx, mcid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if lookup == nil {
|
|
return fmt.Errorf("failed to find message: %s", mcid)
|
|
}
|
|
|
|
ts, err := capi.ChainGetTipSet(ctx, lookup.TipSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pts, err := capi.ChainGetTipSet(ctx, ts.Parents())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cso, err := capi.StateCompute(ctx, pts.Height(), nil, pts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var trace *api.InvocResult
|
|
for _, t := range cso.Trace {
|
|
if t.Msg.From == msg.From && t.Msg.Nonce == msg.Nonce {
|
|
trace = t
|
|
break
|
|
}
|
|
}
|
|
if trace == nil {
|
|
return fmt.Errorf("failed to find message in tipset trace output")
|
|
}
|
|
|
|
out, err := json.MarshalIndent(trace, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(out))
|
|
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.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
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, types.EmptyTSK, 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
|
|
},
|
|
}
|
|
|
|
var StateGetDealSetCmd = &cli.Command{
|
|
Name: "get-deal",
|
|
Usage: "View on-chain deal info",
|
|
ArgsUsage: "[dealId]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
dealid, err := strconv.ParseUint(cctx.Args().First(), 10, 64)
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing deal ID: %w", err)
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
deal, err := api.StateMarketStorageDeal(ctx, abi.DealID(dealid), ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.MarshalIndent(deal, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(data))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateListMinersCmd = &cli.Command{
|
|
Name: "list-miners",
|
|
Usage: "list all miners in the network",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "sort-by",
|
|
Usage: "criteria to sort miners by (none, num-deals)",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
miners, err := api.StateListMiners(ctx, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch cctx.String("sort-by") {
|
|
case "num-deals":
|
|
ndm, err := getDealsCounts(ctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sort.Slice(miners, func(i, j int) bool {
|
|
return ndm[miners[i]] > ndm[miners[j]]
|
|
})
|
|
|
|
for i := 0; i < 50 && i < len(miners); i++ {
|
|
fmt.Printf("%s %d\n", miners[i], ndm[miners[i]])
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unrecognized sorting order")
|
|
case "", "none":
|
|
}
|
|
|
|
for _, m := range miners {
|
|
fmt.Println(m.String())
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func getDealsCounts(ctx context.Context, lapi v0api.FullNode) (map[address.Address]int, error) {
|
|
allDeals, err := lapi.StateMarketDeals(ctx, types.EmptyTSK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make(map[address.Address]int)
|
|
for _, d := range allDeals {
|
|
if d.State.SectorStartEpoch != -1 {
|
|
out[d.Proposal.Provider]++
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
var StateListActorsCmd = &cli.Command{
|
|
Name: "list-actors",
|
|
Usage: "list all actors in the network",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
actors, err := api.StateListActors(ctx, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, a := range actors {
|
|
fmt.Println(a.String())
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateGetActorCmd = &cli.Command{
|
|
Name: "get-actor",
|
|
Usage: "Print actor information",
|
|
ArgsUsage: "[actorAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
a, err := api.StateGetActor(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
strtype := builtin.ActorNameByCode(a.Code)
|
|
|
|
fmt.Printf("Address:\t%s\n", addr)
|
|
fmt.Printf("Balance:\t%s\n", types.FIL(a.Balance))
|
|
fmt.Printf("Nonce:\t\t%d\n", a.Nonce)
|
|
fmt.Printf("Code:\t\t%s (%s)\n", a.Code, strtype)
|
|
fmt.Printf("Head:\t\t%s\n", a.Head)
|
|
if a.Address != nil {
|
|
fmt.Printf("Delegated address:\t\t%s\n", a.Address)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateLookupIDCmd = &cli.Command{
|
|
Name: "lookup",
|
|
Usage: "Find corresponding ID address",
|
|
ArgsUsage: "[address]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "reverse",
|
|
Aliases: []string{"r"},
|
|
Usage: "Perform reverse lookup",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var a address.Address
|
|
if !cctx.Bool("reverse") {
|
|
a, err = api.StateLookupID(ctx, addr, ts.Key())
|
|
} else {
|
|
a, err = api.StateAccountKey(ctx, addr, ts.Key())
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("%s\n", a)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateSectorSizeCmd = &cli.Command{
|
|
Name: "sector-size",
|
|
Usage: "Look up miners sector size",
|
|
ArgsUsage: "[minerAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateReadStateCmd = &cli.Command{
|
|
Name: "read-state",
|
|
Usage: "View a json representation of an actors state",
|
|
ArgsUsage: "[actorAddress]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
as, err := api.StateReadState(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.MarshalIndent(as.State, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(data))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateListMessagesCmd = &cli.Command{
|
|
Name: "list-messages",
|
|
Usage: "list messages on chain matching given criteria",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "to",
|
|
Usage: "return messages to a given address",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "return messages from a given address",
|
|
},
|
|
&cli.Uint64Flag{
|
|
Name: "toheight",
|
|
Usage: "don't look before given block height",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "cids",
|
|
Usage: "print message CIDs instead of messages",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
var toa, froma address.Address
|
|
if tos := cctx.String("to"); tos != "" {
|
|
a, err := address.NewFromString(tos)
|
|
if err != nil {
|
|
return fmt.Errorf("given 'to' address %q was invalid: %w", tos, err)
|
|
}
|
|
toa = a
|
|
}
|
|
|
|
if froms := cctx.String("from"); froms != "" {
|
|
a, err := address.NewFromString(froms)
|
|
if err != nil {
|
|
return fmt.Errorf("given 'from' address %q was invalid: %w", froms, err)
|
|
}
|
|
froma = a
|
|
}
|
|
|
|
toh := abi.ChainEpoch(cctx.Uint64("toheight"))
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
windowSize := abi.ChainEpoch(100)
|
|
|
|
cur := ts
|
|
for cur.Height() > toh {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
end := toh
|
|
if cur.Height()-windowSize > end {
|
|
end = cur.Height() - windowSize
|
|
}
|
|
|
|
msgs, err := api.StateListMessages(ctx, &lapi.MessageMatch{To: toa, From: froma}, cur.Key(), end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, c := range msgs {
|
|
if cctx.Bool("cids") {
|
|
fmt.Println(c.String())
|
|
continue
|
|
}
|
|
|
|
m, err := api.ChainGetMessage(ctx, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b, err := json.MarshalIndent(m, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(b))
|
|
}
|
|
|
|
if end <= 0 {
|
|
break
|
|
}
|
|
|
|
next, err := api.ChainGetTipSetByHeight(ctx, end-1, cur.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cur = next
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateComputeStateCmd = &cli.Command{
|
|
Name: "compute-state",
|
|
Usage: "Perform state computations",
|
|
Flags: []cli.Flag{
|
|
&cli.Uint64Flag{
|
|
Name: "vm-height",
|
|
Usage: "set the height that the vm will see",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "apply-mpool-messages",
|
|
Usage: "apply messages from the mempool to the computed state",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "show-trace",
|
|
Usage: "print out full execution trace for given tipset",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "html",
|
|
Usage: "generate html report",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "json",
|
|
Usage: "generate json output",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "compute-state-output",
|
|
Usage: "a json file containing pre-existing compute-state output, to generate html reports without rerunning state changes",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-timing",
|
|
Usage: "don't show timing information in html traces",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
h := abi.ChainEpoch(cctx.Uint64("vm-height"))
|
|
var ts *types.TipSet
|
|
if tss := cctx.String("tipset"); tss != "" {
|
|
ts, err = ParseTipSetRef(ctx, api, tss)
|
|
} else if h > 0 {
|
|
ts, err = api.ChainGetTipSetByHeight(ctx, h, types.EmptyTSK)
|
|
} else {
|
|
ts, err = api.ChainHead(ctx)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if h == 0 {
|
|
h = ts.Height()
|
|
}
|
|
|
|
var msgs []*types.Message
|
|
if cctx.Bool("apply-mpool-messages") {
|
|
pmsgs, err := api.MpoolSelect(ctx, ts.Key(), 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, sm := range pmsgs {
|
|
msgs = append(msgs, &sm.Message)
|
|
}
|
|
}
|
|
|
|
var stout *lapi.ComputeStateOutput
|
|
if csofile := cctx.String("compute-state-output"); csofile != "" {
|
|
data, err := os.ReadFile(csofile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var o lapi.ComputeStateOutput
|
|
if err := json.Unmarshal(data, &o); err != nil {
|
|
return err
|
|
}
|
|
|
|
stout = &o
|
|
} else {
|
|
o, err := api.StateCompute(ctx, h, msgs, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stout = o
|
|
}
|
|
|
|
if cctx.Bool("json") {
|
|
out, err := json.Marshal(stout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(out))
|
|
return nil
|
|
}
|
|
|
|
if cctx.Bool("html") {
|
|
st, err := state.LoadStateTree(cbor.NewCborStore(blockstore.NewAPIBlockstore(api)), stout.Root)
|
|
if err != nil {
|
|
return xerrors.Errorf("loading state tree: %w", err)
|
|
}
|
|
|
|
codeCache := map[address.Address]cid.Cid{}
|
|
getCode := func(addr address.Address) (cid.Cid, error) {
|
|
if c, found := codeCache[addr]; found {
|
|
return c, nil
|
|
}
|
|
|
|
c, err := st.GetActor(addr)
|
|
if err != nil {
|
|
return cid.Cid{}, err
|
|
}
|
|
|
|
codeCache[addr] = c.Code
|
|
return c.Code, nil
|
|
}
|
|
|
|
_, _ = fmt.Fprintln(os.Stderr, "computed state cid: ", stout.Root)
|
|
|
|
return ComputeStateHTMLTempl(os.Stdout, ts, stout, !cctx.Bool("no-timing"), getCode)
|
|
}
|
|
|
|
fmt.Println("computed state cid: ", stout.Root)
|
|
if cctx.Bool("show-trace") {
|
|
for _, ir := range stout.Trace {
|
|
fmt.Printf("%s\t%s\t%s\t%d\t%x\t%d\t%x\n", ir.Msg.From, ir.Msg.To, ir.Msg.Value, ir.Msg.Method, ir.Msg.Params, ir.MsgRct.ExitCode, ir.MsgRct.Return)
|
|
printInternalExecutions("\t", ir.ExecutionTrace.Subcalls)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func printInternalExecutions(prefix string, trace []types.ExecutionTrace) {
|
|
for _, im := range trace {
|
|
fmt.Printf("%s%s\t%s\t%s\t%d\t%x\t%d\t%x\n", prefix, im.Msg.From, im.Msg.To, im.Msg.Value, im.Msg.Method, im.Msg.Params, im.MsgRct.ExitCode, im.MsgRct.Return)
|
|
printInternalExecutions(prefix+"\t", im.Subcalls)
|
|
}
|
|
}
|
|
|
|
var compStateTemplate = `
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
html, body { font-family: monospace; }
|
|
a:link, a:visited { color: #004; }
|
|
pre { background: #ccc; }
|
|
small { color: #444; }
|
|
.call { color: #00a; }
|
|
.params { background: #dfd; }
|
|
.ret { background: #ddf; }
|
|
.error { color: red; }
|
|
.exit0 { color: green; }
|
|
.exec {
|
|
padding-left: 15px;
|
|
border-left: 2.5px solid;
|
|
margin-bottom: 45px;
|
|
}
|
|
.exec:hover {
|
|
background: #eee;
|
|
}
|
|
.slow-true-false { color: #660; }
|
|
.slow-true-true { color: #f80; }
|
|
.deemp { color: #444; }
|
|
table {
|
|
font-size: 12px;
|
|
border-collapse: collapse;
|
|
}
|
|
tr {
|
|
border-top: 1px solid black;
|
|
border-bottom: 1px solid black;
|
|
}
|
|
tr.sum { border-top: 2px solid black; }
|
|
tr:first-child { border-top: none; }
|
|
tr:last-child { border-bottom: none; }
|
|
|
|
|
|
.ellipsis-content,
|
|
.ellipsis-toggle input {
|
|
display: none;
|
|
}
|
|
.ellipsis-toggle {
|
|
cursor: pointer;
|
|
}
|
|
/**
|
|
Checked State
|
|
**/
|
|
|
|
.ellipsis-toggle input:checked + .ellipsis {
|
|
display: none;
|
|
}
|
|
.ellipsis-toggle input:checked ~ .ellipsis-content {
|
|
display: inline;
|
|
background-color: #ddd;
|
|
}
|
|
hr {
|
|
border: none;
|
|
height: 1px;
|
|
background-color: black;
|
|
margin: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div>Tipset: <b>{{.TipSet.Key}}</b></div>
|
|
<div>Epoch: {{.TipSet.Height}}</div>
|
|
<div>State CID: <b>{{.Comp.Root}}</b></div>
|
|
<div>Calls</div>
|
|
{{range .Comp.Trace}}
|
|
{{template "message" (Call .ExecutionTrace false .MsgCid.String)}}
|
|
{{end}}
|
|
</body>
|
|
</html>
|
|
`
|
|
|
|
var compStateMsg = `
|
|
<div class="exec" id="{{.Hash}}">
|
|
{{$code := GetCode .Msg.To}}
|
|
<div>
|
|
<a href="#{{.Hash}}">
|
|
{{if not .Subcall}}
|
|
<h2 class="call">
|
|
{{else}}
|
|
<h4 class="call">
|
|
{{end}}
|
|
{{- CodeStr $code}}:{{GetMethod ($code) (.Msg.Method)}}
|
|
{{if not .Subcall}}
|
|
</h2>
|
|
{{else}}
|
|
</h4>
|
|
{{end}}
|
|
</a>
|
|
</div>
|
|
|
|
<div><b>{{.Msg.From}}</b> -> <b>{{.Msg.To}}</b> ({{ToFil .Msg.Value}}), M{{.Msg.Method}}</div>
|
|
{{if not .Subcall}}<div><small>Msg CID: {{.Hash}}</small></div>{{end}}
|
|
{{if gt (len .Msg.Params) 0}}
|
|
<div><pre class="params">{{JsonParams ($code) (.Msg.Method) (.Msg.Params) | html}}</pre></div>
|
|
{{end}}
|
|
<div><span class="exit{{IntExit .MsgRct.ExitCode}}">Exit: <b>{{.MsgRct.ExitCode}}</b></span>{{if gt (len .MsgRct.Return) 0}}, Return{{end}}</div>
|
|
{{if gt (len .MsgRct.Return) 0}}
|
|
<div><pre class="ret">{{JsonReturn ($code) (.Msg.Method) (.MsgRct.Return) | html}}</pre></div>
|
|
{{end}}
|
|
|
|
{{if ne .MsgRct.ExitCode 0}}
|
|
<div class="error">Exit: <pre>{{.MsgRct.ExitCode}}</pre></div>
|
|
{{end}}
|
|
|
|
<details>
|
|
<summary>Gas Trace</summary>
|
|
<table>
|
|
<tr><th>Name</th><th>Total/Compute/Storage</th><th>Time Taken</th></tr>
|
|
|
|
{{define "gasC" -}}
|
|
<td>{{.TotalGas}}/{{.ComputeGas}}/{{.StorageGas}}</td>
|
|
{{- end}}
|
|
|
|
{{range .GasCharges}}
|
|
<tr>
|
|
<td>{{.Name}}</td>
|
|
{{template "gasC" .}}
|
|
<td>{{if PrintTiming}}{{.TimeTaken}}{{end}}</td>
|
|
</tr>
|
|
{{end}}
|
|
{{with sumGas .GasCharges}}
|
|
<tr class="sum">
|
|
<td><b>Sum</b></td>
|
|
{{template "gasC" .}}
|
|
<td>{{if PrintTiming}}{{.TimeTaken}}{{end}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
</details>
|
|
|
|
|
|
{{if gt (len .Subcalls) 0}}
|
|
<div>Subcalls:</div>
|
|
{{$hash := .Hash}}
|
|
{{range $i, $call := .Subcalls}}
|
|
{{template "message" (Call $call true (printf "%s-%d" $hash $i))}}
|
|
{{end}}
|
|
{{end}}
|
|
</div>`
|
|
|
|
type compStateHTMLIn struct {
|
|
TipSet *types.TipSet
|
|
Comp *api.ComputeStateOutput
|
|
}
|
|
|
|
func ComputeStateHTMLTempl(w io.Writer, ts *types.TipSet, o *api.ComputeStateOutput, printTiming bool, getCode func(addr address.Address) (cid.Cid, error)) error {
|
|
t, err := template.New("compute_state").Funcs(map[string]interface{}{
|
|
"GetCode": getCode,
|
|
"GetMethod": getMethod,
|
|
"ToFil": toFil,
|
|
"JsonParams": JsonParams,
|
|
"JsonReturn": JsonReturn,
|
|
"IsSlow": isSlow,
|
|
"IsVerySlow": isVerySlow,
|
|
"IntExit": func(i exitcode.ExitCode) int64 { return int64(i) },
|
|
"sumGas": types.SumGas,
|
|
"CodeStr": builtin.ActorNameByCode,
|
|
"Call": call,
|
|
"PrintTiming": func() bool { return printTiming },
|
|
}).Parse(compStateTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t, err = t.New("message").Parse(compStateMsg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return t.ExecuteTemplate(w, "compute_state", &compStateHTMLIn{
|
|
TipSet: ts,
|
|
Comp: o,
|
|
})
|
|
}
|
|
|
|
type callMeta struct {
|
|
types.ExecutionTrace
|
|
Subcall bool
|
|
Hash string
|
|
}
|
|
|
|
func call(e types.ExecutionTrace, subcall bool, hash string) callMeta {
|
|
return callMeta{
|
|
ExecutionTrace: e,
|
|
Subcall: subcall,
|
|
Hash: hash,
|
|
}
|
|
}
|
|
|
|
func getMethod(code cid.Cid, method abi.MethodNum) string {
|
|
return consensus.NewActorRegistry().Methods[code][method].Name // todo: use remote
|
|
}
|
|
|
|
func toFil(f types.BigInt) types.FIL {
|
|
return types.FIL(f)
|
|
}
|
|
|
|
func isSlow(t time.Duration) bool {
|
|
return t > 10*time.Millisecond
|
|
}
|
|
|
|
func isVerySlow(t time.Duration) bool {
|
|
return t > 50*time.Millisecond
|
|
}
|
|
|
|
func JsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) {
|
|
ar := consensus.NewActorRegistry()
|
|
|
|
_, found := ar.Methods[code][method]
|
|
if !found {
|
|
return fmt.Sprintf("raw:%x", params), nil
|
|
}
|
|
|
|
p, err := stmgr.GetParamType(ar, code, method) // todo use api for correct actor registry
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := p.UnmarshalCBOR(bytes.NewReader(params)); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
b, err := json.MarshalIndent(p, "", " ")
|
|
return string(b), err
|
|
}
|
|
|
|
func JsonReturn(code cid.Cid, method abi.MethodNum, ret []byte) (string, error) {
|
|
methodMeta, found := consensus.NewActorRegistry().Methods[code][method] // TODO: use remote
|
|
if !found {
|
|
return "", fmt.Errorf("method %d not found on actor %s", method, code)
|
|
}
|
|
re := reflect.New(methodMeta.Ret.Elem())
|
|
p := re.Interface().(cbg.CBORUnmarshaler)
|
|
if err := p.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
b, err := json.MarshalIndent(p, "", " ")
|
|
return string(b), err
|
|
}
|
|
|
|
var StateWaitMsgCmd = &cli.Command{
|
|
Name: "wait-msg",
|
|
Aliases: []string{"wait-message"},
|
|
Usage: "Wait for a message to appear on chain",
|
|
ArgsUsage: "[messageCid]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "timeout",
|
|
Value: "10m",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
msg, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mw, err := api.StateWaitMsg(ctx, msg, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m, err := api.ChainGetMessage(ctx, msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printMsg(ctx, api, msg, mw, m)
|
|
},
|
|
}
|
|
|
|
var StateSearchMsgCmd = &cli.Command{
|
|
Name: "search-msg",
|
|
Aliases: []string{"search-message"},
|
|
Usage: "Search to see whether a message has appeared on chain",
|
|
ArgsUsage: "[messageCid]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
msg, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mw, err := api.StateSearchMsg(ctx, msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mw == nil {
|
|
return fmt.Errorf("failed to find message: %s", msg)
|
|
}
|
|
|
|
m, err := api.ChainGetMessage(ctx, msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return printMsg(ctx, api, msg, mw, m)
|
|
},
|
|
}
|
|
|
|
func printReceiptReturn(ctx context.Context, api v0api.FullNode, m *types.Message, r types.MessageReceipt) error {
|
|
if len(r.Return) == 0 {
|
|
return nil
|
|
}
|
|
|
|
act, err := api.StateGetActor(ctx, m.To, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
jret, err := JsonReturn(act.Code, m.Method, r.Return)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Decoded return value: ", jret)
|
|
|
|
return nil
|
|
}
|
|
|
|
func printMsg(ctx context.Context, api v0api.FullNode, msg cid.Cid, mw *lapi.MsgLookup, m *types.Message) error {
|
|
if mw == nil {
|
|
fmt.Println("message was not found on chain")
|
|
return nil
|
|
}
|
|
|
|
if mw.Message != msg {
|
|
fmt.Printf("Message was replaced: %s\n", mw.Message)
|
|
}
|
|
|
|
fmt.Printf("Executed in tipset: %s\n", mw.TipSet.Cids())
|
|
fmt.Printf("Exit Code: %d\n", mw.Receipt.ExitCode)
|
|
fmt.Printf("Gas Used: %d\n", mw.Receipt.GasUsed)
|
|
fmt.Printf("Return: %x\n", mw.Receipt.Return)
|
|
if err := printReceiptReturn(ctx, api, m, mw.Receipt); err != nil {
|
|
return err
|
|
}
|
|
if mw.Receipt.EventsRoot != nil {
|
|
fmt.Printf("Events Root: %s\n", mw.Receipt.EventsRoot)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var StateCallCmd = &cli.Command{
|
|
Name: "call",
|
|
Usage: "Invoke a method on an actor locally",
|
|
ArgsUsage: "[toAddress methodId params (optional)]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "",
|
|
Value: builtin.SystemActorAddr.String(),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "value",
|
|
Usage: "specify value field for invocation",
|
|
Value: "0",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "ret",
|
|
Usage: "specify how to parse output (raw, decoded, base64, hex)",
|
|
Value: "decoded",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "encoding",
|
|
Value: "base64",
|
|
Usage: "specify params encoding to parse (base64, hex)",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() < 2 {
|
|
return ShowHelp(cctx, 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)
|
|
}
|
|
|
|
var params []byte
|
|
// If params were passed in, decode them
|
|
if cctx.NArg() > 2 {
|
|
switch cctx.String("encoding") {
|
|
case "base64":
|
|
params, err = base64.StdEncoding.DecodeString(cctx.Args().Get(2))
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding base64 value: %w", err)
|
|
}
|
|
case "hex":
|
|
params, err = hex.DecodeString(cctx.Args().Get(2))
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding hex value: %w", err)
|
|
}
|
|
default:
|
|
return xerrors.Errorf("unrecognized encoding: %s", cctx.String("encoding"))
|
|
}
|
|
}
|
|
|
|
ret, err := api.StateCall(ctx, &types.Message{
|
|
From: froma,
|
|
To: toa,
|
|
Value: types.BigInt(value),
|
|
Method: abi.MethodNum(method),
|
|
Params: params,
|
|
}, ts.Key())
|
|
if err != nil {
|
|
return fmt.Errorf("state call failed: %w", err)
|
|
}
|
|
|
|
if ret.MsgRct.ExitCode != 0 {
|
|
return fmt.Errorf("invocation failed (exit: %d, gasUsed: %d): %s", ret.MsgRct.ExitCode, ret.MsgRct.GasUsed, ret.Error)
|
|
}
|
|
|
|
fmt.Println("Call receipt:")
|
|
fmt.Printf("Exit code: %d\n", ret.MsgRct.ExitCode)
|
|
fmt.Printf("Gas Used: %d\n", ret.MsgRct.GasUsed)
|
|
|
|
switch cctx.String("ret") {
|
|
case "decoded":
|
|
act, err := api.StateGetActor(ctx, toa, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting actor: %w", err)
|
|
}
|
|
|
|
retStr, err := JsonReturn(act.Code, abi.MethodNum(method), ret.MsgRct.Return)
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding return: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Return:\n%s\n", retStr)
|
|
case "raw":
|
|
fmt.Printf("Return: \n%s\n", ret.MsgRct.Return)
|
|
case "hex":
|
|
fmt.Printf("Return: \n%x\n", ret.MsgRct.Return)
|
|
case "base64":
|
|
fmt.Printf("Return: \n%s\n", base64.StdEncoding.EncodeToString(ret.MsgRct.Return))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateCircSupplyCmd = &cli.Command{
|
|
Name: "circulating-supply",
|
|
Usage: "Get the exact current circulating supply of Filecoin",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "vm-supply",
|
|
Usage: "calculates the approximation of the circulating supply used internally by the VM (instead of the exact amount)",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cctx.IsSet("vm-supply") {
|
|
circ, err := api.StateVMCirculatingSupplyInternal(ctx, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Circulating supply: ", types.FIL(circ.FilCirculating))
|
|
fmt.Println("Mined: ", types.FIL(circ.FilMined))
|
|
fmt.Println("Vested: ", types.FIL(circ.FilVested))
|
|
fmt.Println("Burnt: ", types.FIL(circ.FilBurnt))
|
|
fmt.Println("Locked: ", types.FIL(circ.FilLocked))
|
|
} else {
|
|
circ, err := api.StateCirculatingSupply(ctx, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Exact circulating supply: ", types.FIL(circ))
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateSectorCmd = &cli.Command{
|
|
Name: "sector",
|
|
Aliases: []string{"sector-info"},
|
|
Usage: "Get miner sector info",
|
|
ArgsUsage: "[minerAddress] [sectorNumber]",
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 2 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
maddr, err := address.NewFromString(cctx.Args().Get(0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sid, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
si, err := api.StateSectorGetInfo(ctx, maddr, abi.SectorNumber(sid), ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if si == nil {
|
|
return xerrors.Errorf("sector %d for miner %s not found", sid, maddr)
|
|
}
|
|
|
|
fmt.Println("SectorNumber: ", si.SectorNumber)
|
|
fmt.Println("SealProof: ", si.SealProof)
|
|
fmt.Println("SealedCID: ", si.SealedCID)
|
|
if si.SectorKeyCID != nil {
|
|
fmt.Println("SectorKeyCID: ", si.SectorKeyCID)
|
|
}
|
|
fmt.Println("DealIDs: ", si.DealIDs)
|
|
fmt.Println()
|
|
fmt.Println("Activation: ", cliutil.EpochTimeTs(ts.Height(), si.Activation, ts))
|
|
fmt.Println("Expiration: ", cliutil.EpochTimeTs(ts.Height(), si.Expiration, ts))
|
|
fmt.Println()
|
|
fmt.Println("DealWeight: ", si.DealWeight)
|
|
fmt.Println("VerifiedDealWeight: ", si.VerifiedDealWeight)
|
|
fmt.Println("InitialPledge: ", types.FIL(si.InitialPledge))
|
|
fmt.Println("ExpectedDayReward: ", types.FIL(si.ExpectedDayReward))
|
|
fmt.Println("ExpectedStoragePledge: ", types.FIL(si.ExpectedStoragePledge))
|
|
fmt.Println()
|
|
|
|
sp, err := api.StateSectorPartition(ctx, maddr, abi.SectorNumber(sid), ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Deadline: ", sp.Deadline)
|
|
fmt.Println("Partition: ", sp.Partition)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateMarketCmd = &cli.Command{
|
|
Name: "market",
|
|
Usage: "Inspect the storage market actor",
|
|
Subcommands: []*cli.Command{
|
|
stateMarketBalanceCmd,
|
|
},
|
|
}
|
|
|
|
var stateMarketBalanceCmd = &cli.Command{
|
|
Name: "balance",
|
|
Usage: "Get the market balance (locked and escrowed) for a given account",
|
|
ArgsUsage: "[address]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
balance, err := api.StateMarketBalance(ctx, addr, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Escrow: %s\n", types.FIL(balance.Escrow))
|
|
fmt.Printf("Locked: %s\n", types.FIL(balance.Locked))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateNtwkVersionCmd = &cli.Command{
|
|
Name: "network-version",
|
|
Usage: "Returns the network version",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.Args().Present() {
|
|
return ShowHelp(cctx, fmt.Errorf("doesn't expect any arguments"))
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nv, err := api.StateNetworkVersion(ctx, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Network Version: %d\n", nv)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var StateSysActorCIDsCmd = &cli.Command{
|
|
Name: "actor-cids",
|
|
Usage: "Returns the built-in actor bundle manifest ID & system actor cids",
|
|
Flags: []cli.Flag{
|
|
&cli.UintFlag{
|
|
Name: "network-version",
|
|
Usage: "specify network version",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.Args().Present() {
|
|
return ShowHelp(cctx, fmt.Errorf("doesn't expect any arguments"))
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
var nv network.Version
|
|
if cctx.IsSet("network-version") {
|
|
nv = network.Version(cctx.Uint64("network-version"))
|
|
} else {
|
|
nv, err = api.StateNetworkVersion(ctx, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Network Version: %d\n", nv)
|
|
|
|
actorVersion, err := actorstypes.VersionForNetwork(nv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Actor Version: %d\n", actorVersion)
|
|
|
|
manifestCid, ok := actors.GetManifest(actorVersion)
|
|
if ok {
|
|
fmt.Printf("Manifest CID: %v\n", manifestCid)
|
|
}
|
|
|
|
tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
|
|
_, _ = fmt.Fprintln(tw, "\nActor\tCID\t")
|
|
|
|
actorsCids, err := api.StateActorCodeCIDs(ctx, nv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var actorsCidTuples []struct {
|
|
actorName string
|
|
actorCid cid.Cid
|
|
}
|
|
|
|
for name, actorCid := range actorsCids {
|
|
keyVal := struct {
|
|
actorName string
|
|
actorCid cid.Cid
|
|
}{
|
|
actorName: name,
|
|
actorCid: actorCid,
|
|
}
|
|
actorsCidTuples = append(actorsCidTuples, keyVal)
|
|
}
|
|
|
|
sort.Slice(actorsCidTuples, func(i, j int) bool {
|
|
return actorsCidTuples[i].actorName < actorsCidTuples[j].actorName
|
|
})
|
|
|
|
for _, keyVal := range actorsCidTuples {
|
|
_, _ = fmt.Fprintf(tw, "%v\t%v\n", keyVal.actorName, keyVal.actorCid)
|
|
}
|
|
return tw.Flush()
|
|
},
|
|
}
|