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  }