Merge PR #5380: ADR 17 Implementation: Historical Module

This commit is contained in:
Aditya 2019-12-18 05:20:02 -08:00 committed by Alexander Bezobchuk
parent 908fd2ba78
commit fca4cbef88
30 changed files with 827 additions and 175 deletions

View File

@ -147,6 +147,9 @@ that allows for arbitrary vesting periods.
* `ValidateSigCountDecorator`: Validate the number of signatures in tx based on app-parameters. * `ValidateSigCountDecorator`: Validate the number of signatures in tx based on app-parameters.
* `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks. * `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks.
* (cli) [\#5223](https://github.com/cosmos/cosmos-sdk/issues/5223) Cosmos Ledger App v2.0.0 is now supported. The changes are backwards compatible and App v1.5.x is still supported. * (cli) [\#5223](https://github.com/cosmos/cosmos-sdk/issues/5223) Cosmos Ledger App v2.0.0 is now supported. The changes are backwards compatible and App v1.5.x is still supported.
* (x/staking) [\#5380](https://github.com/cosmos/cosmos-sdk/pull/5380) Introduced ability to store historical info entries in staking keeper, allows applications to introspect specified number of past headers and validator sets
* Introduces new parameter `HistoricalEntries` which allows applications to determine how many recent historical info entries they want to persist in store. Default value is 0.
* Introduces cli commands and rest routes to query historical information at a given height
* (modules) [\#5249](https://github.com/cosmos/cosmos-sdk/pull/5249) Funds are now allowed to be directly sent to the community pool (via the distribution module account). * (modules) [\#5249](https://github.com/cosmos/cosmos-sdk/pull/5249) Funds are now allowed to be directly sent to the community pool (via the distribution module account).
* (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Introduce keybase option to allow overriding the default private key implementation of a key generated through the `keys add` cli command. * (keys) [\#4941](https://github.com/cosmos/cosmos-sdk/issues/4941) Introduce keybase option to allow overriding the default private key implementation of a key generated through the `keys add` cli command.

18
x/staking/abci.go Normal file
View File

@ -0,0 +1,18 @@
package staking
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
abci "github.com/tendermint/tendermint/abci/types"
)
// BeginBlocker will persist the current header and validator set as a historical entry
// and prune the oldest entry based on the HistoricalEntries parameter
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
k.TrackHistoricalInfo(ctx)
}
// Called every block, update validator set
func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
return k.BlockValidatorUpdates(ctx)
}

View File

@ -19,6 +19,7 @@ const (
CodeInvalidDelegation = types.CodeInvalidDelegation CodeInvalidDelegation = types.CodeInvalidDelegation
CodeInvalidInput = types.CodeInvalidInput CodeInvalidInput = types.CodeInvalidInput
CodeValidatorJailed = types.CodeValidatorJailed CodeValidatorJailed = types.CodeValidatorJailed
CodeInvalidHistoricalInfo = types.CodeInvalidHistoricalInfo
CodeInvalidAddress = types.CodeInvalidAddress CodeInvalidAddress = types.CodeInvalidAddress
CodeUnauthorized = types.CodeUnauthorized CodeUnauthorized = types.CodeUnauthorized
CodeInternal = types.CodeInternal CodeInternal = types.CodeInternal
@ -47,6 +48,7 @@ const (
QueryDelegatorValidator = types.QueryDelegatorValidator QueryDelegatorValidator = types.QueryDelegatorValidator
QueryPool = types.QueryPool QueryPool = types.QueryPool
QueryParameters = types.QueryParameters QueryParameters = types.QueryParameters
QueryHistoricalInfo = types.QueryHistoricalInfo
MaxMonikerLength = types.MaxMonikerLength MaxMonikerLength = types.MaxMonikerLength
MaxIdentityLength = types.MaxIdentityLength MaxIdentityLength = types.MaxIdentityLength
MaxWebsiteLength = types.MaxWebsiteLength MaxWebsiteLength = types.MaxWebsiteLength
@ -86,6 +88,10 @@ var (
NewDelegationResp = types.NewDelegationResp NewDelegationResp = types.NewDelegationResp
NewRedelegationResponse = types.NewRedelegationResponse NewRedelegationResponse = types.NewRedelegationResponse
NewRedelegationEntryResponse = types.NewRedelegationEntryResponse NewRedelegationEntryResponse = types.NewRedelegationEntryResponse
NewHistoricalInfo = types.NewHistoricalInfo
MustMarshalHistoricalInfo = types.MustMarshalHistoricalInfo
MustUnmarshalHistoricalInfo = types.MustUnmarshalHistoricalInfo
UnmarshalHistoricalInfo = types.UnmarshalHistoricalInfo
ErrNilValidatorAddr = types.ErrNilValidatorAddr ErrNilValidatorAddr = types.ErrNilValidatorAddr
ErrBadValidatorAddr = types.ErrBadValidatorAddr ErrBadValidatorAddr = types.ErrBadValidatorAddr
ErrNoValidatorFound = types.ErrNoValidatorFound ErrNoValidatorFound = types.ErrNoValidatorFound
@ -131,6 +137,8 @@ var (
ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven
ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven
ErrMissingSignature = types.ErrMissingSignature ErrMissingSignature = types.ErrMissingSignature
ErrInvalidHistoricalInfo = types.ErrInvalidHistoricalInfo
ErrNoHistoricalInfo = types.ErrNoHistoricalInfo
NewGenesisState = types.NewGenesisState NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState
NewMultiStakingHooks = types.NewMultiStakingHooks NewMultiStakingHooks = types.NewMultiStakingHooks
@ -159,6 +167,7 @@ var (
GetREDsFromValSrcIndexKey = types.GetREDsFromValSrcIndexKey GetREDsFromValSrcIndexKey = types.GetREDsFromValSrcIndexKey
GetREDsToValDstIndexKey = types.GetREDsToValDstIndexKey GetREDsToValDstIndexKey = types.GetREDsToValDstIndexKey
GetREDsByDelToValDstIndexKey = types.GetREDsByDelToValDstIndexKey GetREDsByDelToValDstIndexKey = types.GetREDsByDelToValDstIndexKey
GetHistoricalInfoKey = types.GetHistoricalInfoKey
NewMsgCreateValidator = types.NewMsgCreateValidator NewMsgCreateValidator = types.NewMsgCreateValidator
NewMsgEditValidator = types.NewMsgEditValidator NewMsgEditValidator = types.NewMsgEditValidator
NewMsgDelegate = types.NewMsgDelegate NewMsgDelegate = types.NewMsgDelegate
@ -174,6 +183,7 @@ var (
NewQueryBondsParams = types.NewQueryBondsParams NewQueryBondsParams = types.NewQueryBondsParams
NewQueryRedelegationParams = types.NewQueryRedelegationParams NewQueryRedelegationParams = types.NewQueryRedelegationParams
NewQueryValidatorsParams = types.NewQueryValidatorsParams NewQueryValidatorsParams = types.NewQueryValidatorsParams
NewQueryHistoricalInfoParams = types.NewQueryHistoricalInfoParams
NewValidator = types.NewValidator NewValidator = types.NewValidator
MustMarshalValidator = types.MustMarshalValidator MustMarshalValidator = types.MustMarshalValidator
MustUnmarshalValidator = types.MustUnmarshalValidator MustUnmarshalValidator = types.MustUnmarshalValidator
@ -196,6 +206,7 @@ var (
UnbondingQueueKey = types.UnbondingQueueKey UnbondingQueueKey = types.UnbondingQueueKey
RedelegationQueueKey = types.RedelegationQueueKey RedelegationQueueKey = types.RedelegationQueueKey
ValidatorQueueKey = types.ValidatorQueueKey ValidatorQueueKey = types.ValidatorQueueKey
HistoricalInfoKey = types.HistoricalInfoKey
KeyUnbondingTime = types.KeyUnbondingTime KeyUnbondingTime = types.KeyUnbondingTime
KeyMaxValidators = types.KeyMaxValidators KeyMaxValidators = types.KeyMaxValidators
KeyMaxEntries = types.KeyMaxEntries KeyMaxEntries = types.KeyMaxEntries
@ -216,6 +227,7 @@ type (
Redelegation = types.Redelegation Redelegation = types.Redelegation
RedelegationEntry = types.RedelegationEntry RedelegationEntry = types.RedelegationEntry
Redelegations = types.Redelegations Redelegations = types.Redelegations
HistoricalInfo = types.HistoricalInfo
DelegationResponse = types.DelegationResponse DelegationResponse = types.DelegationResponse
DelegationResponses = types.DelegationResponses DelegationResponses = types.DelegationResponses
RedelegationResponse = types.RedelegationResponse RedelegationResponse = types.RedelegationResponse
@ -237,6 +249,7 @@ type (
QueryBondsParams = types.QueryBondsParams QueryBondsParams = types.QueryBondsParams
QueryRedelegationParams = types.QueryRedelegationParams QueryRedelegationParams = types.QueryRedelegationParams
QueryValidatorsParams = types.QueryValidatorsParams QueryValidatorsParams = types.QueryValidatorsParams
QueryHistoricalInfoParams = types.QueryHistoricalInfoParams
Validator = types.Validator Validator = types.Validator
Validators = types.Validators Validators = types.Validators
Description = types.Description Description = types.Description

View File

@ -2,6 +2,7 @@ package cli
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -35,6 +36,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
GetCmdQueryValidatorDelegations(queryRoute, cdc), GetCmdQueryValidatorDelegations(queryRoute, cdc),
GetCmdQueryValidatorUnbondingDelegations(queryRoute, cdc), GetCmdQueryValidatorUnbondingDelegations(queryRoute, cdc),
GetCmdQueryValidatorRedelegations(queryRoute, cdc), GetCmdQueryValidatorRedelegations(queryRoute, cdc),
GetCmdQueryHistoricalInfo(queryRoute, cdc),
GetCmdQueryParams(queryRoute, cdc), GetCmdQueryParams(queryRoute, cdc),
GetCmdQueryPool(queryRoute, cdc))...) GetCmdQueryPool(queryRoute, cdc))...)
@ -527,6 +529,50 @@ $ %s query staking redelegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p
} }
} }
// GetCmdQueryHistoricalInfo implements the historical info query command
func GetCmdQueryHistoricalInfo(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "historical-info [height]",
Args: cobra.ExactArgs(1),
Short: "Query historical info at given height",
Long: strings.TrimSpace(
fmt.Sprintf(`Query historical info at given height.
Example:
$ %s query staking historical-info 5
`,
version.ClientName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
height, err := strconv.ParseInt(args[0], 10, 64)
if err != nil || height < 0 {
return fmt.Errorf("height argument provided must be a non-negative-integer: %v", err)
}
bz, err := cdc.MarshalJSON(types.QueryHistoricalInfoParams{Height: height})
if err != nil {
return err
}
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryHistoricalInfo)
res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
var resp types.HistoricalInfo
if err := cdc.UnmarshalJSON(res, &resp); err != nil {
return err
}
return cliCtx.PrintOutput(resp)
},
}
}
// GetCmdQueryPool implements the pool query command. // GetCmdQueryPool implements the pool query command.
func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command { func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{

View File

@ -3,6 +3,7 @@ package rest
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -86,6 +87,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
validatorUnbondingDelegationsHandlerFn(cliCtx), validatorUnbondingDelegationsHandlerFn(cliCtx),
).Methods("GET") ).Methods("GET")
// Get HistoricalInfo at a given height
r.HandleFunc(
"/staking/historical_info/{height}",
historicalInfoHandlerFn(cliCtx),
).Methods("GET")
// Get the current state of the staking pool // Get the current state of the staking pool
r.HandleFunc( r.HandleFunc(
"/staking/pool", "/staking/pool",
@ -313,6 +320,36 @@ func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext) http.Hand
return queryValidator(cliCtx, "custom/staking/validatorUnbondingDelegations") return queryValidator(cliCtx, "custom/staking/validatorUnbondingDelegations")
} }
// HTTP request handler to query historical info at a given height
func historicalInfoHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
heightStr := vars["height"]
height, err := strconv.ParseInt(heightStr, 10, 64)
if err != nil || height < 0 {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Must provide non-negative integer for height: %v", err))
return
}
params := types.NewQueryHistoricalInfoParams(height)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryHistoricalInfo)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
// HTTP request handler to query the pool information // HTTP request handler to query the pool information
func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"time" "time"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/common"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
@ -40,61 +39,6 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
} }
} }
// Called every block, update validator set
func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
// Calculate validator set changes.
//
// NOTE: ApplyAndReturnValidatorSetUpdates has to come before
// UnbondAllMatureValidatorQueue.
// This fixes a bug when the unbonding period is instant (is the case in
// some of the tests). The test expected the validator to be completely
// unbonded after the Endblocker (go from Bonded -> Unbonding during
// ApplyAndReturnValidatorSetUpdates and then Unbonding -> Unbonded during
// UnbondAllMatureValidatorQueue).
validatorUpdates := k.ApplyAndReturnValidatorSetUpdates(ctx)
// Unbond all mature validators from the unbonding queue.
k.UnbondAllMatureValidatorQueue(ctx)
// Remove all mature unbonding delegations from the ubd queue.
matureUnbonds := k.DequeueAllMatureUBDQueue(ctx, ctx.BlockHeader().Time)
for _, dvPair := range matureUnbonds {
err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddress, dvPair.ValidatorAddress)
if err != nil {
continue
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCompleteUnbonding,
sdk.NewAttribute(types.AttributeKeyValidator, dvPair.ValidatorAddress.String()),
sdk.NewAttribute(types.AttributeKeyDelegator, dvPair.DelegatorAddress.String()),
),
)
}
// Remove all mature redelegations from the red queue.
matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time)
for _, dvvTriplet := range matureRedelegations {
err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddress,
dvvTriplet.ValidatorSrcAddress, dvvTriplet.ValidatorDstAddress)
if err != nil {
continue
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCompleteRedelegation,
sdk.NewAttribute(types.AttributeKeyDelegator, dvvTriplet.DelegatorAddress.String()),
sdk.NewAttribute(types.AttributeKeySrcValidator, dvvTriplet.ValidatorSrcAddress.String()),
sdk.NewAttribute(types.AttributeKeyDstValidator, dvvTriplet.ValidatorDstAddress.String()),
),
)
}
return validatorUpdates
}
// These functions assume everything has been authenticated, // These functions assume everything has been authenticated,
// now we just perform action and save // now we just perform action and save

