fa93c23813
This commit moderately refactors the ranged export code. It addresses several problems: * Code does not finish cleanly and things hang on ctrl-c * Same block is read multiple times in a row (artificially increasing cached blockstore metrics to 50%) * It is unclear whether there are additional races (a single worker quits when reaching height 0) * CARs produced have duplicated blocks (~400k for an 80M-blocks CAR or so). Some blocks appear up to 5 times. * Using pointers for tasks where it is not necessary. The changes: * Use a FIFO instead of stack: simpler implementation as its own type. This has not proven to be much more memory-friendly, but it has not made things worse either. * We avoid a probably not small amount of allocations by not using unnecessary pointers. * Fix duplicated blocks by atomically checking+adding to CID set. * Context-termination now works correctly. Worker lifetime is correctly tracked and all channels are closed, avoiding any memory leaks and deadlocks. * We ensure all work is finished before finishing, something that might have been broken in some edge cases previously. In practice, we would not have seen this except perhaps in very early snapshots close to genesis. Initial testing shows the code is currently about 5% faster. Resulting snapshots do not have duplicates so they are a bit smaller. We have manually verified that no CID is lost versus previous results, with both old and recent snapshots.
1676 lines
37 KiB
Go
1676 lines
37 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"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"
|
|
"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"
|
|
"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,
|
|
ChainExportRangeCmd,
|
|
SlashConsensusFault,
|
|
ChainGasPriceCmd,
|
|
ChainInspectUsage,
|
|
ChainDecodeCmd,
|
|
ChainEncodeCmd,
|
|
ChainDisputeSetCmd,
|
|
ChainPruneCmd,
|
|
},
|
|
}
|
|
|
|
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.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
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)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(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)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(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)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(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.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
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)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(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 := consensus.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)
|
|
|
|
if cctx.NArg() != 1 {
|
|
return IncorrectNumArgs(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.NArg() != 1 {
|
|
return IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
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 ChainExportRangeCmd = &cli.Command{
|
|
Name: "export-range",
|
|
Usage: "export chain to a car file",
|
|
ArgsUsage: "[outputPath]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "head",
|
|
Usage: "specify tipset to start the export from (higher epoch)",
|
|
Value: "@head",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tail",
|
|
Usage: "specify tipset to end the export at (lower epoch)",
|
|
Value: "@tail",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "messages",
|
|
Usage: "specify if messages should be include",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "receipts",
|
|
Usage: "specify if receipts should be include",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "stateroots",
|
|
Usage: "specify if stateroots should be include",
|
|
Value: false,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "workers",
|
|
Usage: "specify the number of workers",
|
|
Value: 1,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "cache-size",
|
|
Usage: "specify the size of the cache (in objects) to use while exporting",
|
|
Value: 100_000,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "write-buffer",
|
|
Usage: "specify write buffer size",
|
|
Value: 1 << 20,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "internal",
|
|
Usage: "will cause the daemon to write the file locally",
|
|
},
|
|
},
|
|
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")
|
|
}
|
|
|
|
head, tail := &types.TipSet{}, &types.TipSet{}
|
|
headstr := cctx.String("head")
|
|
if headstr == "@head" {
|
|
head, err = api.ChainHead(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
head, err = ParseTipSetRef(ctx, api, headstr)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing head: %w", err)
|
|
}
|
|
}
|
|
tailstr := cctx.String("tail")
|
|
if tailstr == "@tail" {
|
|
tail, err = api.ChainGetGenesis(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
tail, err = ParseTipSetRef(ctx, api, tailstr)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing tail: %w", err)
|
|
}
|
|
}
|
|
|
|
if head.Height() < tail.Height() {
|
|
return errors.New("Height of --head tipset must be greater or equal to the height of the --tail tipset")
|
|
}
|
|
|
|
if cctx.Bool("internal") {
|
|
if err := api.ChainExportRangeInternal(ctx, head.Key(), tail.Key(), lapi.ChainExportConfig{
|
|
WriteBufferSize: cctx.Int("write-buffer"),
|
|
NumWorkers: cctx.Int("workers"),
|
|
CacheSize: cctx.Int("cache-size"),
|
|
IncludeMessages: cctx.Bool("messages"),
|
|
IncludeReceipts: cctx.Bool("receipts"),
|
|
IncludeStateRoots: cctx.Bool("stateroots"),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
stream, err := api.ChainExportRange(ctx, head.Key(), tail.Key(), lapi.ChainExportConfig{
|
|
WriteBufferSize: cctx.Int("write-buffer"),
|
|
NumWorkers: cctx.Int("workers"),
|
|
CacheSize: cctx.Int("cache-size"),
|
|
IncludeMessages: cctx.Bool("messages"),
|
|
IncludeReceipts: cctx.Bool("receipts"),
|
|
IncludeStateRoots: cctx.Bool("stateroots"),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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)
|
|
}
|
|
}()
|
|
|
|
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.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)
|
|
},
|
|
}
|