Skip to content

Commit

Permalink
Merge PR #3308: Genesis Vesting Accounts & Simulation
Browse files Browse the repository at this point in the history
* Update vesting spec and impl time fields and constructors
* Update genesis to support vesting accounts
* More spec and godoc updates
* Update genesis section in vesting spec
* Fix bank unit tests
* Add test cases to ToAccount in genesis
* Update RegisterCodec to include vesting interface and types
* Fix GetVestedCoins bug where block time is past vesting end time
* Add vesting accounts to simulation
* Update vesting genesis logic to panic on invalid accounts
* Change randomness of vesting accounts in simulation
  • Loading branch information
alexanderbez authored and cwgoes committed Jan 17, 2019
1 parent 20bcacf commit f2e87ad
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 115 deletions.
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ FEATURES

* Gaia
* [\#2182] [x/staking] Added querier for querying a single redelegation
* [\#3305](https://github.com/cosmos/cosmos-sdk/issues/3305) Add support for
vesting accounts at genesis.
* [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) [x/auth] Add multisig transactions support
* [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) `add-genesis-account` can take both account addresses and key names

Expand Down
3 changes: 2 additions & 1 deletion cmd/gaia/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,11 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
sort.Slice(genesisState.Accounts, func(i, j int) bool {
return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber
})

// load the accounts
for _, gacc := range genesisState.Accounts {
acc := gacc.ToAccount()
acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx)
acc = app.accountKeeper.NewAccount(ctx, acc) // set account number
app.accountKeeper.SetAccount(ctx, acc)
}

Expand Down
37 changes: 32 additions & 5 deletions cmd/gaia/app/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,15 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
}
}

