Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADR 17 Implementation: Historical Module #5380

Merged
merged 31 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
17fcad3
add history module and some tests
AdityaSripal Dec 10, 2019
5f916a4
Merge branch 'aditya/history' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Dec 10, 2019
2b052b4
complete query and write tests
AdityaSripal Dec 10, 2019
481ee0d
complete query and write tests
AdityaSripal Dec 10, 2019
954c8b1
add abci test
AdityaSripal Dec 10, 2019
da266e8
docs and CHANGELOG
AdityaSripal Dec 10, 2019
813c634
add client query methods and CHANGELOG
AdityaSripal Dec 11, 2019
5c634a3
fix merge
AdityaSripal Dec 11, 2019
33154f0
Merge branch 'master' into aditya/history
cwgoes Dec 11, 2019
aa34dc7
implement edge case for when historicalentries param gets reduced
AdityaSripal Dec 11, 2019
fb2e660
Merge branch 'aditya/history' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Dec 11, 2019
d0e8d88
Update x/staking/abci.go
cwgoes Dec 11, 2019
5997e15
Apply suggestions from code review
AdityaSripal Dec 12, 2019
82d386f
apply codereview suggestions
AdityaSripal Dec 12, 2019
a45a5fa
appease linter
AdityaSripal Dec 13, 2019
ef43df5
Update x/staking/client/cli/query.go
fedekunze Dec 16, 2019
093dcf5
Merge branch 'master' into aditya/history
fedekunze Dec 16, 2019
c3fc30b
Apply suggestions from code review
AdityaSripal Dec 16, 2019
e4d2c34
Update x/staking/keeper/historical_info.go
AdityaSripal Dec 16, 2019
74b3f19
update spec, alias, and fix minor bug
AdityaSripal Dec 17, 2019
73cbb7e
cleanup changelog
AdityaSripal Dec 17, 2019
277054b
Merge branch 'master' into aditya/history
AdityaSripal Dec 17, 2019
a61d28a
Merge branch 'master' of https://github.com/cosmos/cosmos-sdk into ad…
AdityaSripal Dec 17, 2019
1fdc269
Merge branch 'aditya/history' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Dec 17, 2019
3f59d8c
fix merge
AdityaSripal Dec 17, 2019
9364ba4
Make BeginBlocker/EndBlocker methods on keeper
AdityaSripal Dec 17, 2019
c0f2ac7
Merge branch 'master' into aditya/history
alexanderbez Dec 17, 2019
4402252
move beginblock/endblock logic into keeper, maintain API
AdityaSripal Dec 18, 2019
c3ad793
remove redundant abci tests
AdityaSripal Dec 18, 2019
b1a9ac5
Merge branch 'aditya/history' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Dec 18, 2019
ffc64bf
Merge branch 'master' into aditya/history
AdityaSripal Dec 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions x/staking/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package staking

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)

// BeginBlocker will
func BeginBlocker(ctx sdk.Context, k Keeper) {
entryNum := k.HistoricalEntries(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to move this entire block into a method on the keeper.

  1. This keeps the BeginBlocker abstraction clean.
  2. Improves testability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to doing this here, but what i have follows the pattern of BeginBlockers in x/slashing, x/supply, and x/upgrade

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They should be updated as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the EndBlocker? Since that isn't a method on the keeper either?

// if there is no need to persist historicalInfo, return
if entryNum == 0 {
return
}

// Create HistoricalInfo struct
lastVals := k.GetLastValidators(ctx)
types.Validators(lastVals).Sort()
historicalEntry := types.HistoricalInfo{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use the NewHistoricalInfo constructor here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, the validation (+ panic on error) is missing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if validation here is necessary. Validation is true by construction, so long as Tendermint consensus is working correctly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the validation used then?

Header: ctx.BlockHeader(),
ValSet: lastVals,
}

// Set latest HistoricalInfo at current height
k.SetHistoricalInfo(ctx, ctx.BlockHeight(), historicalEntry)

// prune store to ensure we only have parameter-defined historical entries
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
if ctx.BlockHeight() > int64(entryNum) {
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
k.DeleteHistoricalInfo(ctx, ctx.BlockHeight()-int64(entryNum))
}
}
35 changes: 35 additions & 0 deletions x/staking/keeper/historical_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)

