Merge PR #5240: x/evidence module implementation

This commit is contained in:
Alexander Bezobchuk 2019-11-06 14:08:02 -07:00 committed by GitHub
parent 82a2c5d6d8
commit 95ddc242ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2373 additions and 78 deletions

View File

@ -81,6 +81,7 @@ increased significantly due to modular `AnteHandler` support. Increase GasLimit
### Features
* (x/evidence) [\#5240](https://github.com/cosmos/cosmos-sdk/pull/5240) Initial implementation of the `x/evidence` module.
* (cli) [\#5212](https://github.com/cosmos/cosmos-sdk/issues/5212) The `q gov proposals` command now supports pagination.
* (store) [\#4724](https://github.com/cosmos/cosmos-sdk/issues/4724) Multistore supports substore migrations upon load. New `rootmulti.Store.LoadLatestVersionAndUpgrade` method in
`Baseapp` supports `StoreLoader` to enable various upgrade strategies. It no

View File

@ -3,10 +3,11 @@
## Changelog
- 2019 July 31: Initial draft
- 2019 October 24: Initial implementation
## Status
Proposed
Accepted
## Context
@ -55,7 +56,8 @@ type Evidence interface {
Route() string
Type() string
String() string
ValidateBasic() Error
Hash() HexBytes
ValidateBasic() error
// The consensus address of the malicious validator at time of infraction
GetConsensusAddress() ConsAddress
@ -78,7 +80,7 @@ the `x/evidence` module. It accomplishes this through the `Router` implementatio
```go
type Router interface {
AddRoute(r string, h Handler)
AddRoute(r string, h Handler) Router
HasRoute(r string) bool
GetRoute(path string) Handler
Seal()
@ -97,7 +99,7 @@ necessary in order for the `Handler` to make the necessary state transitions.
If no error is returned, the `Evidence` is considered valid.
```go
type Handler func(Context, Evidence) Error
type Handler func(Context, Evidence) error
```
### Submission
@ -128,7 +130,7 @@ the module's router and invoking the corresponding `Handler` which may include
slashing and jailing the validator. Upon success, the submitted evidence is persisted.
```go
func (k Keeper) SubmitEvidence(ctx Context, evidence Evidence) Error {
func (k Keeper) SubmitEvidence(ctx Context, evidence Evidence) error {
handler := keeper.router.GetRoute(evidence.Route())
if err := handler(ctx, evidence); err != nil {
return ErrInvalidEvidence(keeper.codespace, err)
@ -177,3 +179,4 @@ due to the inability to introduce the new evidence type's corresponding handler
- [ICS](https://github.com/cosmos/ics)
- [IBC Architecture](https://github.com/cosmos/ics/blob/master/ibc/1_IBC_ARCHITECTURE.md)
- [Tendermint Fork Accountability](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/fork-accountability.md)

View File

@ -1,78 +1,75 @@
# Auth
# Modules
## Auth
The `x/auth` modules is used for accounts
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth)
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/auth)
See the [specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/auth)
# Bank
## Bank
The `x/bank` module is for transferring coins between accounts.
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank).
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/bank)
See the [specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/bank)
# Stake
## Staking
The `x/staking` module is for Cosmos Delegated-Proof-of-Stake.
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/staking).
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/staking)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/staking)
See the
[specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/staking)
# Slashing
## Slashing
The `x/slashing` module is for Cosmos Delegated-Proof-of-Stake.
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/slashing)
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/slashing)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/slashing)
See the
[specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/slashing)
# Distribution
## Distribution
The `x/distribution` module is for distributing fees and inflation across bonded
stakeholders.
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/distribution)
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/distribution)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/distribution)
See the
[specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/distribution)
# Governance
## Governance
The `x/gov` module is for bonded stakeholders to make proposals and vote on them.
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/gov)
See the
[specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/governance)
- [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/gov)
- [Specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/governance)
To keep up with the current status of IBC, follow and contribute to [ICS](https://github.com/cosmos/ics)
# Crisis
## Crisis
The `x/crisis` module is for halting the blockchain under certain circumstances.
See the [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/crisis)
- [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/crisis)
- [Specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/crisis)
See the [specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/crisis)
# Mint
## Mint
The `x/mint` module is for flexible inflation rates and effect a balance between market liquidity and staked supply.
See the [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/mint)
- [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/mint)
- [Specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/mint)
See the [specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/mint)
# Params
## Params
The `x/params` module provides a globally available parameter store.
See the [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/params)
- [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/params)
- [Specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/params)
See the [specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/params)
## Evidence
The `x/evidence` modules provides a mechanism for defining and submitting arbitrary
events of misbehavior and a means to execute custom business logic for such misbehavior.
- [API Docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/evidence)
- [Specification](https://github.com/cosmos/cosmos-sdk/blob/master/docs/spec/evidence)

View File

@ -20,6 +20,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
@ -54,6 +55,7 @@ var (
params.AppModuleBasic{},
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
evidence.AppModuleBasic{},
)
// module account permissions
@ -90,6 +92,9 @@ type SimApp struct {
keys map[string]*sdk.KVStoreKey
tkeys map[string]*sdk.TransientStoreKey
// subspaces
subspaces map[string]params.Subspace
// keepers
AccountKeeper auth.AccountKeeper
BankKeeper bank.Keeper
@ -101,6 +106,7 @@ type SimApp struct {
GovKeeper gov.Keeper
CrisisKeeper crisis.Keeper
ParamsKeeper params.Keeper
EvidenceKeeper evidence.Keeper
// the module manager
mm *module.Manager
@ -121,9 +127,10 @@ func NewSimApp(
bApp.SetCommitMultiStoreTracer(traceStore)
bApp.SetAppVersion(version.Version)
keys := sdk.NewKVStoreKeys(bam.MainStoreKey, auth.StoreKey, staking.StoreKey,
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
gov.StoreKey, params.StoreKey)
keys := sdk.NewKVStoreKeys(
bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey,
distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
app := &SimApp{
@ -132,39 +139,68 @@ func NewSimApp(
invCheckPeriod: invCheckPeriod,
keys: keys,
tkeys: tkeys,
subspaces: make(map[string]params.Subspace),
}
// init params keeper and subspaces
app.ParamsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace)
authSubspace := app.ParamsKeeper.Subspace(auth.DefaultParamspace)
bankSubspace := app.ParamsKeeper.Subspace(bank.DefaultParamspace)
stakingSubspace := app.ParamsKeeper.Subspace(staking.DefaultParamspace)
mintSubspace := app.ParamsKeeper.Subspace(mint.DefaultParamspace)
distrSubspace := app.ParamsKeeper.Subspace(distr.DefaultParamspace)
slashingSubspace := app.ParamsKeeper.Subspace(slashing.DefaultParamspace)
govSubspace := app.ParamsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
crisisSubspace := app.ParamsKeeper.Subspace(crisis.DefaultParamspace)
app.subspaces[auth.ModuleName] = app.ParamsKeeper.Subspace(auth.DefaultParamspace)
app.subspaces[bank.ModuleName] = app.ParamsKeeper.Subspace(bank.DefaultParamspace)
app.subspaces[staking.ModuleName] = app.ParamsKeeper.Subspace(staking.DefaultParamspace)
app.subspaces[mint.ModuleName] = app.ParamsKeeper.Subspace(mint.DefaultParamspace)
app.subspaces[distr.ModuleName] = app.ParamsKeeper.Subspace(distr.DefaultParamspace)
app.subspaces[slashing.ModuleName] = app.ParamsKeeper.Subspace(slashing.DefaultParamspace)
app.subspaces[gov.ModuleName] = app.ParamsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
app.subspaces[crisis.ModuleName] = app.ParamsKeeper.Subspace(crisis.DefaultParamspace)
app.subspaces[evidence.ModuleName] = app.ParamsKeeper.Subspace(evidence.DefaultParamspace)
// add keepers
app.AccountKeeper = auth.NewAccountKeeper(app.cdc, keys[auth.StoreKey], authSubspace, auth.ProtoBaseAccount)
app.BankKeeper = bank.NewBaseKeeper(app.AccountKeeper, bankSubspace, bank.DefaultCodespace, app.ModuleAccountAddrs())
app.SupplyKeeper = supply.NewKeeper(app.cdc, keys[supply.StoreKey], app.AccountKeeper, app.BankKeeper, maccPerms)
stakingKeeper := staking.NewKeeper(app.cdc, keys[staking.StoreKey],
app.SupplyKeeper, stakingSubspace, staking.DefaultCodespace)
app.MintKeeper = mint.NewKeeper(app.cdc, keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.SupplyKeeper, auth.FeeCollectorName)
app.DistrKeeper = distr.NewKeeper(app.cdc, keys[distr.StoreKey], distrSubspace, &stakingKeeper,
app.SupplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs())
app.SlashingKeeper = slashing.NewKeeper(app.cdc, keys[slashing.StoreKey], &stakingKeeper,
slashingSubspace, slashing.DefaultCodespace)
app.CrisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName)
app.AccountKeeper = auth.NewAccountKeeper(
app.cdc, keys[auth.StoreKey], app.subspaces[auth.ModuleName], auth.ProtoBaseAccount,
)
app.BankKeeper = bank.NewBaseKeeper(
app.AccountKeeper, app.subspaces[bank.ModuleName], bank.DefaultCodespace,
app.ModuleAccountAddrs(),
)
app.SupplyKeeper = supply.NewKeeper(
app.cdc, keys[supply.StoreKey], app.AccountKeeper, app.BankKeeper, maccPerms,
)
stakingKeeper := staking.NewKeeper(
app.cdc, keys[staking.StoreKey], app.SupplyKeeper, app.subspaces[staking.ModuleName],
staking.DefaultCodespace)
app.MintKeeper = mint.NewKeeper(
app.cdc, keys[mint.StoreKey], app.subspaces[mint.ModuleName], &stakingKeeper,
app.SupplyKeeper, auth.FeeCollectorName,
)
app.DistrKeeper = distr.NewKeeper(
app.cdc, keys[distr.StoreKey], app.subspaces[distr.ModuleName], &stakingKeeper,
app.SupplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs(),
)
app.SlashingKeeper = slashing.NewKeeper(
app.cdc, keys[slashing.StoreKey], &stakingKeeper, app.subspaces[slashing.ModuleName], slashing.DefaultCodespace,
)
app.CrisisKeeper = crisis.NewKeeper(
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
)
// create evidence keeper with router
evidenceKeeper := evidence.NewKeeper(
app.cdc, keys[evidence.StoreKey], app.subspaces[evidence.ModuleName], evidence.DefaultCodespace,
)
evidenceRouter := evidence.NewRouter()
// TODO: Register evidence routes.
evidenceKeeper.SetRouter(evidenceRouter)
app.EvidenceKeeper = *evidenceKeeper
// register the proposal types
govRouter := gov.NewRouter()
govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler).
AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper))
app.GovKeeper = gov.NewKeeper(app.cdc, keys[gov.StoreKey], govSubspace,
app.SupplyKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter)
app.GovKeeper = gov.NewKeeper(
app.cdc, keys[gov.StoreKey], app.subspaces[gov.ModuleName], app.SupplyKeeper,
&stakingKeeper, gov.DefaultCodespace, govRouter,
)
// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@ -185,22 +221,21 @@ func NewSimApp(
distr.NewAppModule(app.DistrKeeper, app.SupplyKeeper),
slashing.NewAppModule(app.SlashingKeeper, app.StakingKeeper),
staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.SupplyKeeper),
evidence.NewAppModule(app.EvidenceKeeper),
)
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant.
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName)
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName)
// NOTE: The genutils moodule must occur after staking so that pools are
// properly initialized with tokens from genesis accounts.
app.mm.SetOrderInitGenesis(
auth.ModuleName, distr.ModuleName, staking.ModuleName,
bank.ModuleName, slashing.ModuleName, gov.ModuleName,
mint.ModuleName, supply.ModuleName, crisis.ModuleName,
genutil.ModuleName,
auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
crisis.ModuleName, genutil.ModuleName, evidence.ModuleName,
)
app.mm.RegisterInvariants(&app.CrisisKeeper)
@ -239,6 +274,7 @@ func NewSimApp(
cmn.Exit(err.Error())
}
}
return app
}
@ -274,21 +310,35 @@ func (app *SimApp) ModuleAccountAddrs() map[string]bool {
return modAccAddrs
}
// Codec returns simapp's codec
// Codec returns SimApp's codec.
//
// NOTE: This is solely to be used for testing purposes as it may be desirable
// for modules to register their own custom testing types.
func (app *SimApp) Codec() *codec.Codec {
return app.cdc
}
// GetKey returns the KVStoreKey for the provided store key
// GetKey returns the KVStoreKey for the provided store key.
//
// NOTE: This is solely to be used for testing purposes.
func (app *SimApp) GetKey(storeKey string) *sdk.KVStoreKey {
return app.keys[storeKey]
}
// GetTKey returns the TransientStoreKey for the provided store key
// GetTKey returns the TransientStoreKey for the provided store key.
//
// NOTE: This is solely to be used for testing purposes.
func (app *SimApp) GetTKey(storeKey string) *sdk.TransientStoreKey {
return app.tkeys[storeKey]
}
// GetSubspace returns a param subspace for a given module name.
//
// NOTE: This is solely to be used for testing purposes.
func (app *SimApp) GetSubspace(moduleName string) params.Subspace {
return app.subspaces[moduleName]
}
// GetMaccPerms returns a copy of the module account permissions
func GetMaccPerms() map[string][]string {
dupMaccPerms := make(map[string][]string)

View File

@ -309,6 +309,22 @@ func ResultFromError(err error) Result {
}
}
// ConvertError accepts a standard error and attempts to convert it to an sdk.Error.
// If the given error is already an sdk.Error, it'll simply be returned. Otherwise,
// it'll convert it to a types.Error. This is meant to provide a migration path
// away from sdk.Error in favor of types.Error.
func ConvertError(err error) Error {
if err == nil {
return nil
}
if sdkError, ok := err.(Error); ok {
return sdkError
}
space, code, log := sdkerrors.ABCIInfo(err, false)
return NewError(CodespaceType(space), CodeType(code), log)
}
//----------------------------------------
// REST error utilities

