cosmos-sdk/client/rpc/validators.go
Emmanuel T Odeke abe3e0c6da
perf: all: remove unnecessary allocations from strings.Builder.WriteString(fmt.Sprintf(...)) (#13230)
This change removes a code pattern that I noticed while on a late night
audit of cosmovisor in which
strings.Builder.WriteString(fmt.Sprintf(...))
calls were being made, yet that's counterproductive to using fmt.Fprintf
which will check whether the writer implements .WriteString and then
avoids the need to firstly build a string using fmt.Sprintf.

The performance wins from this change transcend all dimensions as
exhibited below:

```shell
$ benchstat before.txt after.txt
name            old time/op    new time/op    delta
DetailString-8    5.48µs ±23%    4.40µs ±11%  -19.79%  (p=0.000 n=20+17)

name            old alloc/op   new alloc/op   delta
DetailString-8    2.63kB ± 0%    2.11kB ± 0%  -19.76%  (p=0.000 n=20+20)

name            old allocs/op  new allocs/op  delta
DetailString-8      63.0 ± 0%      50.0 ± 0%  -20.63%  (p=0.000 n=20+20)
```

Fixes #13229
2022-09-09 21:57:46 +00:00

149 lines
4.0 KiB
Go

package rpc
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
tmcli "github.com/tendermint/tendermint/libs/cli"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
)
// TODO these next two functions feel kinda hacky based on their placement
// ValidatorCommand returns the validator set for a given height
func ValidatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tendermint-validator-set [height]",
Short: "Get the full tendermint validator set at given height",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
var height *int64
// optional height
if len(args) > 0 {
val, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
if val > 0 {
height = &val
}
}
page, _ := cmd.Flags().GetInt(flags.FlagPage)
limit, _ := cmd.Flags().GetInt(flags.FlagLimit)
result, err := GetValidators(cmd.Context(), clientCtx, height, &page, &limit)
if err != nil {
return err
}
return clientCtx.PrintObjectLegacy(result)
},
}
cmd.Flags().String(flags.FlagNode, "tcp://localhost:26657", "<host>:<port> to Tendermint RPC interface for this chain")
cmd.Flags().StringP(tmcli.OutputFlag, "o", "text", "Output format (text|json)")
cmd.Flags().Int(flags.FlagPage, query.DefaultPage, "Query a specific page of paginated results")
cmd.Flags().Int(flags.FlagLimit, 100, "Query number of results returned per page")
return cmd
}
// Validator output
type ValidatorOutput struct {
Address sdk.ConsAddress `json:"address"`
PubKey cryptotypes.PubKey `json:"pub_key"`
ProposerPriority int64 `json:"proposer_priority"`
VotingPower int64 `json:"voting_power"`
}
// Validators at a certain height output in bech32 format
type ResultValidatorsOutput struct {
BlockHeight int64 `json:"block_height"`
Validators []ValidatorOutput `json:"validators"`
Total uint64 `json:"total"`
}
func (rvo ResultValidatorsOutput) String() string {
var b strings.Builder
fmt.Fprintf(&b, "block height: %d\n", rvo.BlockHeight)
fmt.Fprintf(&b, "total count: %d\n", rvo.Total)
for _, val := range rvo.Validators {
fmt.Fprintf(&b, `
Address: %s
Pubkey: %s
ProposerPriority: %d
VotingPower: %d
`,
val.Address, val.PubKey, val.ProposerPriority, val.VotingPower,
)
}
return b.String()
}
func validatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
pk, err := cryptocodec.FromTmPubKeyInterface(validator.PubKey)
if err != nil {
return ValidatorOutput{}, err
}
return ValidatorOutput{
Address: sdk.ConsAddress(validator.Address),
PubKey: pk,
ProposerPriority: validator.ProposerPriority,
VotingPower: validator.VotingPower,
}, nil
}
// GetValidators from client
func GetValidators(ctx context.Context, clientCtx client.Context, height *int64, page, limit *int) (ResultValidatorsOutput, error) {
// get the node
node, err := clientCtx.GetNode()
if err != nil {
return ResultValidatorsOutput{}, err
}
validatorsRes, err := node.Validators(ctx, height, page, limit)
if err != nil {
return ResultValidatorsOutput{}, err
}
total := validatorsRes.Total
if validatorsRes.Total < 0 {
total = 0
}
out := ResultValidatorsOutput{
BlockHeight: validatorsRes.BlockHeight,
Validators: make([]ValidatorOutput, len(validatorsRes.Validators)),
Total: uint64(total),
}
for i := 0; i < len(validatorsRes.Validators); i++ {
out.Validators[i], err = validatorOutput(validatorsRes.Validators[i])
if err != nil {
return out, err
}
}
return out, nil
}