github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/bc/bcdelegate.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  	"strings"
    13  
    14  	"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
    15  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    16  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    17  	"github.com/pkg/errors"
    18  	"github.com/spf13/cobra"
    19  	"golang.org/x/exp/slices"
    20  	"google.golang.org/grpc/status"
    21  	"google.golang.org/protobuf/proto"
    22  
    23  	"github.com/iotexproject/iotex-core/ioctl/config"
    24  	"github.com/iotexproject/iotex-core/ioctl/output"
    25  	"github.com/iotexproject/iotex-core/ioctl/util"
    26  )
    27  
    28  // Multi-language support
    29  var (
    30  	_bcDelegateCmdShorts = map[config.Language]string{
    31  		config.English: "Get delegate information on IoTeX blockchain",
    32  		config.Chinese: "在IoTeX区块链上读取代表信息",
    33  	}
    34  	_bcDelegateUses = map[config.Language]string{
    35  		config.English: "delegate [name|address]",
    36  		config.Chinese: "delegate [名字|地址]",
    37  	}
    38  )
    39  
    40  // _bcBucketCmd represents the bc Bucket command
    41  var _bcDelegateCmd = &cobra.Command{
    42  	Use:     config.TranslateInLang(_bcDelegateUses, config.UILanguage),
    43  	Short:   config.TranslateInLang(_bcDelegateCmdShorts, config.UILanguage),
    44  	Args:    cobra.ExactArgs(1),
    45  	Example: `ioctl bc delegate name, to read delegate information by name`,
    46  	RunE: func(cmd *cobra.Command, args []string) (err error) {
    47  		cmd.SilenceUsage = true
    48  		err = getDelegate(args[0])
    49  		return output.PrintError(err)
    50  	},
    51  }
    52  
    53  type delegateMessage struct {
    54  	Delegate *iotextypes.CandidateV2 `json:"delegate"`
    55  }
    56  
    57  func (m *delegateMessage) delegateString() string {
    58  	var (
    59  		d        = m.Delegate
    60  		vote, _  = new(big.Int).SetString(d.TotalWeightedVotes, 10)
    61  		token, _ = new(big.Int).SetString(d.SelfStakingTokens, 10)
    62  		lines    []string
    63  	)
    64  	lines = append(lines, "{")
    65  	lines = append(lines, fmt.Sprintf("	name: %s", d.Name))
    66  	lines = append(lines, fmt.Sprintf("	ownerAddress: %s", d.OwnerAddress))
    67  	lines = append(lines, fmt.Sprintf("	operatorAddress: %s", d.OperatorAddress))
    68  	lines = append(lines, fmt.Sprintf("	rewardAddress: %s", d.RewardAddress))
    69  	lines = append(lines, fmt.Sprintf("	totalWeightedVotes: %s", util.RauToString(vote, util.IotxDecimalNum)))
    70  	lines = append(lines, fmt.Sprintf("	selfStakeBucketIdx: %d", d.SelfStakeBucketIdx))
    71  	lines = append(lines, fmt.Sprintf("	selfStakingTokens: %s IOTX", util.RauToString(token, util.IotxDecimalNum)))
    72  	lines = append(lines, "}")
    73  	return strings.Join(lines, "\n")
    74  }
    75  
    76  func (m *delegateMessage) String() string {
    77  	if output.Format == "" {
    78  		return m.delegateString()
    79  	}
    80  	return output.FormatString(output.Result, m)
    81  }
    82  
    83  func getDelegate(arg string) error {
    84  	var d *iotextypes.CandidateV2
    85  	addr, err := util.Address(arg)
    86  	if err == nil {
    87  		d, err = getDelegateByAddress(addr)
    88  	} else {
    89  		d, err = getDelegateByName(arg)
    90  	}
    91  	if err != nil {
    92  		return err
    93  	}
    94  	message := delegateMessage{
    95  		Delegate: d,
    96  	}
    97  	fmt.Println(message.String())
    98  	return nil
    99  }
   100  
   101  func getDelegateByName(name string) (*iotextypes.CandidateV2, error) {
   102  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
   103  	if err != nil {
   104  		return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err)
   105  	}
   106  	defer conn.Close()
   107  	cli := iotexapi.NewAPIServiceClient(conn)
   108  	method := &iotexapi.ReadStakingDataMethod{
   109  		Method: iotexapi.ReadStakingDataMethod_CANDIDATE_BY_NAME,
   110  	}
   111  	methodData, err := proto.Marshal(method)
   112  	if err != nil {
   113  		return nil, output.NewError(output.SerializationError, "failed to marshal read staking data method", err)
   114  	}
   115  	readStakingdataRequest := &iotexapi.ReadStakingDataRequest{
   116  		Request: &iotexapi.ReadStakingDataRequest_CandidateByName_{
   117  			CandidateByName: &iotexapi.ReadStakingDataRequest_CandidateByName{
   118  				CandName: name,
   119  			},
   120  		},
   121  	}
   122  	requestData, err := proto.Marshal(readStakingdataRequest)
   123  	if err != nil {
   124  		return nil, output.NewError(output.SerializationError, "failed to marshal read staking data request", err)
   125  	}
   126  
   127  	request := &iotexapi.ReadStateRequest{
   128  		ProtocolID: []byte("staking"),
   129  		MethodName: methodData,
   130  		Arguments:  [][]byte{requestData},
   131  	}
   132  
   133  	ctx := context.Background()
   134  	jwtMD, err := util.JwtAuth()
   135  	if err == nil {
   136  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   137  	}
   138  
   139  	response, err := cli.ReadState(ctx, request)
   140  	if err != nil {
   141  		sta, ok := status.FromError(err)
   142  		if ok {
   143  			return nil, output.NewError(output.APIError, sta.Message(), nil)
   144  		}
   145  		return nil, output.NewError(output.NetworkError, "failed to invoke ReadState api", err)
   146  	}
   147  	delegate := iotextypes.CandidateV2{}
   148  	if err := proto.Unmarshal(response.Data, &delegate); err != nil {
   149  		return nil, output.NewError(output.SerializationError, "failed to unmarshal response", err)
   150  	}
   151  	return &delegate, nil
   152  }
   153  
   154  func getDelegateByAddress(addr string) (*iotextypes.CandidateV2, error) {
   155  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
   156  	if err != nil {
   157  		return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err)
   158  	}
   159  	defer conn.Close()
   160  	cli := iotexapi.NewAPIServiceClient(conn)
   161  
   162  	readCandidatesLimit := 200
   163  	for i := uint32(0); ; i++ {
   164  		offset := i * uint32(readCandidatesLimit)
   165  		size := uint32(readCandidatesLimit)
   166  		candidateList, err := util.GetStakingCandidates(cli, offset, size)
   167  		if err != nil {
   168  			return nil, errors.Wrap(err, "failed to get candidates")
   169  		}
   170  		if idx := slices.IndexFunc(candidateList.Candidates, func(cand *iotextypes.CandidateV2) bool {
   171  			return cand.OperatorAddress == addr
   172  		}); idx >= 0 {
   173  			return candidateList.Candidates[idx], nil
   174  		}
   175  		if len(candidateList.Candidates) < readCandidatesLimit {
   176  			break
   177  		}
   178  	}
   179  	return nil, output.NewError(output.UndefinedError, "failed to find delegate", nil)
   180  }