Merge PR #5240: x/evidence module implementation
This commit is contained in:
parent
82a2c5d6d8
commit
95ddc242ad
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
114
simapp/app.go
114
simapp/app.go
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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, ¶ms)
|
||||
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, ¶ms)
|
||||
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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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") })
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// evidence module events
|
||||
const (
|
||||
EventTypeSubmitEvidence = "submit_evidence"
|
||||
|
||||
AttributeValueCategory = "evidence"
|
||||
AttributeKeyEvidenceHash = "evidence_hash"
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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}
|
||||
)
|
|
@ -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}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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) })
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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{}
|
||||
}
|
|
@ -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
|
||||
```
|
|
@ -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`).
|
|
@ -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.
|
|
@ -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 |
|
|
@ -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)**
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue