Merge pull request #8048 from filecoin-project/cli-chain-tests
test: cli: chain category unit tests
This commit is contained in:
commit
6123aa2ed4
@ -3,6 +3,7 @@ package mock
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
@ -24,15 +25,7 @@ func Address(i uint64) address.Address {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MkMessage(from, to address.Address, nonce uint64, w *wallet.LocalWallet) *types.SignedMessage {
|
func MkMessage(from, to address.Address, nonce uint64, w *wallet.LocalWallet) *types.SignedMessage {
|
||||||
msg := &types.Message{
|
msg := UnsignedMessage(from, to, nonce)
|
||||||
To: to,
|
|
||||||
From: from,
|
|
||||||
Value: types.NewInt(1),
|
|
||||||
Nonce: nonce,
|
|
||||||
GasLimit: 1000000,
|
|
||||||
GasFeeCap: types.NewInt(100),
|
|
||||||
GasPremium: types.NewInt(1),
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{})
|
sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -96,3 +89,35 @@ func TipSet(blks ...*types.BlockHeader) *types.TipSet {
|
|||||||
}
|
}
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates count new addresses using the provided seed, and returns them
|
||||||
|
func RandomActorAddresses(seed int64, count int) ([]*address.Address, error) {
|
||||||
|
randAddrs := make([]*address.Address, count)
|
||||||
|
source := rand.New(rand.NewSource(seed))
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
bytes := make([]byte, 32)
|
||||||
|
_, err := source.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := address.NewActorAddress(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
randAddrs[i] = &addr
|
||||||
|
}
|
||||||
|
return randAddrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedMessage(from, to address.Address, nonce uint64) *types.Message {
|
||||||
|
return &types.Message{
|
||||||
|
To: to,
|
||||||
|
From: from,
|
||||||
|
Value: types.NewInt(1),
|
||||||
|
Nonce: nonce,
|
||||||
|
GasLimit: 1000000,
|
||||||
|
GasFeeCap: types.NewInt(100),
|
||||||
|
GasPremium: types.NewInt(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
128
cli/chain.go
128
cli/chain.go
@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@ -67,6 +68,8 @@ var ChainHeadCmd = &cli.Command{
|
|||||||
Name: "head",
|
Name: "head",
|
||||||
Usage: "Print chain head",
|
Usage: "Print chain head",
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -80,7 +83,7 @@ var ChainHeadCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range head.Cids() {
|
for _, c := range head.Cids() {
|
||||||
fmt.Println(c)
|
afmt.Println(c)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -97,6 +100,8 @@ var ChainGetBlock = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -124,7 +129,7 @@ var ChainGetBlock = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(out))
|
afmt.Println(string(out))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +168,8 @@ var ChainGetBlock = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(out))
|
afmt.Println(string(out))
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +186,8 @@ var ChainReadObjCmd = &cli.Command{
|
|||||||
Usage: "Read the raw bytes of an object",
|
Usage: "Read the raw bytes of an object",
|
||||||
ArgsUsage: "[objectCid]",
|
ArgsUsage: "[objectCid]",
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -199,7 +205,7 @@ var ChainReadObjCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%x\n", obj)
|
afmt.Printf("%x\n", obj)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -215,6 +221,8 @@ var ChainDeleteObjCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -236,7 +244,7 @@ var ChainDeleteObjCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Obj %s deleted\n", c.String())
|
afmt.Printf("Obj %s deleted\n", c.String())
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -257,6 +265,7 @@ var ChainStatObjCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -282,8 +291,8 @@ var ChainStatObjCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Links: %d\n", stats.Links)
|
afmt.Printf("Links: %d\n", stats.Links)
|
||||||
fmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size)
|
afmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -293,6 +302,8 @@ var ChainGetMsgCmd = &cli.Command{
|
|||||||
Usage: "Get and print a message by its cid",
|
Usage: "Get and print a message by its cid",
|
||||||
ArgsUsage: "[messageCid]",
|
ArgsUsage: "[messageCid]",
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
if !cctx.Args().Present() {
|
if !cctx.Args().Present() {
|
||||||
return fmt.Errorf("must pass a cid of a message to get")
|
return fmt.Errorf("must pass a cid of a message to get")
|
||||||
}
|
}
|
||||||
@ -331,7 +342,7 @@ var ChainGetMsgCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(enc))
|
afmt.Println(string(enc))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -406,6 +417,7 @@ var ChainInspectUsage = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -507,23 +519,23 @@ var ChainInspectUsage = &cli.Command{
|
|||||||
|
|
||||||
numRes := cctx.Int("num-results")
|
numRes := cctx.Int("num-results")
|
||||||
|
|
||||||
fmt.Printf("Total Gas Limit: %d\n", sum)
|
afmt.Printf("Total Gas Limit: %d\n", sum)
|
||||||
fmt.Printf("By Sender:\n")
|
afmt.Printf("By Sender:\n")
|
||||||
for i := 0; i < numRes && i < len(senderVals); i++ {
|
for i := 0; i < numRes && i < len(senderVals); i++ {
|
||||||
sv := senderVals[i]
|
sv := senderVals[i]
|
||||||
fmt.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.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, bySenderC[sv.Key])
|
||||||
}
|
}
|
||||||
fmt.Println()
|
afmt.Println()
|
||||||
fmt.Printf("By Receiver:\n")
|
afmt.Printf("By Receiver:\n")
|
||||||
for i := 0; i < numRes && i < len(destVals); i++ {
|
for i := 0; i < numRes && i < len(destVals); i++ {
|
||||||
sv := destVals[i]
|
sv := destVals[i]
|
||||||
fmt.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.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byDestC[sv.Key])
|
||||||
}
|
}
|
||||||
fmt.Println()
|
afmt.Println()
|
||||||
fmt.Printf("By Method:\n")
|
afmt.Printf("By Method:\n")
|
||||||
for i := 0; i < numRes && i < len(methodVals); i++ {
|
for i := 0; i < numRes && i < len(methodVals); i++ {
|
||||||
sv := methodVals[i]
|
sv := methodVals[i]
|
||||||
fmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byMethodC[sv.Key])
|
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
|
return nil
|
||||||
@ -548,6 +560,7 @@ var ChainListCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -595,7 +608,7 @@ var ChainListCmd = &cli.Command{
|
|||||||
tss = otss
|
tss = otss
|
||||||
for i, ts := range tss {
|
for i, ts := range tss {
|
||||||
pbf := ts.Blocks()[0].ParentBaseFee
|
pbf := ts.Blocks()[0].ParentBaseFee
|
||||||
fmt.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)))))
|
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() {
|
for _, b := range ts.Blocks() {
|
||||||
msgs, err := api.ChainGetBlockMessages(ctx, b.Cid())
|
msgs, err := api.ChainGetBlockMessages(ctx, b.Cid())
|
||||||
@ -621,7 +634,7 @@ var ChainListCmd = &cli.Command{
|
|||||||
avgpremium = big.Div(psum, big.NewInt(int64(lenmsgs)))
|
avgpremium = big.Div(psum, big.NewInt(int64(lenmsgs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.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)
|
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 {
|
if i < len(tss)-1 {
|
||||||
msgs, err := api.ChainGetParentMessages(ctx, tss[i+1].Blocks()[0].Cid())
|
msgs, err := api.ChainGetParentMessages(ctx, tss[i+1].Blocks()[0].Cid())
|
||||||
@ -646,13 +659,13 @@ var ChainListCmd = &cli.Command{
|
|||||||
gasEfficiency := 100 * float64(gasUsed) / float64(limitSum)
|
gasEfficiency := 100 * float64(gasUsed) / float64(limitSum)
|
||||||
gasCapacity := 100 * float64(limitSum) / float64(build.BlockGasLimit)
|
gasCapacity := 100 * float64(limitSum) / float64(build.BlockGasLimit)
|
||||||
|
|
||||||
fmt.Printf("\ttipset: \t%d msgs, %d (%0.2f%%) / %d (%0.2f%%)\n", len(msgs), gasUsed, gasEfficiency, limitSum, gasCapacity)
|
afmt.Printf("\ttipset: \t%d msgs, %d (%0.2f%%) / %d (%0.2f%%)\n", len(msgs), gasUsed, gasEfficiency, limitSum, gasCapacity)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
afmt.Println()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := len(tss) - 1; i >= 0; i-- {
|
for i := len(tss) - 1; i >= 0; i-- {
|
||||||
printTipSet(cctx.String("format"), tss[i])
|
printTipSet(cctx.String("format"), tss[i], afmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -707,6 +720,8 @@ var ChainGetCmd = &cli.Command{
|
|||||||
- account-state
|
- account-state
|
||||||
`,
|
`,
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -725,7 +740,7 @@ var ChainGetCmd = &cli.Command{
|
|||||||
|
|
||||||
p = "/ipfs/" + ts.ParentState().String() + p
|
p = "/ipfs/" + ts.ParentState().String() + p
|
||||||
if cctx.Bool("verbose") {
|
if cctx.Bool("verbose") {
|
||||||
fmt.Println(p)
|
afmt.Println(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,7 +755,7 @@ var ChainGetCmd = &cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(string(b))
|
afmt.Println(string(b))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,7 +797,7 @@ var ChainGetCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cbu == nil {
|
if cbu == nil {
|
||||||
fmt.Printf("%x", raw)
|
afmt.Printf("%x", raw)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,7 +809,7 @@ var ChainGetCmd = &cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(string(b))
|
afmt.Println(string(b))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -878,7 +893,7 @@ func handleHamtAddress(ctx context.Context, api v0api.FullNode, r cid.Cid) error
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTipSet(format string, ts *types.TipSet) {
|
func printTipSet(format string, ts *types.TipSet, afmt *AppFmt) {
|
||||||
format = strings.ReplaceAll(format, "<height>", fmt.Sprint(ts.Height()))
|
format = strings.ReplaceAll(format, "<height>", fmt.Sprint(ts.Height()))
|
||||||
format = strings.ReplaceAll(format, "<time>", time.Unix(int64(ts.MinTimestamp()), 0).Format(time.Stamp))
|
format = strings.ReplaceAll(format, "<time>", time.Unix(int64(ts.MinTimestamp()), 0).Format(time.Stamp))
|
||||||
blks := "[ "
|
blks := "[ "
|
||||||
@ -897,7 +912,7 @@ func printTipSet(format string, ts *types.TipSet) {
|
|||||||
format = strings.ReplaceAll(format, "<blocks>", blks)
|
format = strings.ReplaceAll(format, "<blocks>", blks)
|
||||||
format = strings.ReplaceAll(format, "<weight>", fmt.Sprint(ts.Blocks()[0].ParentWeight))
|
format = strings.ReplaceAll(format, "<weight>", fmt.Sprint(ts.Blocks()[0].ParentWeight))
|
||||||
|
|
||||||
fmt.Println(format)
|
afmt.Println(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ChainBisectCmd = &cli.Command{
|
var ChainBisectCmd = &cli.Command{
|
||||||
@ -918,6 +933,8 @@ var ChainBisectCmd = &cli.Command{
|
|||||||
For special path elements see 'chain get' help
|
For special path elements see 'chain get' help
|
||||||
`,
|
`,
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -961,7 +978,7 @@ var ChainBisectCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := "/ipld/" + midTs.ParentState().String() + "/" + subPath
|
path := "/ipld/" + midTs.ParentState().String() + "/" + subPath
|
||||||
fmt.Printf("* Testing %d (%d - %d) (%s): ", mid, start, end, path)
|
afmt.Printf("* Testing %d (%d - %d) (%s): ", mid, start, end, path)
|
||||||
|
|
||||||
nd, err := api.ChainGetNode(ctx, path)
|
nd, err := api.ChainGetNode(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -988,32 +1005,32 @@ var ChainBisectCmd = &cli.Command{
|
|||||||
if strings.TrimSpace(out.String()) != "false" {
|
if strings.TrimSpace(out.String()) != "false" {
|
||||||
end = mid
|
end = mid
|
||||||
highest = midTs
|
highest = midTs
|
||||||
fmt.Println("true")
|
afmt.Println("true")
|
||||||
} else {
|
} else {
|
||||||
start = mid
|
start = mid
|
||||||
fmt.Printf("false (cli)\n")
|
afmt.Printf("false (cli)\n")
|
||||||
}
|
}
|
||||||
case *exec.ExitError:
|
case *exec.ExitError:
|
||||||
if len(serr.String()) > 0 {
|
if len(serr.String()) > 0 {
|
||||||
fmt.Println("error")
|
afmt.Println("error")
|
||||||
|
|
||||||
fmt.Printf("> Command: %s\n---->\n", strings.Join(cctx.Args().Slice()[3:], " "))
|
afmt.Printf("> Command: %s\n---->\n", strings.Join(cctx.Args().Slice()[3:], " "))
|
||||||
fmt.Println(string(b))
|
afmt.Println(string(b))
|
||||||
fmt.Println("<----")
|
afmt.Println("<----")
|
||||||
return xerrors.Errorf("error running bisect check: %s", serr.String())
|
return xerrors.Errorf("error running bisect check: %s", serr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
start = mid
|
start = mid
|
||||||
fmt.Println("false")
|
afmt.Println("false")
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if start == end {
|
if start == end {
|
||||||
if strings.TrimSpace(out.String()) == "true" {
|
if strings.TrimSpace(out.String()) == "true" {
|
||||||
fmt.Println(midTs.Height())
|
afmt.Println(midTs.Height())
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(prev)
|
afmt.Println(prev)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1058,7 +1075,7 @@ var ChainExportCmd = &cli.Command{
|
|||||||
return fmt.Errorf("\"recent-stateroots\" has to be greater than %d", build.Finality)
|
return fmt.Errorf("\"recent-stateroots\" has to be greater than %d", build.Finality)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Create(cctx.Args().First())
|
fi, err := createExportFile(cctx.App, cctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1118,6 +1135,8 @@ var SlashConsensusFault = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
srv, err := GetFullNodeServices(cctx)
|
srv, err := GetFullNodeServices(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1222,7 +1241,7 @@ var SlashConsensusFault = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(smsg.Cid())
|
afmt.Println(smsg.Cid())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -1232,6 +1251,8 @@ var ChainGasPriceCmd = &cli.Command{
|
|||||||
Name: "gas-price",
|
Name: "gas-price",
|
||||||
Usage: "Estimate gas prices",
|
Usage: "Estimate gas prices",
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1248,7 +1269,7 @@ var ChainGasPriceCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est))
|
afmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -1278,6 +1299,8 @@ var chainDecodeParamsCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
api, closer, err := GetFullNodeAPI(cctx)
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1329,7 +1352,7 @@ var chainDecodeParamsCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(pstr)
|
afmt.Println(pstr)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -1362,6 +1385,8 @@ var chainEncodeParamsCmd = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
|
afmt := NewAppFmt(cctx.App)
|
||||||
|
|
||||||
if cctx.Args().Len() != 3 {
|
if cctx.Args().Len() != 3 {
|
||||||
return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments"))
|
return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments"))
|
||||||
}
|
}
|
||||||
@ -1410,9 +1435,9 @@ var chainEncodeParamsCmd = &cli.Command{
|
|||||||
|
|
||||||
switch cctx.String("encoding") {
|
switch cctx.String("encoding") {
|
||||||
case "base64", "b64":
|
case "base64", "b64":
|
||||||
fmt.Println(base64.StdEncoding.EncodeToString(p))
|
afmt.Println(base64.StdEncoding.EncodeToString(p))
|
||||||
case "hex":
|
case "hex":
|
||||||
fmt.Println(hex.EncodeToString(p))
|
afmt.Println(hex.EncodeToString(p))
|
||||||
default:
|
default:
|
||||||
return xerrors.Errorf("unknown encoding")
|
return xerrors.Errorf("unknown encoding")
|
||||||
}
|
}
|
||||||
@ -1420,3 +1445,16 @@ var chainEncodeParamsCmd = &cli.Command{
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
557
cli/chain_test.go
Normal file
557
cli/chain_test.go
Normal file
@ -0,0 +1,557 @@
|
|||||||
|
//stm: #cli
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
types "github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types/mock"
|
||||||
|
"github.com/filecoin-project/specs-actors/v7/actors/builtin"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
cid "github.com/ipfs/go-cid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChainHead(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainHeadCmd))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ts := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainHead(ctx).Return(ts, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_HEAD_001
|
||||||
|
err := app.Run([]string{"chain", "head"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Regexp(t, regexp.MustCompile(ts.Cids()[0].String()), buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlock(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainGetBlock))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
block := mock.MkBlock(nil, 0, 0)
|
||||||
|
blockMsgs := api.BlockMessages{}
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetBlock(ctx, block.Cid()).Return(block, nil),
|
||||||
|
mockApi.EXPECT().ChainGetBlockMessages(ctx, block.Cid()).Return(&blockMsgs, nil),
|
||||||
|
mockApi.EXPECT().ChainGetParentMessages(ctx, block.Cid()).Return([]api.Message{}, nil),
|
||||||
|
mockApi.EXPECT().ChainGetParentReceipts(ctx, block.Cid()).Return([]*types.MessageReceipt{}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GET_BLOCK_001
|
||||||
|
err := app.Run([]string{"chain", "getblock", block.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// expected output format
|
||||||
|
out := struct {
|
||||||
|
types.BlockHeader
|
||||||
|
BlsMessages []*types.Message
|
||||||
|
SecpkMessages []*types.SignedMessage
|
||||||
|
ParentReceipts []*types.MessageReceipt
|
||||||
|
ParentMessages []cid.Cid
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf.Bytes(), &out)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.True(t, block.Cid().Equals(out.Cid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadOjb(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainReadObjCmd))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
block := mock.MkBlock(nil, 0, 0)
|
||||||
|
obj := new(bytes.Buffer)
|
||||||
|
err := block.MarshalCBOR(obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainReadObj(ctx, block.Cid()).Return(obj.Bytes(), nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_READ_OBJECT_001
|
||||||
|
err = app.Run([]string{"chain", "read-obj", block.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, buf.String(), fmt.Sprintf("%x\n", obj.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainDeleteObj(t *testing.T) {
|
||||||
|
cmd := WithCategory("chain", ChainDeleteObjCmd)
|
||||||
|
block := mock.MkBlock(nil, 0, 0)
|
||||||
|
|
||||||
|
// given no force flag, it should return an error and no API calls should be made
|
||||||
|
t.Run("no-really-do-it", func(t *testing.T) {
|
||||||
|
app, _, _, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_DELETE_OBJECT_002
|
||||||
|
err := app.Run([]string{"chain", "delete-obj", block.Cid().String()})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given a force flag, it calls API delete
|
||||||
|
t.Run("really-do-it", func(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainDeleteObj(ctx, block.Cid()).Return(nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_DELETE_OBJECT_001
|
||||||
|
err := app.Run([]string{"chain", "delete-obj", "--really-do-it=true", block.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, buf.String(), block.Cid().String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainStatObj(t *testing.T) {
|
||||||
|
cmd := WithCategory("chain", ChainStatObjCmd)
|
||||||
|
block := mock.MkBlock(nil, 0, 0)
|
||||||
|
stat := api.ObjStat{Size: 123, Links: 321}
|
||||||
|
|
||||||
|
checkOutput := func(buf *bytes.Buffer) {
|
||||||
|
out := buf.String()
|
||||||
|
outSplit := strings.Split(out, "\n")
|
||||||
|
|
||||||
|
assert.Contains(t, outSplit[0], fmt.Sprintf("%d", stat.Links))
|
||||||
|
assert.Contains(t, outSplit[1], fmt.Sprintf("%d", stat.Size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// given no --base flag, it calls ChainStatObj with base=cid.Undef
|
||||||
|
t.Run("no-base", func(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainStatObj(ctx, block.Cid(), cid.Undef).Return(stat, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_STAT_OBJECT_001
|
||||||
|
err := app.Run([]string{"chain", "stat-obj", block.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
checkOutput(buf)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given a --base flag, it calls ChainStatObj with that base
|
||||||
|
t.Run("base", func(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainStatObj(ctx, block.Cid(), block.Cid()).Return(stat, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_STAT_OBJECT_002
|
||||||
|
err := app.Run([]string{"chain", "stat-obj", fmt.Sprintf("-base=%s", block.Cid().String()), block.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
checkOutput(buf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainGetMsg(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainGetMsgCmd))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
addrs, err := mock.RandomActorAddresses(12345, 2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
from := addrs[0]
|
||||||
|
to := addrs[1]
|
||||||
|
|
||||||
|
msg := mock.UnsignedMessage(*from, *to, 0)
|
||||||
|
|
||||||
|
obj := new(bytes.Buffer)
|
||||||
|
err = msg.MarshalCBOR(obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainReadObj(ctx, msg.Cid()).Return(obj.Bytes(), nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GET_MESSAGE_001
|
||||||
|
err = app.Run([]string{"chain", "getmessage", msg.Cid().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var out types.Message
|
||||||
|
err = json.Unmarshal(buf.Bytes(), &out)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, *msg, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetHead(t *testing.T) {
|
||||||
|
cmd := WithCategory("chain", ChainSetHeadCmd)
|
||||||
|
genesis := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
||||||
|
ts := mock.TipSet(mock.MkBlock(genesis, 1, 0))
|
||||||
|
epoch := abi.ChainEpoch(uint64(0))
|
||||||
|
|
||||||
|
// given the -genesis flag, resets head to genesis ignoring the provided ts positional argument
|
||||||
|
t.Run("genesis", func(t *testing.T) {
|
||||||
|
app, mockApi, _, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetGenesis(ctx).Return(genesis, nil),
|
||||||
|
mockApi.EXPECT().ChainSetHead(ctx, genesis.Key()).Return(nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_SET_HEAD_003
|
||||||
|
err := app.Run([]string{"chain", "sethead", "-genesis=true", ts.Key().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given the -epoch flag, resets head to given epoch, ignoring the provided ts positional argument
|
||||||
|
t.Run("epoch", func(t *testing.T) {
|
||||||
|
app, mockApi, _, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetTipSetByHeight(ctx, epoch, types.EmptyTSK).Return(genesis, nil),
|
||||||
|
mockApi.EXPECT().ChainSetHead(ctx, genesis.Key()).Return(nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_SET_HEAD_002
|
||||||
|
err := app.Run([]string{"chain", "sethead", fmt.Sprintf("-epoch=%s", epoch), ts.Key().String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given no flag, resets the head to given tipset key
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
app, mockApi, _, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetBlock(ctx, ts.Key().Cids()[0]).Return(ts.Blocks()[0], nil),
|
||||||
|
mockApi.EXPECT().ChainSetHead(ctx, ts.Key()).Return(nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_SET_HEAD_001
|
||||||
|
err := app.Run([]string{"chain", "sethead", ts.Key().Cids()[0].String()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInspectUsage(t *testing.T) {
|
||||||
|
cmd := WithCategory("chain", ChainInspectUsage)
|
||||||
|
ts := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
||||||
|
|
||||||
|
addrs, err := mock.RandomActorAddresses(12345, 2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
from := addrs[0]
|
||||||
|
to := addrs[1]
|
||||||
|
|
||||||
|
msg := mock.UnsignedMessage(*from, *to, 0)
|
||||||
|
msgs := []api.Message{{Cid: msg.Cid(), Message: msg}}
|
||||||
|
|
||||||
|
actor := &types.Actor{
|
||||||
|
Code: builtin.StorageMarketActorCodeID,
|
||||||
|
Nonce: 0,
|
||||||
|
Balance: big.NewInt(1000000000),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainHead(ctx).Return(ts, nil),
|
||||||
|
mockApi.EXPECT().ChainGetParentMessages(ctx, ts.Blocks()[0].Cid()).Return(msgs, nil),
|
||||||
|
mockApi.EXPECT().ChainGetTipSet(ctx, ts.Parents()).Return(nil, nil),
|
||||||
|
mockApi.EXPECT().StateGetActor(ctx, *to, ts.Key()).Return(actor, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_INSPECT_USAGE_001
|
||||||
|
err := app.Run([]string{"chain", "inspect-usage"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
out := buf.String()
|
||||||
|
|
||||||
|
// output is plaintext, had to do string matching
|
||||||
|
assert.Contains(t, out, from.String())
|
||||||
|
assert.Contains(t, out, to.String())
|
||||||
|
// check for gas by sender
|
||||||
|
assert.Contains(t, out, "By Sender")
|
||||||
|
// check for gas by method
|
||||||
|
assert.Contains(t, out, "By Method:\nSend")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainList(t *testing.T) {
|
||||||
|
cmd := WithCategory("chain", ChainListCmd)
|
||||||
|
genesis := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
||||||
|
blk := mock.MkBlock(genesis, 0, 0)
|
||||||
|
blk.Height = 1
|
||||||
|
head := mock.TipSet(blk)
|
||||||
|
|
||||||
|
addrs, err := mock.RandomActorAddresses(12345, 2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
from := addrs[0]
|
||||||
|
to := addrs[1]
|
||||||
|
|
||||||
|
msg := mock.UnsignedMessage(*from, *to, 0)
|
||||||
|
msgs := []api.Message{{Cid: msg.Cid(), Message: msg}}
|
||||||
|
blockMsgs := &api.BlockMessages{}
|
||||||
|
receipts := []*types.MessageReceipt{}
|
||||||
|
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// same method gets called mocked multiple times bcs it's called in a for loop for all tipsets (2 in this case)
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainHead(ctx).Return(head, nil),
|
||||||
|
mockApi.EXPECT().ChainGetTipSet(ctx, head.Parents()).Return(genesis, nil),
|
||||||
|
mockApi.EXPECT().ChainGetBlockMessages(ctx, genesis.Blocks()[0].Cid()).Return(blockMsgs, nil),
|
||||||
|
mockApi.EXPECT().ChainGetParentMessages(ctx, head.Blocks()[0].Cid()).Return(msgs, nil),
|
||||||
|
mockApi.EXPECT().ChainGetParentReceipts(ctx, head.Blocks()[0].Cid()).Return(receipts, nil),
|
||||||
|
mockApi.EXPECT().ChainGetBlockMessages(ctx, head.Blocks()[0].Cid()).Return(blockMsgs, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: CLI_CHAIN_LIST_001
|
||||||
|
err := app.Run([]string{"chain", "love", "--gas-stats=true"}) // chain is love ❤️
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
out := buf.String()
|
||||||
|
|
||||||
|
// should print out 2 blocks, indexed with 0: and 1:
|
||||||
|
assert.Contains(t, out, "0:")
|
||||||
|
assert.Contains(t, out, "1:")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainGet(t *testing.T) {
|
||||||
|
blk := mock.MkBlock(nil, 0, 0)
|
||||||
|
ts := mock.TipSet(blk)
|
||||||
|
cmd := WithCategory("chain", ChainGetCmd)
|
||||||
|
|
||||||
|
// given no -as-type flag & ipfs prefix, should print object as JSON if it's marshalable
|
||||||
|
t.Run("ipfs", func(t *testing.T) {
|
||||||
|
path := fmt.Sprintf("/ipfs/%s", blk.Cid().String())
|
||||||
|
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetNode(ctx, path).Return(&api.IpldObject{Cid: blk.Cid(), Obj: blk}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GET_001
|
||||||
|
err := app.Run([]string{"chain", "get", path})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var out types.BlockHeader
|
||||||
|
err = json.Unmarshal(buf.Bytes(), &out)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, *blk, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given no -as-type flag & ipfs prefix, should traverse from head.ParentStateRoot and print JSON if it's marshalable
|
||||||
|
t.Run("pstate", func(t *testing.T) {
|
||||||
|
p1 := "/pstate"
|
||||||
|
p2 := fmt.Sprintf("/ipfs/%s", ts.ParentState().String())
|
||||||
|
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainHead(ctx).Return(ts, nil),
|
||||||
|
mockApi.EXPECT().ChainGetNode(ctx, p2).Return(&api.IpldObject{Cid: blk.Cid(), Obj: blk}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GET_002
|
||||||
|
err := app.Run([]string{"chain", "get", p1})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var out types.BlockHeader
|
||||||
|
err = json.Unmarshal(buf.Bytes(), &out)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, *blk, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
// given an unknown -as-type value, return an error
|
||||||
|
t.Run("unknown-type", func(t *testing.T) {
|
||||||
|
app, mockApi, _, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/ipfs/%s", blk.Cid().String())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetNode(ctx, path).Return(&api.IpldObject{Cid: blk.Cid(), Obj: blk}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GET_004
|
||||||
|
err := app.Run([]string{"chain", "get", "-as-type=foo", path})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainBisect(t *testing.T) {
|
||||||
|
blk1 := mock.MkBlock(nil, 0, 0)
|
||||||
|
blk1.Height = 0
|
||||||
|
ts1 := mock.TipSet(blk1)
|
||||||
|
|
||||||
|
blk2 := mock.MkBlock(ts1, 0, 0)
|
||||||
|
blk2.Height = 1
|
||||||
|
ts2 := mock.TipSet(blk2)
|
||||||
|
|
||||||
|
subpath := "whatever/its/mocked"
|
||||||
|
minHeight := uint64(0)
|
||||||
|
maxHeight := uint64(1)
|
||||||
|
shell := "echo"
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/ipld/%s/%s", ts2.ParentState(), subpath)
|
||||||
|
|
||||||
|
cmd := WithCategory("chain", ChainBisectCmd)
|
||||||
|
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, cmd)
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainGetTipSetByHeight(ctx, abi.ChainEpoch(maxHeight), types.EmptyTSK).Return(ts2, nil),
|
||||||
|
mockApi.EXPECT().ChainGetTipSetByHeight(ctx, abi.ChainEpoch(maxHeight), ts2.Key()).Return(ts2, nil),
|
||||||
|
mockApi.EXPECT().ChainGetNode(ctx, path).Return(&api.IpldObject{Cid: blk2.Cid(), Obj: blk2}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_BISECT_001
|
||||||
|
err := app.Run([]string{"chain", "bisect", fmt.Sprintf("%d", minHeight), fmt.Sprintf("%d", maxHeight), subpath, shell})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
out := buf.String()
|
||||||
|
assert.Contains(t, out, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainExport(t *testing.T) {
|
||||||
|
app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainExportCmd))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
// export writes to a file, I mocked it so there are no side-effects
|
||||||
|
mockFile := mockExportFile{new(bytes.Buffer)}
|
||||||
|
app.Metadata["export-file"] = mockFile
|
||||||
|
|
||||||
|
blk := mock.MkBlock(nil, 0, 0)
|
||||||
|
ts := mock.TipSet(blk)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
export := make(chan []byte, 2)
|
||||||
|
expBytes := []byte("whatever")
|
||||||
|
export <- expBytes
|
||||||
|
export <- []byte{} // empty slice means export is complete
|
||||||
|
close(export)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mockApi.EXPECT().ChainHead(ctx).Return(ts, nil),
|
||||||
|
mockApi.EXPECT().ChainExport(ctx, abi.ChainEpoch(0), false, ts.Key()).Return(export, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_EXPORT_001
|
||||||
|
err := app.Run([]string{"chain", "export", "whatever.car"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expBytes, mockFile.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainGasPrice(t *testing.T) {
|
||||||
|
app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("chain", ChainGasPriceCmd))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// estimate gas is called with various num blocks in implementation,
|
||||||
|
// so we mock and count how many times it's called, and we expect that many results printed
|
||||||
|
calls := 0
|
||||||
|
mockApi.
|
||||||
|
EXPECT().
|
||||||
|
GasEstimateGasPremium(ctx, gomock.Any(), builtin.SystemActorAddr, int64(10000), types.EmptyTSK).
|
||||||
|
Return(big.NewInt(0), nil).
|
||||||
|
AnyTimes().
|
||||||
|
Do(func(a, b, c, d, e interface{}) { // looks funny, but we don't care about args here, just counting
|
||||||
|
calls++
|
||||||
|
})
|
||||||
|
|
||||||
|
//stm: @CLI_CHAIN_GAS_PRICE_001
|
||||||
|
err := app.Run([]string{"chain", "gas-price"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
lines := strings.Split(strings.Trim(buf.String(), "\n"), "\n")
|
||||||
|
assert.Equal(t, calls, len(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockExportFile struct {
|
||||||
|
*bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mef mockExportFile) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
32
cli/mocks_test.go
Normal file
32
cli/mocks_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/api/mocks"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
ucli "github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newMockAppWithFullAPI returns a gomock-ed CLI app used for unit tests
|
||||||
|
// see cli/util/api.go:GetFullNodeAPI for mock API injection
|
||||||
|
func NewMockAppWithFullAPI(t *testing.T, cmd *ucli.Command) (*ucli.App, *mocks.MockFullNode, *bytes.Buffer, func()) {
|
||||||
|
app := ucli.NewApp()
|
||||||
|
app.Commands = ucli.Commands{cmd}
|
||||||
|
app.Setup()
|
||||||
|
|
||||||
|
// create and inject the mock API into app Metadata
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockFullNode := mocks.NewMockFullNode(ctrl)
|
||||||
|
var fullNode api.FullNode = mockFullNode
|
||||||
|
app.Metadata["test-full-api"] = fullNode
|
||||||
|
|
||||||
|
// this will only work if the implementation uses the app.Writer,
|
||||||
|
// if it uses fmt.*, it has to be refactored
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
app.Writer = buf
|
||||||
|
|
||||||
|
return app, mockFullNode, buf, ctrl.Finish
|
||||||
|
}
|
@ -223,6 +223,11 @@ func GetCommonAPI(ctx *cli.Context) (api.CommonNet, jsonrpc.ClientCloser, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetFullNodeAPI(ctx *cli.Context) (v0api.FullNode, jsonrpc.ClientCloser, error) {
|
func GetFullNodeAPI(ctx *cli.Context) (v0api.FullNode, jsonrpc.ClientCloser, error) {
|
||||||
|
// use the mocked API in CLI unit tests, see cli/mocks_test.go for mock definition
|
||||||
|
if mock, ok := ctx.App.Metadata["test-full-api"]; ok {
|
||||||
|
return &v0api.WrapperV1Full{FullNode: mock.(v1api.FullNode)}, func() {}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
|
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
|
||||||
return &v0api.WrapperV1Full{FullNode: tn.(v1api.FullNode)}, func() {}, nil
|
return &v0api.WrapperV1Full{FullNode: tn.(v1api.FullNode)}, func() {}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user