func (k Keeper) GetHistoricalInfo(ctx sdk.Context, height int64) (hi types.HistoricalInfo, found bool) {
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
key := types.GetHistoricalInfoKey(height)

value := store.Get(key)

AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
if value == nil {
return types.HistoricalInfo{}, false
}

hi = types.MustUnmarshalHistoricalInfo(k.cdc, value)
return hi, true
}

func (k Keeper) SetHistoricalInfo(ctx sdk.Context, height int64, hi types.HistoricalInfo) {
store := ctx.KVStore(k.storeKey)
key := types.GetHistoricalInfoKey(height)

value := types.MustMarshalHistoricalInfo(k.cdc, hi)
store.Set(key, value)
}

func (k Keeper) DeleteHistoricalInfo(ctx sdk.Context, height int64) {
store := ctx.KVStore(k.storeKey)
key := types.GetHistoricalInfoKey(height)

store.Delete(key)
}
34 changes: 34 additions & 0 deletions x/staking/keeper/historical_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package keeper

import (
"sort"
"testing"

"github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/stretchr/testify/require"
)

func TestHistoricalInfo(t *testing.T) {
ctx, _, keeper, _ := CreateTestInput(t, false, 10)
var validators []types.Validator
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved

for i, valAddr := range addrVals {
validators = append(validators, types.NewValidator(valAddr, PKs[i], types.Description{}))
}

hi := types.NewHistoricalInfo(ctx.BlockHeader(), validators)

keeper.SetHistoricalInfo(ctx, 2, hi)

recv, found := keeper.GetHistoricalInfo(ctx, 2)
require.True(t, found, "HistoricalInfo not found after set")
require.Equal(t, hi, recv, "HistoricalInfo not equal")
require.True(t, sort.IsSorted(types.Validators(recv.ValSet)), "HistoricalInfo validators is not sorted")

keeper.DeleteHistoricalInfo(ctx, 2)

recv, found = keeper.GetHistoricalInfo(ctx, 2)
require.False(t, found, "HistoricalInfo found after delete")
require.Equal(t, types.HistoricalInfo{}, recv, "HistoricalInfo is not empty")
}
8 changes: 8 additions & 0 deletions x/staking/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ func (k Keeper) MaxEntries(ctx sdk.Context) (res uint16) {
return
}

// HistoricalEntries = number of historical info entries
// to persist in store
func (k Keeper) HistoricalEntries(ctx sdk.Context) (res uint16) {
k.paramstore.Get(ctx, types.KeyHistoricalEntries, &res)
return
}

// BondDenom - Bondable coin denomination
func (k Keeper) BondDenom(ctx sdk.Context) (res string) {
k.paramstore.Get(ctx, types.KeyBondDenom, &res)
Expand All @@ -49,6 +56,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params {
k.UnbondingTime(ctx),
k.MaxValidators(ctx),
k.MaxEntries(ctx),
k.HistoricalEntries(ctx),
k.BondDenom(ctx),
)
}
Expand Down
11 changes: 11 additions & 0 deletions x/staking/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ func queryRedelegations(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byt
return res, nil
}

func queryHistoricalInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
var params types.QueryHistoricalInfoParams

err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(string(req.Data))
}
return nil, nil

}

func queryPool(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) {
bondDenom := k.BondDenom(ctx)
bondedPool := k.GetBondedPool(ctx)
Expand Down
4 changes: 3 additions & 1 deletion x/staking/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
}

// BeginBlock returns the begin blocker for the staking module.
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}