View File

@ -0,0 +1,71 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// GetHistoricalInfo gets the historical info at a given height
func (k Keeper) GetHistoricalInfo(ctx sdk.Context, height int64) (types.HistoricalInfo, bool) {
store := ctx.KVStore(k.storeKey)
key := types.GetHistoricalInfoKey(height)
value := store.Get(key)
if value == nil {
return types.HistoricalInfo{}, false
}
hi := types.MustUnmarshalHistoricalInfo(k.cdc, value)
return hi, true
}
// SetHistoricalInfo sets the historical info at a given height
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)
}
// DeleteHistoricalInfo deletes the historical info at a given height
func (k Keeper) DeleteHistoricalInfo(ctx sdk.Context, height int64) {
store := ctx.KVStore(k.storeKey)
key := types.GetHistoricalInfoKey(height)
store.Delete(key)
}
// TrackHistoricalInfo saves the latest historical-info and deletes the oldest
// heights that are below pruning height
func (k Keeper) TrackHistoricalInfo(ctx sdk.Context) {
entryNum := k.HistoricalEntries(ctx)
// Prune store to ensure we only have parameter-defined historical entries.
// In most cases, this will involve removing a single historical entry.
// In the rare scenario when the historical entries gets reduced to a lower value k'
// from the original value k. k - k' entries must be deleted from the store.
// Since the entries to be deleted are always in a continuous range, we can iterate
// over the historical entries starting from the most recent version to be pruned
// and then return at the first empty entry.
for i := ctx.BlockHeight() - int64(entryNum); i >= 0; i-- {
_, found := k.GetHistoricalInfo(ctx, i)
if found {
k.DeleteHistoricalInfo(ctx, i)
} else {
break
}
}
// if there is no need to persist historicalInfo, return
if entryNum == 0 {
return
}
// Create HistoricalInfo struct
lastVals := k.GetLastValidators(ctx)
historicalEntry := types.NewHistoricalInfo(ctx.BlockHeader(), lastVals)
// Set latest HistoricalInfo at current height
k.SetHistoricalInfo(ctx, ctx.BlockHeight(), historicalEntry)
}

