// stm: #unit package cli import ( "context" "encoding/json" "fmt" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "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" ) 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, _ := 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), ) //stm: @CLI_MEMPOOL_STAT_002 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, _ := 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), ) //stm: @CLI_MEMPOOL_STAT_001 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, _ := 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), ) //stm: @CLI_MEMPOOL_PENDING_001 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, _ := 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), ) //stm: @CLI_MEMPOOL_PENDING_002 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, _ := 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), ) //stm: @CLI_MEMPOOL_PENDING_003 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, _ := 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), ) //stm: @CLI_MEMPOOL_PENDING_004 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, _ := 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), ) //stm: @CLI_MEMPOOL_REPLACE_002 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, _ := 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), ) //stm: @CLI_MEMPOOL_REPLACE_002 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()) }) t.Run("sender / nonce", 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, _ := 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().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), ) //stm: @CLI_MEMPOOL_REPLACE_001 err = app.Run([]string{"mpool", "replace", "--auto", "--fee-limit", maxFee, sm.Message.From.String(), fmt.Sprint(sm.Message.Nonce)}) 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, _ := 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), ) //stm: @CLI_MEMPOOL_FIND_001 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, _ := 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), ) //stm: @CLI_MEMPOOL_FIND_002 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, _ := 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), ) //stm: @CLI_MEMPOOL_FIND_003 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, _ := 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), ) //stm: @CLI_MEMPOOL_GAS_PERF_002 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, _ := 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), ) //stm: @CLI_MEMPOOL_GAS_PERF_001 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, _ := 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), ) //stm: @CLI_MEMPOOL_CONFIG_001 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, _ := 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) } //stm: @CLI_MEMPOOL_CONFIG_002 err = app.Run([]string{"mpool", "config", string(bytes)}) assert.NoError(t, err) }) }