lotus/cli/chain.go

616 lines
12 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"
2019-07-23 00:54:27 +00:00
"encoding/json"
2019-07-09 15:19:27 +00:00
"fmt"
2020-01-20 23:51:02 +00:00
"os"
2020-01-30 21:30:21 +00:00
"os/exec"
"strconv"
2019-10-16 08:01:41 +00:00
"strings"
"time"
2019-07-09 15:19:27 +00:00
cid "github.com/ipfs/go-cid"
"golang.org/x/xerrors"
2019-07-09 15:19:27 +00:00
"gopkg.in/urfave/cli.v2"
2019-07-23 00:54:27 +00:00
"github.com/filecoin-project/lotus/api"
2019-12-18 15:37:47 +00:00
"github.com/filecoin-project/lotus/chain/actors"
types "github.com/filecoin-project/lotus/chain/types"
2019-07-09 15:19:27 +00:00
)
var chainCmd = &cli.Command{
Name: "chain",
Usage: "Interact with filecoin blockchain",
Subcommands: []*cli.Command{
chainHeadCmd,
2019-07-23 00:54:27 +00:00
chainGetBlock,
chainReadObjCmd,
chainGetMsgCmd,
2019-10-10 03:59:32 +00:00
chainSetHeadCmd,
2019-10-11 06:25:25 +00:00
chainListCmd,
chainGetCmd,
2020-01-30 21:30:21 +00:00
chainBisectCmd,
2020-01-20 23:51:02 +00:00
chainExportCmd,
2019-12-18 15:37:47 +00:00
slashConsensusFault,
2019-07-09 15:19:27 +00:00
},
}
var chainHeadCmd = &cli.Command{
Name: "head",
Usage: "Print chain head",
Action: func(cctx *cli.Context) error {
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() {
2019-07-09 15:19:27 +00:00
fmt.Println(c)
}
return nil
},
}
2019-07-23 00:54:27 +00:00
var chainGetBlock = &cli.Command{
Name: "getblock",
Usage: "Get a block and print its details",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "raw",
Usage: "print just the raw block header",
},
},
Action: func(cctx *cli.Context) error {
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.Args().Present() {
return fmt.Errorf("must pass cid of block to print")
}
bcid, err := cid.Decode(cctx.Args().First())
if err != nil {
return err
}
blk, err := api.ChainGetBlock(ctx, bcid)
if err != nil {
return xerrors.Errorf("get block failed: %w", err)
2019-07-23 00:54:27 +00:00
}
if cctx.Bool("raw") {
out, err := json.MarshalIndent(blk, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
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
}
fmt.Println(string(out))
return nil
},
}
func apiMsgCids(in []api.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",
Action: func(cctx *cli.Context) error {
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)
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
}
fmt.Printf("%x\n", obj)
return nil
},
}
var chainGetMsgCmd = &cli.Command{
Name: "getmessage",
Usage: "Get and print a message by its cid",
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return fmt.Errorf("must pass a cid of a message to get")
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
c, err := cid.Decode(cctx.Args().First())
if err != nil {
return xerrors.Errorf("failed to parse cid input: %w", err)
}
mb, err := api.ChainReadObj(ctx, c)
if err != nil {
return xerrors.Errorf("failed to read object: %w", err)
}
var i interface{}
m, err := types.DecodeMessage(mb)
if err != nil {
sm, err := types.DecodeSignedMessage(mb)
if err != nil {
return xerrors.Errorf("failed to decode object as a message: %w", err)
}
i = sm
} else {
i = m
}
enc, err := json.MarshalIndent(i, "", " ")
if err != nil {
return err
}
fmt.Println(string(enc))
return nil
},
}
2019-10-10 03:50:50 +00:00
2019-10-10 03:59:32 +00:00
var chainSetHeadCmd = &cli.Command{
2019-10-10 03:50:50 +00:00
Name: "sethead",
Usage: "manually set the local nodes head tipset (Caution: normally only used for recovery)",
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)
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, cctx.Uint64("epoch"), nil)
}
if ts == nil {
ts, err = parseTipSet(api, ctx, cctx.Args().Slice())
}
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); err != nil {
return err
}
return nil
},
}
func parseTipSet(api api.FullNode, ctx context.Context, vals []string) (*types.TipSet, error) {
var headers []*types.BlockHeader
for _, c := range vals {
blkc, err := cid.Decode(c)
if err != nil {
return nil, err
}
bh, err := api.ChainGetBlock(ctx, blkc)
if err != nil {
return nil, err
}
headers = append(headers, bh)
}
return types.NewTipSet(headers)
}
2019-10-11 06:25:25 +00:00
var chainListCmd = &cli.Command{
Name: "list",
Usage: "View a segment of the chain",
Flags: []cli.Flag{
&cli.Uint64Flag{Name: "height"},
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>",
},
},
2019-10-11 06:25:25 +00:00
Action: func(cctx *cli.Context) error {
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, cctx.Uint64("height"), nil)
} 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
}
for i := len(tss) - 1; i >= 0; i-- {
2019-10-16 08:01:41 +00:00
printTipSet(cctx.String("format"), tss[i])
2019-10-11 06:25:25 +00:00
}
return nil
},
}
2019-10-16 08:01:41 +00:00
var chainGetCmd = &cli.Command{
Name: "get",
Usage: "Get chain DAG node by path",
Description: `Get ipld node under a specified path:
lotus chain get /ipfs/[cid]/some/path
Note:
You can use special path elements to traverse through some data structures:
- /ipfs/[cid]/@H:elem - get 'elem' from hamt
- /ipfs/[cid]/@Ha:t01 - get element under Addr(t01).Bytes
- /ipfs/[cid]/@A:10 - get 10th amt element
`,
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
nd, err := api.ChainGetNode(ctx, cctx.Args().First())
if err != nil {
return err
}
b, err := json.MarshalIndent(nd, "", "\t")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
},
}
2019-10-16 08:01:41 +00:00
func printTipSet(format string, ts *types.TipSet) {
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 += " ]"
format = strings.ReplaceAll(format, "<blocks>", blks)
format = strings.ReplaceAll(format, "<weight>", fmt.Sprint(ts.Blocks()[0].ParentWeight))
fmt.Println(format)
}
2020-01-16 18:05:07 +00:00
2020-01-30 21:30:21 +00:00
var chainBisectCmd = &cli.Command{
Name: "bisect",
Usage: "bisect chain for an event",
Description: `Bisect the chain state tree:
lotus chain bisect [min height] [max height] '1/2/3/state/path' 'jq script'
Returns the first tipset in which jq 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' '.[2] > 100000'
For special path elements see 'chain get' help
`,
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() != 4 {
return xerrors.New("need 4 args")
}
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)
jqs := cctx.Args().Get(3)
highest, err := api.ChainGetTipSetByHeight(ctx, end, nil)
if err != nil {
return err
}
prev := highest.Height()
for {
mid := (start + end) / 2
if end - start == 1 {
mid = end
start = end
}
midTs, err := api.ChainGetTipSetByHeight(ctx, mid, highest)
if err != nil {
return err
}
path := "/ipld/" + midTs.ParentState().String() + "/" + subPath
fmt.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, "", "\t")
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, "jq", jqs)
cmd.Stdin = bytes.NewReader(b)
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return err
}
if strings.TrimSpace(out.String()) == "true" {
fmt.Println("true")
// it's lower
end = mid
highest = midTs
} else {
fmt.Println("false")
start = mid
}
if start == end {
if strings.TrimSpace(out.String()) == "true" {
fmt.Println(midTs.Height())
} else {
fmt.Println(prev)
}
return nil
}
prev = mid
}
},
}
2020-01-20 23:51:02 +00:00
var chainExportCmd = &cli.Command{
Name: "export",
2020-01-16 18:05:07 +00:00
Usage: "export chain to a car file",
2020-01-21 01:53:55 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tipset",
},
},
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)
2020-01-20 23:51:02 +00:00
if !cctx.Args().Present() {
return fmt.Errorf("must specify filename to export chain to")
}
2020-01-21 01:53:55 +00:00
fi, err := os.Create(cctx.Args().First())
2020-01-20 23:51:02 +00:00
if err != nil {
return err
}
2020-01-21 01:53:55 +00:00
defer fi.Close()
2020-01-20 23:51:02 +00:00
2020-01-21 01:53:55 +00:00
ts, err := loadTipSet(ctx, cctx, api)
2020-01-20 23:51:02 +00:00
if err != nil {
return err
}
stream, err := api.ChainExport(ctx, ts)
if err != nil {
return err
}
for b := range stream {
_, err := fi.Write(b)
if err != nil {
return err
}
}
return nil
2020-01-16 18:05:07 +00:00
},
}
2019-12-18 15:37:47 +00:00
var slashConsensusFault = &cli.Command{
Name: "slash-consensus",
Usage: "Report consensus fault",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
c1, err := cid.Parse(cctx.Args().Get(0))
if err != nil {
return xerrors.Errorf("parsing cid 1: %w", err)
}
b1, err := api.ChainGetBlock(ctx, c1)
if err != nil {
return xerrors.Errorf("getting block 1: %w", err)
}
c2, err := cid.Parse(cctx.Args().Get(0))
if err != nil {
return xerrors.Errorf("parsing cid 2: %w", err)
}
b2, err := api.ChainGetBlock(ctx, c2)
if err != nil {
return xerrors.Errorf("getting block 2: %w", err)
}
def, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
params, err := actors.SerializeParams(&actors.ArbitrateConsensusFaultParams{
Block1: b1,
Block2: b2,
})
msg := &types.Message{
To: actors.StoragePowerAddress,
From: def,
Value: types.NewInt(0),
GasPrice: types.NewInt(1),
GasLimit: types.NewInt(10000000),
Method: actors.SPAMethods.ArbitrateConsensusFault,
Params: params,
}
smsg, err := api.MpoolPushMessage(ctx, msg)
if err != nil {
return err
}
fmt.Println(smsg.Cid())
return nil
},
}