Skip to content

Commit

Permalink
[ioctl] Build block bucketlist command line into new ioctl (#3469)
Browse files Browse the repository at this point in the history
* [ioctl] build block bucketlist command line into new ioctl
  • Loading branch information
LuckyPigeon committed Jul 6, 2022
1 parent e956ca6 commit a20e489
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 0 deletions.
44 changes: 44 additions & 0 deletions ioctl/newcmd/bc/bc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"

"github.com/iotexproject/iotex-core/ioctl"
"github.com/iotexproject/iotex-core/ioctl/config"
Expand Down Expand Up @@ -129,3 +130,46 @@ func GetProbationList(client ioctl.Client, epochNum uint64, epochStartHeight uin
}
return response, nil
}

// GetBucketList get bucket list
func GetBucketList(
client ioctl.Client,
methodName iotexapi.ReadStakingDataMethod_Name,
readStakingDataRequest *iotexapi.ReadStakingDataRequest,
) (*iotextypes.VoteBucketList, error) {
apiServiceClient, err := client.APIServiceClient()
if err != nil {
return nil, err
}
methodData, err := proto.Marshal(&iotexapi.ReadStakingDataMethod{Method: methodName})
if err != nil {
return nil, errors.Wrap(err, "failed to marshal read staking data method")
}
requestData, err := proto.Marshal(readStakingDataRequest)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal read staking data request")
}
request := &iotexapi.ReadStateRequest{
ProtocolID: []byte("staking"),
MethodName: methodData,
Arguments: [][]byte{requestData},
}
ctx := context.Background()
if jwtMD, err := util.JwtAuth(); err == nil {
ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
}

response, err := apiServiceClient.ReadState(ctx, request)
if err != nil {
sta, ok := status.FromError(err)
if ok {
return nil, errors.New(sta.Message())
}
return nil, errors.Wrap(err, "failed to invoke ReadState api")
}
bucketlist := iotextypes.VoteBucketList{}
if err := proto.Unmarshal(response.Data, &bucketlist); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal response")
}
return &bucketlist, nil
}
49 changes: 49 additions & 0 deletions ioctl/newcmd/bc/bc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 bc

import (
"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/test/mock/mock_ioctlclient"
)

func TestGetBucketList(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
client := mock_ioctlclient.NewMockClient(ctrl)
apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl)
request := &iotexapi.ReadStakingDataRequest{}
response := &iotexapi.ReadStateResponse{}
client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2)

t.Run("get bucket list", func(t *testing.T) {
expectedValue := &iotextypes.VoteBucketList{
Buckets: []*iotextypes.VoteBucket{},
}
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.Any()).Return(response, nil)

result, err := GetBucketList(client, iotexapi.ReadStakingDataMethod_BUCKETS_BY_CANDIDATE, request)
require.NoError(err)
require.Contains(result.String(), expectedValue.String())
})

t.Run("failed to invoke ReadState api", func(t *testing.T) {
expectedErr := errors.New("failed to invoke ReadState api")
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.Any()).Return(nil, expectedErr)

_, err := GetBucketList(client, iotexapi.ReadStakingDataMethod_BUCKETS_BY_VOTER, request)
require.Contains(err.Error(), expectedErr.Error())
})
}
145 changes: 145 additions & 0 deletions ioctl/newcmd/bc/bcbucketlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// 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 bc

import (
"strconv"
"strings"

"github.com/iotexproject/iotex-proto/golang/iotexapi"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/iotexproject/iotex-core/ioctl"
"github.com/iotexproject/iotex-core/ioctl/config"
)

// constants
const (
MethodVoter = "voter"
MethodCandidate = "cand"
)

// Multi-language support
var (
_bcBucketListCmdShorts = map[config.Language]string{
config.English: "Get bucket list with method and arg(s) on IoTeX blockchain",
config.Chinese: "根据方法和参数在IoTeX区块链上读取投票列表",
}
_bcBucketListCmdUses = map[config.Language]string{
config.English: "bucketlist <method> [arguments]",
config.Chinese: "bucketlist <方法> [参数]",
}
_bcBucketListCmdLongs = map[config.Language]string{
config.English: "Read bucket list\nValid methods: [" +
MethodVoter + ", " + MethodCandidate + "]",
config.Chinese: "根据方法和参数在IoTeX区块链上读取投票列表\n可用方法有:" +
MethodVoter + "," + MethodCandidate,
}
)

// NewBCBucketListCmd represents the bc bucketlist command
func NewBCBucketListCmd(client ioctl.Client) *cobra.Command {
use, _ := client.SelectTranslation(_bcBucketListCmdUses)
short, _ := client.SelectTranslation(_bcBucketListCmdShorts)
long, _ := client.SelectTranslation(_bcBucketListCmdLongs)

return &cobra.Command{
Use: use,
Short: short,
Long: long,
Args: cobra.MinimumNArgs(2),
Example: `ioctl bc bucketlist voter [VOTER_ADDRESS] [OFFSET] [LIMIT]
ioctl bc bucketlist cand [CANDIDATE_NAME] [OFFSET] [LIMIT]`,
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true

var (
bl *iotextypes.VoteBucketList
address string
err error
)

offset, limit := uint64(0), uint64(1000)
method, addr := args[0], args[1]
s := args[2:]

if len(s) > 0 {
offset, err = strconv.ParseUint(s[0], 10, 64)
if err != nil {
return errors.Wrap(err, "invalid offset")
}
}
if len(s) > 1 {
limit, err = strconv.ParseUint(s[1], 10, 64)
if err != nil {
return errors.Wrap(err, "invalid limit")
}
}
switch method {
case MethodVoter:
address, err = client.AddressWithDefaultIfNotExist(addr)
if err != nil {
return err
}
bl, err = getBucketListByVoterAddress(client, address, uint32(offset), uint32(limit))
case MethodCandidate:
bl, err = getBucketListByCandidateName(client, addr, uint32(offset), uint32(limit))
default:
return errors.New("unknown <method>")
}
if err != nil {
return err
}
var lines []string
if len(bl.Buckets) == 0 {
lines = append(lines, "Empty bucketlist with given address")
} else {
for _, b := range bl.Buckets {
bucket, err := newBucket(b)
if err != nil {
return err
}
lines = append(lines, bucket.String())
}
}
cmd.Println(strings.Join(lines, "\n"))
return nil
},
}
}