View File

@ -65,6 +65,12 @@ var (
// ErrNoSignatures to doc
ErrNoSignatures = Register(RootCodespace, 16, "no signatures supplied")
// ErrJSONMarshal defines an ABCI typed JSON marshalling error
ErrJSONMarshal = Register(RootCodespace, 17, "failed to marshal JSON bytes")
// ErrJSONUnmarshal defines an ABCI typed JSON unmarshalling error
ErrJSONUnmarshal = Register(RootCodespace, 18, "failed to unmarshal JSON bytes")
// ErrPanic is only set when we recover from a panic, so we know to
// redact potentially sensitive system info
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
@ -121,7 +127,7 @@ func ABCIError(codespace string, code uint32, log string) error {
}
// This is a unique error, will never match on .Is()
// Use Wrap here to get a stack trace
return Wrap(&Error{codespace: codespace, code: code, desc: "unknown"}, log)
return Wrap(New(codespace, code, "unknown"), log)
}
// Error represents a root error.
@ -139,6 +145,10 @@ type Error struct {
desc string
}
func New(codespace string, code uint32, desc string) *Error {
return &Error{codespace: codespace, code: code, desc: desc}
}
func (e Error) Error() string {
return e.desc
}

View File

@ -1,5 +1,11 @@
package types
import "regexp"
// IsAlphaNumeric defines a regular expression for matching against alpha-numeric
// values.
var IsAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
// Router provides handlers for each transaction type.
type Router interface {
AddRoute(r string, h Handler) Router

50
x/evidence/alias.go Normal file
View File

@ -0,0 +1,50 @@
package evidence
import (
"github.com/cosmos/cosmos-sdk/x/evidence/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)
// nolint
const (
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
QueryEvidence = types.QueryEvidence
QueryAllEvidence = types.QueryAllEvidence
CodeNoEvidenceHandlerExists = types.CodeNoEvidenceHandlerExists
CodeInvalidEvidence = types.CodeInvalidEvidence
CodeNoEvidenceExists = types.CodeNoEvidenceExists
TypeMsgSubmitEvidence = types.TypeMsgSubmitEvidence
DefaultCodespace = types.DefaultCodespace
EventTypeSubmitEvidence = types.EventTypeSubmitEvidence
AttributeValueCategory = types.AttributeValueCategory
AttributeKeyEvidenceHash = types.AttributeKeyEvidenceHash
)
var (
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
NewMsgSubmitEvidence = types.NewMsgSubmitEvidence
NewRouter = types.NewRouter
NewQueryEvidenceParams = types.NewQueryEvidenceParams
NewQueryAllEvidenceParams = types.NewQueryAllEvidenceParams
RegisterCodec = types.RegisterCodec
RegisterEvidenceTypeCodec = types.RegisterEvidenceTypeCodec
ModuleCdc = types.ModuleCdc
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
)
type (
Keeper = keeper.Keeper
GenesisState = types.GenesisState
MsgSubmitEvidence = types.MsgSubmitEvidence
Handler = types.Handler
Router = types.Router
)

View File

@ -0,0 +1,116 @@
package cli
import (
"encoding/hex"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)
const (
flagPage = "page"
flagLimit = "limit"
)
// GetQueryCmd returns the CLI command with all evidence module query commands
// mounted.
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: "Query for evidence by hash or for all (paginated) submitted evidence",
Long: strings.TrimSpace(
fmt.Sprintf(`Query for specific submitted evidence by hash or query for all (paginated) evidence:
Example:
$ %s query %s DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660
$ %s query %s --page=2 --limit=50
`,
version.ClientName, types.ModuleName, version.ClientName, types.ModuleName,
),
),
Args: cobra.MaximumNArgs(1),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: QueryEvidenceCmd(cdc),
}
cmd.Flags().Int(flagPage, 1, "pagination page of evidence to to query for")
cmd.Flags().Int(flagLimit, 100, "pagination limit of evidence to query for")
return cmd
}
// QueryEvidenceCmd returns the command handler for evidence querying. Evidence
// can be queried for by hash or paginated evidence can be returned.
func QueryEvidenceCmd(cdc *codec.Codec) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
if err := client.ValidateCmd(cmd, args); err != nil {
return err
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
if hash := args[0]; hash != "" {
return queryEvidence(cdc, cliCtx, hash)
}
return queryAllEvidence(cdc, cliCtx)
}
}
func queryEvidence(cdc *codec.Codec, cliCtx context.CLIContext, hash string) error {
if _, err := hex.DecodeString(hash); err != nil {
return fmt.Errorf("invalid evidence hash: %w", err)
}
params := types.NewQueryEvidenceParams(hash)
bz, err := cdc.MarshalJSON(params)
if err != nil {
return fmt.Errorf("failed to marshal query params: %w", err)
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryEvidence)
res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
var evidence exported.Evidence
err = cdc.UnmarshalJSON(res, &evidence)
if err != nil {
return fmt.Errorf("failed to unmarshal evidence: %w", err)
}
return cliCtx.PrintOutput(evidence)
}
func queryAllEvidence(cdc *codec.Codec, cliCtx context.CLIContext) error {
params := types.NewQueryAllEvidenceParams(viper.GetInt(flagPage), viper.GetInt(flagLimit))
bz, err := cdc.MarshalJSON(params)
if err != nil {
return fmt.Errorf("failed to marshal query params: %w", err)
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllEvidence)
res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
var evidence []exported.Evidence
err = cdc.UnmarshalJSON(res, &evidence)
if err != nil {
return fmt.Errorf("failed to unmarshal evidence: %w", err)
}
return cliCtx.PrintOutput(evidence)
}

