Stake module block-local tx index counter

This commit is contained in:
Christopher Goes 2018-04-16 13:22:05 +02:00
parent 4e72250dd1
commit 7f38f34709
No known key found for this signature in database
GPG Key ID: E828D98232D328D3
5 changed files with 104 additions and 41 deletions

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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