func getBucketListByVoterAddress(client ioctl.Client, addr string, offset, limit uint32) (*iotextypes.VoteBucketList, error) {
readStakingdataRequest := &iotexapi.ReadStakingDataRequest{
Request: &iotexapi.ReadStakingDataRequest_BucketsByVoter{
BucketsByVoter: &iotexapi.ReadStakingDataRequest_VoteBucketsByVoter{
VoterAddress: addr,
Pagination: &iotexapi.PaginationParam{
Offset: offset,
Limit: limit,
},
},
},
}
return GetBucketList(client, iotexapi.ReadStakingDataMethod_BUCKETS_BY_VOTER, readStakingdataRequest)
}

func getBucketListByCandidateName(client ioctl.Client, candName string, offset, limit uint32) (*iotextypes.VoteBucketList, error) {
readStakingDataRequest := &iotexapi.ReadStakingDataRequest{
Request: &iotexapi.ReadStakingDataRequest_BucketsByCandidate{
BucketsByCandidate: &iotexapi.ReadStakingDataRequest_VoteBucketsByCandidate{
CandName: candName,
Pagination: &iotexapi.PaginationParam{
Offset: offset,
Limit: limit,
},
},
},
}
return GetBucketList(client, iotexapi.ReadStakingDataMethod_BUCKETS_BY_CANDIDATE, readStakingDataRequest)
}
104 changes: 104 additions & 0 deletions ioctl/newcmd/bc/bcbucketlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 bc

import (
"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"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/iotexproject/iotex-core/ioctl/config"
"github.com/iotexproject/iotex-core/ioctl/util"
"github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient"
"github.com/iotexproject/iotex-core/testutil"
)

func TestNewBCBucketListCmd(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(21)
client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2)
client.EXPECT().Config().Return(config.Config{}).Times(2)

t.Run("get bucket list by voter", func(t *testing.T) {
client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx", nil)
vblist, err := proto.Marshal(&iotextypes.VoteBucketList{
Buckets: []*iotextypes.VoteBucket{
{
Index: 1,
StakedAmount: "10",
UnstakeStartTime: timestamppb.New(testutil.TimestampNow()),
},
{
Index: 2,
StakedAmount: "20",
UnstakeStartTime: timestamppb.New(testutil.TimestampNow()),
},
},
})
require.NoError(err)
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.All()).Return(&iotexapi.ReadStateResponse{
Data: vblist,
}, nil)

cmd := NewBCBucketListCmd(client)
result, err := util.ExecuteCmd(cmd, "voter", "io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx")
require.NoError(err)
require.Contains(result,
"index: 1",
"stakedAmount: 0.00000000000000001 IOTX",
"index: 2",
"stakedAmount: 0.00000000000000002 IOTX")
})

t.Run("get bucket list by candidate", func(t *testing.T) {
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.All()).Return(&iotexapi.ReadStateResponse{}, nil)
cmd := NewBCBucketListCmd(client)
result, err := util.ExecuteCmd(cmd, "cand", "io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx")
require.NoError(err)
require.Equal("Empty bucketlist with given address\n", result)
})

t.Run("invalid voter address", func(t *testing.T) {
expectedErr := errors.New("cannot find address for alias test")
client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("", expectedErr)
cmd := NewBCBucketListCmd(client)
_, err := util.ExecuteCmd(cmd, "voter", "test")
require.Contains(err.Error(), expectedErr.Error())
})

t.Run("unknown method", func(t *testing.T) {
expectedErr := errors.New("unknown <method>")
cmd := NewBCBucketListCmd(client)
_, err := util.ExecuteCmd(cmd, "unknown", "io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx")
require.Equal(expectedErr.Error(), err.Error())
})

t.Run("invalid offset", func(t *testing.T) {
expectedErr := errors.New("invalid offset")
cmd := NewBCBucketListCmd(client)
_, err := util.ExecuteCmd(cmd, "voter", "io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx", "test")
require.Contains(err.Error(), expectedErr.Error())
})

t.Run("invalid limit", func(t *testing.T) {
expectedErr := errors.New("invalid limit")
cmd := NewBCBucketListCmd(client)
_, err := util.ExecuteCmd(cmd, "voter", "io1uwnr55vqmhf3xeg5phgurlyl702af6eju542sx", "0", "test")
require.Contains(err.Error(), expectedErr.Error())
})
}

0 comments on commit a20e489

Please sign in to comment.