Skip to content

Commit 9cefd82

Browse files
authored
add delegate's Name in 'ioctl node delegate' output 2169 (iotexproject#2174)
* add delegate's Name in 'ioctl node delegate' output 2169
1 parent f4c6b23 commit 9cefd82

File tree

2 files changed

+154
-60
lines changed

2 files changed

+154
-60
lines changed

ioctl/cmd/node/nodedelegate.go

+148-59
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import (
1313
"strconv"
1414
"strings"
1515

16+
"github.com/golang/protobuf/proto"
1617
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
18+
"github.com/pkg/errors"
1719
"github.com/spf13/cobra"
1820
"google.golang.org/grpc/codes"
1921
"google.golang.org/grpc/status"
2022

2123
"github.com/iotexproject/iotex-proto/golang/iotexapi"
24+
"github.com/iotexproject/iotex-proto/golang/iotextypes"
2225

2326
"github.com/iotexproject/iotex-core/action/protocol/vote"
2427
"github.com/iotexproject/iotex-core/ioctl/cmd/alias"
@@ -29,6 +32,11 @@ import (
2932
"github.com/iotexproject/iotex-core/state"
3033
)
3134

35+
const (
36+
protocolID = "staking"
37+
readCandidatesLimit = 20000
38+
)
39+
3240
// Multi-language support
3341
var (
3442
delegateCmdUses = map[config.Language]string{
@@ -63,19 +71,15 @@ var nodeDelegateCmd = &cobra.Command{
6371
Args: cobra.ExactArgs(0),
6472
RunE: func(cmd *cobra.Command, args []string) error {
6573
cmd.SilenceUsage = true
66-
var err error
67-
if nextEpoch {
68-
err = nextDelegates()
69-
} else {
70-
err = delegates()
71-
}
74+
err := delegates()
7275
return output.PrintError(err)
7376

7477
},
7578
}
7679

7780
type delegate struct {
7881
Address string `json:"address"`
82+
Name string `json:"string"`
7983
Rank int `json:"rank"`
8084
Alias string `json:"alias"`
8185
Active bool `json:"active"`
@@ -101,43 +105,12 @@ func (m *delegatesMessage) String() string {
101105
}
102106
lines := []string{fmt.Sprintf("Epoch: %d, Start block height: %d,Total blocks produced in epoch: %d\n",
103107
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"
106110
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"))
138112
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))
141114
}
142115
return strings.Join(lines, "\n")
143116
}
@@ -172,16 +145,12 @@ func delegates() error {
172145
StartBlock: int(epochData.Height),
173146
TotalBlocks: int(response.TotalBlocks),
174147
}
175-
176-
probationListRes, err := bc.GetProbationList(epochNum)
148+
probationList, err := getProbationList(epochNum)
177149
if err != nil {
178150
return output.NewError(0, "failed to get probation list", err)
179151
}
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)
185154
}
186155
for rank, bp := range response.BlockProducersInfo {
187156
votes, ok := big.NewInt(0).SetString(bp.Votes, 10)
@@ -208,14 +177,12 @@ func delegates() error {
208177
return nil
209178
}
210179

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 {
213181
chainMeta, err := bc.GetChainMeta()
214182
if err != nil {
215183
return output.NewError(0, "failed to get chain meta", err)
216184
}
217-
epochNum = chainMeta.Epoch.Num + 1
218-
message := nextDelegatesMessage{Epoch: int(epochNum)}
185+
epochNum = chainMeta.Epoch.Num
219186
conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
220187
if err != nil {
221188
return output.NewError(output.NetworkError, "failed to connect to endpoint", err)
@@ -239,15 +206,13 @@ func nextDelegates() error {
239206
if err != nil {
240207
sta, ok := status.FromError(err)
241208
if ok && sta.Code() == codes.NotFound {
242-
message.Determined = false
243209
fmt.Println(message.String())
244210
return nil
245211
} else if ok {
246212
return output.NewError(output.APIError, sta.Message(), nil)
247213
}
248214
return output.NewError(output.NetworkError, "failed to invoke ReadState api", err)
249215
}
250-
message.Determined = true
251216
var ABPs state.CandidateList
252217
if err := ABPs.Deserialize(abpResponse.Data); err != nil {
253218
return output.NewError(output.SerializationError, "failed to deserialize active BPs", err)
@@ -273,17 +238,141 @@ func nextDelegates() error {
273238
for _, abp := range ABPs {
274239
isActive[abp.Address] = true
275240
}
241+
production := make(map[string]int)
242+
for _, info := range epochMeta.BlockProducersInfo {
243+
production[info.Address] = int(info.Production)
244+
}
276245
aliases := alias.GetAliasMap()
277246
for rank, bp := range BPs {
247+
isProbated := false
248+
if _, ok := pb.ProbationInfo[bp.Address]; ok {
249+
isProbated = true
250+
}
278251
votes := big.NewInt(0).SetBytes(bp.Votes.Bytes())
279252
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,
285260
})
286261
}
262+
fillMessage(cli, message, aliases, isActive, pb)
287263
fmt.Println(message.String())
288264
return nil
289265
}
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+
}

ioctl/config/config.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import (
1313

1414
"github.com/spf13/cobra"
1515
"gopkg.in/yaml.v2"
16-
16+
17+
"github.com/iotexproject/iotex-core/config"
1718
"github.com/iotexproject/iotex-core/ioctl/output"
1819
"github.com/iotexproject/iotex-core/pkg/log"
1920
)
@@ -63,6 +64,7 @@ type Config struct {
6364
DefaultAccount Context `json:"defaultAccount" yaml:"defaultAccount"`
6465
Explorer string `json:"explorer" yaml:"explorer"`
6566
Language string `json:"language" yaml:"language"`
67+
FairBankHeight uint64 `json:"fairbankHeight" yaml:"fairbankHeight"`
6668
}
6769

6870
var (
@@ -103,6 +105,9 @@ func init() {
103105
ReadConfig.Language = supportedLanguage[0]
104106
completeness = false
105107
}
108+
if ReadConfig.FairBankHeight == 0 {
109+
ReadConfig.FairBankHeight = config.Default.Genesis.FairbankBlockHeight
110+
}
106111
if !completeness {
107112
err := writeConfig()
108113
if err != nil {

0 commit comments

Comments
 (0)