1747 lines
40 KiB
Go
1747 lines
40 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/urfave/cli/v2"
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
cborutil "github.com/filecoin-project/go-cbor-util"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
evm "github.com/filecoin-project/go-state-types/builtin/v10/evm"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/account"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
|
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
|
|
|
"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/build"
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
)
|
|
|
|
var ChainCmd = &cli.Command{
|
|
Name: "chain",
|
|
Usage: "Interact with filecoin blockchain",
|
|
Subcommands: []*cli.Command{
|
|
ChainHeadCmd,
|
|
ChainGetBlock,
|
|
ChainReadObjCmd,
|
|
ChainDeleteObjCmd,
|
|
ChainStatObjCmd,
|
|
ChainGetMsgCmd,
|
|
ChainSetHeadCmd,
|
|
ChainListCmd,
|
|
ChainGetCmd,
|
|
ChainBisectCmd,
|
|
ChainExportCmd,
|
|
SlashConsensusFault,
|
|
ChainGasPriceCmd,
|
|
ChainInspectUsage,
|
|
ChainDecodeCmd,
|
|
ChainEncodeCmd,
|
|
ChainDisputeSetCmd,
|
|
ChainPruneCmd,
|
|
ChainExecEVMCmd,
|
|
ChainInvokeEVMCmd,
|
|
},
|
|
}
|
|
|
|
var ChainHeadCmd = &cli.Command{
|
|
Name: "head",
|
|
Usage: "Print chain head",
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
head, err := api.ChainHead(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, c := range head.Cids() {
|
|
afmt.Println(c)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainGetBlock = &cli.Command{
|
|
Name: "get-block",
|
|
Aliases: []string{"getblock"},
|
|
Usage: "Get a block and print its details",
|
|
ArgsUsage: "[blockCid]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "raw",
|
|
Usage: "print just the raw block header",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass cid of block to print")
|
|
}
|
|
|
|
bcid, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
blk, err := api.ChainGetBlock(ctx, bcid)
|
|
if err != nil {
|
|
return xerrors.Errorf("get block failed: %w", err)
|
|
}
|
|
|
|
if cctx.Bool("raw") {
|
|
out, err := json.MarshalIndent(blk, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Println(string(out))
|
|
return nil
|
|
}
|
|
|
|
msgs, err := api.ChainGetBlockMessages(ctx, bcid)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get messages: %w", err)
|
|
}
|
|
|
|
pmsgs, err := api.ChainGetParentMessages(ctx, bcid)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get parent messages: %w", err)
|
|
}
|
|
|
|
recpts, err := api.ChainGetParentReceipts(ctx, bcid)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
//return xerrors.Errorf("failed to get receipts: %w", err)
|
|
}
|
|
|
|
cblock := struct {
|
|
types.BlockHeader
|
|
BlsMessages []*types.Message
|
|
SecpkMessages []*types.SignedMessage
|
|
ParentReceipts []*types.MessageReceipt
|
|
ParentMessages []cid.Cid
|
|
}{}
|
|
|
|
cblock.BlockHeader = *blk
|
|
cblock.BlsMessages = msgs.BlsMessages
|
|
cblock.SecpkMessages = msgs.SecpkMessages
|
|
cblock.ParentReceipts = recpts
|
|
cblock.ParentMessages = apiMsgCids(pmsgs)
|
|
|
|
out, err := json.MarshalIndent(cblock, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Println(string(out))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func apiMsgCids(in []lapi.Message) []cid.Cid {
|
|
out := make([]cid.Cid, len(in))
|
|
for k, v := range in {
|
|
out[k] = v.Cid
|
|
}
|
|
return out
|
|
}
|
|
|
|
var ChainReadObjCmd = &cli.Command{
|
|
Name: "read-obj",
|
|
Usage: "Read the raw bytes of an object",
|
|
ArgsUsage: "[objectCid]",
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
c, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse cid input: %s", err)
|
|
}
|
|
|
|
obj, err := api.ChainReadObj(ctx, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Printf("%x\n", obj)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainDeleteObjCmd = &cli.Command{
|
|
Name: "delete-obj",
|
|
Usage: "Delete an object from the chain blockstore",
|
|
Description: "WARNING: Removing wrong objects from the chain blockstore may lead to sync issues",
|
|
ArgsUsage: "[objectCid]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
c, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse cid input: %s", err)
|
|
}
|
|
|
|
if !cctx.Bool("really-do-it") {
|
|
return xerrors.Errorf("pass the --really-do-it flag to proceed")
|
|
}
|
|
|
|
err = api.ChainDeleteObj(ctx, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Printf("Obj %s deleted\n", c.String())
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainStatObjCmd = &cli.Command{
|
|
Name: "stat-obj",
|
|
Usage: "Collect size and ipld link counts for objs",
|
|
ArgsUsage: "[cid]",
|
|
Description: `Collect object size and ipld link count for an object.
|
|
|
|
When a base is provided it will be walked first, and all links visisted
|
|
will be ignored when the passed in object is walked.
|
|
`,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "base",
|
|
Usage: "ignore links found in this obj",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
obj, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse cid input: %s", err)
|
|
}
|
|
|
|
base := cid.Undef
|
|
if cctx.IsSet("base") {
|
|
base, err = cid.Decode(cctx.String("base"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
stats, err := api.ChainStatObj(ctx, obj, base)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Printf("Links: %d\n", stats.Links)
|
|
afmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainGetMsgCmd = &cli.Command{
|
|
Name: "getmessage",
|
|
Aliases: []string{"get-message", "get-msg"},
|
|
Usage: "Get and print a message by its cid",
|
|
ArgsUsage: "[messageCid]",
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass a cid of a message to get")
|
|
}
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
c, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to parse cid input: %w", err)
|
|
}
|
|
|
|
mb, err := api.ChainReadObj(ctx, c)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to read object: %w", err)
|
|
}
|
|
|
|
var i interface{}
|
|
m, err := types.DecodeMessage(mb)
|
|
if err != nil {
|
|
sm, err := types.DecodeSignedMessage(mb)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to decode object as a message: %w", err)
|
|
}
|
|
i = sm
|
|
} else {
|
|
i = m
|
|
}
|
|
|
|
enc, err := json.MarshalIndent(i, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Println(string(enc))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainSetHeadCmd = &cli.Command{
|
|
Name: "sethead",
|
|
Aliases: []string{"set-head"},
|
|
Usage: "manually set the local nodes head tipset (Caution: normally only used for recovery)",
|
|
ArgsUsage: "[tipsetkey]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "genesis",
|
|
Usage: "reset head to genesis",
|
|
},
|
|
&cli.Uint64Flag{
|
|
Name: "epoch",
|
|
Usage: "reset head to given epoch",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
var ts *types.TipSet
|
|
|
|
if cctx.Bool("genesis") {
|
|
ts, err = api.ChainGetGenesis(ctx)
|
|
}
|
|
if ts == nil && cctx.IsSet("epoch") {
|
|
ts, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), types.EmptyTSK)
|
|
}
|
|
if ts == nil {
|
|
ts, err = parseTipSet(ctx, api, cctx.Args().Slice())
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ts == nil {
|
|
return fmt.Errorf("must pass cids for tipset to set as head")
|
|
}
|
|
|
|
if err := api.ChainSetHead(ctx, ts.Key()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainInspectUsage = &cli.Command{
|
|
Name: "inspect-usage",
|
|
Usage: "Inspect block space usage of a given tipset",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset to view block space usage of",
|
|
Value: "@head",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "length",
|
|
Usage: "length of chain to inspect block space usage for",
|
|
Value: 1,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "num-results",
|
|
Usage: "number of results to print per category",
|
|
Value: 10,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
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
|
|
}
|
|
|
|
cur := ts
|
|
var msgs []lapi.Message
|
|
for i := 0; i < cctx.Int("length"); i++ {
|
|
pmsgs, err := api.ChainGetParentMessages(ctx, cur.Blocks()[0].Cid())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msgs = append(msgs, pmsgs...)
|
|
|
|
next, err := api.ChainGetTipSet(ctx, cur.Parents())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cur = next
|
|
}
|
|
|
|
codeCache := make(map[address.Address]cid.Cid)
|
|
|
|
lookupActorCode := func(a address.Address) (cid.Cid, error) {
|
|
c, ok := codeCache[a]
|
|
if ok {
|
|
return c, nil
|
|
}
|
|
|
|
act, err := api.StateGetActor(ctx, a, ts.Key())
|
|
if err != nil {
|
|
return cid.Undef, err
|
|
}
|
|
|
|
codeCache[a] = act.Code
|
|
return act.Code, nil
|
|
}
|
|
|
|
bySender := make(map[string]int64)
|
|
byDest := make(map[string]int64)
|
|
byMethod := make(map[string]int64)
|
|
bySenderC := make(map[string]int64)
|
|
byDestC := make(map[string]int64)
|
|
byMethodC := make(map[string]int64)
|
|
|
|
var sum int64
|
|
for _, m := range msgs {
|
|
bySender[m.Message.From.String()] += m.Message.GasLimit
|
|
bySenderC[m.Message.From.String()]++
|
|
byDest[m.Message.To.String()] += m.Message.GasLimit
|
|
byDestC[m.Message.To.String()]++
|
|
sum += m.Message.GasLimit
|
|
|
|
code, err := lookupActorCode(m.Message.To)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
|
|
mm := filcns.NewActorRegistry().Methods[code][m.Message.Method] // TODO: use remote map
|
|
|
|
byMethod[mm.Name] += m.Message.GasLimit
|
|
byMethodC[mm.Name]++
|
|
}
|
|
|
|
type keyGasPair struct {
|
|
Key string
|
|
Gas int64
|
|
}
|
|
|
|
mapToSortedKvs := func(m map[string]int64) []keyGasPair {
|
|
var vals []keyGasPair
|
|
for k, v := range m {
|
|
vals = append(vals, keyGasPair{
|
|
Key: k,
|
|
Gas: v,
|
|
})
|
|
}
|
|
sort.Slice(vals, func(i, j int) bool {
|
|
return vals[i].Gas > vals[j].Gas
|
|
})
|
|
return vals
|
|
}
|
|
|
|
senderVals := mapToSortedKvs(bySender)
|
|
destVals := mapToSortedKvs(byDest)
|
|
methodVals := mapToSortedKvs(byMethod)
|
|
|
|
numRes := cctx.Int("num-results")
|
|
|
|
afmt.Printf("Total Gas Limit: %d\n", sum)
|
|
afmt.Printf("By Sender:\n")
|
|
for i := 0; i < numRes && i < len(senderVals); i++ {
|
|
sv := senderVals[i]
|
|
afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, bySenderC[sv.Key])
|
|
}
|
|
afmt.Println()
|
|
afmt.Printf("By Receiver:\n")
|
|
for i := 0; i < numRes && i < len(destVals); i++ {
|
|
sv := destVals[i]
|
|
afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byDestC[sv.Key])
|
|
}
|
|
afmt.Println()
|
|
afmt.Printf("By Method:\n")
|
|
for i := 0; i < numRes && i < len(methodVals); i++ {
|
|
sv := methodVals[i]
|
|
afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byMethodC[sv.Key])
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainListCmd = &cli.Command{
|
|
Name: "list",
|
|
Aliases: []string{"love"},
|
|
Usage: "View a segment of the chain",
|
|
Flags: []cli.Flag{
|
|
&cli.Uint64Flag{Name: "height", DefaultText: "current head"},
|
|
&cli.IntFlag{Name: "count", Value: 30},
|
|
&cli.StringFlag{
|
|
Name: "format",
|
|
Usage: "specify the format to print out tipsets",
|
|
Value: "<height>: (<time>) <blocks>",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "gas-stats",
|
|
Usage: "view gas statistics for the chain",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
var head *types.TipSet
|
|
|
|
if cctx.IsSet("height") {
|
|
head, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("height")), types.EmptyTSK)
|
|
} else {
|
|
head, err = api.ChainHead(ctx)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
count := cctx.Int("count")
|
|
if count < 1 {
|
|
return nil
|
|
}
|
|
|
|
tss := make([]*types.TipSet, 0, count)
|
|
tss = append(tss, head)
|
|
|
|
for i := 1; i < count; i++ {
|
|
if head.Height() == 0 {
|
|
break
|
|
}
|
|
|
|
head, err = api.ChainGetTipSet(ctx, head.Parents())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tss = append(tss, head)
|
|
}
|
|
|
|
if cctx.Bool("gas-stats") {
|
|
otss := make([]*types.TipSet, 0, len(tss))
|
|
for i := len(tss) - 1; i >= 0; i-- {
|
|
otss = append(otss, tss[i])
|
|
}
|
|
tss = otss
|
|
for i, ts := range tss {
|
|
pbf := ts.Blocks()[0].ParentBaseFee
|
|
afmt.Printf("%d: %d blocks (baseFee: %s -> maxFee: %s)\n", ts.Height(), len(ts.Blocks()), ts.Blocks()[0].ParentBaseFee, types.FIL(types.BigMul(pbf, types.NewInt(uint64(build.BlockGasLimit)))))
|
|
|
|
for _, b := range ts.Blocks() {
|
|
msgs, err := api.ChainGetBlockMessages(ctx, b.Cid())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var limitSum int64
|
|
psum := big.NewInt(0)
|
|
for _, m := range msgs.BlsMessages {
|
|
limitSum += m.GasLimit
|
|
psum = big.Add(psum, m.GasPremium)
|
|
}
|
|
|
|
for _, m := range msgs.SecpkMessages {
|
|
limitSum += m.Message.GasLimit
|
|
psum = big.Add(psum, m.Message.GasPremium)
|
|
}
|
|
|
|
lenmsgs := len(msgs.BlsMessages) + len(msgs.SecpkMessages)
|
|
|
|
avgpremium := big.Zero()
|
|
if lenmsgs > 0 {
|
|
avgpremium = big.Div(psum, big.NewInt(int64(lenmsgs)))
|
|
}
|
|
|
|
afmt.Printf("\t%s: \t%d msgs, gasLimit: %d / %d (%0.2f%%), avgPremium: %s\n", b.Miner, len(msgs.BlsMessages)+len(msgs.SecpkMessages), limitSum, build.BlockGasLimit, 100*float64(limitSum)/float64(build.BlockGasLimit), avgpremium)
|
|
}
|
|
if i < len(tss)-1 {
|
|
msgs, err := api.ChainGetParentMessages(ctx, tss[i+1].Blocks()[0].Cid())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var limitSum int64
|
|
for _, m := range msgs {
|
|
limitSum += m.Message.GasLimit
|
|
}
|
|
|
|
recpts, err := api.ChainGetParentReceipts(ctx, tss[i+1].Blocks()[0].Cid())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var gasUsed int64
|
|
for _, r := range recpts {
|
|
gasUsed += r.GasUsed
|
|
}
|
|
|
|
gasEfficiency := 100 * float64(gasUsed) / float64(limitSum)
|
|
gasCapacity := 100 * float64(limitSum) / float64(build.BlockGasLimit)
|
|
|
|
afmt.Printf("\ttipset: \t%d msgs, %d (%0.2f%%) / %d (%0.2f%%)\n", len(msgs), gasUsed, gasEfficiency, limitSum, gasCapacity)
|
|
}
|
|
afmt.Println()
|
|
}
|
|
} else {
|
|
for i := len(tss) - 1; i >= 0; i-- {
|
|
printTipSet(cctx.String("format"), tss[i], afmt)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainGetCmd = &cli.Command{
|
|
Name: "get",
|
|
Usage: "Get chain DAG node by path",
|
|
ArgsUsage: "[path]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "as-type",
|
|
Usage: "specify type to interpret output as",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "verbose",
|
|
Value: false,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset for /pstate (pass comma separated array of cids)",
|
|
},
|
|
},
|
|
Description: `Get ipld node under a specified path:
|
|
|
|
lotus chain get /ipfs/[cid]/some/path
|
|
|
|
Path prefixes:
|
|
- /ipfs/[cid], /ipld/[cid] - traverse IPLD path
|
|
- /pstate - traverse from head.ParentStateRoot
|
|
|
|
Note:
|
|
You can use special path elements to traverse through some data structures:
|
|
- /ipfs/[cid]/@H:elem - get 'elem' from hamt
|
|
- /ipfs/[cid]/@Hi:123 - get varint elem 123 from hamt
|
|
- /ipfs/[cid]/@Hu:123 - get uvarint elem 123 from hamt
|
|
- /ipfs/[cid]/@Ha:t01 - get element under Addr(t01).Bytes
|
|
- /ipfs/[cid]/@A:10 - get 10th amt element
|
|
- .../@Ha:t01/@state - get pretty map-based actor state
|
|
|
|
List of --as-type types:
|
|
- raw
|
|
- block
|
|
- message
|
|
- smessage, signedmessage
|
|
- actor
|
|
- amt
|
|
- hamt-epoch
|
|
- hamt-address
|
|
- cronevent
|
|
- account-state
|
|
`,
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
p := path.Clean(cctx.Args().First())
|
|
if strings.HasPrefix(p, "/pstate") {
|
|
p = p[len("/pstate"):]
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p = "/ipfs/" + ts.ParentState().String() + p
|
|
if cctx.Bool("verbose") {
|
|
afmt.Println(p)
|
|
}
|
|
}
|
|
|
|
obj, err := api.ChainGetNode(ctx, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t := strings.ToLower(cctx.String("as-type"))
|
|
if t == "" {
|
|
b, err := json.MarshalIndent(obj.Obj, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
afmt.Println(string(b))
|
|
return nil
|
|
}
|
|
|
|
var cbu cbg.CBORUnmarshaler
|
|
switch t {
|
|
case "raw":
|
|
cbu = nil
|
|
case "block":
|
|
cbu = new(types.BlockHeader)
|
|
case "message":
|
|
cbu = new(types.Message)
|
|
case "smessage", "signedmessage":
|
|
cbu = new(types.SignedMessage)
|
|
case "actor":
|
|
cbu = new(types.Actor)
|
|
case "amt":
|
|
return handleAmt(ctx, api, obj.Cid)
|
|
case "hamt-epoch":
|
|
return handleHamtEpoch(ctx, api, obj.Cid)
|
|
case "hamt-address":
|
|
return handleHamtAddress(ctx, api, obj.Cid)
|
|
case "cronevent":
|
|
cbu = new(power.CronEvent)
|
|
case "account-state":
|
|
cbu = new(account.State)
|
|
case "miner-state":
|
|
cbu = new(miner.State)
|
|
case "power-state":
|
|
cbu = new(power.State)
|
|
case "market-state":
|
|
cbu = new(market.State)
|
|
default:
|
|
return fmt.Errorf("unknown type: %q", t)
|
|
}
|
|
|
|
raw, err := api.ChainReadObj(ctx, obj.Cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cbu == nil {
|
|
afmt.Printf("%x", raw)
|
|
return nil
|
|
}
|
|
|
|
if err := cbu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal as %q", t)
|
|
}
|
|
|
|
b, err := json.MarshalIndent(cbu, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
afmt.Println(string(b))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
type apiIpldStore struct {
|
|
ctx context.Context
|
|
api v0api.FullNode
|
|
}
|
|
|
|
func (ht *apiIpldStore) Context() context.Context {
|
|
return ht.ctx
|
|
}
|
|
|
|
func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
|
|
raw, err := ht.api.ChainReadObj(ctx, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cu, ok := out.(cbg.CBORUnmarshaler)
|
|
if ok {
|
|
if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("Object does not implement CBORUnmarshaler")
|
|
}
|
|
|
|
func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
|
panic("No mutations allowed")
|
|
}
|
|
|
|
func handleAmt(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
|
|
s := &apiIpldStore{ctx, api}
|
|
mp, err := adt.AsArray(s, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mp.ForEach(nil, func(key int64) error {
|
|
fmt.Printf("%d\n", key)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func handleHamtEpoch(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
|
|
s := &apiIpldStore{ctx, api}
|
|
mp, err := adt.AsMap(s, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mp.ForEach(nil, func(key string) error {
|
|
ik, err := abi.ParseIntKey(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("%d\n", ik)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func handleHamtAddress(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
|
|
s := &apiIpldStore{ctx, api}
|
|
mp, err := adt.AsMap(s, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mp.ForEach(nil, func(key string) error {
|
|
addr, err := address.NewFromBytes([]byte(key))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("%s\n", addr)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func printTipSet(format string, ts *types.TipSet, afmt *AppFmt) {
|
|
format = strings.ReplaceAll(format, "<height>", fmt.Sprint(ts.Height()))
|
|
format = strings.ReplaceAll(format, "<time>", time.Unix(int64(ts.MinTimestamp()), 0).Format(time.Stamp))
|
|
blks := "[ "
|
|
for _, b := range ts.Blocks() {
|
|
blks += fmt.Sprintf("%s: %s,", b.Cid(), b.Miner)
|
|
}
|
|
blks += " ]"
|
|
|
|
sCids := make([]string, 0, len(blks))
|
|
|
|
for _, c := range ts.Cids() {
|
|
sCids = append(sCids, c.String())
|
|
}
|
|
|
|
format = strings.ReplaceAll(format, "<tipset>", strings.Join(sCids, ","))
|
|
format = strings.ReplaceAll(format, "<blocks>", blks)
|
|
format = strings.ReplaceAll(format, "<weight>", fmt.Sprint(ts.Blocks()[0].ParentWeight))
|
|
|
|
afmt.Println(format)
|
|
}
|
|
|
|
var ChainBisectCmd = &cli.Command{
|
|
Name: "bisect",
|
|
Usage: "bisect chain for an event",
|
|
ArgsUsage: "[minHeight maxHeight path shellCommand <shellCommandArgs (if any)>]",
|
|
Description: `Bisect the chain state tree:
|
|
|
|
lotus chain bisect [min height] [max height] '1/2/3/state/path' 'shell command' 'args'
|
|
|
|
Returns the first tipset in which condition is true
|
|
v
|
|
[start] FFFFFFFTTT [end]
|
|
|
|
Example: find height at which deal ID 100 000 appeared
|
|
- lotus chain bisect 1 32000 '@Ha:t03/1' jq -e '.[2] > 100000'
|
|
|
|
For special path elements see 'chain get' help
|
|
`,
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() < 4 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
start, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
end, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
subPath := cctx.Args().Get(2)
|
|
|
|
highest, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(end), types.EmptyTSK)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting end tipset: %w", err)
|
|
}
|
|
|
|
prev := highest.Height()
|
|
|
|
for {
|
|
mid := (start + end) / 2
|
|
if end-start == 1 {
|
|
mid = end
|
|
start = end
|
|
}
|
|
|
|
midTs, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(mid), highest.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path := "/ipld/" + midTs.ParentState().String() + "/" + subPath
|
|
afmt.Printf("* Testing %d (%d - %d) (%s): ", mid, start, end, path)
|
|
|
|
nd, err := api.ChainGetNode(ctx, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := json.MarshalIndent(nd.Obj, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, cctx.Args().Get(3), cctx.Args().Slice()[4:]...)
|
|
cmd.Stdin = bytes.NewReader(b)
|
|
|
|
var out bytes.Buffer
|
|
var serr bytes.Buffer
|
|
|
|
cmd.Stdout = &out
|
|
cmd.Stderr = &serr
|
|
|
|
switch cmd.Run().(type) {
|
|
case nil:
|
|
// it's lower
|
|
if strings.TrimSpace(out.String()) != "false" {
|
|
end = mid
|
|
highest = midTs
|
|
afmt.Println("true")
|
|
} else {
|
|
start = mid
|
|
afmt.Printf("false (cli)\n")
|
|
}
|
|
case *exec.ExitError:
|
|
if len(serr.String()) > 0 {
|
|
afmt.Println("error")
|
|
|
|
afmt.Printf("> Command: %s\n---->\n", strings.Join(cctx.Args().Slice()[3:], " "))
|
|
afmt.Println(string(b))
|
|
afmt.Println("<----")
|
|
return xerrors.Errorf("error running bisect check: %s", serr.String())
|
|
}
|
|
|
|
start = mid
|
|
afmt.Println("false")
|
|
default:
|
|
return err
|
|
}
|
|
|
|
if start == end {
|
|
if strings.TrimSpace(out.String()) == "true" {
|
|
afmt.Println(midTs.Height())
|
|
} else {
|
|
afmt.Println(prev)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
prev = abi.ChainEpoch(mid)
|
|
}
|
|
},
|
|
}
|
|
|
|
var ChainExportCmd = &cli.Command{
|
|
Name: "export",
|
|
Usage: "export chain to a car file",
|
|
ArgsUsage: "[outputPath]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset to start the export from",
|
|
Value: "@head",
|
|
},
|
|
&cli.Int64Flag{
|
|
Name: "recent-stateroots",
|
|
Usage: "specify the number of recent state roots to include in the export",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "skip-old-msgs",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must specify filename to export chain to")
|
|
}
|
|
|
|
rsrs := abi.ChainEpoch(cctx.Int64("recent-stateroots"))
|
|
if cctx.IsSet("recent-stateroots") && rsrs < build.Finality {
|
|
return fmt.Errorf("\"recent-stateroots\" has to be greater than %d", build.Finality)
|
|
}
|
|
|
|
fi, err := createExportFile(cctx.App, cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
err := fi.Close()
|
|
if err != nil {
|
|
fmt.Printf("error closing output file: %+v", err)
|
|
}
|
|
}()
|
|
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
skipold := cctx.Bool("skip-old-msgs")
|
|
|
|
if rsrs == 0 && skipold {
|
|
return fmt.Errorf("must pass recent stateroots along with skip-old-msgs")
|
|
}
|
|
|
|
stream, err := api.ChainExport(ctx, rsrs, skipold, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var last bool
|
|
for b := range stream {
|
|
last = len(b) == 0
|
|
|
|
_, err := fi.Write(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !last {
|
|
return xerrors.Errorf("incomplete export (remote connection lost?)")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var SlashConsensusFault = &cli.Command{
|
|
Name: "slash-consensus",
|
|
Usage: "Report consensus fault",
|
|
ArgsUsage: "[blockCid1 blockCid2]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "optionally specify the account to report consensus from",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "extra",
|
|
Usage: "Extra block cid",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
srv, err := GetFullNodeServices(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer srv.Close() //nolint:errcheck
|
|
|
|
a := srv.FullNodeAPI()
|
|
ctx := ReqContext(cctx)
|
|
|
|
c1, err := cid.Parse(cctx.Args().Get(0))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing cid 1: %w", err)
|
|
}
|
|
|
|
b1, err := a.ChainGetBlock(ctx, c1)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting block 1: %w", err)
|
|
}
|
|
|
|
c2, err := cid.Parse(cctx.Args().Get(1))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing cid 2: %w", err)
|
|
}
|
|
|
|
b2, err := a.ChainGetBlock(ctx, c2)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting block 2: %w", err)
|
|
}
|
|
|
|
if b1.Miner != b2.Miner {
|
|
return xerrors.Errorf("block1.miner:%s block2.miner:%s", b1.Miner, b2.Miner)
|
|
}
|
|
|
|
var fromAddr address.Address
|
|
if from := cctx.String("from"); from == "" {
|
|
defaddr, err := a.WalletDefaultAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = defaddr
|
|
} else {
|
|
addr, err := address.NewFromString(from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = addr
|
|
}
|
|
|
|
bh1, err := cborutil.Dump(b1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bh2, err := cborutil.Dump(b2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
params := miner.ReportConsensusFaultParams{
|
|
BlockHeader1: bh1,
|
|
BlockHeader2: bh2,
|
|
}
|
|
|
|
if cctx.String("extra") != "" {
|
|
cExtra, err := cid.Parse(cctx.String("extra"))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing cid extra: %w", err)
|
|
}
|
|
|
|
bExtra, err := a.ChainGetBlock(ctx, cExtra)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting block extra: %w", err)
|
|
}
|
|
|
|
be, err := cborutil.Dump(bExtra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
params.BlockHeaderExtra = be
|
|
}
|
|
|
|
enc, err := actors.SerializeParams(¶ms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
proto := &api.MessagePrototype{
|
|
Message: types.Message{
|
|
To: b2.Miner,
|
|
From: fromAddr,
|
|
Value: types.NewInt(0),
|
|
Method: builtin.MethodsMiner.ReportConsensusFault,
|
|
Params: enc,
|
|
},
|
|
}
|
|
|
|
smsg, err := InteractiveSend(ctx, cctx, srv, proto)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Println(smsg.Cid())
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainGasPriceCmd = &cli.Command{
|
|
Name: "gas-price",
|
|
Usage: "Estimate gas prices",
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
nb := []int{1, 2, 3, 5, 10, 20, 50, 100, 300}
|
|
for _, nblocks := range nb {
|
|
addr := builtin.SystemActorAddr // TODO: make real when used in GasEstimateGasPremium
|
|
|
|
est, err := api.GasEstimateGasPremium(ctx, uint64(nblocks), addr, 10000, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainDecodeCmd = &cli.Command{
|
|
Name: "decode",
|
|
Usage: "decode various types",
|
|
Subcommands: []*cli.Command{
|
|
chainDecodeParamsCmd,
|
|
},
|
|
}
|
|
|
|
var chainDecodeParamsCmd = &cli.Command{
|
|
Name: "params",
|
|
Usage: "Decode message params",
|
|
ArgsUsage: "[toAddr method params]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "encoding",
|
|
Value: "base64",
|
|
Usage: "specify input encoding to parse",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if cctx.NArg() != 3 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
to, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing toAddr: %w", err)
|
|
}
|
|
|
|
method, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64)
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing method id: %w", err)
|
|
}
|
|
|
|
var params []byte
|
|
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"))
|
|
}
|
|
ts, err := LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
act, err := api.StateGetActor(ctx, to, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting actor: %w", err)
|
|
}
|
|
|
|
pstr, err := JsonParams(act.Code, abi.MethodNum(method), params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
afmt.Println(pstr)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainEncodeCmd = &cli.Command{
|
|
Name: "encode",
|
|
Usage: "encode various types",
|
|
Subcommands: []*cli.Command{
|
|
chainEncodeParamsCmd,
|
|
},
|
|
}
|
|
|
|
var chainEncodeParamsCmd = &cli.Command{
|
|
Name: "params",
|
|
Usage: "Encodes the given JSON params",
|
|
ArgsUsage: "[dest method params]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "encoding",
|
|
Value: "base64",
|
|
Usage: "specify input encoding to parse",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "to-code",
|
|
Usage: "interpret dest as code CID instead of as address",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
if cctx.NArg() != 3 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
method, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64)
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing method id: %w", err)
|
|
}
|
|
|
|
ctx := ReqContext(cctx)
|
|
|
|
var p []byte
|
|
if !cctx.Bool("to-code") {
|
|
svc, err := GetFullNodeServices(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer svc.Close() // nolint
|
|
|
|
to, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing to addr: %w", err)
|
|
}
|
|
|
|
p, err = svc.DecodeTypedParamsFromJSON(ctx, to, abi.MethodNum(method), cctx.Args().Get(2))
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding json params: %w", err)
|
|
}
|
|
} else {
|
|
api, done, err := GetFullNodeAPIV1(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer done()
|
|
|
|
to, err := cid.Parse(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing to addr: %w", err)
|
|
}
|
|
|
|
p, err = api.StateEncodeParams(ctx, to, abi.MethodNum(method), json.RawMessage(cctx.Args().Get(2)))
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding json params: %w", err)
|
|
}
|
|
}
|
|
|
|
switch cctx.String("encoding") {
|
|
case "base64", "b64":
|
|
afmt.Println(base64.StdEncoding.EncodeToString(p))
|
|
case "hex":
|
|
afmt.Println(hex.EncodeToString(p))
|
|
default:
|
|
return xerrors.Errorf("unknown encoding")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// createExportFile returns the export file handle from the app metadata, or creates a new file if it doesn't exist
|
|
func createExportFile(app *cli.App, path string) (io.WriteCloser, error) {
|
|
if wc, ok := app.Metadata["export-file"]; ok {
|
|
return wc.(io.WriteCloser), nil
|
|
}
|
|
|
|
fi, err := os.Create(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fi, nil
|
|
}
|
|
|
|
var ChainPruneCmd = &cli.Command{
|
|
Name: "prune",
|
|
Usage: "prune the stored chain state and perform garbage collection",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "online-gc",
|
|
Value: false,
|
|
Usage: "use online gc for garbage collecting the coldstore",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "moving-gc",
|
|
Value: false,
|
|
Usage: "use moving gc for garbage collecting the coldstore",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "move-to",
|
|
Value: "",
|
|
Usage: "specify new path for coldstore during moving gc",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "retention",
|
|
Value: -1,
|
|
Usage: "specify state retention policy",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := GetFullNodeAPIV1(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
opts := lapi.PruneOpts{}
|
|
if cctx.Bool("online-gc") {
|
|
opts.MovingGC = false
|
|
}
|
|
if cctx.Bool("moving-gc") {
|
|
opts.MovingGC = true
|
|
}
|
|
opts.RetainState = int64(cctx.Int("retention"))
|
|
|
|
return api.ChainPrune(ctx, opts)
|
|
},
|
|
}
|
|
|
|
var ChainExecEVMCmd = &cli.Command{
|
|
Name: "create-evm-actor",
|
|
Usage: "Create an new EVM actor via the init actor and return its address",
|
|
ArgsUsage: "contract",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "optionally specify the account to use for sending the exec message",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "hex",
|
|
Usage: "use when input contract is in hex",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if argc := cctx.Args().Len(); argc != 1 {
|
|
return xerrors.Errorf("must pass the contract init code")
|
|
}
|
|
|
|
contract, err := os.ReadFile(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to read contract: %w", err)
|
|
}
|
|
if cctx.Bool("hex") {
|
|
contract, err = hex.DecodeString(string(contract))
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to decode contract: %w", err)
|
|
}
|
|
}
|
|
|
|
constructorParams, err := actors.SerializeParams(&evm.ConstructorParams{
|
|
Initcode: contract,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to serialize constructor params: %w", err)
|
|
}
|
|
|
|
evmActorCid, ok := actors.GetActorCodeID(actors.Version8, "evm")
|
|
if !ok {
|
|
return xerrors.Errorf("failed to lookup evm actor code CID")
|
|
}
|
|
|
|
params, err := actors.SerializeParams(&init8.ExecParams{
|
|
CodeCID: evmActorCid,
|
|
ConstructorParams: constructorParams,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to serialize init actor exec params: %w", err)
|
|
}
|
|
|
|
var fromAddr address.Address
|
|
if from := cctx.String("from"); from == "" {
|
|
defaddr, err := api.WalletDefaultAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = defaddr
|
|
} else {
|
|
addr, err := address.NewFromString(from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = addr
|
|
}
|
|
|
|
msg := &types.Message{
|
|
To: builtin.InitActorAddr,
|
|
From: fromAddr,
|
|
Value: big.Zero(),
|
|
Method: 2,
|
|
Params: params,
|
|
}
|
|
|
|
afmt.Println("sending message...")
|
|
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to push message: %w", err)
|
|
}
|
|
|
|
afmt.Println("waiting for message to execute...")
|
|
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0)
|
|
if err != nil {
|
|
return xerrors.Errorf("error waiting for message: %w", err)
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
return xerrors.Errorf("actor execution failed")
|
|
}
|
|
|
|
var result init8.ExecReturn
|
|
r := bytes.NewReader(wait.Receipt.Return)
|
|
if err := result.UnmarshalCBOR(r); err != nil {
|
|
return xerrors.Errorf("error unmarshaling return value: %w", err)
|
|
}
|
|
|
|
afmt.Printf("ID Address: %s\n", result.IDAddress)
|
|
afmt.Printf("Robust Address: %s\n", result.RobustAddress)
|
|
|
|
if len(wait.Receipt.Return) > 0 {
|
|
result := base64.StdEncoding.EncodeToString(wait.Receipt.Return)
|
|
afmt.Printf("Return: %s\n", result)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var ChainInvokeEVMCmd = &cli.Command{
|
|
Name: "invoke-evm-actor",
|
|
Usage: "Invoke a contract entry point in an EVM actor",
|
|
ArgsUsage: "address contract-entry-point [input-data]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "optionally specify the account to use for sending the exec message",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
afmt := NewAppFmt(cctx.App)
|
|
|
|
api, closer, err := GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
ctx := ReqContext(cctx)
|
|
|
|
if argc := cctx.Args().Len(); argc < 2 || argc > 3 {
|
|
return xerrors.Errorf("must pass the address, entry point and (optionally) input data")
|
|
}
|
|
|
|
addr, err := address.NewFromString(cctx.Args().Get(0))
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to decode address: %w", err)
|
|
}
|
|
|
|
entryPoint, err := hex.DecodeString(cctx.Args().Get(1))
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to decode hex entry point: %w", err)
|
|
}
|
|
|
|
var inputData []byte
|
|
if cctx.Args().Len() == 3 {
|
|
inputData, err = hex.DecodeString(cctx.Args().Get(2))
|
|
if err != nil {
|
|
return xerrors.Errorf("decoding hex input data: %w", err)
|
|
}
|
|
}
|
|
|
|
params, err := actors.SerializeParams(&evm.InvokeParams{
|
|
InputData: append(entryPoint, inputData...),
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to serialize evm actor invoke params: %w", err)
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
if err := cbg.WriteByteArray(&buffer, params); err != nil {
|
|
return xerrors.Errorf("failed to encode evm params as cbor: %w", err)
|
|
}
|
|
params = buffer.Bytes()
|
|
|
|
var fromAddr address.Address
|
|
if from := cctx.String("from"); from == "" {
|
|
defaddr, err := api.WalletDefaultAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = defaddr
|
|
} else {
|
|
addr, err := address.NewFromString(from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddr = addr
|
|
}
|
|
|
|
msg := &types.Message{
|
|
To: addr,
|
|
From: fromAddr,
|
|
Value: big.Zero(),
|
|
Method: abi.MethodNum(2),
|
|
Params: params,
|
|
}
|
|
|
|
afmt.Println("sending message...")
|
|
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to push message: %w", err)
|
|
}
|
|
|
|
afmt.Println("waiting for message to execute...")
|
|
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0)
|
|
if err != nil {
|
|
return xerrors.Errorf("error waiting for message: %w", err)
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
return xerrors.Errorf("actor execution failed")
|
|
}
|
|
|
|
afmt.Println("Gas used: ", wait.Receipt.GasUsed)
|
|
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
|
if err != nil {
|
|
return xerrors.Errorf("evm result not correctly encoded: %w", err)
|
|
}
|
|
|
|
if len(result) > 0 {
|
|
afmt.Println(hex.EncodeToString(result))
|
|
} else {
|
|
afmt.Println("OK")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|