github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/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/pkg/errors" 18 "github.com/spf13/cobra" 19 "google.golang.org/grpc/status" 20 "google.golang.org/protobuf/proto" 21 22 "github.com/iotexproject/iotex-core/ioctl/config" 23 "github.com/iotexproject/iotex-core/ioctl/output" 24 "github.com/iotexproject/iotex-core/ioctl/util" 25 "github.com/iotexproject/iotex-proto/golang/iotexapi" 26 "github.com/iotexproject/iotex-proto/golang/iotextypes" 27 ) 28 29 const ( 30 _bcBucketOptMax = "max" 31 _bcBucketOptCount = "count" 32 ) 33 34 // Multi-language support 35 var ( 36 _bcBucketCmdShorts = map[config.Language]string{ 37 config.English: "Get bucket for given index on IoTeX blockchain", 38 config.Chinese: "在IoTeX区块链上根据索引读取投票", 39 } 40 _bcBucketUses = map[config.Language]string{ 41 config.English: "bucket [OPTION|BUCKET_INDEX]", 42 config.Chinese: "bucket [选项|票索引]", 43 } 44 ) 45 46 // _bcBucketCmd represents the bc Bucket command 47 var _bcBucketCmd = &cobra.Command{ 48 Use: config.TranslateInLang(_bcBucketUses, config.UILanguage), 49 Short: config.TranslateInLang(_bcBucketCmdShorts, config.UILanguage), 50 Args: cobra.ExactArgs(1), 51 Example: `ioctl bc bucket [BUCKET_INDEX], to read bucket information by bucket index 52 ioctl bc bucket max, to query the max bucket index 53 ioctl bc bucket count, to query total number of active buckets 54 `, 55 RunE: func(cmd *cobra.Command, args []string) (err error) { 56 cmd.SilenceUsage = true 57 switch args[0] { 58 case _bcBucketOptMax: 59 err = getBucketsTotalCount() 60 case _bcBucketOptCount: 61 err = getBucketsActiveCount() 62 default: 63 err = getBucket(args[0]) 64 } 65 return output.PrintError(err) 66 }, 67 } 68 69 type bucket struct { 70 Index uint64 `json:"index"` 71 Owner string `json:"owner"` 72 Candidate string `json:"candidate"` 73 StakedAmount string `json:"stakedAmount"` 74 StakedDuration uint32 `json:"stakedDuration"` 75 AutoStake bool `json:"autoStake"` 76 CreateTime string `json:"createTime"` 77 StakeStartTime string `json:"stakeStartTime"` 78 UnstakeStartTime string `json:"unstakeStartTime"` 79 } 80 81 func newBucket(bucketpb *iotextypes.VoteBucket) (*bucket, error) { 82 amount, ok := new(big.Int).SetString(bucketpb.StakedAmount, 10) 83 if !ok { 84 return nil, output.NewError(output.ConvertError, "failed to convert amount into big int", nil) 85 } 86 unstakeStartTimeFormat := "none" 87 if err := bucketpb.UnstakeStartTime.CheckValid(); err != nil { 88 return nil, err 89 } 90 unstakeTime := bucketpb.UnstakeStartTime.AsTime() 91 if unstakeTime != time.Unix(0, 0).UTC() { 92 unstakeStartTimeFormat = unstakeTime.Format(time.RFC3339Nano) 93 } 94 return &bucket{ 95 Index: bucketpb.Index, 96 Owner: bucketpb.Owner, 97 Candidate: bucketpb.CandidateAddress, 98 StakedAmount: util.RauToString(amount, util.IotxDecimalNum), 99 StakedDuration: bucketpb.StakedDuration, 100 AutoStake: bucketpb.AutoStake, 101 CreateTime: bucketpb.CreateTime.AsTime().Format(time.RFC3339Nano), 102 StakeStartTime: bucketpb.StakeStartTime.AsTime().Format(time.RFC3339Nano), 103 UnstakeStartTime: unstakeStartTimeFormat, 104 }, nil 105 } 106 107 func (b *bucket) String() string { 108 var lines []string 109 lines = append(lines, "{") 110 lines = append(lines, fmt.Sprintf(" index: %d", b.Index)) 111 lines = append(lines, fmt.Sprintf(" owner: %s", b.Owner)) 112 lines = append(lines, fmt.Sprintf(" candidate: %s", b.Candidate)) 113 lines = append(lines, fmt.Sprintf(" stakedAmount: %s IOTX", b.StakedAmount)) 114 lines = append(lines, fmt.Sprintf(" stakedDuration: %d days", b.StakedDuration)) 115 lines = append(lines, fmt.Sprintf(" autoStake: %v", b.AutoStake)) 116 lines = append(lines, fmt.Sprintf(" createTime: %s", b.CreateTime)) 117 lines = append(lines, fmt.Sprintf(" stakeStartTime: %s", b.StakeStartTime)) 118 lines = append(lines, fmt.Sprintf(" unstakeStartTime: %s", b.UnstakeStartTime)) 119 lines = append(lines, "}") 120 return strings.Join(lines, "\n") 121 } 122 123 type bucketMessage struct { 124 Node string `json:"node"` 125 Bucket *bucket `json:"bucket"` 126 } 127 128 func (m *bucketMessage) String() string { 129 if output.Format == "" { 130 return m.Bucket.String() 131 } 132 return output.FormatString(output.Result, m) 133 } 134 135 // getBucket get bucket from chain 136 func getBucket(arg string) error { 137 bucketindex, err := strconv.ParseUint(arg, 10, 64) 138 if err != nil { 139 return err 140 } 141 bucketpb, err := getBucketByIndex(bucketindex) 142 if err != nil { 143 return err 144 } 145 if bucketpb == nil { 146 return errors.New("The bucket has been withdrawn") 147 } 148 bucket, err := newBucket(bucketpb) 149 if err != nil { 150 return err 151 } 152 message := bucketMessage{ 153 Node: config.ReadConfig.Endpoint, 154 Bucket: bucket, 155 } 156 fmt.Println(message.String()) 157 return nil 158 } 159 160 func getBucketByIndex(index uint64) (*iotextypes.VoteBucket, error) { 161 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 162 if err != nil { 163 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 164 } 165 defer conn.Close() 166 cli := iotexapi.NewAPIServiceClient(conn) 167 method := &iotexapi.ReadStakingDataMethod{ 168 Method: iotexapi.ReadStakingDataMethod_BUCKETS_BY_INDEXES, 169 } 170 methodData, err := proto.Marshal(method) 171 if err != nil { 172 return nil, output.NewError(output.SerializationError, "failed to marshal read staking data method", err) 173 } 174 readStakingdataRequest := &iotexapi.ReadStakingDataRequest{ 175 Request: &iotexapi.ReadStakingDataRequest_BucketsByIndexes{ 176 BucketsByIndexes: &iotexapi.ReadStakingDataRequest_VoteBucketsByIndexes{ 177 Index: []uint64{index}, 178 }, 179 }, 180 } 181 requestData, err := proto.Marshal(readStakingdataRequest) 182 if err != nil { 183 return nil, output.NewError(output.SerializationError, "failed to marshal read staking data request", err) 184 } 185 186 request := &iotexapi.ReadStateRequest{ 187 ProtocolID: []byte("staking"), 188 MethodName: methodData, 189 Arguments: [][]byte{requestData}, 190 } 191 192 ctx := context.Background() 193 jwtMD, err := util.JwtAuth() 194 if err == nil { 195 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 196 } 197 198 response, err := cli.ReadState(ctx, request) 199 if err != nil { 200 sta, ok := status.FromError(err) 201 if ok { 202 return nil, output.NewError(output.APIError, sta.Message(), nil) 203 } 204 return nil, output.NewError(output.NetworkError, "failed to invoke ReadState api", err) 205 } 206 buckets := iotextypes.VoteBucketList{} 207 if err := proto.Unmarshal(response.Data, &buckets); err != nil { 208 return nil, output.NewError(output.SerializationError, "failed to unmarshal response", err) 209 } 210 if len(buckets.GetBuckets()) == 0 { 211 return nil, output.NewError(output.SerializationError, "", errors.New("zero len response")) 212 } 213 return buckets.GetBuckets()[0], nil 214 } 215 216 func getBucketsTotalCount() error { 217 count, err := getBucketsCount() 218 if err != nil { 219 return err 220 } 221 fmt.Println(count.GetTotal()) 222 return nil 223 } 224 225 func getBucketsActiveCount() error { 226 count, err := getBucketsCount() 227 if err != nil { 228 return err 229 } 230 fmt.Println(count.GetActive()) 231 return nil 232 } 233 234 func getBucketsCount() (count *iotextypes.BucketsCount, err error) { 235 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 236 if err != nil { 237 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 238 } 239 defer conn.Close() 240 cli := iotexapi.NewAPIServiceClient(conn) 241 method := &iotexapi.ReadStakingDataMethod{ 242 Method: iotexapi.ReadStakingDataMethod_BUCKETS_COUNT, 243 } 244 methodData, err := proto.Marshal(method) 245 if err != nil { 246 return nil, output.NewError(output.SerializationError, "failed to marshal read staking data method", err) 247 } 248 readStakingdataRequest := &iotexapi.ReadStakingDataRequest{ 249 Request: &iotexapi.ReadStakingDataRequest_BucketsCount_{ 250 BucketsCount: &iotexapi.ReadStakingDataRequest_BucketsCount{}, 251 }, 252 } 253 requestData, err := proto.Marshal(readStakingdataRequest) 254 if err != nil { 255 return nil, output.NewError(output.SerializationError, "failed to marshal read staking data request", err) 256 } 257 258 request := &iotexapi.ReadStateRequest{ 259 ProtocolID: []byte("staking"), 260 MethodName: methodData, 261 Arguments: [][]byte{requestData}, 262 } 263 264 ctx := context.Background() 265 jwtMD, err := util.JwtAuth() 266 if err == nil { 267 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 268 } 269 270 response, err := cli.ReadState(ctx, request) 271 if err != nil { 272 sta, ok := status.FromError(err) 273 if ok { 274 return nil, output.NewError(output.APIError, sta.Message(), nil) 275 } 276 return nil, output.NewError(output.NetworkError, "failed to invoke ReadState api", err) 277 } 278 count = &iotextypes.BucketsCount{} 279 if err := proto.Unmarshal(response.Data, count); err != nil { 280 return nil, output.NewError(output.SerializationError, "failed to unmarshal response", err) 281 } 282 return count, nil 283 }