View File

@ -0,0 +1,106 @@
package keeper
import (
"sort"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/stretchr/testify/require"
)
func TestHistoricalInfo(t *testing.T) {
ctx, _, keeper, _ := CreateTestInput(t, false, 10)
validators := make([]types.Validator, len(addrVals))
for i, valAddr := range addrVals {
validators[i] = 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")
}
func TestTrackHistoricalInfo(t *testing.T) {
ctx, _, k, _ := CreateTestInput(t, false, 10)
// set historical entries in params to 5
params := types.DefaultParams()
params.HistoricalEntries = 5
k.SetParams(ctx, params)
// set historical info at 5, 4 which should be pruned
// and check that it has been stored
h4 := abci.Header{
ChainID: "HelloChain",
Height: 4,
}
h5 := abci.Header{
ChainID: "HelloChain",
Height: 5,
}
valSet := []types.Validator{
types.NewValidator(sdk.ValAddress(Addrs[0]), PKs[0], types.Description{}),
types.NewValidator(sdk.ValAddress(Addrs[1]), PKs[1], types.Description{}),
}
hi4 := types.NewHistoricalInfo(h4, valSet)
hi5 := types.NewHistoricalInfo(h5, valSet)
k.SetHistoricalInfo(ctx, 4, hi4)
k.SetHistoricalInfo(ctx, 5, hi5)
recv, found := k.GetHistoricalInfo(ctx, 4)
require.True(t, found)
require.Equal(t, hi4, recv)
recv, found = k.GetHistoricalInfo(ctx, 5)
require.True(t, found)
require.Equal(t, hi5, recv)
// Set last validators in keeper
val1 := types.NewValidator(sdk.ValAddress(Addrs[2]), PKs[2], types.Description{})
k.SetValidator(ctx, val1)
k.SetLastValidatorPower(ctx, val1.OperatorAddress, 10)
val2 := types.NewValidator(sdk.ValAddress(Addrs[3]), PKs[3], types.Description{})
vals := []types.Validator{val1, val2}
sort.Sort(types.Validators(vals))
k.SetValidator(ctx, val2)
k.SetLastValidatorPower(ctx, val2.OperatorAddress, 8)
// Set Header for BeginBlock context
header := abci.Header{
ChainID: "HelloChain",
Height: 10,
}
ctx = ctx.WithBlockHeader(header)
k.TrackHistoricalInfo(ctx)
// Check HistoricalInfo at height 10 is persisted
expected := types.HistoricalInfo{
Header: header,
ValSet: vals,
}
recv, found = k.GetHistoricalInfo(ctx, 10)
require.True(t, found, "GetHistoricalInfo failed after BeginBlock")
require.Equal(t, expected, recv, "GetHistoricalInfo returned eunexpected result")
// Check HistoricalInfo at height 5, 4 is pruned
recv, found = k.GetHistoricalInfo(ctx, 4)
require.False(t, found, "GetHistoricalInfo did not prune earlier height")
require.Equal(t, types.HistoricalInfo{}, recv, "GetHistoricalInfo at height 4 is not empty after prune")
recv, found = k.GetHistoricalInfo(ctx, 5)
require.False(t, found, "GetHistoricalInfo did not prune first prune height")
require.Equal(t, types.HistoricalInfo{}, recv, "GetHistoricalInfo at height 5 is not empty after prune")
}

View File

@ -37,6 +37,13 @@ func (k Keeper) MaxEntries(ctx sdk.Context) (res uint16) {
return 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 // BondDenom - Bondable coin denomination
func (k Keeper) BondDenom(ctx sdk.Context) (res string) { func (k Keeper) BondDenom(ctx sdk.Context) (res string) {
k.paramstore.Get(ctx, types.KeyBondDenom, &res) k.paramstore.Get(ctx, types.KeyBondDenom, &res)
@ -49,6 +56,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params {
k.UnbondingTime(ctx), k.UnbondingTime(ctx),
k.MaxValidators(ctx), k.MaxValidators(ctx),
k.MaxEntries(ctx), k.MaxEntries(ctx),
k.HistoricalEntries(ctx),
k.BondDenom(ctx), k.BondDenom(ctx),
) )
} }

View File

@ -38,6 +38,8 @@ func NewQuerier(k Keeper) sdk.Querier {
return queryDelegatorValidators(ctx, req, k) return queryDelegatorValidators(ctx, req, k)
case types.QueryDelegatorValidator: case types.QueryDelegatorValidator:
return queryDelegatorValidator(ctx, req, k) return queryDelegatorValidator(ctx, req, k)
case types.QueryHistoricalInfo:
return queryHistoricalInfo(ctx, req, k)
case types.QueryPool: case types.QueryPool:
return queryPool(ctx, k) return queryPool(ctx, k)
case types.QueryParameters: case types.QueryParameters:
@ -327,6 +329,27 @@ func queryRedelegations(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byt
return res, nil return res, nil
} }
func queryHistoricalInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) {
var params types.QueryHistoricalInfoParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(string(req.Data))
}
hi, found := k.GetHistoricalInfo(ctx, params.Height)
if !found {
return nil, types.ErrNoHistoricalInfo(types.DefaultCodespace)
}
res, err := codec.MarshalJSONIndent(types.ModuleCdc, hi)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return res, nil
}
func queryPool(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { func queryPool(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) {
bondDenom := k.BondDenom(ctx) bondDenom := k.BondDenom(ctx)
bondedPool := k.GetBondedPool(ctx) bondedPool := k.GetBondedPool(ctx)

View File

@ -31,6 +31,13 @@ func TestNewQuerier(t *testing.T) {
keeper.SetValidatorByPowerIndex(ctx, validators[i]) keeper.SetValidatorByPowerIndex(ctx, validators[i])
} }
header := abci.Header{
ChainID: "HelloChain",
Height: 5,
}
hi := types.NewHistoricalInfo(header, validators[:])
keeper.SetHistoricalInfo(ctx, 5, hi)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "", Path: "",
Data: []byte{}, Data: []byte{},
@ -39,53 +46,63 @@ func TestNewQuerier(t *testing.T) {
querier := NewQuerier(keeper) querier := NewQuerier(keeper)
bz, err := querier(ctx, []string{"other"}, query) bz, err := querier(ctx, []string{"other"}, query)
require.NotNil(t, err) require.Error(t, err)
require.Nil(t, bz) require.Nil(t, bz)
_, err = querier(ctx, []string{"pool"}, query) _, err = querier(ctx, []string{"pool"}, query)
require.Nil(t, err) require.NoError(t, err)
_, err = querier(ctx, []string{"parameters"}, query) _, err = querier(ctx, []string{"parameters"}, query)
require.Nil(t, err) require.NoError(t, err)
queryValParams := types.NewQueryValidatorParams(addrVal1) queryValParams := types.NewQueryValidatorParams(addrVal1)
bz, errRes := cdc.MarshalJSON(queryValParams) bz, errRes := cdc.MarshalJSON(queryValParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query.Path = "/custom/staking/validator" query.Path = "/custom/staking/validator"
query.Data = bz query.Data = bz
_, err = querier(ctx, []string{"validator"}, query) _, err = querier(ctx, []string{"validator"}, query)
require.Nil(t, err) require.NoError(t, err)
_, err = querier(ctx, []string{"validatorDelegations"}, query) _, err = querier(ctx, []string{"validatorDelegations"}, query)
require.Nil(t, err) require.NoError(t, err)
_, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query) _, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query)
require.Nil(t, err) require.NoError(t, err)
queryDelParams := types.NewQueryDelegatorParams(addrAcc2) queryDelParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes = cdc.MarshalJSON(queryDelParams) bz, errRes = cdc.MarshalJSON(queryDelParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query.Path = "/custom/staking/validator" query.Path = "/custom/staking/validator"
query.Data = bz query.Data = bz
_, err = querier(ctx, []string{"delegatorDelegations"}, query) _, err = querier(ctx, []string{"delegatorDelegations"}, query)
require.Nil(t, err) require.NoError(t, err)
_, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query) _, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query)
require.Nil(t, err) require.NoError(t, err)
_, err = querier(ctx, []string{"delegatorValidators"}, query) _, err = querier(ctx, []string{"delegatorValidators"}, query)
require.Nil(t, err) require.NoError(t, err)
bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(nil, nil, nil)) bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(nil, nil, nil))
require.Nil(t, errRes) require.NoError(t, errRes)
query.Data = bz query.Data = bz
_, err = querier(ctx, []string{"redelegations"}, query) _, err = querier(ctx, []string{"redelegations"}, query)
require.Nil(t, err) require.NoError(t, err)
queryHisParams := types.NewQueryHistoricalInfoParams(5)
bz, errRes = cdc.MarshalJSON(queryHisParams)
require.NoError(t, errRes)
query.Path = "/custom/staking/historicalInfo"
query.Data = bz
_, err = querier(ctx, []string{"historicalInfo"}, query)
require.NoError(t, err)
} }
func TestQueryParametersPool(t *testing.T) { func TestQueryParametersPool(t *testing.T) {
@ -94,21 +111,21 @@ func TestQueryParametersPool(t *testing.T) {
bondDenom := sdk.DefaultBondDenom bondDenom := sdk.DefaultBondDenom
res, err := queryParameters(ctx, keeper) res, err := queryParameters(ctx, keeper)
require.Nil(t, err) require.NoError(t, err)
var params types.Params var params types.Params
errRes := cdc.UnmarshalJSON(res, &params) errRes := cdc.UnmarshalJSON(res, &params)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, keeper.GetParams(ctx), params) require.Equal(t, keeper.GetParams(ctx), params)
res, err = queryPool(ctx, keeper) res, err = queryPool(ctx, keeper)
require.Nil(t, err) require.NoError(t, err)
var pool types.Pool var pool types.Pool
bondedPool := keeper.GetBondedPool(ctx) bondedPool := keeper.GetBondedPool(ctx)
notBondedPool := keeper.GetNotBondedPool(ctx) notBondedPool := keeper.GetNotBondedPool(ctx)
errRes = cdc.UnmarshalJSON(res, &pool) errRes = cdc.UnmarshalJSON(res, &pool)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, bondedPool.GetCoins().AmountOf(bondDenom), pool.BondedTokens) require.Equal(t, bondedPool.GetCoins().AmountOf(bondDenom), pool.BondedTokens)
require.Equal(t, notBondedPool.GetCoins().AmountOf(bondDenom), pool.NotBondedTokens) require.Equal(t, notBondedPool.GetCoins().AmountOf(bondDenom), pool.NotBondedTokens)
} }
@ -138,7 +155,7 @@ func TestQueryValidators(t *testing.T) {
for i, s := range status { for i, s := range status {
queryValsParams := types.NewQueryValidatorsParams(1, int(params.MaxValidators), s.String()) queryValsParams := types.NewQueryValidatorsParams(1, int(params.MaxValidators), s.String())
bz, err := cdc.MarshalJSON(queryValsParams) bz, err := cdc.MarshalJSON(queryValsParams)
require.Nil(t, err) require.NoError(t, err)
req := abci.RequestQuery{ req := abci.RequestQuery{
Path: fmt.Sprintf("/custom/%s/%s", types.QuerierRoute, types.QueryValidators), Path: fmt.Sprintf("/custom/%s/%s", types.QuerierRoute, types.QueryValidators),
@ -146,11 +163,11 @@ func TestQueryValidators(t *testing.T) {
} }
res, err := queryValidators(ctx, req, keeper) res, err := queryValidators(ctx, req, keeper)
require.Nil(t, err) require.NoError(t, err)
var validatorsResp []types.Validator var validatorsResp []types.Validator
err = cdc.UnmarshalJSON(res, &validatorsResp) err = cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, err) require.NoError(t, err)
require.Equal(t, 1, len(validatorsResp)) require.Equal(t, 1, len(validatorsResp))
require.ElementsMatch(t, validators[i].OperatorAddress, validatorsResp[0].OperatorAddress) require.ElementsMatch(t, validators[i].OperatorAddress, validatorsResp[0].OperatorAddress)
@ -160,18 +177,18 @@ func TestQueryValidators(t *testing.T) {
// Query each validator // Query each validator
queryParams := types.NewQueryValidatorParams(addrVal1) queryParams := types.NewQueryValidatorParams(addrVal1)
bz, err := cdc.MarshalJSON(queryParams) bz, err := cdc.MarshalJSON(queryParams)
require.Nil(t, err) require.NoError(t, err)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "/custom/staking/validator", Path: "/custom/staking/validator",
Data: bz, Data: bz,
} }
res, err := queryValidator(ctx, query, keeper) res, err := queryValidator(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var validator types.Validator var validator types.Validator
err = cdc.UnmarshalJSON(res, &validator) err = cdc.UnmarshalJSON(res, &validator)
require.Nil(t, err) require.NoError(t, err)
require.Equal(t, queriedValidators[0], validator) require.Equal(t, queriedValidators[0], validator)
} }
@ -199,7 +216,7 @@ func TestQueryDelegation(t *testing.T) {
// Query Delegator bonded validators // Query Delegator bonded validators
queryParams := types.NewQueryDelegatorParams(addrAcc2) queryParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes := cdc.MarshalJSON(queryParams) bz, errRes := cdc.MarshalJSON(queryParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "/custom/staking/delegatorValidators", Path: "/custom/staking/delegatorValidators",
@ -209,11 +226,11 @@ func TestQueryDelegation(t *testing.T) {
delValidators := keeper.GetDelegatorValidators(ctx, addrAcc2, params.MaxValidators) delValidators := keeper.GetDelegatorValidators(ctx, addrAcc2, params.MaxValidators)
res, err := queryDelegatorValidators(ctx, query, keeper) res, err := queryDelegatorValidators(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var validatorsResp []types.Validator var validatorsResp []types.Validator
errRes = cdc.UnmarshalJSON(res, &validatorsResp) errRes = cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, len(delValidators), len(validatorsResp)) require.Equal(t, len(delValidators), len(validatorsResp))
require.ElementsMatch(t, delValidators, validatorsResp) require.ElementsMatch(t, delValidators, validatorsResp)
@ -222,12 +239,12 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1] query.Data = bz[:len(bz)-1]
_, err = queryDelegatorValidators(ctx, query, keeper) _, err = queryDelegatorValidators(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// Query bonded validator // Query bonded validator
queryBondParams := types.NewQueryBondsParams(addrAcc2, addrVal1) queryBondParams := types.NewQueryBondsParams(addrAcc2, addrVal1)
bz, errRes = cdc.MarshalJSON(queryBondParams) bz, errRes = cdc.MarshalJSON(queryBondParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/delegatorValidator", Path: "/custom/staking/delegatorValidator",
@ -235,11 +252,11 @@ func TestQueryDelegation(t *testing.T) {
} }
res, err = queryDelegatorValidator(ctx, query, keeper) res, err = queryDelegatorValidator(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var validator types.Validator var validator types.Validator
errRes = cdc.UnmarshalJSON(res, &validator) errRes = cdc.UnmarshalJSON(res, &validator)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, delValidators[0], validator) require.Equal(t, delValidators[0], validator)
@ -247,7 +264,7 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1] query.Data = bz[:len(bz)-1]
_, err = queryDelegatorValidator(ctx, query, keeper) _, err = queryDelegatorValidator(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// Query delegation // Query delegation
@ -260,11 +277,11 @@ func TestQueryDelegation(t *testing.T) {
require.True(t, found) require.True(t, found)
res, err = queryDelegation(ctx, query, keeper) res, err = queryDelegation(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var delegationRes types.DelegationResponse var delegationRes types.DelegationResponse
errRes = cdc.UnmarshalJSON(res, &delegationRes) errRes = cdc.UnmarshalJSON(res, &delegationRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, delegation.ValidatorAddress, delegationRes.ValidatorAddress) require.Equal(t, delegation.ValidatorAddress, delegationRes.ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegationRes.DelegatorAddress) require.Equal(t, delegation.DelegatorAddress, delegationRes.DelegatorAddress)
@ -277,11 +294,11 @@ func TestQueryDelegation(t *testing.T) {
} }
res, err = queryDelegatorDelegations(ctx, query, keeper) res, err = queryDelegatorDelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var delegatorDelegations types.DelegationResponses var delegatorDelegations types.DelegationResponses
errRes = cdc.UnmarshalJSON(res, &delegatorDelegations) errRes = cdc.UnmarshalJSON(res, &delegatorDelegations)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Len(t, delegatorDelegations, 1) require.Len(t, delegatorDelegations, 1)
require.Equal(t, delegation.ValidatorAddress, delegatorDelegations[0].ValidatorAddress) require.Equal(t, delegation.ValidatorAddress, delegatorDelegations[0].ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegatorDelegations[0].DelegatorAddress) require.Equal(t, delegation.DelegatorAddress, delegatorDelegations[0].DelegatorAddress)
@ -291,12 +308,12 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1] query.Data = bz[:len(bz)-1]
_, err = queryDelegation(ctx, query, keeper) _, err = queryDelegation(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// Query validator delegations // Query validator delegations
bz, errRes = cdc.MarshalJSON(types.NewQueryValidatorParams(addrVal1)) bz, errRes = cdc.MarshalJSON(types.NewQueryValidatorParams(addrVal1))
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "custom/staking/validatorDelegations", Path: "custom/staking/validatorDelegations",
@ -304,11 +321,11 @@ func TestQueryDelegation(t *testing.T) {
} }
res, err = queryValidatorDelegations(ctx, query, keeper) res, err = queryValidatorDelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var delegationsRes types.DelegationResponses var delegationsRes types.DelegationResponses
errRes = cdc.UnmarshalJSON(res, &delegationsRes) errRes = cdc.UnmarshalJSON(res, &delegationsRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Len(t, delegatorDelegations, 1) require.Len(t, delegatorDelegations, 1)
require.Equal(t, delegation.ValidatorAddress, delegationsRes[0].ValidatorAddress) require.Equal(t, delegation.ValidatorAddress, delegationsRes[0].ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegationsRes[0].DelegatorAddress) require.Equal(t, delegation.DelegatorAddress, delegationsRes[0].DelegatorAddress)
@ -317,11 +334,11 @@ func TestQueryDelegation(t *testing.T) {
// Query unbonging delegation // Query unbonging delegation
unbondingTokens := sdk.TokensFromConsensusPower(10) unbondingTokens := sdk.TokensFromConsensusPower(10)
_, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddress, unbondingTokens.ToDec()) _, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddress, unbondingTokens.ToDec())
require.Nil(t, err) require.NoError(t, err)
queryBondParams = types.NewQueryBondsParams(addrAcc2, addrVal1) queryBondParams = types.NewQueryBondsParams(addrAcc2, addrVal1)
bz, errRes = cdc.MarshalJSON(queryBondParams) bz, errRes = cdc.MarshalJSON(queryBondParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation", Path: "/custom/staking/unbondingDelegation",
@ -332,11 +349,11 @@ func TestQueryDelegation(t *testing.T) {
require.True(t, found) require.True(t, found)
res, err = queryUnbondingDelegation(ctx, query, keeper) res, err = queryUnbondingDelegation(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var unbondRes types.UnbondingDelegation var unbondRes types.UnbondingDelegation
errRes = cdc.UnmarshalJSON(res, &unbondRes) errRes = cdc.UnmarshalJSON(res, &unbondRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, unbond, unbondRes) require.Equal(t, unbond, unbondRes)
@ -344,7 +361,7 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1] query.Data = bz[:len(bz)-1]
_, err = queryUnbondingDelegation(ctx, query, keeper) _, err = queryUnbondingDelegation(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// Query Delegator Delegations // Query Delegator Delegations
@ -354,29 +371,29 @@ func TestQueryDelegation(t *testing.T) {
} }
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper) res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var delegatorUbds []types.UnbondingDelegation var delegatorUbds []types.UnbondingDelegation
errRes = cdc.UnmarshalJSON(res, &delegatorUbds) errRes = cdc.UnmarshalJSON(res, &delegatorUbds)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Equal(t, unbond, delegatorUbds[0]) require.Equal(t, unbond, delegatorUbds[0])
// error unknown request // error unknown request
query.Data = bz[:len(bz)-1] query.Data = bz[:len(bz)-1]
_, err = queryDelegatorUnbondingDelegations(ctx, query, keeper) _, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// Query redelegation // Query redelegation
redelegationTokens := sdk.TokensFromConsensusPower(10) redelegationTokens := sdk.TokensFromConsensusPower(10)
_, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddress, _, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddress,
val2.OperatorAddress, redelegationTokens.ToDec()) val2.OperatorAddress, redelegationTokens.ToDec())
require.Nil(t, err) require.NoError(t, err)
redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddress, val2.OperatorAddress) redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddress, val2.OperatorAddress)
require.True(t, found) require.True(t, found)
bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(addrAcc2, val1.OperatorAddress, val2.OperatorAddress)) bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(addrAcc2, val1.OperatorAddress, val2.OperatorAddress))
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/redelegations", Path: "/custom/staking/redelegations",
@ -384,11 +401,11 @@ func TestQueryDelegation(t *testing.T) {
} }
res, err = queryRedelegations(ctx, query, keeper) res, err = queryRedelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var redelRes types.RedelegationResponses var redelRes types.RedelegationResponses
errRes = cdc.UnmarshalJSON(res, &redelRes) errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Len(t, redelRes, 1) require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress) require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress) require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -420,7 +437,7 @@ func TestQueryRedelegations(t *testing.T) {
// delegator redelegations // delegator redelegations
queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc2) queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes := cdc.MarshalJSON(queryDelegatorParams) bz, errRes := cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "/custom/staking/redelegations", Path: "/custom/staking/redelegations",
@ -428,11 +445,11 @@ func TestQueryRedelegations(t *testing.T) {
} }
res, err := queryRedelegations(ctx, query, keeper) res, err := queryRedelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
var redelRes types.RedelegationResponses var redelRes types.RedelegationResponses
errRes = cdc.UnmarshalJSON(res, &redelRes) errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Len(t, redelRes, 1) require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress) require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress) require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -442,7 +459,7 @@ func TestQueryRedelegations(t *testing.T) {
// validator redelegations // validator redelegations
queryValidatorParams := types.NewQueryValidatorParams(val1.GetOperator()) queryValidatorParams := types.NewQueryValidatorParams(val1.GetOperator())
bz, errRes = cdc.MarshalJSON(queryValidatorParams) bz, errRes = cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/redelegations", Path: "/custom/staking/redelegations",
@ -450,10 +467,10 @@ func TestQueryRedelegations(t *testing.T) {
} }
res, err = queryRedelegations(ctx, query, keeper) res, err = queryRedelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
errRes = cdc.UnmarshalJSON(res, &redelRes) errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes) require.NoError(t, errRes)
require.Len(t, redelRes, 1) require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress) require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress) require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -489,13 +506,13 @@ func TestQueryUnbondingDelegation(t *testing.T) {
// //
queryValidatorParams := types.NewQueryBondsParams(addrAcc1, val1.GetOperator()) queryValidatorParams := types.NewQueryBondsParams(addrAcc1, val1.GetOperator())
bz, errRes := cdc.MarshalJSON(queryValidatorParams) bz, errRes := cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation", Path: "/custom/staking/unbondingDelegation",
Data: bz, Data: bz,
} }
res, err := queryUnbondingDelegation(ctx, query, keeper) res, err := queryUnbondingDelegation(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
require.NotNil(t, res) require.NotNil(t, res)
var ubDel types.UnbondingDelegation var ubDel types.UnbondingDelegation
require.NoError(t, cdc.UnmarshalJSON(res, &ubDel)) require.NoError(t, cdc.UnmarshalJSON(res, &ubDel))
@ -508,26 +525,26 @@ func TestQueryUnbondingDelegation(t *testing.T) {
// //
queryValidatorParams = types.NewQueryBondsParams(addrAcc2, val1.GetOperator()) queryValidatorParams = types.NewQueryBondsParams(addrAcc2, val1.GetOperator())
bz, errRes = cdc.MarshalJSON(queryValidatorParams) bz, errRes = cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation", Path: "/custom/staking/unbondingDelegation",
Data: bz, Data: bz,
} }
_, err = queryUnbondingDelegation(ctx, query, keeper) _, err = queryUnbondingDelegation(ctx, query, keeper)
require.NotNil(t, err) require.Error(t, err)
// //
// found: query unbonding delegation by delegator and validator // found: query unbonding delegation by delegator and validator
// //
queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc1) queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc1)
bz, errRes = cdc.MarshalJSON(queryDelegatorParams) bz, errRes = cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/delegatorUnbondingDelegations", Path: "/custom/staking/delegatorUnbondingDelegations",
Data: bz, Data: bz,
} }
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper) res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
require.NotNil(t, res) require.NotNil(t, res)
var ubDels []types.UnbondingDelegation var ubDels []types.UnbondingDelegation
require.NoError(t, cdc.UnmarshalJSON(res, &ubDels)) require.NoError(t, cdc.UnmarshalJSON(res, &ubDels))
@ -540,14 +557,56 @@ func TestQueryUnbondingDelegation(t *testing.T) {
// //
queryDelegatorParams = types.NewQueryDelegatorParams(addrAcc2) queryDelegatorParams = types.NewQueryDelegatorParams(addrAcc2)
bz, errRes = cdc.MarshalJSON(queryDelegatorParams) bz, errRes = cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes) require.NoError(t, errRes)
query = abci.RequestQuery{ query = abci.RequestQuery{
Path: "/custom/staking/delegatorUnbondingDelegations", Path: "/custom/staking/delegatorUnbondingDelegations",
Data: bz, Data: bz,
} }
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper) res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err) require.NoError(t, err)
require.NotNil(t, res) require.NotNil(t, res)
require.NoError(t, cdc.UnmarshalJSON(res, &ubDels)) require.NoError(t, cdc.UnmarshalJSON(res, &ubDels))
require.Equal(t, 0, len(ubDels)) require.Equal(t, 0, len(ubDels))
} }
func TestQueryHistoricalInfo(t *testing.T) {
cdc := codec.New()
ctx, _, keeper, _ := CreateTestInput(t, false, 10000)
// Create Validators and Delegation
val1 := types.NewValidator(addrVal1, pk1, types.Description{})
val2 := types.NewValidator(addrVal2, pk2, types.Description{})
vals := []types.Validator{val1, val2}
keeper.SetValidator(ctx, val1)
keeper.SetValidator(ctx, val2)
header := abci.Header{
ChainID: "HelloChain",
Height: 5,
}
hi := types.NewHistoricalInfo(header, vals)
keeper.SetHistoricalInfo(ctx, 5, hi)
queryHistoricalParams := types.NewQueryHistoricalInfoParams(4)
bz, errRes := cdc.MarshalJSON(queryHistoricalParams)
require.NoError(t, errRes)
query := abci.RequestQuery{
Path: "/custom/staking/historicalInfo",
Data: bz,
}
res, err := queryHistoricalInfo(ctx, query, keeper)
require.Error(t, err, "Invalid query passed")
require.Nil(t, res, "Invalid query returned non-nil result")
queryHistoricalParams = types.NewQueryHistoricalInfoParams(5)
bz, errRes = cdc.MarshalJSON(queryHistoricalParams)
require.NoError(t, errRes)
query.Data = bz
res, err = queryHistoricalInfo(ctx, query, keeper)
require.NoError(t, err, "Valid query passed")
require.NotNil(t, res, "Valid query returned nil result")
var recv types.HistoricalInfo
require.NoError(t, cdc.UnmarshalJSON(res, &recv))
require.Equal(t, hi, recv, "HistoricalInfo query returned wrong result")
}

