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()