View File

@ -0,0 +1,45 @@
package cli
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/spf13/cobra"
)
// GetTxCmd returns a CLI command that has all the native evidence module tx
// commands mounted. In addition, it mounts all childCmds, implemented by outside
// modules, under a sub-command. This allows external modules to implement custom
// Evidence types and Handlers while having the ability to create and sign txs
// containing them all from a single root command.
func GetTxCmd(storeKey string, cdc *codec.Codec, childCmds []*cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: "Evidence transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
submitEvidenceCmd := SubmitEvidenceCmd(cdc)
for _, childCmd := range childCmds {
submitEvidenceCmd.AddCommand(client.PostCommands(childCmd)[0])
}
// TODO: Add tx commands.
return cmd
}
// SubmitEvidenceCmd returns the top-level evidence submission command handler.
// All concrete evidence submission child command handlers should be registered
// under this command.
func SubmitEvidenceCmd(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "submit",
Short: "Submit arbitrary evidence of misbehavior",
}
return cmd
}

View File

@ -0,0 +1,31 @@
package client
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/evidence/client/rest"
)
type (
// RESTHandlerFn defines a REST service handler for evidence submission
RESTHandlerFn func(context.CLIContext) rest.EvidenceRESTHandler
// CLIHandlerFn defines a CLI command handler for evidence submission
CLIHandlerFn func(*codec.Codec) *cobra.Command
// EvidenceHandler defines a type that exposes REST and CLI client handlers for
// evidence submission.
EvidenceHandler struct {
CLIHandler CLIHandlerFn
RESTHandler RESTHandlerFn
}
)
func NewEvidenceHandler(cliHandler CLIHandlerFn, restHandler RESTHandlerFn) EvidenceHandler {
return EvidenceHandler{
CLIHandler: cliHandler,
RESTHandler: restHandler,
}
}

View File

@ -0,0 +1,91 @@
package rest
import (
"fmt"
"net/http"
"strings"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/gorilla/mux"
)
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(
fmt.Sprintf("/evidence/{%s}", RestParamEvidenceHash),
queryEvidenceHandler(cliCtx),
).Methods(MethodGet)
r.HandleFunc(
"/evidence",
queryAllEvidenceHandler(cliCtx),
).Methods(MethodGet)
}
func queryEvidenceHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
evidenceHash := vars[RestParamEvidenceHash]
if strings.TrimSpace(evidenceHash) == "" {
rest.WriteErrorResponse(w, http.StatusBadRequest, "evidence hash required but not specified")
return
}
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
params := types.NewQueryEvidenceParams(evidenceHash)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
return
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryEvidence)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
func queryAllEvidenceHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
params := types.NewQueryAllEvidenceParams(page, limit)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
return
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllEvidence)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}

View File

@ -0,0 +1,30 @@
package rest
import (
"net/http"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/gorilla/mux"
)
// REST query and parameter values
const (
RestParamEvidenceHash = "evidence-hash"
MethodGet = "GET"
)
// EvidenceRESTHandler defines a REST service evidence handler implemented in
// another module. The sub-route is mounted on the evidence REST handler.
type EvidenceRESTHandler struct {
SubRoute string
Handler func(http.ResponseWriter, *http.Request)
}
// RegisterRoutes registers all Evidence submission handlers for the evidence module's
// REST service handler.
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, handlers []EvidenceRESTHandler) {
registerQueryRoutes(cliCtx, r)
registerTxRoutes(cliCtx, r, handlers)
}

View File

@ -0,0 +1,11 @@
package rest
import (
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/gorilla/mux"
)
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, handlers []EvidenceRESTHandler) {
// TODO: Register tx handlers.
}

44
x/evidence/doc.go Normal file
View File

@ -0,0 +1,44 @@
/*
Package evidence implements a Cosmos SDK module, per ADR 009, that allows for the
submission and handling of arbitrary evidence of misbehavior.
All concrete evidence types must implement the Evidence interface contract. Submitted
evidence is first routed through the evidence module's Router in which it attempts
to find a corresponding Handler for that specific evidence type. Each evidence type
must have a Handler registered with the evidence module's keeper in order for it
to be successfully executed.
Each corresponding handler must also fulfill the Handler interface contract. The
Handler for a given Evidence type can perform any arbitrary state transitions
such as slashing, jailing, and tombstoning. This provides developers with great
flexibility in designing evidence handling.
A full setup of the evidence module may look something as follows:
ModuleBasics = module.NewBasicManager(
// ...,
evidence.AppModuleBasic{},
)
// First, create the keeper's subspace for parameters and the keeper itself.
evidenceParamspace := app.ParamsKeeper.Subspace(evidence.DefaultParamspace)
evidenceKeeper := evidence.NewKeeper(
app.cdc, keys[evidence.StoreKey], evidenceParamspace, evidence.DefaultCodespace,
)
// Second, create the evidence Handler and register all desired routes.
evidenceRouter := evidence.NewRouter().
AddRoute(evidenceRoute, evidenceHandler).
AddRoute(..., ...)
evidenceKeeper.SetRouter(evidenceRouter)
app.mm = module.NewManager(
// ...
evidence.NewAppModule(evidenceKeeper),
)
// Remaining application bootstrapping...
*/
package evidence

View File

@ -0,0 +1,29 @@
package exported
import (
sdk "github.com/cosmos/cosmos-sdk/types"
cmn "github.com/tendermint/tendermint/libs/common"
)
// Evidence defines the contract which concrete evidence types of misbehavior
// must implement.
type Evidence interface {
Route() string
Type() string
String() string
Hash() cmn.HexBytes
ValidateBasic() error
// The consensus address of the malicious validator at time of infraction
GetConsensusAddress() sdk.ConsAddress
// Height at which the infraction occurred
GetHeight() int64
// The total power of the malicious validator at time of infraction
GetValidatorPower() int64
// The total validator set power at time of infraction
GetTotalPower() int64
}

30
x/evidence/genesis.go Normal file
View File

@ -0,0 +1,30 @@
package evidence
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// InitGenesis initializes the evidence module's state from a provided genesis
// state.
func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) {
if err := gs.Validate(); err != nil {
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
}
for _, e := range gs.Evidence {
if _, ok := k.GetEvidence(ctx, e.Hash()); ok {
panic(fmt.Sprintf("evidence with hash %s already exists", e.Hash()))
}
k.SetEvidence(ctx, e)
}
}
// ExportGenesis returns the evidence module's exported genesis.
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
return GenesisState{
Evidence: k.GetAllEvidence(ctx),
}
}