View File

@ -11,6 +11,62 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/cosmos-sdk/x/staking/types"
) )
// Calculate the ValidatorUpdates for the current block
// Called in each EndBlock
func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
// Calculate validator set changes.
//
// NOTE: ApplyAndReturnValidatorSetUpdates has to come before
// UnbondAllMatureValidatorQueue.
// This fixes a bug when the unbonding period is instant (is the case in
// some of the tests). The test expected the validator to be completely
// unbonded after the Endblocker (go from Bonded -> Unbonding during
// ApplyAndReturnValidatorSetUpdates and then Unbonding -> Unbonded during
// UnbondAllMatureValidatorQueue).
validatorUpdates := k.ApplyAndReturnValidatorSetUpdates(ctx)
// Unbond all mature validators from the unbonding queue.
k.UnbondAllMatureValidatorQueue(ctx)
// Remove all mature unbonding delegations from the ubd queue.
matureUnbonds := k.DequeueAllMatureUBDQueue(ctx, ctx.BlockHeader().Time)
for _, dvPair := range matureUnbonds {
err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddress, dvPair.ValidatorAddress)
if err != nil {
continue
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCompleteUnbonding,
sdk.NewAttribute(types.AttributeKeyValidator, dvPair.ValidatorAddress.String()),
sdk.NewAttribute(types.AttributeKeyDelegator, dvPair.DelegatorAddress.String()),
),
)
}
// Remove all mature redelegations from the red queue.
matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time)
for _, dvvTriplet := range matureRedelegations {
err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddress,
dvvTriplet.ValidatorSrcAddress, dvvTriplet.ValidatorDstAddress)
if err != nil {
continue
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCompleteRedelegation,
sdk.NewAttribute(types.AttributeKeyDelegator, dvvTriplet.DelegatorAddress.String()),
sdk.NewAttribute(types.AttributeKeySrcValidator, dvvTriplet.ValidatorSrcAddress.String()),
sdk.NewAttribute(types.AttributeKeyDstValidator, dvvTriplet.ValidatorDstAddress.String()),
),
)
}
return validatorUpdates
}
// Apply and return accumulated updates to the bonded validator set. Also, // Apply and return accumulated updates to the bonded validator set. Also,
// * Updates the active valset as keyed by LastValidatorPowerKey. // * Updates the active valset as keyed by LastValidatorPowerKey.
// * Updates the total power as keyed by LastTotalPowerKey. // * Updates the total power as keyed by LastTotalPowerKey.