// EndBlock returns the end blocker for the staking module. It returns no validator
// updates.
Expand Down
2 changes: 1 addition & 1 deletion x/staking/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func RandomizedGenState(simState *module.SimulationState) {
// NewSimulationManager constructor for this to work
simState.UnbondTime = unbondTime

params := types.NewParams(simState.UnbondTime, maxValidators, 7, sdk.DefaultBondDenom)
params := types.NewParams(simState.UnbondTime, maxValidators, 7, 3, sdk.DefaultBondDenom)

// validators & delegations
var (
Expand Down
21 changes: 13 additions & 8 deletions x/staking/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ type CodeType = sdk.CodeType
const (
DefaultCodespace sdk.CodespaceType = ModuleName

CodeInvalidValidator CodeType = 101
CodeInvalidDelegation CodeType = 102
CodeInvalidInput CodeType = 103
CodeValidatorJailed CodeType = 104
CodeInvalidAddress CodeType = sdk.CodeInvalidAddress
CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
CodeInvalidValidator CodeType = 101
CodeInvalidDelegation CodeType = 102
CodeInvalidInput CodeType = 103
CodeValidatorJailed CodeType = 104
CodeInvalidHistoricalInfo CodeType = 105
CodeInvalidAddress CodeType = sdk.CodeInvalidAddress
CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)

//validator
Expand Down Expand Up @@ -212,3 +213,7 @@ func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error {
func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "missing signature")
}

func ErrInvalidHistoricalInfo(codespace sdk.CodespaceType) sdk.Error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, godocs

return sdk.NewError(codespace, CodeInvalidHistoricalInfo, "invalid historical info")
}
49 changes: 49 additions & 0 deletions x/staking/types/historical_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package types

import (
"sort"

abci "github.com/tendermint/tendermint/abci/types"

"github.com/cosmos/cosmos-sdk/codec"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

type HistoricalInfo struct {
Header abci.Header
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
ValSet []Validator
}

func NewHistoricalInfo(header abci.Header, valSet []Validator) HistoricalInfo {
return HistoricalInfo{
Header: header,
ValSet: valSet,
}
}

func MustMarshalHistoricalInfo(cdc *codec.Codec, hi HistoricalInfo) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(hi)
}

func MustUnmarshalHistoricalInfo(cdc *codec.Codec, value []byte) HistoricalInfo {
hi, err := UnmarshalHistoricalInfo(cdc, value)
if err != nil {
panic(err)
}
return hi
}

func UnmarshalHistoricalInfo(cdc *codec.Codec, value []byte) (hi HistoricalInfo, err error) {
err = cdc.UnmarshalBinaryLengthPrefixed(value, &hi)
return hi, err
}

func ValidateHistoricalInfo(hi HistoricalInfo) error {
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
if hi.ValSet != nil {
return sdkerrors.Wrap(ErrInvalidHistoricalInfo(DefaultCodespace), "ValidatorSer is nil")
}
if !sort.IsSorted(Validators(hi.ValSet)) {
return sdkerrors.Wrap(ErrInvalidHistoricalInfo(DefaultCodespace), "ValidatorSet is not sorted by address")
}
return nil
}
10 changes: 10 additions & 0 deletions x/staking/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"encoding/binary"
"fmt"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -45,6 +46,8 @@ var (
UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue
RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue
ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue

HistoricalInfoKey = []byte{0x50} // prefix for the historical info
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
)

// gets the key for the validator with address
Expand Down Expand Up @@ -278,3 +281,10 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr
GetREDsToValDstIndexKey(valDstAddr),
delAddr.Bytes()...)
}

//________________________________________________________________________________

// gets the key for historicalinfo
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
func GetHistoricalInfoKey(height int64) []byte {
return append(HistoricalInfoKey, []byte(fmt.Sprintf("%d", height))...)
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
}
43 changes: 25 additions & 18 deletions x/staking/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,41 @@ const (

// Default maximum entries in a UBD/RED pair
DefaultMaxEntries uint16 = 7

DefaultHistoricalEntries uint16 = 0
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
)

// nolint - Keys for parameter access
var (
KeyUnbondingTime = []byte("UnbondingTime")
KeyMaxValidators = []byte("MaxValidators")
KeyMaxEntries = []byte("KeyMaxEntries")
KeyBondDenom = []byte("BondDenom")
KeyUnbondingTime = []byte("UnbondingTime")
KeyMaxValidators = []byte("MaxValidators")
KeyMaxEntries = []byte("KeyMaxEntries")
KeyBondDenom = []byte("BondDenom")
KeyHistoricalEntries = []byte("HistoricalEntries")
)

