diff --git a/Gopkg.lock b/Gopkg.lock index 37a32820c2..7f1f6185e6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -253,6 +253,7 @@ revision = "e6d6b529196422703d54ff5c40e79809ec2020b3" [[projects]] + branch = "cwgoes/timestamp-in-evidence" name = "github.com/tendermint/abci" packages = [ "client", @@ -261,8 +262,7 @@ "server", "types" ] - revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" - version = "v0.10.3" + revision = "e196dacf804e3a4ab74252c27e99cb17b39bf501" [[projects]] branch = "master" @@ -293,18 +293,16 @@ version = "v0.6.2" [[projects]] - name = "github.com/tendermint/go-wire" - packages = ["."] - revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c" - version = "v0.7.3" - -[[projects]] + branch = "develop" name = "github.com/tendermint/iavl" - packages = ["."] - revision = "fd37a0fa3a7454423233bc3d5ea828f38e0af787" - version = "v0.7.0" + packages = [ + ".", + "sha256truncated" + ] + revision = "9b6f9c3f49d599cfcf43f1a56c3c22882d1f9f21" [[projects]] + branch = "cwgoes/update-abci" name = "github.com/tendermint/tendermint" packages = [ "blockchain", @@ -313,6 +311,9 @@ "consensus", "consensus/types", "evidence", + "libs/events", + "libs/pubsub", + "libs/pubsub/query", "lite", "lite/client", "lite/errors", @@ -341,8 +342,7 @@ "types/priv_validator", "version" ] - revision = "018e096748bafe1d2d1e69b909e4158f3b26f6b2" - version = "v0.19.5-rc1" + revision = "d3093426f455c4b33cee818e3bbf2d2b83e4b38d" [[projects]] name = "github.com/tendermint/tmlibs" @@ -355,9 +355,7 @@ "db", "flowrate", "log", - "merkle", - "pubsub", - "pubsub/query" + "merkle" ] revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd" version = "v0.8.3-rc0" @@ -457,6 +455,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7540d2ecdb5d7d5084ab4e6132e929bbd501bd6add3006d8f08a6b2c127e0c7d" + inputs-digest = "2bb789b91c383e3a861979c83e61f5f8fd71ea5661837c092353aaacf3091de1" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 5480bc03fc..05b01bfaef 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -52,9 +52,9 @@ name = "github.com/stretchr/testify" version = "~1.2.1" -[[constraint]] +[[override]] name = "github.com/tendermint/abci" - version = "~0.10.3" + branch = "cwgoes/timestamp-in-evidence" [[constraint]] name = "github.com/tendermint/go-crypto" @@ -70,11 +70,11 @@ [[constraint]] name = "github.com/tendermint/iavl" - version = "~0.7.0" + branch = "develop" [[constraint]] name = "github.com/tendermint/tendermint" - version = "0.19.5-rc1" + branch = "cwgoes/update-abci" [[override]] name = "github.com/tendermint/tmlibs" @@ -88,4 +88,3 @@ [prune] go-tests = true unused-packages = true - diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 4ce8a05d9b..baed1fb47d 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -65,9 +65,10 @@ type BaseApp struct { // See methods setCheckState and setDeliverState. // .valUpdates accumulate in DeliverTx and are reset in BeginBlock. // QUESTION: should we put valUpdates in the deliverState.ctx? - checkState *state // for CheckTx - deliverState *state // for DeliverTx - valUpdates []abci.Validator // cached validator changes from DeliverTx + checkState *state // for CheckTx + deliverState *state // for DeliverTx + valUpdates []abci.Validator // cached validator changes from DeliverTx + absentValidators [][]byte // absent validators from begin block } var _ abci.Application = (*BaseApp)(nil) @@ -234,9 +235,9 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error { // NewContext returns a new Context with the correct store, the given header, and nil txBytes. func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { if isCheckTx { - return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger) + return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger, nil) } - return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger) + return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger, nil) } type state struct { @@ -252,7 +253,7 @@ func (app *BaseApp) setCheckState(header abci.Header) { ms := app.cms.CacheMultiStore() app.checkState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, true, nil, app.Logger), + ctx: sdk.NewContext(ms, header, true, nil, app.Logger, nil), } } @@ -260,7 +261,7 @@ func (app *BaseApp) setDeliverState(header abci.Header) { ms := app.cms.CacheMultiStore() app.deliverState = &state{ ms: ms, - ctx: sdk.NewContext(ms, header, false, nil, app.Logger), + ctx: sdk.NewContext(ms, header, false, nil, app.Logger, nil), } } @@ -384,6 +385,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) } + // set the absent validators for addition to context in deliverTx + app.absentValidators = req.AbsentValidators return } @@ -493,6 +496,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk ctx = app.checkState.ctx.WithTxBytes(txBytes) } else { ctx = app.deliverState.ctx.WithTxBytes(txBytes) + ctx = ctx.WithAbsentValidators(app.absentValidators) } // Simulate a DeliverTx for gas calculation diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 61498b1b19..0b825e1ee8 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -183,7 +183,7 @@ func TestInitChainer(t *testing.T) { // set initChainer and try again - should see the value 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() res = app.Query(query) assert.Equal(t, value, res.Value) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index dbecada004..7ad3709a10 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/ibc" + "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -34,10 +35,11 @@ type GaiaApp struct { cdc *wire.Codec // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keyIBC *sdk.KVStoreKey - keyStake *sdk.KVStoreKey + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -45,6 +47,7 @@ type GaiaApp struct { coinKeeper bank.Keeper ibcMapper ibc.Mapper stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper } 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 var app = &GaiaApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), - cdc: cdc, - keyMain: sdk.NewKVStoreKey("main"), - keyAccount: sdk.NewKVStoreKey("acc"), - keyIBC: sdk.NewKVStoreKey("ibc"), - keyStake: sdk.NewKVStoreKey("stake"), + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), } // define the accountMapper @@ -71,6 +75,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.coinKeeper = bank.NewKeeper(app.accountMapper) 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.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). @@ -80,6 +85,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { // initialize BaseApp app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(slashing.NewBeginBlocker(app.slashingKeeper)) app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) @@ -105,7 +111,8 @@ func MakeCodec() *wire.Codec { // custom logic for gaia initialization 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 err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index bd87ec0285..1eddc3e33d 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -297,12 +297,12 @@ type TxProveLive struct { ## Delegator bond Atom holders may delegate coins to validators, under this circumstance their -funds are held in a `DelegatorBond`. It is owned by one delegator, and is +funds are held in a `Delegation`. It is owned by one delegator, and is associated with the shares for one validator. The sender of the transaction is considered to be the owner of the bond, ``` golang -type DelegatorBond struct { +type Delegation struct { Candidate crypto.PubKey Shares rational.Rat AdjustmentFeePool coin.Coins @@ -318,11 +318,11 @@ Description: - AdjustmentRewardPool: Adjustment factor used to passively calculate each bonds entitled fees from `Candidate.ProposerRewardPool`` -Each `DelegatorBond` is individually indexed within the store by delegator +Each `Delegation` is individually indexed within the store by delegator address and candidate pubkey. - key: Delegator and Candidate-Pubkey - - value: DelegatorBond + - value: Delegation ### Delegating @@ -330,7 +330,7 @@ address and candidate pubkey. Delegator bonds are created using the TxDelegate transaction. Within this transaction the validator candidate queried with an amount of coins, whereby given the current exchange rate of candidate's delegator-shares-to-atoms the -candidate will return shares which are assigned in `DelegatorBond.Shares`. +candidate will return shares which are assigned in `Delegation.Shares`. ``` golang type TxDelegate struct { @@ -671,5 +671,5 @@ rate, all commission on fees must be simultaneously withdrawn. `candidate.Adjustment` must be set to the value of `canidate.Count` for the height which the candidate is added on the validator set. - The feePool of a new delegator bond will be 0 for the height at which the bond - was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to + was added. This is achieved by setting `Delegation.FeeWithdrawalHeight` to the height which the bond was added. diff --git a/docs/spec/staking/old/spec2.md b/docs/spec/staking/old/spec2.md index 72bb8a2e37..68f20703dc 100644 --- a/docs/spec/staking/old/spec2.md +++ b/docs/spec/staking/old/spec2.md @@ -34,7 +34,7 @@ The staking module persists the following to the store: - `GlobalState`, describing the global pools - a `Candidate` for each candidate validator, indexed by public key - a `Candidate` for each candidate validator, indexed by shares in the global pool (ie. ordered) -- a `DelegatorBond` for each delegation to a candidate by a delegator, indexed by delegator and candidate +- a `Delegation` for each delegation to a candidate by a delegator, indexed by delegator and candidate public keys - a `Queue` of unbonding delegations (TODO) @@ -146,15 +146,15 @@ When validators are kicked from the validator set they are removed from this list. -### DelegatorBond +### Delegation Atom holders may delegate coins to validators, under this circumstance their -funds are held in a `DelegatorBond`. It is owned by one delegator, and is +funds are held in a `Delegation`. It is owned by one delegator, and is associated with the shares for one validator. The sender of the transaction is considered to be the owner of the bond, ``` golang -type DelegatorBond struct { +type Delegation struct { Candidate crypto.PubKey Shares rational.Rat AdjustmentFeePool coin.Coins @@ -170,11 +170,11 @@ Description: - AdjustmentRewardPool: Adjustment factor used to passively calculate each bonds entitled fees from `Candidate.ProposerRewardPool`` -Each `DelegatorBond` is individually indexed within the store by delegator +Each `Delegation` is individually indexed within the store by delegator address and candidate pubkey. - key: Delegator and Candidate-Pubkey - - value: DelegatorBond + - value: Delegation ### Unbonding Queue @@ -308,7 +308,7 @@ All bonding, whether self-bonding or delegation, is done via Delegator bonds are created using the TxDelegate transaction. Within this transaction the validator candidate queried with an amount of coins, whereby given the current exchange rate of candidate's delegator-shares-to-atoms the -candidate will return shares which are assigned in `DelegatorBond.Shares`. +candidate will return shares which are assigned in `Delegation.Shares`. ``` golang type TxDelegate struct { @@ -616,7 +616,7 @@ synced past the height of the oldest `powerChange`. This trim procedure will occur on an epoch basis. ```golang -type powerChange struct { +type powerChange struct height int64 // block height at change power rational.Rat // total power at change prevpower rational.Rat // total power at previous height-1 @@ -694,5 +694,5 @@ rate, all commission on fees must be simultaneously withdrawn. `candidate.Adjustment` must be set to the value of `canidate.Count` for the height which the candidate is added on the validator set. - The feePool of a new delegator bond will be 0 for the height at which the bond - was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to + was added. This is achieved by setting `Delegation.FeeWithdrawalHeight` to the height which the bond was added. diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 52f324b0f7..eed082503b 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -203,7 +203,7 @@ unbond(tx TxUnbond): return removeShares(candidate Candidate, shares rational.Rat): - globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + globalPoolSharesToRemove = DelegatorShareExRate(candidate) * shares if candidate.Status == Bonded gs.BondedShares -= globalPoolSharesToRemove @@ -218,7 +218,7 @@ removeShares(candidate Candidate, shares rational.Rat): candidate.IssuedDelegatorShares -= shares return returnedCoins -delegatorShareExRate(candidate Candidate): +DelegatorShareExRate(candidate Candidate): if candidate.IssuedDelegatorShares.IsZero() then return rational.One return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares diff --git a/docs/spec/staking/valset-changes.md b/docs/spec/staking/valset-changes.md index bc52b89980..e5979af39a 100644 --- a/docs/spec/staking/valset-changes.md +++ b/docs/spec/staking/valset-changes.md @@ -31,8 +31,8 @@ tick(ctx Context): if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) - unbondDelegationQueue.remove(elem) + transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) + unbondDelegationQueue.remove(elem) if time > reDelegationQueue.head().InitTime + UnbondingPeriod for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do @@ -55,9 +55,9 @@ nextInflation(hrsPerYr rational.Rat): inflation = gs.Inflation + inflationRateChange if inflation > params.InflationMax then inflation = params.InflationMax - + if inflation < params.InflationMin then inflation = params.InflationMin - + return inflation UpdateValidatorSet(): @@ -71,26 +71,26 @@ UpdateValidatorSet(): updateVotingPower(candidates Candidates): foreach candidate in candidates do - candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) - + candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * DelegatorShareExRate(candidate) + candidates.Sort() - + foreach candidate in candidates do - if candidate is not in the first params.MaxVals - candidate.VotingPower = rational.Zero - if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) - - else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) + if candidate is not in the first params.MaxVals + candidate.VotingPower = rational.Zero + if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) + + else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) - saveCandidate(store, c) - + saveCandidate(store, c) + return candidates unbondedToBondedPool(candidate Candidate): removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares gs.UnbondedShares -= candidate.GlobalStakeShares gs.UnbondedPool -= removedTokens - + gs.BondedPool += removedTokens issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) gs.BondedShares += issuedShares @@ -155,16 +155,17 @@ The following information is stored with each validator candidate, and is only n ```go type ValidatorSigningInfo struct { - StartHeight int64 - SignedBlocksBitArray BitArray + StartHeight int64 + SignedBlocksBitArray BitArray + SignedBlocksCounter int64 } ``` Where: * `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). * `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks, -whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not. -Note it is initialized with all 0s. +whether or not this validator was included in the LastCommit. It uses a `1` if the validator was included, and a `0` if it was not. Note it is initialized with all 0s. +* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: @@ -173,18 +174,23 @@ h = block.Height index = h % SIGNED_BLOCKS_WINDOW for val in block.Validators: - signInfo = val.SignInfo - if val in block.LastCommit: - signInfo.SignedBlocksBitArray.Set(index, 0) - else - signInfo.SignedBlocksBitArray.Set(index, 1) + signInfo = val.SignInfo + previous = signInfo.SignedBlocksBitArray.Get(index) - // validator must be active for at least SIGNED_BLOCKS_WINDOW - // before they can be automatically unbonded for failing to be - // included in 50% of the recent LastCommits - minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW - minSigned = SIGNED_BLOCKS_WINDOW / 2 - blocksSigned = signInfo.SignedBlocksBitArray.Sum() - if h > minHeight AND blocksSigned < minSigned: - unbond the validator + // update counter if array has changed + if previous and val in block.AbsentValidators: + signInfo.SignedBlocksBitArray.Set(index, 0) + signInfo.SignedBlocksCounter-- + else if !previous and val not in block.AbsentValidators: + signInfo.SignedBlocksBitArray.Set(index, 1) + signInfo.SignedBlocksCounter++ + // else previous == val not in block.AbsentValidators, no change + + // validator must be active for at least SIGNED_BLOCKS_WINDOW + // before they can be automatically unbonded for failing to be + // included in 50% of the recent LastCommits + minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW + minSigned = SIGNED_BLOCKS_WINDOW / 2 + if h > minHeight AND signInfo.SignedBlocksCounter < minSigned: + unbond the validator ``` diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 086fa32b36..a0b1f86ad6 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -104,7 +104,7 @@ func MakeCodec() *wire.Codec { // Custom logic for basecoin initialization func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes + stateJSON := req.GenesisBytes genesisState := new(types.GenesisState) err := app.cdc.UnmarshalJSON(stateJSON, genesisState) diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 2075a64da0..b8b8642cf3 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -118,7 +118,7 @@ func MakeCodec() *wire.Codec { // custom logic for democoin initialization func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes + stateJSON := req.GenesisBytes genesisState := new(types.GenesisState) err := app.cdc.UnmarshalJSON(stateJSON, genesisState) diff --git a/examples/democoin/x/cool/keeper_test.go b/examples/democoin/x/cool/keeper_test.go index d497dee699..f632ae31ee 100644 --- a/examples/democoin/x/cool/keeper_test.go +++ b/examples/democoin/x/cool/keeper_test.go @@ -30,7 +30,7 @@ func TestCoolKeeper(t *testing.T) { auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, nil, nil) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, ck, DefaultCodespace) diff --git a/examples/democoin/x/pow/handler_test.go b/examples/democoin/x/pow/handler_test.go index 30aeafa2a5..867120eb84 100644 --- a/examples/democoin/x/pow/handler_test.go +++ b/examples/democoin/x/pow/handler_test.go @@ -20,7 +20,7 @@ func TestPowHandler(t *testing.T) { auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) config := NewConfig("pow", int64(1)) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) diff --git a/examples/democoin/x/pow/keeper_test.go b/examples/democoin/x/pow/keeper_test.go index a4afb876a9..98e8ebcfea 100644 --- a/examples/democoin/x/pow/keeper_test.go +++ b/examples/democoin/x/pow/keeper_test.go @@ -33,7 +33,7 @@ func TestPowKeeperGetSet(t *testing.T) { auth.RegisterBaseAccount(cdc) am := auth.NewAccountMapper(cdc, capKey, &auth.BaseAccount{}) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) config := NewConfig("pow", int64(1)) ck := bank.NewKeeper(am) keeper := NewKeeper(capKey, config, ck, DefaultCodespace) diff --git a/examples/democoin/x/simplestake/keeper_test.go b/examples/democoin/x/simplestake/keeper_test.go index 515c19cc59..ef34f3085c 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/examples/democoin/x/simplestake/keeper_test.go @@ -35,9 +35,9 @@ func TestKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) stakeKeeper := NewKeeper(capKey, bank.NewKeeper(accountMapper), DefaultCodespace) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) addr := sdk.Address([]byte("some-address")) bi := stakeKeeper.getBondInfo(ctx, addr) @@ -63,7 +63,7 @@ func TestBonding(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) coinKeeper := bank.NewKeeper(accountMapper) diff --git a/mock/app.go b/mock/app.go index ab1a8447a5..09d7a658c7 100644 --- a/mock/app.go +++ b/mock/app.go @@ -88,7 +88,7 @@ type GenesisJSON struct { // with key/value pairs func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes + stateJSON := req.GenesisBytes genesisState := new(GenesisJSON) err := json.Unmarshal(stateJSON, genesisState) diff --git a/mock/app_test.go b/mock/app_test.go index be1d778295..50b0761c17 100644 --- a/mock/app_test.go +++ b/mock/app_test.go @@ -26,7 +26,7 @@ func TestInitApp(t *testing.T) { //TODO test validators in the init chain? req := abci.RequestInitChain{ - AppStateBytes: appState, + GenesisBytes: appState, } app.InitChain(req) app.Commit() diff --git a/types/context.go b/types/context.go index 4ab0a5d093..993c148ec5 100644 --- a/types/context.go +++ b/types/context.go @@ -30,7 +30,9 @@ type Context struct { } // 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, absentValidators [][]byte) Context { + c := Context{ Context: context.Background(), pst: newThePast(), @@ -43,6 +45,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt c = c.WithIsCheckTx(isCheckTx) c = c.WithTxBytes(txBytes) c = c.WithLogger(logger) + c = c.WithAbsentValidators(absentValidators) c = c.WithGasMeter(NewInfiniteGasMeter()) return c } @@ -128,6 +131,7 @@ const ( contextKeyIsCheckTx contextKeyTxBytes contextKeyLogger + contextKeyAbsentValidators contextKeyGasMeter ) @@ -157,6 +161,9 @@ func (c Context) TxBytes() []byte { func (c Context) Logger() log.Logger { return c.Value(contextKeyLogger).(log.Logger) } +func (c Context) AbsentValidators() [][]byte { + return c.Value(contextKeyAbsentValidators).([][]byte) +} func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) } @@ -182,6 +189,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context { func (c Context) WithLogger(logger log.Logger) Context { return c.withValue(contextKeyLogger, logger) } +func (c Context) WithAbsentValidators(AbsentValidators [][]byte) Context { + return c.withValue(contextKeyAbsentValidators, AbsentValidators) +} func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) } diff --git a/types/context_test.go b/types/context_test.go index ec5faab440..1eafdaf7e7 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -43,7 +43,7 @@ func (l MockLogger) With(kvs ...interface{}) log.Logger { func TestContextGetOpShouldNeverPanic(t *testing.T) { var ms types.MultiStore - ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) indices := []int64{ -10, 1, 0, 10, 20, } @@ -58,7 +58,7 @@ func defaultContext(key types.StoreKey) types.Context { cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, types.StoreTypeIAVL, db) cms.LoadLatestVersion() - ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil) return ctx } diff --git a/types/lib/mapper_test.go b/types/lib/mapper_test.go index e1759b06ac..a610f1e0fa 100644 --- a/types/lib/mapper_test.go +++ b/types/lib/mapper_test.go @@ -25,7 +25,7 @@ func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil) cdc := wire.NewCodec() return ctx, cdc } diff --git a/types/stake.go b/types/stake.go index df74a705b9..e60c4f43cb 100644 --- a/types/stake.go +++ b/types/stake.go @@ -17,11 +17,14 @@ const ( // validator for a delegated proof of stake system type Validator interface { - GetStatus() BondStatus // status of the validator - GetOwner() Address // owner address to receive/return validators coins - GetPubKey() crypto.PubKey // validation pubkey - GetPower() Rat // validation power - GetBondHeight() int64 // height in which the validator became active + GetStatus() BondStatus // status of the validator + GetOwner() Address // owner address to receive/return validators coins + GetPubKey() crypto.PubKey // validation pubkey + GetPower() Rat // validation power + GetBondHeight() int64 // height in which the validator became active + Slash(Context, int64, Rat) // slash the validator and delegators of the validator + // for an offense at a specified height by a specified fraction + ForceUnbond(Context, int64) // force unbond the validator, including a duration which must pass before they can rebond } // validator which fulfills abci validator interface for use in Tendermint diff --git a/types/tags.go b/types/tags.go index 95a826fd78..5a8eb1f473 100644 --- a/types/tags.go +++ b/types/tags.go @@ -25,6 +25,11 @@ func (t Tags) AppendTags(a Tags) Tags { 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 func NewTags(tags ...interface{}) Tags { var ret Tags diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index b7f22e5d54..729f0769ef 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -75,7 +75,7 @@ func TestAnteHandlerSigErrors(t *testing.T) { mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) // keys and addresses priv1, addr1 := privAndAddr() @@ -117,7 +117,7 @@ func TestAnteHandlerSequences(t *testing.T) { mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) // keys and addresses priv1, addr1 := privAndAddr() @@ -184,7 +184,7 @@ func TestAnteHandlerFees(t *testing.T) { mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) // keys and addresses priv1, addr1 := privAndAddr() @@ -226,7 +226,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) // keys and addresses priv1, addr1 := privAndAddr() @@ -302,7 +302,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) // keys and addresses priv1, addr1 := privAndAddr() diff --git a/x/auth/context_test.go b/x/auth/context_test.go index a93de44d0c..c4aaca269a 100644 --- a/x/auth/context_test.go +++ b/x/auth/context_test.go @@ -12,8 +12,8 @@ import ( ) func TestContextWithSigners(t *testing.T) { - ms, _, _ := setupMultiStore() - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + ms, _ := setupMultiStore() + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) _, _, addr1 := keyPubAddr() _, _, addr2 := keyPubAddr() diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index 7f6397069a..dc6afef57e 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/mapper_test.go @@ -31,7 +31,7 @@ func TestAccountMapperGetSet(t *testing.T) { RegisterBaseAccount(cdc) // make context and mapper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) addr := sdk.Address([]byte("some-address")) diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 117c69e7ae..07ba91e0c5 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -31,7 +31,7 @@ func TestKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) coinKeeper := NewKeeper(accountMapper) @@ -116,7 +116,7 @@ func TestSendKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) coinKeeper := NewKeeper(accountMapper) sendKeeper := NewSendKeeper(accountMapper) @@ -185,7 +185,7 @@ func TestViewKeeper(t *testing.T) { cdc := wire.NewCodec() auth.RegisterBaseAccount(cdc) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) coinKeeper := NewKeeper(accountMapper) viewKeeper := NewViewKeeper(accountMapper) diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index e13df4f8dd..9ed4b38254 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -24,7 +24,7 @@ func defaultContext(key sdk.StoreKey) sdk.Context { cms := store.NewCommitMultiStore(db) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger(), nil) return ctx } diff --git a/x/slashing/errors.go b/x/slashing/errors.go new file mode 100644 index 0000000000..bf57c337d7 --- /dev/null +++ b/x/slashing/errors.go @@ -0,0 +1,32 @@ +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 +) + +func codeToDefaultMsg(code CodeType) string { + switch code { + 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) +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go new file mode 100644 index 0000000000..f1aac7f586 --- /dev/null +++ b/x/slashing/keeper.go @@ -0,0 +1,148 @@ +package slashing + +import ( + "encoding/binary" + "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" +) + +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 - 1 day + // TODO Governance parameter? + DowntimeUnbondDuration int64 = 86400 +) + +var ( + // SlashFractionDoubleSign - currently 5% + // TODO Governance parameter? + SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20)) + + // SlashFractionDowntime - currently 0 + // TODO Governance parameter? + SlashFractionDowntime = sdk.ZeroRat() +) + +// 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 + if age > MaxEvidenceAge { + logger.Info(fmt.Sprintf("Ignored double sign from %v at height %d, age of %d past max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) + return + } + logger.Info(fmt.Sprintf("Confirmed double sign from %v at height %d, age of %d less than max age of %d", pubkey.Address(), height, age, MaxEvidenceAge)) + validator := k.stakeKeeper.Validator(ctx, pubkey.Address()) + validator.Slash(ctx, height, SlashFractionDoubleSign) + logger.Info(fmt.Sprintf("Slashed validator %s by fraction %v for double-sign at height %d", pubkey.Address(), SlashFractionDoubleSign, height)) +} + +// 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 %v at height %d", pubkey.Address(), height)) + } + index := height % SignedBlocksWindow + address := pubkey.Address() + signInfo := k.getValidatorSigningInfo(ctx, address) + previous := k.getValidatorSigningBitArray(ctx, address, index) + if previous && !signed { + k.setValidatorSigningBitArray(ctx, address, index, false) + signInfo.SignedBlocksCounter-- + k.setValidatorSigningInfo(ctx, address, signInfo) + } else if !previous && signed { + k.setValidatorSigningBitArray(ctx, address, index, true) + signInfo.SignedBlocksCounter++ + k.setValidatorSigningInfo(ctx, address, signInfo) + } + minHeight := signInfo.StartHeight + SignedBlocksWindow + if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { + validator := k.stakeKeeper.Validator(ctx, address) + validator.Slash(ctx, height, SlashFractionDowntime) + validator.ForceUnbond(ctx, DowntimeUnbondDuration) + logger.Info(fmt.Sprintf("Slashed validator %s by fraction %v and unbonded for downtime at height %d, cannot rebond for %ds", + address, SlashFractionDowntime, height, DowntimeUnbondDuration)) + } +} + +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(validatorSigningInfoKey(address)) + k.cdc.MustUnmarshalBinary(bz, &info) + return +} + +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) +} + +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)) + k.cdc.MustUnmarshalBinary(bz, &signed) + return +} + +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"` + SignedBlocksCounter int64 `json:"signed_blocks_counter"` +} + +func validatorSigningInfoKey(v sdk.Address) []byte { + return append([]byte{0x01}, v.Bytes()...) +} + +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...)...) +} diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go new file mode 100644 index 0000000000..9085a7ae60 --- /dev/null +++ b/x/slashing/keeper_test.go @@ -0,0 +1,17 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + // sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestHandleDoubleSign(t *testing.T) { + require.Equal(t, true, true) + // TODO +} + +func TestHandleAbsentValidator(t *testing.T) { + // TODO +} diff --git a/x/slashing/tick.go b/x/slashing/tick.go new file mode 100644 index 0000000000..e76c06abe3 --- /dev/null +++ b/x/slashing/tick.go @@ -0,0 +1,44 @@ +package slashing + +import ( + "bytes" + "encoding/binary" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { + return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + heightBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(heightBytes, uint64(req.Header.Height)) + tags := sdk.NewTags("height", heightBytes) + for _, evidence := range req.ByzantineValidators { + var pk crypto.PubKey + sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk) + switch { + case bytes.Compare(evidence.Type, []byte("doubleSign")) == 0: + 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))) + } + } + absent := make(map[string]bool) + for _, pubkey := range req.AbsentValidators { + var pk crypto.PubKey + sk.cdc.MustUnmarshalBinary(pubkey, &pk) + absent[string(pk.Bytes())] = true + } + sk.stakeKeeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + pubkey := validator.GetPubKey() + sk.handleValidatorSignature(ctx, pubkey, !absent[string(pubkey.Bytes())]) + return false + }) + // TODO Add some more tags so clients can track slashing + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } + } +} diff --git a/x/stake/test_common.go b/x/stake/test_common.go index b7a5152c09..1cd46549b0 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -103,7 +103,7 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context err := ms.LoadLatestVersion() require.Nil(t, err) - ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger(), nil) cdc := makeTestCodec() accountMapper := auth.NewAccountMapper( cdc, // amino codec diff --git a/x/stake/validator.go b/x/stake/validator.go index 88f061f315..7848724fd6 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -236,3 +236,9 @@ func (v Validator) GetOwner() sdk.Address { return v.Owner } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } func (v Validator) GetBondHeight() int64 { return v.BondHeight } +func (v Validator) Slash(ctx sdk.Context, height int64, fraction sdk.Rat) { + panic("not implemented") +} +func (v Validator) ForceUnbond(ctx sdk.Context, jailDuration int64) { + panic("not implemented") +}