github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/node/nodereward.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 node 7 8 import ( 9 "context" 10 "fmt" 11 "math/big" 12 13 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 14 "github.com/spf13/cobra" 15 "google.golang.org/grpc/status" 16 17 "github.com/iotexproject/iotex-proto/golang/iotexapi" 18 19 "github.com/iotexproject/iotex-core/ioctl/config" 20 "github.com/iotexproject/iotex-core/ioctl/output" 21 "github.com/iotexproject/iotex-core/ioctl/util" 22 ) 23 24 // Multi-language support 25 var ( 26 _rewardCmdUses = map[config.Language]string{ 27 config.English: "reward unclaimed|pool [ALIAS|REWARD_ADDRESS|NAME]", 28 config.Chinese: "reward 未支取|奖金池 [别名|奖励地址|名称]", 29 } 30 _rewardCmdShorts = map[config.Language]string{ 31 config.English: "Query rewards", 32 config.Chinese: "查询奖励", 33 } 34 _rewardPoolLong = map[config.Language]string{ 35 config.English: "ioctl node reward pool returns unclaimed and available Rewards in fund pool.\nTotalUnclaimed is the amount of all delegates that have been issued but are not claimed;\nTotalAvailable is the amount of balance that has not been issued to anyone.\n\nioctl node reward unclaimed [ALIAS|DELEGATE_ADDRESS] returns unclaimed rewards of a specific delegate.", 36 config.Chinese: "ioctl node reward 返回奖金池中的未支取奖励和可获取的奖励. TotalUnclaimed是所有代表已被发放但未支取的奖励的总和; TotalAvailable 是奖金池中未被发放的奖励的总和.\n\nioctl node [ALIAS|DELEGATE_ADDRESS] 返回特定代表的已被发放但未支取的奖励.", 37 } 38 ) 39 40 // _nodeRewardCmd represents the node reward command 41 var _nodeRewardCmd = &cobra.Command{ 42 Use: config.TranslateInLang(_rewardCmdUses, config.UILanguage), 43 Short: config.TranslateInLang(_rewardCmdShorts, config.UILanguage), 44 Args: cobra.RangeArgs(1, 2), 45 Long: config.TranslateInLang(_rewardPoolLong, config.UILanguage), 46 RunE: func(cmd *cobra.Command, args []string) error { 47 cmd.SilenceUsage = true 48 var err error 49 switch args[0] { 50 case "pool": 51 if len(args) != 1 { 52 return output.NewError(output.InputError, "wrong number of arg(s) for ioctl node reward pool command. \nRun 'ioctl node reward --help' for usage.", nil) 53 } 54 err = rewardPool() 55 case "unclaimed": 56 if len(args) != 2 { 57 return output.NewError(output.InputError, "wrong number of arg(s) for ioctl node reward unclaimed [ALIAS|DELEGATE_ADDRESS] command. \nRun 'ioctl node reward --help' for usage.", nil) 58 } 59 err = reward(args[1]) 60 default: 61 return output.NewError(output.InputError, "unknown command. \nRun 'ioctl node reward --help' for usage.", nil) 62 } 63 return output.PrintError(err) 64 }, 65 } 66 67 // TotalBalance == Total rewards in the pool 68 // TotalAvailable == Rewards in the pool that has not been issued to anyone 69 // TotalUnclaimed == Rewards in the pool that has been issued to a delegate but are not claimed yet 70 type rewardPoolMessage struct { 71 TotalBalance string `json:"TotalBalance"` 72 TotalUnclaimed string `json:"TotalUnclaimed"` 73 TotalAvailable string `json:"TotalAvailable"` 74 } 75 76 func (m *rewardPoolMessage) String() string { 77 if output.Format == "" { 78 message := fmt.Sprintf("Total Unclaimed:\t %s IOTX\nTotal Available:\t %s IOTX\nTotal Balance:\t\t %s IOTX", 79 m.TotalUnclaimed, m.TotalAvailable, m.TotalBalance) 80 return message 81 } 82 return output.FormatString(output.Result, m) 83 } 84 85 type rewardMessage struct { 86 Address string `json:"address"` 87 Reward string `json:"reward"` 88 } 89 90 func (m *rewardMessage) String() string { 91 if output.Format == "" { 92 message := fmt.Sprintf("%s: %s IOTX", m.Address, m.Reward) 93 return message 94 } 95 return output.FormatString(output.Result, m) 96 } 97 98 func rewardPool() error { 99 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 100 if err != nil { 101 return output.NewError(output.NetworkError, "failed to connect to endpoint", err) 102 } 103 defer conn.Close() 104 cli := iotexapi.NewAPIServiceClient(conn) 105 ctx := context.Background() 106 107 jwtMD, err := util.JwtAuth() 108 if err == nil { 109 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 110 } 111 // AvailableBalance == Rewards in the pool that has not been issued to anyone 112 request := &iotexapi.ReadStateRequest{ 113 ProtocolID: []byte("rewarding"), 114 MethodName: []byte("AvailableBalance"), 115 } 116 response, err := cli.ReadState(ctx, request) 117 if err != nil { 118 sta, ok := status.FromError(err) 119 if ok { 120 return output.NewError(output.APIError, sta.Message(), nil) 121 } 122 return output.NewError(output.NetworkError, "failed to invoke ReadState api", err) 123 } 124 availableRewardRau, ok := new(big.Int).SetString(string(response.Data), 10) 125 if !ok { 126 return output.NewError(output.ConvertError, "failed to convert string into big int", err) 127 } 128 // TotalBalance == Total rewards in the pool 129 request = &iotexapi.ReadStateRequest{ 130 ProtocolID: []byte("rewarding"), 131 MethodName: []byte("TotalBalance"), 132 } 133 response, err = cli.ReadState(ctx, request) 134 if err != nil { 135 sta, ok := status.FromError(err) 136 if ok { 137 return output.NewError(output.APIError, sta.Message(), nil) 138 } 139 return output.NewError(output.NetworkError, "failed to invoke ReadState api", err) 140 } 141 totalRewardRau, ok := new(big.Int).SetString(string(response.Data), 10) 142 if !ok { 143 return output.NewError(output.ConvertError, "failed to convert string into big int", err) 144 } 145 // TotalUnclaimedBalance == Rewards in the pool that has been issued and unclaimed 146 totalUnclaimedRewardRau := big.NewInt(0) 147 totalUnclaimedRewardRau.Sub(totalRewardRau, availableRewardRau) 148 message := rewardPoolMessage{ 149 TotalBalance: util.RauToString(totalRewardRau, util.IotxDecimalNum), 150 TotalUnclaimed: util.RauToString(totalUnclaimedRewardRau, util.IotxDecimalNum), 151 TotalAvailable: util.RauToString(availableRewardRau, util.IotxDecimalNum), 152 } 153 fmt.Println(message.String()) 154 return nil 155 } 156 157 func reward(arg string) error { 158 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 159 if err != nil { 160 return output.NewError(output.NetworkError, "failed to connect to endpoint", err) 161 } 162 defer conn.Close() 163 cli := iotexapi.NewAPIServiceClient(conn) 164 165 address, err := getCandidateRewardAddressByAddressOrName(cli, arg) 166 if err != nil { 167 return output.NewError(output.AddressError, "failed to get address", err) 168 } 169 170 ctx := context.Background() 171 172 jwtMD, err := util.JwtAuth() 173 if err == nil { 174 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 175 } 176 177 request := &iotexapi.ReadStateRequest{ 178 ProtocolID: []byte("rewarding"), 179 MethodName: []byte("UnclaimedBalance"), 180 Arguments: [][]byte{[]byte(address)}, 181 } 182 response, err := cli.ReadState(ctx, request) 183 if err != nil { 184 sta, ok := status.FromError(err) 185 if ok { 186 return output.NewError(output.APIError, sta.Message(), nil) 187 } 188 return output.NewError(output.NetworkError, "failed to invoke ReadState api", err) 189 } 190 rewardRau, ok := new(big.Int).SetString(string(response.Data), 10) 191 if !ok { 192 return output.NewError(output.ConvertError, "failed to convert string into big int", err) 193 } 194 message := rewardMessage{Address: address, Reward: util.RauToString(rewardRau, util.IotxDecimalNum)} 195 fmt.Println(message.String()) 196 return nil 197 }