111
x/evidence/genesis_test.go Normal file
View File

@ -0,0 +1,111 @@
package evidence_test
import (
"testing"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/stretchr/testify/suite"
)
type GenesisTestSuite struct {
suite.Suite
ctx sdk.Context
keeper evidence.Keeper
}
func (suite *GenesisTestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
// get the app's codec and register custom testing types
cdc := app.Codec()
cdc.RegisterConcrete(types.TestEquivocationEvidence{}, "test/TestEquivocationEvidence", nil)
// recreate keeper in order to use custom testing types
evidenceKeeper := evidence.NewKeeper(
cdc, app.GetKey(evidence.StoreKey), app.GetSubspace(evidence.ModuleName),
evidence.DefaultCodespace,
)
router := evidence.NewRouter()
router = router.AddRoute(types.TestEvidenceRouteEquivocation, types.TestEquivocationHandler(*evidenceKeeper))
evidenceKeeper.SetRouter(router)
suite.ctx = app.BaseApp.NewContext(checkTx, abci.Header{Height: 1})
suite.keeper = *evidenceKeeper
}
func (suite *GenesisTestSuite) TestInitGenesis_Valid() {
pk := ed25519.GenPrivKey()
testEvidence := make([]exported.Evidence, 100)
for i := 0; i < 100; i++ {
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: int64(i),
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes("test-chain"))
suite.NoError(err)
sv.Signature = sig
testEvidence[i] = types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
}
suite.NotPanics(func() {
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(testEvidence))
})
for _, e := range testEvidence {
_, ok := suite.keeper.GetEvidence(suite.ctx, e.Hash())
suite.True(ok)
}
}
func (suite *GenesisTestSuite) TestInitGenesis_Invalid() {
pk := ed25519.GenPrivKey()
testEvidence := make([]exported.Evidence, 100)
for i := 0; i < 100; i++ {
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: int64(i),
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes("test-chain"))
suite.NoError(err)
sv.Signature = sig
testEvidence[i] = types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: types.TestVote{Height: 10, Round: 1},
}
}
suite.Panics(func() {
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(testEvidence))
})
suite.Empty(suite.keeper.GetAllEvidence(suite.ctx))
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

40
x/evidence/handler.go Normal file
View File

@ -0,0 +1,40 @@
package evidence
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case MsgSubmitEvidence:
return handleMsgSubmitEvidence(ctx, k, msg)
default:
return sdk.ErrUnknownRequest(fmt.Sprintf("unrecognized %s message type: %T", ModuleName, msg)).Result()
}
}
}
func handleMsgSubmitEvidence(ctx sdk.Context, k Keeper, msg MsgSubmitEvidence) sdk.Result {
if err := k.SubmitEvidence(ctx, msg.Evidence); err != nil {
return sdk.ConvertError(err).Result()
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Submitter.String()),
),
)
return sdk.Result{
Data: msg.Evidence.Hash(),
Events: ctx.EventManager().Events(),
}
}

103
x/evidence/handler_test.go Normal file
View File

@ -0,0 +1,103 @@
package evidence_test
import (
"testing"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)
type HandlerTestSuite struct {
suite.Suite
ctx sdk.Context
handler sdk.Handler
keeper evidence.Keeper
}
func (suite *HandlerTestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
// get the app's codec and register custom testing types
cdc := app.Codec()
cdc.RegisterConcrete(types.TestEquivocationEvidence{}, "test/TestEquivocationEvidence", nil)
// recreate keeper in order to use custom testing types
evidenceKeeper := evidence.NewKeeper(
cdc, app.GetKey(evidence.StoreKey), app.GetSubspace(evidence.ModuleName),
evidence.DefaultCodespace,
)
router := evidence.NewRouter()
router = router.AddRoute(types.TestEvidenceRouteEquivocation, types.TestEquivocationHandler(*evidenceKeeper))
evidenceKeeper.SetRouter(router)
suite.ctx = app.BaseApp.NewContext(checkTx, abci.Header{Height: 1})
suite.handler = evidence.NewHandler(*evidenceKeeper)
suite.keeper = *evidenceKeeper
}
func (suite *HandlerTestSuite) TestMsgSubmitEvidence_Valid() {
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes(suite.ctx.ChainID()))
suite.NoError(err)
sv.Signature = sig
s := sdk.AccAddress("test")
e := types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
ctx := suite.ctx.WithIsCheckTx(false)
msg := evidence.NewMsgSubmitEvidence(e, s)
res := suite.handler(ctx, msg)
suite.True(res.IsOK())
suite.Equal(e.Hash().Bytes(), res.Data)
}
func (suite *HandlerTestSuite) TestMsgSubmitEvidence_Invalid() {
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes(suite.ctx.ChainID()))
suite.NoError(err)
sv.Signature = sig
s := sdk.AccAddress("test")
e := types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: types.TestVote{Height: 10, Round: 1},
}
ctx := suite.ctx.WithIsCheckTx(false)
msg := evidence.NewMsgSubmitEvidence(e, s)
res := suite.handler(ctx, msg)
suite.False(res.IsOK())
}
func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}

View File

@ -0,0 +1,147 @@
package keeper
import (
"fmt"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/cosmos/cosmos-sdk/x/params"
)
// Keeper defines the evidence module's keeper. The keeper is responsible for
// managing persistence, state transitions and query handling for the evidence
// module.
type Keeper struct {
cdc *codec.Codec
storeKey sdk.StoreKey
paramSpace params.Subspace
router types.Router
codespace sdk.CodespaceType
}
func NewKeeper(
cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspace, codespace sdk.CodespaceType,
) *Keeper {
return &Keeper{
cdc: cdc,
storeKey: storeKey,
paramSpace: paramSpace,
codespace: codespace,
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// SetRouter sets the Evidence Handler router for the x/evidence module. Note,
// we allow the ability to set the router after the Keeper is constructed as a
// given Handler may need access the Keeper before being constructed. The router
// may only be set once and will be sealed if it's not already sealed.
func (k *Keeper) SetRouter(rtr types.Router) {
// It is vital to seal the Evidence Handler router as to not allow further
// handlers to be registered after the keeper is created since this
// could create invalid or non-deterministic behavior.
if !rtr.Sealed() {
rtr.Seal()
}
if k.router != nil {
panic(fmt.Sprintf("attempting to reset router on x/%s", types.ModuleName))
}
k.router = rtr
}
// GetEvidenceHandler returns a registered Handler for a given Evidence type. If
// no handler exists, an error is returned.
func (k Keeper) GetEvidenceHandler(evidenceRoute string) (types.Handler, error) {
if !k.router.HasRoute(evidenceRoute) {
return nil, types.ErrNoEvidenceHandlerExists(k.codespace, evidenceRoute)
}
return k.router.GetRoute(evidenceRoute), nil
}
// SubmitEvidence attempts to match evidence against the keepers router and execute
// the corresponding registered Evidence Handler. An error is returned if no
// registered Handler exists or if the Handler fails. Otherwise, the evidence is
// persisted.
func (k Keeper) SubmitEvidence(ctx sdk.Context, evidence exported.Evidence) error {
if _, ok := k.GetEvidence(ctx, evidence.Hash()); ok {
return types.ErrEvidenceExists(k.codespace, evidence.Hash().String())
}
if !k.router.HasRoute(evidence.Route()) {
return types.ErrNoEvidenceHandlerExists(k.codespace, evidence.Route())
}
handler := k.router.GetRoute(evidence.Route())
if err := handler(ctx, evidence); err != nil {
return types.ErrInvalidEvidence(k.codespace, err.Error())
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSubmitEvidence,
sdk.NewAttribute(types.AttributeKeyEvidenceHash, evidence.Hash().String()),
),
)
k.SetEvidence(ctx, evidence)
return nil
}
// SetEvidence sets Evidence by hash in the module's KVStore.
func (k Keeper) SetEvidence(ctx sdk.Context, evidence exported.Evidence) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixEvidence)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(evidence)
store.Set(evidence.Hash(), bz)
}
// GetEvidence retrieves Evidence by hash if it exists. If no Evidence exists for
// the given hash, (nil, false) is returned.
func (k Keeper) GetEvidence(ctx sdk.Context, hash cmn.HexBytes) (evidence exported.Evidence, found bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixEvidence)
bz := store.Get(hash)
if len(bz) == 0 {
return nil, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &evidence)
return evidence, true
}
// IterateEvidence provides an interator over all stored Evidence objects. For
// each Evidence object, cb will be called. If the cb returns true, the iterator
// will close and stop.
func (k Keeper) IterateEvidence(ctx sdk.Context, cb func(exported.Evidence) bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixEvidence)
iterator := sdk.KVStorePrefixIterator(store, nil)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var evidence exported.Evidence
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &evidence)
if cb(evidence) {
break
}
}
}
// GetAllEvidence returns all stored Evidence objects.
func (k Keeper) GetAllEvidence(ctx sdk.Context) (evidence []exported.Evidence) {
k.IterateEvidence(ctx, func(e exported.Evidence) bool {
evidence = append(evidence, e)
return false
})
return evidence
}

