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
149 lines
4.0 KiB
Go
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
|
|
}
|