diff --git a/api/grpcserver_test.go b/api/grpcserver_test.go index e91dbeedaa..91b0c280f9 100644 --- a/api/grpcserver_test.go +++ b/api/grpcserver_test.go @@ -23,7 +23,27 @@ import ( ) func TestGrpcServer_GetAccount(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + core := mock_apicoreservice.NewMockCoreService(ctrl) + grpcSvr := newGRPCHandler(core) + for _, test := range _getAccountTests { + accountMeta := &iotextypes.AccountMeta{ + Address: test.address, + } + blockIdentifier := &iotextypes.BlockIdentifier{} + request := &iotexapi.GetAccountRequest{ + Address: test.address, + } + + core.EXPECT().Account(gomock.Any()).Return(accountMeta, blockIdentifier, nil) + + res, err := grpcSvr.GetAccount(context.Background(), request) + require.NoError(err) + require.Equal(test.address, res.AccountMeta.Address) + } } func TestGrpcServer_GetActions(t *testing.T) { diff --git a/ioctl/newcmd/bc/bc.go b/ioctl/newcmd/bc/bc.go index e397c08848..003e3e4f0e 100644 --- a/ioctl/newcmd/bc/bc.go +++ b/ioctl/newcmd/bc/bc.go @@ -100,21 +100,21 @@ func GetEpochMeta(client ioctl.Client, epochNum uint64) (*iotexapi.GetEpochMetaR } // GetProbationList gets probation list -func GetProbationList(client ioctl.Client, epochNum uint64) (*iotexapi.ReadStateResponse, error) { +func GetProbationList(client ioctl.Client, epochNum uint64, epochStartHeight uint64) (*iotexapi.ReadStateResponse, error) { apiServiceClient, err := client.APIServiceClient() if err != nil { return nil, err } - ctx := context.Background() - jwtMD, err := util.JwtAuth() - if err == nil { - ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) - } request := &iotexapi.ReadStateRequest{ ProtocolID: []byte("poll"), MethodName: []byte("ProbationListByEpoch"), Arguments: [][]byte{[]byte(strconv.FormatUint(epochNum, 10))}, + Height: strconv.FormatUint(epochStartHeight, 10), + } + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } response, err := apiServiceClient.ReadState(ctx, request) @@ -123,7 +123,7 @@ func GetProbationList(client ioctl.Client, epochNum uint64) (*iotexapi.ReadState if ok && sta.Code() == codes.NotFound { return nil, nil } else if ok { - return nil, errors.Wrap(nil, sta.Message()) + return nil, errors.New(sta.Message()) } return nil, errors.Wrap(err, "failed to invoke ReadState api") } diff --git a/ioctl/newcmd/node/nodedelegate.go b/ioctl/newcmd/node/nodedelegate.go index 6daad566a9..c10fc5a485 100644 --- a/ioctl/newcmd/node/nodedelegate.go +++ b/ioctl/newcmd/node/nodedelegate.go @@ -20,7 +20,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/iotexproject/iotex-core/action/protocol/vote" "github.com/iotexproject/iotex-core/ioctl" "github.com/iotexproject/iotex-core/ioctl/config" "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" @@ -190,16 +189,10 @@ func NewNodeDelegateCmd(client ioctl.Client) *cobra.Command { StartBlock: int(epochData.Height), TotalBlocks: int(response.TotalBlocks), } - probationListRes, err := bc.GetProbationList(client, epochNum) + probationList, err := getProbationList(client, epochNum, epochData.Height) if err != nil { return errors.Wrap(err, "failed to get probation list") } - probationList := &vote.ProbationList{} - if probationListRes != nil { - if err := probationList.Deserialize(probationListRes.Data); err != nil { - return errors.Wrap(err, "failed to deserialize probation list") - } - } for rank, bp := range response.BlockProducersInfo { votes, ok := new(big.Int).SetString(bp.Votes, 10) if !ok { diff --git a/ioctl/newcmd/node/nodeprobationlist.go b/ioctl/newcmd/node/nodeprobationlist.go new file mode 100644 index 0000000000..d27e3804b0 --- /dev/null +++ b/ioctl/newcmd/node/nodeprobationlist.go @@ -0,0 +1,94 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package node + +import ( + "encoding/json" + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/iotexproject/iotex-core/action/protocol/vote" + "github.com/iotexproject/iotex-core/ioctl" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" +) + +// Multi-language support +var ( + _probationlistCmdUses = map[config.Language]string{ + config.English: "probationlist [-e epoch-num]", + config.Chinese: "probationlist [-e epoch数]", + } + _probationlistCmdShorts = map[config.Language]string{ + config.English: "Print probation list at given epoch", + config.Chinese: "打印给定epoch内的试用名单", + } +) + +// NewNodeProbationlistCmd represents querying probation list command +func NewNodeProbationlistCmd(client ioctl.Client) *cobra.Command { + var epochNum uint64 + use, _ := client.SelectTranslation(_probationlistCmdUses) + short, _ := client.SelectTranslation(_probationlistCmdShorts) + flagEpochNumUsages, _ := client.SelectTranslation(_flagEpochNumUsages) + + cmd := &cobra.Command{ + Use: use, + Short: short, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + if epochNum == 0 { + chainMeta, err := bc.GetChainMeta(client) + if err != nil { + return errors.Wrap(err, "failed to get chain meta") + } + epochNum = chainMeta.GetEpoch().GetNum() + } + response, err := bc.GetEpochMeta(client, epochNum) + if err != nil { + return errors.Wrap(err, "failed to get epoch meta") + } + probationlist, err := getProbationList(client, epochNum, response.EpochData.Height) + if err != nil { + return errors.Wrap(err, "failed to get probation list") + } + delegateList := make([]string, 0) + for addr := range probationlist.ProbationInfo { + delegateList = append(delegateList, addr) + } + byteAsJSON, err := json.MarshalIndent(delegateList, "", " ") + if err != nil { + return err + } + cmd.Printf("EpochNumber : %d, IntensityRate : %d%%\nProbationList : %s", + epochNum, + probationlist.IntensityRate, + fmt.Sprint(string(byteAsJSON)), + ) + return nil + }, + } + cmd.PersistentFlags().Uint64VarP(&epochNum, "epoch-num", "e", 0, flagEpochNumUsages) + return cmd +} + +func getProbationList(client ioctl.Client, epochNum uint64, epochStartHeight uint64) (*vote.ProbationList, error) { + probationListRes, err := bc.GetProbationList(client, epochNum, epochStartHeight) + if err != nil { + return nil, err + } + probationList := &vote.ProbationList{} + if probationListRes != nil { + if err := probationList.Deserialize(probationListRes.Data); err != nil { + return nil, err + } + } + return probationList, nil +} diff --git a/ioctl/newcmd/node/nodeprobationlist_test.go b/ioctl/newcmd/node/nodeprobationlist_test.go new file mode 100644 index 0000000000..ed5cb19200 --- /dev/null +++ b/ioctl/newcmd/node/nodeprobationlist_test.go @@ -0,0 +1,94 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package node + +import ( + "strconv" + "testing" + + "github.com/golang/mock/gomock" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/iotexproject/iotex-core/action/protocol/vote" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" +) + +func TestNewNodeProbationlistCmd(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + + client.EXPECT().SelectTranslation(gomock.Any()).Return("", config.English).Times(12) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(10) + + t.Run("failed to get chain meta", func(t *testing.T) { + expectedErr := errors.New("failed to get chain meta") + apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(nil, expectedErr).Times(1) + + cmd := NewNodeProbationlistCmd(client) + _, err := util.ExecuteCmd(cmd) + require.Contains(err.Error(), "failed to get chain meta") + }) + + var testBlockProducersInfo = []*iotexapi.BlockProducerInfo{ + {Address: "io1kr8c6krd7dhxaaqwdkr6erqgu4z0scug3drgja", Votes: "109510794521770016955545668", Active: true, Production: 30}, + {Address: "io13q2am9nedrd3n746lsj6qan4pymcpgm94vvx2c", Votes: "81497052527306018062463878", Active: false, Production: 0}, + } + + chainMetaResponse := &iotexapi.GetChainMetaResponse{ChainMeta: &iotextypes.ChainMeta{Epoch: &iotextypes.EpochData{Num: 7000}}} + epochMetaResponse := &iotexapi.GetEpochMetaResponse{EpochData: &iotextypes.EpochData{Num: 7000, Height: 3223081}, TotalBlocks: 720, BlockProducersInfo: testBlockProducersInfo} + + apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(chainMetaResponse, nil).Times(3) + apiServiceClient.EXPECT().GetEpochMeta(gomock.Any(), gomock.Any()).Return(epochMetaResponse, nil).Times(3) + + t.Run("query probation list", func(t *testing.T) { + probationList := &iotexapi.ReadStateResponse{} + + apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.Any()).Return(probationList, nil).Times(1) + + cmd := NewNodeProbationlistCmd(client) + result, err := util.ExecuteCmd(cmd) + require.NoError(err) + require.Contains(result, "ProbationList : []") + }) + + t.Run("epochNum > 0", func(t *testing.T) { + d, _ := vote.NewProbationList(1).Serialize() + probationList := &iotexapi.ReadStateResponse{ + Data: d, + } + + apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.Any()).Return(probationList, nil).Times(1) + + cmd := NewNodeProbationlistCmd(client) + result, err := util.ExecuteCmd(cmd, "-e", "1") + require.NoError(err) + require.Contains(result, "ProbationList : []") + }) + + t.Run("failed to get probation list", func(t *testing.T) { + apiServiceClient.EXPECT().ReadState(gomock.Any(), &iotexapi.ReadStateRequest{ + ProtocolID: []byte("poll"), + MethodName: []byte("ProbationListByEpoch"), + Arguments: [][]byte{[]byte("7000")}, + Height: strconv.FormatUint(3223081, 10), + }).Return(&iotexapi.ReadStateResponse{ + Data: []byte("0")}, + nil) + + cmd := NewNodeProbationlistCmd(client) + _, err := util.ExecuteCmd(cmd) + require.Contains(err.Error(), "failed to get probation list") + }) +}