From 95c5baf449b28a55f0b3c6cbcae086a70797e95a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 23 May 2018 22:25:56 +0200 Subject: [PATCH 01/57] Rebase & squash slashing --- Gopkg.lock | 32 ++-- Gopkg.toml | 9 +- baseapp/baseapp.go | 18 ++- baseapp/baseapp_test.go | 2 +- cmd/gaia/app/app.go | 29 ++-- docs/spec/staking/old/spec.md | 12 +- docs/spec/staking/old/spec2.md | 18 +-- docs/spec/staking/transactions.md | 4 +- docs/spec/staking/valset-changes.md | 70 +++++---- examples/basecoin/app/app.go | 2 +- examples/democoin/app/app.go | 2 +- examples/democoin/x/cool/keeper_test.go | 2 +- examples/democoin/x/pow/handler_test.go | 2 +- examples/democoin/x/pow/keeper_test.go | 2 +- .../democoin/x/simplestake/keeper_test.go | 4 +- mock/app.go | 2 +- mock/app_test.go | 2 +- types/context.go | 12 +- types/context_test.go | 4 +- types/lib/mapper_test.go | 2 +- types/stake.go | 13 +- types/tags.go | 5 + x/auth/ante_test.go | 10 +- x/auth/context_test.go | 4 +- x/auth/mapper_test.go | 2 +- x/bank/keeper_test.go | 6 +- x/ibc/ibc_test.go | 2 +- x/slashing/errors.go | 32 ++++ x/slashing/keeper.go | 148 ++++++++++++++++++ x/slashing/keeper_test.go | 17 ++ x/slashing/tick.go | 44 ++++++ x/stake/test_common.go | 2 +- x/stake/validator.go | 6 + 33 files changed, 400 insertions(+), 121 deletions(-) create mode 100644 x/slashing/errors.go create mode 100644 x/slashing/keeper.go create mode 100644 x/slashing/keeper_test.go create mode 100644 x/slashing/tick.go 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") +} From 67f7f31ba9bb3c19e7aec03aa27511f0ba63db48 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 23 May 2018 22:52:56 +0200 Subject: [PATCH 02/57] Fix testcases by mounting store --- cmd/gaia/app/app.go | 2 +- x/slashing/keeper.go | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 7ad3709a10..28904e1ac9 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -87,8 +87,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { 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)) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index f1aac7f586..4945da6a74 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -85,7 +85,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } index := height % SignedBlocksWindow address := pubkey.Address() - signInfo := k.getValidatorSigningInfo(ctx, address) + signInfo, _ := k.getValidatorSigningInfo(ctx, address) previous := k.getValidatorSigningBitArray(ctx, address, index) if previous && !signed { k.setValidatorSigningBitArray(ctx, address, index, false) @@ -106,10 +106,15 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } } -func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo) { +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (info validatorSigningInfo, found bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(validatorSigningInfoKey(address)) - k.cdc.MustUnmarshalBinary(bz, &info) + if bz == nil { + found = false + } else { + k.cdc.MustUnmarshalBinary(bz, &info) + found = true + } return } @@ -122,7 +127,12 @@ func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.Address, in 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) + if bz == nil { + // lazy: treat empty key as unsigned + signed = false + } else { + k.cdc.MustUnmarshalBinary(bz, &signed) + } return } From adec1cd7a956dad6d9a3050fee8d015f514ef262 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 24 May 2018 01:56:54 +0200 Subject: [PATCH 03/57] Fix dependencies, gaiacli build issue persists --- Gopkg.lock | 20 ++++++++++---------- Gopkg.toml | 13 +++++-------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 7f1f6185e6..286760d609 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -250,10 +250,10 @@ "leveldb/table", "leveldb/util" ] - revision = "e6d6b529196422703d54ff5c40e79809ec2020b3" + revision = "5d6fca44a948d2be89a9702de7717f0168403d3d" [[projects]] - branch = "cwgoes/timestamp-in-evidence" + branch = "develop" name = "github.com/tendermint/abci" packages = [ "client", @@ -262,7 +262,7 @@ "server", "types" ] - revision = "e196dacf804e3a4ab74252c27e99cb17b39bf501" + revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" [[projects]] branch = "master" @@ -299,7 +299,7 @@ ".", "sha256truncated" ] - revision = "9b6f9c3f49d599cfcf43f1a56c3c22882d1f9f21" + revision = "c9206995e8f948e99927f5084a88a7e94ca256da" [[projects]] branch = "cwgoes/update-abci" @@ -357,8 +357,8 @@ "log", "merkle" ] - revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd" - version = "v0.8.3-rc0" + revision = "d970af87248a4e162590300dbb74e102183a417d" + version = "v0.8.3" [[projects]] branch = "master" @@ -374,7 +374,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "1a580b3eff7814fc9b40602fd35256c63b50f491" + revision = "75e913eb8a8e3d31a97b216de09de106a7b07681" [[projects]] branch = "master" @@ -388,13 +388,13 @@ "internal/timeseries", "trace" ] - revision = "57065200b4b034a1c8ad54ff77069408c2218ae6" + revision = "9ef9f5bb98a1fdc41f8cf6c250a4404b4085e389" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b" + revision = "77b0e4315053a57ed2962443614bdb28db152054" [[projects]] name = "golang.org/x/text" @@ -455,6 +455,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "2bb789b91c383e3a861979c83e61f5f8fd71ea5661837c092353aaacf3091de1" + inputs-digest = "bbc0ee39f39cb296960a5fcb2a3916a4acc48ecbb675758ea1f61def44e994bb" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 05b01bfaef..53dbabbbb3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -54,29 +54,25 @@ [[override]] name = "github.com/tendermint/abci" - branch = "cwgoes/timestamp-in-evidence" + branch = "develop" [[constraint]] name = "github.com/tendermint/go-crypto" version = "~0.6.2" -[[override]] - name = "github.com/tendermint/go-wire" - version = "0.7.3" - [[constraint]] name = "github.com/tendermint/go-amino" version = "~0.9.9" -[[constraint]] +[[override]] name = "github.com/tendermint/iavl" branch = "develop" -[[constraint]] +[[override]] name = "github.com/tendermint/tendermint" branch = "cwgoes/update-abci" -[[override]] +[[constraint]] name = "github.com/tendermint/tmlibs" version = "~0.8.3-rc0" @@ -88,3 +84,4 @@ [prune] go-tests = true unused-packages = true + From b005f9f18d7116c7d4e17a5cd438d6ea2cebb746 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 24 May 2018 23:51:42 +0200 Subject: [PATCH 04/57] Validator by pubkey, tests work-in-progress --- types/stake.go | 5 +- x/slashing/keeper.go | 10 +++- x/slashing/keeper_test.go | 97 ++++++++++++++++++++++++++++++++++++++- x/stake/keeper.go | 22 +++++++++ x/stake/keeper_keys.go | 20 +++++--- 5 files changed, 141 insertions(+), 13 deletions(-) diff --git a/types/stake.go b/types/stake.go index e60c4f43cb..6e3d02c3d3 100644 --- a/types/stake.go +++ b/types/stake.go @@ -45,8 +45,9 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, Address) Validator // get a particular validator by owner address - TotalPower(Context) Rat // total power of the validator set + Validator(Context, Address) Validator // get a particular validator by owner address + ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by public key + TotalPower(Context) Rat // total power of the validator set } //_______________________________________________________________________________ diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 4945da6a74..686f84b65c 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -71,7 +71,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, 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 := k.stakeKeeper.ValidatorByPubKey(ctx, pubkey) + if validator == nil { + panic(fmt.Errorf("Attempted to slash nonexistent validator with address %s", 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)) } @@ -98,7 +101,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } minHeight := signInfo.StartHeight + SignedBlocksWindow if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { - validator := k.stakeKeeper.Validator(ctx, address) + validator := k.stakeKeeper.ValidatorByPubKey(ctx, pubkey) + if validator == nil { + panic(fmt.Errorf("Attempted to slash nonexistent validator with address %s", pubkey.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", diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 9085a7ae60..f360fd9e99 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -1,17 +1,110 @@ package slashing import ( + "encoding/hex" "testing" "github.com/stretchr/testify/require" - // sdk "github.com/cosmos/cosmos-sdk/types" + + 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 = 100 +) + +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.NewNopLogger(), nil) + cdc := createTestCodec() + accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{}) + ck := bank.NewKeeper(accountMapper) + sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) + stake.InitGenesis(ctx, sk, stake.DefaultGenesisState()) + 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 TestHandleDoubleSign(t *testing.T) { - require.Equal(t, true, true) + ctx, ck, sk, keeper := createTestInput(t) + addr, val, amt := addrs[0], pks[0], int64(10) + 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}}) + keeper.handleDoubleSign(ctx, 0, 0, val) // TODO } func TestHandleAbsentValidator(t *testing.T) { // TODO } + +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, err := sdk.GetAddress(addr) + if err != nil { + panic(err) + } + 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}, + } +} diff --git a/x/stake/keeper.go b/x/stake/keeper.go index ce84b1e177..f46cc6f23a 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" ) // keeper of the staking store @@ -38,6 +39,18 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid 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) + b := store.Get(GetValidatorByPubKeyKey(pubkey)) + if b == nil { + return validator, false + } + var addr sdk.Address + k.cdc.MustUnmarshalBinary(b, &addr) + return k.getValidator(store, addr) +} + // get a single validator (reuse store) func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) { b := store.Get(GetValidatorKey(addr)) @@ -710,6 +723,15 @@ func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { return val } +// get the sdk.validator for a particular pubkey +func (k Keeper) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + return nil + } + return val +} + // total power from the bond func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { pool := k.GetPool(ctx) diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 5a84d08f29..e09a47ecab 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -16,13 +16,14 @@ var ( ParamKey = []byte{0x00} // key for parameters relating to staking PoolKey = []byte{0x01} // key for the staking pools ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators - ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power - ValidatorCliffKey = []byte{0x05} // key for block-local tx index - ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index - TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated - DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x09} // key for block-local tx index + ValidatorsByPubKeyKey = []byte{0x03} // prefix for each key to a validator by pubkey + ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators + ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power + ValidatorCliffKey = []byte{0x06} // key for block-local tx index + ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + 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 @@ -32,6 +33,11 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte { return append(ValidatorsKey, ownerAddr.Bytes()...) } +// get the key for the validator with pubkey +func GetValidatorByPubKeyKey(pubkey crypto.PubKey) []byte { + return append(ValidatorsByPubKeyKey, pubkey.Bytes()...) +} + // get the key for the current validator group, ordered like tendermint func GetValidatorsBondedKey(pk crypto.PubKey) []byte { addr := pk.Address() From 66b4461543ef3ccd9572ac558611647d7607b976 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 24 May 2018 23:55:13 +0200 Subject: [PATCH 05/57] Update pointer-by-pubkey store on set and delete --- x/stake/keeper.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index f46cc6f23a..83c5b0a0e8 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -64,8 +64,12 @@ func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Val // set the main record holding validator details func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { store := ctx.KVStore(k.storeKey) + // set main store bz := k.cdc.MustMarshalBinary(validator) store.Set(GetValidatorKey(validator.Owner), bz) + // set pointer by pubkey + bz = k.cdc.MustMarshalBinary(validator.Owner) + store.Set(GetValidatorByPubKeyKey(validator.PubKey), bz) } // Get the set of all validators with no limits, used during genesis dump @@ -483,6 +487,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) pool := k.getPool(store) store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByPubKeyKey(validator.PubKey)) store.Delete(GetValidatorsByPowerKey(validator, pool)) // delete from the current and power weighted validator groups if the validator From 366d8f932356f63e72888764c8fcd33d9889b4eb Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 25 May 2018 00:12:40 +0200 Subject: [PATCH 06/57] Slash() and ForceUnbond() are functions of ValidatorSet, not Validator --- types/stake.go | 15 +++++++-------- x/slashing/keeper.go | 14 +++----------- x/stake/keeper.go | 8 ++++++++ x/stake/validator.go | 6 ------ 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/types/stake.go b/types/stake.go index 6e3d02c3d3..fed241d014 100644 --- a/types/stake.go +++ b/types/stake.go @@ -17,14 +17,11 @@ 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 - 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 + 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 } // validator which fulfills abci validator interface for use in Tendermint @@ -48,6 +45,8 @@ type ValidatorSet interface { Validator(Context, Address) Validator // get a particular validator by owner address ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by public key 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 + ForceUnbond(Context, crypto.PubKey, int64) // force unbond the validator, including a duration which must pass before they can rebond } //_______________________________________________________________________________ diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 686f84b65c..17cf349ad3 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -71,11 +71,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, 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.ValidatorByPubKey(ctx, pubkey) - if validator == nil { - panic(fmt.Errorf("Attempted to slash nonexistent validator with address %s", pubkey.Address())) - } - validator.Slash(ctx, height, SlashFractionDoubleSign) + k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDoubleSign) logger.Info(fmt.Sprintf("Slashed validator %s by fraction %v for double-sign at height %d", pubkey.Address(), SlashFractionDoubleSign, height)) } @@ -101,12 +97,8 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } minHeight := signInfo.StartHeight + SignedBlocksWindow if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { - validator := k.stakeKeeper.ValidatorByPubKey(ctx, pubkey) - if validator == nil { - panic(fmt.Errorf("Attempted to slash nonexistent validator with address %s", pubkey.Address())) - } - validator.Slash(ctx, height, SlashFractionDowntime) - validator.ForceUnbond(ctx, DowntimeUnbondDuration) + k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDowntime) + k.stakeKeeper.ForceUnbond(ctx, pubkey, 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)) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 83c5b0a0e8..46790e8490 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -776,3 +776,11 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func( } iterator.Close() } + +// slash a validator +func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) { +} + +// force unbond a validator +func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration int64) { +} diff --git a/x/stake/validator.go b/x/stake/validator.go index 7848724fd6..88f061f315 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -236,9 +236,3 @@ 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") -} From 7da5833b8109e574e8e461827ff15785fb11b2da Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 25 May 2018 01:03:26 +0200 Subject: [PATCH 07/57] Implement ValidatorSet.Slash --- x/slashing/keeper.go | 3 --- x/slashing/keeper_test.go | 6 ++++-- x/stake/keeper.go | 20 ++++++++++++++++++++ x/stake/validator.go | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 17cf349ad3..6ec093dc65 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -72,7 +72,6 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, } 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)) k.stakeKeeper.Slash(ctx, pubkey, 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 @@ -99,8 +98,6 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDowntime) k.stakeKeeper.ForceUnbond(ctx, pubkey, 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)) } } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index f360fd9e99..54461aa31e 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -2,6 +2,7 @@ package slashing import ( "encoding/hex" + "os" "testing" "github.com/stretchr/testify/require" @@ -52,7 +53,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewTMLogger(os.Stdout), nil) cdc := createTestCodec() accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{}) ck := bank.NewKeeper(accountMapper) @@ -74,8 +75,9 @@ func TestHandleDoubleSign(t *testing.T) { 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()) keeper.handleDoubleSign(ctx, 0, 0, val) - // TODO + require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) } func TestHandleAbsentValidator(t *testing.T) { diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 46790e8490..7d5073399b 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -779,8 +779,28 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func( // 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 + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Attempted to slash a nonexistent validator with pubkey %s", pubkey)) + } + 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 + ctx.Logger().With("module", "x/stake").Info(fmt.Sprintf("Validator %v slashed by fraction %v, removed %v shares and burned %d tokens", pubkey, fraction, sharesToRemove, burned)) + return } // force unbond a validator func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration int64) { + // TODO Implement + /* + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) + return + } + */ } diff --git a/x/stake/validator.go b/x/stake/validator.go index 88f061f315..31ef290618 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -153,6 +153,21 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, return v, pool } +// Remove & burn pool shares, e.g. when slashing a validator +func (v Validator) removePoolShares(pool Pool, amt sdk.Rat) (Validator, Pool, int64) { + var tokens int64 + switch v.Status() { + case sdk.Unbonded: + pool, tokens = pool.removeSharesUnbonded(amt) + case sdk.Unbonding: + pool, tokens = pool.removeSharesUnbonding(amt) + case sdk.Bonded: + pool, tokens = pool.removeSharesBonded(amt) + } + v.PoolShares.Amount = v.PoolShares.Amount.Sub(amt) + return v, pool, tokens +} + // XXX TEST // get the power or potential power for a validator // if bonded, the power is the BondedShares From 14744cff20df53e93ac89679371c76da58f9901e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 25 May 2018 01:07:18 +0200 Subject: [PATCH 08/57] Update for new Tendermint RPC interface --- client/tx/search.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/tx/search.go b/client/tx/search.go index 527661626a..672f29ff14 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -61,15 +61,12 @@ func searchTx(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]byte, } prove := !viper.GetBool(client.FlagTrustNode) - // TODO: take these as args - page := 0 - perPage := 100 - res, err := node.TxSearch(query, prove, page, perPage) + txs, err := node.TxSearch(query, prove) if err != nil { return nil, err } - info, err := formatTxResults(cdc, res.Txs) + info, err := formatTxResults(cdc, txs) if err != nil { return nil, err } From be4b140003874980e86ab180432e5a2ca8c99650 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 25 May 2018 01:30:43 +0200 Subject: [PATCH 09/57] Add testcase past max evidence age --- x/slashing/keeper_test.go | 5 ++++- x/stake/keeper.go | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 54461aa31e..3204ea7aa8 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -76,7 +76,10 @@ func TestHandleDoubleSign(t *testing.T) { _ = 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()) - keeper.handleDoubleSign(ctx, 0, 0, val) + keeper.handleDoubleSign(ctx, 0, 0, val) // double sign less than max age + 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}) + keeper.handleDoubleSign(ctx, 0, 0, val) // double sign past max age require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 7d5073399b..00930aaa64 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -780,6 +780,7 @@ func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func( // 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 pubkey %s", pubkey)) @@ -789,7 +790,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fract 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 - ctx.Logger().With("module", "x/stake").Info(fmt.Sprintf("Validator %v slashed by fraction %v, removed %v shares and burned %d tokens", pubkey, fraction, sharesToRemove, burned)) + logger.Info(fmt.Sprintf("Validator %v slashed by fraction %v, removed %v shares and burned %d tokens", pubkey, fraction, sharesToRemove, burned)) return } From 796948b838efab3c5b709709cdc9689f778540e1 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 25 May 2018 03:30:17 +0200 Subject: [PATCH 10/57] Downtime slashing testcases --- x/slashing/keeper.go | 4 ++-- x/slashing/keeper_test.go | 41 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 6ec093dc65..c940f60c31 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -36,9 +36,9 @@ var ( // TODO Governance parameter? SlashFractionDoubleSign = sdk.NewRat(1).Quo(sdk.NewRat(20)) - // SlashFractionDowntime - currently 0 + // SlashFractionDowntime - currently 1% // TODO Governance parameter? - SlashFractionDowntime = sdk.ZeroRat() + SlashFractionDowntime = sdk.NewRat(1).Quo(sdk.NewRat(100)) ) // Keeper of the slashing store diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 3204ea7aa8..429b14f937 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -84,7 +84,46 @@ func TestHandleDoubleSign(t *testing.T) { } func TestHandleAbsentValidator(t *testing.T) { - // TODO + ctx, ck, sk, keeper := createTestInput(t) + addr, val, amt := addrs[0], pks[0], int64(10) + 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()) + info, found := keeper.getValidatorSigningInfo(ctx, val.Address()) + require.False(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.SignedBlocksCounter) + height := int64(0) + // 1000 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) + // 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) + height++ + // should have been slashed + require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(99).Quo(sdk.NewRat(100))), sk.Validator(ctx, addr).GetPower()) } func newPubKey(pk string) (res crypto.PubKey) { From e614799d0f16479aa16e7a91e088ef65eea2e318 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 26 May 2018 00:13:29 +0200 Subject: [PATCH 11/57] ForceUnbond() implementation WIP --- x/slashing/keeper.go | 9 +++++---- x/slashing/keeper_test.go | 20 ++++++++++++++++++-- x/stake/errors.go | 6 ++++++ x/stake/handler.go | 25 +++++++++++++++++++++++++ x/stake/keeper.go | 26 ++++++++++++++++---------- x/stake/msg.go | 33 +++++++++++++++++++++++++++++++++ x/stake/validator.go | 9 ++++++--- x/stake/wire.go | 1 + 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index c940f60c31..b64bd6e06b 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -67,10 +67,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, height int64, timestamp int64, 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)) + 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 } - 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)) + 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) } @@ -79,7 +79,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, 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)) + logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height)) } index := height % SignedBlocksWindow address := pubkey.Address() @@ -96,8 +96,9 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } minHeight := signInfo.StartHeight + SignedBlocksWindow if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { + 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.ForceUnbond(ctx, pubkey, DowntimeUnbondDuration) + k.stakeKeeper.ForceUnbond(ctx, pubkey, DowntimeUnbondDuration) // TODO } } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 429b14f937..dd4bb4cd69 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -58,7 +58,9 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep accountMapper := auth.NewAccountMapper(cdc, keyAcc, &auth.BaseAccount{}) ck := bank.NewKeeper(accountMapper) sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) - stake.InitGenesis(ctx, sk, stake.DefaultGenesisState()) + genesis := stake.DefaultGenesisState() + genesis.Pool.BondedTokens = initCoins * int64(len(addrs)) + stake.InitGenesis(ctx, sk, genesis) for _, addr := range addrs { ck.AddCoins(ctx, addr, sdk.Coins{ {sk.GetParams(ctx).BondDenom, initCoins}, @@ -86,7 +88,8 @@ func TestHandleDoubleSign(t *testing.T) { func TestHandleAbsentValidator(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) addr, val, amt := addrs[0], pks[0], int64(10) - got := stake.NewHandler(sk)(ctx, newTestMsgDeclareCandidacy(addr, val, amt)) + sh := stake.NewHandler(sk) + 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}}) @@ -114,6 +117,9 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter) + // should be bonded still + validator := sk.ValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) // 51st block missed ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) @@ -122,6 +128,16 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) height++ + // should have been revoked + validator = sk.ValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Unbonded, validator.GetStatus()) + got = sh(ctx, stake.NewMsgUnrevoke(addr)) + require.False(t, got.IsOK()) // should fail prior to jail expiration + ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)}) + got = sh(ctx, stake.NewMsgUnrevoke(addr)) + require.True(t, got.IsOK()) // should succeed after jail expiration + validator = sk.ValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) // should have been slashed require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(99).Quo(sdk.NewRat(100))), sk.Validator(ctx, addr).GetPower()) } diff --git a/x/stake/errors.go b/x/stake/errors.go index 83c38d528d..2664a56cad 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -16,6 +16,7 @@ const ( CodeInvalidValidator CodeType = 201 CodeInvalidBond CodeType = 202 CodeInvalidInput CodeType = 203 + CodeValidatorJailed CodeType = 204 CodeUnauthorized CodeType = sdk.CodeUnauthorized CodeInternal CodeType = sdk.CodeInternal CodeUnknownRequest CodeType = sdk.CodeUnknownRequest @@ -30,6 +31,8 @@ func codeToDefaultMsg(code CodeType) string { return "Invalid Bond" case CodeInvalidInput: return "Invalid Input" + case CodeValidatorJailed: + return "Validator Jailed" case CodeUnauthorized: return "Unauthorized" case CodeInternal: @@ -98,6 +101,9 @@ func ErrBadShares(codespace sdk.CodespaceType) sdk.Error { func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Error removing validator") } +func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") +} //---------------------------------------- diff --git a/x/stake/handler.go b/x/stake/handler.go index 53653557cc..62c4f27df4 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -19,6 +19,8 @@ func NewHandler(k Keeper) sdk.Handler { return handleMsgDelegate(ctx, msg, k) case MsgUnbond: return handleMsgUnbond(ctx, msg, k) + case MsgUnrevoke: + return handleMsgUnrevoke(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -247,3 +249,26 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { Tags: tags, } } + +func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { + validator, found := k.GetValidator(ctx, msg.ValidatorAddr) + if !found { + return ErrNoValidatorForAddress(k.codespace).Result() + } + + if ctx.BlockHeader().Time < validator.RevokedUntilTime { + return ErrValidatorJailed(k.codespace).Result() + } + + if ctx.IsCheckTx() { + return sdk.Result{} + } + + validator.Revoked = false + k.updateValidator(ctx, validator) + + tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes()) + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 00930aaa64..887cdab192 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -220,7 +220,9 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { + fmt.Printf("val preupdate: %v\n", validator) validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + fmt.Printf("val postupdate: %v\n", validator) k.setPool(ctx, pool) } @@ -783,25 +785,29 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fract 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 pubkey %s", pubkey)) + 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 %v slashed by fraction %v, removed %v shares and burned %d tokens", pubkey, fraction, sharesToRemove, burned)) + logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned)) return } // force unbond a validator func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration int64) { - // TODO Implement - /* - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) - return - } - */ + logger := ctx.Logger().With("module", "x/stake") + val, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) + return + } + val.Revoked = true + val.RevokedUntilTime = ctx.BlockHeader().Time + jailDuration + k.updateValidator(ctx, val) // update the validator, now revoked + val, _ = k.GetValidatorByPubKey(ctx, pubkey) + logger.Info(fmt.Sprintf("Validator %s revoked for minimum duration %d", pubkey.Address(), jailDuration)) + return } diff --git a/x/stake/msg.go b/x/stake/msg.go index 0adff84d9b..a668d47d18 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -209,3 +209,36 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { } return nil } + +//______________________________________________________________________ + +// MsgUnrevoke - struct for unrevoking revoked validator +type MsgUnrevoke struct { + ValidatorAddr sdk.Address `json:"address"` +} + +func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke { + return MsgUnrevoke{ + ValidatorAddr: validatorAddr, + } +} + +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 := json.Marshal(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 +} diff --git a/x/stake/validator.go b/x/stake/validator.go index 31ef290618..76905212f1 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -17,9 +17,10 @@ import ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { - Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator - Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + RevokedUntilTime int64 `json:"revoked_until_time"` // timestamp before which the validator cannot unrevoke PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators @@ -46,6 +47,8 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti return Validator{ Owner: owner, PubKey: pubKey, + Revoked: false, + RevokedUntilTime: int64(0), PoolShares: NewUnbondedShares(sdk.ZeroRat()), DelegatorShares: sdk.ZeroRat(), Description: description, diff --git a/x/stake/wire.go b/x/stake/wire.go index 6e6e382606..ac382ff14d 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -10,6 +10,7 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(MsgEditCandidacy{}, "cosmos-sdk/MsgEditCandidacy", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) + cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil) } var cdcEmpty = wire.NewCodec() From 97b084b842f4b10369de78439531440168a8401a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 26 May 2018 00:27:02 +0200 Subject: [PATCH 12/57] Fix ForceUnbond() testcase --- x/slashing/keeper_test.go | 5 ++++- x/stake/keeper.go | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index dd4bb4cd69..7c7be34e8c 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -120,6 +120,8 @@ func TestHandleAbsentValidator(t *testing.T) { // should be bonded still validator := sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool := sk.GetPool(ctx) + require.Equal(t, int64(210), pool.BondedTokens) // 51st block missed ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) @@ -138,8 +140,9 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, got.IsOK()) // should succeed after jail expiration validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool = sk.GetPool(ctx) // should have been slashed - require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(99).Quo(sdk.NewRat(100))), sk.Validator(ctx, addr).GetPower()) + require.Equal(t, int64(208), pool.BondedTokens) } func newPubKey(pk string) (res crypto.PubKey) { diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 887cdab192..e8ac6947c8 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -220,9 +220,7 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { - fmt.Printf("val preupdate: %v\n", validator) validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - fmt.Printf("val postupdate: %v\n", validator) k.setPool(ctx, pool) } From 8aaff4b96e29010d7013fa29757959f30331858a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 26 May 2018 00:36:12 +0200 Subject: [PATCH 13/57] Cleanup testcase a bit --- x/slashing/keeper_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 7c7be34e8c..895be24e1b 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -29,7 +29,7 @@ var ( newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), } - initCoins int64 = 100 + initCoins int64 = 200 ) func createTestCodec() *wire.Codec { @@ -59,7 +59,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep ck := bank.NewKeeper(accountMapper) sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() - genesis.Pool.BondedTokens = initCoins * int64(len(addrs)) + genesis.Pool.LooseUnbondedTokens = initCoins * int64(len(addrs)) stake.InitGenesis(ctx, sk, genesis) for _, addr := range addrs { ck.AddCoins(ctx, addr, sdk.Coins{ @@ -72,7 +72,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep func TestHandleDoubleSign(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) - addr, val, amt := addrs[0], pks[0], int64(10) + 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) @@ -87,7 +87,7 @@ func TestHandleDoubleSign(t *testing.T) { func TestHandleAbsentValidator(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) - addr, val, amt := addrs[0], pks[0], int64(10) + addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) got := sh(ctx, newTestMsgDeclareCandidacy(addr, val, amt)) require.True(t, got.IsOK()) @@ -121,7 +121,7 @@ func TestHandleAbsentValidator(t *testing.T) { validator := sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(210), pool.BondedTokens) + require.Equal(t, int64(100), pool.BondedTokens) // 51st block missed ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) @@ -140,9 +140,9 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, got.IsOK()) // should succeed after jail expiration validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) - pool = sk.GetPool(ctx) // should have been slashed - require.Equal(t, int64(208), pool.BondedTokens) + pool = sk.GetPool(ctx) + require.Equal(t, int64(99), pool.BondedTokens) } func newPubKey(pk string) (res crypto.PubKey) { From 9cfc6de055b7704914ed4d0f613915e05472c77e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 26 May 2018 01:34:16 +0200 Subject: [PATCH 14/57] Linter fix --- x/stake/msg.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/stake/msg.go b/x/stake/msg.go index a668d47d18..c0fdb4e5a1 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -223,6 +223,7 @@ func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke { } } +//nolint func (msg MsgUnrevoke) Type() string { return MsgType } func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } From 6f3d81d5d6368af3d9059e55353a1037d228032d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 26 May 2018 02:05:46 +0200 Subject: [PATCH 15/57] Swap to individual offset --- x/slashing/keeper.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index b64bd6e06b..87016b18f2 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -81,9 +81,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, if !signed { logger.Info(fmt.Sprintf("Absent validator %s at height %d", pubkey.Address(), height)) } - index := height % SignedBlocksWindow address := pubkey.Address() signInfo, _ := k.getValidatorSigningInfo(ctx, address) + signInfo.IndexOffset++ + index := signInfo.IndexOffset % SignedBlocksWindow previous := k.getValidatorSigningBitArray(ctx, address, index) if previous && !signed { k.setValidatorSigningBitArray(ctx, address, index, false) @@ -140,6 +141,7 @@ func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address type validatorSigningInfo struct { StartHeight int64 `json:"start_height"` + IndexOffset int64 `json:"index_offset"` SignedBlocksCounter int64 `json:"signed_blocks_counter"` } From e4b0d0a618524d3040788b1d9cb8b3c33753a4c4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 21:38:02 +0200 Subject: [PATCH 16/57] Reorganization in progress --- cmd/gaia/app/app.go | 1 + types/stake.go | 3 ++- x/slashing/errors.go | 19 +++++++++++++++++ x/slashing/handler.go | 42 ++++++++++++++++++++++++++++++++++++ x/slashing/keeper.go | 2 +- x/slashing/keeper_test.go | 5 +++-- x/slashing/msg.go | 45 +++++++++++++++++++++++++++++++++++++++ x/slashing/wire.go | 12 +++++++++++ x/stake/errors.go | 5 ----- x/stake/handler.go | 25 ---------------------- x/stake/keeper.go | 22 ++++++++++++++----- x/stake/msg.go | 34 ----------------------------- x/stake/validator.go | 8 +++---- x/stake/wire.go | 1 - 14 files changed, 145 insertions(+), 79 deletions(-) create mode 100644 x/slashing/handler.go create mode 100644 x/slashing/msg.go create mode 100644 x/slashing/wire.go diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 28904e1ac9..4fdb6a6c92 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -103,6 +103,7 @@ func MakeCodec() *wire.Codec { ibc.RegisterWire(cdc) bank.RegisterWire(cdc) stake.RegisterWire(cdc) + slashing.RegisterWire(cdc) auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) diff --git a/types/stake.go b/types/stake.go index fed241d014..a52dc9b071 100644 --- a/types/stake.go +++ b/types/stake.go @@ -46,7 +46,8 @@ type ValidatorSet interface { ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by public key 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 - ForceUnbond(Context, crypto.PubKey, int64) // force unbond the validator, including a duration which must pass before they can rebond + Revoke(Context, crypto.PubKey) // revoke a validator + Unrevoke(Context, crypto.PubKey) // unrevoke a validator } //_______________________________________________________________________________ diff --git a/x/slashing/errors.go b/x/slashing/errors.go index bf57c337d7..f61eff17e5 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -10,10 +10,29 @@ 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) } diff --git a/x/slashing/handler.go b/x/slashing/handler.go new file mode 100644 index 0000000000..931ef58465 --- /dev/null +++ b/x/slashing/handler.go @@ -0,0 +1,42 @@ +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() + } + } +} + +func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { + validator := k.stakeKeeper.Validator(ctx, msg.ValidatorAddr) + if validator == nil { + return ErrNoValidatorForAddress(k.codespace).Result() + } + + // TODO + /* + if ctx.BlockHeader().Time < validator.RevokedUntilTime { + return ErrValidatorJailed(k.codespace).Result() + } + */ + + if ctx.IsCheckTx() { + return sdk.Result{} + } + + k.stakeKeeper.Unrevoke(ctx, validator.GetPubKey()) + + tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes()) + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 87016b18f2..918e85c2a3 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -99,7 +99,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { 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.ForceUnbond(ctx, pubkey, DowntimeUnbondDuration) // TODO + k.stakeKeeper.Revoke(ctx, pubkey) // , DowntimeUnbondDuration) // TODO } } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 895be24e1b..9598b24f3c 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -89,6 +89,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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) @@ -133,10 +134,10 @@ func TestHandleAbsentValidator(t *testing.T) { // should have been revoked validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) - got = sh(ctx, stake.NewMsgUnrevoke(addr)) + got = slh(ctx, NewMsgUnrevoke(addr)) require.False(t, got.IsOK()) // should fail prior to jail expiration ctx = ctx.WithBlockHeader(abci.Header{Time: int64(86400 * 2)}) - got = sh(ctx, stake.NewMsgUnrevoke(addr)) + got = slh(ctx, NewMsgUnrevoke(addr)) require.True(t, got.IsOK()) // should succeed after jail expiration validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) diff --git a/x/slashing/msg.go b/x/slashing/msg.go new file mode 100644 index 0000000000..d67ddf70df --- /dev/null +++ b/x/slashing/msg.go @@ -0,0 +1,45 @@ +package slashing + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// 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"` +} + +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 := json.Marshal(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 +} diff --git a/x/slashing/wire.go b/x/slashing/wire.go new file mode 100644 index 0000000000..465a06587e --- /dev/null +++ b/x/slashing/wire.go @@ -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() diff --git a/x/stake/errors.go b/x/stake/errors.go index 2664a56cad..77090d9dc3 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -31,8 +31,6 @@ func codeToDefaultMsg(code CodeType) string { return "Invalid Bond" case CodeInvalidInput: return "Invalid Input" - case CodeValidatorJailed: - return "Validator Jailed" case CodeUnauthorized: return "Unauthorized" case CodeInternal: @@ -101,9 +99,6 @@ func ErrBadShares(codespace sdk.CodespaceType) sdk.Error { func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Error removing validator") } -func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") -} //---------------------------------------- diff --git a/x/stake/handler.go b/x/stake/handler.go index 62c4f27df4..53653557cc 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -19,8 +19,6 @@ func NewHandler(k Keeper) sdk.Handler { return handleMsgDelegate(ctx, msg, k) case MsgUnbond: return handleMsgUnbond(ctx, msg, k) - case MsgUnrevoke: - return handleMsgUnrevoke(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -249,26 +247,3 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { Tags: tags, } } - -func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) - if !found { - return ErrNoValidatorForAddress(k.codespace).Result() - } - - if ctx.BlockHeader().Time < validator.RevokedUntilTime { - return ErrValidatorJailed(k.codespace).Result() - } - - if ctx.IsCheckTx() { - return sdk.Result{} - } - - validator.Revoked = false - k.updateValidator(ctx, validator) - - tags := sdk.NewTags("action", []byte("unrevoke"), "validator", msg.ValidatorAddr.Bytes()) - return sdk.Result{ - Tags: tags, - } -} diff --git a/x/stake/keeper.go b/x/stake/keeper.go index e8ac6947c8..aba118ed5a 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -794,8 +794,8 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fract return } -// force unbond a validator -func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration int64) { +// 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 { @@ -803,9 +803,21 @@ func (k Keeper) ForceUnbond(ctx sdk.Context, pubkey crypto.PubKey, jailDuration return } val.Revoked = true - val.RevokedUntilTime = ctx.BlockHeader().Time + jailDuration k.updateValidator(ctx, val) // update the validator, now revoked - val, _ = k.GetValidatorByPubKey(ctx, pubkey) - logger.Info(fmt.Sprintf("Validator %s revoked for minimum duration %d", pubkey.Address(), jailDuration)) + 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 { + ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) + return + } + val.Revoked = false + k.updateValidator(ctx, val) // update the validator, now unrevoked + logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) return } diff --git a/x/stake/msg.go b/x/stake/msg.go index c0fdb4e5a1..0adff84d9b 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -209,37 +209,3 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { } return nil } - -//______________________________________________________________________ - -// MsgUnrevoke - struct for unrevoking revoked validator -type MsgUnrevoke struct { - ValidatorAddr sdk.Address `json:"address"` -} - -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 := json.Marshal(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 -} diff --git a/x/stake/validator.go b/x/stake/validator.go index 76905212f1..20f5710569 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -17,10 +17,9 @@ import ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { - Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator - Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? - RevokedUntilTime int64 `json:"revoked_until_time"` // timestamp before which the validator cannot unrevoke + Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators @@ -48,7 +47,6 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti Owner: owner, PubKey: pubKey, Revoked: false, - RevokedUntilTime: int64(0), PoolShares: NewUnbondedShares(sdk.ZeroRat()), DelegatorShares: sdk.ZeroRat(), Description: description, diff --git a/x/stake/wire.go b/x/stake/wire.go index ac382ff14d..6e6e382606 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -10,7 +10,6 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(MsgEditCandidacy{}, "cosmos-sdk/MsgEditCandidacy", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) - cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil) } var cdcEmpty = wire.NewCodec() From d03577a2fc312cc1b260a3d6a59dab1bfa1d56a4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 22:08:13 +0200 Subject: [PATCH 17/57] Fixes after rebase, jail in x/slashing --- x/auth/context_test.go | 2 +- x/auth/feekeeper_test.go | 6 +++--- x/slashing/handler.go | 14 ++++++++------ x/slashing/keeper.go | 5 ++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/x/auth/context_test.go b/x/auth/context_test.go index c4aaca269a..615b7d5196 100644 --- a/x/auth/context_test.go +++ b/x/auth/context_test.go @@ -12,7 +12,7 @@ import ( ) func TestContextWithSigners(t *testing.T) { - ms, _ := setupMultiStore() + ms, _, _ := setupMultiStore() ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) _, _, addr1 := keyPubAddr() diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 2f1ffc59bc..3c9190e4ec 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -23,7 +23,7 @@ func TestFeeCollectionKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially @@ -42,7 +42,7 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially @@ -62,7 +62,7 @@ func TestFeeCollectionKeeperClear(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) fck := NewFeeCollectionKeeper(cdc, capKey2) // set coins initially diff --git a/x/slashing/handler.go b/x/slashing/handler.go index 931ef58465..e1d6607898 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -22,12 +22,14 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { return ErrNoValidatorForAddress(k.codespace).Result() } - // TODO - /* - if ctx.BlockHeader().Time < validator.RevokedUntilTime { - return ErrValidatorJailed(k.codespace).Result() - } - */ + info, found := k.getValidatorSigningInfo(ctx, validator.GetPubKey().Address()) + if !found { + return ErrNoValidatorForAddress(k.codespace).Result() + } + + if ctx.BlockHeader().Time < info.JailedUntil { + return ErrValidatorJailed(k.codespace).Result() + } if ctx.IsCheckTx() { return sdk.Result{} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 918e85c2a3..4c9d92a2fa 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -99,7 +99,9 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, if height > minHeight && signInfo.SignedBlocksCounter < MinSignedPerWindow { 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) // , DowntimeUnbondDuration) // TODO + k.stakeKeeper.Revoke(ctx, pubkey) + signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration + k.setValidatorSigningInfo(ctx, address, signInfo) } } @@ -142,6 +144,7 @@ func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address type validatorSigningInfo struct { StartHeight int64 `json:"start_height"` IndexOffset int64 `json:"index_offset"` + JailedUntil int64 `json:"jailed_until"` SignedBlocksCounter int64 `json:"signed_blocks_counter"` } From bfa9d5f9142d1d196c3b56c4f64d3deef3f8932f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 22:12:45 +0200 Subject: [PATCH 18/57] Linter fixes --- x/slashing/errors.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/slashing/errors.go b/x/slashing/errors.go index f61eff17e5..8a923f7542 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -17,12 +17,17 @@ const ( CodeValidatorJailed CodeType = 202 ) +//nolint func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator") } + +//nolint func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") } + +//nolint func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") } From 93f1cb45cc242f55b1c5b683eab026354b8e9305 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 23:24:58 +0200 Subject: [PATCH 19/57] Split slashing params & signing info into separate files --- x/slashing/keeper.go | 85 -------------------------------------- x/slashing/params.go | 37 +++++++++++++++++ x/slashing/signing_info.go | 60 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 85 deletions(-) create mode 100644 x/slashing/params.go create mode 100644 x/slashing/signing_info.go diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 4c9d92a2fa..c8fcbe1b92 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -1,7 +1,6 @@ package slashing import ( - "encoding/binary" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,37 +9,6 @@ import ( 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 1% - // TODO Governance parameter? - SlashFractionDowntime = sdk.NewRat(1).Quo(sdk.NewRat(100)) -) - // Keeper of the slashing store type Keeper struct { storeKey sdk.StoreKey @@ -104,56 +72,3 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, k.setValidatorSigningInfo(ctx, address, signInfo) } } - -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 - } else { - k.cdc.MustUnmarshalBinary(bz, &info) - found = true - } - 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)) - if bz == nil { - // lazy: treat empty key as unsigned - signed = false - } else { - 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"` - IndexOffset int64 `json:"index_offset"` - JailedUntil int64 `json:"jailed_until"` - 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/params.go b/x/slashing/params.go new file mode 100644 index 0000000000..5e611fa0b5 --- /dev/null +++ b/x/slashing/params.go @@ -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 6 hours for testnets + DowntimeUnbondDuration int64 = 60 * 60 * 6 +) + +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)) +) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go new file mode 100644 index 0000000000..8cb66318db --- /dev/null +++ b/x/slashing/signing_info.go @@ -0,0 +1,60 @@ +package slashing + +import ( + "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +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 + } else { + k.cdc.MustUnmarshalBinary(bz, &info) + found = true + } + 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)) + if bz == nil { + // lazy: treat empty key as unsigned + signed = false + } else { + 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"` + IndexOffset int64 `json:"index_offset"` + JailedUntil int64 `json:"jailed_until"` + 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...)...) +} From 02ab73e26690e08f25c9acb1c13a55e704930934 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 23:39:57 +0200 Subject: [PATCH 20/57] Signing info slashing testcases --- x/slashing/keeper_test.go | 86 ----------------------------- x/slashing/signing_info.go | 8 +-- x/slashing/signing_info_test.go | 35 ++++++++++++ x/slashing/test_common.go | 98 +++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 90 deletions(-) create mode 100644 x/slashing/signing_info_test.go create mode 100644 x/slashing/test_common.go diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 9598b24f3c..0919b0a20d 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -1,75 +1,16 @@ 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), nil) - 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 TestHandleDoubleSign(t *testing.T) { ctx, ck, sk, keeper := createTestInput(t) addr, val, amt := addrs[0], pks[0], int64(100) @@ -145,30 +86,3 @@ func TestHandleAbsentValidator(t *testing.T) { pool = sk.GetPool(ctx) require.Equal(t, int64(99), pool.BondedTokens) } - -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, err := sdk.GetAddress(addr) - if err != nil { - panic(err) - } - 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}, - } -} diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 8cb66318db..cadd6d5805 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -43,10 +43,10 @@ func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address } type validatorSigningInfo struct { - StartHeight int64 `json:"start_height"` - IndexOffset int64 `json:"index_offset"` - JailedUntil int64 `json:"jailed_until"` - SignedBlocksCounter int64 `json:"signed_blocks_counter"` + 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) } func validatorSigningInfoKey(v sdk.Address) []byte { diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go new file mode 100644 index 0000000000..8000d67450 --- /dev/null +++ b/x/slashing/signing_info_test.go @@ -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 +} diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go new file mode 100644 index 0000000000..21274df1b8 --- /dev/null +++ b/x/slashing/test_common.go @@ -0,0 +1,98 @@ +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), nil) + 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, err := sdk.GetAddress(addr) + if err != nil { + panic(err) + } + 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}, + } +} From c0487996abf709cfec6bb2b6b3bd256d1310fa3a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 23:46:08 +0200 Subject: [PATCH 21/57] Update slashing docs, slight index change --- docs/spec/staking/valset-changes.md | 18 ++++++++++++------ x/slashing/keeper.go | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/spec/staking/valset-changes.md b/docs/spec/staking/valset-changes.md index e5979af39a..0e635fd047 100644 --- a/docs/spec/staking/valset-changes.md +++ b/docs/spec/staking/valset-changes.md @@ -156,25 +156,30 @@ The following information is stored with each validator candidate, and is only n ```go type ValidatorSigningInfo struct { StartHeight int64 - SignedBlocksBitArray BitArray + IndexOffset int64 + JailedUntil int64 SignedBlocksCounter int64 + SignedBlocksBitArray BitArray } ``` Where: * `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). +* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). +* `JailedUntil` is set whenever the candidate is revoked due to downtime +* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. * `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 `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: ``` -h = block.Height -index = h % SIGNED_BLOCKS_WINDOW +height := block.Height for val in block.Validators: signInfo = val.SignInfo + index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW + signInfo.IndexOffset++ previous = signInfo.SignedBlocksBitArray.Get(index) // update counter if array has changed @@ -191,6 +196,7 @@ for val in block.Validators: // 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 + if height > minHeight AND signInfo.SignedBlocksCounter < minSigned: + signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION + slash & unbond the validator ``` diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index c8fcbe1b92..8b0d585ec5 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -51,8 +51,8 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, } address := pubkey.Address() signInfo, _ := k.getValidatorSigningInfo(ctx, address) - signInfo.IndexOffset++ index := signInfo.IndexOffset % SignedBlocksWindow + signInfo.IndexOffset++ previous := k.getValidatorSigningBitArray(ctx, address, index) if previous && !signed { k.setValidatorSigningBitArray(ctx, address, index, false) From 26f22dbe4dd712f1df1271aa478f81fffd583003 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 28 May 2018 23:55:39 +0200 Subject: [PATCH 22/57] Test start height update --- x/slashing/handler.go | 10 +++++++++- x/slashing/keeper_test.go | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/x/slashing/handler.go b/x/slashing/handler.go index e1d6607898..a3218f6e63 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -22,7 +22,9 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { return ErrNoValidatorForAddress(k.codespace).Result() } - info, found := k.getValidatorSigningInfo(ctx, validator.GetPubKey().Address()) + addr := validator.GetPubKey().Address() + + info, found := k.getValidatorSigningInfo(ctx, addr) if !found { return ErrNoValidatorForAddress(k.codespace).Result() } @@ -35,9 +37,15 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result { 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, } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 0919b0a20d..b6aad3e6ca 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -71,7 +71,6 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) - height++ // should have been revoked validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) @@ -85,4 +84,23 @@ func TestHandleAbsentValidator(t *testing.T) { // should have been slashed pool = sk.GetPool(ctx) require.Equal(t, int64(99), pool.BondedTokens) + // 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) + // should not be immediately revoked again + height++ + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + validator = sk.ValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + // should be revoked again after 100 blocks + nextHeight := height + 100 + for ; height <= nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val, false) + } + validator = sk.ValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Unbonded, validator.GetStatus()) } From f4f8cc66d980db731f74d63d77cf62012dc4cae7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 00:10:52 +0200 Subject: [PATCH 23/57] Add some explanatory comments --- x/slashing/handler.go | 6 ++++++ x/slashing/keeper.go | 10 ++++++++++ x/slashing/keeper_test.go | 21 +++++++++++++-------- x/slashing/tick.go | 12 +++++++++++- x/stake/keeper.go | 6 ++++-- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/x/slashing/handler.go b/x/slashing/handler.go index a3218f6e63..cf83a6a992 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -16,7 +16,11 @@ func NewHandler(k Keeper) sdk.Handler { } } +// Validators must submit a transaction to unrevoke themselves 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() @@ -24,11 +28,13 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.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() } diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 8b0d585ec5..f003b33540 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -34,10 +34,14 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, sk stake.Keeper, codespace sdk 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) } @@ -50,9 +54,13 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, 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 signInfo, _ := k.getValidatorSigningInfo(ctx, address) index := signInfo.IndexOffset % SignedBlocksWindow signInfo.IndexOffset++ + + // Update signed block bit array & counter previous := k.getValidatorSigningBitArray(ctx, address, index) if previous && !signed { k.setValidatorSigningBitArray(ctx, address, index, false) @@ -63,8 +71,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, signInfo.SignedBlocksCounter++ k.setValidatorSigningInfo(ctx, address, signInfo) } + 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) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index b6aad3e6ca..2fef0c974a 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -39,7 +39,9 @@ func TestHandleAbsentValidator(t *testing.T) { 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 blocks OK for ; height < 1000; height++ { @@ -59,7 +61,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter) - // should be bonded still + // validator should be bonded still validator := sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) @@ -71,31 +73,34 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) - // should have been revoked + // validator should have been revoked validator = sk.ValidatorByPubKey(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()) // should fail prior to jail expiration + 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()) // should succeed after jail expiration + require.True(t, got.IsOK()) + // validator should be rebonded now validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) - // should have been slashed + // validator should have been slashed pool = sk.GetPool(ctx) require.Equal(t, int64(99), pool.BondedTokens) - // start height should have been changed + // 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) - // should not be immediately revoked again + // validator should not be immediately revoked again height++ ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) validator = sk.ValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) - // should be revoked again after 100 blocks + // validator should be revoked again after 100 unsigned blocks nextHeight := height + 100 for ; height <= nextHeight; height++ { ctx = ctx.WithBlockHeight(height) diff --git a/x/slashing/tick.go b/x/slashing/tick.go index e76c06abe3..abec2b3751 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -12,9 +12,12 @@ import ( 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) @@ -25,18 +28,25 @@ func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { 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[string]bool) for _, pubkey := range req.AbsentValidators { var pk crypto.PubKey sk.cdc.MustUnmarshalBinary(pubkey, &pk) absent[string(pk.Bytes())] = true } + + // 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() sk.handleValidatorSignature(ctx, pubkey, !absent[string(pubkey.Bytes())]) return false }) - // TODO Add some more tags so clients can track slashing + + // 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(), } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index aba118ed5a..8775dbef53 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -799,7 +799,8 @@ 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 { - ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) + // TODO Should we panic? + ctx.Logger().Info("Validator with pubkey %s not found, cannot revoke", pubkey) return } val.Revoked = true @@ -813,7 +814,8 @@ 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 { - ctx.Logger().Info("Validator with pubkey %s not found, cannot force unbond", pubkey) + // TODO Should we panic? + ctx.Logger().Info("Validator with pubkey %s not found, cannot unrevoke", pubkey) return } val.Revoked = false From aed5d94b1ceba2969af993386f1bd9ebc827ac94 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:26:17 +0200 Subject: [PATCH 24/57] Remove absent validators from sdk.NewContext --- baseapp/baseapp.go | 8 ++++---- examples/democoin/x/cool/keeper_test.go | 2 +- examples/democoin/x/pow/handler_test.go | 2 +- examples/democoin/x/pow/keeper_test.go | 2 +- examples/democoin/x/simplestake/keeper_test.go | 4 ++-- types/context.go | 5 ++--- types/context_test.go | 4 ++-- types/lib/mapper_test.go | 2 +- x/auth/ante_test.go | 10 +++++----- x/auth/context_test.go | 2 +- x/auth/feekeeper_test.go | 6 +++--- x/auth/mapper_test.go | 2 +- x/bank/keeper_test.go | 6 +++--- x/ibc/ibc_test.go | 2 +- x/slashing/test_common.go | 2 +- x/stake/test_common.go | 2 +- 16 files changed, 30 insertions(+), 31 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index baed1fb47d..cdc5ffcf7a 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -235,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, nil) + return sdk.NewContext(app.checkState.ms, header, true, nil, app.Logger) } - return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger, nil) + return sdk.NewContext(app.deliverState.ms, header, false, nil, app.Logger) } type state struct { @@ -253,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, nil), + ctx: sdk.NewContext(ms, header, true, nil, app.Logger), } } @@ -261,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, nil), + ctx: sdk.NewContext(ms, header, false, nil, app.Logger), } } diff --git a/examples/democoin/x/cool/keeper_test.go b/examples/democoin/x/cool/keeper_test.go index f632ae31ee..d497dee699 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, nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, 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 867120eb84..30aeafa2a5 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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 98e8ebcfea..a4afb876a9 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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 ef34f3085c..15bd14c79b 100644 --- a/examples/democoin/x/simplestake/keeper_test.go +++ b/examples/democoin/x/simplestake/keeper_test.go @@ -37,7 +37,7 @@ func TestKeeperGetSet(t *testing.T) { 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) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{}) coinKeeper := bank.NewKeeper(accountMapper) diff --git a/types/context.go b/types/context.go index 993c148ec5..10ae99724f 100644 --- a/types/context.go +++ b/types/context.go @@ -30,8 +30,7 @@ type Context struct { } // create a new context -func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, - txBytes []byte, logger log.Logger, absentValidators [][]byte) Context { +func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte, logger log.Logger) Context { c := Context{ Context: context.Background(), @@ -45,7 +44,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, c = c.WithIsCheckTx(isCheckTx) c = c.WithTxBytes(txBytes) c = c.WithLogger(logger) - c = c.WithAbsentValidators(absentValidators) + c = c.WithAbsentValidators(nil) c = c.WithGasMeter(NewInfiniteGasMeter()) return c } diff --git a/types/context_test.go b/types/context_test.go index 1eafdaf7e7..ec5faab440 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(), nil) + ctx := types.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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(), nil) + ctx := types.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) return ctx } diff --git a/types/lib/mapper_test.go b/types/lib/mapper_test.go index a610f1e0fa..e1759b06ac 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(), nil) + ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) cdc := wire.NewCodec() return ctx, cdc } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 729f0769ef..b7f22e5d54 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses priv1, addr1 := privAndAddr() diff --git a/x/auth/context_test.go b/x/auth/context_test.go index 615b7d5196..a93de44d0c 100644 --- a/x/auth/context_test.go +++ b/x/auth/context_test.go @@ -13,7 +13,7 @@ import ( func TestContextWithSigners(t *testing.T) { ms, _, _ := setupMultiStore() - ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) _, _, addr1 := keyPubAddr() _, _, addr2 := keyPubAddr() diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 3c9190e4ec..2f1ffc59bc 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -23,7 +23,7 @@ func TestFeeCollectionKeeperGetSet(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially @@ -42,7 +42,7 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // no coins initially @@ -62,7 +62,7 @@ func TestFeeCollectionKeeperClear(t *testing.T) { cdc := wire.NewCodec() // make context and keeper - ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) fck := NewFeeCollectionKeeper(cdc, capKey2) // set coins initially diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index dc6afef57e..7f6397069a 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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 07ba91e0c5..117c69e7ae 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) 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 9ed4b38254..e13df4f8dd 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(), nil) + ctx := sdk.NewContext(cms, abci.Header{}, false, nil, log.NewNopLogger()) return ctx } diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 21274df1b8..8ce654f338 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -53,7 +53,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep 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), nil) + 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) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 1cd46549b0..b7a5152c09 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(), nil) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil, log.NewNopLogger()) cdc := makeTestCodec() accountMapper := auth.NewAccountMapper( cdc, // amino codec From 0dae7f8e4c2de90dd39e8f2932dd544c068dafa9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:27:43 +0200 Subject: [PATCH 25/57] Panic on revoke/unrevoke nonexistent validator --- x/stake/keeper.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 8775dbef53..d943193a11 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -799,9 +799,7 @@ 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 { - // TODO Should we panic? - ctx.Logger().Info("Validator with pubkey %s not found, cannot revoke", pubkey) - return + panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey)) } val.Revoked = true k.updateValidator(ctx, val) // update the validator, now revoked @@ -814,9 +812,7 @@ 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 { - // TODO Should we panic? - ctx.Logger().Info("Validator with pubkey %s not found, cannot unrevoke", pubkey) - return + panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey)) } val.Revoked = false k.updateValidator(ctx, val) // update the validator, now unrevoked From b8b4fe24e3386a1b4a22c144cd812f5cdfd1c249 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:33:01 +0200 Subject: [PATCH 26/57] Absent validators map[crypto.PubKey]struct{} --- x/slashing/tick.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x/slashing/tick.go b/x/slashing/tick.go index abec2b3751..0d200f5934 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -30,17 +30,21 @@ func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { } // Figure out which validators were absent - absent := make(map[string]bool) + absent := make(map[crypto.PubKey]struct{}) for _, pubkey := range req.AbsentValidators { var pk crypto.PubKey sk.cdc.MustUnmarshalBinary(pubkey, &pk) - absent[string(pk.Bytes())] = true + 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() - sk.handleValidatorSignature(ctx, pubkey, !absent[string(pubkey.Bytes())]) + abs := false + if _, ok := absent[pubkey]; ok { + abs = true + } + sk.handleValidatorSignature(ctx, pubkey, abs) return false }) From 7fbecc6b727f83448aa2eae72d0a298fb8f5d279 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:35:42 +0200 Subject: [PATCH 27/57] Clarify default signing info --- x/slashing/keeper.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index f003b33540..b27fdb5927 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -56,6 +56,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, 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++ @@ -65,11 +66,9 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, 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 @@ -79,6 +78,8 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, k.stakeKeeper.Slash(ctx, pubkey, height, SlashFractionDowntime) k.stakeKeeper.Revoke(ctx, pubkey) signInfo.JailedUntil = ctx.BlockHeader().Time + DowntimeUnbondDuration - k.setValidatorSigningInfo(ctx, address, signInfo) } + + // Set the updated signing info + k.setValidatorSigningInfo(ctx, address, signInfo) } From 65945c069c7cbc9fabbe6523fd0086054fd82a1b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:48:29 +0200 Subject: [PATCH 28/57] Clarify counter logic --- x/slashing/keeper.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index b27fdb5927..46ed4591b2 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -63,10 +63,14 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, // Update signed block bit array & counter previous := k.getValidatorSigningBitArray(ctx, address, index) - if previous && !signed { + if previous == signed { + // No need to update counter + } else if previous && !signed { + // Signed => unsigned, decrement counter k.setValidatorSigningBitArray(ctx, address, index, false) signInfo.SignedBlocksCounter-- } else if !previous && signed { + // Unsigned => signed, increment counter k.setValidatorSigningBitArray(ctx, address, index, true) signInfo.SignedBlocksCounter++ } From 345b5b88a8bb357c047f990ded1b97fbb88e5c33 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:50:04 +0200 Subject: [PATCH 29/57] Add comment on MsgUnrevoke --- x/slashing/msg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/msg.go b/x/slashing/msg.go index d67ddf70df..72d2255d61 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -14,7 +14,7 @@ var _ sdk.Msg = &MsgUnrevoke{} // MsgUnrevoke - struct for unrevoking revoked validator type MsgUnrevoke struct { - ValidatorAddr sdk.Address `json:"address"` + ValidatorAddr sdk.Address `json:"address"` // address of the validator owner } func NewMsgUnrevoke(validatorAddr sdk.Address) MsgUnrevoke { From 88e00257242c028f1f72e1b010deb33994e4f6ef Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:56:40 +0200 Subject: [PATCH 30/57] DowntimeUnbondDuration to 10 minutes --- x/slashing/params.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/slashing/params.go b/x/slashing/params.go index 5e611fa0b5..3bba85fa66 100644 --- a/x/slashing/params.go +++ b/x/slashing/params.go @@ -22,8 +22,8 @@ const ( // Downtime unbond duration // TODO Governance parameter? - // TODO Temporarily set to 6 hours for testnets - DowntimeUnbondDuration int64 = 60 * 60 * 6 + // TODO Temporarily set to 10 minutes for testnets + DowntimeUnbondDuration int64 = 60 * 10 ) var ( From 69af8b1a945fc8a42262b87bb06b451c0b38b76f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 02:58:43 +0200 Subject: [PATCH 31/57] Clarify signing info stored by validator address --- x/slashing/signing_info.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index cadd6d5805..fcbb0d1c7e 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -6,6 +6,7 @@ import ( 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)) @@ -18,12 +19,14 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (i 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)) @@ -36,6 +39,7 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address 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) @@ -49,10 +53,12 @@ type validatorSigningInfo struct { 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)) From ef3e2200a5fca8313a3e0340224355d5722e51ef Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 05:00:47 +0200 Subject: [PATCH 32/57] Fix dependencies --- Gopkg.lock | 17 ++++++++--------- Gopkg.toml | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 286760d609..5fe7b1a876 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "1432d294a5b055c297457c25434efbf13384cc46" + revision = "bc0944904505aab55e089371a892be2f87883161" [[projects]] name = "github.com/davecgh/go-spew" @@ -253,7 +253,6 @@ revision = "5d6fca44a948d2be89a9702de7717f0168403d3d" [[projects]] - branch = "develop" name = "github.com/tendermint/abci" packages = [ "client", @@ -277,8 +276,8 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "ed62928576cfcaf887209dc96142cd79cdfff389" - version = "0.9.9" + revision = "3c22a7a539411f89a96738fcfa14c1027e24e5ec" + version = "0.9.10" [[projects]] name = "github.com/tendermint/go-crypto" @@ -342,7 +341,7 @@ "types/priv_validator", "version" ] - revision = "d3093426f455c4b33cee818e3bbf2d2b83e4b38d" + revision = "3b8c1ae119da990664f3b98bb2518032f5d837f9" [[projects]] name = "github.com/tendermint/tmlibs" @@ -374,7 +373,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "75e913eb8a8e3d31a97b216de09de106a7b07681" + revision = "ab813273cd59e1333f7ae7bff5d027d4aadf528c" [[projects]] branch = "master" @@ -388,13 +387,13 @@ "internal/timeseries", "trace" ] - revision = "9ef9f5bb98a1fdc41f8cf6c250a4404b4085e389" + revision = "dfa909b99c79129e1100513e5cd36307665e5723" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "77b0e4315053a57ed2962443614bdb28db152054" + revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f" [[projects]] name = "golang.org/x/text" @@ -455,6 +454,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "bbc0ee39f39cb296960a5fcb2a3916a4acc48ecbb675758ea1f61def44e994bb" + inputs-digest = "dc095a5d071dc8e1d7c3fd1208efbbe7d3c09551ecef76fdfafcdcdd88a49f50" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 53dbabbbb3..61d11f999b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -54,7 +54,7 @@ [[override]] name = "github.com/tendermint/abci" - branch = "develop" + revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" [[constraint]] name = "github.com/tendermint/go-crypto" From 5254a4aa1d847181d373144c1a283b5205909dd4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 05:24:57 +0200 Subject: [PATCH 33/57] Dependency fix, try II --- Gopkg.lock | 6 +++--- Gopkg.toml | 4 ++-- client/tx/search.go | 7 +++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 5fe7b1a876..e30a97fda3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -276,8 +276,8 @@ [[projects]] name = "github.com/tendermint/go-amino" packages = ["."] - revision = "3c22a7a539411f89a96738fcfa14c1027e24e5ec" - version = "0.9.10" + revision = "ed62928576cfcaf887209dc96142cd79cdfff389" + version = "0.9.9" [[projects]] name = "github.com/tendermint/go-crypto" @@ -454,6 +454,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "dc095a5d071dc8e1d7c3fd1208efbbe7d3c09551ecef76fdfafcdcdd88a49f50" + inputs-digest = "5ec04e1fee08c36e90fd44ec2cdd217f41d80ef5b256fee07cf4030c6cdfd413" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 61d11f999b..ce6ecda982 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -62,9 +62,9 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "~0.9.9" + version = "=0.9.9" -[[override]] +[[constraint]] name = "github.com/tendermint/iavl" branch = "develop" diff --git a/client/tx/search.go b/client/tx/search.go index 672f29ff14..527661626a 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -61,12 +61,15 @@ func searchTx(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]byte, } prove := !viper.GetBool(client.FlagTrustNode) - txs, err := node.TxSearch(query, prove) + // TODO: take these as args + page := 0 + perPage := 100 + res, err := node.TxSearch(query, prove, page, perPage) if err != nil { return nil, err } - info, err := formatTxResults(cdc, txs) + info, err := formatTxResults(cdc, res.Txs) if err != nil { return nil, err } From 4447440c2907d15d50ba26a9345856018dc3f3c4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 05:47:28 +0200 Subject: [PATCH 34/57] Switch to tagged ABCI version --- Gopkg.lock | 3 ++- Gopkg.toml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index e30a97fda3..f4da3a15cc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -262,6 +262,7 @@ "types" ] revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" + version = "v0.11.0-rc0" [[projects]] branch = "master" @@ -454,6 +455,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5ec04e1fee08c36e90fd44ec2cdd217f41d80ef5b256fee07cf4030c6cdfd413" + inputs-digest = "ad811206ccd65d54f53041f1284d45d3645c1e89b77e1e6843af786630f0b587" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index ce6ecda982..74569e5082 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -52,9 +52,9 @@ name = "github.com/stretchr/testify" version = "~1.2.1" -[[override]] +[[constraint]] name = "github.com/tendermint/abci" - revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" + version = "0.11.0-rc0" [[constraint]] name = "github.com/tendermint/go-crypto" From a5395f061925e87232589bb605b60ecc01671d20 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 06:07:55 +0200 Subject: [PATCH 35/57] Swap to tagged IAVL version --- Gopkg.lock | 6 +++--- Gopkg.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index f4da3a15cc..39e549b08f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -293,13 +293,13 @@ version = "v0.6.2" [[projects]] - branch = "develop" name = "github.com/tendermint/iavl" packages = [ ".", "sha256truncated" ] revision = "c9206995e8f948e99927f5084a88a7e94ca256da" + version = "v0.8.0-rc0" [[projects]] branch = "cwgoes/update-abci" @@ -342,7 +342,7 @@ "types/priv_validator", "version" ] - revision = "3b8c1ae119da990664f3b98bb2518032f5d837f9" + revision = "3bf9a7dc503b8fd39bda3d64f2d039253afa6f8c" [[projects]] name = "github.com/tendermint/tmlibs" @@ -455,6 +455,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ad811206ccd65d54f53041f1284d45d3645c1e89b77e1e6843af786630f0b587" + inputs-digest = "473825a42fdba2832156190a00251b6f06aa6f039e5931ec00219b1b7004a89e" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 74569e5082..8dc5a7a7f5 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -66,7 +66,7 @@ [[constraint]] name = "github.com/tendermint/iavl" - branch = "develop" + version = "0.8.0-rc0" [[override]] name = "github.com/tendermint/tendermint" From 6712ea7f3a14d78be83b59eb45c3316ef99314c4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 08:32:39 +0200 Subject: [PATCH 36/57] Present, not absent --- x/slashing/tick.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 0d200f5934..8125522741 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -40,11 +40,11 @@ func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { // 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() - abs := false + present := true if _, ok := absent[pubkey]; ok { - abs = true + present = false } - sk.handleValidatorSignature(ctx, pubkey, abs) + sk.handleValidatorSignature(ctx, pubkey, present) return false }) From 74e8159c3ff1a9f14cd5eab8ee0289b81ff07028 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 08:49:37 +0200 Subject: [PATCH 37/57] Force update staking store --- x/stake/genesis.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index d45adc3d7f..e8965dcf2f 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -32,11 +32,15 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { k.setPool(ctx, data.Pool) k.setNewParams(ctx, data.Params) for _, validator := range data.Validators { + // Staking assumes bonded validators are already stored, need to force update + validator.PoolShares.Status = sdk.Unbonded k.updateValidator(ctx, validator) } for _, bond := range data.Bonds { k.setDelegation(ctx, bond) } + store := ctx.KVStore(k.storeKey) + k.updateBondedValidatorsFull(ctx, store) } // WriteGenesis - output genesis parameters From 9dfaf1797c89a385107cce7e6b5cd72d6dce928e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 08:56:53 +0200 Subject: [PATCH 38/57] Staking InitGenesis fix II --- x/stake/genesis.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index e8965dcf2f..e6baa099b5 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -34,6 +34,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { for _, validator := range data.Validators { // Staking assumes bonded validators are already stored, need to force update validator.PoolShares.Status = sdk.Unbonded + k.setValidator(ctx, validator) k.updateValidator(ctx, validator) } for _, bond := range data.Bonds { From 3b4aa4d0ae89165609223ad48595c1d829f7ed6b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 29 May 2018 21:46:38 +0200 Subject: [PATCH 39/57] Minor test fix after merge --- x/slashing/test_common.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 8ce654f338..0228e9498b 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -81,10 +81,7 @@ func newPubKey(pk string) (res crypto.PubKey) { } func testAddr(addr string) sdk.Address { - res, err := sdk.GetAddress(addr) - if err != nil { - panic(err) - } + res := []byte(addr) return res } From c8133a0f6a96bfddd3cf2398986b0a503dcf5f53 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 30 May 2018 14:43:22 -0700 Subject: [PATCH 40/57] fix revoke validator bug --- client/context/helpers.go | 2 +- x/stake/keeper.go | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/context/helpers.go b/client/context/helpers.go index f4686befde..f47cc7ff41 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -59,7 +59,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName // Query from Tendermint with the provided storename and path func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { - path := fmt.Sprintf("/store/%s/key", storeName) + path := fmt.Sprintf("/store/%s/%s", storeName, endPath) node, err := ctx.GetNode() if err != nil { return res, err diff --git a/x/stake/keeper.go b/x/stake/keeper.go index d943193a11..a3991a6c60 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -220,8 +220,12 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - k.setPool(ctx, pool) + k.unbondValidator(ctx, store, validator) + + // 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 @@ -674,6 +678,13 @@ func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Poo 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.Set(ValidatorPowerCliffKey, nil) + store.Set(ValidatorCliffKey, nil) +} + //__________________________________________________________________________ // Implements ValidatorSet From 40526d3e770681fd0109659019df31a3c52d6665 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 30 May 2018 14:52:56 -0700 Subject: [PATCH 41/57] unbond validator return updated validator --- x/stake/keeper.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index a3991a6c60..c97e7840e1 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -220,7 +220,7 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) if validator.Revoked && oldValidator.Status() == sdk.Bonded { - k.unbondValidator(ctx, store, validator) + validator = k.unbondValidator(ctx, store, validator) // need to also clear the cliff validator spot because the revoke has // opened up a new spot which will be filled when @@ -430,7 +430,7 @@ func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) { } // 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) // sanity check @@ -452,6 +452,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Va // also remove from the Bonded Validators Store store.Delete(GetValidatorsBondedKey(validator.PubKey)) + return validator } // perform all the store operations for when a validator status becomes bonded From 4e266013a8ffcbf9a9a9a5545c2f8bbcbd0e2906 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:13:13 +0200 Subject: [PATCH 42/57] store.Delete instead of store.Set to nil --- x/stake/keeper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index c97e7840e1..4f93308c73 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -682,8 +682,8 @@ func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Poo // 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.Set(ValidatorPowerCliffKey, nil) - store.Set(ValidatorCliffKey, nil) + store.Delete(ValidatorPowerCliffKey) + store.Delete(ValidatorCliffKey) } //__________________________________________________________________________ From 5f03e370c313d492858615af2d61c9af9c0f4a5d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:19:23 +0200 Subject: [PATCH 43/57] Remove ValidatorByPubKey, don't marshal sdk.Address --- types/stake.go | 11 +++++------ x/slashing/keeper_test.go | 10 +++++----- x/stake/keeper.go | 18 +++--------------- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/types/stake.go b/types/stake.go index cd1a68533f..bfcef7fa0c 100644 --- a/types/stake.go +++ b/types/stake.go @@ -56,12 +56,11 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, Address) Validator // get a particular validator by owner address - ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by public key - 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 + Validator(Context, Address) Validator // get a particular validator by owner address + 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 } //_______________________________________________________________________________ diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 2fef0c974a..70f42b3243 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -62,7 +62,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-50, info.SignedBlocksCounter) // validator should be bonded still - validator := sk.ValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) require.Equal(t, int64(100), pool.BondedTokens) @@ -74,7 +74,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.StartHeight) require.Equal(t, SignedBlocksWindow-51, info.SignedBlocksCounter) // validator should have been revoked - validator = sk.ValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) // unrevocation should fail prior to jail expiration got = slh(ctx, NewMsgUnrevoke(addr)) @@ -84,7 +84,7 @@ func TestHandleAbsentValidator(t *testing.T) { got = slh(ctx, NewMsgUnrevoke(addr)) require.True(t, got.IsOK()) // validator should be rebonded now - validator = sk.ValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) // validator should have been slashed pool = sk.GetPool(ctx) @@ -98,7 +98,7 @@ func TestHandleAbsentValidator(t *testing.T) { height++ ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) - validator = sk.ValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) // validator should be revoked again after 100 unsigned blocks nextHeight := height + 100 @@ -106,6 +106,6 @@ func TestHandleAbsentValidator(t *testing.T) { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, false) } - validator = sk.ValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 4f93308c73..bab1328bed 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -42,12 +42,10 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid // 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) - b := store.Get(GetValidatorByPubKeyKey(pubkey)) - if b == nil { + addr := store.Get(GetValidatorByPubKeyKey(pubkey)) + if addr == nil { return validator, false } - var addr sdk.Address - k.cdc.MustUnmarshalBinary(b, &addr) return k.getValidator(store, addr) } @@ -68,8 +66,7 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { bz := k.cdc.MustMarshalBinary(validator) store.Set(GetValidatorKey(validator.Owner), bz) // set pointer by pubkey - bz = k.cdc.MustMarshalBinary(validator.Owner) - store.Set(GetValidatorByPubKeyKey(validator.PubKey), bz) + store.Set(GetValidatorByPubKeyKey(validator.PubKey), validator.Owner) } // Get the set of all validators with no limits, used during genesis dump @@ -740,15 +737,6 @@ func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { return val } -// get the sdk.validator for a particular pubkey -func (k Keeper) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - return nil - } - return val -} - // total power from the bond func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { pool := k.GetPool(ctx) From 1a3ef511298ef7891ea648e20a23944bb6e75a6b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:21:00 +0200 Subject: [PATCH 44/57] PubKeyKey => PubKeyIndexKey --- x/stake/keeper.go | 6 +++--- x/stake/keeper_keys.go | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index bab1328bed..21b58c1feb 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -42,7 +42,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Valid // 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(GetValidatorByPubKeyKey(pubkey)) + addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) if addr == nil { return validator, false } @@ -66,7 +66,7 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { bz := k.cdc.MustMarshalBinary(validator) store.Set(GetValidatorKey(validator.Owner), bz) // set pointer by pubkey - store.Set(GetValidatorByPubKeyKey(validator.PubKey), validator.Owner) + store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) } // Get the set of all validators with no limits, used during genesis dump @@ -489,7 +489,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) pool := k.getPool(store) store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByPubKeyKey(validator.PubKey)) + store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) store.Delete(GetValidatorsByPowerKey(validator, pool)) // delete from the current and power weighted validator groups if the validator diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index e09a47ecab..632a86ec3c 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -13,17 +13,17 @@ import ( //nolint var ( // Keys for store prefixes - ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPubKeyKey = []byte{0x03} // prefix for each key to a validator by pubkey - ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators - ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power - ValidatorCliffKey = []byte{0x06} // key for block-local tx index - ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index - TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated - DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x10} // key for block-local tx index + ParamKey = []byte{0x00} // key for parameters relating to staking + PoolKey = []byte{0x01} // key for the staking pools + ValidatorsKey = []byte{0x02} // prefix for each key to a validator + ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey + ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators + ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power + ValidatorCliffKey = []byte{0x06} // key for block-local tx index + ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + 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 @@ -34,8 +34,8 @@ func GetValidatorKey(ownerAddr sdk.Address) []byte { } // get the key for the validator with pubkey -func GetValidatorByPubKeyKey(pubkey crypto.PubKey) []byte { - return append(ValidatorsByPubKeyKey, pubkey.Bytes()...) +func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { + return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) } // get the key for the current validator group, ordered like tendermint From 99bed49c8d3e5d695de2e4fe5c7e9199c8e79671 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:25:18 +0200 Subject: [PATCH 45/57] Minor wording changes --- x/slashing/errors.go | 6 +----- x/slashing/handler.go | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/x/slashing/errors.go b/x/slashing/errors.go index 8a923f7542..087dc03141 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -1,3 +1,4 @@ +//nolint package slashing import ( @@ -17,17 +18,12 @@ const ( CodeValidatorJailed CodeType = 202 ) -//nolint func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "That address is not associated with any known validator") } - -//nolint func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") } - -//nolint func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeValidatorJailed, "Validator jailed, cannot yet be unrevoked") } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index cf83a6a992..98e9d30ada 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -16,7 +16,7 @@ func NewHandler(k Keeper) sdk.Handler { } } -// Validators must submit a transaction to unrevoke themselves after +// 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 { From 004e10ebcdc4228946eaf2b322205216ae527c7c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:32:08 +0200 Subject: [PATCH 46/57] More comments on counter logic --- x/slashing/keeper.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 46ed4591b2..a37c5a07b8 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -62,15 +62,17 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, 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 { - // No need to update counter + // Array value at this index has not changed, no need to update counter } else if previous && !signed { - // Signed => unsigned, decrement counter + // Array value has changed from signed to unsigned, decrement counter k.setValidatorSigningBitArray(ctx, address, index, false) signInfo.SignedBlocksCounter-- } else if !previous && signed { - // Unsigned => signed, increment counter + // Array value has changed from unsigned to signed, increment counter k.setValidatorSigningBitArray(ctx, address, index, true) signInfo.SignedBlocksCounter++ } From 0324be418951bfe098d4a7d112dc23ca9553b363 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:38:20 +0200 Subject: [PATCH 47/57] Clarify comment on removePoolShares --- x/stake/validator.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/x/stake/validator.go b/x/stake/validator.go index 1edb8bb836..729a605a96 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -155,18 +155,20 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, return v, pool } -// Remove & burn pool shares, e.g. when slashing a validator -func (v Validator) removePoolShares(pool Pool, amt sdk.Rat) (Validator, Pool, int64) { +// 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(amt) + pool, tokens = pool.removeSharesUnbonded(poolShares) case sdk.Unbonding: - pool, tokens = pool.removeSharesUnbonding(amt) + pool, tokens = pool.removeSharesUnbonding(poolShares) case sdk.Bonded: - pool, tokens = pool.removeSharesBonded(amt) + pool, tokens = pool.removeSharesBonded(poolShares) } - v.PoolShares.Amount = v.PoolShares.Amount.Sub(amt) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) return v, pool, tokens } From 7e9192f513c16d1c1fd93af3bee9a132f88adf50 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:41:28 +0200 Subject: [PATCH 48/57] Separate validator pub key index updates --- x/stake/genesis.go | 1 + x/stake/handler.go | 1 + x/stake/keeper.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index e6baa099b5..a2cd58bdb5 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -35,6 +35,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // Staking assumes bonded validators are already stored, need to force update validator.PoolShares.Status = sdk.Unbonded k.setValidator(ctx, validator) + k.setValidatorByPubKeyIndex(ctx, validator) k.updateValidator(ctx, validator) } for _, bond := range data.Bonds { diff --git a/x/stake/handler.go b/x/stake/handler.go index 53653557cc..6f2360adf7 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -55,6 +55,7 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) k.setValidator(ctx, validator) + k.setValidatorByPubKeyIndex(ctx, validator) tags := sdk.NewTags( "action", []byte("declareCandidacy"), "validator", msg.ValidatorAddr.Bytes(), diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 21b58c1feb..b94f392a63 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -65,6 +65,10 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { // set main store bz := k.cdc.MustMarshalBinary(validator) 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) } From f5f53c0a8df6677f7f215d754c3b139e012d69b3 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 00:55:16 +0200 Subject: [PATCH 49/57] Remove spec changes (now in #1048) --- docs/spec/staking/old/spec.md | 12 ++--- docs/spec/staking/old/spec2.md | 18 +++---- docs/spec/staking/transactions.md | 4 +- docs/spec/staking/valset-changes.md | 80 ++++++++++++----------------- 4 files changed, 51 insertions(+), 63 deletions(-) diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index 1eddc3e33d..bd87ec0285 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 `Delegation`. It is owned by one delegator, and is +funds are held in a `DelegatorBond`. 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 Delegation struct { +type DelegatorBond 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 `Delegation` is individually indexed within the store by delegator +Each `DelegatorBond` is individually indexed within the store by delegator address and candidate pubkey. - key: Delegator and Candidate-Pubkey - - value: Delegation + - value: DelegatorBond ### 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 `Delegation.Shares`. +candidate will return shares which are assigned in `DelegatorBond.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 `Delegation.FeeWithdrawalHeight` to + was added. This is achieved by setting `DelegatorBond.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 68f20703dc..72bb8a2e37 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 `Delegation` for each delegation to a candidate by a delegator, indexed by delegator and candidate +- a `DelegatorBond` 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. -### Delegation +### DelegatorBond Atom holders may delegate coins to validators, under this circumstance their -funds are held in a `Delegation`. It is owned by one delegator, and is +funds are held in a `DelegatorBond`. 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 Delegation struct { +type DelegatorBond 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 `Delegation` is individually indexed within the store by delegator +Each `DelegatorBond` is individually indexed within the store by delegator address and candidate pubkey. - key: Delegator and Candidate-Pubkey - - value: Delegation + - value: DelegatorBond ### 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 `Delegation.Shares`. +candidate will return shares which are assigned in `DelegatorBond.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 `Delegation.FeeWithdrawalHeight` to + was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to the height which the bond was added. diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index eed082503b..52f324b0f7 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 0e635fd047..bc52b89980 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,48 +155,36 @@ The following information is stored with each validator candidate, and is only n ```go type ValidatorSigningInfo struct { - StartHeight int64 - IndexOffset int64 - JailedUntil int64 - SignedBlocksCounter int64 - SignedBlocksBitArray BitArray + StartHeight int64 + SignedBlocksBitArray BitArray } ``` Where: * `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). -* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). -* `JailedUntil` is set whenever the candidate is revoked due to downtime -* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. * `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 `1` if the validator was included, and a `0` if it was not. Note it is initialized with all 0s. +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. At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: ``` -height := block.Height +h = block.Height +index = h % SIGNED_BLOCKS_WINDOW for val in block.Validators: - signInfo = val.SignInfo - index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW - signInfo.IndexOffset++ - previous = signInfo.SignedBlocksBitArray.Get(index) + signInfo = val.SignInfo + if val in block.LastCommit: + signInfo.SignedBlocksBitArray.Set(index, 0) + else + signInfo.SignedBlocksBitArray.Set(index, 1) - // 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 height > minHeight AND signInfo.SignedBlocksCounter < minSigned: - signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION - slash & unbond the validator + // 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 ``` From 604fd4c9a78f7397cf6e0629e6a5829c419c9132 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 01:37:38 +0200 Subject: [PATCH 50/57] Address a few style comments --- x/slashing/keeper_test.go | 4 ++-- x/slashing/signing_info.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 70f42b3243..c14e063151 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -16,7 +16,7 @@ func TestHandleDoubleSign(t *testing.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) + 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()) keeper.handleDoubleSign(ctx, 0, 0, val) // double sign less than max age @@ -33,7 +33,7 @@ func TestHandleAbsentValidator(t *testing.T) { slh := NewHandler(keeper) got := sh(ctx, newTestMsgDeclareCandidacy(addr, val, amt)) require.True(t, got.IsOK()) - _ = sk.Tick(ctx) + 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()) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index fcbb0d1c7e..05285b6809 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -12,10 +12,10 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.Address) (i bz := store.Get(validatorSigningInfoKey(address)) if bz == nil { found = false - } else { - k.cdc.MustUnmarshalBinary(bz, &info) - found = true + return } + k.cdc.MustUnmarshalBinary(bz, &info) + found = true return } @@ -33,9 +33,9 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.Address if bz == nil { // lazy: treat empty key as unsigned signed = false - } else { - k.cdc.MustUnmarshalBinary(bz, &signed) + return } + k.cdc.MustUnmarshalBinary(bz, &signed) return } From 5c4c486e7b0ad51076a8e78f16f0f24a3767ad1d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 01:39:57 +0200 Subject: [PATCH 51/57] Add newlines to clarify testcase separation --- x/slashing/keeper_test.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index c14e063151..3d6da8c232 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -12,6 +12,8 @@ import ( ) 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)) @@ -19,14 +21,20 @@ func TestHandleDoubleSign(t *testing.T) { 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()) - keeper.handleDoubleSign(ctx, 0, 0, val) // double sign less than max age + + // 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}) - keeper.handleDoubleSign(ctx, 0, 0, val) // double sign past max age + + // 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) @@ -43,7 +51,8 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.SignedBlocksCounter) require.Equal(t, int64(0), info.JailedUntil) height := int64(0) - // 1000 blocks OK + + // 1000 first blocks OK for ; height < 1000; height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val, true) @@ -52,6 +61,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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) @@ -61,11 +71,13 @@ func TestHandleAbsentValidator(t *testing.T) { 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) @@ -73,33 +85,41 @@ func TestHandleAbsentValidator(t *testing.T) { 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++ { From 3d37d51795454c64c9468908af169505bbaccaf1 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 01:47:24 +0200 Subject: [PATCH 52/57] Switch to evidence type enum --- Gopkg.lock | 5 ++--- Gopkg.toml | 4 ++-- x/slashing/tick.go | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 57986c440c..5e76a49cfd 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -267,8 +267,7 @@ "server", "types" ] - revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" - version = "v0.11.0-rc0" + revision = "c67bb414c7ee617f18d81a50f8a837318bb6d7dc" [[projects]] branch = "master" @@ -461,6 +460,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "04b888e63be8b6999da76f98c44c12a21920b12186378957d6b874270f300aaf" + inputs-digest = "8f9c58893f04daef93d91512e658560cf076a766fc100c8bc4f718d7abf1ea9c" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index d30953da59..843b3c4618 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.11.0-rc0" + revision = "c67bb414c7ee617f18d81a50f8a837318bb6d7dc" [[constraint]] name = "github.com/tendermint/go-crypto" diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 8125522741..14db48b379 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -1,7 +1,6 @@ package slashing import ( - "bytes" "encoding/binary" "fmt" @@ -21,8 +20,8 @@ func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { for _, evidence := range req.ByzantineValidators { var pk crypto.PubKey sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk) - switch { - case bytes.Compare(evidence.Type, []byte("doubleSign")) == 0: + switch evidence.Type { + case abci.EvidenceType_DOUBLE_SIGN: 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))) From 141bc5fb1cffcd7ec191a501656e7ba8d39735ae Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 01:52:07 +0200 Subject: [PATCH 53/57] amino.MarshalJSON instead of json.Marshal --- x/slashing/msg.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x/slashing/msg.go b/x/slashing/msg.go index 72d2255d61..d2676af81a 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -1,11 +1,12 @@ package slashing import ( - "encoding/json" - 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" @@ -29,7 +30,7 @@ func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.Val // get the bytes for the message signer to sign on func (msg MsgUnrevoke) GetSignBytes() []byte { - b, err := json.Marshal(msg) + b, err := cdc.MarshalJSON(msg) if err != nil { panic(err) } From e0b5118fce6726250e6ccd830bea6041fd509093 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 03:00:11 +0200 Subject: [PATCH 54/57] Staking InitGenesis fixes --- x/stake/genesis.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index a2cd58bdb5..cd1c0f9959 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -29,19 +29,20 @@ func DefaultGenesisState() GenesisState { // InitGenesis - store genesis parameters func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + store := ctx.KVStore(k.storeKey) k.setPool(ctx, data.Pool) k.setNewParams(ctx, data.Params) for _, validator := range data.Validators { - // Staking assumes bonded validators are already stored, need to force update - validator.PoolShares.Status = sdk.Unbonded - k.setValidator(ctx, validator) - k.setValidatorByPubKeyIndex(ctx, validator) k.updateValidator(ctx, validator) + k.setValidatorByPubKeyIndex(ctx, validator) + // manually set validator to bonded if necessary + if validator.Status() == sdk.Bonded { + store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) + } } for _, bond := range data.Bonds { k.setDelegation(ctx, bond) } - store := ctx.KVStore(k.storeKey) k.updateBondedValidatorsFull(ctx, store) } From 81e4a9797bccd4b7b036e9159d16c04919be0ec0 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 30 May 2018 18:28:02 -0700 Subject: [PATCH 55/57] genesis validator index setting --- x/stake/genesis.go | 8 ++++++-- x/stake/keeper.go | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index cd1c0f9959..4887a6002c 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -33,9 +33,13 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { k.setPool(ctx, data.Pool) k.setNewParams(ctx, data.Params) 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) - // manually set validator to bonded if necessary + k.setValidatorByPowerIndex(ctx, validator, data.Pool) if validator.Status() == sdk.Bonded { store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index b94f392a63..9fd902053a 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -73,6 +73,11 @@ func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) 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 func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { store := ctx.KVStore(k.storeKey) From f32093e9e3f4abf454ce896d2692de05b5f752cd Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 05:43:54 +0200 Subject: [PATCH 56/57] Use evidence type constants from Tendermint --- Gopkg.lock | 10 +++++----- Gopkg.toml | 2 +- x/slashing/tick.go | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 5e76a49cfd..e4ae15af4c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "bc0944904505aab55e089371a892be2f87883161" + revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" [[projects]] branch = "master" @@ -267,7 +267,7 @@ "server", "types" ] - revision = "c67bb414c7ee617f18d81a50f8a837318bb6d7dc" + revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" [[projects]] branch = "master" @@ -347,7 +347,7 @@ "types/priv_validator", "version" ] - revision = "3bf9a7dc503b8fd39bda3d64f2d039253afa6f8c" + revision = "9cc39a21ddbe8efd005aa5a001f54055a0d14fca" [[projects]] name = "github.com/tendermint/tmlibs" @@ -393,7 +393,7 @@ "internal/timeseries", "trace" ] - revision = "dfa909b99c79129e1100513e5cd36307665e5723" + revision = "1e491301e022f8f977054da4c2d852decd59571f" [[projects]] branch = "master" @@ -460,6 +460,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "8f9c58893f04daef93d91512e658560cf076a766fc100c8bc4f718d7abf1ea9c" + inputs-digest = "da12d07a753d17b8d9b834bb6b4b61617978c3c515fb38aa5dad10a66a8e78e8" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 843b3c4618..cb8fa2559c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -54,7 +54,7 @@ [[override]] name = "github.com/tendermint/abci" - revision = "c67bb414c7ee617f18d81a50f8a837318bb6d7dc" + revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" [[constraint]] name = "github.com/tendermint/go-crypto" diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 14db48b379..402c8997b8 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -7,6 +7,7 @@ import ( 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 { @@ -20,8 +21,8 @@ func NewBeginBlocker(sk Keeper) sdk.BeginBlocker { for _, evidence := range req.ByzantineValidators { var pk crypto.PubKey sk.cdc.MustUnmarshalBinary(evidence.PubKey, &pk) - switch evidence.Type { - case abci.EvidenceType_DOUBLE_SIGN: + 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))) From d82683ace581453594a058e244c1025fe01ef830 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 31 May 2018 19:54:36 +0200 Subject: [PATCH 57/57] Pin to upstream versions --- Gopkg.lock | 7 ++++--- Gopkg.toml | 18 ++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index e4ae15af4c..9c8b9f0ca3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -268,6 +268,7 @@ "types" ] revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" + version = "v0.11.0-rc0" [[projects]] branch = "master" @@ -307,7 +308,6 @@ version = "v0.8.0-rc0" [[projects]] - branch = "cwgoes/update-abci" name = "github.com/tendermint/tendermint" packages = [ "blockchain", @@ -347,7 +347,8 @@ "types/priv_validator", "version" ] - revision = "9cc39a21ddbe8efd005aa5a001f54055a0d14fca" + revision = "73de99ecab464208f6ea3a96525f4e4b78425e61" + version = "v0.20.0-rc0" [[projects]] name = "github.com/tendermint/tmlibs" @@ -460,6 +461,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "da12d07a753d17b8d9b834bb6b4b61617978c3c515fb38aa5dad10a66a8e78e8" + inputs-digest = "a6a5d886519fa9ca97a23715faa852ee14ecb5337e03641d19ea3d3d1c392fee" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index cb8fa2559c..deabcd6452 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -52,9 +52,9 @@ name = "github.com/stretchr/testify" version = "~1.2.1" -[[override]] +[[constraint]] name = "github.com/tendermint/abci" - revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9" + version = "0.11.0-rc0" [[constraint]] name = "github.com/tendermint/go-crypto" @@ -68,25 +68,23 @@ name = "github.com/tendermint/iavl" version = "0.8.0-rc0" -[[override]] +[[constraint]] name = "github.com/tendermint/tendermint" - branch = "cwgoes/update-abci" + version = "0.20.0-rc0" [[constraint]] name = "github.com/tendermint/tmlibs" 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 ... [[override]] name = "google.golang.org/genproto" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" - [[constraint]] - name = "github.com/cosmos/bech32cosmos" - branch = "master" - - [prune] go-tests = true unused-packages = true -