Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ioctl] Build block bucket command line into new ioctl #3386

Merged
merged 31 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6b6c94a
[ioctl] build block bucket command line into new ioctl
LuckyPigeon May 12, 2022
4f08f36
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 12, 2022
db5c016
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 12, 2022
0188560
replace config.ReadConfig with client.Config()
LuckyPigeon May 12, 2022
70323e2
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 12, 2022
ff8e501
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 15, 2022
f59e8b5
fix commit
LuckyPigeon May 15, 2022
2586d63
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 17, 2022
96fd688
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 19, 2022
bd2c141
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 20, 2022
3f7d1f3
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 23, 2022
83d0002
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 25, 2022
d2edbf0
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 26, 2022
4dd7823
Merge branch 'master' into dev_bc_bucket
huof6829 May 27, 2022
2a3669e
Merge branch 'master' into dev_bc_bucket
LuckyPigeon May 27, 2022
296aa47
refactor unit test to cover the modification
LuckyPigeon May 27, 2022
85fe6d3
Merge branch 'master' into dev_bc_bucket
huof6829 May 31, 2022
b61fe2b
Merge branch 'master' into dev_bc_bucket
LuckyPigeon Jun 1, 2022
0191b26
add chinese translation for example
LuckyPigeon Jun 1, 2022
14d394f
combine similar code for another function
LuckyPigeon Jun 1, 2022
26c4aa3
Merge branch 'master' into dev_bc_bucket
huof6829 Jun 2, 2022
c1f2b12
Merge branch 'master' into dev_bc_bucket
LuckyPigeon Jun 8, 2022
a76c0fc
Merge branch 'master' into dev_bc_bucket
huof6829 Jun 10, 2022
63aad50
delete useless message struct
huof6829 Jun 10, 2022
02b72ff
point argument for method and readStakingdataRequest
LuckyPigeon Jun 10, 2022
81cabda
point argument for method and readStakingdataRequest
LuckyPigeon Jun 10, 2022
c0fcf55
Merge branch 'master' into dev_bc_bucket
huof6829 Jun 17, 2022
6cd525e
Merge branch 'master' into dev_bc_bucket
LuckyPigeon Jun 18, 2022
098c3e7
fix client.APIServiceClient()
LuckyPigeon Jun 18, 2022
9a9d50b
Merge branch 'master' into dev_bc_bucket
huof6829 Jun 20, 2022
5ff4c65
add require.NoError
huof6829 Jun 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions ioctl/newcmd/bc/bcbucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// 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) {
var endpoint string
var insecure bool

apiClient, err := client.APIServiceClient(ioctl.APIServiceConfig{
Endpoint: endpoint,
Insecure: insecure,
})
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(gomock.Any()).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)
LuckyPigeon marked this conversation as resolved.
Show resolved Hide resolved
require.Equal("0\n", result)
})

t.Run("get active blockchain bucket count", func(t *testing.T) {
client.EXPECT().APIServiceClient(gomock.Any()).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)
LuckyPigeon marked this conversation as resolved.
Show resolved Hide resolved
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, _ := proto.Marshal(&iotextypes.VoteBucketList{
Buckets: []*iotextypes.VoteBucket{vb},
})
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")
})
}