Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Genesis Vesting Accounts & Simulation #3308

Merged
merged 17 commits into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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 {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
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))
}
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
}

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)
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
} else {
AddAccountToState(baseAccount)
// invalid genesis vesting account provided
panic()
}
}

return bacc
}
```

Expand Down
Loading