Skip to content

Commit

Permalink
refactor(staking)!: binary format for HistoricalInfoKey (#15701)
Browse files Browse the repository at this point in the history
## Description

Closes: #11463



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

* [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
* [ ] added `!` to the type prefix if API or client breaking change
* [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
* [ ] provided a link to the relevant issue or specification
* [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules)
* [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
* [ ] added a changelog entry to `CHANGELOG.md`
* [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
* [ ] updated the relevant documentation or specification
* [ ] reviewed "Files changed" and left comments if necessary
* [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

* [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
* [ ] confirmed `!` in the type prefix if API or client breaking change
* [ ] confirmed all author checklist items have been addressed 
* [ ] reviewed state machine logic
* [ ] reviewed API design and naming
* [ ] reviewed documentation is accurate
* [ ] reviewed tests and test coverage
* [ ] manually tested (if applicable)
  • Loading branch information
babadro authored Apr 13, 2023
1 parent ac3c209 commit 491cf3b
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### State Machine Breaking

* (x/staking) [#15701](https://github.com/cosmos/cosmos-sdk/pull/15701) The `HistoricalInfoKey` has been updated to use a binary format.
* (x/slashing) [#15580](https://github.com/cosmos/cosmos-sdk/pull/15580) The validator slashing window now stores "chunked" bitmap entries for each validator's signing window instead of a single boolean entry per signing window index.
* (x/feegrant) [#14294](https://github.com/cosmos/cosmos-sdk/pull/14294) Moved the logic of rejecting duplicate grant from `msg_server` to `keeper` method.
* (x/staking) [#14590](https://github.com/cosmos/cosmos-sdk/pull/14590) `MsgUndelegateResponse` now includes undelegated amount. `x/staking` module's `keeper.Undelegate` now returns 3 values (completionTime,undelegateAmount,error) instead of 2.
Expand Down Expand Up @@ -161,6 +162,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Client Breaking Changes

* (x/staking) [#15701](https://github.com/cosmos/cosmos-sdk/pull/15701) `HistoricalInfoKey` now has a binary format.
* (grpc-web) [#14652](https://github.com/cosmos/cosmos-sdk/pull/14652) Use same port for gRPC-Web and the API server.

### CLI Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/staking/keeper/determinstic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ func TestGRPCHistoricalInfo(t *testing.T) {
Height: height,
}

testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.HistoricalInfo, 1930, false)
testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.HistoricalInfo, 1945, false)
}

func TestGRPCDelegatorValidators(t *testing.T) {
Expand Down
8 changes: 4 additions & 4 deletions x/staking/keeper/historical_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ func (s *KeeperTestSuite) TestGetAllHistoricalInfo() {
testutil.NewValidator(s.T(), addrVals[1], PKs[1]),
}

header1 := cmtproto.Header{ChainID: "HelloChain", Height: 10}
header2 := cmtproto.Header{ChainID: "HelloChain", Height: 11}
header3 := cmtproto.Header{ChainID: "HelloChain", Height: 12}
header1 := cmtproto.Header{ChainID: "HelloChain", Height: 9}
header2 := cmtproto.Header{ChainID: "HelloChain", Height: 10}
header3 := cmtproto.Header{ChainID: "HelloChain", Height: 11}

hist1 := stakingtypes.HistoricalInfo{Header: header1, Valset: valSet}
hist2 := stakingtypes.HistoricalInfo{Header: header2, Valset: valSet}
Expand All @@ -147,7 +147,7 @@ func (s *KeeperTestSuite) TestGetAllHistoricalInfo() {
expHistInfos := []stakingtypes.HistoricalInfo{hist1, hist2, hist3}

for i, hi := range expHistInfos {
keeper.SetHistoricalInfo(ctx, int64(10+i), &hi) //nolint:gosec // G601: Implicit memory aliasing in for loop.
keeper.SetHistoricalInfo(ctx, int64(9+i), &hi) //nolint:gosec // G601: Implicit memory aliasing in for loop.
}

infos := keeper.GetAllHistoricalInfo(ctx)
Expand Down
6 changes: 6 additions & 0 deletions x/staking/keeper/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
v2 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v2"
v3 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v3"
v4 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v4"
v5 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v5"
)

// Migrator is a struct for handling in-place store migrations.
Expand Down Expand Up @@ -36,3 +37,8 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error {
func (m Migrator) Migrate3to4(ctx sdk.Context) error {
return v4.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc, m.legacySubspace)
}

// Migrate4to5 migrates x/staking state from consensus version 4 to 5.
func (m Migrator) Migrate4to5(ctx sdk.Context) error {
return v5.MigrateStore(ctx, m.keeper.storeKey)
}
9 changes: 9 additions & 0 deletions x/staking/migrations/v2/keys.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package v2

import "strconv"

const (
// ModuleName is the name of the module
ModuleName = "staking"
)

var HistoricalInfoKey = []byte{0x50} // prefix for the historical info

// GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects.
func GetHistoricalInfoKey(height int64) []byte {
return append(HistoricalInfoKey, []byte(strconv.FormatInt(height, 10))...)
}
2 changes: 1 addition & 1 deletion x/staking/migrations/v2/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func TestStoreMigration(t *testing.T) {
{
"HistoricalInfoKey",
v1.GetHistoricalInfoKey(4),
types.GetHistoricalInfoKey(4),
v2.GetHistoricalInfoKey(4),
},
}

Expand Down
12 changes: 12 additions & 0 deletions x/staking/migrations/v5/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package v5

import "encoding/binary"

var HistoricalInfoKey = []byte{0x50} // prefix for the historical info

// GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects.
func GetHistoricalInfoKey(height int64) []byte {
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, uint64(height))
return append(HistoricalInfoKey, heightBytes...)
}
71 changes: 71 additions & 0 deletions x/staking/migrations/v5/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package v5_test

import (
"math"
"math/rand"
"testing"
"time"

storetypes "cosmossdk.io/store/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/testutil"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
v2 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v2"
v5 "github.com/cosmos/cosmos-sdk/x/staking/migrations/v5"
stackingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"
)

func TestHistoricalKeysMigration(t *testing.T) {
storeKey := storetypes.NewKVStoreKey("staking")
tKey := storetypes.NewTransientStoreKey("transient_test")
ctx := testutil.DefaultContext(storeKey, tKey)
store := ctx.KVStore(storeKey)

type testCase struct {
oldKey, newKey []byte
historicalInfo []byte
}

testCases := make(map[int64]testCase)

// edge cases
testCases[0], testCases[1], testCases[math.MaxInt32] = testCase{}, testCase{}, testCase{}

// random cases
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
for i := 0; i < 10; i++ {
height := r.Intn(math.MaxInt32-2) + 2

testCases[int64(height)] = testCase{}
}

cdc := moduletestutil.MakeTestEncodingConfig().Codec
for height := range testCases {
testCases[height] = testCase{
oldKey: v2.GetHistoricalInfoKey(height),
newKey: v5.GetHistoricalInfoKey(height),
historicalInfo: cdc.MustMarshal(createHistoricalInfo(height, "testChainID")),
}
}

// populate store using old key format
for _, tc := range testCases {
store.Set(tc.oldKey, tc.historicalInfo)
}

// migrate store to new key format
require.NoErrorf(t, v5.MigrateStore(ctx, storeKey), "v5.MigrateStore failed, seed: %d", seed)

// check results
for _, tc := range testCases {
require.Nilf(t, store.Get(tc.oldKey), "old key should be deleted, seed: %d", seed)
require.NotNilf(t, store.Get(tc.newKey), "new key should be created, seed: %d", seed)
require.Equalf(t, tc.historicalInfo, store.Get(tc.newKey), "seed: %d", seed)
}
}

func createHistoricalInfo(height int64, chainID string) *stackingtypes.HistoricalInfo {
return &stackingtypes.HistoricalInfo{Header: cmtproto.Header{ChainID: chainID, Height: height}}
}
46 changes: 46 additions & 0 deletions x/staking/migrations/v5/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package v5

import (
"fmt"
"strconv"

"cosmossdk.io/log"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// MigrateStore performs in-place store migrations from v4 to v5.
func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey) error {
store := ctx.KVStore(storeKey)
return migrateHistoricalInfoKeys(store, ctx.Logger())
}

// migrateHistoricalInfoKeys migrate HistoricalInfo keys to binary format
func migrateHistoricalInfoKeys(store storetypes.KVStore, logger log.Logger) error {
// old key is of format:
// prefix (0x50) || heightBytes (string representation of height in 10 base)
// new key is of format:
// prefix (0x50) || heightBytes (byte array representation using big-endian byte order)
oldStore := prefix.NewStore(store, HistoricalInfoKey)

oldStoreIter := oldStore.Iterator(nil, nil)
defer sdk.LogDeferred(logger, func() error { return oldStoreIter.Close() })

for ; oldStoreIter.Valid(); oldStoreIter.Next() {
strHeight := oldStoreIter.Key()

intHeight, err := strconv.ParseInt(string(strHeight), 10, 64)
if err != nil {
return fmt.Errorf("can't parse height from key %q to int64: %v", strHeight, err)
}

newStoreKey := GetHistoricalInfoKey(intHeight)

// Set new key on store. Values don't change.
store.Set(newStoreKey, oldStoreIter.Value())
oldStore.Delete(oldStoreIter.Key())
}

return nil
}
5 changes: 4 additions & 1 deletion x/staking/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
)

const (
consensusVersion uint64 = 4
consensusVersion uint64 = 5
)

var (
Expand Down Expand Up @@ -165,6 +165,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {
if err := cfg.RegisterMigration(types.ModuleName, 3, m.Migrate3to4); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 3 to 4: %v", types.ModuleName, err))
}
if err := cfg.RegisterMigration(types.ModuleName, 4, m.Migrate4to5); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 4 to 5: %v", types.ModuleName, err))
}
}

// InitGenesis performs genesis initialization for the staking module.
Expand Down
5 changes: 3 additions & 2 deletions x/staking/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"time"

"cosmossdk.io/math"
Expand Down Expand Up @@ -376,5 +375,7 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr

// GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects.
func GetHistoricalInfoKey(height int64) []byte {
return append(HistoricalInfoKey, []byte(strconv.FormatInt(height, 10))...)
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, uint64(height))
return append(HistoricalInfoKey, heightBytes...)
}
20 changes: 20 additions & 0 deletions x/staking/types/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package types_test
import (
"bytes"
"encoding/hex"
math2 "math"
"math/big"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -129,3 +131,21 @@ func TestTestGetValidatorQueueKeyOrder(t *testing.T) {
require.Equal(t, -1, bytes.Compare(keyB, endKey)) // keyB <= endKey
require.Equal(t, 1, bytes.Compare(keyC, endKey)) // keyB >= endKey
}

func TestGetHistoricalInfoKey(t *testing.T) {
tests := []struct {
height int64
want []byte
}{
{0, append(types.HistoricalInfoKey, []byte{0, 0, 0, 0, 0, 0, 0, 0}...)},
{1, append(types.HistoricalInfoKey, []byte{0, 0, 0, 0, 0, 0, 0, 1}...)},
{2, append(types.HistoricalInfoKey, []byte{0, 0, 0, 0, 0, 0, 0, 2}...)},
{514, append(types.HistoricalInfoKey, []byte{0, 0, 0, 0, 0, 0, 2, 2}...)},
{math2.MaxInt64, append(types.HistoricalInfoKey, []byte{127, 255, 255, 255, 255, 255, 255, 255}...)},
}
for _, tt := range tests {
t.Run(strconv.FormatInt(tt.height, 10), func(t *testing.T) {
require.Equal(t, tt.want, types.GetHistoricalInfoKey(tt.height))
})
}
}

0 comments on commit 491cf3b

Please sign in to comment.