Skip to content

Commit

Permalink
[ioctl] Build block bucket command line into new ioctl (#3386)
Browse files Browse the repository at this point in the history
* [ioctl] build block bucket command line into new ioctl
Co-authored-by: huofei <[email protected]>
  • Loading branch information
LuckyPigeon authored Jun 20, 2022
1 parent 61d189f commit 9c24171
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 0 deletions.
238 changes: 238 additions & 0 deletions ioctl/newcmd/bc/bcbucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// 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 (
"context"
"fmt"
"math/big"
"strconv"
"strings"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/iotexproject/iotex-proto/golang/iotexapi"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"

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

const (
_bcBucketOptMax = "max"
_bcBucketOptCount = "count"
)

// Multi-language support
var (
_bcBucketUses = map[config.Language]string{
config.English: "bucket [OPTION|BUCKET_INDEX]",
config.Chinese: "bucket [选项|票索引]",
}
_bcBucketCmdShorts = map[config.Language]string{
config.English: "Get bucket for given index on IoTeX blockchain",
config.Chinese: "在IoTeX区块链上根据索引读取投票",
}
_bcBucketCmdExample = map[config.Language]string{
config.English: "ioctl bc bucket [BUCKET_INDEX], to read bucket information by bucket index\n" +
"ioctl bc bucket max, to query the max bucket index\n" +
"ioctl bc bucket count, to query total number of active buckets",
config.Chinese: "ioctl bc bucket [BUCKET_INDEX], 依票索引取得投票资讯\n" +
"ioctl bc bucket max, 查询最大票索引\n" +
"ioctl bc bucket count, 查询活跃总票数",
}
)

type bucket struct {
Index uint64 `json:"index"`
Owner string `json:"owner"`
Candidate string `json:"candidate"`
StakedAmount string `json:"stakedAmount"`
StakedDuration uint32 `json:"stakedDuration"`
AutoStake bool `json:"autoStake"`
CreateTime string `json:"createTime"`
StakeStartTime string `json:"stakeStartTime"`
UnstakeStartTime string `json:"unstakeStartTime"`
}

// NewBCBucketCmd represents the bc Bucket command
func NewBCBucketCmd(client ioctl.Client) *cobra.Command {
bcBucketUses, _ := client.SelectTranslation(_bcBlockCmdUses)
bcBucketCmdShorts, _ := client.SelectTranslation(_bcBlockCmdShorts)
bcBucketCmdExample, _ := client.SelectTranslation(_bcBucketCmdExample)

return &cobra.Command{
Use: bcBucketUses,
Short: bcBucketCmdShorts,
Args: cobra.ExactArgs(1),
Example: bcBucketCmdExample,
RunE: func(cmd *cobra.Command, args []string) (err error) {
cmd.SilenceUsage = true
switch args[0] {
case _bcBucketOptMax:
count, err := getBucketsCount(client)
if err != nil {
return err
}
cmd.Println(count.GetTotal())
case _bcBucketOptCount:
count, err := getBucketsCount(client)
if err != nil {
return err
}
cmd.Println(count.GetActive())
default:
bucketindex, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}
bucketpb, err := getBucketByIndex(client, bucketindex)
if err != nil {
return err
}
if bucketpb == nil {
return errors.New("The bucket has been withdrawn")
}
bucket, err := newBucket(bucketpb)
if err != nil {
return err
}
cmd.Println(bucket.String())
}
return nil
},
}
}

func newBucket(bucketpb *iotextypes.VoteBucket) (*bucket, error) {
amount, ok := new(big.Int).SetString(bucketpb.StakedAmount, 10)
if !ok {
return nil, errors.New("failed to convert amount into big int")
}
unstakeStartTimeFormat := "none"
if err := bucketpb.UnstakeStartTime.CheckValid(); err != nil {
return nil, err
}
unstakeTime := bucketpb.UnstakeStartTime.AsTime()
if unstakeTime != time.Unix(0, 0).UTC() {
unstakeStartTimeFormat = unstakeTime.Format(time.RFC3339Nano)
}
return &bucket{
Index: bucketpb.Index,
Owner: bucketpb.Owner,
Candidate: bucketpb.CandidateAddress,
StakedAmount: util.RauToString(amount, util.IotxDecimalNum),
StakedDuration: bucketpb.StakedDuration,
AutoStake: bucketpb.AutoStake,
CreateTime: bucketpb.CreateTime.AsTime().Format(time.RFC3339Nano),
StakeStartTime: bucketpb.StakeStartTime.AsTime().Format(time.RFC3339Nano),
UnstakeStartTime: unstakeStartTimeFormat,
}, nil
}

