diff --git a/cli/mpool.go b/cli/mpool.go index adefd25a8..0224b15d2 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -60,6 +60,8 @@ var MpoolPending = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -72,7 +74,7 @@ var MpoolPending = &cli.Command{ if tos := cctx.String("to"); tos != "" { a, err := address.NewFromString(tos) if err != nil { - return fmt.Errorf("given 'to' address %q was invalid: %w", tos, err) + return xerrors.Errorf("given 'to' address %q was invalid: %w", tos, err) } toa = a } @@ -80,7 +82,7 @@ var MpoolPending = &cli.Command{ if froms := cctx.String("from"); froms != "" { a, err := address.NewFromString(froms) if err != nil { - return fmt.Errorf("given 'from' address %q was invalid: %w", froms, err) + return xerrors.Errorf("given 'from' address %q was invalid: %w", froms, err) } froma = a } @@ -119,13 +121,13 @@ var MpoolPending = &cli.Command{ } if cctx.Bool("cids") { - fmt.Println(msg.Cid()) + afmt.Println(msg.Cid()) } else { out, err := json.MarshalIndent(msg, "", " ") if err != nil { return err } - fmt.Println(string(out)) + afmt.Println(string(out)) } } @@ -216,6 +218,8 @@ var MpoolStat = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -234,6 +238,7 @@ var MpoolStat = &cli.Command{ currTs := ts for i := 0; i < cctx.Int("basefee-lookback"); i++ { currTs, err = api.ChainGetTipSet(ctx, currTs.Parents()) + if err != nil { return xerrors.Errorf("walking chain: %w", err) } @@ -296,7 +301,7 @@ var MpoolStat = &cli.Command{ for a, bkt := range buckets { act, err := api.StateGetActor(ctx, a, ts.Key()) if err != nil { - fmt.Printf("%s, err: %s\n", a, err) + afmt.Printf("%s, err: %s\n", a, err) continue } @@ -350,11 +355,11 @@ var MpoolStat = &cli.Command{ total.belowPast += stat.belowPast total.gasLimit = big.Add(total.gasLimit, stat.gasLimit) - fmt.Printf("%s: Nonce past: %d, cur: %d, future: %d; FeeCap cur: %d, min-%d: %d, gasLimit: %s\n", stat.addr, stat.past, stat.cur, stat.future, stat.belowCurr, cctx.Int("basefee-lookback"), stat.belowPast, stat.gasLimit) + afmt.Printf("%s: Nonce past: %d, cur: %d, future: %d; FeeCap cur: %d, min-%d: %d, gasLimit: %s\n", stat.addr, stat.past, stat.cur, stat.future, stat.belowCurr, cctx.Int("basefee-lookback"), stat.belowPast, stat.gasLimit) } - fmt.Println("-----") - fmt.Printf("total: Nonce past: %d, cur: %d, future: %d; FeeCap cur: %d, min-%d: %d, gasLimit: %s\n", total.past, total.cur, total.future, total.belowCurr, cctx.Int("basefee-lookback"), total.belowPast, total.gasLimit) + afmt.Println("-----") + afmt.Printf("total: Nonce past: %d, cur: %d, future: %d; FeeCap cur: %d, min-%d: %d, gasLimit: %s\n", total.past, total.cur, total.future, total.belowCurr, cctx.Int("basefee-lookback"), total.belowPast, total.gasLimit) return nil }, @@ -385,8 +390,9 @@ var MpoolReplaceCmd = &cli.Command{ Usage: "Spend up to X FIL for this message in units of FIL. Previously when flag was `max-fee` units were in attoFIL. Applicable for auto mode", }, }, - ArgsUsage: " | ", + ArgsUsage: " | ", Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) api, closer, err := GetFullNodeAPI(cctx) if err != nil { @@ -407,13 +413,14 @@ var MpoolReplaceCmd = &cli.Command{ msg, err := api.ChainGetMessage(ctx, mcid) if err != nil { - return fmt.Errorf("could not find referenced message: %w", err) + return xerrors.Errorf("could not find referenced message: %w", err) } from = msg.From nonce = msg.Nonce case 2: - f, err := address.NewFromString(cctx.Args().Get(0)) + arg0 := cctx.Args().Get(0) + f, err := address.NewFromString(arg0) if err != nil { return err } @@ -448,7 +455,7 @@ var MpoolReplaceCmd = &cli.Command{ } if found == nil { - return fmt.Errorf("no pending message found from %s with nonce %d", from, nonce) + return xerrors.Errorf("no pending message found from %s with nonce %d", from, nonce) } msg := found.Message @@ -460,7 +467,7 @@ var MpoolReplaceCmd = &cli.Command{ if cctx.IsSet("fee-limit") { maxFee, err := types.ParseFIL(cctx.String("fee-limit")) if err != nil { - return fmt.Errorf("parsing max-spend: %w", err) + return xerrors.Errorf("parsing max-spend: %w", err) } mss = &lapi.MessageSendSpec{ MaxFee: abi.TokenAmount(maxFee), @@ -472,7 +479,7 @@ var MpoolReplaceCmd = &cli.Command{ msg.GasPremium = abi.NewTokenAmount(0) retm, err := api.GasEstimateMessageGas(ctx, &msg, mss, types.EmptyTSK) if err != nil { - return fmt.Errorf("failed to estimate gas values: %w", err) + return xerrors.Errorf("failed to estimate gas values: %w", err) } msg.GasPremium = big.Max(retm.GasPremium, minRBF) @@ -489,26 +496,26 @@ var MpoolReplaceCmd = &cli.Command{ } msg.GasPremium, err = types.BigFromString(cctx.String("gas-premium")) if err != nil { - return fmt.Errorf("parsing gas-premium: %w", err) + return xerrors.Errorf("parsing gas-premium: %w", err) } // TODO: estimate fee cap here msg.GasFeeCap, err = types.BigFromString(cctx.String("gas-feecap")) if err != nil { - return fmt.Errorf("parsing gas-feecap: %w", err) + return xerrors.Errorf("parsing gas-feecap: %w", err) } } smsg, err := api.WalletSignMessage(ctx, msg.From, &msg) if err != nil { - return fmt.Errorf("failed to sign message: %w", err) + return xerrors.Errorf("failed to sign message: %w", err) } cid, err := api.MpoolPush(ctx, smsg) if err != nil { - return fmt.Errorf("failed to push new message to mempool: %w", err) + return xerrors.Errorf("failed to push new message to mempool: %w", err) } - fmt.Println("new message cid: ", cid) + afmt.Println("new message cid: ", cid) return nil }, } @@ -531,6 +538,8 @@ var MpoolFindCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -548,7 +557,7 @@ var MpoolFindCmd = &cli.Command{ if cctx.IsSet("to") { a, err := address.NewFromString(cctx.String("to")) if err != nil { - return fmt.Errorf("'to' address was invalid: %w", err) + return xerrors.Errorf("'to' address was invalid: %w", err) } toFilter = a @@ -557,7 +566,7 @@ var MpoolFindCmd = &cli.Command{ if cctx.IsSet("from") { a, err := address.NewFromString(cctx.String("from")) if err != nil { - return fmt.Errorf("'from' address was invalid: %w", err) + return xerrors.Errorf("'from' address was invalid: %w", err) } fromFilter = a @@ -591,7 +600,7 @@ var MpoolFindCmd = &cli.Command{ return err } - fmt.Println(string(b)) + afmt.Println(string(b)) return nil }, } @@ -605,6 +614,8 @@ var MpoolConfig = &cli.Command{ return cli.ShowCommandHelp(cctx, cctx.Command.Name) } + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -624,7 +635,7 @@ var MpoolConfig = &cli.Command{ return err } - fmt.Println(string(bytes)) + afmt.Println(string(bytes)) } else { cfg := new(types.MpoolConfig) bytes := []byte(cctx.Args().Get(0)) @@ -651,6 +662,8 @@ var MpoolGasPerfCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -717,7 +730,7 @@ var MpoolGasPerfCmd = &cli.Command{ gasReward := getGasReward(m) gasPerf := getGasPerf(gasReward, m.Message.GasLimit) - fmt.Printf("%s\t%d\t%s\t%f\n", m.Message.From, m.Message.Nonce, gasReward, gasPerf) + afmt.Printf("%s\t%d\t%s\t%f\n", m.Message.From, m.Message.Nonce, gasReward, gasPerf) } return nil diff --git a/cli/mpool_test.go b/cli/mpool_test.go new file mode 100644 index 000000000..1bb45fd62 --- /dev/null +++ b/cli/mpool_test.go @@ -0,0 +1,522 @@ +package cli + +import ( + "context" + "fmt" + "testing" + + "encoding/json" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestStat(t *testing.T) { + + t.Run("local", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolStat)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // add blocks to the chain + first := mock.TipSet(mock.MkBlock(nil, 5, 4)) + head := mock.TipSet(mock.MkBlock(first, 15, 7)) + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + // mock actor to return for the sender + actor := types.Actor{Nonce: 2, Balance: big.NewInt(200000)} + + gomock.InOrder( + mockApi.EXPECT().ChainHead(ctx).Return(head, nil), + mockApi.EXPECT().ChainGetTipSet(ctx, head.Parents()).Return(first, nil), + mockApi.EXPECT().WalletList(ctx).Return([]address.Address{senderAddr, toAddr}, nil), + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + mockApi.EXPECT().StateGetActor(ctx, senderAddr, head.Key()).Return(&actor, nil), + ) + + err = app.Run([]string{"mpool", "stat", "--basefee-lookback", "1", "--local"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), "Nonce past: 1") + }) + + t.Run("all", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolStat)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // add blocks to the chain + first := mock.TipSet(mock.MkBlock(nil, 5, 4)) + head := mock.TipSet(mock.MkBlock(first, 15, 7)) + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + // mock actor to return for the sender + actor := types.Actor{Nonce: 2, Balance: big.NewInt(200000)} + + gomock.InOrder( + mockApi.EXPECT().ChainHead(ctx).Return(head, nil), + mockApi.EXPECT().ChainGetTipSet(ctx, head.Parents()).Return(first, nil), + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + mockApi.EXPECT().StateGetActor(ctx, senderAddr, head.Key()).Return(&actor, nil), + ) + + err = app.Run([]string{"mpool", "stat", "--basefee-lookback", "1"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), "Nonce past: 1") + }) +} + +func TestPending(t *testing.T) { + t.Run("all", func(t *testing.T) { + + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolPending)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "pending", "--cids"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("local", func(t *testing.T) { + + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolPending)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().WalletList(ctx).Return([]address.Address{senderAddr}, nil), + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "pending", "--local"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("to", func(t *testing.T) { + + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolPending)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "pending", "--to", sm.Message.To.String()}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("from", func(t *testing.T) { + + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolPending)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "pending", "--from", sm.Message.From.String()}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + +} + +func TestReplace(t *testing.T) { + t.Run("manual", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolReplaceCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().ChainGetMessage(ctx, sm.Cid()).Return(&sm.Message, nil), + mockApi.EXPECT().ChainHead(ctx).Return(nil, nil), + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + mockApi.EXPECT().WalletSignMessage(ctx, sm.Message.From, &sm.Message).Return(sm, nil), + mockApi.EXPECT().MpoolPush(ctx, sm).Return(sm.Cid(), nil), + ) + + err = app.Run([]string{"mpool", "replace", "--gas-premium", "1", "--gas-feecap", "100", sm.Cid().String()}) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("auto", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolReplaceCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + // gas fee param should be equal to the one passed in the cli invocation (used below) + maxFee := "1000000" + parsedFee, err := types.ParseFIL(maxFee) + if err != nil { + t.Fatal(err) + } + mss := api.MessageSendSpec{MaxFee: abi.TokenAmount(parsedFee)} + + gomock.InOrder( + mockApi.EXPECT().ChainGetMessage(ctx, sm.Cid()).Return(&sm.Message, nil), + mockApi.EXPECT().ChainHead(ctx).Return(nil, nil), + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + // use gomock.any to match the message in expected api calls + // since the replace function modifies the message between calls, it would be pointless to try to match the exact argument + mockApi.EXPECT().GasEstimateMessageGas(ctx, gomock.Any(), &mss, types.EmptyTSK).Return(&sm.Message, nil), + mockApi.EXPECT().WalletSignMessage(ctx, sm.Message.From, gomock.Any()).Return(sm, nil), + mockApi.EXPECT().MpoolPush(ctx, sm).Return(sm.Cid(), nil), + ) + + err = app.Run([]string{"mpool", "replace", "--auto", "--fee-limit", maxFee, sm.Cid().String()}) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), sm.Cid().String()) + }) +} + +func TestFindMsg(t *testing.T) { + t.Run("from", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolFindCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "find", "--from", sm.Message.From.String()}) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("to", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolFindCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "find", "--to", sm.Message.To.String()}) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), sm.Cid().String()) + }) + + t.Run("method", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolFindCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 1, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + ) + + err = app.Run([]string{"mpool", "find", "--method", sm.Message.Method.String()}) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), sm.Cid().String()) + }) +} + +func TestGasPerf(t *testing.T) { + t.Run("all", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolGasPerfCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // add blocks to the chain + first := mock.TipSet(mock.MkBlock(nil, 5, 4)) + head := mock.TipSet(mock.MkBlock(first, 15, 7)) + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 13, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + mockApi.EXPECT().ChainHead(ctx).Return(head, nil), + ) + + err = app.Run([]string{"mpool", "gas-perf", "--all", "true"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Message.From.String()) + assert.Contains(t, buf.String(), fmt.Sprint(sm.Message.Nonce)) + }) + + t.Run("local", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolGasPerfCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // add blocks to the chain + first := mock.TipSet(mock.MkBlock(nil, 5, 4)) + head := mock.TipSet(mock.MkBlock(first, 15, 7)) + + // create a signed message to be returned as a pending message + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + toAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + sm := mock.MkMessage(senderAddr, toAddr, 13, w) + + gomock.InOrder( + mockApi.EXPECT().MpoolPending(ctx, types.EmptyTSK).Return([]*types.SignedMessage{sm}, nil), + mockApi.EXPECT().WalletList(ctx).Return([]address.Address{senderAddr}, nil), + mockApi.EXPECT().ChainHead(ctx).Return(head, nil), + ) + + err = app.Run([]string{"mpool", "gas-perf"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), sm.Message.From.String()) + assert.Contains(t, buf.String(), fmt.Sprint(sm.Message.Nonce)) + }) +} + +func TestConfig(t *testing.T) { + t.Run("get", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolConfig)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 1234567, SizeLimitLow: 6, ReplaceByFeeRatio: 0.25} + gomock.InOrder( + mockApi.EXPECT().MpoolGetConfig(ctx).Return(mpoolCfg, nil), + ) + + err = app.Run([]string{"mpool", "config"}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), mpoolCfg.PriorityAddrs[0].String()) + assert.Contains(t, buf.String(), fmt.Sprint(mpoolCfg.SizeLimitHigh)) + assert.Contains(t, buf.String(), fmt.Sprint(mpoolCfg.SizeLimitLow)) + assert.Contains(t, buf.String(), fmt.Sprint(mpoolCfg.ReplaceByFeeRatio)) + }) + + t.Run("set", func(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("mpool", MpoolConfig)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + senderAddr, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + mpoolCfg := &types.MpoolConfig{PriorityAddrs: []address.Address{senderAddr}, SizeLimitHigh: 234567, SizeLimitLow: 3, ReplaceByFeeRatio: 0.33} + gomock.InOrder( + mockApi.EXPECT().MpoolSetConfig(ctx, mpoolCfg).Return(nil), + ) + + bytes, err := json.Marshal(mpoolCfg) + if err != nil { + t.Fatal(err) + } + + err = app.Run([]string{"mpool", "config", string(bytes)}) + assert.NoError(t, err) + }) +}