diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 88d82650a..935a0ab46 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -33,6 +33,42 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinKeeper, codespace return keeper } +// InitGenesis - store genesis parameters +func (k Keeper) InitGenesis(ctx sdk.Context, data json.RawMessage) error { + var state GenesisState + if err := json.Unmarshal(data, &state); err != nil { + return err + } + k.setPool(ctx, state.Pool) + k.setParams(ctx, state.Params) + return nil +} + +// get the current counter +func (k Keeper) getCounter(ctx sdk.Context) int64 { + store := ctx.KVStore(k.storeKey) + b := store.Get(CounterKey) + if b == nil { + return 0 + } + var counter int64 + err := k.cdc.UnmarshalBinary(b, &counter) + if err != nil { + panic(err) + } + return counter +} + +// set the current counter +func (k Keeper) setCounter(ctx sdk.Context, counter int64) { + store := ctx.KVStore(k.storeKey) + bz, err := k.cdc.MarshalBinary(counter) + if err != nil { + panic(err) + } + store.Set(CounterKey, bz) +} + //_________________________________________________________________________ // get a single candidate @@ -80,6 +116,12 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { // retreive the old candidate record oldCandidate, oldFound := k.GetCandidate(ctx, address) + // if found, copy the old block height and counter + if oldFound { + candidate.ValidatorHeight = oldCandidate.ValidatorHeight + candidate.ValidatorCounter = oldCandidate.ValidatorCounter + } + // marshal the candidate record and add to the state bz, err := k.cdc.MarshalBinary(candidate) if err != nil { @@ -92,19 +134,25 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { return } + updateHeight := false + // update the list ordered by voting power if oldFound { - if k.isNewValidator(ctx, store, candidate.Address) { - // already in the validator set - retain the old validator height - candidate.ValidatorHeight = oldCandidate.ValidatorHeight - } else { - // wasn't in the validator set, update the validator block height - candidate.ValidatorHeight = ctx.BlockHeight() + if !k.isNewValidator(ctx, store, candidate.Address) { + updateHeight = true } - store.Delete(GetValidatorKey(address, oldCandidate.Assets, oldCandidate.ValidatorHeight, k.cdc)) + // else already in the validator set - retain the old validator height and counter + store.Delete(GetValidatorKey(address, oldCandidate.Assets, oldCandidate.ValidatorHeight, oldCandidate.ValidatorCounter, k.cdc)) } else { - // wasn't a candidate, update the validator block height + updateHeight = true + } + + if updateHeight { + // wasn't a candidate or wasn't in the validator set, update the validator block height and counter candidate.ValidatorHeight = ctx.BlockHeight() + counter := k.getCounter(ctx) + candidate.ValidatorCounter = counter + k.setCounter(ctx, counter+1) } // update the candidate record @@ -121,7 +169,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { panic(err) } - store.Set(GetValidatorKey(address, validator.Power, validator.Height, k.cdc), bz) + store.Set(GetValidatorKey(address, validator.Power, validator.Height, validator.Counter, k.cdc), bz) // add to the validators to update list if is already a validator // or is a new validator @@ -156,7 +204,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { // delete the old candidate record store := ctx.KVStore(k.storeKey) store.Delete(GetCandidateKey(address)) - store.Delete(GetValidatorKey(address, candidate.Assets, candidate.ValidatorHeight, k.cdc)) + store.Delete(GetValidatorKey(address, candidate.Assets, candidate.ValidatorHeight, candidate.ValidatorCounter, k.cdc)) // delete from recent and power weighted validator groups if the validator // exists and add validator with zero power to the validator updates diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index e9db23c2c..c34d40480 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -22,6 +22,8 @@ var ( ToKickOutValidatorsKey = []byte{0x06} // prefix for each key to the last updated validator group DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + + CounterKey = []byte{0x08} // key for block-local tx index ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -32,11 +34,13 @@ func GetCandidateKey(addr sdk.Address) []byte { } // get the key for the validator used in the power-store -func GetValidatorKey(addr sdk.Address, power sdk.Rat, height int64, cdc *wire.Codec) []byte { +func GetValidatorKey(addr sdk.Address, power sdk.Rat, height int64, counter int64, cdc *wire.Codec) []byte { powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) heightBytes := make([]byte, binary.MaxVarintLen64) binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) - return append(ValidatorsKey, append(powerBytes, append(heightBytes, addr.Bytes()...)...)...) + counterBytes := make([]byte, binary.MaxVarintLen64) + binary.BigEndian.PutUint64(counterBytes, ^uint64(counter)) // invert counter (first txns have priority) + return append(ValidatorsKey, append(powerBytes, append(heightBytes, append(counterBytes, addr.Bytes()...)...)...)...) } // get the key for the accumulated update validators diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index a137047e5..442bc2c5e 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -251,8 +251,8 @@ func TestGetValidators(t *testing.T) { require.Equal(t, len(validators), n) assert.Equal(t, sdk.NewRat(200), validators[0].Power, "%v", validators) assert.Equal(t, sdk.NewRat(200), validators[1].Power, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) assert.Equal(t, int64(0), validators[0].Height, "%v", validators) assert.Equal(t, int64(0), validators[1].Height, "%v", validators) @@ -261,8 +261,8 @@ func TestGetValidators(t *testing.T) { keeper.setCandidate(ctx, candidates[4]) validators = keeper.GetValidators(ctx) require.Equal(t, len(validators), n) - assert.Equal(t, candidates[4].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) // change in voting power of both candidates, both still in v-set, no age change candidates[3].Assets = sdk.NewRat(300) @@ -274,8 +274,8 @@ func TestGetValidators(t *testing.T) { keeper.setCandidate(ctx, candidates[4]) validators = keeper.GetValidators(ctx) require.Equal(t, len(validators), n, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) // now 2 max validators params := keeper.GetParams(ctx) @@ -286,8 +286,8 @@ func TestGetValidators(t *testing.T) { validators = keeper.GetValidators(ctx) require.Equal(t, uint16(len(validators)), params.MaxValidators) require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - // candidate 4 was set before candidate 3 - require.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) + // candidate 3 was set before candidate 4 + require.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) // simulate scenario from https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-380757108 ctx = ctx.WithBlockHeight(40) @@ -309,23 +309,25 @@ func TestGetValidators(t *testing.T) { require.Equal(t, exists, true) require.Equal(t, candidate.ValidatorHeight, int64(40)) - // first transaction wins + // first transaction wins, https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-381250392 + candidates[0].Assets = sdk.NewRat(2000) + keeper.setCandidate(ctx, candidates[0]) candidates[1].Assets = sdk.NewRat(1000) candidates[2].Assets = sdk.NewRat(1000) keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2]) validators = keeper.GetValidators(ctx) require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[1].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[2].Address, validators[1].Address, "%v", validators) + require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) + require.Equal(t, candidates[1].Address, validators[1].Address, "%v", validators) candidates[1].Assets = sdk.NewRat(1100) candidates[2].Assets = sdk.NewRat(1100) keeper.setCandidate(ctx, candidates[2]) keeper.setCandidate(ctx, candidates[1]) validators = keeper.GetValidators(ctx) require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[2].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[1].Address, validators[1].Address, "%v", validators) + require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) + require.Equal(t, candidates[2].Address, validators[1].Address, "%v", validators) // reset assets / heights params.MaxValidators = 100 @@ -627,7 +629,9 @@ func TestIsRecentValidator(t *testing.T) { validators = keeper.GetValidators(ctx) require.Equal(t, 2, len(validators)) assert.Equal(t, candidatesIn[0].validator(), validators[0]) - assert.Equal(t, candidatesIn[1].validator(), validators[1]) + c1ValWithCounter := candidatesIn[1].validator() + c1ValWithCounter.Counter = int64(1) + assert.Equal(t, c1ValWithCounter, validators[1]) // test a basic retrieve of something that should be a recent validator assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address)) diff --git a/x/stake/tick.go b/x/stake/tick.go index 129be58f0..0b3dd1c83 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -26,6 +26,9 @@ func (k Keeper) Tick(ctx sdk.Context) (change []abci.Validator) { // save the params k.setPool(ctx, p) + // reset the counter + k.setCounter(ctx, 0) + change = k.getAccUpdateValidators(ctx) return diff --git a/x/stake/types.go b/x/stake/types.go index 052a50fa2..64f1e3dc2 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -59,13 +59,14 @@ const ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Assets sdk.Rat `json:"assets"` // total shares of a global hold pools - Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators - Description Description `json:"description"` // Description terms for the candidate - ValidatorHeight int64 `json:"validator_height"` // Earliest height at current voting power + Status CandidateStatus `json:"status"` // Bonded status + Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Assets sdk.Rat `json:"assets"` // total shares of a global hold pools + Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators + Description Description `json:"description"` // Description terms for the candidate + ValidatorHeight int64 `json:"validator_height"` // Earliest height at current voting power + ValidatorCounter int64 `json:"validator_counter"` // Block-local tx index of validator change } // Candidates - list of Candidates @@ -74,13 +75,14 @@ type Candidates []Candidate // NewCandidate - initialize a new candidate func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) Candidate { return Candidate{ - Status: Unbonded, - Address: address, - PubKey: pubKey, - Assets: sdk.ZeroRat, - Liabilities: sdk.ZeroRat, - Description: description, - ValidatorHeight: int64(0), + Status: Unbonded, + Address: address, + PubKey: pubKey, + Assets: sdk.ZeroRat, + Liabilities: sdk.ZeroRat, + Description: description, + ValidatorHeight: int64(0), + ValidatorCounter: int64(0), } } @@ -117,6 +119,7 @@ func (c Candidate) validator() Validator { PubKey: c.PubKey, Power: c.Assets, Height: c.ValidatorHeight, + Counter: c.ValidatorCounter, } } @@ -130,7 +133,8 @@ type Validator struct { Address sdk.Address `json:"address"` PubKey crypto.PubKey `json:"pub_key"` Power sdk.Rat `json:"voting_power"` - Height int64 `json:"height"` // Earliest height at current voting power + Height int64 `json:"height"` // Earliest height at current voting power + Counter int64 `json:"counter"` // Block-local tx index for resolving equal voting power & height } // abci validator from stake validator type