Skip to content

Commit

Permalink
Merge PR cosmos#5380: ADR 17 Implementation: Historical Module
Browse files Browse the repository at this point in the history
  • Loading branch information
AdityaSripal authored and alexanderbez committed Dec 18, 2019
1 parent 908fd2b commit fca4cbe
Show file tree
Hide file tree
Showing 30 changed files with 827 additions and 175 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
18 changes: 18 additions & 0 deletions x/staking/abci.go
Original file line number Diff line number Diff line change
@@ -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)
}
13 changes: 13 additions & 0 deletions x/staking/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
46 changes: 46 additions & 0 deletions x/staking/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"strconv"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -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))...)

Expand Down Expand Up @@ -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{
Expand Down
37 changes: 37 additions & 0 deletions x/staking/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rest
import (
"fmt"
"net/http"
"strconv"
"strings"

"github.com/gorilla/mux"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down
56 changes: 0 additions & 56 deletions x/staking/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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

Expand Down
71 changes: 71 additions & 0 deletions x/staking/keeper/historical_info.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit fca4cbe

Please sign in to comment.