View File

@ -0,0 +1,181 @@
package keeper_test
import (
"testing"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)
type KeeperTestSuite struct {
suite.Suite
ctx sdk.Context
querier sdk.Querier
keeper keeper.Keeper
}
func (suite *KeeperTestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
// get the app's codec and register custom testing types
cdc := app.Codec()
cdc.RegisterConcrete(types.TestEquivocationEvidence{}, "test/TestEquivocationEvidence", nil)
// recreate keeper in order to use custom testing types
evidenceKeeper := evidence.NewKeeper(
cdc, app.GetKey(evidence.StoreKey), app.GetSubspace(evidence.ModuleName),
evidence.DefaultCodespace,
)
router := evidence.NewRouter()
router = router.AddRoute(types.TestEvidenceRouteEquivocation, types.TestEquivocationHandler(*evidenceKeeper))
evidenceKeeper.SetRouter(router)
suite.ctx = app.BaseApp.NewContext(checkTx, abci.Header{Height: 1})
suite.querier = keeper.NewQuerier(*evidenceKeeper)
suite.keeper = *evidenceKeeper
}
func (suite *KeeperTestSuite) populateEvidence(ctx sdk.Context, numEvidence int) []exported.Evidence {
evidence := make([]exported.Evidence, numEvidence)
for i := 0; i < numEvidence; i++ {
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: int64(i),
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes(ctx.ChainID()))
suite.NoError(err)
sv.Signature = sig
evidence[i] = types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
suite.Nil(suite.keeper.SubmitEvidence(ctx, evidence[i]))
}
return evidence
}
func (suite *KeeperTestSuite) TestSubmitValidEvidence() {
ctx := suite.ctx.WithIsCheckTx(false)
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes(ctx.ChainID()))
suite.NoError(err)
sv.Signature = sig
e := types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
suite.Nil(suite.keeper.SubmitEvidence(ctx, e))
res, ok := suite.keeper.GetEvidence(ctx, e.Hash())
suite.True(ok)
suite.Equal(e, res)
}
func (suite *KeeperTestSuite) TestSubmitValidEvidence_Duplicate() {
ctx := suite.ctx.WithIsCheckTx(false)
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes(ctx.ChainID()))
suite.NoError(err)
sv.Signature = sig
e := types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
suite.Nil(suite.keeper.SubmitEvidence(ctx, e))
suite.Error(suite.keeper.SubmitEvidence(ctx, e))
res, ok := suite.keeper.GetEvidence(ctx, e.Hash())
suite.True(ok)
suite.Equal(e, res)
}
func (suite *KeeperTestSuite) TestSubmitInvalidEvidence() {
ctx := suite.ctx.WithIsCheckTx(false)
pk := ed25519.GenPrivKey()
e := types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 10,
Round: 0,
},
VoteB: types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
},
}
suite.Error(suite.keeper.SubmitEvidence(ctx, e))
res, ok := suite.keeper.GetEvidence(ctx, e.Hash())
suite.False(ok)
suite.Nil(res)
}
func (suite *KeeperTestSuite) TestIterateEvidence() {
ctx := suite.ctx.WithIsCheckTx(false)
numEvidence := 100
suite.populateEvidence(ctx, numEvidence)
evidence := suite.keeper.GetAllEvidence(ctx)
suite.Len(evidence, numEvidence)
}
func (suite *KeeperTestSuite) TestGetEvidenceHandler() {
handler, err := suite.keeper.GetEvidenceHandler(types.TestEquivocationEvidence{}.Route())
suite.NoError(err)
suite.NotNil(handler)
handler, err = suite.keeper.GetEvidenceHandler("invalidHandler")
suite.Error(err)
suite.Nil(handler)
}
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

View File

@ -0,0 +1,87 @@
package keeper
import (
"encoding/hex"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
abci "github.com/tendermint/tendermint/abci/types"
)
func NewQuerier(k Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) {
var (
res []byte
err error
)
switch path[0] {
case types.QueryEvidence:
res, err = queryEvidence(ctx, path[1:], req, k)
case types.QueryAllEvidence:
res, err = queryAllEvidence(ctx, path[1:], req, k)
default:
err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
}
return res, sdk.ConvertError(err)
}
}
func queryEvidence(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryEvidenceParams
err := k.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
hash, err := hex.DecodeString(params.EvidenceHash)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to decode evidence hash string query")
}
evidence, ok := k.GetEvidence(ctx, hash)
if !ok {
return nil, types.ErrNoEvidenceExists(k.codespace, params.EvidenceHash)
}
res, err := codec.MarshalJSONIndent(k.cdc, evidence)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return res, nil
}
func queryAllEvidence(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryAllEvidenceParams
err := k.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
evidence := k.GetAllEvidence(ctx)
start, end := client.Paginate(len(evidence), params.Page, params.Limit, 100)
if start < 0 || end < 0 {
evidence = []exported.Evidence{}
} else {
evidence = evidence[start:end]
}
res, err := codec.MarshalJSONIndent(k.cdc, evidence)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return res, nil
}

View File

@ -0,0 +1,86 @@
package keeper_test
import (
"strings"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
abci "github.com/tendermint/tendermint/abci/types"
)
const (
custom = "custom"
)
func (suite *KeeperTestSuite) TestQueryEvidence_Existing() {
ctx := suite.ctx.WithIsCheckTx(false)
numEvidence := 100
evidence := suite.populateEvidence(ctx, numEvidence)
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryEvidence}, "/"),
Data: types.TestingCdc.MustMarshalJSON(types.NewQueryEvidenceParams(evidence[0].Hash().String())),
}
bz, err := suite.querier(ctx, []string{types.QueryEvidence}, query)
suite.Nil(err)
suite.NotNil(bz)
var e exported.Evidence
suite.Nil(types.TestingCdc.UnmarshalJSON(bz, &e))
suite.Equal(evidence[0], e)
}
func (suite *KeeperTestSuite) TestQueryEvidence_NonExisting() {
ctx := suite.ctx.WithIsCheckTx(false)
numEvidence := 100
suite.populateEvidence(ctx, numEvidence)
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryEvidence}, "/"),
Data: types.TestingCdc.MustMarshalJSON(types.NewQueryEvidenceParams("0000000000000000000000000000000000000000000000000000000000000000")),
}
bz, err := suite.querier(ctx, []string{types.QueryEvidence}, query)
suite.NotNil(err)
suite.Nil(bz)
}
func (suite *KeeperTestSuite) TestQueryAllEvidence() {
ctx := suite.ctx.WithIsCheckTx(false)
numEvidence := 100
suite.populateEvidence(ctx, numEvidence)
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryAllEvidence}, "/"),
Data: types.TestingCdc.MustMarshalJSON(types.NewQueryAllEvidenceParams(1, numEvidence)),
}
bz, err := suite.querier(ctx, []string{types.QueryAllEvidence}, query)
suite.Nil(err)
suite.NotNil(bz)
var e []exported.Evidence
suite.Nil(types.TestingCdc.UnmarshalJSON(bz, &e))
suite.Len(e, numEvidence)
}
func (suite *KeeperTestSuite) TestQueryAllEvidence_InvalidPagination() {
ctx := suite.ctx.WithIsCheckTx(false)
numEvidence := 100
suite.populateEvidence(ctx, numEvidence)
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryAllEvidence}, "/"),
Data: types.TestingCdc.MustMarshalJSON(types.NewQueryAllEvidenceParams(0, numEvidence)),
}
bz, err := suite.querier(ctx, []string{types.QueryAllEvidence}, query)
suite.Nil(err)
suite.NotNil(bz)
var e []exported.Evidence
suite.Nil(types.TestingCdc.UnmarshalJSON(bz, &e))
suite.Len(e, 0)
}

View File