func (b *bucket) String() string {
var lines []string
lines = append(lines, "{")
lines = append(lines, fmt.Sprintf(" index: %d", b.Index))
lines = append(lines, fmt.Sprintf(" owner: %s", b.Owner))
lines = append(lines, fmt.Sprintf(" candidate: %s", b.Candidate))
lines = append(lines, fmt.Sprintf(" stakedAmount: %s IOTX", b.StakedAmount))
lines = append(lines, fmt.Sprintf(" stakedDuration: %d days", b.StakedDuration))
lines = append(lines, fmt.Sprintf(" autoStake: %v", b.AutoStake))
lines = append(lines, fmt.Sprintf(" createTime: %s", b.CreateTime))
lines = append(lines, fmt.Sprintf(" stakeStartTime: %s", b.StakeStartTime))
lines = append(lines, fmt.Sprintf(" unstakeStartTime: %s", b.UnstakeStartTime))
lines = append(lines, "}")
return strings.Join(lines, "\n")
}

func getBucketByIndex(client ioctl.Client, index uint64) (*iotextypes.VoteBucket, error) {
method := &iotexapi.ReadStakingDataMethod{
Method: iotexapi.ReadStakingDataMethod_BUCKETS_BY_INDEXES,
}
readStakingdataRequest := &iotexapi.ReadStakingDataRequest{
Request: &iotexapi.ReadStakingDataRequest_BucketsByIndexes{
BucketsByIndexes: &iotexapi.ReadStakingDataRequest_VoteBucketsByIndexes{
Index: []uint64{index},
},
},
}
response, err := getBuckets(client, method, readStakingdataRequest)
if err != nil {
return nil, err
}
buckets := iotextypes.VoteBucketList{}
if err := proto.Unmarshal(response.Data, &buckets); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal response")
}
if len(buckets.GetBuckets()) == 0 {
return nil, errors.New("zero len response")
}
return buckets.GetBuckets()[0], nil
}

func getBucketsCount(client ioctl.Client) (count *iotextypes.BucketsCount, err error) {
method := &iotexapi.ReadStakingDataMethod{
Method: iotexapi.ReadStakingDataMethod_BUCKETS_COUNT,
}
readStakingdataRequest := &iotexapi.ReadStakingDataRequest{
Request: &iotexapi.ReadStakingDataRequest_BucketsCount_{
BucketsCount: &iotexapi.ReadStakingDataRequest_BucketsCount{},
},
}
response, err := getBuckets(client, method, readStakingdataRequest)
if err != nil {
return nil, err
}
count = &iotextypes.BucketsCount{}
if err := proto.Unmarshal(response.Data, count); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal response")
}
return count, nil
}

func getBuckets(client ioctl.Client, method *iotexapi.ReadStakingDataMethod, readStakingdataRequest *iotexapi.ReadStakingDataRequest) (response *iotexapi.ReadStateResponse, err error) {
apiClient, err := client.APIServiceClient()
if err != nil {
return nil, errors.Wrap(err, "failed to connect to endpoint")
}
methodData, err := proto.Marshal(method)
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()
jwtMD, err := util.JwtAuth()
if err == nil {
ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
}

response, err = apiClient.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")
}
return response, nil
}
74 changes: 74 additions & 0 deletions ioctl/newcmd/bc/bcbucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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/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 TestBCBucketCmd(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(9)

t.Run("get total blockchain bucket count", func(t *testing.T) {
client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2)
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.All()).Return(&iotexapi.ReadStateResponse{}, nil)

cmd := NewBCBucketCmd(client)
result, err := util.ExecuteCmd(cmd, "max")
require.NoError(err)
require.Equal("0\n", result)
})

t.Run("get active blockchain bucket count", func(t *testing.T) {
client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2)
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.All()).Return(&iotexapi.ReadStateResponse{}, nil)

cmd := NewBCBucketCmd(client)
result, err := util.ExecuteCmd(cmd, "count")
require.NoError(err)
require.Equal("0\n", result)
})

t.Run("get default blockchain bucket count", func(t *testing.T) {
cfg := config.Config{}
vb := &iotextypes.VoteBucket{
Index: 1,
StakedAmount: "10",
UnstakeStartTime: timestamppb.New(testutil.TimestampNow()),
}
vblist, err := proto.Marshal(&iotextypes.VoteBucketList{
Buckets: []*iotextypes.VoteBucket{vb},
})
require.NoError(err)
client.EXPECT().Config().Return(cfg).Times(1)
apiServiceClient.EXPECT().ReadState(gomock.Any(), gomock.All()).Return(&iotexapi.ReadStateResponse{
Data: vblist,
}, nil)

cmd := NewBCBucketCmd(client)
result, err := util.ExecuteCmd(cmd, "0")
require.NoError(err)
require.Contains(result, "index: 1")
})
}

0 comments on commit 9c24171

Please sign in to comment.