diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index bdfee6161..87e994fc5 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -34,31 +34,31 @@ type EPostProof struct { } type BlockHeader struct { - Miner address.Address + Miner address.Address // 0 - Ticket *Ticket + Ticket *Ticket // 1 - EPostProof EPostProof + EPostProof EPostProof // 2 - Parents []cid.Cid + Parents []cid.Cid // 3 - ParentWeight BigInt + ParentWeight BigInt // 4 - Height uint64 + Height uint64 // 5 - ParentStateRoot cid.Cid + ParentStateRoot cid.Cid // 6 - ParentMessageReceipts cid.Cid + ParentMessageReceipts cid.Cid // 7 - Messages cid.Cid + Messages cid.Cid // 8 - BLSAggregate Signature + BLSAggregate Signature // 9 - Timestamp uint64 + Timestamp uint64 // 10 - BlockSig *Signature + BlockSig *Signature // 11 - ForkSignaling uint64 + ForkSignaling uint64 // 12 } func (b *BlockHeader) ToStorageBlock() (block.Block, error) { diff --git a/cli/chain.go b/cli/chain.go index aed541f8c..0442af6ca 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1,10 +1,13 @@ package cli import ( + "bytes" "context" "encoding/json" "fmt" "os" + "os/exec" + "strconv" "strings" "time" @@ -28,6 +31,7 @@ var chainCmd = &cli.Command{ chainSetHeadCmd, chainListCmd, chainGetCmd, + chainBisectCmd, chainExportCmd, slashConsensusFault, }, @@ -394,6 +398,113 @@ func printTipSet(format string, ts *types.TipSet) { fmt.Println(format) } +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 + } + }, +} + var chainExportCmd = &cli.Command{ Name: "export", Usage: "export chain to a car file",