// nolint
// GenesisAccount defines an account initialized at genesis.
type GenesisAccount struct {
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
Sequence uint64 `json:"sequence_number"`
AccountNumber uint64 `json:"account_number"`
Vesting bool `json:"vesting"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
}

func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
Expand All @@ -76,22 +79,43 @@ func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount {
}

func NewGenesisAccountI(acc auth.Account) GenesisAccount {
return GenesisAccount{
gacc := GenesisAccount{
Address: acc.GetAddress(),
Coins: acc.GetCoins(),
AccountNumber: acc.GetAccountNumber(),
Sequence: acc.GetSequence(),
}

vacc, ok := acc.(auth.VestingAccount)
if ok {
gacc.Vesting = true
gacc.StartTime = vacc.GetStartTime()
gacc.EndTime = vacc.GetEndTime()
}

return gacc
}

// convert GenesisAccount to auth.BaseAccount
func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) {
return &auth.BaseAccount{
func (ga *GenesisAccount) ToAccount() auth.Account {
bacc := &auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins.Sort(),
AccountNumber: ga.AccountNumber,
Sequence: ga.Sequence,
}

if ga.Vesting {
if ga.StartTime != 0 && ga.EndTime != 0 {
return auth.NewContinuousVestingAccount(bacc, ga.StartTime, ga.EndTime)
} else if ga.EndTime != 0 {
return auth.NewDelayedVestingAccount(bacc, ga.EndTime)
} else {
panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga))
}
}

return bacc
}

// Create the core parameters for genesis initialization for gaia
Expand All @@ -114,28 +138,31 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js
if err := cdc.UnmarshalJSON(genTx, &tx); err != nil {
return genesisState, err
}

msgs := tx.GetMsgs()
if len(msgs) != 1 {
return genesisState, errors.New(
"must provide genesis StdTx with exactly 1 CreateValidator message")
}

if _, ok := msgs[0].(staking.MsgCreateValidator); !ok {
return genesisState, fmt.Errorf(
"Genesis transaction %v does not contain a MsgCreateValidator", i)
}
}

for _, acc := range genesisState.Accounts {
// create the genesis account, give'm few steaks and a buncha token with there name
for _, coin := range acc.Coins {
if coin.Denom == bondDenom {
stakingData.Pool.LooseTokens = stakingData.Pool.LooseTokens.
Add(coin.Amount) // increase the supply
}
}
}

genesisState.StakingData = stakingData
genesisState.GenTxs = appGenTxs

return genesisState, nil
}

Expand Down
13 changes: 12 additions & 1 deletion cmd/gaia/app/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"encoding/json"
"testing"
"time"

"github.com/tendermint/tendermint/crypto/secp256k1"
tmtypes "github.com/tendermint/tendermint/types"
Expand Down Expand Up @@ -56,7 +57,17 @@ func TestToAccount(t *testing.T) {
addr := sdk.AccAddress(priv.PubKey().Address())
authAcc := auth.NewBaseAccountWithAddress(addr)
genAcc := NewGenesisAccount(&authAcc)
require.Equal(t, authAcc, *genAcc.ToAccount())
acc := genAcc.ToAccount()
require.IsType(t, &auth.BaseAccount{}, acc)
require.Equal(t, &authAcc, acc.(*auth.BaseAccount))

vacc := auth.NewContinuousVestingAccount(
&authAcc, time.Now().Unix(), time.Now().Add(24*time.Hour).Unix(),
)
genAcc = NewGenesisAccountI(vacc)
acc = genAcc.ToAccount()
require.IsType(t, &auth.ContinuousVestingAccount{}, acc)
require.Equal(t, vacc, acc.(*auth.ContinuousVestingAccount))
}

func TestGaiaAppGenTx(t *testing.T) {
Expand Down
46 changes: 39 additions & 7 deletions cmd/gaia/app/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func init() {
flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions")
}

func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage {
var genesisAccounts []GenesisAccount

amount := int64(r.Intn(1e6))
Expand All @@ -67,13 +67,44 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
"\t{amount of steak per account: %v, initially bonded validators: %v}\n",
amount, numInitiallyBonded)

// Randomly generate some genesis accounts
for _, acc := range accs {
// randomly generate some genesis accounts
for i, acc := range accs {
coins := sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))}
genesisAccounts = append(genesisAccounts, GenesisAccount{
Address: acc.Address,
Coins: coins,
})
bacc := auth.NewBaseAccountWithAddress(acc.Address)
bacc.SetCoins(coins)

var gacc GenesisAccount

// Only consider making a vesting account once the initial bonded validator
// set is exhausted due to needing to track DelegatedVesting.
if int64(i) > numInitiallyBonded && r.Intn(100) < 50 {
var (
vacc auth.VestingAccount
endTime int
)

startTime := genesisTimestamp.Unix()

// Allow for some vesting accounts to vest very quickly while others very
// slowly.
if r.Intn(100) < 50 {
endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*24*30)))
} else {
endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*12)))
}

if r.Intn(100) < 50 {
vacc = auth.NewContinuousVestingAccount(&bacc, startTime, int64(endTime))
} else {
vacc = auth.NewDelayedVestingAccount(&bacc, int64(endTime))
}

gacc = NewGenesisAccountI(vacc)
} else {
gacc = NewGenesisAccount(&bacc)
}

genesisAccounts = append(genesisAccounts, gacc)
}

authGenesis := auth.GenesisState{
Expand Down Expand Up @@ -156,6 +187,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
validators = append(validators, validator)
delegations = append(delegations, delegation)
}

stakingGenesis.Pool.LooseTokens = sdk.NewInt((amount * numAccs) + (numInitiallyBonded * amount))
stakingGenesis.Validators = validators
stakingGenesis.Bonds = delegations
Expand Down
77 changes: 35 additions & 42 deletions docs/spec/auth/vesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- [Undelegating](#undelegating)
- [Keepers/Handlers](#keepershandlers-2)
- [Keepers & Handlers](#keepers--handlers)
- [Initializing at Genesis](#initializing-at-genesis)
- [Genesis Initialization](#genesis-initialization)
- [Examples](#examples)
- [Simple](#simple)
- [Slashing](#slashing)
Expand Down Expand Up @@ -45,13 +45,16 @@ order to make such a distinction.
type VestingAccount interface {
Account

GetVestedCoins(Time) Coins
GetVestedCoins(Time) Coins
GetVestingCoins(Time) Coins

// Delegation and undelegation accounting that returns the resulting base
// coins amount.
TrackDelegation(Time, Coins)
TrackUndelegation(Coins)

GetStartTime() int64
GetEndTime() int64
}

// BaseVestingAccount implements the VestingAccount interface. It contains all
Expand All @@ -63,15 +66,15 @@ type BaseVestingAccount struct {
DelegatedFree Coins // coins that are vested and delegated
DelegatedVesting Coins // coins that vesting and delegated

EndTime Time // when the coins become unlocked
EndTime int64 // when the coins become unlocked
}

// ContinuousVestingAccount implements the VestingAccount interface. It
// continuously vests by unlocking coins linearly with respect to time.
type ContinuousVestingAccount struct {
BaseVestingAccount

StartTime Time // when the coins start to vest
StartTime int64 // when the coins start to vest
}

// DelayedVestingAccount implements the VestingAccount interface. It vests all
Expand Down Expand Up @@ -127,11 +130,13 @@ is _vesting_.

```go
func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins {
// We must handle the case where the start time for a vesting account has
// been set into the future or when the start of the chain is not exactly
// known.
if t <= va.StartTime {
if t <= cva.StartTime {
// We must handle the case where the start time for a vesting account has
// been set into the future or when the start of the chain is not exactly
// known.
return ZeroCoins
} else if t >= cva.EndTime {
return cva.OriginalVesting
}

x := t - cva.StartTime
Expand Down Expand Up @@ -299,50 +304,38 @@ unlocked coin amount.

See the above specification for full implementation details.

## Initializing at Genesis
## Genesis Initialization

To initialize both vesting and base accounts, the `GenesisAccount` struct will
include an `EndTime`. Accounts meant to be of type `BaseAccount` will
have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into
BaseAccounts and VestingAccounts as appropriate.
To initialize both vesting and non-vesting accounts, the `GenesisAccount` struct will
include new fields: `Vesting`, `StartTime`, and `EndTime`. Accounts meant to be
of type `BaseAccount` or any non-vesting type will have `Vesting = false`. The
genesis initialization logic (e.g. `initFromGenesisState`) will have to parse
and return the correct accounts accordingly based off of these new fields.

```go
type GenesisAccount struct {
Address sdk.AccAddress
GenesisCoins sdk.Coins
EndTime int64
StartTime int64
// ...

Vesting bool
EndTime int64
StartTime int64
}

func initChainer() {
for genAcc in GenesisAccounts {
baseAccount := BaseAccount{
Address: genAcc.Address,
Coins: genAcc.GenesisCoins,
}
func ToAccount(gacc GenesisAccount) Account {
bacc := NewBaseAccount(gacc)

if genAcc.StartTime != 0 && genAcc.EndTime != 0 {
vestingAccount := ContinuousVestingAccount{
BaseAccount: baseAccount,
OriginalVesting: genAcc.GenesisCoins,
StartTime: RequestInitChain.Time,
StartTime: genAcc.StartTime,
EndTime: genAcc.EndTime,
}

AddAccountToState(vestingAccount)
} else if genAcc.EndTime != 0 {
vestingAccount := DelayedVestingAccount{
BaseAccount: baseAccount,
OriginalVesting: genAcc.GenesisCoins,
EndTime: genAcc.EndTime,
}

AddAccountToState(vestingAccount)
if gacc.Vesting {
if ga.StartTime != 0 && ga.EndTime != 0 {
return NewContinuousVestingAccount(bacc, gacc.StartTime, gacc.EndTime)
} else if ga.EndTime != 0 {
return NewDelayedVestingAccount(bacc, gacc.EndTime)
} else {
AddAccountToState(baseAccount)
// invalid genesis vesting account provided
panic()
}
}

return bacc
}
```

Expand Down
Loading

0 comments on commit f2e87ad

Please sign in to comment.