github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/node/nodedelegate.go (about) 1 // Copyright (c) 2022 IoTeX 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 "strconv" 13 "strings" 14 15 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 16 "github.com/iotexproject/iotex-proto/golang/iotexapi" 17 "github.com/pkg/errors" 18 "github.com/spf13/cobra" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/status" 21 22 "github.com/iotexproject/iotex-core/ioctl" 23 "github.com/iotexproject/iotex-core/ioctl/config" 24 "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" 25 "github.com/iotexproject/iotex-core/ioctl/util" 26 "github.com/iotexproject/iotex-core/state" 27 ) 28 29 // Multi-language support 30 var ( 31 _delegateUses = map[config.Language]string{ 32 config.English: "delegate [-e epoch-num|-n]", 33 config.Chinese: "delegate [-e epoch数|-n]", 34 } 35 _delegateShorts = map[config.Language]string{ 36 config.English: "Print consensus delegates information in certain epoch", 37 config.Chinese: "打印在特定epoch内的共识委托信息", 38 } 39 _flagEpochNumUsages = map[config.Language]string{ 40 config.English: "specify specific epoch", 41 config.Chinese: "指定特定epoch", 42 } 43 _flagNextEpochUsages = map[config.Language]string{ 44 config.English: "query delegate of upcoming epoch", 45 config.Chinese: "查询即将到来的epoch的委托", 46 } 47 ) 48 49 type nextDelegatesMessage struct { 50 Epoch int `json:"epoch"` 51 Determined bool `json:"determined"` 52 Delegates []delegate `json:"delegates"` 53 } 54 55 type delegate struct { 56 Address string `json:"address"` 57 Rank int `json:"rank"` 58 Alias string `json:"alias"` 59 Active bool `json:"active"` 60 Production int `json:"production"` 61 Votes string `json:"votes"` 62 ProbatedStatus bool `json:"_probatedStatus"` 63 } 64 65 type delegatesMessage struct { 66 Epoch int `json:"epoch"` 67 StartBlock int `json:"startBlock"` 68 TotalBlocks int `json:"totalBlocks"` 69 Delegates []delegate `json:"delegates"` 70 } 71 72 // NewNodeDelegateCmd represents the node delegate command 73 func NewNodeDelegateCmd(client ioctl.Client) *cobra.Command { 74 var ( 75 epochNum uint64 76 nextEpoch bool 77 ) 78 79 use, _ := client.SelectTranslation(_delegateUses) 80 short, _ := client.SelectTranslation(_delegateShorts) 81 flagEpochNumUsage, _ := client.SelectTranslation(_flagEpochNumUsages) 82 flagNextEpochUsage, _ := client.SelectTranslation(_flagNextEpochUsages) 83 84 cmd := &cobra.Command{ 85 Use: use, 86 Short: short, 87 Args: cobra.ExactArgs(0), 88 RunE: func(cmd *cobra.Command, args []string) error { 89 cmd.SilenceUsage = true 90 91 if nextEpoch { 92 //nextDelegates 93 //deprecated: It won't be able to query next delegate after Easter height, because it will be determined at the end of the epoch. 94 currEpochNum, err := currEpochNum(client) 95 if err != nil { 96 return err 97 } 98 epochNum = currEpochNum + 1 99 message := nextDelegatesMessage{Epoch: int(epochNum)} 100 101 ctx := context.Background() 102 if jwtMD, err := util.JwtAuth(); err == nil { 103 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 104 } 105 apiServiceClient, err := client.APIServiceClient() 106 if err != nil { 107 return err 108 } 109 abpResponse, err := apiServiceClient.ReadState( 110 ctx, 111 &iotexapi.ReadStateRequest{ 112 ProtocolID: []byte("poll"), 113 MethodName: []byte("ActiveBlockProducersByEpoch"), 114 Arguments: [][]byte{[]byte(strconv.FormatUint(epochNum, 10))}, 115 }, 116 ) 117 if err != nil { 118 if sta, ok := status.FromError(err); ok { 119 if sta.Code() == codes.NotFound { 120 cmd.Println(message.String(epochNum)) 121 return nil 122 } else if sta.Code() == codes.Unavailable { 123 return ioctl.ErrInvalidEndpointOrInsecure 124 } 125 return errors.New(sta.Message()) 126 } 127 return errors.Wrap(err, "failed to invoke ReadState api") 128 } 129 message.Determined = true 130 var abps state.CandidateList 131 if err := abps.Deserialize(abpResponse.Data); err != nil { 132 return errors.Wrap(err, "failed to deserialize active bps") 133 } 134 135 bpResponse, err := apiServiceClient.ReadState( 136 ctx, 137 &iotexapi.ReadStateRequest{ 138 ProtocolID: []byte("poll"), 139 MethodName: []byte("BlockProducersByEpoch"), 140 Arguments: [][]byte{[]byte(strconv.FormatUint(epochNum, 10))}, 141 }, 142 ) 143 if err != nil { 144 if sta, ok := status.FromError(err); ok { 145 if sta.Code() == codes.Unavailable { 146 return ioctl.ErrInvalidEndpointOrInsecure 147 } 148 return errors.New(sta.Message()) 149 } 150 return errors.Wrap(err, "failed to invoke ReadState api") 151 } 152 var bps state.CandidateList 153 if err := bps.Deserialize(bpResponse.Data); err != nil { 154 return errors.Wrap(err, "failed to deserialize bps") 155 } 156 157 isActive := make(map[string]bool) 158 for _, abp := range abps { 159 isActive[abp.Address] = true 160 } 161 aliases := client.AliasMap() 162 for rank, bp := range bps { 163 votes := big.NewInt(0).SetBytes(bp.Votes.Bytes()) 164 message.Delegates = append(message.Delegates, delegate{ 165 Address: bp.Address, 166 Rank: rank + 1, 167 Alias: aliases[bp.Address], 168 Active: isActive[bp.Address], 169 Votes: util.RauToString(votes, util.IotxDecimalNum), 170 }) 171 } 172 cmd.Println(message.String(epochNum)) 173 } else { 174 // specfic epoch-num 175 if epochNum == 0 { 176 currEpochNum, err := currEpochNum(client) 177 if err != nil { 178 return err 179 } 180 epochNum = currEpochNum 181 } 182 183 response, err := bc.GetEpochMeta(client, epochNum) 184 if err != nil { 185 return errors.Wrap(err, "failed to get epoch meta") 186 } 187 if response.EpochData == nil { 188 return errors.New("rolldpos is not registered") 189 } 190 epochData := response.EpochData 191 aliases := client.AliasMap() 192 message := delegatesMessage{ 193 Epoch: int(epochData.Num), 194 StartBlock: int(epochData.Height), 195 TotalBlocks: int(response.TotalBlocks), 196 } 197 probationList, err := getProbationList(client, epochNum, epochData.Height) 198 if err != nil { 199 return errors.Wrap(err, "failed to get probation list") 200 } 201 for rank, bp := range response.BlockProducersInfo { 202 votes, ok := new(big.Int).SetString(bp.Votes, 10) 203 if !ok { 204 return errors.New("failed to convert votes into big int") 205 } 206 isProbated := false 207 if _, ok := probationList.ProbationInfo[bp.Address]; ok { 208 // if it exists in probation list 209 isProbated = true 210 } 211 delegate := delegate{ 212 Address: bp.Address, 213 Rank: rank + 1, 214 Alias: aliases[bp.Address], 215 Active: bp.Active, 216 Production: int(bp.Production), 217 Votes: util.RauToString(votes, util.IotxDecimalNum), 218 ProbatedStatus: isProbated, 219 } 220 message.Delegates = append(message.Delegates, delegate) 221 } 222 cmd.Println(message.String()) 223 } 224 return nil 225 }, 226 } 227 228 cmd.Flags().Uint64VarP(&epochNum, "epoch-num", "e", 0, 229 flagEpochNumUsage) 230 cmd.Flags().BoolVarP(&nextEpoch, "next-epoch", "n", false, 231 flagNextEpochUsage) 232 return cmd 233 } 234 235 func (m *nextDelegatesMessage) String(epochNum uint64) string { 236 if !m.Determined { 237 return fmt.Sprintf("delegates of upcoming epoch #%d are not determined", epochNum) 238 } 239 aliasLen := 5 240 for _, bp := range m.Delegates { 241 if len(bp.Alias) > aliasLen { 242 aliasLen = len(bp.Alias) 243 } 244 } 245 lines := []string{fmt.Sprintf("Epoch: %d\n", epochNum)} 246 formatTitleString := "%-41s %-4s %-" + strconv.Itoa(aliasLen) + "s %-6s %s" 247 formatDataString := "%-41s %4d %-" + strconv.Itoa(aliasLen) + "s %-6s %s" 248 lines = append(lines, fmt.Sprintf(formatTitleString, "Address", "Rank", "Alias", "Status", "Votes")) 249 250 var status string 251 for _, bp := range m.Delegates { 252 if bp.Active { 253 status = "active" 254 } 255 lines = append(lines, fmt.Sprintf(formatDataString, bp.Address, bp.Rank, 256 bp.Alias, status, bp.Votes)) 257 } 258 return strings.Join(lines, "\n") 259 } 260 261 func (m *delegatesMessage) String() string { 262 aliasLen := 5 263 for _, bp := range m.Delegates { 264 if len(bp.Alias) > aliasLen { 265 aliasLen = len(bp.Alias) 266 } 267 } 268 lines := []string{fmt.Sprintf("Epoch: %d, Start block height: %d,Total blocks in epoch: %d\n", 269 m.Epoch, m.StartBlock, m.TotalBlocks)} 270 formatTitleString := "%-41s %-4s %-" + strconv.Itoa(aliasLen) + "s %-6s %-6s %-12s %s" 271 formatDataString := "%-41s %4d %-" + strconv.Itoa(aliasLen) + "s %-6s %-6d %-12s %s" 272 lines = append(lines, fmt.Sprintf(formatTitleString, 273 "Address", "Rank", "Alias", "Status", "Blocks", "ProbatedStatus", "Votes")) 274 275 var ( 276 status string 277 probatedStatus string 278 ) 279 for _, bp := range m.Delegates { 280 if bp.Active { 281 status = "active" 282 } 283 if bp.ProbatedStatus { 284 probatedStatus = "probated" 285 } 286 lines = append(lines, fmt.Sprintf(formatDataString, bp.Address, bp.Rank, 287 bp.Alias, status, bp.Production, probatedStatus, bp.Votes)) 288 } 289 return strings.Join(lines, "\n") 290 } 291 292 func currEpochNum(client ioctl.Client) (uint64, error) { 293 chainMeta, err := bc.GetChainMeta(client) 294 if err != nil { 295 return 0, errors.Wrap(err, "failed to get chain meta") 296 } 297 epoch := chainMeta.Epoch 298 if epoch == nil { 299 return 0, errors.Wrap(err, "rolldpos is not registered") 300 } 301 return epoch.Num, nil 302 }