@ -0,0 +1,28 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
)
// ModuleCdc defines the evidence module's codec. The codec is not sealed as to
// allow other modules to register their concrete Evidence types.
var ModuleCdc = codec.New()
// RegisterCodec registers all the necessary types and interfaces for the
// evidence module.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*exported.Evidence)(nil), nil)
cdc.RegisterConcrete(MsgSubmitEvidence{}, "cosmos-sdk/MsgSubmitEvidence", nil)
}
// RegisterEvidenceTypeCodec registers an external concrete Evidence type defined
// in another module for the internal ModuleCdc. This allows the MsgSubmitEvidence
// to be correctly Amino encoded and decoded.
func RegisterEvidenceTypeCodec(o interface{}, name string) {
ModuleCdc.RegisterConcrete(o, name, nil)
}
func init() {
RegisterCodec(ModuleCdc)
}

View File

@ -0,0 +1,42 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)
var _ exported.Evidence = (*testEvidence)(nil)
type testEvidence struct{}
func (te testEvidence) Route() string { return "" }
func (te testEvidence) Type() string { return "" }
func (te testEvidence) String() string { return "" }
func (te testEvidence) ValidateBasic() error { return nil }
func (te testEvidence) GetConsensusAddress() sdk.ConsAddress { return nil }
func (te testEvidence) Hash() cmn.HexBytes { return nil }
func (te testEvidence) GetHeight() int64 { return 0 }
func (te testEvidence) GetValidatorPower() int64 { return 0 }
func (te testEvidence) GetTotalPower() int64 { return 0 }
func TestCodec(t *testing.T) {
cdc := codec.New()
types.RegisterCodec(cdc)
types.RegisterEvidenceTypeCodec(testEvidence{}, "cosmos-sdk/testEvidence")
var e exported.Evidence = testEvidence{}
bz, err := cdc.MarshalBinaryBare(e)
require.NoError(t, err)
var te testEvidence
require.NoError(t, cdc.UnmarshalBinaryBare(bz, &te))
require.Panics(t, func() { types.RegisterEvidenceTypeCodec(testEvidence{}, "cosmos-sdk/testEvidence") })
}

View File

@ -0,0 +1,58 @@
// DONTCOVER
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Error codes specific to the evidence module
const (
DefaultCodespace sdk.CodespaceType = ModuleName
CodeNoEvidenceHandlerExists sdk.CodeType = 1
CodeInvalidEvidence sdk.CodeType = 2
CodeNoEvidenceExists sdk.CodeType = 3
CodeEvidenceExists sdk.CodeType = 4
)
// ErrNoEvidenceHandlerExists returns a typed ABCI error for an invalid evidence
// handler route.
func ErrNoEvidenceHandlerExists(codespace sdk.CodespaceType, route string) error {
return sdkerrors.New(
string(codespace),
uint32(CodeNoEvidenceHandlerExists),
fmt.Sprintf("route '%s' does not have a registered evidence handler", route),
)
}
// ErrInvalidEvidence returns a typed ABCI error for invalid evidence.
func ErrInvalidEvidence(codespace sdk.CodespaceType, msg string) error {
return sdkerrors.New(
string(codespace),
uint32(CodeInvalidEvidence),
fmt.Sprintf("invalid evidence: %s", msg),
)
}
// ErrNoEvidenceExists returns a typed ABCI error for Evidence that does not exist
// for a given hash.
func ErrNoEvidenceExists(codespace sdk.CodespaceType, hash string) error {
return sdkerrors.New(
string(codespace),
uint32(CodeNoEvidenceExists),
fmt.Sprintf("evidence with hash %s does not exist", hash),
)
}
// ErrEvidenceExists returns a typed ABCI error for Evidence that already exists
// by hash in state.
func ErrEvidenceExists(codespace sdk.CodespaceType, hash string) error {
return sdkerrors.New(
string(codespace),
uint32(CodeEvidenceExists),
fmt.Sprintf("evidence with hash %s already exists", hash),
)
}

View File

@ -0,0 +1,9 @@
package types
// evidence module events
const (
EventTypeSubmitEvidence = "submit_evidence"
AttributeValueCategory = "evidence"
AttributeKeyEvidenceHash = "evidence_hash"
)

View File

@ -0,0 +1,31 @@
package types
import "github.com/cosmos/cosmos-sdk/x/evidence/exported"
// DONTCOVER
// GenesisState defines the evidence module's genesis state.
type GenesisState struct {
Evidence []exported.Evidence `json:"evidence" yaml:"evidence"`
}
func NewGenesisState(e []exported.Evidence) GenesisState {
return GenesisState{Evidence: e}
}
// DefaultGenesisState returns the evidence module's default genesis state.
func DefaultGenesisState() GenesisState {
return GenesisState{Evidence: []exported.Evidence{}}
}
// Validate performs basic gensis state validation returning an error upon any
// failure.
func (gs GenesisState) Validate() error {
for _, e := range gs.Evidence {
if err := e.ValidateBasic(); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,71 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)
func TestDefaultGenesisState(t *testing.T) {
gs := types.DefaultGenesisState()
require.NotNil(t, gs.Evidence)
require.Len(t, gs.Evidence, 0)
}
func TestGenesisStateValidate_Valid(t *testing.T) {
pk := ed25519.GenPrivKey()
evidence := make([]exported.Evidence, 100)
for i := 0; i < 100; i++ {
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: int64(i),
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes("test-chain"))
require.NoError(t, err)
sv.Signature = sig
evidence[i] = types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
}
}
gs := types.NewGenesisState(evidence)
require.NoError(t, gs.Validate())
}
func TestGenesisStateValidate_Invalid(t *testing.T) {
pk := ed25519.GenPrivKey()
evidence := make([]exported.Evidence, 100)
for i := 0; i < 100; i++ {
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: int64(i),
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes("test-chain"))
require.NoError(t, err)
sv.Signature = sig
evidence[i] = types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: types.TestVote{Height: 10, Round: 1},
}
}
gs := types.NewGenesisState(evidence)
require.Error(t, gs.Validate())
}

View File

@ -0,0 +1,23 @@
package types
const (
// ModuleName defines the module name
ModuleName = "evidence"
// StoreKey defines the primary module store key
StoreKey = ModuleName
// RouterKey defines the module's message routing key
RouterKey = ModuleName
// QuerierRoute defines the module's query routing key
QuerierRoute = ModuleName
// DefaultParamspace defines the module's default paramspace name
DefaultParamspace = ModuleName
)
// KVStore key prefixes
var (
KeyPrefixEvidence = []byte{0x00}
)

View File

@ -0,0 +1,59 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
)
// Message types for the evidence module
const (
TypeMsgSubmitEvidence = "submit_evidence"
)
var (
_ sdk.Msg = MsgSubmitEvidence{}
)
// MsgSubmitEvidence defines an sdk.Msg type that supports submitting arbitrary
// Evidence.
type MsgSubmitEvidence struct {
Evidence exported.Evidence `json:"evidence" yaml:"evidence"`
Submitter sdk.AccAddress `json:"submitter" yaml:"submitter"`
}
func NewMsgSubmitEvidence(e exported.Evidence, s sdk.AccAddress) MsgSubmitEvidence {
return MsgSubmitEvidence{Evidence: e, Submitter: s}
}
// Route returns the MsgSubmitEvidence's route.
func (m MsgSubmitEvidence) Route() string { return RouterKey }
// Type returns the MsgSubmitEvidence's type.
func (m MsgSubmitEvidence) Type() string { return TypeMsgSubmitEvidence }
// ValidateBasic performs basic (non-state-dependant) validation on a MsgSubmitEvidence.
func (m MsgSubmitEvidence) ValidateBasic() sdk.Error {
if m.Evidence == nil {
return sdk.ConvertError(ErrInvalidEvidence(DefaultCodespace, "missing evidence"))
}
if err := m.Evidence.ValidateBasic(); err != nil {
return sdk.ConvertError(err)
}
if m.Submitter.Empty() {
return sdk.ConvertError(sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, m.Submitter.String()))
}
return nil
}
// GetSignBytes returns the raw bytes a signer is expected to sign when submitting
// a MsgSubmitEvidence message.
func (m MsgSubmitEvidence) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(m))
}
// GetSigners returns the single expected signer for a MsgSubmitEvidence.
func (m MsgSubmitEvidence) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{m.Submitter}
}

View File

@ -0,0 +1,66 @@
package types_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
)
func TestMsgSubmitEvidence(t *testing.T) {
pk := ed25519.GenPrivKey()
sv := types.TestVote{
ValidatorAddress: pk.PubKey().Address(),
Height: 11,
Round: 0,
}
sig, err := pk.Sign(sv.SignBytes("test-chain"))
require.NoError(t, err)
sv.Signature = sig
submitter := sdk.AccAddress("test")
testCases := []struct {
evidence exported.Evidence
submitter sdk.AccAddress
expectErr bool
}{
{nil, submitter, true},
{
types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: sv,
},
submitter,
false,
},
{
types.TestEquivocationEvidence{
Power: 100,
TotalPower: 100000,
PubKey: pk.PubKey(),
VoteA: sv,
VoteB: types.TestVote{Height: 10, Round: 1},
},
submitter,
true,
},
}
for i, tc := range testCases {
msg := types.NewMsgSubmitEvidence(tc.evidence, tc.submitter)
require.Equal(t, msg.Route(), types.RouterKey, "unexpected result for tc #%d", i)
require.Equal(t, msg.Type(), types.TypeMsgSubmitEvidence, "unexpected result for tc #%d", i)
require.Equal(t, tc.expectErr, msg.ValidateBasic() != nil, "unexpected result for tc #%d", i)
if !tc.expectErr {
require.Equal(t, msg.GetSigners(), []sdk.AccAddress{tc.submitter}, "unexpected result for tc #%d", i)
}
}
}

