lotus/cli/chain.go

1688 lines
38 KiB
Go
Raw Normal View History

2019-07-09 15:19:27 +00:00
package cli
import (
2020-01-30 21:30:21 +00:00
"bytes"
2019-10-10 03:50:50 +00:00
"context"
2020-11-06 05:36:24 +00:00
"encoding/base64"
"encoding/hex"
2019-07-23 00:54:27 +00:00
"encoding/json"
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
"errors"
2019-07-09 15:19:27 +00:00
"fmt"
"io"
2020-01-20 23:51:02 +00:00
"os"
2020-01-30 21:30:21 +00:00
"os/exec"
2020-04-01 00:06:26 +00:00
"path"
"sort"
2020-01-30 21:30:21 +00:00
"strconv"
2019-10-16 08:01:41 +00:00
"strings"
"time"
2019-07-09 15:19:27 +00:00
"github.com/ipfs/go-cid"
2022-06-14 15:00:51 +00:00
"github.com/urfave/cli/v2"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
2020-03-09 06:45:24 +00:00
"github.com/filecoin-project/go-address"
2020-02-13 03:50:37 +00:00
cborutil "github.com/filecoin-project/go-cbor-util"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
lapi "github.com/filecoin-project/lotus/api"
2021-04-03 10:55:29 +00:00
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/build"
2019-12-18 15:37:47 +00:00
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/consensus"
"github.com/filecoin-project/lotus/chain/types"
2023-11-13 23:59:34 +00:00
"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"
2019-07-09 15:19:27 +00:00
)
2021-03-23 23:19:33 +00:00
var ChainCmd = &cli.Command{
2019-07-09 15:19:27 +00:00
Name: "chain",
Usage: "Interact with filecoin blockchain",
Subcommands: []*cli.Command{
2021-03-23 23:19:33 +00:00
ChainHeadCmd,
ChainGetBlock,
ChainReadObjCmd,
ChainDeleteObjCmd,
ChainStatObjCmd,
ChainGetMsgCmd,
ChainSetHeadCmd,
ChainListCmd,
ChainGetCmd,
ChainBisectCmd,
ChainExportCmd,
ChainExportRangeCmd,
2021-03-23 23:19:33 +00:00
SlashConsensusFault,
ChainGasPriceCmd,
ChainInspectUsage,
ChainDecodeCmd,
ChainEncodeCmd,
ChainDisputeSetCmd,
2022-09-26 01:01:08 +00:00
ChainPruneCmd,
2019-07-09 15:19:27 +00:00
},
}
2021-03-23 23:19:33 +00:00
var ChainHeadCmd = &cli.Command{
2019-07-09 15:19:27 +00:00
Name: "head",
Usage: "Print chain head",
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
2019-07-10 17:28:49 +00:00
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
defer closer()
2019-07-18 23:16:23 +00:00
ctx := ReqContext(cctx)
2019-07-09 15:19:27 +00:00
head, err := api.ChainHead(ctx)
if err != nil {
return err
}
2019-07-11 02:36:43 +00:00
for _, c := range head.Cids() {
afmt.Println(c)
2019-07-09 15:19:27 +00:00
}
return nil
},
}
2019-07-23 00:54:27 +00:00
2021-03-23 23:19:33 +00:00
var ChainGetBlock = &cli.Command{
2022-05-03 18:35:06 +00:00
Name: "get-block",
Aliases: []string{"getblock"},
2020-03-06 00:20:57 +00:00
Usage: "Get a block and print its details",
ArgsUsage: "[blockCid]",
2019-07-23 00:54:27 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "raw",
Usage: "print just the raw block header",
},
},
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
2019-07-23 00:54:27 +00:00
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
defer closer()
2019-07-18 23:16:23 +00:00
ctx := ReqContext(cctx)
2019-07-23 00:54:27 +00:00
if cctx.NArg() != 1 {
return IncorrectNumArgs(cctx)
2019-07-23 00:54:27 +00:00
}
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)
2019-07-23 00:54:27 +00:00
}
if cctx.Bool("raw") {
out, err := json.MarshalIndent(blk, "", " ")
if err != nil {
return err
}
afmt.Println(string(out))
2019-07-23 00:54:27 +00:00
return nil
}
msgs, err := api.ChainGetBlockMessages(ctx, bcid)
if err != nil {
return xerrors.Errorf("failed to get messages: %w", err)
2019-07-23 00:54:27 +00:00
}
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)
}
2019-07-23 00:54:27 +00:00
cblock := struct {
types.BlockHeader
BlsMessages []*types.Message
SecpkMessages []*types.SignedMessage
ParentReceipts []*types.MessageReceipt
ParentMessages []cid.Cid
2019-07-23 00:54:27 +00:00
}{}
cblock.BlockHeader = *blk
2019-08-02 03:51:34 +00:00
cblock.BlsMessages = msgs.BlsMessages
cblock.SecpkMessages = msgs.SecpkMessages
cblock.ParentReceipts = recpts
cblock.ParentMessages = apiMsgCids(pmsgs)
2019-07-23 00:54:27 +00:00
out, err := json.MarshalIndent(cblock, "", " ")
if err != nil {
return err
}
afmt.Println(string(out))
2019-07-23 00:54:27 +00:00
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
}
2021-03-23 23:19:33 +00:00
var ChainReadObjCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "read-obj",
Usage: "Read the raw bytes of an object",
ArgsUsage: "[objectCid]",
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2019-10-03 18:12:30 +00:00
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
2019-10-03 18:12:30 +00:00
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
},
}
2021-03-23 23:19:33 +00:00
var ChainDeleteObjCmd = &cli.Command{
2020-09-23 17:31:36 +00:00
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",
},
},
2020-09-10 17:35:06 +00:00
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2020-09-10 17:35:06 +00:00
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if cctx.NArg() != 1 {
return IncorrectNumArgs(cctx)
}
2020-09-10 17:35:06 +00:00
c, err := cid.Decode(cctx.Args().First())
if err != nil {
return fmt.Errorf("failed to parse cid input: %s", err)
}
2020-09-23 17:31:36 +00:00
if !cctx.Bool("really-do-it") {
return xerrors.Errorf("pass the --really-do-it flag to proceed")
}
2020-09-10 17:35:06 +00:00
err = api.ChainDeleteObj(ctx, c)
if err != nil {
return err
}
afmt.Printf("Obj %s deleted\n", c.String())
2020-09-10 17:35:06 +00:00
return nil
},
}
2021-03-23 23:19:33 +00:00
var ChainStatObjCmd = &cli.Command{
2020-03-04 23:52:28 +00:00
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)
2020-03-04 23:52:28 +00:00
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if cctx.NArg() != 1 {
return IncorrectNumArgs(cctx)
}
2020-03-04 23:52:28 +00:00
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
}
2020-03-04 23:52:28 +00:00
}
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)
2020-03-04 23:52:28 +00:00
return nil
},
}
2021-03-23 23:19:33 +00:00
var ChainGetMsgCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "getmessage",
2022-05-03 18:35:06 +00:00
Aliases: []string{"get-message", "get-msg"},
2020-03-06 00:20:57 +00:00
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
},
}
2019-10-10 03:50:50 +00:00
2021-03-23 23:19:33 +00:00
var ChainSetHeadCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "sethead",
2022-05-03 18:35:06 +00:00
Aliases: []string{"set-head"},
2020-03-06 00:20:57 +00:00
Usage: "manually set the local nodes head tipset (Caution: normally only used for recovery)",
ArgsUsage: "[tipsetkey]",
2019-10-11 02:14:22 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "genesis",
Usage: "reset head to genesis",
},
2019-12-16 19:35:07 +00:00
&cli.Uint64Flag{
Name: "epoch",
Usage: "reset head to given epoch",
},
2019-10-11 02:14:22 +00:00
},
2019-10-10 03:50:50 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
2023-05-03 17:37:08 +00:00
if !cctx.Bool("genesis") && !cctx.IsSet("epoch") && cctx.NArg() != 1 {
return IncorrectNumArgs(cctx)
}
2019-12-16 19:35:07 +00:00
var ts *types.TipSet
2019-10-11 02:14:22 +00:00
2019-12-16 19:35:07 +00:00
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)
2019-12-16 19:35:07 +00:00
}
if ts == nil {
ts, err = parseTipSet(ctx, api, cctx.Args().Slice())
2019-12-16 19:35:07 +00:00
}
if err != nil {
return err
2019-10-10 03:50:50 +00:00
}
2019-12-16 19:35:07 +00:00
if ts == nil {
return fmt.Errorf("must pass cids for tipset to set as head")
2019-10-10 03:50:50 +00:00
}
if err := api.ChainSetHead(ctx, ts.Key()); err != nil {
2019-10-10 03:50:50 +00:00
return err
}
return nil
},
}
2021-03-23 23:19:33 +00:00
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 {
2022-02-09 22:47:40 +00:00
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")
2022-02-09 22:47:40 +00:00
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]
2022-02-09 22:47:40 +00:00
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])
}
2022-02-09 22:47:40 +00:00
afmt.Println()
afmt.Printf("By Receiver:\n")
for i := 0; i < numRes && i < len(destVals); i++ {
sv := destVals[i]
2022-02-09 22:47:40 +00:00
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])
}
2022-02-09 22:47:40 +00:00
afmt.Println()
afmt.Printf("By Method:\n")
for i := 0; i < numRes && i < len(methodVals); i++ {
sv := methodVals[i]
2022-02-09 22:47:40 +00:00
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
},
}
2021-03-23 23:19:33 +00:00
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"},
2019-10-16 08:01:41 +00:00
&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",
},
},
2019-10-11 06:25:25 +00:00
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2019-10-11 06:25:25 +00:00
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)
}
2019-10-11 06:25:25 +00:00
if err != nil {
return err
}
2019-10-16 08:01:41 +00:00
count := cctx.Int("count")
if count < 1 {
return nil
}
2019-10-16 08:01:41 +00:00
tss := make([]*types.TipSet, 0, count)
tss = append(tss, head)
2019-10-16 08:01:41 +00:00
for i := 1; i < count; i++ {
if head.Height() == 0 {
2019-10-11 06:25:25 +00:00
break
}
2019-12-16 19:22:56 +00:00
head, err = api.ChainGetTipSet(ctx, head.Parents())
2019-10-11 06:25:25 +00:00
if err != nil {
return err
}
2019-10-16 08:01:41 +00:00
tss = append(tss, head)
2019-10-11 06:25:25 +00:00
}
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 {
2020-08-10 23:22:02 +00:00
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)
}
2019-10-11 06:25:25 +00:00
}
return nil
},
}
2019-10-16 08:01:41 +00:00
2021-03-23 23:19:33 +00:00
var ChainGetCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "get",
Usage: "Get chain DAG node by path",
ArgsUsage: "[path]",
2020-03-07 02:47:19 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "as-type",
Usage: "specify type to interpret output as",
},
2020-04-01 00:06:26 +00:00
&cli.BoolFlag{
Name: "verbose",
Value: false,
2020-04-01 00:06:26 +00:00
},
&cli.StringFlag{
Name: "tipset",
Usage: "specify tipset for /pstate (pass comma separated array of cids)",
},
2020-03-07 02:47:19 +00:00
},
Description: `Get ipld node under a specified path:
lotus chain get /ipfs/[cid]/some/path
2020-04-01 00:06:26 +00:00
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
2020-04-04 01:49:42 +00:00
- /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
2020-04-20 17:34:08 +00:00
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)
}
2020-04-01 00:06:26 +00:00
p := path.Clean(cctx.Args().First())
if strings.HasPrefix(p, "/pstate") {
p = p[len("/pstate"):]
ts, err := LoadTipSet(ctx, cctx, api)
2020-04-01 00:06:26 +00:00
if err != nil {
return err
}
p = "/ipfs/" + ts.ParentState().String() + p
2020-04-01 00:06:26 +00:00
if cctx.Bool("verbose") {
afmt.Println(p)
2020-04-01 00:06:26 +00:00
}
}
obj, err := api.ChainGetNode(ctx, p)
2020-03-07 02:47:19 +00:00
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))
2020-03-07 02:47:19 +00:00
return nil
}
var cbu cbg.CBORUnmarshaler
switch t {
2020-03-10 21:06:23 +00:00
case "raw":
cbu = nil
2020-03-07 02:47:19 +00:00
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)
2020-03-09 03:09:45 +00:00
case "amt":
return handleAmt(ctx, api, obj.Cid)
case "hamt-epoch":
return handleHamtEpoch(ctx, api, obj.Cid)
2020-03-09 06:45:24 +00:00
case "hamt-address":
return handleHamtAddress(ctx, api, obj.Cid)
2020-03-09 03:09:45 +00:00
case "cronevent":
cbu = new(power.CronEvent)
case "account-state":
cbu = new(account.State)
2020-05-16 19:41:04 +00:00
case "miner-state":
cbu = new(miner.State)
case "power-state":
cbu = new(power.State)
case "market-state":
cbu = new(market.State)
2020-03-07 02:47:19 +00:00
default:
return fmt.Errorf("unknown type: %q", t)
}
raw, err := api.ChainReadObj(ctx, obj.Cid)
if err != nil {
return err
}
2020-03-10 21:06:23 +00:00
if cbu == nil {
afmt.Printf("%x", raw)
2020-03-10 21:06:23 +00:00
return nil
}
2020-03-07 02:47:19 +00:00
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
},
}
2020-03-09 03:09:45 +00:00
type apiIpldStore struct {
ctx context.Context
2021-04-03 10:55:29 +00:00
api v0api.FullNode
2020-03-09 03:09:45 +00:00
}
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")
}
2021-04-03 10:55:29 +00:00
func handleAmt(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
2020-03-09 03:09:45 +00:00
s := &apiIpldStore{ctx, api}
2020-04-13 21:05:16 +00:00
mp, err := adt.AsArray(s, r)
if err != nil {
return err
}
2020-03-09 03:09:45 +00:00
return mp.ForEach(nil, func(key int64) error {
fmt.Printf("%d\n", key)
return nil
})
}
2021-04-03 10:55:29 +00:00
func handleHamtEpoch(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
2020-03-09 03:09:45 +00:00
s := &apiIpldStore{ctx, api}
2020-04-13 21:05:16 +00:00
mp, err := adt.AsMap(s, r)
if err != nil {
return err
}
2020-03-09 03:09:45 +00:00
return mp.ForEach(nil, func(key string) error {
ik, err := abi.ParseIntKey(key)
2020-03-09 03:09:45 +00:00
if err != nil {
return err
}
fmt.Printf("%d\n", ik)
return nil
})
}
2021-04-03 10:55:29 +00:00
func handleHamtAddress(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
2020-03-09 06:45:24 +00:00
s := &apiIpldStore{ctx, api}
2020-04-13 21:05:16 +00:00
mp, err := adt.AsMap(s, r)
if err != nil {
return err
}
2020-03-09 06:45:24 +00:00
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) {
2019-10-16 08:01:41 +00:00
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, ","))
2019-10-16 08:01:41 +00:00
format = strings.ReplaceAll(format, "<blocks>", blks)
format = strings.ReplaceAll(format, "<weight>", fmt.Sprint(ts.Blocks()[0].ParentWeight))
afmt.Println(format)
2019-10-16 08:01:41 +00:00
}
2020-01-16 18:05:07 +00:00
2021-03-23 23:19:33 +00:00
var ChainBisectCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "bisect",
Usage: "bisect chain for an event",
ArgsUsage: "[minHeight maxHeight path shellCommand <shellCommandArgs (if any)>]",
2020-01-30 21:30:21 +00:00
Description: `Bisect the chain state tree:
2020-02-04 03:40:49 +00:00
lotus chain bisect [min height] [max height] '1/2/3/state/path' 'shell command' 'args'
2020-01-30 21:30:21 +00:00
2020-02-04 03:40:49 +00:00
Returns the first tipset in which condition is true
2020-01-30 21:30:21 +00:00
v
[start] FFFFFFFTTT [end]
Example: find height at which deal ID 100 000 appeared
2020-02-04 03:40:49 +00:00
- lotus chain bisect 1 32000 '@Ha:t03/1' jq -e '.[2] > 100000'
2020-01-30 21:30:21 +00:00
For special path elements see 'chain get' help
`,
Action: func(cctx *cli.Context) error {
2022-02-10 14:46:06 +00:00
afmt := NewAppFmt(cctx.App)
2020-01-30 21:30:21 +00:00
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
2022-09-14 18:33:29 +00:00
if cctx.NArg() < 4 {
return IncorrectNumArgs(cctx)
2020-01-30 21:30:21 +00:00
}
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)
2020-01-30 21:30:21 +00:00
if err != nil {
2020-05-05 16:14:47 +00:00
return xerrors.Errorf("getting end tipset: %w", err)
2020-01-30 21:30:21 +00:00
}
prev := highest.Height()
for {
mid := (start + end) / 2
2020-02-04 06:55:57 +00:00
if end-start == 1 {
2020-01-30 21:30:21 +00:00
mid = end
start = end
}
midTs, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(mid), highest.Key())
2020-01-30 21:30:21 +00:00
if err != nil {
return err
}
path := "/ipld/" + midTs.ParentState().String() + "/" + subPath
2022-02-10 14:46:06 +00:00
afmt.Printf("* Testing %d (%d - %d) (%s): ", mid, start, end, path)
2020-01-30 21:30:21 +00:00
nd, err := api.ChainGetNode(ctx, path)
if err != nil {
return err
}
b, err := json.MarshalIndent(nd.Obj, "", "\t")
2020-01-30 21:30:21 +00:00
if err != nil {
return err
}
2020-02-04 03:40:49 +00:00
cmd := exec.CommandContext(ctx, cctx.Args().Get(3), cctx.Args().Slice()[4:]...)
2020-01-30 21:30:21 +00:00
cmd.Stdin = bytes.NewReader(b)
var out bytes.Buffer
2020-05-05 16:14:47 +00:00
var serr bytes.Buffer
2020-01-30 21:30:21 +00:00
cmd.Stdout = &out
2020-05-05 16:14:47 +00:00
cmd.Stderr = &serr
2020-01-30 21:30:21 +00:00
2020-02-04 03:40:49 +00:00
switch cmd.Run().(type) {
case nil:
2020-01-30 21:30:21 +00:00
// it's lower
if strings.TrimSpace(out.String()) != "false" {
2020-05-05 16:14:47 +00:00
end = mid
highest = midTs
2022-02-10 14:46:06 +00:00
afmt.Println("true")
2020-05-05 16:14:47 +00:00
} else {
start = mid
2022-02-10 14:46:06 +00:00
afmt.Printf("false (cli)\n")
2020-05-05 16:14:47 +00:00
}
2020-02-04 03:40:49 +00:00
case *exec.ExitError:
2020-05-05 16:14:47 +00:00
if len(serr.String()) > 0 {
2022-02-10 14:46:06 +00:00
afmt.Println("error")
2020-05-05 16:14:47 +00:00
2022-02-10 14:46:06 +00:00
afmt.Printf("> Command: %s\n---->\n", strings.Join(cctx.Args().Slice()[3:], " "))
afmt.Println(string(b))
afmt.Println("<----")
2020-05-05 16:14:47 +00:00
return xerrors.Errorf("error running bisect check: %s", serr.String())
}
2020-01-30 21:30:21 +00:00
start = mid
2022-02-10 14:46:06 +00:00
afmt.Println("false")
2020-02-04 03:40:49 +00:00
default:
return err
2020-01-30 21:30:21 +00:00
}
if start == end {
if strings.TrimSpace(out.String()) == "true" {
2022-02-10 14:46:06 +00:00
afmt.Println(midTs.Height())
2020-01-30 21:30:21 +00:00
} else {
2022-02-10 14:46:06 +00:00
afmt.Println(prev)
2020-01-30 21:30:21 +00:00
}
return nil
}
2020-02-08 02:18:32 +00:00
prev = abi.ChainEpoch(mid)
2020-01-30 21:30:21 +00:00
}
},
}
2021-03-23 23:19:33 +00:00
var ChainExportCmd = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "export",
Usage: "export chain to a car file",
ArgsUsage: "[outputPath]",
2020-01-21 01:53:55 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
2021-07-03 16:41:43 +00:00
Name: "tipset",
Usage: "specify tipset to start the export from",
Value: "@head",
2020-01-21 01:53:55 +00:00
},
&cli.Int64Flag{
Name: "recent-stateroots",
Usage: "specify the number of recent state roots to include in the export",
},
&cli.BoolFlag{
Name: "skip-old-msgs",
},
2020-01-21 01:53:55 +00:00
},
2020-01-16 18:05:07 +00:00
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)
2020-01-20 23:51:02 +00:00
}
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())
2020-01-20 23:51:02 +00:00
if err != nil {
return err
}
defer func() {
err := fi.Close()
if err != nil {
fmt.Printf("error closing output file: %+v", err)
}
}()
2020-01-20 23:51:02 +00:00
2020-03-07 00:46:20 +00:00
ts, err := LoadTipSet(ctx, cctx, api)
2020-01-20 23:51:02 +00:00
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())
2020-01-20 23:51:02 +00:00
if err != nil {
return err
}
var last bool
2020-01-20 23:51:02 +00:00
for b := range stream {
last = len(b) == 0
2020-01-20 23:51:02 +00:00
_, err := fi.Write(b)
if err != nil {
return err
}
}
if !last {
return xerrors.Errorf("incomplete export (remote connection lost?)")
}
2020-01-20 23:51:02 +00:00
return nil
2020-01-16 18:05:07 +00:00
},
}
2019-12-18 15:37:47 +00:00
var ChainExportRangeCmd = &cli.Command{
Name: "export-range",
Usage: "export chain to a car file",
ArgsUsage: "",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "head",
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
Usage: "specify tipset to start the export from (higher epoch)",
Value: "@head",
},
&cli.StringFlag{
Name: "tail",
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
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,
},
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
&cli.IntFlag{
Name: "workers",
Usage: "specify the number of workers",
Value: 1,
},
&cli.IntFlag{
Name: "write-buffer",
Usage: "specify write buffer size",
Value: 1 << 20,
},
&cli.BoolFlag{
Name: "internal",
Usage: "write the file locally to disk",
Value: true,
Hidden: true, // currently, non-internal export is not implemented.
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPIV1(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
var head, tail *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)
}
}
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
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") {
return errors.New("Non-internal exports are not implemented")
}
err = api.ChainExportRangeInternal(ctx, head.Key(), tail.Key(), lapi.ChainExportConfig{
WriteBufferSize: cctx.Int("write-buffer"),
Chain ranged export: rework and address current shortcomings 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.
2023-02-02 16:51:52 +00:00
NumWorkers: cctx.Int("workers"),
IncludeMessages: cctx.Bool("messages"),
IncludeReceipts: cctx.Bool("receipts"),
IncludeStateRoots: cctx.Bool("stateroots"),
})
if err != nil {
return err
}
return nil
},
}
2021-03-23 23:19:33 +00:00
var SlashConsensusFault = &cli.Command{
2020-03-06 00:20:57 +00:00
Name: "slash-consensus",
Usage: "Report consensus fault",
ArgsUsage: "[blockCid1 blockCid2]",
2020-04-13 21:05:16 +00:00
Flags: []cli.Flag{
2021-01-19 06:18:46 +00:00
&cli.StringFlag{
Name: "from",
Usage: "optionally specify the account to report consensus from",
},
2020-06-11 11:19:17 +00:00
&cli.StringFlag{
Name: "extra",
Usage: "Extra block cid",
},
2020-04-13 21:05:16 +00:00
},
2019-12-18 15:37:47 +00:00
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
srv, err := GetFullNodeServices(cctx)
2019-12-18 15:37:47 +00:00
if err != nil {
return err
}
defer srv.Close() //nolint:errcheck
a := srv.FullNodeAPI()
2019-12-18 15:37:47 +00:00
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)
2019-12-18 15:37:47 +00:00
if err != nil {
return xerrors.Errorf("getting block 1: %w", err)
}
c2, err := cid.Parse(cctx.Args().Get(1))
2019-12-18 15:37:47 +00:00
if err != nil {
return xerrors.Errorf("parsing cid 2: %w", err)
}
b2, err := a.ChainGetBlock(ctx, c2)
2019-12-18 15:37:47 +00:00
if err != nil {
return xerrors.Errorf("getting block 2: %w", err)
}
2020-10-24 05:46:15 +00:00
if b1.Miner != b2.Miner {
return xerrors.Errorf("block1.miner:%s block2.miner:%s", b1.Miner, b2.Miner)
}
2021-01-19 06:18:46 +00:00
var fromAddr address.Address
if from := cctx.String("from"); from == "" {
defaddr, err := a.WalletDefaultAddress(ctx)
2021-01-19 06:18:46 +00:00
if err != nil {
return err
}
fromAddr = defaddr
} else {
addr, err := address.NewFromString(from)
if err != nil {
return err
}
fromAddr = addr
2019-12-18 15:37:47 +00:00
}
2020-02-13 03:50:37 +00:00
bh1, err := cborutil.Dump(b1)
if err != nil {
return err
}
bh2, err := cborutil.Dump(b2)
if err != nil {
return err
}
2020-06-11 11:19:17 +00:00
params := miner.ReportConsensusFaultParams{
2020-02-13 03:50:37 +00:00
BlockHeader1: bh1,
BlockHeader2: bh2,
2020-06-11 11:19:17 +00:00
}
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)
2020-06-11 11:19:17 +00:00
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(&params)
2020-06-10 12:34:04 +00:00
if err != nil {
return err
}
2019-12-18 15:37:47 +00:00
proto := &api.MessagePrototype{
Message: types.Message{
To: b2.Miner,
From: fromAddr,
Value: types.NewInt(0),
Method: builtin.MethodsMiner.ReportConsensusFault,
Params: enc,
},
2019-12-18 15:37:47 +00:00
}
smsg, err := InteractiveSend(ctx, cctx, srv, proto)
2019-12-18 15:37:47 +00:00
if err != nil {
return err
}
afmt.Println(smsg.Cid())
2019-12-18 15:37:47 +00:00
return nil
},
}
2020-08-01 07:40:11 +00:00
2021-03-23 23:19:33 +00:00
var ChainGasPriceCmd = &cli.Command{
2020-08-01 07:40:11 +00:00
Name: "gas-price",
Usage: "Estimate gas prices",
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2020-08-01 07:40:11 +00:00
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 {
2020-08-11 05:10:12 +00:00
addr := builtin.SystemActorAddr // TODO: make real when used in GasEstimateGasPremium
2020-08-01 07:40:11 +00:00
2020-08-11 05:10:12 +00:00
est, err := api.GasEstimateGasPremium(ctx, uint64(nblocks), addr, 10000, types.EmptyTSK)
2020-08-01 07:40:11 +00:00
if err != nil {
return err
}
afmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est))
2020-08-01 07:40:11 +00:00
}
return nil
},
}
2021-03-23 23:19:33 +00:00
var ChainDecodeCmd = &cli.Command{
Name: "decode",
Usage: "decode various types",
Subcommands: []*cli.Command{
chainDecodeParamsCmd,
},
}
var chainDecodeParamsCmd = &cli.Command{
2020-11-06 05:36:24 +00:00
Name: "params",
Usage: "Decode message params",
ArgsUsage: "[toAddr method params]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tipset",
},
2020-11-06 07:02:08 +00:00
&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)
2022-09-14 18:33:29 +00:00
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)
}
2020-11-06 07:02:08 +00:00
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":
2020-11-06 05:36:24 +00:00
params, err = hex.DecodeString(cctx.Args().Get(2))
if err != nil {
2020-11-06 07:02:08 +00:00
return xerrors.Errorf("decoding hex value: %w", err)
2020-11-06 05:36:24 +00:00
}
2020-11-06 07:02:08 +00:00
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)
}
2020-10-23 19:51:10 +00:00
pstr, err := JsonParams(act.Code, abi.MethodNum(method), params)
if err != nil {
return err
}
afmt.Println(pstr)
return nil
},
}
2021-01-08 06:25:24 +00:00
2021-03-23 23:19:33 +00:00
var ChainEncodeCmd = &cli.Command{
2021-01-08 06:25:24 +00:00
Name: "encode",
Usage: "encode various types",
Subcommands: []*cli.Command{
chainEncodeParamsCmd,
},
}
var chainEncodeParamsCmd = &cli.Command{
Name: "params",
Usage: "Encodes the given JSON params",
2021-08-20 16:00:38 +00:00
ArgsUsage: "[dest method params]",
2021-01-08 06:25:24 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tipset",
},
&cli.StringFlag{
Name: "encoding",
Value: "base64",
Usage: "specify input encoding to parse",
},
2021-08-20 16:00:38 +00:00
&cli.BoolFlag{
Name: "to-code",
Usage: "interpret dest as code CID instead of as address",
},
2021-01-08 06:25:24 +00:00
},
Action: func(cctx *cli.Context) error {
afmt := NewAppFmt(cctx.App)
2022-09-14 18:33:29 +00:00
if cctx.NArg() != 3 {
return IncorrectNumArgs(cctx)
2021-01-08 06:25:24 +00:00
}
method, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64)
if err != nil {
return xerrors.Errorf("parsing method id: %w", err)
}
2021-08-20 16:00:38 +00:00
ctx := ReqContext(cctx)
2021-01-08 06:25:24 +00:00
2021-08-20 16:00:38 +00:00
var p []byte
if !cctx.Bool("to-code") {
svc, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer svc.Close() // nolint
2021-01-08 06:25:24 +00:00
2021-08-20 16:00:38 +00:00
to, err := address.NewFromString(cctx.Args().First())
if err != nil {
return xerrors.Errorf("parsing to addr: %w", err)
}
2021-01-08 06:25:24 +00:00
2021-08-20 16:00:38 +00:00
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()
2021-01-08 06:25:24 +00:00
2021-08-20 16:00:38 +00:00
to, err := cid.Parse(cctx.Args().First())
if err != nil {
return xerrors.Errorf("parsing to addr: %w", err)
}
2021-01-08 06:25:24 +00:00
2021-08-20 16:00:38 +00:00
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)
}
2021-01-08 06:25:24 +00:00
}
switch cctx.String("encoding") {
2021-08-20 16:00:38 +00:00
case "base64", "b64":
afmt.Println(base64.StdEncoding.EncodeToString(p))
2021-01-08 06:25:24 +00:00
case "hex":
afmt.Println(hex.EncodeToString(p))
2021-01-08 06:25:24 +00:00
default:
2021-08-20 16:00:38 +00:00
return xerrors.Errorf("unknown encoding")
2021-01-08 06:25:24 +00:00
}
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",
2023-03-03 16:14:52 +00:00
Usage: "splitstore gc",
Subcommands: []*cli.Command{
chainPruneColdCmd,
chainPruneHotGCCmd,
2023-03-06 19:05:04 +00:00
chainPruneHotMovingGCCmd,
2023-03-03 16:14:52 +00:00
},
}
var chainPruneHotGCCmd = &cli.Command{
Name: "hot",
Usage: "run online (badger vlog) garbage collection on hotstore",
Flags: []cli.Flag{
&cli.Float64Flag{Name: "threshold", Value: 0.01, Usage: "Threshold of vlog garbage for gc"},
&cli.BoolFlag{Name: "periodic", Value: false, Usage: "Run periodic gc over multiple vlogs. Otherwise run gc once"},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPIV1(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
opts := lapi.HotGCOpts{}
opts.Periodic = cctx.Bool("periodic")
opts.Threshold = cctx.Float64("threshold")
2023-03-06 19:05:04 +00:00
gcStart := time.Now()
err = api.ChainHotGC(ctx, opts)
gcTime := time.Since(gcStart)
fmt.Printf("Online GC took %v (periodic <%t> threshold <%f>)", gcTime, opts.Periodic, opts.Threshold)
return err
2023-03-03 16:14:52 +00:00
},
}
var chainPruneHotMovingGCCmd = &cli.Command{
Name: "hot-moving",
Usage: "run moving gc on hotstore",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPIV1(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
opts := lapi.HotGCOpts{}
opts.Moving = true
2023-03-06 19:05:04 +00:00
gcStart := time.Now()
err = api.ChainHotGC(ctx, opts)
gcTime := time.Since(gcStart)
fmt.Printf("Moving GC took %v", gcTime)
return err
},
}
2023-03-03 16:14:52 +00:00
var chainPruneColdCmd = &cli.Command{
Name: "compact-cold",
Usage: "force splitstore compaction on cold store state and run gc",
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)
},
}