@@ -13,12 +13,15 @@ import (
13
13
"strconv"
14
14
"strings"
15
15
16
+ "github.com/golang/protobuf/proto"
16
17
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
18
+ "github.com/pkg/errors"
17
19
"github.com/spf13/cobra"
18
20
"google.golang.org/grpc/codes"
19
21
"google.golang.org/grpc/status"
20
22
21
23
"github.com/iotexproject/iotex-proto/golang/iotexapi"
24
+ "github.com/iotexproject/iotex-proto/golang/iotextypes"
22
25
23
26
"github.com/iotexproject/iotex-core/action/protocol/vote"
24
27
"github.com/iotexproject/iotex-core/ioctl/cmd/alias"
@@ -29,6 +32,11 @@ import (
29
32
"github.com/iotexproject/iotex-core/state"
30
33
)
31
34
35
+ const (
36
+ protocolID = "staking"
37
+ readCandidatesLimit = 20000
38
+ )
39
+
32
40
// Multi-language support
33
41
var (
34
42
delegateCmdUses = map [config.Language ]string {
@@ -63,19 +71,15 @@ var nodeDelegateCmd = &cobra.Command{
63
71
Args : cobra .ExactArgs (0 ),
64
72
RunE : func (cmd * cobra.Command , args []string ) error {
65
73
cmd .SilenceUsage = true
66
- var err error
67
- if nextEpoch {
68
- err = nextDelegates ()
69
- } else {
70
- err = delegates ()
71
- }
74
+ err := delegates ()
72
75
return output .PrintError (err )
73
76
74
77
},
75
78
}
76
79
77
80
type delegate struct {
78
81
Address string `json:"address"`
82
+ Name string `json:"string"`
79
83
Rank int `json:"rank"`
80
84
Alias string `json:"alias"`
81
85
Active bool `json:"active"`
@@ -101,43 +105,12 @@ func (m *delegatesMessage) String() string {
101
105
}
102
106
lines := []string {fmt .Sprintf ("Epoch: %d, Start block height: %d,Total blocks produced in epoch: %d\n " ,
103
107
m .Epoch , m .StartBlock , m .TotalBlocks )}
104
- formatTitleString := "%-41s %-4s %-" + strconv .Itoa (aliasLen ) + "s %-6s %-6s %-12s %s"
105
- formatDataString := "%-41s %4d %-" + strconv .Itoa (aliasLen ) + "s %-6s %-6d %-12s %s"
108
+ formatTitleString := "%-41s %-12s %- 4s %-" + strconv .Itoa (aliasLen ) + "s %-6s %-6s %-12s %s"
109
+ formatDataString := "%-41s %-12s % 4d %-" + strconv .Itoa (aliasLen ) + "s %-6s %-6d %-12s %s"
106
110
lines = append (lines , fmt .Sprintf (formatTitleString ,
107
- "Address" , "Rank" , "Alias" , "Status" , "Blocks" , "ProbatedStatus" , "Votes" ))
108
- for _ , bp := range m .Delegates {
109
- lines = append (lines , fmt .Sprintf (formatDataString , bp .Address , bp .Rank ,
110
- bp .Alias , nodeStatus [bp .Active ], bp .Production , probatedStatus [bp .ProbatedStatus ], bp .Votes ))
111
- }
112
- return strings .Join (lines , "\n " )
113
- }
114
- return output .FormatString (output .Result , m )
115
- }
116
-
117
- type nextDelegatesMessage struct {
118
- Epoch int `json:"epoch"`
119
- Determined bool `json:"determined"`
120
- Delegates []delegate `json:"delegates"`
121
- }
122
-
123
- func (m * nextDelegatesMessage ) String () string {
124
- if output .Format == "" {
125
- if ! m .Determined {
126
- return fmt .Sprintf ("delegates of upcoming epoch #%d are not determined" , epochNum )
127
- }
128
- aliasLen := 5
129
- for _ , bp := range m .Delegates {
130
- if len (bp .Alias ) > aliasLen {
131
- aliasLen = len (bp .Alias )
132
- }
133
- }
134
- lines := []string {fmt .Sprintf ("Epoch: %d\n " , epochNum )}
135
- formatTitleString := "%-41s %-4s %-" + strconv .Itoa (aliasLen ) + "s %-6s %s"
136
- formatDataString := "%-41s %4d %-" + strconv .Itoa (aliasLen ) + "s %-6s %s"
137
- lines = append (lines , fmt .Sprintf (formatTitleString , "Address" , "Rank" , "Alias" , "Status" , "Votes" ))
111
+ "Address" , "Name" , "Rank" , "Alias" , "Status" , "Blocks" , "ProbatedStatus" , "Votes" ))
138
112
for _ , bp := range m .Delegates {
139
- lines = append (lines , fmt .Sprintf (formatDataString , bp .Address , bp .Rank ,
140
- bp .Alias , nodeStatus [bp .Active ], bp .Votes ))
113
+ lines = append (lines , fmt .Sprintf (formatDataString , bp .Address , bp .Name , bp .Rank , bp .Alias , nodeStatus [bp .Active ], bp .Production , probatedStatus [bp .ProbatedStatus ], bp .Votes ))
141
114
}
142
115
return strings .Join (lines , "\n " )
143
116
}
@@ -172,16 +145,12 @@ func delegates() error {
172
145
StartBlock : int (epochData .Height ),
173
146
TotalBlocks : int (response .TotalBlocks ),
174
147
}
175
-
176
- probationListRes , err := bc .GetProbationList (epochNum )
148
+ probationList , err := getProbationList (epochNum )
177
149
if err != nil {
178
150
return output .NewError (0 , "failed to get probation list" , err )
179
151
}
180
- probationList := & vote.ProbationList {}
181
- if probationListRes != nil {
182
- if err := probationList .Deserialize (probationListRes .Data ); err != nil {
183
- return output .NewError (output .SerializationError , "failed to deserialize probation list" , err )
184
- }
152
+ if epochData .Height >= config .ReadConfig .FairBankHeight {
153
+ return delegatesV2 (probationList , response , & message )
185
154
}
186
155
for rank , bp := range response .BlockProducersInfo {
187
156
votes , ok := big .NewInt (0 ).SetString (bp .Votes , 10 )
@@ -208,14 +177,12 @@ func delegates() error {
208
177
return nil
209
178
}
210
179
211
- // deprecated: It won't be able to query next delegate after Easter height, because it will be determined at the end of the epoch.
212
- func nextDelegates () error {
180
+ func delegatesV2 (pb * vote.ProbationList , epochMeta * iotexapi.GetEpochMetaResponse , message * delegatesMessage ) error {
213
181
chainMeta , err := bc .GetChainMeta ()
214
182
if err != nil {
215
183
return output .NewError (0 , "failed to get chain meta" , err )
216
184
}
217
- epochNum = chainMeta .Epoch .Num + 1
218
- message := nextDelegatesMessage {Epoch : int (epochNum )}
185
+ epochNum = chainMeta .Epoch .Num
219
186
conn , err := util .ConnectToEndpoint (config .ReadConfig .SecureConnect && ! config .Insecure )
220
187
if err != nil {
221
188
return output .NewError (output .NetworkError , "failed to connect to endpoint" , err )
@@ -239,15 +206,13 @@ func nextDelegates() error {
239
206
if err != nil {
240
207
sta , ok := status .FromError (err )
241
208
if ok && sta .Code () == codes .NotFound {
242
- message .Determined = false
243
209
fmt .Println (message .String ())
244
210
return nil
245
211
} else if ok {
246
212
return output .NewError (output .APIError , sta .Message (), nil )
247
213
}
248
214
return output .NewError (output .NetworkError , "failed to invoke ReadState api" , err )
249
215
}
250
- message .Determined = true
251
216
var ABPs state.CandidateList
252
217
if err := ABPs .Deserialize (abpResponse .Data ); err != nil {
253
218
return output .NewError (output .SerializationError , "failed to deserialize active BPs" , err )
@@ -273,17 +238,141 @@ func nextDelegates() error {
273
238
for _ , abp := range ABPs {
274
239
isActive [abp .Address ] = true
275
240
}
241
+ production := make (map [string ]int )
242
+ for _ , info := range epochMeta .BlockProducersInfo {
243
+ production [info .Address ] = int (info .Production )
244
+ }
276
245
aliases := alias .GetAliasMap ()
277
246
for rank , bp := range BPs {
247
+ isProbated := false
248
+ if _ , ok := pb .ProbationInfo [bp .Address ]; ok {
249
+ isProbated = true
250
+ }
278
251
votes := big .NewInt (0 ).SetBytes (bp .Votes .Bytes ())
279
252
message .Delegates = append (message .Delegates , delegate {
280
- Address : bp .Address ,
281
- Rank : rank + 1 ,
282
- Alias : aliases [bp .Address ],
283
- Active : isActive [bp .Address ],
284
- Votes : util .RauToString (votes , util .IotxDecimalNum ),
253
+ Address : bp .Address ,
254
+ Rank : rank + 1 ,
255
+ Alias : aliases [bp .Address ],
256
+ Active : isActive [bp .Address ],
257
+ Production : production [bp .Address ],
258
+ Votes : util .RauToString (votes , util .IotxDecimalNum ),
259
+ ProbatedStatus : isProbated ,
285
260
})
286
261
}
262
+ fillMessage (cli , message , aliases , isActive , pb )
287
263
fmt .Println (message .String ())
288
264
return nil
289
265
}
266
+
267
+ func getProbationList (epochNum uint64 ) (* vote.ProbationList , error ) {
268
+ probationListRes , err := bc .GetProbationList (epochNum )
269
+ if err != nil {
270
+ return nil , err
271
+ }
272
+ probationList := & vote.ProbationList {}
273
+ if probationListRes != nil {
274
+ if err := probationList .Deserialize (probationListRes .Data ); err != nil {
275
+ return nil , err
276
+ }
277
+ }
278
+ return probationList , nil
279
+ }
280
+
281
+ func fillMessage (cli iotexapi.APIServiceClient , message * delegatesMessage , alias map [string ]string , active map [string ]bool , pb * vote.ProbationList ) error {
282
+ cl , err := getAllStakingCandidates (cli )
283
+ if err != nil {
284
+ return err
285
+ }
286
+ addressMap := make (map [string ]* iotextypes.CandidateV2 )
287
+ for _ , candidate := range cl .Candidates {
288
+ addressMap [candidate .OperatorAddress ] = candidate
289
+ }
290
+ delegateAddressMap := make (map [string ]struct {})
291
+ for _ , m := range message .Delegates {
292
+ delegateAddressMap [m .Address ] = struct {}{}
293
+ }
294
+ for i , m := range message .Delegates {
295
+ if c , ok := addressMap [m .Address ]; ok {
296
+ message .Delegates [i ].Name = c .Name
297
+ continue
298
+ }
299
+ }
300
+ rank := len (message .Delegates ) + 1
301
+ for _ , candidate := range cl .Candidates {
302
+ if _ , ok := delegateAddressMap [candidate .OperatorAddress ]; ok {
303
+ continue
304
+ }
305
+ isProbated := false
306
+ if _ , ok := pb .ProbationInfo [candidate .OwnerAddress ]; ok {
307
+ isProbated = true
308
+ }
309
+ iotx , err := util .StringToIOTX (candidate .TotalWeightedVotes )
310
+ if err != nil {
311
+ return err
312
+ }
313
+ message .Delegates = append (message .Delegates , delegate {
314
+ Address : candidate .OperatorAddress ,
315
+ Name : candidate .Name ,
316
+ Rank : rank ,
317
+ Alias : alias [candidate .OperatorAddress ],
318
+ Active : active [candidate .OperatorAddress ],
319
+ Votes : iotx ,
320
+ ProbatedStatus : isProbated ,
321
+ })
322
+ rank ++
323
+ }
324
+ return nil
325
+ }
326
+
327
+ func getAllStakingCandidates (chainClient iotexapi.APIServiceClient ) (candidateListAll * iotextypes.CandidateListV2 , err error ) {
328
+ candidateListAll = & iotextypes.CandidateListV2 {}
329
+ for i := uint32 (0 ); ; i ++ {
330
+ offset := i * readCandidatesLimit
331
+ size := uint32 (readCandidatesLimit )
332
+ candidateList , err := getStakingCandidates (chainClient , offset , size )
333
+ if err != nil {
334
+ return nil , errors .Wrap (err , "failed to get candidates" )
335
+ }
336
+ candidateListAll .Candidates = append (candidateListAll .Candidates , candidateList .Candidates ... )
337
+ if len (candidateList .Candidates ) < readCandidatesLimit {
338
+ break
339
+ }
340
+ }
341
+ return
342
+ }
343
+
344
+ func getStakingCandidates (chainClient iotexapi.APIServiceClient , offset , limit uint32 ) (candidateList * iotextypes.CandidateListV2 , err error ) {
345
+ methodName , err := proto .Marshal (& iotexapi.ReadStakingDataMethod {
346
+ Method : iotexapi .ReadStakingDataMethod_CANDIDATES ,
347
+ })
348
+ if err != nil {
349
+ return nil , err
350
+ }
351
+ arg , err := proto .Marshal (& iotexapi.ReadStakingDataRequest {
352
+ Request : & iotexapi.ReadStakingDataRequest_Candidates_ {
353
+ Candidates : & iotexapi.ReadStakingDataRequest_Candidates {
354
+ Pagination : & iotexapi.PaginationParam {
355
+ Offset : offset ,
356
+ Limit : limit ,
357
+ },
358
+ },
359
+ },
360
+ })
361
+ if err != nil {
362
+ return nil , err
363
+ }
364
+ readStateRequest := & iotexapi.ReadStateRequest {
365
+ ProtocolID : []byte (protocolID ),
366
+ MethodName : methodName ,
367
+ Arguments : [][]byte {arg },
368
+ }
369
+ readStateRes , err := chainClient .ReadState (context .Background (), readStateRequest )
370
+ if err != nil {
371
+ return
372
+ }
373
+ candidateList = & iotextypes.CandidateListV2 {}
374
+ if err := proto .Unmarshal (readStateRes .GetData (), candidateList ); err != nil {
375
+ return nil , errors .Wrap (err , "failed to unmarshal VoteBucketList" )
376
+ }
377
+ return
378
+ }
0 commit comments