Merge pull request #1011 from cosmos/cwgoes/slashing
Implement slashing (v1)
This commit is contained in:
commit
0fb2bbdfef
|
@ -11,13 +11,13 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/btcsuite/btcd"
|
name = "github.com/btcsuite/btcd"
|
||||||
packages = ["btcec"]
|
packages = ["btcec"]
|
||||||
revision = "1432d294a5b055c297457c25434efbf13384cc46"
|
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/cosmos/bech32cosmos"
|
name = "github.com/cosmos/bech32cosmos"
|
||||||
packages = ["go"]
|
packages = ["go"]
|
||||||
revision = "efca97cd8c0852c44d96dfdcc70565c306eddff0"
|
revision = "c12e4b6ed52acc1d35fea49acec35f980914dc95"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
|
@ -256,7 +256,7 @@
|
||||||
"leveldb/table",
|
"leveldb/table",
|
||||||
"leveldb/util"
|
"leveldb/util"
|
||||||
]
|
]
|
||||||
revision = "e6d6b529196422703d54ff5c40e79809ec2020b3"
|
revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/abci"
|
name = "github.com/tendermint/abci"
|
||||||
|
@ -267,8 +267,8 @@
|
||||||
"server",
|
"server",
|
||||||
"types"
|
"types"
|
||||||
]
|
]
|
||||||
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f"
|
revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9"
|
||||||
version = "v0.10.3"
|
version = "v0.11.0-rc0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -298,17 +298,14 @@
|
||||||
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
|
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
|
||||||
version = "v0.6.2"
|
version = "v0.6.2"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/tendermint/go-wire"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
|
||||||
version = "v0.7.3"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/iavl"
|
name = "github.com/tendermint/iavl"
|
||||||
packages = ["."]
|
packages = [
|
||||||
revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787"
|
".",
|
||||||
version = "v0.7.0"
|
"sha256truncated"
|
||||||
|
]
|
||||||
|
revision = "c9206995e8f948e99927f5084a88a7e94ca256da"
|
||||||
|
version = "v0.8.0-rc0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
|
@ -319,6 +316,9 @@
|
||||||
"consensus",
|
"consensus",
|
||||||
"consensus/types",
|
"consensus/types",
|
||||||
"evidence",
|
"evidence",
|
||||||
|
"libs/events",
|
||||||
|
"libs/pubsub",
|
||||||
|
"libs/pubsub/query",
|
||||||
"lite",
|
"lite",
|
||||||
"lite/client",
|
"lite/client",
|
||||||
"lite/errors",
|
"lite/errors",
|
||||||
|
@ -347,8 +347,8 @@
|
||||||
"types/priv_validator",
|
"types/priv_validator",
|
||||||
"version"
|
"version"
|
||||||
]
|
]
|
||||||
revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2"
|
revision = "73de99ecab464208f6ea3a96525f4e4b78425e61"
|
||||||
version = "v0.19.5-rc1"
|
version = "v0.20.0-rc0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/tmlibs"
|
name = "github.com/tendermint/tmlibs"
|
||||||
|
@ -361,12 +361,10 @@
|
||||||
"db",
|
"db",
|
||||||
"flowrate",
|
"flowrate",
|
||||||
"log",
|
"log",
|
||||||
"merkle",
|
"merkle"
|
||||||
"pubsub",
|
|
||||||
"pubsub/query"
|
|
||||||
]
|
]
|
||||||
revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd"
|
revision = "d970af87248a4e162590300dbb74e102183a417d"
|
||||||
version = "v0.8.3-rc0"
|
version = "v0.8.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -382,7 +380,7 @@
|
||||||
"ripemd160",
|
"ripemd160",
|
||||||
"salsa20/salsa"
|
"salsa20/salsa"
|
||||||
]
|
]
|
||||||
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
|
revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -396,13 +394,13 @@
|
||||||
"internal/timeseries",
|
"internal/timeseries",
|
||||||
"trace"
|
"trace"
|
||||||
]
|
]
|
||||||
revision = "57065200b4b034a1c8ad54ff77069408c2218ae6"
|
revision = "1e491301e022f8f977054da4c2d852decd59571f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix"]
|
||||||
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
|
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/text"
|
name = "golang.org/x/text"
|
||||||
|
@ -463,6 +461,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "f0c6224dc5f30c1a7dea716d619665831ea0932b0eb9afc6ac897dbc459134fa"
|
inputs-digest = "a6a5d886519fa9ca97a23715faa852ee14ecb5337e03641d19ea3d3d1c392fee"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
24
Gopkg.toml
24
Gopkg.toml
|
@ -54,43 +54,37 @@
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/abci"
|
name = "github.com/tendermint/abci"
|
||||||
version = "~0.10.3"
|
version = "0.11.0-rc0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/go-crypto"
|
name = "github.com/tendermint/go-crypto"
|
||||||
version = "~0.6.2"
|
version = "~0.6.2"
|
||||||
|
|
||||||
[[override]]
|
|
||||||
name = "github.com/tendermint/go-wire"
|
|
||||||
version = "0.7.3"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "github.com/tendermint/go-amino"
|
||||||
version = "~0.9.9"
|
version = "=0.9.9"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/iavl"
|
name = "github.com/tendermint/iavl"
|
||||||
version = "~0.7.0"
|
version = "0.8.0-rc0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
version = "0.19.5-rc1"
|
version = "0.20.0-rc0"
|
||||||
|
|
||||||
[[override]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/tmlibs"
|
name = "github.com/tendermint/tmlibs"
|
||||||
version = "~0.8.3-rc0"
|
version = "~0.8.3-rc0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/cosmos/bech32cosmos"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
# this got updated and broke, so locked to an old working commit ...
|
# this got updated and broke, so locked to an old working commit ...
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/cosmos/bech32cosmos"
|
|
||||||
branch = "master"
|
|
||||||
|
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
|
||||||
|
|
|
@ -65,9 +65,10 @@ type BaseApp struct {
|
||||||
// See methods setCheckState and setDeliverState.
|
// See methods setCheckState and setDeliverState.
|
||||||
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
|
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
|
||||||
// QUESTION: should we put valUpdates in the deliverState.ctx?
|
// QUESTION: should we put valUpdates in the deliverState.ctx?
|
||||||
checkState *state // for CheckTx
|
checkState *state // for CheckTx
|
||||||
deliverState *state // for DeliverTx
|
deliverState *state // for DeliverTx
|
||||||
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
valUpdates []abci.Validator // cached validator changes from DeliverTx
|
||||||
|
absentValidators [][]byte // absent validators from begin block
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ abci.Application = (*BaseApp)(nil)
|
var _ abci.Application = (*BaseApp)(nil)
|
||||||
|
@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
||||||
if app.beginBlocker != nil {
|
if app.beginBlocker != nil {
|
||||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||||
}
|
}
|
||||||
|
// set the absent validators for addition to context in deliverTx
|
||||||
|
app.absentValidators = req.AbsentValidators
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||||
} else {
|
} else {
|
||||||
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
||||||
|
ctx = ctx.WithAbsentValidators(app.absentValidators)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate a DeliverTx for gas calculation
|
// Simulate a DeliverTx for gas calculation
|
||||||
|
|
|
@ -183,7 +183,7 @@ func TestInitChainer(t *testing.T) {
|
||||||
|
|
||||||
// set initChainer and try again - should see the value
|
// set initChainer and try again - should see the value
|
||||||
app.SetInitChainer(initChainer)
|
app.SetInitChainer(initChainer)
|
||||||
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
app.InitChain(abci.RequestInitChain{GenesisBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
||||||
app.Commit()
|
app.Commit()
|
||||||
res = app.Query(query)
|
res = app.Query(query)
|
||||||
assert.Equal(t, value, res.Value)
|
assert.Equal(t, value, res.Value)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,10 +35,11 @@ type GaiaApp struct {
|
||||||
cdc *wire.Codec
|
cdc *wire.Codec
|
||||||
|
|
||||||
// keys to access the substores
|
// keys to access the substores
|
||||||
keyMain *sdk.KVStoreKey
|
keyMain *sdk.KVStoreKey
|
||||||
keyAccount *sdk.KVStoreKey
|
keyAccount *sdk.KVStoreKey
|
||||||
keyIBC *sdk.KVStoreKey
|
keyIBC *sdk.KVStoreKey
|
||||||
keyStake *sdk.KVStoreKey
|
keyStake *sdk.KVStoreKey
|
||||||
|
keySlashing *sdk.KVStoreKey
|
||||||
|
|
||||||
// Manage getting and setting accounts
|
// Manage getting and setting accounts
|
||||||
accountMapper auth.AccountMapper
|
accountMapper auth.AccountMapper
|
||||||
|
@ -45,6 +47,7 @@ type GaiaApp struct {
|
||||||
coinKeeper bank.Keeper
|
coinKeeper bank.Keeper
|
||||||
ibcMapper ibc.Mapper
|
ibcMapper ibc.Mapper
|
||||||
stakeKeeper stake.Keeper
|
stakeKeeper stake.Keeper
|
||||||
|
slashingKeeper slashing.Keeper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||||
|
@ -52,12 +55,13 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||||
|
|
||||||
// create your application object
|
// create your application object
|
||||||
var app = &GaiaApp{
|
var app = &GaiaApp{
|
||||||
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
|
||||||
cdc: cdc,
|
cdc: cdc,
|
||||||
keyMain: sdk.NewKVStoreKey("main"),
|
keyMain: sdk.NewKVStoreKey("main"),
|
||||||
keyAccount: sdk.NewKVStoreKey("acc"),
|
keyAccount: sdk.NewKVStoreKey("acc"),
|
||||||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||||
keyStake: sdk.NewKVStoreKey("stake"),
|
keyStake: sdk.NewKVStoreKey("stake"),
|
||||||
|
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// define the accountMapper
|
// define the accountMapper
|
||||||
|
@ -71,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||||
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
app.coinKeeper = bank.NewKeeper(app.accountMapper)
|
||||||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
||||||
|
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace))
|
||||||
|
|
||||||
// register message routes
|
// register message routes
|
||||||
app.Router().
|
app.Router().
|
||||||
|
@ -80,9 +85,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||||
|
|
||||||
// initialize BaseApp
|
// initialize BaseApp
|
||||||
app.SetInitChainer(app.initChainer)
|
app.SetInitChainer(app.initChainer)
|
||||||
|
app.SetBeginBlocker(slashing.NewBeginBlocker(app.slashingKeeper))
|
||||||
app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper))
|
app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper))
|
||||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake)
|
|
||||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
||||||
|
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing)
|
||||||
err := app.LoadLatestVersion(app.keyMain)
|
err := app.LoadLatestVersion(app.keyMain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmn.Exit(err.Error())
|
cmn.Exit(err.Error())
|
||||||
|
@ -97,6 +103,7 @@ func MakeCodec() *wire.Codec {
|
||||||
ibc.RegisterWire(cdc)
|
ibc.RegisterWire(cdc)
|
||||||
bank.RegisterWire(cdc)
|
bank.RegisterWire(cdc)
|
||||||
stake.RegisterWire(cdc)
|
stake.RegisterWire(cdc)
|
||||||
|
slashing.RegisterWire(cdc)
|
||||||
auth.RegisterWire(cdc)
|
auth.RegisterWire(cdc)
|
||||||
sdk.RegisterWire(cdc)
|
sdk.RegisterWire(cdc)
|
||||||
wire.RegisterCrypto(cdc)
|
wire.RegisterCrypto(cdc)
|
||||||
|
@ -105,7 +112,8 @@ func MakeCodec() *wire.Codec {
|
||||||
|
|
||||||
// custom logic for gaia initialization
|
// custom logic for gaia initialization
|
||||||
func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
stateJSON := req.AppStateBytes
|
stateJSON := req.GenesisBytes
|
||||||
|
// TODO is this now the whole genesis file?
|
||||||
|
|
||||||
var genesisState GenesisState
|
var genesisState GenesisState
|
||||||
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
|
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
|
||||||
|
|
|
@ -104,7 +104,7 @@ func MakeCodec() *wire.Codec {
|
||||||
|
|
||||||
// Custom logic for basecoin initialization
|
// Custom logic for basecoin initialization
|
||||||
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
stateJSON := req.AppStateBytes
|
stateJSON := req.GenesisBytes
|
||||||
|
|
||||||
genesisState := new(types.GenesisState)
|
genesisState := new(types.GenesisState)
|
||||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||||
|
|
|
@ -118,7 +118,7 @@ func MakeCodec() *wire.Codec {
|
||||||
// custom logic for democoin initialization
|
// custom logic for democoin initialization
|
||||||
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
|
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
|
||||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
stateJSON := req.AppStateBytes
|
stateJSON := req.GenesisBytes
|
||||||
|
|
||||||
genesisState := new(types.GenesisState)
|
genesisState := new(types.GenesisState)
|
||||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||||
|
|
|
@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) {
|
||||||
cdc := wire.NewCodec()
|
cdc := wire.NewCodec()
|
||||||
auth.RegisterBaseAccount(cdc)
|
auth.RegisterBaseAccount(cdc)
|
||||||
|
|
||||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
|
||||||
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
|
||||||
stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace)
|
stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace)
|
||||||
|
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
|
||||||
addr := sdk.Address([]byte("some-address"))
|
addr := sdk.Address([]byte("some-address"))
|
||||||
|
|
||||||
bi := stakeKeeper.getBondInfo(ctx, addr)
|
bi := stakeKeeper.getBondInfo(ctx, addr)
|
||||||
|
|
|
@ -88,7 +88,7 @@ type GenesisJSON struct {
|
||||||
// with key/value pairs
|
// with key/value pairs
|
||||||
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
|
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
stateJSON := req.AppStateBytes
|
stateJSON := req.GenesisBytes
|
||||||
|
|
||||||
genesisState := new(GenesisJSON)
|
genesisState := new(GenesisJSON)
|
||||||
err := json.Unmarshal(stateJSON, genesisState)
|
err := json.Unmarshal(stateJSON, genesisState)
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestInitApp(t *testing.T) {
|
||||||
|
|
||||||
//TODO test validators in the init chain?
|
//TODO test validators in the init chain?
|
||||||
req := abci.RequestInitChain{
|
req := abci.RequestInitChain{
|
||||||
AppStateBytes: appState,
|
GenesisBytes: appState,
|
||||||
}
|
}
|
||||||
app.InitChain(req)
|
app.InitChain(req)
|
||||||
app.Commit()
|
app.Commit()
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Context struct {
|
||||||
|
|
||||||
// create a new context
|
// create a new context
|
||||||
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context {
|
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context {
|
||||||
|
|
||||||
c := Context{
|
c := Context{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
pst: newThePast(),
|
pst: newThePast(),
|
||||||
|
@ -43,6 +44,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt
|
||||||
c = c.WithIsCheckTx(isCheckTx)
|
c = c.WithIsCheckTx(isCheckTx)
|
||||||
c = c.WithTxBytes(txBytes)
|
c = c.WithTxBytes(txBytes)
|
||||||
c = c.WithLogger(logger)
|
c = c.WithLogger(logger)
|
||||||
|
c = c.WithAbsentValidators(nil)
|
||||||
c = c.WithGasMeter(NewInfiniteGasMeter())
|
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
@ -128,6 +130,7 @@ const (
|
||||||
contextKeyIsCheckTx
|
contextKeyIsCheckTx
|
||||||
contextKeyTxBytes
|
contextKeyTxBytes
|
||||||
contextKeyLogger
|
contextKeyLogger
|
||||||
|
contextKeyAbsentValidators
|
||||||
contextKeyGasMeter
|
contextKeyGasMeter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -157,6 +160,9 @@ func (c Context) TxBytes() []byte {
|
||||||
func (c Context) Logger() log.Logger {
|
func (c Context) Logger() log.Logger {
|
||||||
return c.Value(contextKeyLogger).(log.Logger)
|
return c.Value(contextKeyLogger).(log.Logger)
|
||||||
}
|
}
|
||||||
|
func (c Context) AbsentValidators() [][]byte {
|
||||||
|
return c.Value(contextKeyAbsentValidators).([][]byte)
|
||||||
|
}
|
||||||
func (c Context) GasMeter() GasMeter {
|
func (c Context) GasMeter() GasMeter {
|
||||||
return c.Value(contextKeyGasMeter).(GasMeter)
|
return c.Value(contextKeyGasMeter).(GasMeter)
|
||||||
}
|
}
|
||||||
|
@ -182,6 +188,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context {
|
||||||
func (c Context) WithLogger(logger log.Logger) Context {
|
func (c Context) WithLogger(logger log.Logger) Context {
|
||||||
return c.withValue(contextKeyLogger, logger)
|
return c.withValue(contextKeyLogger, logger)
|
||||||
}
|
}
|
||||||
|
func (c Context) WithAbsentValidators(AbsentValidators [][]byte) Context {
|
||||||
|
return c.withValue(contextKeyAbsentValidators, AbsentValidators)
|
||||||
|
}
|
||||||
func (c Context) WithGasMeter(meter GasMeter) Context {
|
func (c Context) WithGasMeter(meter GasMeter) Context {
|
||||||
return c.withValue(contextKeyGasMeter, meter)
|
return c.withValue(contextKeyGasMeter, meter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,11 @@ type ValidatorSet interface {
|
||||||
IterateValidatorsBonded(Context,
|
IterateValidatorsBonded(Context,
|
||||||
func(index int64, validator Validator) (stop bool))
|
func(index int64, validator Validator) (stop bool))
|
||||||
|
|
||||||
Validator(Context, Address) Validator // get a particular validator by owner address
|
Validator(Context, Address) Validator // get a particular validator by owner address
|
||||||
TotalPower(Context) Rat // total power of the validator set
|
TotalPower(Context) Rat // total power of the validator set
|
||||||
|
Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction
|
||||||
|
Revoke(Context, crypto.PubKey) // revoke a validator
|
||||||
|
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
|
||||||
}
|
}
|
||||||
|
|
||||||
//_______________________________________________________________________________
|
//_______________________________________________________________________________
|
||||||
|
|
|
@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags {
|
||||||
return append(t, a...)
|
return append(t, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn tags into KVPair list
|
||||||
|
func (t Tags) ToKVPairs() []cmn.KVPair {
|
||||||
|
return []cmn.KVPair(t)
|
||||||
|
}
|
||||||
|
|
||||||
// New variadic tags, must be k string, v []byte repeating
|
// New variadic tags, must be k string, v []byte repeating
|
||||||
func NewTags(tags ...interface{}) Tags {
|
func NewTags(tags ...interface{}) Tags {
|
||||||
var ret Tags
|
var ret Tags
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
//nolint
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Local code type
|
||||||
|
type CodeType = sdk.CodeType
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Default slashing codespace
|
||||||
|
DefaultCodespace sdk.CodespaceType = 10
|
||||||
|
|
||||||
|
// Invalid validator
|
||||||
|
CodeInvalidValidator CodeType = 201
|
||||||
|
// Validator jailed
|
||||||
|
CodeValidatorJailed CodeType = 202
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
|
||||||
|
return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator")
|
||||||
|
}
|
||||||
|
func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
|
||||||
|
return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address")
|
||||||
|
}
|
||||||
|
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
|
||||||
|
return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func codeToDefaultMsg(code CodeType) string {
|
||||||
|
switch code {
|
||||||
|
case CodeInvalidValidator:
|
||||||
|
return "Invalid Validator"
|
||||||
|
case CodeValidatorJailed:
|
||||||
|
return "Validator Jailed"
|
||||||
|
default:
|
||||||
|
return sdk.CodeToDefaultMsg(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func msgOrDefaultMsg(msg string, code CodeType) string {
|
||||||
|
if msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return codeToDefaultMsg(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
|
||||||
|
msg = msgOrDefaultMsg(msg, code)
|
||||||
|
return sdk.NewError(codespace, code, msg)
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHandler(k Keeper) sdk.Handler {
|
||||||
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
// NOTE msg already has validate basic run
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case MsgUnrevoke:
|
||||||
|
return handleMsgUnrevoke(ctx, msg, k)
|
||||||
|
default:
|
||||||
|
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validators must submit a transaction to unrevoke itself after
|
||||||
|
// having been revoked (and thus unbonded) for downtime
|
||||||
|
func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
|
||||||
|
|
||||||
|
// Validator must exist
|
||||||
|
validator := k.stakeKeeper.Validator(ctx, msg.ValidatorAddr)
|
||||||
|
if validator == nil {
|
||||||
|
return ErrNoValidatorForAddress(k.codespace).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := validator.GetPubKey().Address()
|
||||||
|
|
||||||
|
// Signing info must exist
|
||||||
|
info, found := k.getValidatorSigningInfo(ctx, addr)
|
||||||
|
if !found {
|
||||||
|
return ErrNoValidatorForAddress(k.codespace).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot be unrevoked until out of jail
|
||||||
|
if ctx.BlockHeader().Time < info.JailedUntil {
|
||||||
|
return ErrValidatorJailed(k.codespace).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.IsCheckTx() {
|
||||||
|
return sdk.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the starting height (so the validator can't be immediately revoked again)
|
||||||
|
info.StartHeight = ctx.BlockHeight()
|
||||||
|
k.setValidatorSigningInfo(ctx, addr, info)
|
||||||
|
|
||||||
|
// Unrevoke the validator
|
||||||
|
k.stakeKeeper.Unrevoke(ctx, validator.GetPubKey())
|
||||||
|
|
||||||
|
tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes())
|
||||||
|
|
||||||
|
return sdk.Result{
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keeper of the slashing store
|
||||||
|
type Keeper struct {
|
||||||
|
storeKey sdk.StoreKey
|
||||||
|
cdc *wire.Codec
|
||||||
|
stakeKeeper stake.Keeper
|
||||||
|
|
||||||
|
// codespace
|
||||||
|
codespace sdk.CodespaceType
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeeper creates a slashing keeper
|
||||||
|
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, sk stake.Keeper, codespace sdk.CodespaceType) Keeper {
|
||||||
|
keeper := Keeper{
|
||||||
|
storeKey: key,
|
||||||
|
cdc: cdc,
|
||||||
|
stakeKeeper: sk,
|
||||||
|
codespace: codespace,
|
||||||
|
}
|
||||||
|
return keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle a validator signing two blocks at the same height
|
||||||
|
func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, pubkey crypto.PubKey) {
|
||||||
|
logger := ctx.Logger().With("module", "x/slashing")
|
||||||
|
age := ctx.BlockHeader().Time - timestamp
|
||||||
|
|
||||||
|
// Double sign too old
|
||||||
|
if age > MaxEvidenceAge {
|
||||||
|
logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double sign confirmed
|
||||||
|
logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge))
|
||||||
|
k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDoubleSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle a validator signature, must be called once per validator per block
|
||||||
|
func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signed bool) {
|
||||||
|
logger := ctx.Logger().With("module", "x/slashing")
|
||||||
|
height := ctx.BlockHeight()
|
||||||
|
if !signed {
|
||||||
|
logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height))
|
||||||
|
}
|
||||||
|
address := pubkey.Address()
|
||||||
|
|
||||||
|
// Local index, so counts blocks validator *should* have signed
|
||||||
|
// Will use the 0-value default signing info if not present
|
||||||
|
signInfo, _ := k.getValidatorSigningInfo(ctx, address)
|
||||||
|
index := signInfo.IndexOffset % SignedBlocksWindow
|
||||||
|
signInfo.IndexOffset++
|
||||||
|
|
||||||
|
// Update signed block bit array & counter
|
||||||
|
// This counter just tracks the sum of the bit array
|
||||||
|
// That way we avoid needing to read/write the whole array each time
|
||||||
|
previous := k.getValidatorSigningBitArray(ctx, address, index)
|
||||||
|
if previous == signed {
|
||||||
|
// Array value at this index has not changed, no need to update counter
|
||||||
|
} else if previous && !signed {
|
||||||
|
// Array value has changed from signed to unsigned, decrement counter
|
||||||
|
k.setValidatorSigningBitArray(ctx, address, index, false)
|
||||||
|
signInfo.SignedBlocksCounter--
|
||||||
|
} else if !previous && signed {
|
||||||
|
// Array value has changed from unsigned to signed, increment counter
|
||||||
|
k.setValidatorSigningBitArray(ctx, address, index, true)
|
||||||
|
signInfo.SignedBlocksCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
minHeight := signInfo.StartHeight + SignedBlocksWindow
|
||||||
|
if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow {
|
||||||
|
// Downtime confirmed, slash, revoke, and jail the validator
|
||||||
|
logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, MinSignedPerWindow))
|
||||||
|
k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDowntime)
|
||||||
|
k.stakeKeeper.Revoke(ctx, pubkey)
|
||||||
|
signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the updated signing info
|
||||||
|
k.setValidatorSigningInfo(ctx, address, signInfo)
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/abci/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleDoubleSign(t *testing.T) {
|
||||||
|
|
||||||
|
// initial setup
|
||||||
|
ctx, ck, sk, keeper := createTestInput(t)
|
||||||
|
addr, val, amt := addrs[0], pks[0], int64(100)
|
||||||
|
got := stake.NewHandler(sk)(ctx, newTestMsgDeclareCandidacy(addr, val, amt))
|
||||||
|
require.True(t, got.IsOK())
|
||||||
|
sk.Tick(ctx)
|
||||||
|
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}})
|
||||||
|
require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower())
|
||||||
|
|
||||||
|
// double sign less than max age
|
||||||
|
keeper.handleDoubleSign(ctx, 0, 0, val)
|
||||||
|
require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower())
|
||||||
|
ctx = ctx.WithBlockHeader(abci.Header{Time: 300})
|
||||||
|
|
||||||
|
// double sign past max age
|
||||||
|
keeper.handleDoubleSign(ctx, 0, 0, val)
|
||||||
|
require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleAbsentValidator(t *testing.T) {
|
||||||
|
|
||||||
|
// initial setup
|
||||||
|
ctx, ck, sk, keeper := createTestInput(t)
|
||||||
|
addr, val, amt := addrs[0], pks[0], int64(100)
|
||||||
|
sh := stake.NewHandler(sk)
|
||||||
|
slh := NewHandler(keeper)
|
||||||
|
got := sh(ctx, newTestMsgDeclareCandidacy(addr, val, amt))
|
||||||
|
require.True(t, got.IsOK())
|
||||||
|
sk.Tick(ctx)
|
||||||
|
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}})
|
||||||
|
require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower())
|
||||||
|
info, found := keeper.getValidatorSigningInfo(ctx, val.Address())
|
||||||
|
require.False(t, found)
|
||||||
|
require.Equal(t, int64(0), info.StartHeight)
|
||||||
|
require.Equal(t, int64(0), info.IndexOffset)
|
||||||
|
require.Equal(t, int64(0), info.SignedBlocksCounter)
|
||||||
|
require.Equal(t, int64(0), info.JailedUntil)
|
||||||
|
height := int64(0)
|
||||||
|
|
||||||
|
// 1000 first blocks OK
|
||||||
|
for ; height < 1000; height++ {
|
||||||
|
ctx = ctx.WithBlockHeight(height)
|
||||||
|
keeper.handleValidatorSignature(ctx, val, true)
|
||||||
|
}
|
||||||
|
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, int64(0), info.StartHeight)
|
||||||
|
require.Equal(t, SignedBlocksWindow, info.SignedBlocksCounter)
|
||||||
|
|
||||||
|
// 50 blocks missed
|
||||||
|
for ; height < 1050; height++ {
|
||||||
|
ctx = ctx.WithBlockHeight(height)
|
||||||
|
keeper.handleValidatorSignature(ctx, val, false)
|
||||||
|
}
|
||||||
|
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, int64(0), info.StartHeight)
|
||||||
|
require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter)
|
||||||
|
|
||||||
|
// validator should be bonded still
|
||||||
|
validator, _ := sk.GetValidatorByPubKey(ctx, val)
|
||||||
|
require.Equal(t, sdk.Bonded, validator.GetStatus())
|
||||||
|
pool := sk.GetPool(ctx)
|
||||||
|
require.Equal(t, int64(100), pool.BondedTokens)
|
||||||
|
|
||||||
|
// 51st block missed
|
||||||
|
ctx = ctx.WithBlockHeight(height)
|
||||||
|
keeper.handleValidatorSignature(ctx, val, false)
|
||||||
|
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, int64(0), info.StartHeight)
|
||||||
|
require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter)
|
||||||
|
|
||||||
|
// validator should have been revoked
|
||||||
|
validator, _ = sk.GetValidatorByPubKey(ctx, val)
|
||||||
|
require.Equal(t, sdk.Unbonded, validator.GetStatus())
|
||||||
|
|
||||||
|
// unrevocation should fail prior to jail expiration
|
||||||
|
got = slh(ctx, NewMsgUnrevoke(addr))
|
||||||
|
require.False(t, got.IsOK())
|
||||||
|
|
||||||
|
// unrevocation should succeed after jail expiration
|
||||||
|
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)})
|
||||||
|
got = slh(ctx, NewMsgUnrevoke(addr))
|
||||||
|
require.True(t, got.IsOK())
|
||||||
|
|
||||||
|
// validator should be rebonded now
|
||||||
|
validator, _ = sk.GetValidatorByPubKey(ctx, val)
|
||||||
|
require.Equal(t, sdk.Bonded, validator.GetStatus())
|
||||||
|
|
||||||
|
// validator should have been slashed
|
||||||
|
pool = sk.GetPool(ctx)
|
||||||
|
require.Equal(t, int64(99), pool.BondedTokens)
|
||||||
|
|
||||||
|
// validator start height should have been changed
|
||||||
|
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, height, info.StartHeight)
|
||||||
|
require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter)
|
||||||
|
|
||||||
|
// validator should not be immediately revoked again
|
||||||
|
height++
|
||||||
|
ctx = ctx.WithBlockHeight(height)
|
||||||
|
keeper.handleValidatorSignature(ctx, val, false)
|
||||||
|
validator, _ = sk.GetValidatorByPubKey(ctx, val)
|
||||||
|
require.Equal(t, sdk.Bonded, validator.GetStatus())
|
||||||
|
|
||||||
|
// validator should be revoked again after 100 unsigned blocks
|
||||||
|
nextHeight := height + 100
|
||||||
|
for ; height <= nextHeight; height++ {
|
||||||
|
ctx = ctx.WithBlockHeight(height)
|
||||||
|
keeper.handleValidatorSignature(ctx, val, false)
|
||||||
|
}
|
||||||
|
validator, _ = sk.GetValidatorByPubKey(ctx, val)
|
||||||
|
require.Equal(t, sdk.Unbonded, validator.GetStatus())
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = wire.NewCodec()
|
||||||
|
|
||||||
|
// name to identify transaction types
|
||||||
|
const MsgType = "slashing"
|
||||||
|
|
||||||
|
// verify interface at compile time
|
||||||
|
var _ sdk.Msg = &MsgUnrevoke{}
|
||||||
|
|
||||||
|
// MsgUnrevoke - struct for unrevoking revoked validator
|
||||||
|
type MsgUnrevoke struct {
|
||||||
|
ValidatorAddr sdk.Address `json:"address"` // address of the validator owner
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke {
|
||||||
|
return MsgUnrevoke{
|
||||||
|
ValidatorAddr: validatorAddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
func (msg MsgUnrevoke) Type() string { return MsgType }
|
||||||
|
func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} }
|
||||||
|
|
||||||
|
// get the bytes for the message signer to sign on
|
||||||
|
func (msg MsgUnrevoke) GetSignBytes() []byte {
|
||||||
|
b, err := cdc.MarshalJSON(msg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// quick validity check
|
||||||
|
func (msg MsgUnrevoke) ValidateBasic() sdk.Error {
|
||||||
|
if msg.ValidatorAddr == nil {
|
||||||
|
return ErrBadValidatorAddr(DefaultCodespace)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxEvidenceAge - Max age for evidence - 21 days (3 weeks)
|
||||||
|
// TODO Should this be a governance parameter or just modifiable with SoftwareUpgradeProposals?
|
||||||
|
// MaxEvidenceAge = 60 * 60 * 24 * 7 * 3
|
||||||
|
// TODO Temporarily set to 2 minutes for testnets.
|
||||||
|
MaxEvidenceAge int64 = 60 * 2
|
||||||
|
|
||||||
|
// SignedBlocksWindow - sliding window for downtime slashing
|
||||||
|
// TODO Governance parameter?
|
||||||
|
// TODO Temporarily set to 100 blocks for testnets
|
||||||
|
SignedBlocksWindow int64 = 100
|
||||||
|
|
||||||
|
// Downtime slashing threshold - 50%
|
||||||
|
// TODO Governance parameter?
|
||||||
|
MinSignedPerWindow int64 = SignedBlocksWindow / 2
|
||||||
|
|
||||||
|
// Downtime unbond duration
|
||||||
|
// TODO Governance parameter?
|
||||||
|
// TODO Temporarily set to 10 minutes for testnets
|
||||||
|
DowntimeUnbondDuration int64 = 60 * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SlashFractionDoubleSign - currently 5%
|
||||||
|
// TODO Governance parameter?
|
||||||
|
SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20))
|
||||||
|
|
||||||
|
// SlashFractionDowntime - currently 1%
|
||||||
|
// TODO Governance parameter?
|
||||||
|
SlashFractionDowntime = sdk.NewRat(1).Quo(sdk.NewRat(100))
|
||||||
|
)
|
|
@ -0,0 +1,66 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo, found bool) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := store.Get(validatorSigningInfoKey(address))
|
||||||
|
if bz == nil {
|
||||||
|
found = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshalBinary(bz, &info)
|
||||||
|
found = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, info validatorSigningInfo) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := k.cdc.MustMarshalBinary(info)
|
||||||
|
store.Set(validatorSigningInfoKey(address), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64) (signed bool) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := store.Get(validatorSigningBitArrayKey(address, index))
|
||||||
|
if bz == nil {
|
||||||
|
// lazy: treat empty key as unsigned
|
||||||
|
signed = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshalBinary(bz, &signed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address, index int64, signed bool) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
bz := k.cdc.MustMarshalBinary(signed)
|
||||||
|
store.Set(validatorSigningBitArrayKey(address, index), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
type validatorSigningInfo struct {
|
||||||
|
StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unrevoked
|
||||||
|
IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array
|
||||||
|
JailedUntil int64 `json:"jailed_until"` // timestamp validator cannot be unrevoked until
|
||||||
|
SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func validatorSigningInfoKey(v sdk.Address) []byte {
|
||||||
|
return append([]byte{0x01}, v.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stored by *validator* address (not owner address)
|
||||||
|
func validatorSigningBitArrayKey(v sdk.Address, i int64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(b, uint64(i))
|
||||||
|
return append([]byte{0x02}, append(v.Bytes(), b...)...)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSetValidatorSigningInfo(t *testing.T) {
|
||||||
|
ctx, _, _, keeper := createTestInput(t)
|
||||||
|
info, found := keeper.getValidatorSigningInfo(ctx, addrs[0])
|
||||||
|
require.False(t, found)
|
||||||
|
newInfo := validatorSigningInfo{
|
||||||
|
StartHeight: int64(4),
|
||||||
|
IndexOffset: int64(3),
|
||||||
|
JailedUntil: int64(2),
|
||||||
|
SignedBlocksCounter: int64(10),
|
||||||
|
}
|
||||||
|
keeper.setValidatorSigningInfo(ctx, addrs[0], newInfo)
|
||||||
|
info, found = keeper.getValidatorSigningInfo(ctx, addrs[0])
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, info.StartHeight, int64(4))
|
||||||
|
require.Equal(t, info.IndexOffset, int64(3))
|
||||||
|
require.Equal(t, info.JailedUntil, int64(2))
|
||||||
|
require.Equal(t, info.SignedBlocksCounter, int64(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSetValidatorSigningBitArray(t *testing.T) {
|
||||||
|
ctx, _, _, keeper := createTestInput(t)
|
||||||
|
signed := keeper.getValidatorSigningBitArray(ctx, addrs[0], 0)
|
||||||
|
require.False(t, signed) // treat empty key as unsigned
|
||||||
|
keeper.setValidatorSigningBitArray(ctx, addrs[0], 0, true)
|
||||||
|
signed = keeper.getValidatorSigningBitArray(ctx, addrs[0], 0)
|
||||||
|
require.True(t, signed) // now should be signed
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/abci/types"
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addrs = []sdk.Address{
|
||||||
|
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"),
|
||||||
|
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"),
|
||||||
|
}
|
||||||
|
pks = []crypto.PubKey{
|
||||||
|
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"),
|
||||||
|
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
|
||||||
|
}
|
||||||
|
initCoins int64 = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTestCodec() *wire.Codec {
|
||||||
|
cdc := wire.NewCodec()
|
||||||
|
sdk.RegisterWire(cdc)
|
||||||
|
auth.RegisterWire(cdc)
|
||||||
|
bank.RegisterWire(cdc)
|
||||||
|
stake.RegisterWire(cdc)
|
||||||
|
wire.RegisterCrypto(cdc)
|
||||||
|
return cdc
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keeper) {
|
||||||
|
keyAcc := sdk.NewKVStoreKey("acc")
|
||||||
|
keyStake := sdk.NewKVStoreKey("stake")
|
||||||
|
keySlashing := sdk.NewKVStoreKey("slashing")
|
||||||
|
db := dbm.NewMemDB()
|
||||||
|
ms := store.NewCommitMultiStore(db)
|
||||||
|
ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db)
|
||||||
|
err := ms.LoadLatestVersion()
|
||||||
|
require.Nil(t, err)
|
||||||
|
ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewTMLogger(os.Stdout))
|
||||||
|
cdc := createTestCodec()
|
||||||
|
accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{})
|
||||||
|
ck := bank.NewKeeper(accountMapper)
|
||||||
|
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
|
||||||
|
genesis := stake.DefaultGenesisState()
|
||||||
|
genesis.Pool.LooseUnbondedTokens = initCoins * int64(len(addrs))
|
||||||
|
stake.InitGenesis(ctx, sk, genesis)
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ck.AddCoins(ctx, addr, sdk.Coins{
|
||||||
|
{sk.GetParams(ctx).BondDenom, initCoins},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
keeper := NewKeeper(cdc, keySlashing, sk, DefaultCodespace)
|
||||||
|
return ctx, ck, sk, keeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPubKey(pk string) (res crypto.PubKey) {
|
||||||
|
pkBytes, err := hex.DecodeString(pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var pkEd crypto.PubKeyEd25519
|
||||||
|
copy(pkEd[:], pkBytes[:])
|
||||||
|
return pkEd
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAddr(addr string) sdk.Address {
|
||||||
|
res := []byte(addr)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) stake.MsgDeclareCandidacy {
|
||||||
|
return stake.MsgDeclareCandidacy{
|
||||||
|
Description: stake.Description{},
|
||||||
|
ValidatorAddr: address,
|
||||||
|
PubKey: pubKey,
|
||||||
|
Bond: sdk.Coin{"steak", amt},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
abci "github.com/tendermint/abci/types"
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBeginBlocker(sk Keeper) sdk.BeginBlocker {
|
||||||
|
return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||||
|
// Tag the height
|
||||||
|
heightBytes := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height))
|
||||||
|
tags := sdk.NewTags("height", heightBytes)
|
||||||
|
|
||||||
|
// Deal with any equivocation evidence
|
||||||
|
for _, evidence := range req.ByzantineValidators {
|
||||||
|
var pk crypto.PubKey
|
||||||
|
sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk)
|
||||||
|
switch string(evidence.Type) {
|
||||||
|
case tmtypes.DUPLICATE_VOTE:
|
||||||
|
sk.handleDoubleSign(ctx, evidence.Height, evidence.Time, pk)
|
||||||
|
default:
|
||||||
|
ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("Ignored unknown evidence type: %s", string(evidence.Type)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out which validators were absent
|
||||||
|
absent := make(map[crypto.PubKey]struct{})
|
||||||
|
for _, pubkey := range req.AbsentValidators {
|
||||||
|
var pk crypto.PubKey
|
||||||
|
sk.cdc.MustUnmarshalBinary(pubkey, &pk)
|
||||||
|
absent[pk] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all the validators which *should* have signed this block
|
||||||
|
sk.stakeKeeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
|
||||||
|
pubkey := validator.GetPubKey()
|
||||||
|
present := true
|
||||||
|
if _, ok := absent[pubkey]; ok {
|
||||||
|
present = false
|
||||||
|
}
|
||||||
|
sk.handleValidatorSignature(ctx, pubkey, present)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return the begin block response
|
||||||
|
// TODO Return something composable, so other modules can also have BeginBlockers
|
||||||
|
// TODO Add some more tags so clients can track slashing events
|
||||||
|
return abci.ResponseBeginBlock{
|
||||||
|
Tags: tags.ToKVPairs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package slashing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register concrete types on wire codec
|
||||||
|
func RegisterWire(cdc *wire.Codec) {
|
||||||
|
cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cdcEmpty = wire.NewCodec()
|
|
@ -16,6 +16,7 @@ const (
|
||||||
CodeInvalidValidator CodeType = 201
|
CodeInvalidValidator CodeType = 201
|
||||||
CodeInvalidBond CodeType = 202
|
CodeInvalidBond CodeType = 202
|
||||||
CodeInvalidInput CodeType = 203
|
CodeInvalidInput CodeType = 203
|
||||||
|
CodeValidatorJailed CodeType = 204
|
||||||
CodeUnauthorized CodeType = sdk.CodeUnauthorized
|
CodeUnauthorized CodeType = sdk.CodeUnauthorized
|
||||||
CodeInternal CodeType = sdk.CodeInternal
|
CodeInternal CodeType = sdk.CodeInternal
|
||||||
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
||||||
|
|
|
@ -29,14 +29,25 @@ func DefaultGenesisState() GenesisState {
|
||||||
|
|
||||||
// InitGenesis - store genesis parameters
|
// InitGenesis - store genesis parameters
|
||||||
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
|
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
k.setPool(ctx, data.Pool)
|
k.setPool(ctx, data.Pool)
|
||||||
k.setNewParams(ctx, data.Params)
|
k.setNewParams(ctx, data.Params)
|
||||||
for _, validator := range data.Validators {
|
for _, validator := range data.Validators {
|
||||||
k.updateValidator(ctx, validator)
|
|
||||||
|
// set validator
|
||||||
|
k.setValidator(ctx, validator)
|
||||||
|
|
||||||
|
// manually set indexes for the first time
|
||||||
|
k.setValidatorByPubKeyIndex(ctx, validator)
|
||||||
|
k.setValidatorByPowerIndex(ctx, validator, data.Pool)
|
||||||
|
if validator.Status() == sdk.Bonded {
|
||||||
|
store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, bond := range data.Bonds {
|
for _, bond := range data.Bonds {
|
||||||
k.setDelegation(ctx, bond)
|
k.setDelegation(ctx, bond)
|
||||||
}
|
}
|
||||||
|
k.updateBondedValidatorsFull(ctx, store)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteGenesis - output genesis parameters
|
// WriteGenesis - output genesis parameters
|
||||||
|
|
|
@ -55,6 +55,7 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe
|
||||||
|
|
||||||
validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description)
|
validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description)
|
||||||
k.setValidator(ctx, validator)
|
k.setValidator(ctx, validator)
|
||||||
|
k.setValidatorByPubKeyIndex(ctx, validator)
|
||||||
tags := sdk.NewTags(
|
tags := sdk.NewTags(
|
||||||
"action", []byte("declareCandidacy"),
|
"action", []byte("declareCandidacy"),
|
||||||
"validator", msg.ValidatorAddr.Bytes(),
|
"validator", msg.ValidatorAddr.Bytes(),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/wire"
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keeper of the staking store
|
// keeper of the staking store
|
||||||
|
@ -38,6 +39,16 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid
|
||||||
return k.getValidator(store, addr)
|
return k.getValidator(store, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get a single validator by pubkey
|
||||||
|
func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey))
|
||||||
|
if addr == nil {
|
||||||
|
return validator, false
|
||||||
|
}
|
||||||
|
return k.getValidator(store, addr)
|
||||||
|
}
|
||||||
|
|
||||||
// get a single validator (reuse store)
|
// get a single validator (reuse store)
|
||||||
func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) {
|
func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) {
|
||||||
b := store.Get(GetValidatorKey(addr))
|
b := store.Get(GetValidatorKey(addr))
|
||||||
|
@ -51,10 +62,22 @@ func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Val
|
||||||
// set the main record holding validator details
|
// set the main record holding validator details
|
||||||
func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
|
func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
|
||||||
store := ctx.KVStore(k.storeKey)
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
// set main store
|
||||||
bz := k.cdc.MustMarshalBinary(validator)
|
bz := k.cdc.MustMarshalBinary(validator)
|
||||||
store.Set(GetValidatorKey(validator.Owner), bz)
|
store.Set(GetValidatorKey(validator.Owner), bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
// set pointer by pubkey
|
||||||
|
store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the set of all validators with no limits, used during genesis dump
|
// Get the set of all validators with no limits, used during genesis dump
|
||||||
func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) {
|
func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) {
|
||||||
store := ctx.KVStore(k.storeKey)
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
@ -203,8 +226,12 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator
|
||||||
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
|
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
|
||||||
|
|
||||||
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
|
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
|
||||||
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
|
validator = k.unbondValidator(ctx, store, validator)
|
||||||
k.setPool(ctx, pool)
|
|
||||||
|
// need to also clear the cliff validator spot because the revoke has
|
||||||
|
// opened up a new spot which will be filled when
|
||||||
|
// updateValidatorsBonded is called
|
||||||
|
k.clearCliffValidator(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
powerIncreasing := false
|
powerIncreasing := false
|
||||||
|
@ -409,7 +436,7 @@ func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform all the store operations for when a validator status becomes unbonded
|
// perform all the store operations for when a validator status becomes unbonded
|
||||||
func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) {
|
func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator {
|
||||||
pool := k.GetPool(ctx)
|
pool := k.GetPool(ctx)
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
|
@ -431,6 +458,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Va
|
||||||
|
|
||||||
// also remove from the Bonded Validators Store
|
// also remove from the Bonded Validators Store
|
||||||
store.Delete(GetValidatorsBondedKey(validator.PubKey))
|
store.Delete(GetValidatorsBondedKey(validator.PubKey))
|
||||||
|
return validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform all the store operations for when a validator status becomes bonded
|
// perform all the store operations for when a validator status becomes bonded
|
||||||
|
@ -470,6 +498,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) {
|
||||||
store := ctx.KVStore(k.storeKey)
|
store := ctx.KVStore(k.storeKey)
|
||||||
pool := k.getPool(store)
|
pool := k.getPool(store)
|
||||||
store.Delete(GetValidatorKey(address))
|
store.Delete(GetValidatorKey(address))
|
||||||
|
store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey))
|
||||||
store.Delete(GetValidatorsByPowerKey(validator, pool))
|
store.Delete(GetValidatorsByPowerKey(validator, pool))
|
||||||
|
|
||||||
// delete from the current and power weighted validator groups if the validator
|
// delete from the current and power weighted validator groups if the validator
|
||||||
|
@ -656,6 +685,13 @@ func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Poo
|
||||||
store.Set(ValidatorCliffKey, validator.Owner)
|
store.Set(ValidatorCliffKey, validator.Owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear the current validator and power of the validator on the cliff
|
||||||
|
func (k Keeper) clearCliffValidator(ctx sdk.Context) {
|
||||||
|
store := ctx.KVStore(k.storeKey)
|
||||||
|
store.Delete(ValidatorPowerCliffKey)
|
||||||
|
store.Delete(ValidatorCliffKey)
|
||||||
|
}
|
||||||
|
|
||||||
//__________________________________________________________________________
|
//__________________________________________________________________________
|
||||||
|
|
||||||
// Implements ValidatorSet
|
// Implements ValidatorSet
|
||||||
|
@ -749,3 +785,46 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func(
|
||||||
}
|
}
|
||||||
iterator.Close()
|
iterator.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slash a validator
|
||||||
|
func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) {
|
||||||
|
// TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957
|
||||||
|
logger := ctx.Logger().With("module", "x/stake")
|
||||||
|
val, found := k.GetValidatorByPubKey(ctx, pubkey)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address()))
|
||||||
|
}
|
||||||
|
sharesToRemove := val.PoolShares.Amount.Mul(fraction)
|
||||||
|
pool := k.GetPool(ctx)
|
||||||
|
val, pool, burned := val.removePoolShares(pool, sharesToRemove)
|
||||||
|
k.setPool(ctx, pool) // update the pool
|
||||||
|
k.updateValidator(ctx, val) // update the validator, possibly kicking it out
|
||||||
|
logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// revoke a validator
|
||||||
|
func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
|
||||||
|
logger := ctx.Logger().With("module", "x/stake")
|
||||||
|
val, found := k.GetValidatorByPubKey(ctx, pubkey)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey))
|
||||||
|
}
|
||||||
|
val.Revoked = true
|
||||||
|
k.updateValidator(ctx, val) // update the validator, now revoked
|
||||||
|
logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// unrevoke a validator
|
||||||
|
func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
|
||||||
|
logger := ctx.Logger().With("module", "x/stake")
|
||||||
|
val, found := k.GetValidatorByPubKey(ctx, pubkey)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey))
|
||||||
|
}
|
||||||
|
val.Revoked = false
|
||||||
|
k.updateValidator(ctx, val) // update the validator, now unrevoked
|
||||||
|
logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -13,16 +13,17 @@ import (
|
||||||
//nolint
|
//nolint
|
||||||
var (
|
var (
|
||||||
// Keys for store prefixes
|
// Keys for store prefixes
|
||||||
ParamKey = []byte{0x00} // key for parameters relating to staking
|
ParamKey = []byte{0x00} // key for parameters relating to staking
|
||||||
PoolKey = []byte{0x01} // key for the staking pools
|
PoolKey = []byte{0x01} // key for the staking pools
|
||||||
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
|
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
|
||||||
ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators
|
ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey
|
||||||
ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power
|
ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators
|
||||||
ValidatorCliffKey = []byte{0x05} // key for block-local tx index
|
ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power
|
||||||
ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index
|
ValidatorCliffKey = []byte{0x06} // key for block-local tx index
|
||||||
TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated
|
ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index
|
||||||
DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond
|
TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated
|
||||||
IntraTxCounterKey = []byte{0x09} // key for block-local tx index
|
DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond
|
||||||
|
IntraTxCounterKey = []byte{0x10} // key for block-local tx index
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
|
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
|
||||||
|
@ -32,6 +33,11 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte {
|
||||||
return append(ValidatorsKey, ownerAddr.Bytes()...)
|
return append(ValidatorsKey, ownerAddr.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the key for the validator with pubkey
|
||||||
|
func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte {
|
||||||
|
return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
// get the key for the current validator group, ordered like tendermint
|
// get the key for the current validator group, ordered like tendermint
|
||||||
func GetValidatorsBondedKey(pk crypto.PubKey) []byte {
|
func GetValidatorsBondedKey(pk crypto.PubKey) []byte {
|
||||||
addr := pk.Address()
|
addr := pk.Address()
|
||||||
|
|
|
@ -47,6 +47,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti
|
||||||
return Validator{
|
return Validator{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
|
Revoked: false,
|
||||||
PoolShares: NewUnbondedShares(sdk.ZeroRat()),
|
PoolShares: NewUnbondedShares(sdk.ZeroRat()),
|
||||||
DelegatorShares: sdk.ZeroRat(),
|
DelegatorShares: sdk.ZeroRat(),
|
||||||
Description: description,
|
Description: description,
|
||||||
|
@ -154,6 +155,23 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator,
|
||||||
return v, pool
|
return v, pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove pool shares
|
||||||
|
// Returns corresponding tokens, which could be burned (e.g. when slashing
|
||||||
|
// a validator) or redistributed elsewhere
|
||||||
|
func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) {
|
||||||
|
var tokens int64
|
||||||
|
switch v.Status() {
|
||||||
|
case sdk.Unbonded:
|
||||||
|
pool, tokens = pool.removeSharesUnbonded(poolShares)
|
||||||
|
case sdk.Unbonding:
|
||||||
|
pool, tokens = pool.removeSharesUnbonding(poolShares)
|
||||||
|
case sdk.Bonded:
|
||||||
|
pool, tokens = pool.removeSharesBonded(poolShares)
|
||||||
|
}
|
||||||
|
v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares)
|
||||||
|
return v, pool, tokens
|
||||||
|
}
|
||||||
|
|
||||||
// XXX TEST
|
// XXX TEST
|
||||||
// get the power or potential power for a validator
|
// get the power or potential power for a validator
|
||||||
// if bonded, the power is the BondedShares
|
// if bonded, the power is the BondedShares
|
||||||
|
|
Loading…
Reference in New Issue