github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/bc/bcbucket.go (about) 1 // Copyright (c) 2022 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package bc 7 8 import ( 9 "context" 10 "fmt" 11 "math/big" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 17 "github.com/iotexproject/iotex-proto/golang/iotexapi" 18 "github.com/iotexproject/iotex-proto/golang/iotextypes" 19 "github.com/pkg/errors" 20 "github.com/spf13/cobra" 21 "google.golang.org/grpc/codes" 22 "google.golang.org/grpc/status" 23 "google.golang.org/protobuf/proto" 24 25 "github.com/iotexproject/iotex-core/ioctl" 26 "github.com/iotexproject/iotex-core/ioctl/config" 27 "github.com/iotexproject/iotex-core/ioctl/util" 28 ) 29 30 const ( 31 _bcBucketOptMax = "max" 32 _bcBucketOptCount = "count" 33 ) 34 35 // Multi-language support 36 var ( 37 _bcBucketUses = map[config.Language]string{ 38 config.English: "bucket [OPTION|BUCKET_INDEX]", 39 config.Chinese: "bucket [选项|票索引]", 40 } 41 _bcBucketCmdShorts = map[config.Language]string{ 42 config.English: "Get bucket for given index on IoTeX blockchain", 43 config.Chinese: "在IoTeX区块链上根据索引读取投票", 44 } 45 _bcBucketCmdExample = map[config.Language]string{ 46 config.English: "ioctl bc bucket [BUCKET_INDEX], to read bucket information by bucket index\n" + 47 "ioctl bc bucket max, to query the max bucket index\n" + 48 "ioctl bc bucket count, to query total number of active buckets", 49 config.Chinese: "ioctl bc bucket [BUCKET_INDEX], 依票索引取得投票资讯\n" + 50 "ioctl bc bucket max, 查询最大票索引\n" + 51 "ioctl bc bucket count, 查询活跃总票数", 52 } 53 ) 54 55 type bucket struct { 56 Index uint64 `json:"index"` 57 Owner string `json:"owner"` 58 Candidate string `json:"candidate"` 59 StakedAmount string `json:"stakedAmount"` 60 StakedDuration uint32 `json:"stakedDuration"` 61 AutoStake bool `json:"autoStake"` 62 CreateTime string `json:"createTime"` 63 StakeStartTime string `json:"stakeStartTime"` 64 UnstakeStartTime string `json:"unstakeStartTime"` 65 } 66 67 // NewBCBucketCmd represents the bc Bucket command 68 func NewBCBucketCmd(client ioctl.Client) *cobra.Command { 69 bcBucketUses, _ := client.SelectTranslation(_bcBucketUses) 70 bcBucketCmdShorts, _ := client.SelectTranslation(_bcBucketCmdShorts) 71 bcBucketCmdExample, _ := client.SelectTranslation(_bcBucketCmdExample) 72 73 return &cobra.Command{ 74 Use: bcBucketUses, 75 Short: bcBucketCmdShorts, 76 Args: cobra.ExactArgs(1), 77 Example: bcBucketCmdExample, 78 RunE: func(cmd *cobra.Command, args []string) (err error) { 79 cmd.SilenceUsage = true 80 switch args[0] { 81 case _bcBucketOptMax: 82 count, err := getBucketsCount(client) 83 if err != nil { 84 return err 85 } 86 cmd.Println(count.GetTotal()) 87 case _bcBucketOptCount: 88 count, err := getBucketsCount(client) 89 if err != nil { 90 return err 91 } 92 cmd.Println(count.GetActive()) 93 default: 94 bucketindex, err := strconv.ParseUint(args[0], 10, 64) 95 if err != nil { 96 return err 97 } 98 bucketpb, err := getBucketByIndex(client, bucketindex) 99 if err != nil { 100 return err 101 } 102 if bucketpb == nil { 103 return errors.New("The bucket has been withdrawn") 104 } 105 bucket, err := newBucket(bucketpb) 106 if err != nil { 107 return err 108 } 109 cmd.Println(bucket.String()) 110 } 111 return nil 112 }, 113 } 114 } 115 116 func newBucket(bucketpb *iotextypes.VoteBucket) (*bucket, error) { 117 amount, ok := new(big.Int).SetString(bucketpb.StakedAmount, 10) 118 if !ok { 119 return nil, errors.New("failed to convert amount into big int") 120 } 121 unstakeStartTimeFormat := "none" 122 if err := bucketpb.UnstakeStartTime.CheckValid(); err != nil { 123 return nil, err 124 } 125 unstakeTime := bucketpb.UnstakeStartTime.AsTime() 126 if unstakeTime != time.Unix(0, 0).UTC() { 127 unstakeStartTimeFormat = unstakeTime.Format(time.RFC3339Nano) 128 } 129 return &bucket{ 130 Index: bucketpb.Index, 131 Owner: bucketpb.Owner, 132 Candidate: bucketpb.CandidateAddress, 133 StakedAmount: util.RauToString(amount, util.IotxDecimalNum), 134 StakedDuration: bucketpb.StakedDuration, 135 AutoStake: bucketpb.AutoStake, 136 CreateTime: bucketpb.CreateTime.AsTime().Format(time.RFC3339Nano), 137 StakeStartTime: bucketpb.StakeStartTime.AsTime().Format(time.RFC3339Nano), 138 UnstakeStartTime: unstakeStartTimeFormat, 139 }, nil 140 } 141 142 func (b *bucket) String() string { 143 var lines []string 144 lines = append(lines, "{") 145 lines = append(lines, fmt.Sprintf(" index: %d", b.Index)) 146 lines = append(lines, fmt.Sprintf(" owner: %s", b.Owner)) 147 lines = append(lines, fmt.Sprintf(" candidate: %s", b.Candidate)) 148 lines = append(lines, fmt.Sprintf(" stakedAmount: %s IOTX", b.StakedAmount)) 149 lines = append(lines, fmt.Sprintf(" stakedDuration: %d days", b.StakedDuration)) 150 lines = append(lines, fmt.Sprintf(" autoStake: %v", b.AutoStake)) 151 lines = append(lines, fmt.Sprintf(" createTime: %s", b.CreateTime)) 152 lines = append(lines, fmt.Sprintf(" stakeStartTime: %s", b.StakeStartTime)) 153 lines = append(lines, fmt.Sprintf(" unstakeStartTime: %s", b.UnstakeStartTime)) 154 lines = append(lines, "}") 155 return strings.Join(lines, "\n") 156 } 157 158 func getBucketByIndex(client ioctl.Client, index uint64) (*iotextypes.VoteBucket, error) { 159 method := &iotexapi.ReadStakingDataMethod{ 160 Method: iotexapi.ReadStakingDataMethod_BUCKETS_BY_INDEXES, 161 } 162 readStakingdataRequest := &iotexapi.ReadStakingDataRequest{ 163 Request: &iotexapi.ReadStakingDataRequest_BucketsByIndexes{ 164 BucketsByIndexes: &iotexapi.ReadStakingDataRequest_VoteBucketsByIndexes{ 165 Index: []uint64{index}, 166 }, 167 }, 168 } 169 response, err := getBuckets(client, method, readStakingdataRequest) 170 if err != nil { 171 return nil, err 172 } 173 buckets := iotextypes.VoteBucketList{} 174 if err := proto.Unmarshal(response.Data, &buckets); err != nil { 175 return nil, errors.Wrap(err, "failed to unmarshal response") 176 } 177 if len(buckets.GetBuckets()) == 0 { 178 return nil, errors.New("zero len response") 179 } 180 return buckets.GetBuckets()[0], nil 181 } 182 183 func getBucketsCount(client ioctl.Client) (count *iotextypes.BucketsCount, err error) { 184 method := &iotexapi.ReadStakingDataMethod{ 185 Method: iotexapi.ReadStakingDataMethod_BUCKETS_COUNT, 186 } 187 readStakingdataRequest := &iotexapi.ReadStakingDataRequest{ 188 Request: &iotexapi.ReadStakingDataRequest_BucketsCount_{ 189 BucketsCount: &iotexapi.ReadStakingDataRequest_BucketsCount{}, 190 }, 191 } 192 response, err := getBuckets(client, method, readStakingdataRequest) 193 if err != nil { 194 return nil, err 195 } 196 count = &iotextypes.BucketsCount{} 197 if err := proto.Unmarshal(response.Data, count); err != nil { 198 return nil, errors.Wrap(err, "failed to unmarshal response") 199 } 200 return count, nil 201 } 202 203 func getBuckets(client ioctl.Client, method *iotexapi.ReadStakingDataMethod, readStakingdataRequest *iotexapi.ReadStakingDataRequest) (response *iotexapi.ReadStateResponse, err error) { 204 apiClient, err := client.APIServiceClient() 205 if err != nil { 206 return nil, errors.Wrap(err, "failed to connect to endpoint") 207 } 208 methodData, err := proto.Marshal(method) 209 if err != nil { 210 return nil, errors.Wrap(err, "failed to marshal read staking data method") 211 } 212 requestData, err := proto.Marshal(readStakingdataRequest) 213 if err != nil { 214 return nil, errors.Wrap(err, "failed to marshal read staking data request") 215 } 216 217 request := &iotexapi.ReadStateRequest{ 218 ProtocolID: []byte("staking"), 219 MethodName: methodData, 220 Arguments: [][]byte{requestData}, 221 } 222 223 ctx := context.Background() 224 jwtMD, err := util.JwtAuth() 225 if err == nil { 226 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 227 } 228 229 response, err = apiClient.ReadState(ctx, request) 230 if err != nil { 231 if sta, ok := status.FromError(err); ok { 232 if sta.Code() == codes.Unavailable { 233 return nil, ioctl.ErrInvalidEndpointOrInsecure 234 } 235 return nil, errors.New(sta.Message()) 236 } 237 return nil, errors.Wrap(err, "failed to invoke ReadState api") 238 } 239 return response, nil 240 }