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

View File

@ -2,6 +2,7 @@ package cli
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
@ -35,6 +36,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
GetCmdQueryValidatorDelegations(queryRoute, cdc),
GetCmdQueryValidatorUnbondingDelegations(queryRoute, cdc),
GetCmdQueryValidatorRedelegations(queryRoute, cdc),
GetCmdQueryHistoricalInfo(queryRoute, cdc),
GetCmdQueryParams(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.
func GetCmdQueryPool(storeName string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{

View File

@ -3,6 +3,7 @@ package rest
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gorilla/mux"
@ -86,6 +87,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
validatorUnbondingDelegationsHandlerFn(cliCtx),
).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
r.HandleFunc(
"/staking/pool",
@ -313,6 +320,36 @@ func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext) http.Hand
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
func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

View File

@ -4,7 +4,6 @@ import (
"fmt"
"time"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/common"
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,
// 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
}
// 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)
@ -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),
)
}

View File

@ -38,6 +38,8 @@ func NewQuerier(k Keeper) sdk.Querier {
return queryDelegatorValidators(ctx, req, k)
case types.QueryDelegatorValidator:
return queryDelegatorValidator(ctx, req, k)
case types.QueryHistoricalInfo:
return queryHistoricalInfo(ctx, req, k)
case types.QueryPool:
return queryPool(ctx, k)
case types.QueryParameters:
@ -327,6 +329,27 @@ 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) {
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) {
bondDenom := k.BondDenom(ctx)
bondedPool := k.GetBondedPool(ctx)

View File

@ -31,6 +31,13 @@ func TestNewQuerier(t *testing.T) {
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{
Path: "",
Data: []byte{},
@ -39,53 +46,63 @@ func TestNewQuerier(t *testing.T) {
querier := NewQuerier(keeper)
bz, err := querier(ctx, []string{"other"}, query)
require.NotNil(t, err)
require.Error(t, err)
require.Nil(t, bz)
_, err = querier(ctx, []string{"pool"}, query)
require.Nil(t, err)
require.NoError(t, err)
_, err = querier(ctx, []string{"parameters"}, query)
require.Nil(t, err)
require.NoError(t, err)
queryValParams := types.NewQueryValidatorParams(addrVal1)
bz, errRes := cdc.MarshalJSON(queryValParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query.Path = "/custom/staking/validator"
query.Data = bz
_, err = querier(ctx, []string{"validator"}, query)
require.Nil(t, err)
require.NoError(t, err)
_, err = querier(ctx, []string{"validatorDelegations"}, query)
require.Nil(t, err)
require.NoError(t, err)
_, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query)
require.Nil(t, err)
require.NoError(t, err)
queryDelParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes = cdc.MarshalJSON(queryDelParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query.Path = "/custom/staking/validator"
query.Data = bz
_, err = querier(ctx, []string{"delegatorDelegations"}, query)
require.Nil(t, err)
require.NoError(t, err)
_, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query)
require.Nil(t, err)
require.NoError(t, err)
_, err = querier(ctx, []string{"delegatorValidators"}, query)
require.Nil(t, err)
require.NoError(t, err)
bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(nil, nil, nil))
require.Nil(t, errRes)
require.NoError(t, errRes)
query.Data = bz
_, 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) {
@ -94,21 +111,21 @@ func TestQueryParametersPool(t *testing.T) {
bondDenom := sdk.DefaultBondDenom
res, err := queryParameters(ctx, keeper)
require.Nil(t, err)
require.NoError(t, err)
var params types.Params
errRes := cdc.UnmarshalJSON(res, &params)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, keeper.GetParams(ctx), params)
res, err = queryPool(ctx, keeper)
require.Nil(t, err)
require.NoError(t, err)
var pool types.Pool
bondedPool := keeper.GetBondedPool(ctx)
notBondedPool := keeper.GetNotBondedPool(ctx)
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, notBondedPool.GetCoins().AmountOf(bondDenom), pool.NotBondedTokens)
}
@ -138,7 +155,7 @@ func TestQueryValidators(t *testing.T) {
for i, s := range status {
queryValsParams := types.NewQueryValidatorsParams(1, int(params.MaxValidators), s.String())
bz, err := cdc.MarshalJSON(queryValsParams)
require.Nil(t, err)
require.NoError(t, err)
req := abci.RequestQuery{
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)
require.Nil(t, err)
require.NoError(t, err)
var validatorsResp []types.Validator
err = cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, 1, len(validatorsResp))
require.ElementsMatch(t, validators[i].OperatorAddress, validatorsResp[0].OperatorAddress)
@ -160,18 +177,18 @@ func TestQueryValidators(t *testing.T) {
// Query each validator
queryParams := types.NewQueryValidatorParams(addrVal1)
bz, err := cdc.MarshalJSON(queryParams)
require.Nil(t, err)
require.NoError(t, err)
query := abci.RequestQuery{
Path: "/custom/staking/validator",
Data: bz,
}
res, err := queryValidator(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var validator types.Validator
err = cdc.UnmarshalJSON(res, &validator)
require.Nil(t, err)
require.NoError(t, err)
require.Equal(t, queriedValidators[0], validator)
}
@ -199,7 +216,7 @@ func TestQueryDelegation(t *testing.T) {
// Query Delegator bonded validators
queryParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes := cdc.MarshalJSON(queryParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query := abci.RequestQuery{
Path: "/custom/staking/delegatorValidators",
@ -209,11 +226,11 @@ func TestQueryDelegation(t *testing.T) {
delValidators := keeper.GetDelegatorValidators(ctx, addrAcc2, params.MaxValidators)
res, err := queryDelegatorValidators(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var validatorsResp []types.Validator
errRes = cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, len(delValidators), len(validatorsResp))
require.ElementsMatch(t, delValidators, validatorsResp)
@ -222,12 +239,12 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1]
_, err = queryDelegatorValidators(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
// Query bonded validator
queryBondParams := types.NewQueryBondsParams(addrAcc2, addrVal1)
bz, errRes = cdc.MarshalJSON(queryBondParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/delegatorValidator",
@ -235,11 +252,11 @@ func TestQueryDelegation(t *testing.T) {
}
res, err = queryDelegatorValidator(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var validator types.Validator
errRes = cdc.UnmarshalJSON(res, &validator)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, delValidators[0], validator)
@ -247,7 +264,7 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1]
_, err = queryDelegatorValidator(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
// Query delegation
@ -260,11 +277,11 @@ func TestQueryDelegation(t *testing.T) {
require.True(t, found)
res, err = queryDelegation(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var delegationRes types.DelegationResponse
errRes = cdc.UnmarshalJSON(res, &delegationRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, delegation.ValidatorAddress, delegationRes.ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegationRes.DelegatorAddress)
@ -277,11 +294,11 @@ func TestQueryDelegation(t *testing.T) {
}
res, err = queryDelegatorDelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var delegatorDelegations types.DelegationResponses
errRes = cdc.UnmarshalJSON(res, &delegatorDelegations)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Len(t, delegatorDelegations, 1)
require.Equal(t, delegation.ValidatorAddress, delegatorDelegations[0].ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegatorDelegations[0].DelegatorAddress)
@ -291,12 +308,12 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1]
_, err = queryDelegation(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
// Query validator delegations
bz, errRes = cdc.MarshalJSON(types.NewQueryValidatorParams(addrVal1))
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "custom/staking/validatorDelegations",
@ -304,11 +321,11 @@ func TestQueryDelegation(t *testing.T) {
}
res, err = queryValidatorDelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var delegationsRes types.DelegationResponses
errRes = cdc.UnmarshalJSON(res, &delegationsRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Len(t, delegatorDelegations, 1)
require.Equal(t, delegation.ValidatorAddress, delegationsRes[0].ValidatorAddress)
require.Equal(t, delegation.DelegatorAddress, delegationsRes[0].DelegatorAddress)
@ -317,11 +334,11 @@ func TestQueryDelegation(t *testing.T) {
// Query unbonging delegation
unbondingTokens := sdk.TokensFromConsensusPower(10)
_, err = keeper.Undelegate(ctx, addrAcc2, val1.OperatorAddress, unbondingTokens.ToDec())
require.Nil(t, err)
require.NoError(t, err)
queryBondParams = types.NewQueryBondsParams(addrAcc2, addrVal1)
bz, errRes = cdc.MarshalJSON(queryBondParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation",
@ -332,11 +349,11 @@ func TestQueryDelegation(t *testing.T) {
require.True(t, found)
res, err = queryUnbondingDelegation(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var unbondRes types.UnbondingDelegation
errRes = cdc.UnmarshalJSON(res, &unbondRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, unbond, unbondRes)
@ -344,7 +361,7 @@ func TestQueryDelegation(t *testing.T) {
query.Data = bz[:len(bz)-1]
_, err = queryUnbondingDelegation(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
// Query Delegator Delegations
@ -354,29 +371,29 @@ func TestQueryDelegation(t *testing.T) {
}
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var delegatorUbds []types.UnbondingDelegation
errRes = cdc.UnmarshalJSON(res, &delegatorUbds)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Equal(t, unbond, delegatorUbds[0])
// error unknown request
query.Data = bz[:len(bz)-1]
_, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
// Query redelegation
redelegationTokens := sdk.TokensFromConsensusPower(10)
_, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddress,
val2.OperatorAddress, redelegationTokens.ToDec())
require.Nil(t, err)
require.NoError(t, err)
redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddress, val2.OperatorAddress)
require.True(t, found)
bz, errRes = cdc.MarshalJSON(types.NewQueryRedelegationParams(addrAcc2, val1.OperatorAddress, val2.OperatorAddress))
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/redelegations",
@ -384,11 +401,11 @@ func TestQueryDelegation(t *testing.T) {
}
res, err = queryRedelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var redelRes types.RedelegationResponses
errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -420,7 +437,7 @@ func TestQueryRedelegations(t *testing.T) {
// delegator redelegations
queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc2)
bz, errRes := cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query := abci.RequestQuery{
Path: "/custom/staking/redelegations",
@ -428,11 +445,11 @@ func TestQueryRedelegations(t *testing.T) {
}
res, err := queryRedelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
var redelRes types.RedelegationResponses
errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -442,7 +459,7 @@ func TestQueryRedelegations(t *testing.T) {
// validator redelegations
queryValidatorParams := types.NewQueryValidatorParams(val1.GetOperator())
bz, errRes = cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/redelegations",
@ -450,10 +467,10 @@ func TestQueryRedelegations(t *testing.T) {
}
res, err = queryRedelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
errRes = cdc.UnmarshalJSON(res, &redelRes)
require.Nil(t, errRes)
require.NoError(t, errRes)
require.Len(t, redelRes, 1)
require.Equal(t, redel.DelegatorAddress, redelRes[0].DelegatorAddress)
require.Equal(t, redel.ValidatorSrcAddress, redelRes[0].ValidatorSrcAddress)
@ -489,13 +506,13 @@ func TestQueryUnbondingDelegation(t *testing.T) {
//
queryValidatorParams := types.NewQueryBondsParams(addrAcc1, val1.GetOperator())
bz, errRes := cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query := abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation",
Data: bz,
}
res, err := queryUnbondingDelegation(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
require.NotNil(t, res)
var ubDel types.UnbondingDelegation
require.NoError(t, cdc.UnmarshalJSON(res, &ubDel))
@ -508,26 +525,26 @@ func TestQueryUnbondingDelegation(t *testing.T) {
//
queryValidatorParams = types.NewQueryBondsParams(addrAcc2, val1.GetOperator())
bz, errRes = cdc.MarshalJSON(queryValidatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/unbondingDelegation",
Data: bz,
}
_, err = queryUnbondingDelegation(ctx, query, keeper)
require.NotNil(t, err)
require.Error(t, err)
//
// found: query unbonding delegation by delegator and validator
//
queryDelegatorParams := types.NewQueryDelegatorParams(addrAcc1)
bz, errRes = cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/delegatorUnbondingDelegations",
Data: bz,
}
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
require.NotNil(t, res)
var ubDels []types.UnbondingDelegation
require.NoError(t, cdc.UnmarshalJSON(res, &ubDels))
@ -540,14 +557,56 @@ func TestQueryUnbondingDelegation(t *testing.T) {
//
queryDelegatorParams = types.NewQueryDelegatorParams(addrAcc2)
bz, errRes = cdc.MarshalJSON(queryDelegatorParams)
require.Nil(t, errRes)
require.NoError(t, errRes)
query = abci.RequestQuery{
Path: "/custom/staking/delegatorUnbondingDelegations",
Data: bz,
}
res, err = queryDelegatorUnbondingDelegations(ctx, query, keeper)
require.Nil(t, err)
require.NoError(t, err)
require.NotNil(t, res)
require.NoError(t, cdc.UnmarshalJSON(res, &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"
)
// 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,
// * Updates the active valset as keyed by LastValidatorPowerKey.
// * 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.
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.

View File

@ -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 (

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
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.
## 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

View File

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

View File

@ -1,5 +1,5 @@
<!--
order: 6
order: 7
-->
# 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)
- [Redelegation](01_state.md#redelegation)
- [Queues](01_state.md#queues)
- [HistoricalInfo](01_state.md#historicalinfo)
2. **[State Transitions](02_state_transitions.md)**
- [Validators](02_state_transitions.md#validators)
- [Delegations](02_state_transitions.md#delegations)
@ -42,11 +43,13 @@ network.
- [MsgDelegate](03_messages.md#msgdelegate)
- [MsgBeginUnbonding](03_messages.md#msgbeginunbonding)
- [MsgBeginRedelegate](03_messages.md#msgbeginredelegate)
4. **[End-Block ](04_end_block.md)**
- [Validator Set Changes](04_end_block.md#validator-set-changes)
- [Queues ](04_end_block.md#queues-)
5. **[Hooks](05_hooks.md)**
6. **[Events](06_events.md)**
- [EndBlocker](06_events.md#endblocker)
- [Handlers](06_events.md#handlers)
7. **[Parameters](07_params.md)**
4. **[Begin-Block](04_begin_block.md)**
- [Historical Info Tracking](04_begin_block.md#historical-info-tracking)
4. **[End-Block ](05_end_block.md)**
- [Validator Set Changes](05_end_block.md#validator-set-changes)
- [Queues ](05_end_block.md#queues-)
5. **[Hooks](06_hooks.md)**
6. **[Events](07_events.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 (
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
@ -212,3 +213,11 @@ 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 {
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 (
"encoding/binary"
"strconv"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -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
)
// gets the key for the validator with address
@ -278,3 +281,10 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr
GetREDsToValDstIndexKey(valDstAddr),
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
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
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)
BondDenom string `json:"bond_denom" yaml:"bond_denom"` // bondable coin denomination
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
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,
}
}
@ -62,6 +69,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
params.NewParamSetPair(KeyUnbondingTime, &p.UnbondingTime, validateUnbondingTime),
params.NewParamSetPair(KeyMaxValidators, &p.MaxValidators, validateMaxValidators),
params.NewParamSetPair(KeyMaxEntries, &p.MaxEntries, validateMaxEntries),
params.NewParamSetPair(KeyHistoricalEntries, &p.HistoricalEntries, validateHistoricalEntries),
params.NewParamSetPair(KeyBondDenom, &p.BondDenom, validateBondDenom),
}
}
@ -76,17 +84,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
@ -164,6 +173,15 @@ func validateMaxEntries(i interface{}) error {
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 {
v, ok := i.(string)
if !ok {

View File

@ -20,6 +20,7 @@ const (
QueryDelegatorValidator = "delegatorValidator"
QueryPool = "pool"
QueryParameters = "parameters"
QueryHistoricalInfo = "historicalInfo"
)
// defines the params for the following queries:
@ -96,3 +97,14 @@ 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 int64
}
// NewQueryHistoricalInfoParams creates a new QueryHistoricalInfoParams instance
func NewQueryHistoricalInfoParams(height int64) QueryHistoricalInfoParams {
return QueryHistoricalInfoParams{height}
}

View File

@ -3,6 +3,7 @@ package types
import (
"bytes"
"fmt"
"sort"
"strings"
"time"
@ -102,6 +103,28 @@ func (v Validators) ToSDKValidators() (validators []exported.ValidatorI) {
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
func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator {
return Validator{

View File

@ -2,10 +2,14 @@ package types
import (
"fmt"
"math/rand"
"reflect"
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
tmtypes "github.com/tendermint/tendermint/types"
yaml "gopkg.in/yaml.v2"
@ -303,3 +307,31 @@ func TestValidatorMarshalYAML(t *testing.T) {
`, validator.OperatorAddress.String(), bechifiedPub)
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")
}
}