Skip to content

Commit b49e864

Browse files
alpeValarDragonAlex | Interchain Labs
authored
perf(staking): optimize endblock by reducing bech32 conversions (#24354)
Co-authored-by: Dev Ojha <[email protected]> Co-authored-by: Alex | Interchain Labs <[email protected]>
1 parent f758346 commit b49e864

File tree

3 files changed

+153
-24
lines changed

3 files changed

+153
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
6262

6363
### Improvements
6464

65+
* (x/staking) [#24354](https://github.com/cosmos/cosmos-sdk/pull/24354) Optimize validator endblock by reducing bech32 conversions, resulting in significant performance improvement
6566
* (client/keys) [#18950](https://github.com/cosmos/cosmos-sdk/pull/18950) Improve `<appd> keys add`, `<appd> keys import` and `<appd> keys rename` by checking name validation.
6667
* (client/keys) [#18703](https://github.com/cosmos/cosmos-sdk/pull/18703) Improve `<appd> keys add` and `<appd> keys show` by checking whether there are duplicate keys in the multisig case.
6768
* (client/keys) [#18745](https://github.com/cosmos/cosmos-sdk/pull/18745) Improve `<appd> keys export` and `<appd> keys mnemonic` by adding --yes option to skip interactive confirmation.

x/staking/keeper/val_state_change.go

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,13 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx context.Context) (updates
188188
panic("unexpected validator status")
189189
}
190190

191+
valAddrStr := string(valAddr)
191192
// fetch the old power bytes
192-
valAddrStr, err := k.validatorAddressCodec.BytesToString(valAddr)
193-
if err != nil {
194-
return nil, err
195-
}
196-
oldPowerBytes, found := last[valAddrStr]
193+
oldPower, found := last[valAddrStr]
197194
newPower := validator.ConsensusPower(powerReduction)
198-
newPowerBytes := k.cdc.MustMarshal(&gogotypes.Int64Value{Value: newPower})
199195

200196
// update the validator set if power has changed
201-
if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) {
197+
if !found || oldPower != newPower {
202198
updates = append(updates, validator.ABCIValidatorUpdate(powerReduction))
203199

204200
if err = k.SetLastValidatorPower(ctx, valAddr, newPower); err != nil {
@@ -209,7 +205,7 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx context.Context) (updates
209205
delete(last, valAddrStr)
210206
count++
211207

212-
totalPower = totalPower.Add(math.NewInt(newPower))
208+
totalPower = totalPower.AddRaw(newPower)
213209
}
214210

215211
noLongerBonded, err := sortNoLongerBonded(last, k.validatorAddressCodec)
@@ -454,9 +450,9 @@ func (k Keeper) completeUnbondingValidator(ctx context.Context, validator types.
454450
return validator, nil
455451
}
456452

457-
// map of operator bech32-addresses to serialized power
458-
// We use bech32 strings here, because we can't have slices as keys: map[[]byte][]byte
459-
type validatorsByAddr map[string][]byte
453+
// map of operator addresses to power
454+
// We use (non bech32) strings here, because we can't have slices as keys: map[[]byte][]byte
455+
type validatorsByAddr map[string]int64
460456

461457
// get the last validator set
462458
func (k Keeper) getLastValidatorsByAddr(ctx context.Context) (validatorsByAddr, error) {
@@ -468,17 +464,12 @@ func (k Keeper) getLastValidatorsByAddr(ctx context.Context) (validatorsByAddr,
468464
}
469465
defer iterator.Close()
470466

467+
var intVal gogotypes.Int64Value
471468
for ; iterator.Valid(); iterator.Next() {
472469
// extract the validator address from the key (prefix is 1-byte, addrLen is 1-byte)
473-
valAddr := types.AddressFromLastValidatorPowerKey(iterator.Key())
474-
valAddrStr, err := k.validatorAddressCodec.BytesToString(valAddr)
475-
if err != nil {
476-
return nil, err
477-
}
478-
479-
powerBytes := iterator.Value()
480-
last[valAddrStr] = make([]byte, len(powerBytes))
481-
copy(last[valAddrStr], powerBytes)
470+
valAddrStr := string(types.AddressFromLastValidatorPowerKey(iterator.Key()))
471+
k.cdc.MustUnmarshal(iterator.Value(), &intVal)
472+
last[valAddrStr] = intVal.GetValue()
482473
}
483474

484475
return last, nil
@@ -492,10 +483,7 @@ func sortNoLongerBonded(last validatorsByAddr, ac address.Codec) ([][]byte, erro
492483
index := 0
493484

494485
for valAddrStr := range last {
495-
valAddrBytes, err := ac.StringToBytes(valAddrStr)
496-
if err != nil {
497-
return nil, err
498-
}
486+
valAddrBytes := []byte(valAddrStr)
499487
noLongerBonded[index] = valAddrBytes
500488
index++
501489
}

x/staking/keeper_bench_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package staking_test
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
7+
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
8+
cmttime "github.com/cometbft/cometbft/types/time"
9+
"github.com/stretchr/testify/require"
10+
"go.uber.org/mock/gomock"
11+
12+
sdkmath "cosmossdk.io/math"
13+
storetypes "cosmossdk.io/store/types"
14+
15+
"github.com/cosmos/cosmos-sdk/baseapp"
16+
"github.com/cosmos/cosmos-sdk/codec/address"
17+
"github.com/cosmos/cosmos-sdk/runtime"
18+
"github.com/cosmos/cosmos-sdk/testutil"
19+
sdk "github.com/cosmos/cosmos-sdk/types"
20+
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
21+
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
22+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
23+
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
24+
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
25+
stakingtestutil "github.com/cosmos/cosmos-sdk/x/staking/testutil"
26+
"github.com/cosmos/cosmos-sdk/x/staking/types"
27+
)
28+
29+
func BenchmarkApplyAndReturnValidatorSetUpdates(b *testing.B) {
30+
// goal of this benchmark to measure the performance changes in ApplyAndReturnValidatorSetUpdates
31+
// for dropping the bench32 conversion and different index types.
32+
// therefore the validator power, max or state is not modified to focus on comparing the valset
33+
// for an update only.
34+
const validatorCount = 150
35+
testEnv := newTestEnvironment(b)
36+
keeper, ctx := testEnv.stakingKeeper, testEnv.ctx
37+
r := rand.New(rand.NewSource(int64(1)))
38+
vals, valAddrs := setupState(b, r, validatorCount)
39+
40+
params, err := keeper.GetParams(ctx)
41+
require.NoError(b, err)
42+
params.MaxValidators = uint32(validatorCount)
43+
require.NoError(b, keeper.SetParams(ctx, params))
44+
45+
b.Logf("vals count: %d", validatorCount)
46+
for i, validator := range vals {
47+
require.NoError(b, keeper.SetValidator(ctx, validator))
48+
require.NoError(b, keeper.SetValidatorByConsAddr(ctx, validator))
49+
require.NoError(b, keeper.SetValidatorByPowerIndex(ctx, validator))
50+
require.NoError(b, keeper.SetLastValidatorPower(ctx, valAddrs[i], validator.ConsensusPower(sdk.DefaultPowerReduction)))
51+
}
52+
ctx, _ = testEnv.ctx.CacheContext()
53+
b.ResetTimer()
54+
for i := 0; i < b.N; i++ {
55+
_, _ = keeper.ApplyAndReturnValidatorSetUpdates(ctx)
56+
}
57+
}
58+
59+
type KeeperTestEnvironment struct {
60+
ctx sdk.Context
61+
stakingKeeper *stakingkeeper.Keeper
62+
bankKeeper *stakingtestutil.MockBankKeeper
63+
accountKeeper *stakingtestutil.MockAccountKeeper
64+
queryClient types.QueryClient
65+
msgServer types.MsgServer
66+
}
67+
68+
func newTestEnvironment(tb testing.TB) *KeeperTestEnvironment {
69+
tb.Helper()
70+
key := storetypes.NewKVStoreKey(types.StoreKey)
71+
storeService := runtime.NewKVStoreService(key)
72+
testCtx := testutil.DefaultContextWithDB(tb, key, storetypes.NewTransientStoreKey("transient_test"))
73+
ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()})
74+
encCfg := moduletestutil.MakeTestEncodingConfig()
75+
76+
ctrl := gomock.NewController(tb)
77+
accountKeeper := stakingtestutil.NewMockAccountKeeper(ctrl)
78+
accountKeeper.EXPECT().GetModuleAddress(types.BondedPoolName).
79+
Return(authtypes.NewEmptyModuleAccount(types.BondedPoolName).GetAddress())
80+
accountKeeper.EXPECT().GetModuleAddress(types.NotBondedPoolName).
81+
Return(authtypes.NewEmptyModuleAccount(types.NotBondedPoolName).GetAddress())
82+
accountKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes()
83+
84+
bankKeeper := stakingtestutil.NewMockBankKeeper(ctrl)
85+
86+
keeper := stakingkeeper.NewKeeper(
87+
encCfg.Codec,
88+
storeService,
89+
accountKeeper,
90+
bankKeeper,
91+
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
92+
address.NewBech32Codec("cosmosvaloper"),
93+
address.NewBech32Codec("cosmosvalcons"),
94+
)
95+
require.NoError(tb, keeper.SetParams(ctx, types.DefaultParams()))
96+
97+
testEnv := &KeeperTestEnvironment{
98+
ctx: ctx,
99+
stakingKeeper: keeper,
100+
bankKeeper: bankKeeper,
101+
accountKeeper: accountKeeper,
102+
}
103+
types.RegisterInterfaces(encCfg.InterfaceRegistry)
104+
queryHelper := baseapp.NewQueryServerTestHelper(ctx, encCfg.InterfaceRegistry)
105+
types.RegisterQueryServer(queryHelper, stakingkeeper.Querier{Keeper: keeper})
106+
testEnv.queryClient = types.NewQueryClient(queryHelper)
107+
testEnv.msgServer = stakingkeeper.NewMsgServerImpl(keeper)
108+
return testEnv
109+
}
110+
111+
func setupState(b *testing.B, r *rand.Rand, numBonded int) ([]types.Validator, []sdk.ValAddress) {
112+
b.Helper()
113+
accs := simtypes.RandomAccounts(r, numBonded)
114+
initialStake := sdkmath.NewInt(r.Int63n(1000) + 10)
115+
116+
validators := make([]types.Validator, numBonded)
117+
valAddrs := make([]sdk.ValAddress, numBonded)
118+
119+
for i := 0; i < numBonded; i++ {
120+
valAddr := sdk.ValAddress(accs[i].Address)
121+
valAddrs[i] = valAddr
122+
123+
maxCommission := sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 100)), 2)
124+
commission := types.NewCommission(
125+
simtypes.RandomDecAmount(r, maxCommission),
126+
maxCommission,
127+
simtypes.RandomDecAmount(r, maxCommission),
128+
)
129+
130+
validator, err := types.NewValidator(valAddr.String(), accs[i].ConsKey.PubKey(), types.Description{})
131+
require.NoError(b, err)
132+
startStake := sdkmath.NewInt(r.Int63n(1000) + initialStake.Int64())
133+
validator.Tokens = startStake.Mul(sdk.DefaultPowerReduction)
134+
validator.DelegatorShares = sdkmath.LegacyNewDecFromInt(initialStake)
135+
validator.Commission = commission
136+
validator.Status = types.Bonded
137+
validators[i] = validator
138+
}
139+
return validators, valAddrs
140+
}

0 commit comments

Comments
 (0)