From fa64487e653407fab84b9b33961d063ee3b8ae47 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 9 May 2018 21:39:14 -0400 Subject: [PATCH] working fee distribution reorg --- cmd/gaia/app/app_test.go | 4 +- cmd/gaia/cmd/gaiacli/main.go | 4 +- docs/spec/staking/old/spec.md | 12 +- docs/spec/staking/old/spec2.md | 16 +- docs/spec/staking/spec-technical.md | 624 ++++++++++++++++++ types/stake.go | 57 ++ types/validator_set.go | 43 -- .../movement.go} | 3 + x/fee_distribution/types.go | 113 ++++ x/stake/client/cli/query.go | 6 +- x/stake/client/rest/query.go | 4 +- x/stake/handler.go | 14 +- x/stake/handler_test.go | 8 +- x/stake/keeper.go | 117 ++-- x/stake/keeper_keys.go | 34 +- x/stake/keeper_test.go | 58 +- x/stake/types.go | 193 ++---- x/stake/view_slash_keeper.go | 12 +- x/stake/view_slash_keeper_test.go | 42 +- 19 files changed, 1017 insertions(+), 347 deletions(-) create mode 100644 docs/spec/staking/spec-technical.md create mode 100644 types/stake.go delete mode 100644 types/validator_set.go rename x/{stake/fee_distribution.go => fee_distribution/movement.go} (96%) create mode 100644 x/fee_distribution/types.go diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 694843461c..73c7022d19 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -431,7 +431,7 @@ func TestStakeMsgs(t *testing.T) { ctxDeliver = gapp.BaseApp.NewContext(false, abci.Header{}) res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) require.Equal(t, genCoins.Minus(sdk.Coins{bondCoin}), res2.GetCoins()) - bond, found := gapp.stakeKeeper.GetDelegatorBond(ctxDeliver, addr2, addr1) + bond, found := gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) require.True(t, found) require.Equal(t, bond.DelegatorAddr, addr2) @@ -445,7 +445,7 @@ func TestStakeMsgs(t *testing.T) { ctxDeliver = gapp.BaseApp.NewContext(false, abci.Header{}) res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) require.Equal(t, genCoins, res2.GetCoins()) - _, found = gapp.stakeKeeper.GetDelegatorBond(ctxDeliver, addr2, addr1) + _, found = gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) require.False(t, found) } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 8de2e3acc2..76314f0361 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -47,8 +47,8 @@ func main() { authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), stakecmd.GetCmdQueryCandidate("stake", cdc), stakecmd.GetCmdQueryCandidates("stake", cdc), - stakecmd.GetCmdQueryDelegatorBond("stake", cdc), - //stakecmd.GetCmdQueryDelegatorBonds("stake", cdc), + stakecmd.GetCmdQueryDelegation("stake", cdc), + stakecmd.GetCmdQueryDelegations("stake", cdc), )...) rootCmd.AddCommand( client.PostCommands( 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 3a40b9f449..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 { @@ -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/spec-technical.md b/docs/spec/staking/spec-technical.md new file mode 100644 index 0000000000..a71308a822 --- /dev/null +++ b/docs/spec/staking/spec-technical.md @@ -0,0 +1,624 @@ +# Staking Module + +## Overview + +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that +serves as a backbone of the Cosmos ecosystem. It is operated and secured by an +open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in +the process of exchanging protocol messages in the production of each block. To +avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the +validator's private key, and this is a basis for Tendermint strict +accountability that allows punishing misbehaving validators by slashing +(burning) their bonded Atoms. On the other hand, validators are rewarded for +their service of securing blockchain network by the inflationary provisions and +transactions fees. This incentives correct behavior of the validators and +provides the economic security of the network. + +The native token of the Cosmos Hub is called Atom; becoming a validator of the +Cosmos Hub requires holding Atoms. However, not all Atom holders are validators +of the Cosmos Hub. More precisely, there is a selection process that determines +the validator set as a subset of all validator candidates (Atom holders that +wants to become a validator). The other option for Atom holder is to delegate +their atoms to validators, i.e., being a delegator. A delegator is an Atom +holder that has bonded its Atoms by delegating it to a validator (or validator +candidate). By bonding Atoms to secure the network (and taking a risk of being +slashed in case of misbehaviour), a user is rewarded with inflationary +provisions and transaction fees proportional to the amount of its bonded Atoms. +The Cosmos Hub is designed to efficiently facilitate a small numbers of +validators (hundreds), and large numbers of delegators (tens of thousands). +More precisely, it is the role of the Staking module of the Cosmos Hub to +support various staking functionality including validator set selection, +delegating, bonding and withdrawing Atoms, and the distribution of inflationary +provisions and transaction fees. + +## State + +The staking module persists the following information to the store: +* `GlobalState`, describing the global pools and the inflation related fields +* validator candidates (including current validators), indexed by public key and shares in the global pool +(bonded or unbonded depending on candidate status) +* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate + public key +* the queue of unbonding delegations +* the queue of re-delegations + +### Global State + +The GlobalState data structure contains total Atom supply, amount of Atoms in +the bonded pool, sum of all shares distributed for the bonded pool, amount of +Atoms in the unbonded pool, sum of all shares distributed for the unbonded +pool, a timestamp of the last processing of inflation, the current annual +inflation rate, a timestamp for the last comission accounting reset, the global +fee pool, a pool of reserve taxes collected for the governance use and an +adjustment factor for calculating global fee accum. `Params` is global data +structure that stores system parameters and defines overall functioning of the +module. + +``` go +type GlobalState struct { + TotalSupply int64 // total supply of Atoms + BondedPool int64 // reserve of bonded tokens + BondedShares rational.Rat // sum of all shares distributed for the BondedPool + UnbondedPool int64 // reserve of unbonding tokens held with candidates + UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool + InflationLastTime int64 // timestamp of last processing of inflation + Inflation rational.Rat // current annual inflation rate + DateLastCommissionReset int64 // unix timestamp for last commission accounting reset + FeePool coin.Coins // fee pool for all the fee shares which have already been distributed + ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use + Adjustment rational.Rat // Adjustment factor for calculating global fee accum +} + +type Params struct { + HoldBonded Address // account where all bonded coins are held + HoldUnbonding Address // account where all delegated but unbonding coins are held + + InflationRateChange rational.Rational // maximum annual change in inflation rate + InflationMax rational.Rational // maximum inflation rate + InflationMin rational.Rational // minimum inflation rate + GoalBonded rational.Rational // Goal of percent bonded atoms + ReserveTax rational.Rational // Tax collected on all fees + + MaxVals uint16 // maximum number of validators + AllowedBondDenom string // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 + GasEditCandidacy int64 + GasDelegate int64 + GasRedelegate int64 + GasUnbond int64 +} +``` + +### Candidate + +The `Candidate` data structure holds the current state and some historical +actions of validators or candidate-validators. + +``` go +type Candidate struct { + Status CandidateStatus + ConsensusPubKey crypto.PubKey + GovernancePubKey crypto.PubKey + Owner crypto.Address + GlobalStakeShares rational.Rat + IssuedDelegatorShares rational.Rat + RedelegatingShares rational.Rat + VotingPower rational.Rat + Commission rational.Rat + CommissionMax rational.Rat + CommissionChangeRate rational.Rat + CommissionChangeToday rational.Rat + ProposerRewardPool coin.Coins + Adjustment rational.Rat + Description Description +} + +type Description struct { + Name string + DateBonded string + Identity string + Website string + Details string +} +``` + +Candidate parameters are described: +* Status: it can be Bonded (active validator), Unbonding (validator candidate) + or Revoked +* ConsensusPubKey: candidate public key that is used strictly for participating in + consensus +* GovernancePubKey: public key used by the validator for governance voting +* Owner: Address that is allowed to unbond coins. +* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` + otherwise +* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators + (which includes the candidate's self-bond); a delegator share represents + their stake in the Candidate's `GlobalStakeShares` +* RedelegatingShares: The portion of `IssuedDelegatorShares` which are + currently re-delegating to a new validator +* VotingPower: Proportional to the amount of bonded tokens which the validator + has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` +* Commission: The commission rate of fees charged to any delegators +* CommissionMax: The maximum commission rate this candidate can charge each + day from the date `GlobalState.DateLastCommissionReset` +* CommissionChangeRate: The maximum daily increase of the candidate commission +* CommissionChangeToday: Counter for the amount of change to commission rate + which has occurred today, reset on the first block of each day (UTC time) +* ProposerRewardPool: reward pool for extra fees collected when this candidate + is the proposer of a block +* Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` +* Description + * Name: moniker + * DateBonded: date determined which the validator was bonded + * Identity: optional field to provide a signature which verifies the + validators identity (ex. UPort or Keybase) + * Website: optional website link + * Details: optional details + +### Delegation + +Atom holders may delegate coins to candidates; under this circumstance their +funds are held in a `Delegation` data structure. It is owned by one +delegator, and is associated with the shares for one candidate. The sender of +the transaction is the owner of the bond. + +``` go +type Delegation struct { + Candidate crypto.PubKey + Shares rational.Rat + AdjustmentFeePool coin.Coins + AdjustmentRewardPool coin.Coins +} +``` + +Description: +* Candidate: the public key of the validator candidate: bonding too +* Shares: the number of delegator shares received from the validator candidate +* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` +* AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Candidate.ProposerRewardPool` + + +### QueueElem + +The Unbonding and re-delegation process is implemented using the ordered queue +data structure. All queue elements share a common structure: + +```golang +type QueueElem struct { + Candidate crypto.PubKey + InitTime int64 // when the element was added to the queue +} +``` + +The queue is ordered so the next element to unbond/re-delegate is at the head. +Every tick the head of the queue is checked and if the unbonding period has +passed since `InitTime`, the final settlement of the unbonding is started or +re-delegation is executed, and the element is popped from the queue. Each +`QueueElem` is persisted in the store until it is popped from the queue. + +### QueueElemUnbondDelegation + +QueueElemUnbondDelegation structure is used in the unbonding queue. + +```golang +type QueueElemUnbondDelegation struct { + QueueElem + Payout Address // account to pay out to + Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio +} +``` + +### QueueElemReDelegate + +QueueElemReDelegate structure is used in the re-delegation queue. + +```golang +type QueueElemReDelegate struct { + QueueElem + Payout Address // account to pay out to + Shares rational.Rat // amount of shares which are unbonding + NewCandidate crypto.PubKey // validator to bond to after unbond +} +``` + +### Transaction Overview + +Available Transactions: +* TxDeclareCandidacy +* TxEditCandidacy +* TxDelegate +* TxUnbond +* TxRedelegate +* TxLivelinessCheck +* TxProveLive + +## Transaction processing + +In this section we describe the processing of the transactions and the +corresponding updates to the global state. In the following text we will use +`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a +reference to the queue of unbond delegations, `reDelegationQueue` is the +reference for the queue of redelegations. We use `tx` to denote a +reference to a transaction that is being processed, and `sender` to denote the +address of the sender of the transaction. We use function +`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, +and `saveCandidate(store, candidate)` to save it. Similarly, we use +`loadDelegation(store, sender, PubKey)` to load a delegator bond with the +key (sender and PubKey) from the store, and +`saveDelegation(store, sender, bond)` to save it. +`removeDelegation(store, sender, bond)` is used to remove the bond from the +store. + +### TxDeclareCandidacy + +A validator candidacy is declared using the `TxDeclareCandidacy` transaction. + +```golang +type TxDeclareCandidacy struct { + ConsensusPubKey crypto.PubKey + Amount coin.Coin + GovernancePubKey crypto.PubKey + Commission rational.Rat + CommissionMax int64 + CommissionMaxChange int64 + Description Description +} + +declareCandidacy(tx TxDeclareCandidacy): + candidate = loadCandidate(store, tx.PubKey) + if candidate != nil return // candidate with that public key already exists + + candidate = NewCandidate(tx.PubKey) + candidate.Status = Unbonded + candidate.Owner = sender + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero + init commision related fields based on the values from tx + candidate.ProposerRewardPool = Coin(0) + candidate.Description = tx.Description + + saveCandidate(store, candidate) + + txDelegate = TxDelegate(tx.PubKey, tx.Amount) + return delegateWithCandidate(txDelegate, candidate) + +// see delegateWithCandidate function in [TxDelegate](TxDelegate) +``` + +### TxEditCandidacy + +If either the `Description` (excluding `DateBonded` which is constant), +`Commission`, or the `GovernancePubKey` need to be updated, the +`TxEditCandidacy` transaction should be sent from the owner account: + +```golang +type TxEditCandidacy struct { + GovernancePubKey crypto.PubKey + Commission int64 + Description Description +} + +editCandidacy(tx TxEditCandidacy): + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil or candidate.Status == Revoked return + + if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 candidate.Commission = tx.Commission + if tx.Description != nil candidate.Description = tx.Description + + saveCandidate(store, candidate) + return +``` + +### TxDelegate + +Delegator bonds are created using the `TxDelegate` transaction. Within this +transaction the delegator provides an amount of coins, and in return receives +some amount of candidate's delegator shares that are assigned to +`Delegation.Shares`. + +```golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin +} + +delegate(tx TxDelegate): + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil return + return delegateWithCandidate(tx, candidate) + +delegateWithCandidate(tx TxDelegate, candidate Candidate): + if candidate.Status == Revoked return + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + err = transfer(sender, poolAccount, tx.Amount) + if err != nil return + + bond = loadDelegation(store, sender, tx.PubKey) + if bond == nil then bond = Delegation(tx.PubKey, rational.Zero, Coin(0), Coin(0)) + + issuedDelegatorShares = addTokens(tx.Amount, candidate) + bond.Shares += issuedDelegatorShares + + saveCandidate(store, candidate) + saveDelegation(store, sender, bond) + saveGlobalState(store, gs) + return + +addTokens(amount coin.Coin, candidate Candidate): + if candidate.Status == Bonded + gs.BondedPool += amount + issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares + else + gs.UnbondedPool += amount + issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares += issuedShares + + if candidate.IssuedDelegatorShares.IsZero() + exRate = rational.One + else + exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + + issuedDelegatorShares = issuedShares / exRate + candidate.IssuedDelegatorShares += issuedDelegatorShares + return issuedDelegatorShares + +exchangeRate(shares rational.Rat, tokenAmount int64): + if shares.IsZero() then return rational.One + return tokenAmount / shares + +``` + +### TxUnbond + +Delegator unbonding is defined with the following transaction: + +```golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat +} + +unbond(tx TxUnbond): + bond = loadDelegation(store, sender, tx.PubKey) + if bond == nil return + if bond.Shares < tx.Shares return + + bond.Shares -= tx.Shares + + candidate = loadCandidate(store, tx.PubKey) + + revokeCandidacy = false + if bond.Shares.IsZero() + if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegation(store, sender, bond) + else + saveDelegation(store, sender, bond) + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + returnedCoins = removeShares(candidate, shares) + + unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) + unbondDelegationQueue.add(unbondDelegationElem) + + transfer(poolAccount, unbondingPoolAddress, returnCoins) + + if revokeCandidacy + if candidate.Status == Bonded then bondedToUnbondedPool(candidate) + candidate.Status = Revoked + + if candidate.IssuedDelegatorShares.IsZero() + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) + + saveGlobalState(store, gs) + return + +removeShares(candidate Candidate, shares rational.Rat): + globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + + if candidate.Status == Bonded + gs.BondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove + gs.BondedPool -= removedTokens + else + gs.UnbondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove + gs.UnbondedPool -= removedTokens + + candidate.GlobalStakeShares -= removedTokens + candidate.IssuedDelegatorShares -= shares + return returnedCoins + +delegatorShareExRate(candidate Candidate): + if candidate.IssuedDelegatorShares.IsZero() then return rational.One + return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + +bondedToUnbondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares + gs.BondedShares -= candidate.GlobalStakeShares + gs.BondedPool -= removedTokens + + gs.UnbondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Unbonded + + return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) +``` + +### TxRedelegate + +The re-delegation command allows delegators to switch validators while still +receiving equal reward to as if they had never unbonded. + +```golang +type TxRedelegate struct { + PubKeyFrom crypto.PubKey + PubKeyTo crypto.PubKey + Shares rational.Rat +} + +redelegate(tx TxRedelegate): + bond = loadDelegation(store, sender, tx.PubKey) + if bond == nil then return + + if bond.Shares < tx.Shares return + candidate = loadCandidate(store, tx.PubKeyFrom) + if candidate == nil return + + candidate.RedelegatingShares += tx.Shares + reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) + redelegationQueue.add(reDelegationElem) + return +``` + +### TxLivelinessCheck + +Liveliness issues are calculated by keeping track of the block precommits in +the block header. A queue is persisted which contains the block headers from +all recent blocks for the duration of the unbonding period. A validator is +defined as having livliness issues if they have not been included in more than +33% of the blocks over: +* The most recent 24 Hours if they have >= 20% of global stake +* The most recent week if they have = 0% of global stake +* Linear interpolation of the above two scenarios + +Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is +submitted. + +```golang +type TxLivelinessCheck struct { + PubKey crypto.PubKey + RewardAccount Addresss +} +``` + +If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the +liveliness punishment is provided as a reward to `RewardAccount`. + +### TxProveLive + +If the validator was kicked for liveliness issues and is able to regain +liveliness then all delegators in the temporary unbonding pool which have not +transacted to move will be bonded back to the now-live validator and begin to +once again collect provisions and rewards. Regaining liveliness is demonstrated +by sending in a `TxProveLive` transaction: + +```golang +type TxProveLive struct { + PubKey crypto.PubKey +} +``` + +### End of block handling + +```golang +tick(ctx Context): + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + + time = ctx.Time() + if time > gs.InflationLastTime + ProvisionTimeout + gs.InflationLastTime = time + gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) + + provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) + + gs.BondedPool += provisions + gs.TotalSupply += provisions + + saveGlobalState(store, gs) + + 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) + + if time > reDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + candidate = getCandidate(store, elem.PubKey) + returnedCoins = removeShares(candidate, elem.Shares) + candidate.RedelegatingShares -= elem.Shares + delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) + reDelegationQueue.remove(elem) + + return UpdateValidatorSet() + +nextInflation(hrsPerYr rational.Rat): + if gs.TotalSupply > 0 + bondedRatio = gs.BondedPool / gs.TotalSupply + else + bondedRation = 0 + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr + + inflation = gs.Inflation + inflationRateChange + if inflation > params.InflationMax then inflation = params.InflationMax + + if inflation < params.InflationMin then inflation = params.InflationMin + + return inflation + +UpdateValidatorSet(): + candidates = loadCandidates(store) + + v1 = candidates.Validators() + v2 = updateVotingPower(candidates).Validators() + + change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets + return change + +updateVotingPower(candidates Candidates): + foreach candidate in candidates do + 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) + + 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 + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Bonded + + return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) +``` diff --git a/types/stake.go b/types/stake.go new file mode 100644 index 0000000000..0ff267f170 --- /dev/null +++ b/types/stake.go @@ -0,0 +1,57 @@ +package types + +import ( + abci "github.com/tendermint/abci/types" + "github.com/tendermint/go-crypto" +) + +// status of a validator +type ValidatorStatus byte + +// nolint +const ( + Bonded ValidatorStatus = 0x00 + Unbonding ValidatorStatus = 0x01 + Unbonded ValidatorStatus = 0x02 + Revoked ValidatorStatus = 0x03 +) + +// validator for a delegated proof of stake system +type Validator interface { + Status() ValidatorStatus // 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 +func ABCIValidator(v Validator) abci.Validator { + return abci.Validator{ + PubKey: v.GetPubKey().Bytes(), + Power: v.GetPower().Evaluate(), + } +} + +// properties for the set of all validators +type ValidatorSet interface { + Iterate(func(index int64, validator Validator)) // execute arbitrary logic for each validator + Validator(Context, Address) Validator // get a particular validator by owner address + TotalPower(Context) Rat // total power of the validator set +} + +//_______________________________________________________________________________ + +// delegation bond for a delegated proof of stake system +type Delegation interface { + GetDelegator() Address // delegator address for the bond + GetValidator() Address // validator owner address for the bond + GetBondShares() Rat // amount of validator's shares +} + +// properties for the set of all delegations for a particular +type DelegationSet interface { + + // execute arbitrary logic for each validator which a delegator has a delegation for + Iterate(delegator Address, fn func(index int64, delegation Delegation)) +} diff --git a/types/validator_set.go b/types/validator_set.go deleted file mode 100644 index faa21277bb..0000000000 --- a/types/validator_set.go +++ /dev/null @@ -1,43 +0,0 @@ -package types - -import ( - abci "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" -) - -type Validator interface { - GetAddress() Address - GetPubKey() crypto.PubKey - GetPower() Rat -} - -func ABCIValidator(v Validator) abci.Validator { - return abci.Validator{ - PubKey: v.GetPubKey().Bytes(), - Power: v.GetPower().Evaluate(), - } -} - -type ValidatorSet interface { - Iterate(func(int, Validator)) - Size() int -} - -type ValidatorSetKeeper interface { - ValidatorSet(Context) ValidatorSet - Validator(Context, Address) Validator - TotalPower(Context) Rat - DelegationSet(Context, Address) DelegationSet - Delegation(Context, Address, Address) Delegation -} - -type Delegation interface { - GetDelegator() Address - GetValidator() Address - GetBondAmount() Rat -} - -type DelegationSet interface { - Iterate(func(int, Delegation)) - Size() int -} diff --git a/x/stake/fee_distribution.go b/x/fee_distribution/movement.go similarity index 96% rename from x/stake/fee_distribution.go rename to x/fee_distribution/movement.go index 22f2602e74..02d2c62a1e 100644 --- a/x/stake/fee_distribution.go +++ b/x/fee_distribution/movement.go @@ -4,6 +4,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// burn burn burn +func BurnFeeHandler(ctx sdk.Context, collectedFees sdk.Coins) {} + // Handle fee distribution to the validators and delegators func (k Keeper) FeeHandler(ctx sdk.Context, collectedFees sdk.Coins) { pool := k.GetPool(ctx) diff --git a/x/fee_distribution/types.go b/x/fee_distribution/types.go new file mode 100644 index 0000000000..1303e9a62b --- /dev/null +++ b/x/fee_distribution/types.go @@ -0,0 +1,113 @@ +package stake + +import ( + "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + Pool Pool `json:"pool"` + Params Params `json:"params"` +} + +func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []Delegation) GenesisState { + return GenesisState{ + Pool: pool, + Params: params, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Pool: initialPool(), + Params: defaultParams(), + } +} + +// fee information for a validator +type Validator struct { + Adjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools +} + +//_________________________________________________________________________ + +// Params defines the high level settings for staking +type Params struct { + FeeDenoms []string `json:"fee_denoms"` // accepted fee denoms + ReservePoolFee sdk.Rat `json:"reserve_pool_fee"` // percent of fees which go to reserve pool +} + +func (p Params) equal(p2 Params) bool { + return p.BondDenom == p2.BondDenom && + p.ReservePoolFee.Equal(p2.ReservePoolFee) +} + +func defaultParams() Params { + return Params{ + FeeDenoms: []string{"steak"}, + ReservePoolFee: sdk.NewRat(5, 100), + } +} + +//_________________________________________________________________________ + +// Pool - dynamic parameters of the current state +type Pool struct { + FeeReservePool sdk.Coins `json:"fee_reserve_pool"` // XXX reserve pool of collected fees for use by governance + FeePool sdk.Coins `json:"fee_pool"` // XXX fee pool for all the fee shares which have already been distributed + FeeSumReceived sdk.Coins `json:"fee_sum_received"` // XXX sum of all fees received, post reserve pool `json:"fee_sum_received"` + FeeRecent sdk.Coins `json:"fee_recent"` // XXX most recent fee collected + FeeAdjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // XXX last recorded bonded shares +} + +func (p Pool) equal(p2 Pool) bool { + return p.FeeReservePool.IsEqual(p2.FeeReservePool) && + p.FeePool.IsEqual(p2.FeePool) && + p.FeeSumReceived.IsEqual(p2.FeeSumReceived) && + p.FeeRecent.IsEqual(p2.FeeRecent) && + sdk.RatsEqual(p.FeeAdjustments, p2.FeeAdjustments) && + p.PrevBondedShares.Equal(p2.PrevBondedShares) +} + +// initial pool for testing +func initialPool() Pool { + return Pool{ + FeeReservePool: sdk.Coins(nil), + FeePool: sdk.Coins(nil), + FeeSumReceived: sdk.Coins(nil), + FeeRecent: sdk.Coins(nil), + FeeAdjustments: []sdk.Rat{sdk.ZeroRat()}, + PrevBondedShares: sdk.ZeroRat(), + } +} + +//_________________________________________________________________________ + +// Used in calculation of fee shares, added to a queue for each block where a power change occures +type PowerChange struct { + Height int64 `json:"height"` // block height at change + Power sdk.Rat `json:"power"` // total power at change + PrevPower sdk.Rat `json:"prev_power"` // total power at previous height-1 + FeesIn sdk.Coins `json:"fees_in"` // fees in at block height + PrevFeePool sdk.Coins `json:"prev_fee_pool"` // total fees in at previous block height +} + +//_________________________________________________________________________ +// KEY MANAGEMENT + +var ( + // Keys for store prefixes + PowerChangeKey = []byte{0x09} // prefix for power change object +) + +// get the key for the accumulated update validators +func GetPowerChangeKey(height int64) []byte { + heightBytes := make([]byte, binary.MaxVarintLen64) + binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) + return append(PowerChangeKey, heightBytes...) +} diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 851ed3c499..47b022ecb3 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -87,7 +87,7 @@ func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { } // get the command to query a single delegator bond -func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command { +func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegator-bond", Short: "Query a delegators bond based on address and candidate pubkey", @@ -104,7 +104,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command } delegator := crypto.Address(bz) - key := stake.GetDelegatorBondKey(delegator, addr, cdc) + key := stake.GetDelegationKey(delegator, addr, cdc) ctx := context.NewCoreContextFromViper() res, err := ctx.Query(key, storeName) if err != nil { @@ -112,7 +112,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command } // parse out the bond - bond := new(stake.DelegatorBond) + bond := new(stake.Delegation) cdc.MustUnmarshalBinary(res, bond) output, err := wire.MarshalJSONIndent(cdc, bond) if err != nil { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 8eb0e03ceb..2467fec55e 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -43,7 +43,7 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, } candidateAddr := sdk.Address(bz) - key := stake.GetDelegatorBondKey(delegatorAddr, candidateAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, candidateAddr, cdc) res, err := ctx.Query(key, storeName) if err != nil { @@ -58,7 +58,7 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, return } - var bond stake.DelegatorBond + var bond stake.Delegation err = cdc.UnmarshalBinary(res, &bond) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/x/stake/handler.go b/x/stake/handler.go index d82ab3dd1b..f6b41c3491 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -56,7 +56,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { k.setCandidate(ctx, candidate) } for _, bond := range data.Bonds { - k.setDelegatorBond(ctx, bond) + k.setDelegation(ctx, bond) } } @@ -169,9 +169,9 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, bondAmt sdk.Coin, candidate Candidate) (sdk.Tags, sdk.Error) { // Get or create the delegator bond - bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address) + bond, found := k.GetDelegation(ctx, delegatorAddr, candidate.Address) if !found { - bond = DelegatorBond{ + bond = Delegation{ DelegatorAddr: delegatorAddr, CandidateAddr: candidate.Address, Shares: sdk.ZeroRat(), @@ -190,7 +190,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, // Update bond height bond.Height = ctx.BlockHeight() - k.setDelegatorBond(ctx, bond) + k.setDelegation(ctx, bond) k.setCandidate(ctx, candidate) k.setPool(ctx, pool) tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) @@ -200,7 +200,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // check if bond has any shares in it unbond - bond, found := k.GetDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr) + bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.CandidateAddr) if !found { return ErrNoDelegatorForAddress(k.codespace).Result() } @@ -257,11 +257,11 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { revokeCandidacy = true } - k.removeDelegatorBond(ctx, bond) + k.removeDelegation(ctx, bond) } else { // Update bond height bond.Height = ctx.BlockHeight() - k.setDelegatorBond(ctx, bond) + k.setDelegation(ctx, bond) } // Add the coins diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 96d5972628..d27bd54c25 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -86,7 +86,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { //Check that the accounts and the bond account have the appropriate values candidate, found := keeper.GetCandidate(ctx, candidateAddr) require.True(t, found) - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, candidateAddr) require.True(t, found) expBond := int64(i+1) * bondAmount @@ -144,7 +144,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { //Check that the accounts and the bond account have the appropriate values candidate, found = keeper.GetCandidate(ctx, candidateAddr) require.True(t, found) - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, candidateAddr) require.True(t, found) expBond := initBond - int64(i+1)*unbondShares @@ -259,7 +259,7 @@ func TestMultipleMsgDelegate(t *testing.T) { require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, candidateAddr) require.True(t, found) require.NotNil(t, bond, "expected delegatee bond %d to exist", bond) } @@ -271,7 +271,7 @@ func TestMultipleMsgDelegate(t *testing.T) { require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded - _, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + _, found := keeper.GetDelegation(ctx, delegatorAddr, candidateAddr) require.False(t, found) } } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 6e36588301..941b69e519 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -4,6 +4,7 @@ import ( "bytes" "sort" + "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/bank" @@ -133,12 +134,12 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store.Set(GetValidatorKey(validator), bzVal) // add to the validators to update list if is already a validator - if store.Get(GetRecentValidatorKey(candidate.PubKey)) != nil { + if store.Get(GetCurrentValidatorsKey(candidate.PubKey)) != nil { bzAbci := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) store.Set(GetAccUpdateValidatorKey(address), bzAbci) - // also update the recent validator store - store.Set(GetRecentValidatorKey(validator.PubKey), bzVal) + // also update the current validator store + store.Set(GetCurrentValidatorsKey(validator.PubKey), bzVal) return } @@ -160,19 +161,19 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { store.Delete(GetCandidateKey(address)) store.Delete(GetValidatorKey(candidate.validator())) - // delete from recent and power weighted validator groups if the validator + // delete from current and power weighted validator groups if the validator // exists and add validator with zero power to the validator updates - if store.Get(GetRecentValidatorKey(candidate.PubKey)) == nil { + if store.Get(GetCurrentValidatorsKey(candidate.PubKey)) == nil { return } bz := k.cdc.MustMarshalBinary(candidate.validator().abciValidatorZero(k.cdc)) store.Set(GetAccUpdateValidatorKey(address), bz) - store.Delete(GetRecentValidatorKey(candidate.PubKey)) + store.Delete(GetCurrentValidatorsKey(candidate.PubKey)) } //___________________________________________________________________________ -// get the group of the most recent validators +// get the group of the most current validators func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) @@ -180,7 +181,7 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { maxValidators := k.GetParams(ctx).MaxValidators validators = make([]Validator, maxValidators) - iterator := store.SubspaceIterator(RecentValidatorsKey) + iterator := store.SubspaceIterator(CurrentValidatorsKey) i := 0 for ; iterator.Valid(); iterator.Next() { bz := iterator.Value() @@ -194,17 +195,17 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { } // Only used for testing -// get the group of the most recent validators +// get the group of the most current validators func (k Keeper) getValidatorsOrdered(ctx sdk.Context) []Validator { vals := k.GetValidators(ctx) sort.Sort(sort.Reverse(validators(vals))) return vals } -// Is the address provided a part of the most recently saved validator group? +// Is the address provided a part of the current validator set? func (k Keeper) IsValidator(ctx sdk.Context, pk crypto.PubKey) bool { store := ctx.KVStore(k.storeKey) - if store.Get(GetRecentValidatorKey(pk)) == nil { + if store.Get(GetCurrentValidatorsKey(pk)) == nil { return false } return true @@ -215,14 +216,14 @@ func (k Keeper) IsValidator(ctx sdk.Context, pk crypto.PubKey) bool { // // The correct subset is retrieved by iterating through an index of the // candidates sorted by power, stored using the ValidatorsKey. Simultaniously -// the most recent the validator records are updated in store with the -// RecentValidatorsKey. This store is used to determine if a candidate is a +// the current validator records are updated in store with the +// CurrentValidatorsKey. This store is used to determine if a candidate is a // validator without needing to iterate over the subspace as we do in // GetValidators func (k Keeper) addNewValidatorOrNot(ctx sdk.Context, store sdk.KVStore, address sdk.Address) { - // clear the recent validators store, add to the ToKickOut temp store - iterator := store.SubspaceIterator(RecentValidatorsKey) + // clear the current validators store, add to the ToKickOut temp store + iterator := store.SubspaceIterator(CurrentValidatorsKey) for ; iterator.Valid(); iterator.Next() { bz := iterator.Value() @@ -253,8 +254,8 @@ func (k Keeper) addNewValidatorOrNot(ctx sdk.Context, store sdk.KVStore, address // remove from ToKickOut group store.Delete(GetToKickOutValidatorKey(validator.Address)) - // also add to the recent validators group - store.Set(GetRecentValidatorKey(validator.PubKey), bz) + // also add to the current validators group + store.Set(GetCurrentValidatorsKey(validator.PubKey), bz) // MOST IMPORTANTLY, add to the accumulated changes if this is the modified candidate if bytes.Equal(address, validator.Address) { @@ -292,7 +293,7 @@ func (k Keeper) GetTotalPrecommitVotingPower(ctx sdk.Context) sdk.Rat { TotalPower := sdk.ZeroRat() i := int32(0) - iterator := store.SubspaceIterator(RecentValidatorsKey) + iterator := store.SubspaceIterator(CurrentValidatorsKey) for ; iterator.Valid(); iterator.Next() { skip := false @@ -340,7 +341,7 @@ func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []abci.Validato return } -// remove all validator update entries +// remove all validator update entries after applied to Tendermint func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) { store := ctx.KVStore(k.storeKey) @@ -355,11 +356,11 @@ func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) { //_____________________________________________________________________ // load a delegator bond -func (k Keeper) GetDelegatorBond(ctx sdk.Context, - delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { +func (k Keeper) GetDelegation(ctx sdk.Context, + delegatorAddr, candidateAddr sdk.Address) (bond Delegation, found bool) { store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegatorBondKey(delegatorAddr, candidateAddr, k.cdc)) + delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, candidateAddr, k.cdc)) if delegatorBytes == nil { return bond, false } @@ -369,11 +370,11 @@ func (k Keeper) GetDelegatorBond(ctx sdk.Context, } // load all bonds -func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorBond) { +func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []Delegation) { store := ctx.KVStore(k.storeKey) - iterator := store.SubspaceIterator(DelegatorBondKeyPrefix) + iterator := store.SubspaceIterator(DelegationKeyPrefix) - bonds = make([]DelegatorBond, maxRetrieve) + bonds = make([]Delegation, maxRetrieve) i := 0 for ; ; i++ { if !iterator.Valid() || i > int(maxRetrieve-1) { @@ -381,7 +382,7 @@ func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorB break } bondBytes := iterator.Value() - var bond DelegatorBond + var bond Delegation k.cdc.MustUnmarshalBinary(bondBytes, &bond) bonds[i] = bond iterator.Next() @@ -390,12 +391,12 @@ func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorB } // load all bonds of a delegator -func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { +func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegatorBondsKey(delegator, k.cdc) + delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) iterator := store.SubspaceIterator(delegatorPrefixKey) //smallest to largest - bonds = make([]DelegatorBond, maxRetrieve) + bonds = make([]Delegation, maxRetrieve) i := 0 for ; ; i++ { if !iterator.Valid() || i > int(maxRetrieve-1) { @@ -403,7 +404,7 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet break } bondBytes := iterator.Value() - var bond DelegatorBond + var bond Delegation k.cdc.MustUnmarshalBinary(bondBytes, &bond) bonds[i] = bond iterator.Next() @@ -411,15 +412,15 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet return bonds[:i] // trim } -func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { +func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) { store := ctx.KVStore(k.storeKey) b := k.cdc.MustMarshalBinary(bond) - store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) + store.Set(GetDelegationKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) } -func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { +func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) { store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) + store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) } //_______________________________________________________________________ @@ -501,15 +502,25 @@ func (k Keeper) setPool(ctx sdk.Context, p Pool) { //__________________________________________________________________________ -// Implements ValidatorSetKeeper +// Implements ValidatorSet -var _ sdk.ValidatorSetKeeper = Keeper{} +var _ sdk.ValidatorSet = Keeper{} -func (k Keeper) ValidatorSet(ctx sdk.Context) sdk.ValidatorSet { - vals := k.GetValidators(ctx) - return ValidatorSet(vals) +// iterate through the active validator set and perform the provided function +func (k Keeper) Iterate(fn func(index int64, validator sdk.Validator)) { + iterator := store.SubspaceIterator(CurrentValidatorsKey) + i := 0 + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var validator Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + i++ + } + iterator.Close() } +// get the sdk.validator for a particular address func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { can, ok := k.GetCandidate(ctx, addr) if !ok { @@ -521,20 +532,38 @@ func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { return can.validator() } +// total power from the bond func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { pool := k.GetPool(ctx) return pool.BondedShares } -func (k Keeper) Delegation(ctx sdk.Context, del sdk.Address, val sdk.Address) sdk.Delegation { - bond, ok := k.GetDelegatorBond(ctx, del, val) +//__________________________________________________________________________ + +// Implements DelegationSet + +var _ sdk.ValidatorSet = Keeper{} + +// get the delegation for a particular set of delegator and validator addresses +func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { + bond, ok := k.GetDelegation(ctx, addrDel, addrVal) if !ok { return nil } return bond } -func (k Keeper) DelegationSet(ctx sdk.Context, del sdk.Address) sdk.DelegationSet { - bs := k.GetDelegatorBonds(ctx, del, 32767) - return DelegationSet(bs) +// iterate through the active validator set and perform the provided function +func (k Keeper) Iterate(delAddr sdk.Address, fn func(index int64, delegator sdk.Delegator)) { + key := GetDelegationsKey(delAddr, k.cdc) + iterator := store.SubspaceIterator(CurrentValidatorsKey) + i := 0 + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var delegation Delegation + k.cdc.MustUnmarshalBinary(bz, &delegation) + fn(i, delegator) // XXX is this safe will the fields be able to get written to? + i++ + } + iterator.Close() } diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index a7e1e8a7de..ca532a2d0f 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -18,11 +18,10 @@ var ( CandidatesKey = []byte{0x02} // prefix for each key to a candidate ValidatorsKey = []byte{0x03} // prefix for each key to a validator AccUpdateValidatorsKey = []byte{0x04} // prefix for each key to a validator which is being updated - RecentValidatorsKey = []byte{0x05} // prefix for each key to the last updated validator group + CurrentValidatorsKey = []byte{0x05} // prefix for each key to the last updated validator group 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 + DelegationKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond IntraTxCounterKey = []byte{0x08} // key for block-local tx index - PowerChangeKey = []byte{0x09} // prefix for power change object ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -53,15 +52,10 @@ func GetAccUpdateValidatorKey(addr sdk.Address) []byte { return append(AccUpdateValidatorsKey, addr.Bytes()...) } -// get the key for the recent validator group, ordered like tendermint -func GetRecentValidatorKey(pk crypto.PubKey) []byte { +// get the key for the current validator group, ordered like tendermint +func GetCurrentValidatorsKey(pk crypto.PubKey) []byte { addr := pk.Address() - return append(RecentValidatorsKey, addr.Bytes()...) -} - -// remove the prefix byte from a key -func AddrFromKey(key []byte) sdk.Address { - return key[1:] + return append(CurrentValidatorsKey, addr.Bytes()...) } // get the key for the accumulated update validators @@ -70,22 +64,22 @@ func GetToKickOutValidatorKey(addr sdk.Address) []byte { } // get the key for delegator bond with candidate -func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...) +func GetDelegationKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegationsKey(delegatorAddr, cdc), candidateAddr.Bytes()...) } // get the prefix for a delegator for all candidates -func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { +func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { res, err := cdc.MarshalBinary(&delegatorAddr) if err != nil { panic(err) } - return append(DelegatorBondKeyPrefix, res...) + return append(DelegationKeyPrefix, res...) } -// get the key for the accumulated update validators -func GetPowerChangeKey(height int64) []byte { - heightBytes := make([]byte, binary.MaxVarintLen64) - binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) - return append(PowerChangeKey, heightBytes...) +//______________________________________________________________ + +// remove the prefix byte from a key, possibly revealing and address +func AddrFromKey(key []byte) sdk.Address { + return key[1:] } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index e442833456..6c504331b8 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -81,7 +81,7 @@ func TestCandidate(t *testing.T) { assert.False(t, found) } -// tests GetDelegatorBond, GetDelegatorBonds, SetDelegatorBond, removeDelegatorBond, GetBonds +// tests GetDelegation, GetDelegations, SetDelegation, removeDelegation, GetBonds func TestBond(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) @@ -97,54 +97,54 @@ func TestBond(t *testing.T) { // first add a candidates[0] to delegate too keeper.setCandidate(ctx, candidates[0]) - bond1to1 := DelegatorBond{ + bond1to1 := Delegation{ DelegatorAddr: addrDels[0], CandidateAddr: addrVals[0], Shares: sdk.NewRat(9), } // check the empty keeper first - _, found := keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.False(t, found) // set and retrieve a record - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found := keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bond1to1.equal(resBond)) // modify a records, save, and retrieve bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bond1to1.equal(resBond)) // add some more records keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegatorBond(ctx, bond1to2) - keeper.setDelegatorBond(ctx, bond1to3) - keeper.setDelegatorBond(ctx, bond2to1) - keeper.setDelegatorBond(ctx, bond2to2) - keeper.setDelegatorBond(ctx, bond2to3) + bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} + keeper.setDelegation(ctx, bond1to2) + keeper.setDelegation(ctx, bond1to3) + keeper.setDelegation(ctx, bond2to1) + keeper.setDelegation(ctx, bond2to2) + keeper.setDelegation(ctx, bond2to3) // test all bond retrieve capabilities - resBonds := keeper.GetDelegatorBonds(ctx, addrDels[0], 5) + resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bond1to1.equal(resBonds[0])) assert.True(t, bond1to2.equal(resBonds[1])) assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[0], 3) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) require.Equal(t, 3, len(resBonds)) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[0], 2) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bond2to1.equal(resBonds[0])) assert.True(t, bond2to2.equal(resBonds[1])) @@ -159,22 +159,22 @@ func TestBond(t *testing.T) { assert.True(t, bond2to3.equal(allBonds[5])) // delete a record - keeper.removeDelegatorBond(ctx, bond2to3) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[2]) + keeper.removeDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) assert.False(t, found) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 2, len(resBonds)) assert.True(t, bond2to1.equal(resBonds[0])) assert.True(t, bond2to2.equal(resBonds[1])) // delete all the records from delegator 2 - keeper.removeDelegatorBond(ctx, bond2to1) - keeper.removeDelegatorBond(ctx, bond2to2) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[0]) + keeper.removeDelegation(ctx, bond2to1) + keeper.removeDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) assert.False(t, found) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[1]) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) assert.False(t, found) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 0, len(resBonds)) } diff --git a/x/stake/types.go b/x/stake/types.go index da5953f9fb..00ac2b4101 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -11,13 +11,13 @@ import ( // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Candidates []Candidate `json:"candidates"` - Bonds []DelegatorBond `json:"bonds"` + Pool Pool `json:"pool"` + Params Params `json:"params"` + Candidates []Candidate `json:"candidates"` + Bonds []Delegation `json:"bonds"` } -func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []DelegatorBond) GenesisState { +func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []Delegation) GenesisState { return GenesisState{ Pool: pool, Params: params, @@ -45,9 +45,6 @@ type Params struct { MaxValidators uint16 `json:"max_validators"` // maximum number of validators BondDenom string `json:"bond_denom"` // bondable coin denomination - - FeeDenoms []string `json:"fee_denoms"` // accepted fee denoms - ReservePoolFee sdk.Rat `json:"reserve_pool_fee"` // percent of fees which go to reserve pool } func (p Params) equal(p2 Params) bool { @@ -56,8 +53,7 @@ func (p Params) equal(p2 Params) bool { p.InflationMin.Equal(p2.InflationMin) && p.GoalBonded.Equal(p2.GoalBonded) && p.MaxValidators == p2.MaxValidators && - p.BondDenom == p2.BondDenom && - p.ReservePoolFee.Equal(p2.ReservePoolFee) + p.BondDenom == p2.BondDenom } func defaultParams() Params { @@ -68,8 +64,6 @@ func defaultParams() Params { GoalBonded: sdk.NewRat(67, 100), MaxValidators: 100, BondDenom: "steak", - FeeDenoms: []string{"steak"}, - ReservePoolFee: sdk.NewRat(5, 100), } } @@ -90,12 +84,7 @@ type Pool struct { DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) // Fee Related - FeeReservePool sdk.Coins `json:"fee_reserve_pool"` // XXX reserve pool of collected fees for use by governance - FeePool sdk.Coins `json:"fee_pool"` // XXX fee pool for all the fee shares which have already been distributed - FeeSumReceived sdk.Coins `json:"fee_sum_received"` // XXX sum of all fees received, post reserve pool `json:"fee_sum_received"` - FeeRecent sdk.Coins `json:"fee_recent"` // XXX most recent fee collected - FeeAdjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // XXX last recorded bonded shares + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calcualtions } func (p Pool) equal(p2 Pool) bool { @@ -107,11 +96,6 @@ func (p Pool) equal(p2 Pool) bool { p.InflationLastTime == p2.InflationLastTime && p.Inflation.Equal(p2.Inflation) && p.DateLastCommissionReset == p2.DateLastCommissionReset && - p.FeeReservePool.IsEqual(p2.FeeReservePool) && - p.FeePool.IsEqual(p2.FeePool) && - p.FeeSumReceived.IsEqual(p2.FeeSumReceived) && - p.FeeRecent.IsEqual(p2.FeeRecent) && - sdk.RatsEqual(p.FeeAdjustments, p2.FeeAdjustments) && p.PrevBondedShares.Equal(p2.PrevBondedShares) } @@ -128,42 +112,16 @@ func initialPool() Pool { InflationLastTime: 0, Inflation: sdk.NewRat(7, 100), DateLastCommissionReset: 0, - FeeReservePool: sdk.Coins(nil), - FeePool: sdk.Coins(nil), - FeeSumReceived: sdk.Coins(nil), - FeeRecent: sdk.Coins(nil), - FeeAdjustments: []sdk.Rat{sdk.ZeroRat()}, PrevBondedShares: sdk.ZeroRat(), } } //_________________________________________________________________________ -// Used in calculation of fee shares, added to a queue for each block where a power change occures -type PowerChange struct { - Height int64 `json:"height"` // block height at change - Power sdk.Rat `json:"power"` // total power at change - PrevPower sdk.Rat `json:"prev_power"` // total power at previous height-1 - FeesIn sdk.Coins `json:"fees_in"` // fees in at block height - PrevFeePool sdk.Coins `json:"prev_fee_pool"` // total fees in at previous block height -} - -//_________________________________________________________________________ - -// CandidateStatus - status of a validator-candidate -type CandidateStatus byte - -const ( - // nolint - Bonded CandidateStatus = 0x00 - Unbonded CandidateStatus = 0x01 - Revoked CandidateStatus = 0x02 -) - // Candidate defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this -// candidate, the candidate is credited with a DelegatorBond whose number of +// candidate, the candidate is credited with a Delegation whose number of // bond shares is based on the amount of coins delegated divided by the current // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. @@ -187,8 +145,7 @@ type Candidate struct { CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) // fee related - FeeAdjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools } // Candidates - list of Candidates @@ -197,21 +154,21 @@ 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, - BondedShares: sdk.ZeroRat(), - DelegatorShares: sdk.ZeroRat(), - Description: description, - ValidatorBondHeight: int64(0), - ValidatorBondCounter: int16(0), - ProposerRewardPool: sdk.Coins{}, - Commission: sdk.ZeroRat(), - CommissionMax: sdk.ZeroRat(), - CommissionChangeRate: sdk.ZeroRat(), - CommissionChangeToday: sdk.ZeroRat(), - FeeAdjustments: []sdk.Rat(nil), - PrevBondedShares: sdk.ZeroRat(), + Status: Unbonded, + Address: address, + PubKey: pubKey, + BondedShares: sdk.ZeroRat(), + DelegatorShares: sdk.ZeroRat(), + Description: description, + ValidatorBondHeight: int64(0), + ValidatorBondIntraTxCounter: int16(0), + ProposerRewardPool: sdk.Coins{}, + Commission: sdk.ZeroRat(), + CommissionMax: sdk.ZeroRat(), + CommissionChangeRate: sdk.ZeroRat(), + CommissionChangeToday: sdk.ZeroRat(), + FeeAdjustments: []sdk.Rat(nil), + PrevBondedShares: sdk.ZeroRat(), } } @@ -258,39 +215,6 @@ func (c Candidate) delegatorShareExRate() sdk.Rat { return c.BondedShares.Quo(c.DelegatorShares) } -// 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{ - Address: c.Address, - PubKey: c.PubKey, - Power: c.BondedShares, - Height: c.ValidatorBondHeight, - Counter: c.ValidatorBondCounter, - } -} - -//XXX updateDescription function -//XXX enforce limit to number of description characters - -//______________________________________________________________________ - -// Validator is one of the top Candidates -type Validator struct { - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - Power sdk.Rat `json:"power"` - Height int64 `json:"height"` // Earliest height as a validator - Counter int16 `json:"counter"` // Block-local tx index for resolving equal voting power & height -} - -// verify equal not including height or counter -func (v Validator) equal(v2 Validator) bool { - return bytes.Equal(v.Address, v2.Address) && - v.PubKey.Equals(v2.PubKey) && - v.Power.Equal(v2.Power) -} - // abci validator from stake validator type func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { return abci.Validator{ @@ -308,74 +232,43 @@ func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { } } +//XXX updateDescription function +//XXX enforce limit to number of description characters + +//______________________________________________________________________ + +// ensure fulfills the sdk validator types var _ sdk.Validator = Validator{} -func (v Validator) GetAddress() sdk.Address { - return v.Address -} - -func (v Validator) GetPubKey() crypto.PubKey { - return v.PubKey -} - -func (v Validator) GetPower() sdk.Rat { - return v.Power -} - -type ValidatorSet []Validator - -var _ sdk.ValidatorSet = ValidatorSet{} - -func (vs ValidatorSet) Iterate(fn func(int, sdk.Validator)) { - for i, v := range vs { - fn(i, v) - } -} - -func (vs ValidatorSet) Size() int { - return len(vs) -} +// nolint - for sdk.Validator +func (v Validator) GetAddress() sdk.Address { return v.Address } +func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } +func (v Validator) GetPower() sdk.Rat { return v.Power } //_________________________________________________________________________ -// DelegatorBond represents the bond with tokens held by an account. It is +// Delegation 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. // TODO better way of managing space -type DelegatorBond struct { +type Delegation struct { DelegatorAddr sdk.Address `json:"delegator_addr"` CandidateAddr sdk.Address `json:"candidate_addr"` Shares sdk.Rat `json:"shares"` Height int64 `json:"height"` // Last height bond updated } -func (b DelegatorBond) equal(b2 DelegatorBond) bool { +func (b Delegation) equal(b2 Delegation) bool { return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && bytes.Equal(b.CandidateAddr, b2.CandidateAddr) && b.Height == b2.Height && b.Shares.Equal(b2.Shares) } -func (b DelegatorBond) GetDelegator() sdk.Address { - return b.DelegatorAddr -} +// ensure fulfills the sdk validator types +var _ sdk.Delegation = Delegation{} -func (b DelegatorBond) GetValidator() sdk.Address { - return b.CandidateAddr -} - -func (b DelegatorBond) GetBondAmount() sdk.Rat { - return b.Shares -} - -type DelegationSet []DelegatorBond - -func (ds DelegationSet) Iterate(fn func(int, sdk.Delegation)) { - for i, d := range ds { - fn(i, d) - } -} - -func (ds DelegationSet) Size() int { - return len(ds) -} +// nolint - for sdk.Delegation +func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } +func (b Delegation) GetValidator() sdk.Address { return b.CandidateAddr } +func (b Delegation) GetBondAmount() sdk.Rat { return b.Shares } diff --git a/x/stake/view_slash_keeper.go b/x/stake/view_slash_keeper.go index 375c7323b1..c4c46cbeff 100644 --- a/x/stake/view_slash_keeper.go +++ b/x/stake/view_slash_keeper.go @@ -17,13 +17,13 @@ func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { } // load a delegator bond -func (v ViewSlashKeeper) GetDelegatorBond(ctx sdk.Context, - delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { - return v.keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) +func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context, + delegatorAddr, candidateAddr sdk.Address) (bond Delegation, found bool) { + return v.keeper.GetDelegation(ctx, delegatorAddr, candidateAddr) } // load n delegator bonds -func (v ViewSlashKeeper) GetDelegatorBonds(ctx sdk.Context, - delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { - return v.keeper.GetDelegatorBonds(ctx, delegator, maxRetrieve) +func (v ViewSlashKeeper) GetDelegations(ctx sdk.Context, + delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { + return v.keeper.GetDelegations(ctx, delegator, maxRetrieve) } diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go index e59c05852f..7264585aca 100644 --- a/x/stake/view_slash_keeper_test.go +++ b/x/stake/view_slash_keeper_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -// tests GetDelegatorBond, GetDelegatorBonds +// tests GetDelegation, GetDelegations func TestViewSlashBond(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) @@ -28,7 +28,7 @@ func TestViewSlashBond(t *testing.T) { // first add a candidates[0] to delegate too keeper.setCandidate(ctx, candidates[0]) - bond1to1 := DelegatorBond{ + bond1to1 := Delegation{ DelegatorAddr: addrDels[0], CandidateAddr: addrVals[0], Shares: sdk.NewRat(9), @@ -37,47 +37,47 @@ func TestViewSlashBond(t *testing.T) { viewSlashKeeper := NewViewSlashKeeper(keeper) // check the empty keeper first - _, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + _, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.False(t, found) // set and retrieve a record - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bond1to1.equal(resBond)) // modify a records, save, and retrieve bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bond1to1.equal(resBond)) // add some more records keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegatorBond(ctx, bond1to2) - keeper.setDelegatorBond(ctx, bond1to3) - keeper.setDelegatorBond(ctx, bond2to1) - keeper.setDelegatorBond(ctx, bond2to2) - keeper.setDelegatorBond(ctx, bond2to3) + bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4} + keeper.setDelegation(ctx, bond1to2) + keeper.setDelegation(ctx, bond1to3) + keeper.setDelegation(ctx, bond2to1) + keeper.setDelegation(ctx, bond2to2) + keeper.setDelegation(ctx, bond2to3) // test all bond retrieve capabilities - resBonds := viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 5) + resBonds := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bond1to1.equal(resBonds[0])) assert.True(t, bond1to2.equal(resBonds[1])) assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 3) + resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 3) require.Equal(t, 3, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 2) + resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bond2to1.equal(resBonds[0])) assert.True(t, bond2to2.equal(resBonds[1]))