View File

@ -0,0 +1,26 @@
package types
// Querier routes for the evidence module
const (
QueryEvidence = "evidence"
QueryAllEvidence = "all_evidence"
)
// QueryEvidenceParams defines the parameters necessary for querying Evidence.
type QueryEvidenceParams struct {
EvidenceHash string `json:"evidence_hash" yaml:"evidence_hash"`
}
func NewQueryEvidenceParams(hash string) QueryEvidenceParams {
return QueryEvidenceParams{EvidenceHash: hash}
}
// QueryAllEvidenceParams defines the parameters necessary for querying for all Evidence.
type QueryAllEvidenceParams struct {
Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"`
}
func NewQueryAllEvidenceParams(page, limit int) QueryAllEvidenceParams {
return QueryAllEvidenceParams{Page: page, Limit: limit}
}

View File

@ -0,0 +1,81 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
)
type (
// Handler defines an agnostic Evidence handler. The handler is responsible
// for executing all corresponding business logic necessary for verifying the
// evidence as valid. In addition, the Handler may execute any necessary
// slashing and potential jailing.
Handler func(sdk.Context, exported.Evidence) error
// Router defines a contract for which any Evidence handling module must
// implement in order to route Evidence to registered Handlers.
Router interface {
AddRoute(r string, h Handler) Router
HasRoute(r string) bool
GetRoute(path string) Handler
Seal()
Sealed() bool
}
router struct {
routes map[string]Handler
sealed bool
}
)
func NewRouter() Router {
return &router{
routes: make(map[string]Handler),
}
}
// Seal prevents the router from any subsequent route handlers to be registered.
// Seal will panic if called more than once.
func (rtr *router) Seal() {
if rtr.sealed {
panic("router already sealed")
}
rtr.sealed = true
}
// Sealed returns a boolean signifying if the Router is sealed or not.
func (rtr router) Sealed() bool {
return rtr.sealed
}
// AddRoute adds a governance handler for a given path. It returns the Router
// so AddRoute calls can be linked. It will panic if the router is sealed.
func (rtr *router) AddRoute(path string, h Handler) Router {
if rtr.sealed {
panic(fmt.Sprintf("router sealed; cannot register %s route handler", path))
}
if !sdk.IsAlphaNumeric(path) {
panic("route expressions can only contain alphanumeric characters")
}
if rtr.HasRoute(path) {
panic(fmt.Sprintf("route %s has already been registered", path))
}
rtr.routes[path] = h
return rtr
}
// HasRoute returns true if the router has a path registered or false otherwise.
func (rtr *router) HasRoute(path string) bool {
return rtr.routes[path] != nil
}
// GetRoute returns a Handler for a given path.
func (rtr *router) GetRoute(path string) Handler {
if !rtr.HasRoute(path) {
panic(fmt.Sprintf("route does not exist for path %s", path))
}
return rtr.routes[path]
}

View File

@ -0,0 +1,28 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)
func testHandler(sdk.Context, exported.Evidence) error { return nil }
func TestRouterSeal(t *testing.T) {
r := types.NewRouter()
r.Seal()
require.Panics(t, func() { r.AddRoute("test", nil) })
require.Panics(t, func() { r.Seal() })
}
func TestRouter(t *testing.T) {
r := types.NewRouter()
r.AddRoute("test", testHandler)
require.True(t, r.HasRoute("test"))
require.Panics(t, func() { r.AddRoute("test", testHandler) })
require.Panics(t, func() { r.AddRoute(" ", testHandler) })
}

View File

@ -0,0 +1,135 @@
/*
Common testing types and utility functions and methods to be used in unit and
integration testing of the evidence module.
*/
// DONTCOVER
package types
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/exported"
"gopkg.in/yaml.v2"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash"
cmn "github.com/tendermint/tendermint/libs/common"
)
var (
_ exported.Evidence = (*TestEquivocationEvidence)(nil)
TestingCdc = codec.New()
)
const (
TestEvidenceRouteEquivocation = "TestEquivocationEvidence"
TestEvidenceTypeEquivocation = "equivocation"
)
type (
TestVote struct {
Height int64
Round int64
Timestamp time.Time
ValidatorAddress cmn.HexBytes
Signature []byte
}
TestCanonicalVote struct {
Height int64
Round int64
Timestamp time.Time
ChainID string
}
TestEquivocationEvidence struct {
Power int64
TotalPower int64
PubKey crypto.PubKey
VoteA TestVote
VoteB TestVote
}
)
func init() {
RegisterCodec(TestingCdc)
codec.RegisterCrypto(TestingCdc)
TestingCdc.RegisterConcrete(TestEquivocationEvidence{}, "test/TestEquivocationEvidence", nil)
}
func (e TestEquivocationEvidence) Route() string { return TestEvidenceRouteEquivocation }
func (e TestEquivocationEvidence) Type() string { return TestEvidenceTypeEquivocation }
func (e TestEquivocationEvidence) GetHeight() int64 { return e.VoteA.Height }
func (e TestEquivocationEvidence) GetValidatorPower() int64 { return e.Power }
func (e TestEquivocationEvidence) GetTotalPower() int64 { return e.TotalPower }
func (e TestEquivocationEvidence) String() string {
bz, _ := yaml.Marshal(e)
return string(bz)
}
func (e TestEquivocationEvidence) GetConsensusAddress() sdk.ConsAddress {
return sdk.ConsAddress(e.PubKey.Address())
}
func (e TestEquivocationEvidence) ValidateBasic() error {
if e.VoteA.Height != e.VoteB.Height ||
e.VoteA.Round != e.VoteB.Round {
return fmt.Errorf("H/R/S does not match (got %v and %v)", e.VoteA, e.VoteB)
}
if !bytes.Equal(e.VoteA.ValidatorAddress, e.VoteB.ValidatorAddress) {
return fmt.Errorf(
"validator addresses do not match (got %X and %X)",
e.VoteA.ValidatorAddress,
e.VoteB.ValidatorAddress,
)
}
return nil
}
func (e TestEquivocationEvidence) Hash() cmn.HexBytes {
return tmhash.Sum(TestingCdc.MustMarshalBinaryBare(e))
}
func (v TestVote) SignBytes(chainID string) []byte {
scv := TestCanonicalVote{
Height: v.Height,
Round: v.Round,
Timestamp: v.Timestamp,
ChainID: chainID,
}
bz, _ := TestingCdc.MarshalBinaryLengthPrefixed(scv)
return bz
}
func TestEquivocationHandler(k interface{}) Handler {
return func(ctx sdk.Context, e exported.Evidence) error {
if err := e.ValidateBasic(); err != nil {
return err
}
ee, ok := e.(TestEquivocationEvidence)
if !ok {
return fmt.Errorf("unexpected evidence type: %T", e)
}
if !ee.PubKey.VerifyBytes(ee.VoteA.SignBytes(ctx.ChainID()), ee.VoteA.Signature) {
return errors.New("failed to verify vote A signature")
}
if !ee.PubKey.VerifyBytes(ee.VoteB.SignBytes(ctx.ChainID()), ee.VoteB.Signature) {
return errors.New("failed to verify vote B signature")
}
// TODO: Slashing!
return nil
}
}

167
x/evidence/module.go Normal file
View File