View File

@ -165,7 +165,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
} }
// BeginBlock returns the begin blocker for the staking module. // 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 // EndBlock returns the end blocker for the staking module. It returns no validator
// updates. // updates.

View File

@ -50,7 +50,7 @@ func RandomizedGenState(simState *module.SimulationState) {
// NewSimulationManager constructor for this to work // NewSimulationManager constructor for this to work
simState.UnbondTime = unbondTime simState.UnbondTime = unbondTime
params := types.NewParams(simState.UnbondTime, maxValidators, 7, sdk.DefaultBondDenom) params := types.NewParams(simState.UnbondTime, maxValidators, 7, 3, sdk.DefaultBondDenom)
// validators & delegations // validators & delegations
var ( var (

View File

@ -282,3 +282,21 @@ The stored object as each key is an array of validator operator addresses from
which the validator object can be accessed. Typically it is expected that only which the validator object can be accessed. Typically it is expected that only
a single validator record will be associated with a given timestamp however it is possible a single validator record will be associated with a given timestamp however it is possible
that multiple validators exist in the queue at the same location. that multiple validators exist in the queue at the same location.
## HistoricalInfo
HistoricalInfo objects are stored and pruned at each block such that the staking keeper persists
the `n` most recent historical info defined by staking module parameter: `HistoricalEntries`.
```go
type HistoricalInfo struct {
Header abci.Header
ValSet []types.Validator
}
```
At each BeginBlock, the staking keeper will persist the current Header and the Validators that committed
the current block in a `HistoricalInfo` object. The Validators are sorted on their address to ensure that
they are in a determisnistic order.
The oldest HistoricalEntries will be pruned to ensure that there only exist the parameter-defined number of
historical entries.

View File

@ -0,0 +1,16 @@
<!--
order: 4
-->
# Begin-Block
Each abci begin block call, the historical info will get stored and pruned
according to the `HistoricalEntries` parameter.
## Historical Info Tracking
If the `HistoricalEntries` parameter is 0, then the `BeginBlock` performs a no-op.
Otherwise, the latest historical info is stored under the key `historicalInfoKey|height`, while any entries older than `height - HistoricalEntries` is deleted.
In most cases, this results in a single entry being pruned per block.
However, if the parameter `HistoricalEntries` has changed to a lower value there will be multiple entries in the store that must be pruned.

View File

@ -1,5 +1,5 @@
<!-- <!--
order: 4 order: 5
--> -->
# End-Block # End-Block

View File

@ -1,5 +1,5 @@
<!-- <!--
order: 5 order: 6
--> -->
# Hooks # Hooks

View File

@ -1,5 +1,5 @@
<!-- <!--
order: 6 order: 7
--> -->
# Events # Events

View File

@ -1,14 +0,0 @@
<!--
order: 7
-->
# Parameters
The staking module contains the following parameters:
| Key | Type | Example |
|---------------|------------------|-------------------|
| UnbondingTime | string (time ns) | "259200000000000" |
| MaxValidators | uint16 | 100 |
| KeyMaxEntries | uint16 | 7 |
| BondDenom | string | "uatom" |

View File

@ -0,0 +1,15 @@
<!--
order: 8
-->
# Parameters
The staking module contains the following parameters:
| Key | Type | Example |
|-------------------|------------------|-------------------|
| UnbondingTime | string (time ns) | "259200000000000" |
| MaxValidators | uint16 | 100 |
| KeyMaxEntries | uint16 | 7 |
| HistoricalEntries | uint16 | 3 |
| BondDenom | string | "uatom" |

View File

@ -32,6 +32,7 @@ network.
- [UnbondingDelegation](01_state.md#unbondingdelegation) - [UnbondingDelegation](01_state.md#unbondingdelegation)
- [Redelegation](01_state.md#redelegation) - [Redelegation](01_state.md#redelegation)
- [Queues](01_state.md#queues) - [Queues](01_state.md#queues)
- [HistoricalInfo](01_state.md#historicalinfo)
2. **[State Transitions](02_state_transitions.md)** 2. **[State Transitions](02_state_transitions.md)**
- [Validators](02_state_transitions.md#validators) - [Validators](02_state_transitions.md#validators)
- [Delegations](02_state_transitions.md#delegations) - [Delegations](02_state_transitions.md#delegations)
@ -42,11 +43,13 @@ network.
- [MsgDelegate](03_messages.md#msgdelegate) - [MsgDelegate](03_messages.md#msgdelegate)
- [MsgBeginUnbonding](03_messages.md#msgbeginunbonding) - [MsgBeginUnbonding](03_messages.md#msgbeginunbonding)
- [MsgBeginRedelegate](03_messages.md#msgbeginredelegate) - [MsgBeginRedelegate](03_messages.md#msgbeginredelegate)
4. **[End-Block ](04_end_block.md)** 4. **[Begin-Block](04_begin_block.md)**
- [Validator Set Changes](04_end_block.md#validator-set-changes) - [Historical Info Tracking](04_begin_block.md#historical-info-tracking)
- [Queues ](04_end_block.md#queues-) 4. **[End-Block ](05_end_block.md)**
5. **[Hooks](05_hooks.md)** - [Validator Set Changes](05_end_block.md#validator-set-changes)
6. **[Events](06_events.md)** - [Queues ](05_end_block.md#queues-)
- [EndBlocker](06_events.md#endblocker) 5. **[Hooks](06_hooks.md)**
- [Handlers](06_events.md#handlers) 6. **[Events](07_events.md)**
7. **[Parameters](07_params.md)** - [EndBlocker](07_events.md#endblocker)
- [Handlers](07_events.md#handlers)
7. **[Parameters](08_params.md)**

View File

@ -14,14 +14,15 @@ type CodeType = sdk.CodeType
const ( const (
DefaultCodespace sdk.CodespaceType = ModuleName DefaultCodespace sdk.CodespaceType = ModuleName
CodeInvalidValidator CodeType = 101 CodeInvalidValidator CodeType = 101
CodeInvalidDelegation CodeType = 102 CodeInvalidDelegation CodeType = 102
CodeInvalidInput CodeType = 103 CodeInvalidInput CodeType = 103
CodeValidatorJailed CodeType = 104 CodeValidatorJailed CodeType = 104
CodeInvalidAddress CodeType = sdk.CodeInvalidAddress CodeInvalidHistoricalInfo CodeType = 105
CodeUnauthorized CodeType = sdk.CodeUnauthorized CodeInvalidAddress CodeType = sdk.CodeInvalidAddress
CodeInternal CodeType = sdk.CodeInternal CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
) )
//validator //validator
@ -212,3 +213,11 @@ func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error {
func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") return sdk.NewError(codespace, CodeInvalidValidator, "missing signature")
} }
func ErrInvalidHistoricalInfo(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidHistoricalInfo, "invalid historical info")
}
func ErrNoHistoricalInfo(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidHistoricalInfo, "no historical info found")
}

View File

@ -0,0 +1,57 @@
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"
)
// HistoricalInfo contains the historical information that gets stored at each height
type HistoricalInfo struct {
Header abci.Header `json:"header" yaml:"header"`
ValSet []Validator `json:"valset" yaml:"valset"`
}
// NewHistoricalInfo will create a historical information struct from header and valset
// it will first sort valset before inclusion into historical info
func NewHistoricalInfo(header abci.Header, valSet []Validator) HistoricalInfo {
sort.Sort(Validators(valSet))
return HistoricalInfo{
Header: header,
ValSet: valSet,
}
}
// MustMarshalHistoricalInfo wll marshal historical info and panic on error
func MustMarshalHistoricalInfo(cdc *codec.Codec, hi HistoricalInfo) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(hi)
}
// MustUnmarshalHistoricalInfo wll unmarshal historical info and panic on error
func MustUnmarshalHistoricalInfo(cdc *codec.Codec, value []byte) HistoricalInfo {
hi, err := UnmarshalHistoricalInfo(cdc, value)
if err != nil {
panic(err)
}
return hi
}
// UnmarshalHistoricalInfo will unmarshal historical info and return any error
func UnmarshalHistoricalInfo(cdc *codec.Codec, value []byte) (hi HistoricalInfo, err error) {
err = cdc.UnmarshalBinaryLengthPrefixed(value, &hi)
return hi, err
}
// ValidateBasic will ensure HistoricalInfo is not nil and sorted
func ValidateBasic(hi HistoricalInfo) error {
if len(hi.ValSet) == 0 {
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
}

View File

@ -0,0 +1,67 @@
package types
import (
"math/rand"
"sort"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)
var (
validators = []Validator{
NewValidator(valAddr1, pk1, Description{}),
NewValidator(valAddr2, pk2, Description{}),
NewValidator(valAddr3, pk3, Description{}),
}
header = abci.Header{
ChainID: "hello",
Height: 5,
}
)
func TestHistoricalInfo(t *testing.T) {
hi := NewHistoricalInfo(header, validators)
require.True(t, sort.IsSorted(Validators(hi.ValSet)), "Validators are not sorted")
var value []byte
require.NotPanics(t, func() {
value = MustMarshalHistoricalInfo(ModuleCdc, hi)
})
require.NotNil(t, value, "Marshalled HistoricalInfo is nil")
recv, err := UnmarshalHistoricalInfo(ModuleCdc, value)
require.Nil(t, err, "Unmarshalling HistoricalInfo failed")
require.Equal(t, hi, recv, "Unmarshalled HistoricalInfo is different from original")
require.True(t, sort.IsSorted(Validators(hi.ValSet)), "Validators are not sorted")
}
func TestValidateBasic(t *testing.T) {
hi := HistoricalInfo{
Header: header,
}
err := ValidateBasic(hi)
require.Error(t, err, "ValidateBasic passed on nil ValSet")
// Ensure validators are not sorted
for sort.IsSorted(Validators(validators)) {
rand.Shuffle(len(validators), func(i, j int) {
it := validators[i]
validators[i] = validators[j]
validators[j] = it
})
}
hi = HistoricalInfo{
Header: header,
ValSet: validators,
}
err = ValidateBasic(hi)
require.Error(t, err, "ValidateBasic passed on unsorted ValSet")
hi = NewHistoricalInfo(header, validators)
err = ValidateBasic(hi)
require.NoError(t, err, "ValidateBasic failed on valid HistoricalInfo")
}

View File

@ -2,6 +2,7 @@ package types
import ( import (
"encoding/binary" "encoding/binary"
"strconv"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -45,6 +46,8 @@ var (
UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue
RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue
ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue
HistoricalInfoKey = []byte{0x50} // prefix for the historical info
) )
// gets the key for the validator with address // gets the key for the validator with address
@ -278,3 +281,10 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr
GetREDsToValDstIndexKey(valDstAddr), GetREDsToValDstIndexKey(valDstAddr),
delAddr.Bytes()...) delAddr.Bytes()...)
} }
//________________________________________________________________________________
// GetHistoricalInfoKey gets the key for the historical info
func GetHistoricalInfoKey(height int64) []byte {
return append(HistoricalInfoKey, []byte(strconv.FormatInt(height, 10))...)
}

View File

@ -24,35 +24,42 @@ const (
// Default maximum entries in a UBD/RED pair // Default maximum entries in a UBD/RED pair
DefaultMaxEntries uint16 = 7 DefaultMaxEntries uint16 = 7
// DefaultHistorical entries is 0 since it must only be non-zero for
// IBC connected chains
DefaultHistoricalEntries uint16 = 0
) )
// nolint - Keys for parameter access // nolint - Keys for parameter access
var ( var (
KeyUnbondingTime = []byte("UnbondingTime") KeyUnbondingTime = []byte("UnbondingTime")
KeyMaxValidators = []byte("MaxValidators") KeyMaxValidators = []byte("MaxValidators")
KeyMaxEntries = []byte("KeyMaxEntries") KeyMaxEntries = []byte("KeyMaxEntries")
KeyBondDenom = []byte("BondDenom") KeyBondDenom = []byte("BondDenom")
KeyHistoricalEntries = []byte("HistoricalEntries")
) )
var _ params.ParamSet = (*Params)(nil) var _ params.ParamSet = (*Params)(nil)
// Params defines the high level settings for staking // Params defines the high level settings for staking
type Params struct { type Params struct {
UnbondingTime time.Duration `json:"unbonding_time" yaml:"unbonding_time"` // time duration of unbonding 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) 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) MaxEntries uint16 `json:"max_entries" yaml:"max_entries"` // max entries for either unbonding delegation or redelegation (per pair/trio)
BondDenom string `json:"bond_denom" yaml:"bond_denom"` // bondable coin denomination HistoricalEntries uint16 `json:"historical_entries" yaml:"historical_entries"` // number of historical entries to persist
BondDenom string `json:"bond_denom" yaml:"bond_denom"` // bondable coin denomination
} }
// NewParams creates a new Params instance // 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 { bondDenom string) Params {
return Params{ return Params{
UnbondingTime: unbondingTime, UnbondingTime: unbondingTime,
MaxValidators: maxValidators, MaxValidators: maxValidators,
MaxEntries: maxEntries, MaxEntries: maxEntries,
BondDenom: bondDenom, HistoricalEntries: historicalEntries,
BondDenom: bondDenom,
} }
} }
@ -62,6 +69,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
params.NewParamSetPair(KeyUnbondingTime, &p.UnbondingTime, validateUnbondingTime), params.NewParamSetPair(KeyUnbondingTime, &p.UnbondingTime, validateUnbondingTime),
params.NewParamSetPair(KeyMaxValidators, &p.MaxValidators, validateMaxValidators), params.NewParamSetPair(KeyMaxValidators, &p.MaxValidators, validateMaxValidators),
params.NewParamSetPair(KeyMaxEntries, &p.MaxEntries, validateMaxEntries), params.NewParamSetPair(KeyMaxEntries, &p.MaxEntries, validateMaxEntries),
params.NewParamSetPair(KeyHistoricalEntries, &p.HistoricalEntries, validateHistoricalEntries),
params.NewParamSetPair(KeyBondDenom, &p.BondDenom, validateBondDenom), params.NewParamSetPair(KeyBondDenom, &p.BondDenom, validateBondDenom),
} }
} }
@ -76,17 +84,18 @@ func (p Params) Equal(p2 Params) bool {
// DefaultParams returns a default set of parameters. // DefaultParams returns a default set of parameters.
func DefaultParams() Params { 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. // String returns a human readable string representation of the parameters.
func (p Params) String() string { func (p Params) String() string {
return fmt.Sprintf(`Params: return fmt.Sprintf(`Params:
Unbonding Time: %s Unbonding Time: %s
Max Validators: %d Max Validators: %d
Max Entries: %d Max Entries: %d
Bonded Coin Denom: %s`, p.UnbondingTime, Historical Entries: %d
p.MaxValidators, p.MaxEntries, p.BondDenom) 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 // unmarshal the current staking params value from store key or panic
@ -164,6 +173,15 @@ func validateMaxEntries(i interface{}) error {
return nil return nil
} }
func validateHistoricalEntries(i interface{}) error {
_, ok := i.(uint16)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
return nil
}
func validateBondDenom(i interface{}) error { func validateBondDenom(i interface{}) error {
v, ok := i.(string) v, ok := i.(string)
if !ok { if !ok {

View File

@ -20,6 +20,7 @@ const (
QueryDelegatorValidator = "delegatorValidator" QueryDelegatorValidator = "delegatorValidator"
QueryPool = "pool" QueryPool = "pool"
QueryParameters = "parameters" QueryParameters = "parameters"
QueryHistoricalInfo = "historicalInfo"
) )
// defines the params for the following queries: // defines the params for the following queries:
@ -96,3 +97,14 @@ type QueryValidatorsParams struct {
func NewQueryValidatorsParams(page, limit int, status string) QueryValidatorsParams { func NewQueryValidatorsParams(page, limit int, status string) QueryValidatorsParams {
return QueryValidatorsParams{page, limit, status} return QueryValidatorsParams{page, limit, status}
} }
// QueryHistoricalInfoParams defines the params for the following queries:
// - 'custom/staking/historicalInfo'
type QueryHistoricalInfoParams struct {
Height int64
}
// NewQueryHistoricalInfoParams creates a new QueryHistoricalInfoParams instance
func NewQueryHistoricalInfoParams(height int64) QueryHistoricalInfoParams {
return QueryHistoricalInfoParams{height}
}

View File

@ -3,6 +3,7 @@ package types
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
@ -102,6 +103,28 @@ func (v Validators) ToSDKValidators() (validators []exported.ValidatorI) {
return validators return validators
} }
// Sort Validators sorts validator array in ascending operator address order
func (v Validators) Sort() {
sort.Sort(v)
}
// Implements sort interface
func (v Validators) Len() int {
return len(v)
}
// Implements sort interface
func (v Validators) Less(i, j int) bool {
return bytes.Compare(v[i].OperatorAddress, v[j].OperatorAddress) == -1
}
// Implements sort interface
func (v Validators) Swap(i, j int) {
it := v[i]
v[i] = v[j]
v[j] = it
}
// NewValidator - initialize a new validator // NewValidator - initialize a new validator
func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator {
return Validator{ return Validator{

View File

@ -2,10 +2,14 @@ package types
import ( import (
"fmt" "fmt"
"math/rand"
"reflect"
"sort"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -303,3 +307,31 @@ func TestValidatorMarshalYAML(t *testing.T) {
`, validator.OperatorAddress.String(), bechifiedPub) `, validator.OperatorAddress.String(), bechifiedPub)
require.Equal(t, want, string(bs)) require.Equal(t, want, string(bs))
} }
// Check that sort will create deterministic ordering of validators
func TestValidatorsSortDeterminism(t *testing.T) {
vals := make([]Validator, 10)
sortedVals := make([]Validator, 10)
// Create random validator slice
for i := range vals {
pk := ed25519.GenPrivKey().PubKey()
vals[i] = NewValidator(sdk.ValAddress(pk.Address()), pk, Description{})
}
// Save sorted copy
sort.Sort(Validators(vals))
copy(sortedVals, vals)
// Randomly shuffle validators, sort, and check it is equal to original sort
for i := 0; i < 10; i++ {
rand.Shuffle(10, func(i, j int) {
it := vals[i]
vals[i] = vals[j]
vals[j] = it
})
Validators(vals).Sort()
require.True(t, reflect.DeepEqual(sortedVals, vals), "Validator sort returned different slices")
}
}