From a1c7722a8070da61dc40e4a23188a52234dd790a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 31 Jan 2018 21:56:46 -0500 Subject: [PATCH] Integrate in types and rational staking --- glide.yaml | 55 ++++++++++ x/stake/store.go | 221 ++++++++++++++++++++++------------------- x/stake/store_test.go | 217 ++++++++++++++++++++++++++++++++++++---- x/stake/test_common.go | 31 ++++-- x/stake/types.go | 215 +++++++++++++++++++++++++++++++-------- x/stake/types_test.go | 3 + 6 files changed, 568 insertions(+), 174 deletions(-) create mode 100644 glide.yaml create mode 100644 x/stake/types_test.go diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 000000000..34cdb13d8 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,55 @@ +package: github.com/cosmos/cosmos-sdk +import: +- package: github.com/golang/protobuf + version: ^1.0.0 + subpackages: + - proto +- package: github.com/bgentry/speakeasy + version: ^0.1.0 +- package: github.com/mattn/go-isatty + version: ~0.0.3 +- package: github.com/pkg/errors + version: ^0.8.0 +- package: github.com/rigelrozanski/common +- package: github.com/tendermint/abci + version: develop + subpackages: + - server + - types +- package: github.com/tendermint/go-crypto + version: develop +- package: github.com/tendermint/go-wire + version: develop +- package: github.com/tendermint/iavl + version: develop +- package: github.com/tendermint/tmlibs + version: develop + subpackages: + - common + - db + - log + - logger + - merkle +- package: github.com/tendermint/tendermint + version: breaking/wire-sdk2 + subpackages: + - cmd/tendermint/commands + - config + - lite + - rpc/client + - types +- package: golang.org/x/crypto + subpackages: + - ripemd160 +- package: github.com/spf13/pflag + version: v1.0.0 +- package: github.com/spf13/cobra + version: v0.0.1 +- package: github.com/spf13/viper + version: ^1.0.0 +testImport: +- package: github.com/stretchr/testify + version: ^1.2.1 + subpackages: + - assert + - require diff --git a/x/stake/store.go b/x/stake/store.go index ec1bb8a20..e738b220c 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -1,39 +1,54 @@ package stake import ( - "encoding/binary" - crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/types" ) // nolint var ( + + // internal wire codec + cdc *wire.Codec + // Keys for store prefixes CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys ParamKey = []byte{0x02} // key for global parameters relating to staking GlobalStateKey = []byte{0x03} // key for global parameters relating to staking // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x06} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond ) +func init() { + cdc = wire.NewCodec() + cdc.RegisterInterface((*rational.Rational)(nil), nil) + cdc.RegisterConcrete(rational.Rat{}, "rat", nil) +} + // GetCandidateKey - get the key for the candidate with pubKey func GetCandidateKey(pubKey crypto.PubKey) []byte { return append(CandidateKeyPrefix, pubKey.Bytes()...) } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(pubKey crypto.PubKey, power int64) []byte { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, uint64(power)) +func GetValidatorKey(pubKey crypto.PubKey, power rational.Rational) []byte { + b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store } +// GetValidatorUpdatesKey - get the key for the validator used in the power-store +func GetValidatorUpdatesKey(pubKey crypto.PubKey) []byte { + return append(ValidatorUpdatesKeyPrefix, pubKey.Bytes()...) // TODO does this need prefix if its in its own store +} + // GetDelegatorBondKey - get the key for delegator bond with candidate func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte { return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) @@ -41,7 +56,7 @@ func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []by // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { - res, err := cdc.MarshalBinary(&delegator) + res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) } @@ -50,7 +65,7 @@ func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { // GetDelegatorBondsKey - get the key for list of all the delegator's bonds func GetDelegatorBondsKey(delegator crypto.Address) []byte { - res, err := cdc.MarshalBinary(&delegator) + res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) } @@ -60,15 +75,12 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { //--------------------------------------------------------------------- func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { - //if pubKey.Empty() { - //return nil - //} b := store.Get(GetCandidateKey(pubKey)) if b == nil { return nil } candidate := new(Candidate) - err := cdc.UnmarshalBinary(b, candidate) + err := cdc.UnmarshalJSON(b, candidate) if err != nil { panic(err) // This error should never occur big problem if does } @@ -77,11 +89,12 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { func saveCandidate(store types.KVStore, candidate *Candidate) { - removeValidatorFromKey(store, candidate.PubKey) + // XXX should only remove validator if we know candidate is a validator + removeValidator(store, candidate.PubKey) validator := &Validator{candidate.PubKey, candidate.VotingPower} - saveValidator(store, validator) + updateValidator(store, validator) - b, err := cdc.MarshalBinary(*candidate) + b, err := cdc.MarshalJSON(*candidate) if err != nil { panic(err) } @@ -89,54 +102,67 @@ func saveCandidate(store types.KVStore, candidate *Candidate) { } func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { - removeValidatorFromKey(store, pubKey) + + // XXX should only remove validator if we know candidate is a validator + removeValidator(store, pubKey) store.Delete(GetCandidateKey(pubKey)) } //--------------------------------------------------------------------- -func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) *Validator { - b := store.Get(GetValidatorKey(pubKey, votingPower)) - if b == nil { - return nil - } - validator := new(Validator) - err := cdc.UnmarshalBinary(b, validator) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return validator -} +//func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower rational.Rational) *Validator { +//b := store.Get(GetValidatorKey(pubKey, votingPower)) +//if b == nil { +//return nil +//} +//validator := new(Validator) +//err := cdc.UnmarshalJSON(b, validator) +//if err != nil { +//panic(err) // This error should never occur big problem if does +//} +//return validator +//} -func saveValidator(store types.KVStore, validator *Validator) { - b, err := cdc.MarshalBinary(*validator) +// updateValidator - update a validator and create accumulate any changes +// in the changed validator substore +func updateValidator(store types.KVStore, validator *Validator) { + + b, err := cdc.MarshalJSON(*validator) if err != nil { panic(err) } + + // add to the validators to update list if necessary + store.Set(GetValidatorUpdatesKey(validator.PubKey), b) + + // update the list ordered by voting power store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) } -func removeValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) { - store.Delete(GetValidatorKey(pubKey, votingPower)) -} +func removeValidator(store types.KVStore, pubKey crypto.PubKey) { -func removeValidatorFromKey(store types.KVStore, pubKey crypto.PubKey) { - // remove validator if already there, then add new validator + //add validator with zero power to the validator updates + b, err := cdc.MarshalJSON(Validator{pubKey, rational.Zero}) + if err != nil { + panic(err) + } + store.Set(GetValidatorUpdatesKey(pubKey), b) + + // now actually delete from the validator set candidate := loadCandidate(store, pubKey) if candidate != nil { - removeValidator(store, pubKey, candidate.VotingPower) + store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) } } -// Validators - get the most recent updated validator set from the -// Candidates. These bonds are already sorted by VotingPower from -// the UpdateVotingPower function which is the only function which -// is to modify the VotingPower -func getValidators(store types.KVStore, maxVal int) Validators { +// get the most recent updated validator set from the Candidates. These bonds +// are already sorted by VotingPower from the UpdateVotingPower function which +// is the only function which is to modify the VotingPower +func getValidators(store types.KVStore, maxVal int) (validators []Validator) { iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest - validators := make(Validators, maxVal) + validators = make([]Validator, maxVal) for i := 0; ; i++ { if !iterator.Valid() || i > maxVal { iterator.Close() @@ -144,7 +170,7 @@ func getValidators(store types.KVStore, maxVal int) Validators { } valBytes := iterator.Value() var val Validator - err := cdc.UnmarshalBinary(valBytes, &val) + err := cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -152,7 +178,37 @@ func getValidators(store types.KVStore, maxVal int) Validators { iterator.Next() } - return validators + return +} + +//--------------------------------------------------------------------- + +// get the most updated validators +func getValidatorUpdates(store types.KVStore) (updates []Validator) { + + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest + + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val Validator + err := cdc.UnmarshalJSON(valBytes, &val) + if err != nil { + panic(err) + } + updates = append(updates, val) + } + + iterator.Close() + return +} + +// remove all validator update entries +func clearValidatorUpdates(store types.KVStore, maxVal int) { + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + } + iterator.Close() } //--------------------------------------------------------------------- @@ -160,26 +216,20 @@ func getValidators(store types.KVStore, maxVal int) Validators { // loadCandidates - get the active list of all candidates TODO replace with multistore func loadCandidates(store types.KVStore) (candidates Candidates) { - //iterator := store.Iterator(subspace(CandidateKeyPrefix)) //smallest to largest - //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) //smallest to largest - iterator := store.Iterator([]byte{}, []byte(nil)) //smallest to largest + iterator := store.Iterator(subspace(CandidateKeyPrefix)) + //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) + //iterator := store.Iterator([]byte{}, []byte(nil)) - for i := 0; ; i++ { - if !iterator.Valid() { - //panic(fmt.Sprintf("debug i: %v\n", i)) - iterator.Close() - break - } + for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() var candidate Candidate - err := cdc.UnmarshalBinary(candidateBytes, &candidate) + err := cdc.UnmarshalJSON(candidateBytes, &candidate) if err != nil { panic(err) } - candidates[i] = &candidate - iterator.Next() + candidates = append(candidates, &candidate) } - + iterator.Close() return candidates } @@ -194,7 +244,7 @@ func loadDelegatorCandidates(store types.KVStore, return nil } - err := cdc.UnmarshalBinary(candidateBytes, &candidates) + err := cdc.UnmarshalJSON(candidateBytes, &candidates) if err != nil { panic(err) } @@ -212,7 +262,7 @@ func loadDelegatorBond(store types.KVStore, } bond := new(DelegatorBond) - err := cdc.UnmarshalBinary(delegatorBytes, bond) + err := cdc.UnmarshalJSON(delegatorBytes, bond) if err != nil { panic(err) } @@ -225,7 +275,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele if loadDelegatorBond(store, delegator, bond.PubKey) == nil { pks := loadDelegatorCandidates(store, delegator) pks = append(pks, (*bond).PubKey) - b, err := cdc.MarshalBinary(pks) + b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } @@ -233,7 +283,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele } // now actually save the bond - b, err := cdc.MarshalBinary(*bond) + b, err := cdc.MarshalJSON(*bond) if err != nil { panic(err) } @@ -250,7 +300,7 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat pks = append(pks[:i], pks[i+1:]...) } } - b, err := cdc.MarshalBinary(pks) + b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } @@ -261,39 +311,6 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat //updateDelegatorBonds(store, delegator) } -//func updateDelegatorBonds(store types.KVStore, -//delegator crypto.Address) { - -//var bonds []*DelegatorBond - -//prefix := GetDelegatorBondKeyPrefix(delegator) -//l := len(prefix) -//delegatorsBytes := store.List(prefix, -//append(prefix[:l-1], (prefix[l-1]+1)), loadParams(store).MaxVals) - -//for _, delegatorBytesModel := range delegatorsBytes { -//delegatorBytes := delegatorBytesModel.Value -//if delegatorBytes == nil { -//continue -//} - -//bond := new(DelegatorBond) -//err := wire.UnmarshalBinary(delegatorBytes, bond) -//if err != nil { -//panic(err) -//} -//bonds = append(bonds, bond) -//} - -//if len(bonds) == 0 { -//store.Remove(GetDelegatorBondsKey(delegator)) -//return -//} - -//b := wire.MarshalBinary(bonds) -//store.Set(GetDelegatorBondsKey(delegator), b) -//} - //_______________________________________________________________________ // load/save the global staking params @@ -303,7 +320,7 @@ func loadParams(store types.KVStore) (params Params) { return defaultParams() } - err := cdc.UnmarshalBinary(b, ¶ms) + err := cdc.UnmarshalJSON(b, ¶ms) if err != nil { panic(err) // This error should never occur big problem if does } @@ -311,7 +328,7 @@ func loadParams(store types.KVStore) (params Params) { return } func saveParams(store types.KVStore, params Params) { - b, err := cdc.MarshalBinary(params) + b, err := cdc.MarshalJSON(params) if err != nil { panic(err) } @@ -327,14 +344,14 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { return initialGlobalState() } gs = new(GlobalState) - err := cdc.UnmarshalBinary(b, gs) + err := cdc.UnmarshalJSON(b, gs) if err != nil { panic(err) // This error should never occur big problem if does } return } func saveGlobalState(store types.KVStore, gs *GlobalState) { - b, err := cdc.MarshalBinary(*gs) + b, err := cdc.MarshalJSON(*gs) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index 155450102..b366015ec 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -4,25 +4,201 @@ import ( "bytes" "testing" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/rational" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" ) -func initTestStore(t *testing.T) sdk.KVStore { - // Capabilities key to access the main KVStore. - db, err := dbm.NewGoLevelDB("stake", "data") - require.Nil(t, err) - stakeStoreKey := sdk.NewKVStoreKey("stake") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms.GetKVStore(stakeStoreKey) -} +// XXX XXX XXX +// XXX revive these tests but for the store update proceedure +// XXX XXX XXX + +//func TestUpdateVotingPower(t *testing.T) { +//assert := assert.New(t) +//store := initTestStore(t) +//params := loadParams(store) +//gs := loadGlobalState(store) + +//N := 5 +//actors := newAddrs(N) +//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + +//// test a basic change in voting power +//candidates[0].Assets = rational.New(500) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + +//// test a swap in voting power +//candidates[1].Assets = rational.New(600) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) +//assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) + +//// test the max validators term +//params.MaxVals = 4 +//saveParams(store, params) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) +//} + +//func TestValidatorsChanged(t *testing.T) { +//require := require.New(t) + +//v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator() +//v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator() +//v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator() +//v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator() +//v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator() + +//// test from nothing to something +//vs1 := []Validator{} +//vs2 := []Validator{v1, v2} +//changed := vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[0], changed[0]) +//testChange(t, vs2[1], changed[1]) + +//// test from something to nothing +//vs1 = []Validator{v1, v2} +//vs2 = []Validator{} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testRemove(t, vs1[0], changed[0]) +//testRemove(t, vs1[1], changed[1]) + +//// test identical +//vs1 = []Validator{v1, v2, v4} +//vs2 = []Validator{v1, v2, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Zero(len(changed)) + +//// test single value change +//vs2[2].VotingPower = rational.One +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[2], changed[0]) + +//// test multiple value change +//vs2[0].VotingPower = rational.New(11) +//vs2[2].VotingPower = rational.New(5) +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[0], changed[0]) +//testChange(t, vs2[2], changed[1]) + +//// test validator added at the beginning +//vs1 = []Validator{v2, v4} +//vs2 = []Validator{v2, v4, v1} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[0], changed[0]) + +//// test validator added in the middle +//vs1 = []Validator{v1, v2, v4} +//vs2 = []Validator{v3, v1, v4, v2} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[2], changed[0]) + +//// test validator added at the end +//vs2 = []Validator{v1, v2, v4, v5} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[3], changed[0]) + +//// test multiple validators added +//vs2 = []Validator{v1, v2, v3, v4, v5} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[2], changed[0]) +//testChange(t, vs2[4], changed[1]) + +//// test validator removed at the beginning +//vs2 = []Validator{v2, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[0], changed[0]) + +//// test validator removed in the middle +//vs2 = []Validator{v1, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[1], changed[0]) + +//// test validator removed at the end +//vs2 = []Validator{v1, v2} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[2], changed[0]) + +//// test multiple validators removed +//vs2 = []Validator{v1} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testRemove(t, vs1[1], changed[0]) +//testRemove(t, vs1[2], changed[1]) + +//// test many types of changes +//vs2 = []Validator{v1, v3, v4, v5} +//vs2[2].VotingPower = rational.New(11) +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 +//testRemove(t, vs1[1], changed[0]) +//testChange(t, vs2[1], changed[1]) +//testChange(t, vs2[2], changed[2]) +//testChange(t, vs2[3], changed[3]) + +//} + +//func TestUpdateValidatorSet(t *testing.T) { +//assert, require := assert.New(t), require.New(t) +//store := initTestStore(t) +//params := loadParams(store) +//gs := loadGlobalState(store) + +//N := 5 +//actors := newAddrs(N) +//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) +//for _, c := range candidates { +//saveCandidate(store, c) +//} + +//// they should all already be validators +//change, err := UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 + +//// test the max value and test again +//params.MaxVals = 4 +//saveParams(store, params) +//change, err = UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(1, len(change), "%v", change) +//testRemove(t, candidates[4].validator(), change[0]) +//candidates = loadCandidates(store) +//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) + +//// mess with the power's of the candidates and test +//candidates[0].Assets = rational.New(10) +//candidates[1].Assets = rational.New(600) +//candidates[2].Assets = rational.New(1000) +//candidates[3].Assets = rational.One +//candidates[4].Assets = rational.New(10) +//for _, c := range candidates { +//saveCandidate(store, c) +//} +//change, err = UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed +//candidates = loadCandidates(store) +//testChange(t, candidates[0].validator(), change[0]) +//testChange(t, candidates[1].validator(), change[1]) +//testChange(t, candidates[2].validator(), change[2]) +//testRemove(t, candidates[3].validator(), change[3]) +//testChange(t, candidates[4].validator(), change[4]) +//} func TestState(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -45,9 +221,9 @@ func TestState(t *testing.T) { candidate := &Candidate{ Owner: validator, PubKey: pk, - Assets: 9, //rational.New(9), - Liabilities: 9, // rational.New(9), - VotingPower: 0, //rational.Zero, + Assets: rational.New(9), + Liabilities: rational.New(9), + VotingPower: rational.Zero, } candidatesEqual := func(c1, c2 *Candidate) bool { @@ -72,12 +248,11 @@ func TestState(t *testing.T) { assert.True(candidatesEqual(candidate, resCand)) // modify a records, save, and retrieve - candidate.Liabilities = 99 //rational.New(99) + candidate.Liabilities = rational.New(99) saveCandidate(store, candidate) resCand = loadCandidate(store, pk) assert.True(candidatesEqual(candidate, resCand)) - store.Write() // also test that the pubkey has been added to pubkey list resPks = loadCandidates(store) require.Equal(1, len(resPks)) @@ -88,7 +263,7 @@ func TestState(t *testing.T) { bond := &DelegatorBond{ PubKey: pk, - Shares: 9, // rational.New(9), + Shares: rational.New(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -106,7 +281,7 @@ func TestState(t *testing.T) { assert.True(bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = 99 //rational.New(99) + bond.Shares = rational.New(99) saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) assert.True(bondsEqual(bond, resBond)) @@ -133,7 +308,7 @@ func TestGetValidators(t *testing.T) { store := initTestStore(t) N := 5 addrs := newAddrs(N) - candidatesFromActors(store, addrs, []int{400, 200, 0, 0, 0}) + candidatesFromActors(store, addrs, []int64{400, 200, 0, 0, 0}) validators := getValidators(store, 5) require.Equal(2, len(validators)) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index c7e1be336..cf59ca48a 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -3,9 +3,15 @@ package stake import ( "encoding/hex" "fmt" + "testing" + + "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/rational" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,6 +21,17 @@ func subspace(prefix []byte) (start, end []byte) { return } +func initTestStore(t *testing.T) sdk.KVStore { + // Capabilities key to access the main KVStore. + db, err := dbm.NewGoLevelDB("stake", "data") + require.Nil(t, err) + stakeStoreKey := sdk.NewKVStoreKey("stake") + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + return ms.GetKVStore(stakeStoreKey) +} + func newAddrs(n int) (addrs []crypto.Address) { for i := 0; i < n; i++ { addrs = append(addrs, []byte(fmt.Sprintf("addr%d", i))) @@ -49,15 +66,15 @@ var pks = []crypto.PubKey{ // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey // instead this is just being set the address here for testing purposes -func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int) { +func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int64) { for i := 0; i < len(addrs); i++ { c := &Candidate{ Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: int64(amts[i]), //rational.New(amts[i]), - Liabilities: int64(amts[i]), //rational.New(amts[i]), - VotingPower: int64(amts[i]), //rational.New(amts[i]), + Assets: rational.New(amts[i]), + Liabilities: rational.New(amts[i]), + VotingPower: rational.New(amts[i]), } saveCandidate(store, c) } @@ -69,9 +86,9 @@ func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: 0, //rational.Zero, - Liabilities: 0, //rational.Zero, - VotingPower: 0, //rational.Zero, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, } candidates = append(candidates, c) } diff --git a/x/stake/types.go b/x/stake/types.go index 4ecd907e3..f615f188c 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -1,21 +1,20 @@ package stake import ( + abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" ) -var cdc = wire.NewCodec() - -//nolint +// Params defines the high level settings for staking type Params struct { HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - InflationRateChange int64 `json:"inflation_rate_change"` // XXX maximum annual change in inflation rate - InflationMax int64 `json:"inflation_max"` // XXX maximum inflation rate - InflationMin int64 `json:"inflation_min"` // XXX minimum inflation rate - GoalBonded int64 `json:"goal_bonded"` // XXX Goal of percent bonded atoms + InflationRateChange rational.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax rational.Rational `json:"inflation_max"` // maximum inflation rate + InflationMin rational.Rational `json:"inflation_min"` // minimum inflation rate + GoalBonded rational.Rational `json:"goal_bonded"` // Goal of percent bonded atoms MaxVals uint16 `json:"max_vals"` // maximum number of validators AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination @@ -31,10 +30,10 @@ func defaultParams() Params { return Params{ HoldBonded: []byte("77777777777777777777777777777777"), HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: 13, //rational.New(13, 100), - InflationMax: 20, //rational.New(20, 100), - InflationMin: 7, //rational.New(7, 100), - GoalBonded: 67, //rational.New(67, 100), + InflationRateChange: rational.New(13, 100), + InflationMax: rational.New(20, 100), + InflationMin: rational.New(7, 100), + GoalBonded: rational.New(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -44,15 +43,17 @@ func defaultParams() Params { } } +//_________________________________________________________________________ + // GlobalState - dynamic parameters of the current state type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares int64 `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares int64 `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation int64 `json:"inflation"` // current annual inflation rate + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares rational.Rational `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares rational.Rational `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation rational.Rational `json:"inflation"` // current annual inflation rate } // XXX define globalstate interface? @@ -60,15 +61,69 @@ type GlobalState struct { func initialGlobalState() *GlobalState { return &GlobalState{ TotalSupply: 0, - BondedShares: 0, //rational.Zero, - UnbondedShares: 0, //rational.Zero, + BondedShares: rational.Zero, + UnbondedShares: rational.Zero, BondedPool: 0, UnbondedPool: 0, InflationLastTime: 0, - Inflation: 0, //rational.New(7, 100), + Inflation: rational.New(7, 100), } } +// get the bond ratio of the global state +func (gs *GlobalState) bondedRatio() rational.Rational { + if gs.TotalSupply > 0 { + return rational.New(gs.BondedPool, gs.TotalSupply) + } + return rational.Zero +} + +// get the exchange rate of bonded token per issued share +func (gs *GlobalState) bondedShareExRate() rational.Rational { + if gs.BondedShares.IsZero() { + return rational.One + } + return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) +} + +// get the exchange rate of unbonded tokens held in candidates per issued share +func (gs *GlobalState) unbondedShareExRate() rational.Rational { + if gs.UnbondedShares.IsZero() { + return rational.One + } + return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) +} + +func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rational) { + issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.BondedPool += amount + gs.BondedShares = gs.BondedShares.Add(issuedShares) + return +} + +func (gs *GlobalState) removeSharesBonded(shares rational.Rational) (removedTokens int64) { + removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.BondedShares = gs.BondedShares.Sub(shares) + gs.BondedPool -= removedTokens + return +} + +func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rational) { + issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + gs.UnbondedPool += amount + return +} + +func (gs *GlobalState) removeSharesUnbonded(shares rational.Rational) (removedTokens int64) { + removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.UnbondedShares = gs.UnbondedShares.Sub(shares) + gs.UnbondedPool -= removedTokens + return +} + +//_______________________________________________________________________________________________________ + // CandidateStatus - status of a validator-candidate type CandidateStatus byte @@ -87,23 +142,15 @@ const ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets int64 `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities int64 `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets rational.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities rational.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate } -//nolint -type Candidates []*Candidate -type Validator struct { - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator -} -type Validators []Validator - // Description - description fields for a candidate type Description struct { Moniker string `json:"moniker"` @@ -118,15 +165,95 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri Status: Unbonded, PubKey: pubKey, Owner: owner, - Assets: 0, // rational.Zero, - Liabilities: 0, // rational.Zero, - VotingPower: 0, //rational.Zero, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, Description: description, } } -//nolint -type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares int64 `json:"shares"` +// XXX define candidate interface? + +// get the exchange rate of global pool shares over delegator shares +func (c *Candidate) delegatorShareExRate() rational.Rational { + if c.Liabilities.IsZero() { + return rational.One + } + return c.Assets.Quo(c.Liabilities) +} + +// add tokens to a candidate +func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rational) { + + exRate := c.delegatorShareExRate() + + var receivedGlobalShares rational.Rational + if c.Status == Bonded { + receivedGlobalShares = gs.addTokensBonded(amount) + } else { + receivedGlobalShares = gs.addTokensUnbonded(amount) + } + c.Assets = c.Assets.Add(receivedGlobalShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) + return +} + +// remove shares from a candidate +func (c *Candidate) removeShares(shares rational.Rational, gs *GlobalState) (removedTokens int64) { + + globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) + + if c.Status == Bonded { + removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) + } else { + removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) + } + c.Assets = c.Assets.Sub(globalPoolSharesToRemove) + + c.Liabilities = c.Liabilities.Sub(shares) + return +} + +// Validator returns a copy of the Candidate as a Validator. +// Should only be called when the Candidate qualifies as a validator. +func (c *Candidate) validator() Validator { + return Validator{ + PubKey: c.PubKey, + VotingPower: c.VotingPower, + } +} + +// Validator is one of the top Candidates +type Validator struct { + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator +} + +// ABCIValidator - Get the validator from a bond value +func (v Validator) ABCIValidator() (*abci.Validator, error) { + pkBytes, err := cdc.MarshalBinary(v.PubKey) + if err != nil { + return nil, err + } + return &abci.Validator{ + PubKey: pkBytes, + Power: v.VotingPower.Evaluate(), + }, nil +} + +//_________________________________________________________________________ + +// Candidates - list of Candidates +type Candidates []*Candidate + +//_________________________________________________________________________ + +// DelegatorBond represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type DelegatorBond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares rational.Rational `json:"shares"` } diff --git a/x/stake/types_test.go b/x/stake/types_test.go new file mode 100644 index 000000000..ec16f32d9 --- /dev/null +++ b/x/stake/types_test.go @@ -0,0 +1,3 @@ +package stake + +// XXX test global state functions, candidate exchange rate functions etc.