@ -0,0 +1,167 @@
package evidence
import (
"encoding/json"
"fmt"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/evidence/client"
"github.com/cosmos/cosmos-sdk/x/evidence/client/cli"
"github.com/cosmos/cosmos-sdk/x/evidence/client/rest"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
)
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
// TODO: Enable simulation once concrete types are defined.
// _ module.AppModuleSimulation = AppModuleSimulation{}
)
// ----------------------------------------------------------------------------
// AppModuleBasic
// ----------------------------------------------------------------------------
// AppModuleBasic implements the AppModuleBasic interface for the evidence module.
type AppModuleBasic struct {
evidenceHandlers []client.EvidenceHandler // client evidence submission handlers
}
func NewAppModuleBasic(evidenceHandlers ...client.EvidenceHandler) AppModuleBasic {
return AppModuleBasic{
evidenceHandlers: evidenceHandlers,
}
}
// Name returns the evidence module's name.
func (AppModuleBasic) Name() string {
return ModuleName
}
// RegisterCodec registers the evidence module's types to the provided codec.
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
RegisterCodec(cdc)
}
// DefaultGenesis returns the evidence module's default genesis state.
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
return ModuleCdc.MustMarshalJSON(DefaultGenesisState())
}
// ValidateGenesis performs genesis state validation for the evidence module.
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
var gs GenesisState
err := ModuleCdc.UnmarshalJSON(bz, &gs)
if err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err)
}
return gs.Validate()
}
// RegisterRESTRoutes registers the evidence module's REST service handlers.
func (a AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
evidenceRESTHandlers := make([]rest.EvidenceRESTHandler, len(a.evidenceHandlers))
for i, evidenceHandler := range a.evidenceHandlers {
evidenceRESTHandlers[i] = evidenceHandler.RESTHandler(ctx)
}
rest.RegisterRoutes(ctx, rtr, evidenceRESTHandlers)
}
// GetTxCmd returns the evidence module's root tx command.
func (a AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
evidenceCLIHandlers := make([]*cobra.Command, len(a.evidenceHandlers))
for i, evidenceHandler := range a.evidenceHandlers {
evidenceCLIHandlers[i] = evidenceHandler.CLIHandler(cdc)
}
return cli.GetTxCmd(StoreKey, cdc, evidenceCLIHandlers)
}
// GetTxCmd returns the evidence module's root query command.
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetQueryCmd(StoreKey, cdc)
}
// ----------------------------------------------------------------------------
// AppModule
// ----------------------------------------------------------------------------
// AppModule implements the AppModule interface for the evidence module.
type AppModule struct {
AppModuleBasic
keeper Keeper
}
func NewAppModule(keeper Keeper) AppModule {
return AppModule{
AppModuleBasic: NewAppModuleBasic(),
keeper: keeper,
}
}
// Name returns the evidence module's name.
func (am AppModule) Name() string {
return am.AppModuleBasic.Name()
}
// Route returns the evidence module's message routing key.
func (AppModule) Route() string {
return RouterKey
}
// QuerierRoute returns the evidence module's query routing key.
func (AppModule) QuerierRoute() string {
return QuerierRoute
}
// NewHandler returns the evidence module's message Handler.
func (am AppModule) NewHandler() sdk.Handler {
return NewHandler(am.keeper)
}
// NewQuerierHandler returns the evidence module's Querier.
func (am AppModule) NewQuerierHandler() sdk.Querier {
return NewQuerier(am.keeper)
}
// RegisterInvariants registers the evidence module's invariants.
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
// InitGenesis performs the evidence module's genesis initialization It returns
// no validator updates.
func (am AppModule) InitGenesis(ctx sdk.Context, bz json.RawMessage) []abci.ValidatorUpdate {
var gs GenesisState
err := ModuleCdc.UnmarshalJSON(bz, &gs)
if err != nil {
panic(fmt.Sprintf("failed to unmarshal %s genesis state: %s", ModuleName, err))
}
InitGenesis(ctx, am.keeper, gs)
return []abci.ValidatorUpdate{}
}
// ExportGenesis returns the evidence module's exported genesis state as raw JSON bytes.
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
return ModuleCdc.MustMarshalJSON(ExportGenesis(ctx, am.keeper))
}
// BeginBlock executes all ABCI BeginBlock logic respective to the evidence module.
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
// EndBlock executes all ABCI EndBlock logic respective to the evidence module. It
// returns no validator updates.
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}

View File

@ -0,0 +1,58 @@
# Concepts
## Evidence
Any concrete type of evidence submitted to the `x/evidence` module must fulfill the
`Evidence` contract outlined below. Not all concrete types of evidence will fulfill
this contract in the same way and some data may be entirely irrelevant to certain
types of evidence.
```go
type Evidence interface {
Route() string
Type() string
String() string
Hash() HexBytes
ValidateBasic() error
// The consensus address of the malicious validator at time of infraction
GetConsensusAddress() ConsAddress
// Height at which the infraction occurred
GetHeight() int64
// The total power of the malicious validator at time of infraction
GetValidatorPower() int64
// The total validator set power at time of infraction
GetTotalPower() int64
}
```
## Registration & Handling
The `x/evidence` module must first know about all types of evidence it is expected
to handle. This is accomplished by registering the `Route` method in the `Evidence`
contract with what is known as a `Router` (defined below). The `Router` accepts
`Evidence` and attempts to find the corresponding `Handler` for the `Evidence`
via the `Route` method.
```go
type Router interface {
AddRoute(r string, h Handler) Router
HasRoute(r string) bool
GetRoute(path string) Handler
Seal()
Sealed() bool
}
```
The `Handler` (defined below) is responsible for executing the entirety of the
business logic for handling `Evidence`. This typically includes validating the
evidence, both stateless checks via `ValidateBasic` and stateful checks via any
keepers provided to the `Handler`. In addition, the `Handler` may also perform
capabilities such as slashing and jailing a validator.
```go
type Handler func(Context, Evidence) error
```

View File

@ -0,0 +1,12 @@
# State
Currently the `x/evidence` module only stores valid submitted `Evidence` in state.
The evidence state is also stored and exported in the `x/evidence` module's `GenesisState`.
```go
type GenesisState struct {
Evidence []Evidence `json:"evidence" yaml:"evidence"`
}
```
All `Evidence` is retrieved and stored via a prefix `KVStore` using prefix `0x00` (`KeyPrefixEvidence`).

View File

@ -0,0 +1,42 @@
# Messages
## MsgSubmitEvidence
Evidence is submitted through a `MsgSubmitEvidence` message:
```go
type MsgSubmitEvidence struct {
Evidence Evidence
Submitter AccAddress
}
```
Note, the `Evidence` of a `MsgSubmitEvidence` message must have a corresponding
`Handler` registered with the `x/evidence` module's `Router` in order to be processed
and routed correctly.
Given the `Evidence` is registered with a corresponding `Handler`, it is processed
as follows:
```go
func SubmitEvidence(ctx Context, evidence Evidence) error {
if _, ok := GetEvidence(ctx, evidence.Hash()); ok {
return ErrEvidenceExists(codespace, evidence.Hash().String())
}
if !router.HasRoute(evidence.Route()) {
return ErrNoEvidenceHandlerExists(codespace, evidence.Route())
}
handler := router.GetRoute(evidence.Route())
if err := handler(ctx, evidence); err != nil {
return ErrInvalidEvidence(codespace, err.Error())
}
SetEvidence(ctx, evidence)
return nil
}
```
First, there must not already exist valid submitted `Evidence` of the exact same
type. Secondly, the `Evidence` is routed to the `Handler` and executed. Finally,
if there is no error in handling the `Evidence`, it is persisted to state.

View File

@ -0,0 +1,14 @@
# Events
The `x/evidence` module emits the following events:
## Handlers
### MsgSubmitEvidence
| Type | Attribute Key | Attribute Value |
| --------------- | ------------- | --------------- |
| submit_evidence | evidence_hash | {evidenceHash} |
| message | module | evidence |
| message | sender | {senderAddress} |
| message | action | submit_evidence |

28
x/evidence/spec/README.md Normal file
View File

@ -0,0 +1,28 @@
# Evidence Module Specification
## Abstract
`x/evidence` is an implementation of a Cosmos SDK module, per [ADR 009](./../../../docs/architecture/adr-009-evidence-module.md),
that allows for the submission and handling of arbitrary evidence of misbehavior such
as equivocation and counterfactual signing.
The evidence module differs from standard evidence handling which typically expects the
underlying consensus engine, e.g. Tendermint, to automatically submit evidence when
it is discovered by allowing clients and foreign chains to submit more complex evidence
directly.
All concrete evidence types must implement the `Evidence` interface contract. Submitted
`Evidence` is first routed through the evidence module's `Router` in which it attempts
to find a corresponding registered `Handler` for that specific `Evidence` type.
Each `Evidence` type must have a `Handler` registered with the evidence module's
keeper in order for it to be successfully routed and executed.
Each corresponding handler must also fulfill the `Handler` interface contract. The
`Handler` for a given `Evidence` type can perform any arbitrary state transitions
such as slashing, jailing, and tombstoning.
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**

View File

@ -3,6 +3,8 @@ package types
import (
"fmt"
"regexp"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var (
@ -49,7 +51,7 @@ func (rtr *router) AddRoute(path string, h Handler) Router {
panic("router sealed; cannot add route handler")
}
if !isAlphaNumeric(path) {
if !sdk.IsAlphaNumeric(path) {
panic("route expressions can only contain alphanumeric characters")
}
if rtr.HasRoute(path) {