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  }