var _ params.ParamSet = (*Params)(nil)

// Params defines the high level settings for staking
type Params struct {
UnbondingTime time.Duration `json:"unbonding_time" yaml:"unbonding_time"` // time duration of unbonding
MaxValidators uint16 `json:"max_validators" yaml:"max_validators"` // maximum number of validators (max uint16 = 65535)
MaxEntries uint16 `json:"max_entries" yaml:"max_entries"` // max entries for either unbonding delegation or redelegation (per pair/trio)
UnbondingTime time.Duration `json:"unbonding_time" yaml:"unbonding_time"` // time duration of unbonding
MaxValidators uint16 `json:"max_validators" yaml:"max_validators"` // maximum number of validators (max uint16 = 65535)
MaxEntries uint16 `json:"max_entries" yaml:"max_entries"` // max entries for either unbonding delegation or redelegation (per pair/trio)
HistoricalEntries uint16 `json:"historical_entries" yaml:"historical_entries"` // number of historical entries to persist
// note: we need to be a bit careful about potential overflow here, since this is user-determined
BondDenom string `json:"bond_denom" yaml:"bond_denom"` // bondable coin denomination
}

// NewParams creates a new Params instance
func NewParams(unbondingTime time.Duration, maxValidators, maxEntries uint16,
func NewParams(unbondingTime time.Duration, maxValidators, maxEntries, historicalEntries uint16,
bondDenom string) Params {

return Params{
UnbondingTime: unbondingTime,
MaxValidators: maxValidators,
MaxEntries: maxEntries,
BondDenom: bondDenom,
UnbondingTime: unbondingTime,
MaxValidators: maxValidators,
MaxEntries: maxEntries,
HistoricalEntries: historicalEntries,
BondDenom: bondDenom,
}
}

Expand All @@ -61,6 +66,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
{Key: KeyUnbondingTime, Value: &p.UnbondingTime},
{Key: KeyMaxValidators, Value: &p.MaxValidators},
{Key: KeyMaxEntries, Value: &p.MaxEntries},
{Key: KeyHistoricalEntries, Value: &p.HistoricalEntries},
{Key: KeyBondDenom, Value: &p.BondDenom},
}
}
Expand All @@ -75,17 +81,18 @@ func (p Params) Equal(p2 Params) bool {

// DefaultParams returns a default set of parameters.
func DefaultParams() Params {
return NewParams(DefaultUnbondingTime, DefaultMaxValidators, DefaultMaxEntries, sdk.DefaultBondDenom)
return NewParams(DefaultUnbondingTime, DefaultMaxValidators, DefaultMaxEntries, DefaultHistoricalEntries, sdk.DefaultBondDenom)
}

// String returns a human readable string representation of the parameters.
func (p Params) String() string {
return fmt.Sprintf(`Params:
Unbonding Time: %s
Max Validators: %d
Max Entries: %d
Bonded Coin Denom: %s`, p.UnbondingTime,
p.MaxValidators, p.MaxEntries, p.BondDenom)
Unbonding Time: %s
Max Validators: %d
Max Entries: %d
Historical Entries: %d
Bonded Coin Denom: %s`, p.UnbondingTime,
p.MaxValidators, p.MaxEntries, p.HistoricalEntries, p.BondDenom)
}

// unmarshal the current staking params value from store key or panic
Expand Down
11 changes: 11 additions & 0 deletions x/staking/types/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
QueryDelegatorValidator = "delegatorValidator"
QueryPool = "pool"
QueryParameters = "parameters"
QueryHistoricalInfo = "historicalInfo"
)

// defines the params for the following queries:
Expand Down Expand Up @@ -96,3 +97,13 @@ type QueryValidatorsParams struct {
func NewQueryValidatorsParams(page, limit int, status string) QueryValidatorsParams {
return QueryValidatorsParams{page, limit, status}
}

// QueryHistoricalInfoParams defines the params for the following queries:
// - 'custom/staking/historicalInfo'
type QueryHistoricalInfoParams struct {
Height int
}

func NewQueryHistoricalInfoParams(height int) QueryHistoricalInfoParams {
return QueryHistoricalInfoParams{height}
}
Loading