diff --git a/README.md b/README.md index 2c9743a4..606c1ec8 100644 --- a/README.md +++ b/README.md @@ -1 +1,59 @@ -# _ \ No newline at end of file +

+ +

+

DeFi for Crypto.

+ +
+ +[![Go Report Card](https://goreportcard.com/badge/github.com/kava-labs/kava)](https://goreportcard.com/report/github.com/kava-labs/kava) +[![API Reference](https://godoc.org/github.com/Kava-Labs/kava?status.svg)](https://godoc.org/github.com/Kava-Labs/kava) +[![GitHub](https://img.shields.io/github/license/kava-labs/kava.svg)](https://github.com/Kava-Labs/kava/blob/master/LICENSE.md) +[![Twitter Follow](https://img.shields.io/twitter/follow/kava_labs.svg?label=Follow&style=social)](https://twitter.com/kava_labs) +[![riot.im](https://img.shields.io/badge/riot.im-JOIN%20CHAT-green.svg)](https://riot.im/app/#/room/#kava-validators:matrix.org) + +
+ +
+ +[Telegram](https://t.me/kavalabs) | +[Medium](https://medium.com/kava-labs) | +[Validator Chat](https://riot.im/app/#/room/#kava-validators:matrix.org) + +
+ +## Installing + +This guide assumes you have worked with `cosmos-sdk` blockchains previously. If you are just getting started, great! See the complete guide [here](https://medium.com/kava-labs). + +#### Installing KVD + +``` +git clone https://github.com/Kava-Labs/kava.git +cd kava +go install ./cmd/kvd ./cmd/kvcli +``` + +#### Create a Wallet +``` +kvd init --chain-id=kava-testnet-1 +kvcli keys add +``` + +**Be sure to back up your mnemonic!** + +#### Create a Genesis Transaction +``` +kvd add-genesis-account $(kvcli keys show -a) 1000000kava +kvd gentx --name --amount 1000000kava +``` + +A genesis transaction should be written to `$HOME/.kvd/config/gentx/gentx-.json` + +#### Submit Genesis Transaction +To be included in the genesis file for testnet one, post your genesis transaction as a PR on this repo [here](https://github.com/Kava-Labs/kava/pulls) + +## License + +Copyright © Kava Labs, Inc. All rights reserved. + +Licensed under the [Apache v2 License](LICENSE). \ No newline at end of file diff --git a/app/app.go b/app/app.go index 501717a9..71a82b7c 100644 --- a/app/app.go +++ b/app/app.go @@ -1,11 +1,10 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package app import ( + "fmt" "io" "os" + "sort" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" @@ -15,69 +14,29 @@ import ( bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" - distrclient "github.com/cosmos/cosmos-sdk/x/distribution/client" - "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" - paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" ) -const appName = "KavaApp" - -var ( - // default home directories for cli - DefaultCLIHome = os.ExpandEnv("$HOME/.kvcli") - - // default home directories for daemon - DefaultNodeHome = os.ExpandEnv("$HOME/.kvd") - - // The ModuleBasicManager is in charge of setting up basic, - // non-dependant module elements, such as codec registration - // and genesis verification. - ModuleBasics module.BasicManager +const ( + appName = "kava" + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" + Bech32MainPrefix = "k" ) -func init() { - ModuleBasics = module.NewBasicManager( - genaccounts.AppModuleBasic{}, - genutil.AppModuleBasic{}, - auth.AppModuleBasic{}, - bank.AppModuleBasic{}, - staking.AppModuleBasic{}, - mint.AppModuleBasic{}, - distr.AppModuleBasic{}, - gov.NewAppModuleBasic(paramsclient.ProposalHandler, distrclient.ProposalHandler), - params.AppModuleBasic{}, - crisis.AppModuleBasic{}, - slashing.AppModuleBasic{}, - ) -} - -// custom tx codec -func MakeCodec() *codec.Codec { - var cdc = codec.New() - ModuleBasics.RegisterCodec(cdc) - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - return cdc -} - -// SetAddressPrefixes sets the bech32 address prefixes on an sdk config. -func SetAddressPrefixes(config *sdk.Config) { - config.SetBech32PrefixForAccount("k", "k"+"pub") - config.SetBech32PrefixForValidator("k"+"val"+"oper", "k"+"val"+"oper"+"pub") - config.SetBech32PrefixForConsensusNode("k"+"val"+"cons", "k"+"val"+"cons"+"pub") -} +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.kvcli") + DefaultNodeHome = os.ExpandEnv("$HOME/.kvd") +) // Extended ABCI application type App struct { @@ -100,7 +59,7 @@ type App struct { keyParams *sdk.KVStoreKey tkeyParams *sdk.TransientStoreKey - // keepers + // Manage getting and setting accounts accountKeeper auth.AccountKeeper feeCollectionKeeper auth.FeeCollectionKeeper bankKeeper bank.Keeper @@ -111,20 +70,17 @@ type App struct { govKeeper gov.Keeper crisisKeeper crisis.Keeper paramsKeeper params.Keeper - - // the module manager - mm *module.Manager } // NewApp returns a reference to an initialized App. func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, - invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp)) *App { + invCheckPeriod uint, + baseAppOptions ...func(*bam.BaseApp)) *App { cdc := MakeCodec() bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) - bApp.SetAppVersion(version.Version) var app = &App{ BaseApp: bApp, @@ -144,82 +100,99 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), } - // init params keeper and subspaces - app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace) - authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) - bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) - stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) - mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) - distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) - slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) - govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace) - crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) + app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams) - // add keepers - app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.keyAccount, authSubspace, auth.ProtoBaseAccount) - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace) - app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) - stakingKeeper := staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, - stakingSubspace, staking.DefaultCodespace) - app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, mintSubspace, &stakingKeeper, app.feeCollectionKeeper) - app.distrKeeper = distr.NewKeeper(app.cdc, app.keyDistr, distrSubspace, app.bankKeeper, &stakingKeeper, - app.feeCollectionKeeper, distr.DefaultCodespace) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, &stakingKeeper, - slashingSubspace, slashing.DefaultCodespace) - app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.distrKeeper, - app.bankKeeper, app.feeCollectionKeeper) - - // register the proposal types - govRouter := gov.NewRouter() - govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler). - AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)). - AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)) - app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper, govSubspace, - app.bankKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter) - - // register the staking hooks - // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks - app.stakingKeeper = *stakingKeeper.SetHooks( - staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks())) - - app.mm = module.NewManager( - genaccounts.NewAppModule(app.accountKeeper), - genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), - auth.NewAppModule(app.accountKeeper, app.feeCollectionKeeper), - bank.NewAppModule(app.bankKeeper, app.accountKeeper), - crisis.NewAppModule(app.crisisKeeper, app.Logger()), - distr.NewAppModule(app.distrKeeper), - gov.NewAppModule(app.govKeeper), - mint.NewAppModule(app.mintKeeper), - slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), - staking.NewAppModule(app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), + // define the accountKeeper + app.accountKeeper = auth.NewAccountKeeper( + app.cdc, + app.keyAccount, + app.paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, ) - // During begin block slashing happens after distr.BeginBlocker so that - // there is nothing left over in the validator fee pool, so as to keep the - // CanWithdrawInvariant invariant. - app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName) + // add handlers + app.bankKeeper = bank.NewBaseKeeper( + app.accountKeeper, + app.paramsKeeper.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + ) + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.cdc, + app.keyFeeCollection, + ) + stakingKeeper := staking.NewKeeper( + app.cdc, + app.keyStaking, app.tkeyStaking, + app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), + staking.DefaultCodespace, + ) + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, + app.paramsKeeper.Subspace(mint.DefaultParamspace), + &stakingKeeper, app.feeCollectionKeeper, + ) + app.distrKeeper = distr.NewKeeper( + app.cdc, + app.keyDistr, + app.paramsKeeper.Subspace(distr.DefaultParamspace), + app.bankKeeper, &stakingKeeper, app.feeCollectionKeeper, + distr.DefaultCodespace, + ) + app.slashingKeeper = slashing.NewKeeper( + app.cdc, + app.keySlashing, + &stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), + slashing.DefaultCodespace, + ) + app.govKeeper = gov.NewKeeper( + app.cdc, + app.keyGov, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakingKeeper, + gov.DefaultCodespace, + ) + app.crisisKeeper = crisis.NewKeeper( + app.paramsKeeper.Subspace(crisis.DefaultParamspace), + app.distrKeeper, + app.bankKeeper, + app.feeCollectionKeeper, + ) - app.mm.SetOrderEndBlockers(gov.ModuleName, staking.ModuleName) + // register the staking hooks + // NOTE: The stakingKeeper above is passed by reference, so that it can be + // modified like below: + app.stakingKeeper = *stakingKeeper.SetHooks( + NewStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()), + ) - // genutils must occur after staking so that pools are properly - // initialized with tokens from genesis accounts. - app.mm.SetOrderInitGenesis(genaccounts.ModuleName, distr.ModuleName, - staking.ModuleName, auth.ModuleName, bank.ModuleName, slashing.ModuleName, - gov.ModuleName, mint.ModuleName, crisis.ModuleName, genutil.ModuleName) + // register the crisis routes + bank.RegisterInvariants(&app.crisisKeeper, app.accountKeeper) + distr.RegisterInvariants(&app.crisisKeeper, app.distrKeeper, app.stakingKeeper) + staking.RegisterInvariants(&app.crisisKeeper, app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper) - app.mm.RegisterInvariants(&app.crisisKeeper) - app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) + // register message routes + app.Router(). + AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)). + AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)). + AddRoute(distr.RouterKey, distr.NewHandler(app.distrKeeper)). + AddRoute(slashing.RouterKey, slashing.NewHandler(app.slashingKeeper)). + AddRoute(gov.RouterKey, gov.NewHandler(app.govKeeper)). + AddRoute(crisis.RouterKey, crisis.NewHandler(app.crisisKeeper)) - // initialize stores - app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, - app.keyDistr, app.keySlashing, app.keyGov, app.keyFeeCollection, - app.keyParams, app.tkeyParams, app.tkeyStaking, app.tkeyDistr) + app.QueryRouter(). + AddRoute(auth.QuerierRoute, auth.NewQuerier(app.accountKeeper)). + AddRoute(distr.QuerierRoute, distr.NewQuerier(app.distrKeeper)). + AddRoute(gov.QuerierRoute, gov.NewQuerier(app.govKeeper)). + AddRoute(slashing.QuerierRoute, slashing.NewQuerier(app.slashingKeeper, app.cdc)). + AddRoute(staking.QuerierRoute, staking.NewQuerier(app.stakingKeeper, app.cdc)). + AddRoute(mint.QuerierRoute, mint.NewQuerier(app.mintKeeper)) // initialize BaseApp - app.SetInitChainer(app.InitChainer) + app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr, + app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams, + app.tkeyParams, app.tkeyStaking, app.tkeyDistr, + ) + app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) - app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper, auth.DefaultSigVerificationGasConsumer)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper)) app.SetEndBlocker(app.EndBlocker) if loadLatest { @@ -228,27 +201,209 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, cmn.Exit(err.Error()) } } + return app } -// application updates every begin block -func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - return app.mm.BeginBlock(ctx, req) +// custom tx codec +func MakeCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + distr.RegisterCodec(cdc) + slashing.RegisterCodec(cdc) + gov.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + crisis.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc } // application updates every end block -func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return app.mm.EndBlock(ctx, req) +func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + // mint new tokens for the previous block + mint.BeginBlocker(ctx, app.mintKeeper) + + // distribute rewards for the previous block + distr.BeginBlocker(ctx, req, app.distrKeeper) + + // slash anyone who double signed. + // NOTE: This should happen after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, + // so as to keep the CanWithdrawInvariant invariant. + // TODO: This should really happen at EndBlocker. + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } } -// application update at chain initialization -func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { +// application updates every end block +// nolint: unparam +func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + tags := gov.EndBlocker(ctx, app.govKeeper) + validatorUpdates, endBlockerTags := staking.EndBlocker(ctx, app.stakingKeeper) + tags = append(tags, endBlockerTags...) + + if app.invCheckPeriod != 0 && ctx.BlockHeight()%int64(app.invCheckPeriod) == 0 { + app.assertRuntimeInvariants() + } + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Tags: tags, + } +} + +// initialize store from a genesis state +func (app *App) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { + genesisState.Sanitize() + + // load the accounts + for _, gacc := range genesisState.Accounts { + acc := gacc.ToAccount() + acc = app.accountKeeper.NewAccount(ctx, acc) // set account number + app.accountKeeper.SetAccount(ctx, acc) + } + + // initialize distribution (must happen before staking) + distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) + + // load the initial staking information + validators, err := staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData) + if err != nil { + panic(err) // TODO find a way to do this w/o panics + } + + // initialize module-specific stores + auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData) + bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData) + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData.Validators.ToSDKValidators()) + gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + crisis.InitGenesis(ctx, app.crisisKeeper, genesisState.CrisisData) + mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) + + // validate genesis state + if err := ValidateGenesisState(genesisState); err != nil { + panic(err) // TODO find a way to do this w/o panics + } + + if len(genesisState.GenTxs) > 0 { + for _, genTx := range genesisState.GenTxs { + var tx auth.StdTx + err = app.cdc.UnmarshalJSON(genTx, &tx) + if err != nil { + panic(err) + } + bz := app.cdc.MustMarshalBinaryLengthPrefixed(tx) + res := app.BaseApp.DeliverTx(bz) + if !res.IsOK() { + panic(res.Log) + } + } + + validators = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + } + return validators +} + +// custom logic for app initialization +func (app *App) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + var genesisState GenesisState - app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState) - return app.mm.InitGenesis(ctx, genesisState) + err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + validators := app.initFromGenesisState(ctx, genesisState) + + // sanity check + if len(req.Validators) > 0 { + if len(req.Validators) != len(validators) { + panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d)", + len(req.Validators), len(validators))) + } + sort.Sort(abci.ValidatorUpdates(req.Validators)) + sort.Sort(abci.ValidatorUpdates(validators)) + for i, val := range validators { + if !val.Equal(req.Validators[i]) { + panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i)) + } + } + } + + // assert runtime invariants + app.assertRuntimeInvariants() + + return abci.ResponseInitChain{ + Validators: validators, + } } // load a particular height func (app *App) LoadHeight(height int64) error { return app.LoadVersion(height, app.keyMain) } + +// ______________________________________________________________________________________________ + +var _ sdk.StakingHooks = StakingHooks{} + +// StakingHooks contains combined distribution and slashing hooks needed for the +// staking module. +type StakingHooks struct { + dh distr.Hooks + sh slashing.Hooks +} + +func NewStakingHooks(dh distr.Hooks, sh slashing.Hooks) StakingHooks { + return StakingHooks{dh, sh} +} + +// nolint +func (h StakingHooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.AfterValidatorCreated(ctx, valAddr) + h.sh.AfterValidatorCreated(ctx, valAddr) +} +func (h StakingHooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.BeforeValidatorModified(ctx, valAddr) + h.sh.BeforeValidatorModified(ctx, valAddr) +} +func (h StakingHooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorRemoved(ctx, consAddr, valAddr) + h.sh.AfterValidatorRemoved(ctx, consAddr, valAddr) +} +func (h StakingHooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorBonded(ctx, consAddr, valAddr) + h.sh.AfterValidatorBonded(ctx, consAddr, valAddr) +} +func (h StakingHooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) + h.sh.AfterValidatorBeginUnbonding(ctx, consAddr, valAddr) +} +func (h StakingHooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationCreated(ctx, delAddr, valAddr) + h.sh.BeforeDelegationCreated(ctx, delAddr, valAddr) +} +func (h StakingHooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationSharesModified(ctx, delAddr, valAddr) + h.sh.BeforeDelegationSharesModified(ctx, delAddr, valAddr) +} +func (h StakingHooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.BeforeDelegationRemoved(ctx, delAddr, valAddr) + h.sh.BeforeDelegationRemoved(ctx, delAddr, valAddr) +} +func (h StakingHooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.AfterDelegationModified(ctx, delAddr, valAddr) + h.sh.AfterDelegationModified(ctx, delAddr, valAddr) +} +func (h StakingHooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { + h.dh.BeforeValidatorSlashed(ctx, valAddr, fraction) + h.sh.BeforeValidatorSlashed(ctx, valAddr, fraction) +} diff --git a/app/app_test.go b/app/app_test.go new file mode 100644 index 00000000..d2b0c890 --- /dev/null +++ b/app/app_test.go @@ -0,0 +1,65 @@ +package app + +import ( + "os" + "testing" + + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + + abci "github.com/tendermint/tendermint/abci/types" +) + +func setGenesis(app *App, accs ...*auth.BaseAccount) error { + genaccs := make([]GenesisAccount, len(accs)) + for i, acc := range accs { + genaccs[i] = NewGenesisAccount(acc) + } + + genesisState := NewGenesisState( + genaccs, + auth.DefaultGenesisState(), + bank.DefaultGenesisState(), + staking.DefaultGenesisState(), + mint.DefaultGenesisState(), + distr.DefaultGenesisState(), + gov.DefaultGenesisState(), + crisis.DefaultGenesisState(), + slashing.DefaultGenesisState(), + ) + + stateBytes, err := codec.MarshalJSONIndent(app.cdc, genesisState) + if err != nil { + return err + } + + // Initialize the chain + vals := []abci.ValidatorUpdate{} + app.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) + app.Commit() + + return nil +} + +func TestExport(t *testing.T) { + db := db.NewMemDB() + app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + setGenesis(app) + + // Making a new app object with the db, so that initchain hasn't been called + newApp := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + _, _, err := newApp.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err, "ExportAppStateAndValidators should not have an error") +} diff --git a/app/benchmarks/txsize_test.go b/app/benchmarks/txsize_test.go new file mode 100644 index 00000000..0d4e4f13 --- /dev/null +++ b/app/benchmarks/txsize_test.go @@ -0,0 +1,40 @@ +package app + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto/secp256k1" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/kava-labs/kava/app" +) + +// This will fail half the time with the second output being 173 +// This is due to secp256k1 signatures not being constant size. +// nolint: vet +func ExampleTxSendSize() { + cdc := app.MakeCodec() + var gas uint64 = 1 + + priv1 := secp256k1.GenPrivKeySecp256k1([]byte{0}) + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewCoin("denom", sdk.NewInt(10))} + msg1 := bank.MsgMultiSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } + fee := auth.NewStdFee(gas, coins) + signBytes := auth.StdSignBytes("example-chain-ID", + 1, 1, fee, []sdk.Msg{msg1}, "") + sig, _ := priv1.Sign(signBytes) + sigs := []auth.StdSignature{{nil, sig}} + tx := auth.NewStdTx([]sdk.Msg{msg1}, fee, sigs, "") + fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1}))) + fmt.Println(len(cdc.MustMarshalBinaryBare(tx))) + // output: 80 + // 169 +} diff --git a/app/export.go b/app/export.go index 9cce8f60..d8dfe017 100644 --- a/app/export.go +++ b/app/export.go @@ -1,6 +1,3 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package app import ( @@ -12,13 +9,19 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" ) // export the state of the app for a genesis file -func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string, -) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { +func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) ( + appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { // as if they could withdraw from the start of the next block ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) @@ -27,7 +30,26 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList [] app.prepForZeroHeightGenesis(ctx, jailWhiteList) } - genState := app.mm.ExportGenesis(ctx) + // iterate to get the accounts + accounts := []GenesisAccount{} + appendAccount := func(acc auth.Account) (stop bool) { + account := NewGenesisAccountI(acc) + accounts = append(accounts, account) + return false + } + app.accountKeeper.IterateAccounts(ctx, appendAccount) + + genState := NewGenesisState( + accounts, + auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper), + bank.ExportGenesis(ctx, app.bankKeeper), + staking.ExportGenesis(ctx, app.stakingKeeper), + mint.ExportGenesis(ctx, app.mintKeeper), + distr.ExportGenesis(ctx, app.distrKeeper), + gov.ExportGenesis(ctx, app.govKeeper), + crisis.ExportGenesis(ctx, app.crisisKeeper), + slashing.ExportGenesis(ctx, app.slashingKeeper), + ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err @@ -37,8 +59,6 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList [] } // prepare for fresh start at zero height -// NOTE zero height genesis is a temporary feature which will be deprecated -// in favour of export at a block height func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) { applyWhiteList := false @@ -58,12 +78,12 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string } /* Just to be safe, assert the invariants on current state. */ - app.crisisKeeper.AssertInvariants(ctx, app.Logger()) + app.assertRuntimeInvariantsOnContext(ctx) /* Handle fee distribution state. */ // withdraw all validator commission - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val staking.ValidatorI) (stop bool) { + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) return false }) @@ -85,7 +105,7 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string ctx = ctx.WithBlockHeight(0) // reinitialize all validators - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val staking.ValidatorI) (stop bool) { + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { // donate any unwithdrawn outstanding reward fraction tokens to the community pool scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) diff --git a/app/genesis.go b/app/genesis.go index 228bae6d..572c4a32 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -1,22 +1,417 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package app import ( "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "time" + + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" ) -// The genesis state of the blockchain is represented here as a map of raw json -// messages key'd by a identifier string. -// The identifier is used to determine which module genesis information belongs -// to so it may be appropriately routed during init chain. -// Within this application default genesis information is retrieved from -// the ModuleBasicManager which populates json from each BasicModule -// object provided to it during init. -type GenesisState map[string]json.RawMessage +var ( + // bonded tokens given to genesis validators/accounts + freeTokensPerAcc = sdk.TokensFromTendermintPower(150) + defaultBondDenom = sdk.DefaultBondDenom +) + +// State to Unmarshal +type GenesisState struct { + Accounts []GenesisAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` + BankData bank.GenesisState `json:"bank"` + StakingData staking.GenesisState `json:"staking"` + MintData mint.GenesisState `json:"mint"` + DistrData distr.GenesisState `json:"distr"` + GovData gov.GenesisState `json:"gov"` + CrisisData crisis.GenesisState `json:"crisis"` + SlashingData slashing.GenesisState `json:"slashing"` + GenTxs []json.RawMessage `json:"gentxs"` +} + +func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, + bankData bank.GenesisState, + stakingData staking.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, crisisData crisis.GenesisState, + slashingData slashing.GenesisState) GenesisState { + + return GenesisState{ + Accounts: accounts, + AuthData: authData, + BankData: bankData, + StakingData: stakingData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + CrisisData: crisisData, + SlashingData: slashingData, + } +} + +// Sanitize sorts accounts and coin sets. +func (gs GenesisState) Sanitize() { + sort.Slice(gs.Accounts, func(i, j int) bool { + return gs.Accounts[i].AccountNumber < gs.Accounts[j].AccountNumber + }) + + for _, acc := range gs.Accounts { + acc.Coins = acc.Coins.Sort() + } +} + +// 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 account fields + OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time) +} + +func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { + return GenesisAccount{ + Address: acc.Address, + Coins: acc.Coins, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, + } +} + +func NewGenesisAccountI(acc auth.Account) GenesisAccount { + gacc := GenesisAccount{ + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), + } + + vacc, ok := acc.(auth.VestingAccount) + if ok { + gacc.OriginalVesting = vacc.GetOriginalVesting() + gacc.DelegatedFree = vacc.GetDelegatedFree() + gacc.DelegatedVesting = vacc.GetDelegatedVesting() + gacc.StartTime = vacc.GetStartTime() + gacc.EndTime = vacc.GetEndTime() + } + + return gacc +} + +// convert GenesisAccount to 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.OriginalVesting.IsZero() { + baseVestingAcc := &auth.BaseVestingAccount{ + BaseAccount: bacc, + OriginalVesting: ga.OriginalVesting, + DelegatedFree: ga.DelegatedFree, + DelegatedVesting: ga.DelegatedVesting, + EndTime: ga.EndTime, + } + + if ga.StartTime != 0 && ga.EndTime != 0 { + return &auth.ContinuousVestingAccount{ + BaseVestingAccount: baseVestingAcc, + StartTime: ga.StartTime, + } + } else if ga.EndTime != 0 { + return &auth.DelayedVestingAccount{ + BaseVestingAccount: baseVestingAcc, + } + } else { + panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga)) + } + } + + return bacc +} + +// Create the core parameters for genesis initialization for gaia +// note that the pubkey input is this machines pubkey +func AppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + genesisState GenesisState, err error) { + + if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return genesisState, err + } + + // if there are no gen txs to be processed, return the default empty state + if len(appGenTxs) == 0 { + return genesisState, errors.New("there must be at least one genesis tx") + } + + stakingData := genesisState.StakingData + for i, genTx := range appGenTxs { + var tx auth.StdTx + 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 { + for _, coin := range acc.Coins { + if coin.Denom == genesisState.StakingData.Params.BondDenom { + stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens. + Add(coin.Amount) // increase the supply + } + } + } + + genesisState.StakingData = stakingData + genesisState.GenTxs = appGenTxs + + return genesisState, nil +} // NewDefaultGenesisState generates the default state for gaia. func NewDefaultGenesisState() GenesisState { - return ModuleBasics.DefaultGenesis() + return GenesisState{ + Accounts: nil, + AuthData: auth.DefaultGenesisState(), + BankData: bank.DefaultGenesisState(), + StakingData: staking.DefaultGenesisState(), + MintData: mint.DefaultGenesisState(), + DistrData: distr.DefaultGenesisState(), + GovData: gov.DefaultGenesisState(), + CrisisData: crisis.DefaultGenesisState(), + SlashingData: slashing.DefaultGenesisState(), + GenTxs: nil, + } +} + +// ValidateGenesisState ensures that the genesis state obeys the expected invariants +// TODO: No validators are both bonded and jailed (#2088) +// TODO: Error if there is a duplicate validator (#1708) +// TODO: Ensure all state machine parameters are in genesis (#1704) +func ValidateGenesisState(genesisState GenesisState) error { + if err := validateGenesisStateAccounts(genesisState.Accounts); err != nil { + return err + } + + // skip stakingData validation as genesis is created from txs + if len(genesisState.GenTxs) > 0 { + return nil + } + + if err := auth.ValidateGenesis(genesisState.AuthData); err != nil { + return err + } + if err := bank.ValidateGenesis(genesisState.BankData); err != nil { + return err + } + if err := staking.ValidateGenesis(genesisState.StakingData); err != nil { + return err + } + if err := mint.ValidateGenesis(genesisState.MintData); err != nil { + return err + } + if err := distr.ValidateGenesis(genesisState.DistrData); err != nil { + return err + } + if err := gov.ValidateGenesis(genesisState.GovData); err != nil { + return err + } + if err := crisis.ValidateGenesis(genesisState.CrisisData); err != nil { + return err + } + + return slashing.ValidateGenesis(genesisState.SlashingData) +} + +// validateGenesisStateAccounts performs validation of genesis accounts. It +// ensures that there are no duplicate accounts in the genesis state and any +// provided vesting accounts are valid. +func validateGenesisStateAccounts(accs []GenesisAccount) error { + addrMap := make(map[string]bool, len(accs)) + for _, acc := range accs { + addrStr := acc.Address.String() + + // disallow any duplicate accounts + if _, ok := addrMap[addrStr]; ok { + return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) + } + + // validate any vesting fields + if !acc.OriginalVesting.IsZero() { + if acc.EndTime == 0 { + return fmt.Errorf("missing end time for vesting account; address: %s", addrStr) + } + + if acc.StartTime >= acc.EndTime { + return fmt.Errorf( + "vesting start time must before end time; address: %s, start: %s, end: %s", + addrStr, + time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339), + time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339), + ) + } + } + + addrMap[addrStr] = true + } + + return nil +} + +// AppGenState but with JSON +func AppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { + // create the final app state + genesisState, err := AppGenState(cdc, genDoc, appGenTxs) + if err != nil { + return nil, err + } + return codec.MarshalJSONIndent(cdc, genesisState) +} + +// CollectStdTxs processes and validates application's genesis StdTxs and returns +// the list of appGenTxs, and persistent peers required to generate genesis.json. +func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tmtypes.GenesisDoc) ( + appGenTxs []auth.StdTx, persistentPeers string, err error) { + + var fos []os.FileInfo + fos, err = ioutil.ReadDir(genTxsDir) + if err != nil { + return appGenTxs, persistentPeers, err + } + + // prepare a map of all accounts in genesis state to then validate + // against the validators addresses + var appState GenesisState + if err := cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { + return appGenTxs, persistentPeers, err + } + + addrMap := make(map[string]GenesisAccount, len(appState.Accounts)) + for i := 0; i < len(appState.Accounts); i++ { + acc := appState.Accounts[i] + addrMap[acc.Address.String()] = acc + } + + // addresses and IPs (and port) validator server info + var addressesIPs []string + + for _, fo := range fos { + filename := filepath.Join(genTxsDir, fo.Name()) + if !fo.IsDir() && (filepath.Ext(filename) != ".json") { + continue + } + + // get the genStdTx + var jsonRawTx []byte + if jsonRawTx, err = ioutil.ReadFile(filename); err != nil { + return appGenTxs, persistentPeers, err + } + var genStdTx auth.StdTx + if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil { + return appGenTxs, persistentPeers, err + } + appGenTxs = append(appGenTxs, genStdTx) + + // the memo flag is used to store + // the ip and node-id, for example this may be: + // "528fd3df22b31f4969b05652bfe8f0fe921321d5@192.168.2.37:26656" + nodeAddrIP := genStdTx.GetMemo() + if len(nodeAddrIP) == 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "couldn't find node's address and IP in %s", fo.Name()) + } + + // genesis transactions must be single-message + msgs := genStdTx.GetMsgs() + if len(msgs) != 1 { + + return appGenTxs, persistentPeers, errors.New( + "each genesis transaction must provide a single genesis message") + } + + msg := msgs[0].(staking.MsgCreateValidator) + // validate delegator and validator addresses and funds against the accounts in the state + delAddr := msg.DelegatorAddress.String() + valAddr := sdk.AccAddress(msg.ValidatorAddress).String() + + delAcc, delOk := addrMap[delAddr] + _, valOk := addrMap[valAddr] + + accsNotInGenesis := []string{} + if !delOk { + accsNotInGenesis = append(accsNotInGenesis, delAddr) + } + if !valOk { + accsNotInGenesis = append(accsNotInGenesis, valAddr) + } + if len(accsNotInGenesis) != 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "account(s) %v not in genesis.json: %+v", strings.Join(accsNotInGenesis, " "), addrMap) + } + + if delAcc.Coins.AmountOf(msg.Value.Denom).LT(msg.Value.Amount) { + return appGenTxs, persistentPeers, fmt.Errorf( + "insufficient fund for delegation %v: %v < %v", + delAcc.Address, delAcc.Coins.AmountOf(msg.Value.Denom), msg.Value.Amount, + ) + } + + // exclude itself from persistent peers + if msg.Description.Moniker != moniker { + addressesIPs = append(addressesIPs, nodeAddrIP) + } + } + + sort.Strings(addressesIPs) + persistentPeers = strings.Join(addressesIPs, ",") + + return appGenTxs, persistentPeers, nil +} + +func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { + accAuth := auth.NewBaseAccountWithAddress(addr) + coins := sdk.Coins{ + sdk.NewCoin("footoken", sdk.NewInt(1000)), + sdk.NewCoin(defaultBondDenom, freeTokensPerAcc), + } + + coins.Sort() + + accAuth.Coins = coins + return NewGenesisAccount(&accAuth) } diff --git a/app/genesis_test.go b/app/genesis_test.go new file mode 100644 index 00000000..e16f5299 --- /dev/null +++ b/app/genesis_test.go @@ -0,0 +1,188 @@ +package app + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +var ( + pk1 = ed25519.GenPrivKey().PubKey() + pk2 = ed25519.GenPrivKey().PubKey() + pk3 = ed25519.GenPrivKey().PubKey() + addr1 = sdk.ValAddress(pk1.Address()) + addr2 = sdk.ValAddress(pk2.Address()) + addr3 = sdk.ValAddress(pk3.Address()) + + emptyAddr sdk.ValAddress + emptyPubkey crypto.PubKey +) + +func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { + // start with the default staking genesis state + appState := NewDefaultGenesisState() + stakingData := appState.StakingData + genAccs := make([]GenesisAccount, len(genTxs)) + + for i, genTx := range genTxs { + msgs := genTx.GetMsgs() + require.Equal(t, 1, len(msgs)) + msg := msgs[0].(staking.MsgCreateValidator) + + acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddress)) + acc.Coins = sdk.NewCoins(sdk.NewInt64Coin(defaultBondDenom, 150)) + genAccs[i] = NewGenesisAccount(&acc) + stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens.Add(sdk.NewInt(150)) // increase the supply + } + + // create the final app state + appState.Accounts = genAccs + return appState +} + +func TestToAccount(t *testing.T) { + priv := ed25519.GenPrivKey() + addr := sdk.AccAddress(priv.PubKey().Address()) + authAcc := auth.NewBaseAccountWithAddress(addr) + authAcc.SetCoins(sdk.NewCoins(sdk.NewInt64Coin(defaultBondDenom, 150))) + genAcc := NewGenesisAccount(&authAcc) + 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 TestAppGenTx(t *testing.T) { + cdc := MakeCodec() + _ = cdc + + //TODO test that key overwrite flags work / no overwrites if set off + //TODO test validator created has provided pubkey + //TODO test the account created has the correct pubkey +} + +func TestAppGenState(t *testing.T) { + cdc := MakeCodec() + _ = cdc + var genDoc tmtypes.GenesisDoc + + // test unmarshalling error + _, err := AppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) + + appState := makeGenesisState(t, []auth.StdTx{}) + genDoc.AppState, err = json.Marshal(appState) + require.NoError(t, err) + + // test validation error + _, err = AppGenState(cdc, genDoc, []json.RawMessage{}) + require.Error(t, err) + + // TODO test must provide at least genesis transaction + // TODO test with both one and two genesis transactions: + // TODO correct: genesis account created, canididates created, pool token variance +} + +func makeMsg(name string, pk crypto.PubKey) auth.StdTx { + desc := staking.NewDescription(name, "", "", "") + comm := staking.CommissionMsg{} + msg := staking.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(defaultBondDenom, + 50), desc, comm, sdk.OneInt()) + return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") +} + +func TestGenesisValidation(t *testing.T) { + genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)} + dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)} + + // require duplicate accounts fails validation + genesisState := makeGenesisState(t, dupGenTxs) + err := ValidateGenesisState(genesisState) + require.Error(t, err) + + // require invalid vesting account fails validation (invalid end time) + genesisState = makeGenesisState(t, genTxs) + genesisState.Accounts[0].OriginalVesting = genesisState.Accounts[0].Coins + err = ValidateGenesisState(genesisState) + require.Error(t, err) + genesisState.Accounts[0].StartTime = 1548888000 + genesisState.Accounts[0].EndTime = 1548775410 + err = ValidateGenesisState(genesisState) + require.Error(t, err) + + // require bonded + jailed validator fails validation + genesisState = makeGenesisState(t, genTxs) + val1 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #2", "", "", "")) + val1.Jailed = true + val1.Status = sdk.Bonded + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) + err = ValidateGenesisState(genesisState) + require.Error(t, err) + + // require duplicate validator fails validation + val1.Jailed = false + genesisState = makeGenesisState(t, genTxs) + val2 := staking.NewValidator(addr1, pk1, staking.NewDescription("test #3", "", "", "")) + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) + genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2) + err = ValidateGenesisState(genesisState) + require.Error(t, err) +} + +func TestNewDefaultGenesisAccount(t *testing.T) { + addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address() + acc := NewDefaultGenesisAccount(sdk.AccAddress(addr)) + require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken")) + require.Equal(t, sdk.TokensFromTendermintPower(150), acc.Coins.AmountOf(defaultBondDenom)) +} + +func TestGenesisStateSanitize(t *testing.T) { + genesisState := makeGenesisState(t, nil) + require.Nil(t, ValidateGenesisState(genesisState)) + + addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc1 := auth.NewBaseAccountWithAddress(addr1) + authAcc1.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("bcoin", 150), + sdk.NewInt64Coin("acoin", 150), + }) + authAcc1.SetAccountNumber(1) + genAcc1 := NewGenesisAccount(&authAcc1) + + addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc2 := auth.NewBaseAccountWithAddress(addr2) + authAcc2.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("acoin", 150), + sdk.NewInt64Coin("bcoin", 150), + }) + genAcc2 := NewGenesisAccount(&authAcc2) + + genesisState.Accounts = []GenesisAccount{genAcc1, genAcc2} + require.True(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber) + require.Equal(t, genesisState.Accounts[0].Coins[0].Denom, "bcoin") + require.Equal(t, genesisState.Accounts[0].Coins[1].Denom, "acoin") + require.Equal(t, genesisState.Accounts[1].Address, addr2) + genesisState.Sanitize() + require.False(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber) + require.Equal(t, genesisState.Accounts[1].Address, addr1) + require.Equal(t, genesisState.Accounts[1].Coins[0].Denom, "acoin") + require.Equal(t, genesisState.Accounts[1].Coins[1].Denom, "bcoin") +} diff --git a/app/invariants.go b/app/invariants.go new file mode 100644 index 00000000..6cfbe16a --- /dev/null +++ b/app/invariants.go @@ -0,0 +1,31 @@ +package app + +import ( + "fmt" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (app *App) assertRuntimeInvariants() { + ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1}) + app.assertRuntimeInvariantsOnContext(ctx) +} + +func (app *App) assertRuntimeInvariantsOnContext(ctx sdk.Context) { + start := time.Now() + invarRoutes := app.crisisKeeper.Routes() + for _, ir := range invarRoutes { + if err := ir.Invar(ctx); err != nil { + panic(fmt.Errorf("invariant broken: %s\n"+ + "\tCRITICAL please submit the following transaction:\n"+ + "\t\t kvcli tx crisis invariant-broken %v %v", err, ir.ModuleName, ir.Route)) + } + } + end := time.Now() + diff := end.Sub(start) + app.BaseApp.Logger().With("module", "invariants").Info( + "Asserted all invariants", "duration", diff, "height", app.LastBlockHeight()) +} diff --git a/app/sim_test.go b/app/sim_test.go new file mode 100644 index 00000000..701bb213 --- /dev/null +++ b/app/sim_test.go @@ -0,0 +1,564 @@ +package app + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation" + "github.com/cosmos/cosmos-sdk/x/bank" + banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" + "github.com/cosmos/cosmos-sdk/x/gov" + govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" +) + +var ( + genesisFile string + seed int64 + numBlocks int + blockSize int + enabled bool + verbose bool + lean bool + commit bool + period int +) + +func init() { + flag.StringVar(&genesisFile, "SimulationGenesis", "", "custom simulation genesis file") + flag.Int64Var(&seed, "SimulationSeed", 42, "simulation random seed") + flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "number of blocks") + flag.IntVar(&blockSize, "SimulationBlockSize", 200, "operations per block") + flag.BoolVar(&enabled, "SimulationEnabled", false, "enable the simulation") + flag.BoolVar(&verbose, "SimulationVerbose", false, "verbose log output") + flag.BoolVar(&lean, "SimulationLean", false, "lean simulation log output") + flag.BoolVar(&commit, "SimulationCommit", false, "have the simulation commit") + flag.IntVar(&period, "SimulationPeriod", 1, "run slow invariants only once every period assertions") +} + +// helper function for populating input for SimulateFromSeed +func getSimulateFromSeedInput(tb testing.TB, app *App) ( + testing.TB, *baseapp.BaseApp, simulation.AppStateFn, int64, + simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool) { + + return tb, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, lean +} + +func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + var genesis tmtypes.GenesisDoc + cdc := MakeCodec() + bytes, err := ioutil.ReadFile(genesisFile) + if err != nil { + panic(err) + } + cdc.MustUnmarshalJSON(bytes, &genesis) + var appState GenesisState + cdc.MustUnmarshalJSON(genesis.AppState, &appState) + var newAccs []simulation.Account + for _, acc := range appState.Accounts { + // Pick a random private key, since we don't know the actual key + // This should be fine as it's only used for mock Tendermint validators + // and these keys are never actually used to sign by mock Tendermint. + privkeySeed := make([]byte, 15) + r.Read(privkeySeed) + privKey := secp256k1.GenPrivKeySecp256k1(privkeySeed) + newAccs = append(newAccs, simulation.Account{privKey, privKey.PubKey(), acc.Address}) + } + return genesis.AppState, newAccs, genesis.ChainID +} + +func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + + var genesisAccounts []GenesisAccount + + amount := int64(r.Intn(1e12)) + numInitiallyBonded := int64(r.Intn(250)) + numAccs := int64(len(accs)) + if numInitiallyBonded > numAccs { + numInitiallyBonded = numAccs + } + fmt.Printf("Selected randomly generated parameters for simulated genesis:\n"+ + "\t{amount of stake per account: %v, initially bonded validators: %v}\n", + amount, numInitiallyBonded) + + // randomly generate some genesis accounts + for i, acc := range accs { + coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(amount))} + 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 int64 + ) + + startTime := genesisTimestamp.Unix() + + // Allow for some vesting accounts to vest very quickly while others very + // slowly. + if r.Intn(100) < 50 { + endTime = int64(simulation.RandIntBetween(r, int(startTime), int(startTime+(60*60*24*30)))) + } else { + endTime = int64(simulation.RandIntBetween(r, int(startTime), int(startTime+(60*60*12)))) + } + + if startTime == endTime { + endTime += 1 + } + + if r.Intn(100) < 50 { + vacc = auth.NewContinuousVestingAccount(&bacc, startTime, endTime) + } else { + vacc = auth.NewDelayedVestingAccount(&bacc, endTime) + } + + gacc = NewGenesisAccountI(vacc) + } else { + gacc = NewGenesisAccount(&bacc) + } + + genesisAccounts = append(genesisAccounts, gacc) + } + + authGenesis := auth.GenesisState{ + Params: auth.Params{ + MaxMemoCharacters: uint64(simulation.RandIntBetween(r, 100, 200)), + TxSigLimit: uint64(r.Intn(7) + 1), + TxSizeCostPerByte: uint64(simulation.RandIntBetween(r, 5, 15)), + SigVerifyCostED25519: uint64(simulation.RandIntBetween(r, 500, 1000)), + SigVerifyCostSecp256k1: uint64(simulation.RandIntBetween(r, 500, 1000)), + }, + } + fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis) + + bankGenesis := bank.NewGenesisState(r.Int63n(2) == 0) + fmt.Printf("Selected randomly generated bank parameters:\n\t%+v\n", bankGenesis) + + // Random genesis states + vp := time.Duration(r.Intn(2*172800)) * time.Second + govGenesis := gov.GenesisState{ + StartingProposalID: uint64(r.Intn(100)), + DepositParams: gov.DepositParams{ + MinDeposit: sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(r.Intn(1e3)))}, + MaxDepositPeriod: vp, + }, + VotingParams: gov.VotingParams{ + VotingPeriod: vp, + }, + TallyParams: gov.TallyParams{ + Quorum: sdk.NewDecWithPrec(334, 3), + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), + }, + } + fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis) + + stakingGenesis := staking.GenesisState{ + Pool: staking.InitialPool(), + Params: staking.Params{ + UnbondingTime: time.Duration(simulation.RandIntBetween(r, 60, 60*60*24*3*2)) * time.Second, + MaxValidators: uint16(r.Intn(250) + 1), + BondDenom: sdk.DefaultBondDenom, + }, + } + fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakingGenesis) + + slashingGenesis := slashing.GenesisState{ + Params: slashing.Params{ + MaxEvidenceAge: stakingGenesis.Params.UnbondingTime, + SignedBlocksWindow: int64(simulation.RandIntBetween(r, 10, 1000)), + MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1), + DowntimeJailDuration: time.Duration(simulation.RandIntBetween(r, 60, 60*60*24)) * time.Second, + SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))), + SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))), + }, + } + fmt.Printf("Selected randomly generated slashing parameters:\n\t%+v\n", slashingGenesis) + + mintGenesis := mint.GenesisState{ + Minter: mint.InitialMinter( + sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), + Params: mint.NewParams( + sdk.DefaultBondDenom, + sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + sdk.NewDecWithPrec(20, 2), + sdk.NewDecWithPrec(7, 2), + sdk.NewDecWithPrec(67, 2), + uint64(60*60*8766/5)), + } + fmt.Printf("Selected randomly generated minting parameters:\n\t%+v\n", mintGenesis) + + var validators []staking.Validator + var delegations []staking.Delegation + + valAddrs := make([]sdk.ValAddress, numInitiallyBonded) + for i := 0; i < int(numInitiallyBonded); i++ { + valAddr := sdk.ValAddress(accs[i].Address) + valAddrs[i] = valAddr + + validator := staking.NewValidator(valAddr, accs[i].PubKey, staking.Description{}) + validator.Tokens = sdk.NewInt(amount) + validator.DelegatorShares = sdk.NewDec(amount) + delegation := staking.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount)} + validators = append(validators, validator) + delegations = append(delegations, delegation) + } + + stakingGenesis.Pool.NotBondedTokens = sdk.NewInt((amount * numAccs) + (numInitiallyBonded * amount)) + stakingGenesis.Validators = validators + stakingGenesis.Delegations = delegations + + distrGenesis := distr.GenesisState{ + FeePool: distr.InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BaseProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + BonusProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)), + } + fmt.Printf("Selected randomly generated distribution parameters:\n\t%+v\n", distrGenesis) + + genesis := GenesisState{ + Accounts: genesisAccounts, + AuthData: authGenesis, + BankData: bankGenesis, + StakingData: stakingGenesis, + MintData: mintGenesis, + DistrData: distrGenesis, + SlashingData: slashingGenesis, + GovData: govGenesis, + } + + // Marshal genesis + appState, err := MakeCodec().MarshalJSON(genesis) + if err != nil { + panic(err) + } + + return appState, accs, "simulation" +} + +func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + if genesisFile != "" { + return appStateFromGenesisFileFn(r, accs, genesisTimestamp) + } + return appStateRandomizedFn(r, accs, genesisTimestamp) +} + +func testAndRunTxs(app *App) []simulation.WeightedOperation { + return []simulation.WeightedOperation{ + {5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)}, + {100, banksim.SimulateMsgSend(app.accountKeeper, app.bankKeeper)}, + {10, banksim.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper)}, + {50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)}, + {50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)}, + {50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)}, + {5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper)}, + {100, govsim.SimulateMsgDeposit(app.govKeeper)}, + {100, stakingsim.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper)}, + {5, stakingsim.SimulateMsgEditValidator(app.stakingKeeper)}, + {100, stakingsim.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper)}, + {100, stakingsim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper)}, + {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, + } +} + +func invariants(app *App) []sdk.Invariant { + return []sdk.Invariant{ + simulation.PeriodicInvariant(bank.NonnegativeBalanceInvariant(app.accountKeeper), period, 0), + simulation.PeriodicInvariant(distr.AllInvariants(app.distrKeeper, app.stakingKeeper), period, 0), + simulation.PeriodicInvariant(staking.AllInvariants(app.stakingKeeper, app.feeCollectionKeeper, + app.distrKeeper, app.accountKeeper), period, 0), + } +} + +// Pass this in as an option to use a dbStoreAdapter instead of an IAVLStore for simulation speed. +func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} + +// Profile with: +// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out +func BenchmarkFullGaiaSimulation(b *testing.B) { + // Setup Gaia application + logger := log.NewNopLogger() + + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewApp(logger, db, nil, true, 0) + + // Run randomized simulation + // TODO parameterize numbers, save for a later PR + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(b, app)) + if err != nil { + fmt.Println(err) + b.Fail() + } + if commit { + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } +} + +func TestFullGaiaSimulation(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "kava", app.Name()) + + // Run randomized simulation + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app)) + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) +} + +func TestGaiaImportExport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia import/export simulation") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "kava", app.Name()) + + // Run randomized simulation + _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app)) + + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err) + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "kava", newApp.Name()) + var genesisState GenesisState + err = app.cdc.UnmarshalJSON(appState, &genesisState) + if err != nil { + panic(err) + } + ctxB := newApp.NewContext(true, abci.Header{}) + newApp.initFromGenesisState(ctxB, genesisState) + + fmt.Printf("Comparing stores...\n") + ctxA := app.NewContext(true, abci.Header{}) + type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte + } + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.keyMain, newApp.keyMain, [][]byte{}}, + {app.keyAccount, newApp.keyAccount, [][]byte{}}, + {app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey, + staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter + {app.keySlashing, newApp.keySlashing, [][]byte{}}, + {app.keyMint, newApp.keyMint, [][]byte{}}, + {app.keyDistr, newApp.keyDistr, [][]byte{}}, + {app.keyFeeCollection, newApp.keyFeeCollection, [][]byte{}}, + {app.keyParams, newApp.keyParams, [][]byte{}}, + {app.keyGov, newApp.keyGov, [][]byte{}}, + } + for _, storeKeysPrefix := range storeKeysPrefixes { + storeKeyA := storeKeysPrefix.A + storeKeyB := storeKeysPrefix.B + prefixes := storeKeysPrefix.Prefixes + storeA := ctxA.KVStore(storeKeyA) + storeB := ctxB.KVStore(storeKeyB) + kvA, kvB, count, equal := sdk.DiffKVStores(storeA, storeB, prefixes) + fmt.Printf("Compared %d key/value pairs between %s and %s\n", count, storeKeyA, storeKeyB) + require.True(t, equal, + "unequal stores: %s / %s:\nstore A %X => %X\nstore B %X => %X", + storeKeyA, storeKeyB, kvA.Key, kvA.Value, kvB.Key, kvB.Value, + ) + } + +} + +func TestGaiaSimulationAfterImport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation after import") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ := sdk.NewLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "kava", app.Name()) + + // Run randomized simulation + stopEarly, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, app)) + + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + if stopEarly { + // we can't export or import a zero-validator genesis + fmt.Printf("We can't export or import a zero-validator genesis, exiting test...\n") + return + } + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators(true, []string{}) + if err != nil { + panic(err) + } + + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) + require.Equal(t, "kava", newApp.Name()) + newApp.InitChain(abci.RequestInitChain{ + AppStateBytes: appState, + }) + + // Run randomized simulation on imported app + _, err = simulation.SimulateFromSeed(getSimulateFromSeedInput(t, newApp)) + require.Nil(t, err) + +} + +// TODO: Make another test for the fuzzer itself, which just has noOp txs +// and doesn't depend on gaia +func TestAppStateDeterminism(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation") + } + + numSeeds := 3 + numTimesToRunPerSeed := 5 + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + + for i := 0; i < numSeeds; i++ { + seed := rand.Int63() + for j := 0; j < numTimesToRunPerSeed; j++ { + logger := log.NewNopLogger() + db := dbm.NewMemDB() + app := NewApp(logger, db, nil, true, 0) + + // Run randomized simulation + simulation.SimulateFromSeed( + t, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []sdk.Invariant{}, + 50, + 100, + true, + false, + ) + appHash := app.LastCommitID().Hash + appHashList[j] = appHash + } + for k := 1; k < numTimesToRunPerSeed; k++ { + require.Equal(t, appHashList[0], appHashList[k], "appHash list: %v", appHashList) + } + } +} diff --git a/cmd/kvcli/main.go b/cmd/kvcli/main.go index ddc13ff4..975c9240 100644 --- a/cmd/kvcli/main.go +++ b/cmd/kvcli/main.go @@ -1,6 +1,3 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package main import ( @@ -8,6 +5,14 @@ import ( "os" "path" + "github.com/cosmos/cosmos-sdk/x/mint" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/lcd" @@ -15,18 +20,29 @@ import ( "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/kava-labs/kava/app" + + at "github.com/cosmos/cosmos-sdk/x/auth" + auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" + gv "github.com/cosmos/cosmos-sdk/x/gov" + gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" + mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest" + sl "github.com/cosmos/cosmos-sdk/x/slashing" + slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" + st "github.com/cosmos/cosmos-sdk/x/staking" + staking "github.com/cosmos/cosmos-sdk/x/staking/client/rest" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - "github.com/cosmos/cosmos-sdk/x/bank" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/libs/cli" - - "github.com/kava-labs/_/app" + crisisclient "github.com/cosmos/cosmos-sdk/x/crisis/client" + distcmd "github.com/cosmos/cosmos-sdk/x/distribution" + distClient "github.com/cosmos/cosmos-sdk/x/distribution/client" + govClient "github.com/cosmos/cosmos-sdk/x/gov/client" + mintclient "github.com/cosmos/cosmos-sdk/x/mint/client" + slashingclient "github.com/cosmos/cosmos-sdk/x/slashing/client" + stakingclient "github.com/cosmos/cosmos-sdk/x/staking/client" ) func main() { @@ -38,13 +54,26 @@ func main() { // Read in the configuration file for the sdk config := sdk.GetConfig() - app.SetAddressPrefixes(config) + config.SetBech32PrefixForAccount(app.Bech32MainPrefix, app.Bech32MainPrefix+sdk.PrefixPublic) + config.SetBech32PrefixForValidator(app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixOperator, app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixOperator+sdk.PrefixPublic) + config.SetBech32PrefixForConsensusNode(app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixConsensus, app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixConsensus+sdk.PrefixPublic) config.Seal() // TODO: setup keybase, viper object, etc. to be passed into // the below functions and eliminate global vars, like we do // with the cdc + // Module clients hold cli commnads (tx,query) and lcd routes + // TODO: Make the lcd command take a list of ModuleClient + mc := []sdk.ModuleClients{ + govClient.NewModuleClient(gv.StoreKey, cdc), + distClient.NewModuleClient(distcmd.StoreKey, cdc), + stakingclient.NewModuleClient(st.StoreKey, cdc), + mintclient.NewModuleClient(mint.StoreKey, cdc), + slashingclient.NewModuleClient(sl.StoreKey, cdc), + crisisclient.NewModuleClient(sl.StoreKey, cdc), + } + rootCmd := &cobra.Command{ Use: "kvcli", Short: "Command line interface for interacting with kvd", @@ -60,14 +89,14 @@ func main() { rootCmd.AddCommand( rpc.StatusCommand(), client.ConfigCmd(app.DefaultCLIHome), - queryCmd(cdc), - txCmd(cdc), + queryCmd(cdc, mc), + txCmd(cdc, mc), client.LineBreak, lcd.ServeCommand(cdc, registerRoutes), client.LineBreak, keys.Commands(), client.LineBreak, - version.Cmd, + version.VersionCmd, client.NewCompletionCmd(rootCmd, true), ) @@ -81,7 +110,7 @@ func main() { } } -func queryCmd(cdc *amino.Codec) *cobra.Command { +func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { queryCmd := &cobra.Command{ Use: "query", Aliases: []string{"q"}, @@ -89,22 +118,25 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { } queryCmd.AddCommand( - authcmd.GetAccountCmd(cdc), - client.LineBreak, rpc.ValidatorCommand(cdc), rpc.BlockCommand(), tx.SearchTxCmd(cdc), tx.QueryTxCmd(cdc), client.LineBreak, + authcmd.GetAccountCmd(at.StoreKey, cdc), ) - // add modules' query commands - app.ModuleBasics.AddQueryCommands(queryCmd, cdc) + for _, m := range mc { + mQueryCmd := m.GetQueryCmd() + if mQueryCmd != nil { + queryCmd.AddCommand(mQueryCmd) + } + } return queryCmd } -func txCmd(cdc *amino.Codec) *cobra.Command { +func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command { txCmd := &cobra.Command{ Use: "tx", Short: "Transactions subcommands", @@ -115,26 +147,15 @@ func txCmd(cdc *amino.Codec) *cobra.Command { client.LineBreak, authcmd.GetSignCommand(cdc), authcmd.GetMultiSignCommand(cdc), - client.LineBreak, tx.GetBroadcastCommand(cdc), tx.GetEncodeCommand(cdc), client.LineBreak, ) - // add modules' tx commands - app.ModuleBasics.AddTxCommands(txCmd, cdc) - - // remove auth and bank commands as they're mounted under the root tx command - var cmdsToRemove []*cobra.Command - - for _, cmd := range txCmd.Commands() { - if cmd.Use == auth.ModuleName || cmd.Use == bank.ModuleName { - cmdsToRemove = append(cmdsToRemove, cmd) - } + for _, m := range mc { + txCmd.AddCommand(m.GetTxCmd()) } - txCmd.RemoveCommand(cmdsToRemove...) - return txCmd } @@ -142,8 +163,15 @@ func txCmd(cdc *amino.Codec) *cobra.Command { // NOTE: details on the routes added for each module are in the module documentation // NOTE: If making updates here you also need to update the test helper in client/lcd/test_helper.go func registerRoutes(rs *lcd.RestServer) { - client.RegisterRoutes(rs.CliCtx, rs.Mux) - app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux) + rpc.RegisterRoutes(rs.CliCtx, rs.Mux) + tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) + bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey) + staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) } func initConfig(cmd *cobra.Command) error { diff --git a/cmd/kvd/main.go b/cmd/kvd/main.go index 14c1e72d..520badc4 100644 --- a/cmd/kvd/main.go +++ b/cmd/kvd/main.go @@ -1,6 +1,3 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - package main import ( @@ -10,25 +7,22 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" - genaccscli "github.com/cosmos/cosmos-sdk/x/auth/genaccounts/client/cli" - genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" - "github.com/cosmos/cosmos-sdk/x/staking" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/cli" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" - "github.com/kava-labs/_/app" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + initPkg "github.com/kava-labs/kava/init" ) -// gaiad custom flags +// kvd custom flags const flagInvCheckPeriod = "inv-check-period" var invCheckPeriod uint @@ -37,7 +31,9 @@ func main() { cdc := app.MakeCodec() config := sdk.GetConfig() - app.SetAddressPrefixes(config) + config.SetBech32PrefixForAccount(app.Bech32MainPrefix, app.Bech32MainPrefix+sdk.PrefixPublic) + config.SetBech32PrefixForValidator(app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixOperator, app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixOperator+sdk.PrefixPublic) + config.SetBech32PrefixForConsensusNode(app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixConsensus, app.Bech32MainPrefix+sdk.PrefixValidator+sdk.PrefixConsensus+sdk.PrefixPublic) config.Seal() ctx := server.NewDefaultContext() @@ -48,24 +44,23 @@ func main() { PersistentPreRunE: server.PersistentPreRunEFn(ctx), } - rootCmd.AddCommand(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome)) - rootCmd.AddCommand(genutilcli.CollectGenTxsCmd(ctx, cdc, genaccounts.AppModuleBasic{}, app.DefaultNodeHome)) - rootCmd.AddCommand(genutilcli.GenTxCmd(ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, - genaccounts.AppModuleBasic{}, app.DefaultNodeHome, app.DefaultCLIHome)) - rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics)) - rootCmd.AddCommand(genaccscli.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome)) + rootCmd.AddCommand(initPkg.InitCmd(ctx, cdc)) + rootCmd.AddCommand(initPkg.CollectGenTxsCmd(ctx, cdc)) + rootCmd.AddCommand(initPkg.TestnetFilesCmd(ctx, cdc)) + rootCmd.AddCommand(initPkg.GenTxCmd(ctx, cdc)) + rootCmd.AddCommand(initPkg.AddGenesisAccountCmd(ctx, cdc)) + rootCmd.AddCommand(initPkg.ValidateGenesisCmd(ctx, cdc)) rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) - rootCmd.AddCommand(testnetCmd(ctx, cdc, app.ModuleBasics, genaccounts.AppModuleBasic{})) - rootCmd.AddCommand(replayCmd()) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) // prepare and add flags - executor := cli.PrepareBaseCmd(rootCmd, "KA", app.DefaultNodeHome) + executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome) rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod, 0, "Assert registered invariants every N blocks") err := executor.Execute() if err != nil { + // handle with #870 panic(err) } } @@ -75,7 +70,6 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application logger, db, traceStore, true, invCheckPeriod, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))), baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), - baseapp.SetHaltHeight(uint64(viper.GetInt(server.FlagHaltHeight))), ) } @@ -84,13 +78,13 @@ func exportAppStateAndTMValidators( ) (json.RawMessage, []tmtypes.GenesisValidator, error) { if height != -1 { - appStruct := app.NewApp(logger, db, traceStore, false, uint(1)) - err := appStruct.LoadHeight(height) + gApp := app.NewApp(logger, db, traceStore, false, uint(1)) + err := gApp.LoadHeight(height) if err != nil { return nil, nil, err } - return appStruct.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } - appStruct := app.NewApp(logger, db, traceStore, true, uint(1)) - return appStruct.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) + gApp := app.NewApp(logger, db, traceStore, true, uint(1)) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } diff --git a/cmd/kvd/replay.go b/cmd/kvd/replay.go deleted file mode 100644 index 12d2e0ad..00000000 --- a/cmd/kvd/replay.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "time" - - cpm "github.com/otiai10/copy" - "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/tendermint/abci/types" - bcm "github.com/tendermint/tendermint/blockchain" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/proxy" - tmsm "github.com/tendermint/tendermint/state" - tm "github.com/tendermint/tendermint/types" - - "github.com/kava-labs/_/app" -) - -func replayCmd() *cobra.Command { - return &cobra.Command{ - Use: "replay ", - Short: "Replay transactions", - RunE: func(_ *cobra.Command, args []string) error { - return replayTxs(args[0]) - }, - Args: cobra.ExactArgs(1), - } -} - -func replayTxs(rootDir string) error { - - if false { - // Copy the rootDir to a new directory, to preserve the old one. - fmt.Fprintln(os.Stderr, "Copying rootdir over") - oldRootDir := rootDir - rootDir = oldRootDir + "_replay" - if cmn.FileExists(rootDir) { - cmn.Exit(fmt.Sprintf("temporary copy dir %v already exists", rootDir)) - } - if err := cpm.Copy(oldRootDir, rootDir); err != nil { - return err - } - } - - configDir := filepath.Join(rootDir, "config") - dataDir := filepath.Join(rootDir, "data") - ctx := server.NewDefaultContext() - - // App DB - // appDB := dbm.NewMemDB() - fmt.Fprintln(os.Stderr, "Opening app database") - appDB, err := sdk.NewLevelDB("application", dataDir) - if err != nil { - return err - } - - // TM DB - // tmDB := dbm.NewMemDB() - fmt.Fprintln(os.Stderr, "Opening tendermint state database") - tmDB, err := sdk.NewLevelDB("state", dataDir) - if err != nil { - return err - } - - // Blockchain DB - fmt.Fprintln(os.Stderr, "Opening blockstore database") - bcDB, err := sdk.NewLevelDB("blockstore", dataDir) - if err != nil { - return err - } - - // TraceStore - var traceStoreWriter io.Writer - var traceStoreDir = filepath.Join(dataDir, "trace.log") - traceStoreWriter, err = os.OpenFile( - traceStoreDir, - os.O_WRONLY|os.O_APPEND|os.O_CREATE, - 0666, - ) - if err != nil { - return err - } - - // Application - fmt.Fprintln(os.Stderr, "Creating application") - myapp := app.NewApp( - ctx.Logger, appDB, traceStoreWriter, true, uint(1), - baseapp.SetPruning(store.PruneEverything), // nothing - ) - - // Genesis - var genDocPath = filepath.Join(configDir, "genesis.json") - genDoc, err := tm.GenesisDocFromFile(genDocPath) - if err != nil { - return err - } - genState, err := tmsm.MakeGenesisState(genDoc) - if err != nil { - return err - } - // tmsm.SaveState(tmDB, genState) - - cc := proxy.NewLocalClientCreator(myapp) - proxyApp := proxy.NewAppConns(cc) - err = proxyApp.Start() - if err != nil { - return err - } - defer func() { - _ = proxyApp.Stop() - }() - - state := tmsm.LoadState(tmDB) - if state.LastBlockHeight == 0 { - // Send InitChain msg - fmt.Fprintln(os.Stderr, "Sending InitChain msg") - validators := tm.TM2PB.ValidatorUpdates(genState.Validators) - csParams := tm.TM2PB.ConsensusParams(genDoc.ConsensusParams) - req := abci.RequestInitChain{ - Time: genDoc.GenesisTime, - ChainId: genDoc.ChainID, - ConsensusParams: csParams, - Validators: validators, - AppStateBytes: genDoc.AppState, - } - res, err := proxyApp.Consensus().InitChainSync(req) - if err != nil { - return err - } - newValidatorz, err := tm.PB2TM.ValidatorUpdates(res.Validators) - if err != nil { - return err - } - newValidators := tm.NewValidatorSet(newValidatorz) - - // Take the genesis state. - state = genState - state.Validators = newValidators - state.NextValidators = newValidators - } - - // Create executor - fmt.Fprintln(os.Stderr, "Creating block executor") - blockExec := tmsm.NewBlockExecutor(tmDB, ctx.Logger, proxyApp.Consensus(), - tmsm.MockMempool{}, tmsm.MockEvidencePool{}) - - // Create block store - fmt.Fprintln(os.Stderr, "Creating block store") - blockStore := bcm.NewBlockStore(bcDB) - - tz := []time.Duration{0, 0, 0} - for i := int(state.LastBlockHeight) + 1; ; i++ { - fmt.Fprintln(os.Stderr, "Running block ", i) - t1 := time.Now() - - // Apply block - fmt.Printf("loading and applying block %d\n", i) - blockmeta := blockStore.LoadBlockMeta(int64(i)) - if blockmeta == nil { - fmt.Printf("Couldn't find block meta %d... done?\n", i) - return nil - } - block := blockStore.LoadBlock(int64(i)) - if block == nil { - return fmt.Errorf("couldn't find block %d", i) - } - - t2 := time.Now() - - state, err = blockExec.ApplyBlock(state, blockmeta.BlockID, block) - if err != nil { - return err - } - - t3 := time.Now() - tz[0] += t2.Sub(t1) - tz[1] += t3.Sub(t2) - - fmt.Fprintf(os.Stderr, "new app hash: %X\n", state.AppHash) - fmt.Fprintln(os.Stderr, tz) - } -} diff --git a/go.mod b/go.mod index 679d6845..9ee7c520 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,15 @@ -module github.com/kava-labs/_ +module github.com/kava-labs/kava go 1.12 require ( - github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c // indirect - github.com/cosmos/cosmos-sdk v0.28.2-0.20190606154315-3180e68c7b57 - github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect - github.com/google/gofuzz v1.0.0 // indirect - github.com/gorilla/mux v1.7.2 // indirect - github.com/magiconair/properties v1.8.1 // indirect - github.com/mattn/go-isatty v0.0.8 // indirect - github.com/onsi/ginkgo v1.8.0 // indirect - github.com/onsi/gomega v1.5.0 // indirect - github.com/otiai10/copy v1.0.1 - github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect - github.com/pelletier/go-toml v1.4.0 // indirect - github.com/prometheus/common v0.4.1 // indirect - github.com/prometheus/procfs v0.0.0-20190523193104-a7aeb8df3389 // indirect - github.com/rakyll/statik v0.1.6 // indirect - github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect - github.com/spf13/afero v1.2.2 // indirect - github.com/spf13/cobra v0.0.4 - github.com/spf13/viper v1.4.0 - github.com/syndtr/goleveldb v1.0.0 // indirect - github.com/tendermint/go-amino v0.15.0 + github.com/cosmos/cosmos-sdk v0.34.7 + github.com/rakyll/statik v0.1.4 + github.com/spf13/cobra v0.0.3 + github.com/spf13/viper v1.0.3 + github.com/stretchr/testify v1.3.0 + github.com/tendermint/go-amino v0.14.1 github.com/tendermint/tendermint v0.31.5 - golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect - golang.org/x/sys v0.0.0-20190527104216-9cd6430ef91e // indirect - golang.org/x/text v0.3.2 // indirect - google.golang.org/appengine v1.4.0 // indirect - google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 // indirect ) replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 diff --git a/go.sum b/go.sum index bde5ddbf..74793e86 100644 --- a/go.sum +++ b/go.sum @@ -9,17 +9,17 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d h1:1aAija9gr0Hyv4KfQcRcwlmFIrhkDmIj2dz5bkg/s/8= github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d/go.mod h1:icNx/6QdFblhsEjZehARqbNumymUT/ydwlLojFdv7Sk= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= -github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c h1:aEbSeNALREWXk0G7UdNhR3ayBV7tZ4M2PNmnrCAph6Q= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= @@ -34,10 +34,12 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/cosmos-sdk v0.28.2-0.20190606154315-3180e68c7b57 h1:bLViLq/BPtEMhxsYhE5NxxTM664Bt1Z+vM6SJflQXTU= github.com/cosmos/cosmos-sdk v0.28.2-0.20190606154315-3180e68c7b57/go.mod h1:MvaJDmjgAK7X1rTnpk8+c6tUFfIZ++iuNCp2sUWzprM= +github.com/cosmos/cosmos-sdk v0.34.7 h1:S6yMldhrAMB/SDMsR2Hvz05tpUpQQGCHf0INXAZ7VW0= +github.com/cosmos/cosmos-sdk v0.34.7/go.mod h1:ruF+G4D7hRf34uzZQvf/SIja9fsIThU5D7GirwTMQ9I= +github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao= +github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= -github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= @@ -58,25 +60,26 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -100,11 +103,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -118,14 +121,14 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= +github.com/otiai10/copy v0.0.0-20180813032824-7e9a647135a1/go.mod h1:pXzZSDlN+HPzSdyIBnKNN9ptD9Hx7iZMWIJPTwo4FPE= github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -133,30 +136,30 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 h1:3l8oligPtjd4JuM+OZ+U8sjtwFGJs98cdWsqs6QZRWs= github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190523193104-a7aeb8df3389 h1:F/k2nob1S9M6v5Xkq7KjSTQirOYaYQord0jR4TwyVmY= github.com/prometheus/procfs v0.0.0-20190523193104-a7aeb8df3389/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rakyll/statik v0.1.4 h1:zCS/YQCxfo/fQjCtGVGIyWGFnRbQ18Y55mhS3XPE+Oo= github.com/rakyll/statik v0.1.4/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= -github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= @@ -166,38 +169,40 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.0.3 h1:z5LPUc2iz8VLT5Cw1UyrESG6FUUnOGecYGY08BLKSuc= github.com/spf13/viper v1.0.3/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 h1:sAlSBRDl4psFR3ysKXRSE8ss6Mt90+ma1zRTroTNBJA= github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 h1:u8i49c+BxloX3XQ55cvzFNXplizZP/q00i+IlttUjAU= github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= -github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= +github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= +github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/iavl v0.12.2 h1:Ls5p5VINCM1HRT9g5Vvs2zmDOCU/CCIvIHzd/pZ8P0E= +github.com/tendermint/iavl v0.12.1 h1:JDfyhM/Hhrumu1CL1Nxrypm8sNTPYqmeHo1IZLiJoXM= +github.com/tendermint/iavl v0.12.1/go.mod h1:EoKMMv++tDOL5qKKVnoIqtVPshRrEPeJ0WsgDOLAauM= github.com/tendermint/iavl v0.12.2/go.mod h1:EoKMMv++tDOL5qKKVnoIqtVPshRrEPeJ0WsgDOLAauM= github.com/tendermint/tendermint v0.31.5 h1:vTet8tCq3B9/J9Yo11dNZ8pOB7NtSy++bVSfkP4KzR4= github.com/tendermint/tendermint v0.31.5/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc= @@ -211,6 +216,9 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -219,11 +227,11 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -237,11 +245,11 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190527104216-9cd6430ef91e h1:Pzdi8HRppinixnWWzN6KSa0QkBM+GKsTJaWwwfJskNw= golang.org/x/sys v0.0.0-20190527104216-9cd6430ef91e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -251,12 +259,12 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs= google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/init/collect.go b/init/collect.go new file mode 100644 index 00000000..808b318a --- /dev/null +++ b/init/collect.go @@ -0,0 +1,145 @@ +package init + +// DONTCOVER + +import ( + "encoding/json" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/kava-labs/kava/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +const ( + flagGenTxDir = "gentx-dir" +) + +type initConfig struct { + ChainID string + GenTxsDir string + Name string + NodeID string + ValPubKey crypto.PubKey +} + +// nolint +func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "collect-gentxs", + Short: "Collect genesis txs and output a genesis.json file", + RunE: func(_ *cobra.Command, _ []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + name := viper.GetString(client.FlagName) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) + if err != nil { + return err + } + + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + genTxsDir := viper.GetString(flagGenTxDir) + if genTxsDir == "" { + genTxsDir = filepath.Join(config.RootDir, "config", "gentx") + } + + toPrint := newPrintInfo(config.Moniker, genDoc.ChainID, nodeID, genTxsDir, json.RawMessage("")) + initCfg := newInitConfig(genDoc.ChainID, genTxsDir, name, nodeID, valPubKey) + + appMessage, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + toPrint.AppMessage = appMessage + + // print out some key information + return displayInfo(cdc, toPrint) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagGenTxDir, "", + "override default \"gentx\" directory from which collect and execute "+ + "genesis transactions; default [--home]/config/gentx/") + return cmd +} + +func genAppStateFromConfig( + cdc *codec.Codec, config *cfg.Config, initCfg initConfig, genDoc types.GenesisDoc, +) (appState json.RawMessage, err error) { + + genFile := config.GenesisFile() + var ( + appGenTxs []auth.StdTx + persistentPeers string + genTxs []json.RawMessage + jsonRawTx json.RawMessage + ) + + // process genesis transactions, else create default genesis.json + appGenTxs, persistentPeers, err = app.CollectStdTxs( + cdc, config.Moniker, initCfg.GenTxsDir, genDoc, + ) + if err != nil { + return + } + + genTxs = make([]json.RawMessage, len(appGenTxs)) + config.P2P.PersistentPeers = persistentPeers + + for i, stdTx := range appGenTxs { + jsonRawTx, err = cdc.MarshalJSON(stdTx) + if err != nil { + return + } + genTxs[i] = jsonRawTx + } + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + + appState, err = app.AppGenStateJSON(cdc, genDoc, genTxs) + if err != nil { + return + } + + err = ExportGenesisFile(genFile, initCfg.ChainID, nil, appState) + return +} + +func newInitConfig(chainID, genTxsDir, name, nodeID string, + valPubKey crypto.PubKey) initConfig { + + return initConfig{ + ChainID: chainID, + GenTxsDir: genTxsDir, + Name: name, + NodeID: nodeID, + ValPubKey: valPubKey, + } +} + +func newPrintInfo(moniker, chainID, nodeID, genTxsDir string, + appMessage json.RawMessage) printInfo { + + return printInfo{ + Moniker: moniker, + ChainID: chainID, + NodeID: nodeID, + GenTxsDir: genTxsDir, + AppMessage: appMessage, + } +} diff --git a/init/genesis_accts.go b/init/genesis_accts.go new file mode 100644 index 00000000..161f405e --- /dev/null +++ b/init/genesis_accts.go @@ -0,0 +1,141 @@ +package init + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/kava-labs/kava/app" +) + +// AddGenesisAccountCmd returns add-genesis-account cobra Command. +func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", + Short: "Add genesis account to genesis.json", + Args: cobra.ExactArgs(2), + RunE: func(_ *cobra.Command, args []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome)) + if err != nil { + return err + } + + info, err := kb.Get(args[0]) + if err != nil { + return err + } + + addr = info.GetAddress() + } + + coins, err := sdk.ParseCoins(args[1]) + if err != nil { + return err + } + + vestingStart := viper.GetInt64(flagVestingStart) + vestingEnd := viper.GetInt64(flagVestingEnd) + vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt)) + if err != nil { + return err + } + + genFile := config.GenesisFile() + if !common.FileExists(genFile) { + return fmt.Errorf("%s does not exist, run `kvd init` first", genFile) + } + + genDoc, err := LoadGenesisDoc(cdc, genFile) + if err != nil { + return err + } + + var appState app.GenesisState + if err = cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { + return err + } + + appState, err = addGenesisAccount(cdc, appState, addr, coins, vestingAmt, vestingStart, vestingEnd) + if err != nil { + return err + } + + appStateJSON, err := cdc.MarshalJSON(appState) + if err != nil { + return err + } + + return ExportGenesisFile(genFile, genDoc.ChainID, nil, appStateJSON) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + + return cmd +} + +func addGenesisAccount( + cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, + coins, vestingAmt sdk.Coins, vestingStart, vestingEnd int64, +) (app.GenesisState, error) { + + for _, stateAcc := range appState.Accounts { + if stateAcc.Address.Equals(addr) { + return appState, fmt.Errorf("the application state already contains account %v", addr) + } + } + + acc := auth.NewBaseAccountWithAddress(addr) + acc.Coins = coins + + if !vestingAmt.IsZero() { + var vacc auth.VestingAccount + + bvacc := &auth.BaseVestingAccount{ + BaseAccount: &acc, + OriginalVesting: vestingAmt, + EndTime: vestingEnd, + } + + if bvacc.OriginalVesting.IsAllGT(acc.Coins) { + return appState, fmt.Errorf("vesting amount cannot be greater than total amount") + } + if vestingStart >= vestingEnd { + return appState, fmt.Errorf("vesting start time must before end time") + } + + if vestingStart != 0 { + vacc = &auth.ContinuousVestingAccount{ + BaseVestingAccount: bvacc, + StartTime: vestingStart, + } + } else { + vacc = &auth.DelayedVestingAccount{ + BaseVestingAccount: bvacc, + } + } + + appState.Accounts = append(appState.Accounts, app.NewGenesisAccountI(vacc)) + } else { + appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc)) + } + + return appState, nil +} diff --git a/init/genesis_accts_test.go b/init/genesis_accts_test.go new file mode 100644 index 00000000..0e55bab6 --- /dev/null +++ b/init/genesis_accts_test.go @@ -0,0 +1,88 @@ +package init + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/secp256k1" + + "github.com/kava-labs/kava/app" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestAddGenesisAccount(t *testing.T) { + cdc := codec.New() + addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + type args struct { + appState app.GenesisState + addr sdk.AccAddress + coins sdk.Coins + vestingAmt sdk.Coins + vestingStart int64 + vestingEnd int64 + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "valid account", + args{ + app.GenesisState{}, + addr1, + sdk.NewCoins(), + sdk.NewCoins(), + 0, + 0, + }, + false, + }, + { + "dup account", + args{ + app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}}, + addr1, + sdk.NewCoins(), + sdk.NewCoins(), + 0, + 0, + }, + true, + }, + { + "invalid vesting amount", + args{ + app.GenesisState{}, + addr1, + sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), + sdk.NewCoins(sdk.NewInt64Coin("stake", 100)), + 0, + 0, + }, + true, + }, + { + "invalid vesting times", + args{ + app.GenesisState{}, + addr1, + sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), + sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), + 1654668078, + 1554668078, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := addGenesisAccount( + cdc, tt.args.appState, tt.args.addr, tt.args.coins, + tt.args.vestingAmt, tt.args.vestingStart, tt.args.vestingEnd, + ) + require.Equal(t, tt.wantErr, (err != nil)) + }) + } +} diff --git a/init/gentx.go b/init/gentx.go new file mode 100644 index 00000000..3c7c3926 --- /dev/null +++ b/init/gentx.go @@ -0,0 +1,312 @@ +package init + +// DONTCOVER + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + tmcli "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + kbkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/staking/client/cli" + "github.com/kava-labs/kava/app" +) + +var ( + defaultTokens = sdk.TokensFromTendermintPower(100) + defaultAmount = defaultTokens.String() + sdk.DefaultBondDenom + defaultCommissionRate = "0.1" + defaultCommissionMaxRate = "0.2" + defaultCommissionMaxChangeRate = "0.01" + defaultMinSelfDelegation = "1" +) + +// GenTxCmd builds the kvd gentx command. +// nolint: errcheck +func GenTxCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "gentx", + Short: "Generate a genesis tx carrying a self delegation", + Args: cobra.NoArgs, + Long: fmt.Sprintf(`This command is an alias of the 'kvd tx create-validator' command'. + +It creates a genesis piece carrying a self delegation with the +following delegation and commission default parameters: + + delegation amount: %s + commission rate: %s + commission max rate: %s + commission max change rate: %s + minimum self delegation: %s +`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate, defaultMinSelfDelegation), + RunE: func(cmd *cobra.Command, args []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(tmcli.HomeFlag)) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(ctx.Config) + if err != nil { + return err + } + + // Read --nodeID, if empty take it from priv_validator.json + if nodeIDString := viper.GetString(cli.FlagNodeID); nodeIDString != "" { + nodeID = nodeIDString + } + + ip := viper.GetString(cli.FlagIP) + if ip == "" { + fmt.Fprintf(os.Stderr, "couldn't retrieve an external IP; "+ + "the tx's memo field will be unset") + } + + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) + if err != nil { + return err + } + + genesisState := app.GenesisState{} + if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return err + } + + if err = app.ValidateGenesisState(genesisState); err != nil { + return err + } + + kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome)) + if err != nil { + return err + } + + name := viper.GetString(client.FlagName) + key, err := kb.Get(name) + if err != nil { + return err + } + + // Read --pubkey, if empty take it from priv_validator.json + if valPubKeyString := viper.GetString(cli.FlagPubKey); valPubKeyString != "" { + valPubKey, err = sdk.GetConsPubKeyBech32(valPubKeyString) + if err != nil { + return err + } + } + + website := viper.GetString(cli.FlagWebsite) + details := viper.GetString(cli.FlagDetails) + identity := viper.GetString(cli.FlagIdentity) + + // Set flags for creating gentx + prepareFlagsForTxCreateValidator(config, nodeID, ip, genDoc.ChainID, valPubKey, website, details, identity) + + // Fetch the amount of coins staked + amount := viper.GetString(cli.FlagAmount) + coins, err := sdk.ParseCoins(amount) + if err != nil { + return err + } + + err = accountInGenesis(genesisState, key.GetAddress(), coins) + if err != nil { + return err + } + + txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + // XXX: Set the generate-only flag here after the CLI context has + // been created. This allows the from name/key to be correctly populated. + // + // TODO: Consider removing the manual setting of generate-only in + // favor of a 'gentx' flag in the create-validator command. + viper.Set(client.FlagGenerateOnly, true) + + // create a 'create-validator' message + txBldr, msg, err := cli.BuildCreateValidatorMsg(cliCtx, txBldr) + if err != nil { + return err + } + + info, err := txBldr.Keybase().Get(name) + if err != nil { + return err + } + + if info.GetType() == kbkeys.TypeOffline || info.GetType() == kbkeys.TypeMulti { + fmt.Println("Offline key passed in. Use `kvcli tx sign` command to sign:") + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true) + } + + // write the unsigned transaction to the buffer + w := bytes.NewBuffer([]byte{}) + cliCtx = cliCtx.WithOutput(w) + + if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil { + return err + } + + // read the transaction + stdTx, err := readUnsignedGenTxFile(cdc, w) + if err != nil { + return err + } + + // sign the transaction and write it to the output file + signedTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, false, true) + if err != nil { + return err + } + + // Fetch output file name + outputDocument := viper.GetString(client.FlagOutputDocument) + if outputDocument == "" { + outputDocument, err = makeOutputFilepath(config.RootDir, nodeID) + if err != nil { + return err + } + } + + if err := writeSignedGenTx(cdc, outputDocument, signedTx); err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "Genesis transaction written to %q\n", outputDocument) + return nil + + }, + } + + ip, _ := server.ExternalIP() + + cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().String(client.FlagOutputDocument, "", + "write the genesis transaction JSON document to the given file instead of the default location") + cmd.Flags().String(cli.FlagIP, ip, "The node's public IP") + cmd.Flags().String(cli.FlagNodeID, "", "The node's NodeID") + cmd.Flags().String(cli.FlagWebsite, "", "The validator's (optional) website") + cmd.Flags().String(cli.FlagDetails, "", "The validator's (optional) details") + cmd.Flags().String(cli.FlagIdentity, "", "The (optional) identity signature (ex. UPort or Keybase)") + cmd.Flags().AddFlagSet(cli.FsCommissionCreate) + cmd.Flags().AddFlagSet(cli.FsMinSelfDelegation) + cmd.Flags().AddFlagSet(cli.FsAmount) + cmd.Flags().AddFlagSet(cli.FsPk) + cmd.MarkFlagRequired(client.FlagName) + return cmd +} + +func accountInGenesis(genesisState app.GenesisState, key sdk.AccAddress, coins sdk.Coins) error { + accountIsInGenesis := false + bondDenom := genesisState.StakingData.Params.BondDenom + + // Check if the account is in genesis + for _, acc := range genesisState.Accounts { + // Ensure that account is in genesis + if acc.Address.Equals(key) { + + // Ensure account contains enough funds of default bond denom + if coins.AmountOf(bondDenom).GT(acc.Coins.AmountOf(bondDenom)) { + return fmt.Errorf( + "account %v is in genesis, but it only has %v%v available to stake, not %v%v", + key.String(), acc.Coins.AmountOf(bondDenom), bondDenom, coins.AmountOf(bondDenom), bondDenom, + ) + } + accountIsInGenesis = true + break + } + } + + if accountIsInGenesis { + return nil + } + + return fmt.Errorf("account %s in not in the app_state.accounts array of genesis.json", key) +} + +func prepareFlagsForTxCreateValidator( + config *cfg.Config, nodeID, ip, chainID string, valPubKey crypto.PubKey, website, details, identity string, +) { + viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) + viper.Set(client.FlagChainID, chainID) + viper.Set(client.FlagFrom, viper.GetString(client.FlagName)) + viper.Set(cli.FlagNodeID, nodeID) + viper.Set(cli.FlagIP, ip) + viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) + viper.Set(cli.FlagMoniker, config.Moniker) + viper.Set(cli.FlagWebsite, website) + viper.Set(cli.FlagDetails, details) + viper.Set(cli.FlagIdentity, identity) + + if config.Moniker == "" { + viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName)) + } + if viper.GetString(cli.FlagAmount) == "" { + viper.Set(cli.FlagAmount, defaultAmount) + } + if viper.GetString(cli.FlagCommissionRate) == "" { + viper.Set(cli.FlagCommissionRate, defaultCommissionRate) + } + if viper.GetString(cli.FlagCommissionMaxRate) == "" { + viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate) + } + if viper.GetString(cli.FlagCommissionMaxChangeRate) == "" { + viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) + } + if viper.GetString(cli.FlagMinSelfDelegation) == "" { + viper.Set(cli.FlagMinSelfDelegation, defaultMinSelfDelegation) + } +} + +func makeOutputFilepath(rootDir, nodeID string) (string, error) { + writePath := filepath.Join(rootDir, "config", "gentx") + if err := common.EnsureDir(writePath, 0700); err != nil { + return "", err + } + return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil +} + +func readUnsignedGenTxFile(cdc *codec.Codec, r io.Reader) (auth.StdTx, error) { + var stdTx auth.StdTx + bytes, err := ioutil.ReadAll(r) + if err != nil { + return stdTx, err + } + err = cdc.UnmarshalJSON(bytes, &stdTx) + return stdTx, err +} + +// nolint: errcheck +func writeSignedGenTx(cdc *codec.Codec, outputDocument string, tx auth.StdTx) error { + outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer outputFile.Close() + json, err := cdc.MarshalJSON(tx) + if err != nil { + return err + } + _, err = fmt.Fprintf(outputFile, "%s\n", json) + return err +} diff --git a/init/gentx_test.go b/init/gentx_test.go new file mode 100644 index 00000000..d99a562f --- /dev/null +++ b/init/gentx_test.go @@ -0,0 +1,85 @@ +package init + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/client/cli" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/log" +) + +func Test_prepareFlagsForTxCreateValidator(t *testing.T) { + defer server.SetupViper(t)() + defer setupClientHome(t)() + config, err := tcmd.ParseConfig() + require.Nil(t, err) + logger := log.NewNopLogger() + ctx := server.NewContext(config, logger) + + valPubKey, _ := sdk.GetConsPubKeyBech32("cosmosvalconspub1zcjduepq7jsrkl9fgqk0wj3ahmfr8pgxj6vakj2wzn656s8pehh0zhv2w5as5gd80a") + + type args struct { + config *cfg.Config + nodeID string + ip string + chainID string + valPubKey crypto.PubKey + website string + details string + identity string + } + + type extraParams struct { + amount string + commissionRate string + commissionMaxRate string + commissionMaxChangeRate string + minSelfDelegation string + } + + type testcase struct { + name string + args args + } + + runTest := func(t *testing.T, tt testcase, params extraParams) { + prepareFlagsForTxCreateValidator(tt.args.config, tt.args.nodeID, tt.args.ip, tt.args.chainID, tt.args.valPubKey, tt.args.website, tt.args.details, tt.args.identity) + require.Equal(t, tt.args.website, viper.GetString(cli.FlagWebsite)) + require.Equal(t, tt.args.details, viper.GetString(cli.FlagDetails)) + require.Equal(t, tt.args.identity, viper.GetString(cli.FlagIdentity)) + require.Equal(t, params.amount, viper.GetString(cli.FlagAmount)) + require.Equal(t, params.commissionRate, viper.GetString(cli.FlagCommissionRate)) + require.Equal(t, params.commissionMaxRate, viper.GetString(cli.FlagCommissionMaxRate)) + require.Equal(t, params.commissionMaxChangeRate, viper.GetString(cli.FlagCommissionMaxChangeRate)) + require.Equal(t, params.minSelfDelegation, viper.GetString(cli.FlagMinSelfDelegation)) + } + + tests := []testcase{ + {"No parameters", args{ctx.Config, "X", "0.0.0.0", "chainId", valPubKey, "", "", ""}}, + {"Optional parameters fed", args{ctx.Config, "X", "0.0.0.0", "chainId", valPubKey, "cosmos.network", "details", "identity"}}, + } + + defaultParams := extraParams{defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate, defaultMinSelfDelegation} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { runTest(t, tt, defaultParams) }) + }) + } + + // Override default params + params := extraParams{"5stake", "1.0", "1.0", "1.0", "1.0"} + viper.Set(cli.FlagAmount, params.amount) + viper.Set(cli.FlagCommissionRate, params.commissionRate) + viper.Set(cli.FlagCommissionMaxRate, params.commissionMaxRate) + viper.Set(cli.FlagCommissionMaxChangeRate, params.commissionMaxChangeRate) + viper.Set(cli.FlagMinSelfDelegation, params.minSelfDelegation) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { runTest(t, tt, params) }) + } +} diff --git a/init/init.go b/init/init.go new file mode 100644 index 00000000..e6076400 --- /dev/null +++ b/init/init.go @@ -0,0 +1,95 @@ +package init + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/kava-labs/kava/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" +) + +const ( + flagOverwrite = "overwrite" + flagClientHome = "home-client" + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" +) + +type printInfo struct { + Moniker string `json:"moniker"` + ChainID string `json:"chain_id"` + NodeID string `json:"node_id"` + GenTxsDir string `json:"gentxs_dir"` + AppMessage json.RawMessage `json:"app_message"` +} + +func displayInfo(cdc *codec.Codec, info printInfo) error { + out, err := codec.MarshalJSONIndent(cdc, info) + if err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "%s\n", string(out)) // nolint: errcheck + return nil +} + +// InitCmd returns a command that initializes all files needed for Tendermint +// and the respective application. +func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { // nolint: golint + cmd := &cobra.Command{ + Use: "init [moniker]", + Short: "Initialize private validator, p2p, genesis, and application configuration files", + Long: `Initialize validators's and node's configuration files.`, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) + } + + nodeID, _, err := InitializeNodeValidatorFiles(config) + if err != nil { + return err + } + + config.Moniker = args[0] + + var appState json.RawMessage + genFile := config.GenesisFile() + + if appState, err = initializeEmptyGenesis(cdc, genFile, chainID, + viper.GetBool(flagOverwrite)); err != nil { + return err + } + + if err = ExportGenesisFile(genFile, chainID, nil, appState); err != nil { + return err + } + + toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + return displayInfo(cdc, toPrint) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + + return cmd +} diff --git a/init/init_test.go b/init/init_test.go new file mode 100644 index 00000000..1f06dd74 --- /dev/null +++ b/init/init_test.go @@ -0,0 +1,136 @@ +package init + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/kava-labs/kava/app" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/mock" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + abciServer "github.com/tendermint/tendermint/abci/server" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" +) + +func TestInitCmd(t *testing.T) { + defer server.SetupViper(t)() + defer setupClientHome(t)() + + logger := log.NewNopLogger() + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + + ctx := server.NewContext(cfg, logger) + cdc := app.MakeCodec() + cmd := InitCmd(ctx, cdc) + + require.NoError(t, cmd.RunE(nil, []string{"kavanode-test"})) +} + +func setupClientHome(t *testing.T) func() { + clientDir, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + viper.Set(flagClientHome, clientDir) + return func() { + if err := os.RemoveAll(clientDir); err != nil { + // TODO: Handle with #870 + panic(err) + } + } +} + +func TestEmptyState(t *testing.T) { + defer server.SetupViper(t)() + defer setupClientHome(t)() + + logger := log.NewNopLogger() + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + + ctx := server.NewContext(cfg, logger) + cdc := app.MakeCodec() + + cmd := InitCmd(ctx, cdc) + require.NoError(t, cmd.RunE(nil, []string{"kavanode-test"})) + + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + cmd = server.ExportCmd(ctx, cdc, nil) + + err = cmd.RunE(nil, nil) + require.NoError(t, err) + + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + w.Close() + os.Stdout = old + out := <-outC + require.Contains(t, out, "genesis_time") + require.Contains(t, out, "chain_id") + require.Contains(t, out, "consensus_params") + require.Contains(t, out, "validators") + require.Contains(t, out, "app_hash") +} + +func TestStartStandAlone(t *testing.T) { + home, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + defer func() { + os.RemoveAll(home) + }() + viper.Set(cli.HomeFlag, home) + defer setupClientHome(t)() + + logger := log.NewNopLogger() + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + ctx := server.NewContext(cfg, logger) + cdc := app.MakeCodec() + initCmd := InitCmd(ctx, cdc) + require.NoError(t, initCmd.RunE(nil, []string{"kavanode-test"})) + + app, err := mock.NewApp(home, logger) + require.Nil(t, err) + svrAddr, _, err := server.FreeTCPAddr() + require.Nil(t, err) + svr, err := abciServer.NewServer(svrAddr, "socket", app) + require.Nil(t, err, "error creating listener") + svr.SetLogger(logger.With("module", "abci-server")) + svr.Start() + + timer := time.NewTimer(time.Duration(2) * time.Second) + select { + case <-timer.C: + svr.Stop() + } +} + +func TestInitNodeValidatorFiles(t *testing.T) { + home, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + defer func() { + os.RemoveAll(home) + }() + viper.Set(cli.HomeFlag, home) + viper.Set(client.FlagName, "moniker") + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(cfg) + require.Nil(t, err) + require.NotEqual(t, "", nodeID) + require.NotEqual(t, 0, len(valPubKey.Bytes())) +} diff --git a/cmd/kvd/testnet.go b/init/testnet.go similarity index 62% rename from cmd/kvd/testnet.go rename to init/testnet.go index 4c2e256b..80d7cf83 100644 --- a/cmd/kvd/testnet.go +++ b/init/testnet.go @@ -1,7 +1,4 @@ -// Copyright 2016 All in Bits, inc -// Modifications copyright 2019 Kava Labs - -package main +package init // DONTCOVER @@ -12,6 +9,17 @@ import ( "os" "path/filepath" + "github.com/cosmos/cosmos-sdk/client/keys" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/kava-labs/kava/app" + "github.com/spf13/cobra" "github.com/spf13/viper" tmconfig "github.com/tendermint/tendermint/config" @@ -20,17 +28,7 @@ import ( "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - srvconfig "github.com/cosmos/cosmos-sdk/server/config" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/genaccounts" - "github.com/cosmos/cosmos-sdk/x/genutil" - "github.com/cosmos/cosmos-sdk/x/staking" ) var ( @@ -38,17 +36,18 @@ var ( flagNumValidators = "v" flagOutputDir = "output-dir" flagNodeDaemonHome = "node-daemon-home" - flagNodeCLIHome = "node-cli-home" + flagNodeCliHome = "node-cli-home" flagStartingIPAddress = "starting-ip-address" ) +const nodeDirPerm = 0755 + // get cmd to initialize all files for tendermint testnet and application -func testnetCmd(ctx *server.Context, cdc *codec.Codec, - mbm module.BasicManager, genAccIterator genutil.GenesisAccountsIterator) *cobra.Command { +func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "testnet", - Short: "Initialize files for a kvd testnet", + Short: "Initialize files for a testnet", Long: `testnet will create "v" number of directories and populate each with necessary files (private validator, genesis, config, etc.). @@ -59,49 +58,46 @@ Example: `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - - outputDir := viper.GetString(flagOutputDir) - chainID := viper.GetString(client.FlagChainID) - minGasPrices := viper.GetString(server.FlagMinGasPrices) - nodeDirPrefix := viper.GetString(flagNodeDirPrefix) - nodeDaemonHome := viper.GetString(flagNodeDaemonHome) - nodeCLIHome := viper.GetString(flagNodeCLIHome) - startingIPAddress := viper.GetString(flagStartingIPAddress) - numValidators := viper.GetInt(flagNumValidators) - - return InitTestnet(config, cdc, mbm, genAccIterator, outputDir, chainID, minGasPrices, - nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, numValidators) + return initTestnet(config, cdc) }, } cmd.Flags().Int(flagNumValidators, 4, - "Number of validators to initialize the testnet with") + "Number of validators to initialize the testnet with", + ) cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") + "Directory to store initialization data for the testnet", + ) cmd.Flags().String(flagNodeDirPrefix, "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") + "Prefix the directory name for each node with (node results in node0, node1, ...)", + ) cmd.Flags().String(flagNodeDaemonHome, "kvd", - "Home directory of the node's daemon configuration") - cmd.Flags().String(flagNodeCLIHome, "kvcli", - "Home directory of the node's cli configuration") + "Home directory of the node's daemon configuration", + ) + cmd.Flags().String(flagNodeCliHome, "kvcli", + "Home directory of the node's cli configuration", + ) cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + cmd.Flags().String( - client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created", + ) cmd.Flags().String( server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), - "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") + "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)", + ) + return cmd } -const nodeDirPerm = 0755 +func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error { + var chainID string -// Initialize the testnet -func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicManager, - genAccIterator genutil.GenesisAccountsIterator, - outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome, - nodeCLIHome, startingIPAddress string, numValidators int) error { + outDir := viper.GetString(flagOutputDir) + numValidators := viper.GetInt(flagNumValidators) + chainID = viper.GetString(client.FlagChainID) if chainID == "" { chainID = "chain-" + cmn.RandStr(6) } @@ -111,46 +107,48 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana valPubKeys := make([]crypto.PubKey, numValidators) appConfig := srvconfig.DefaultConfig() - appConfig.MinGasPrices = minGasPrices + appConfig.MinGasPrices = viper.GetString(server.FlagMinGasPrices) var ( - accs []genaccounts.GenesisAccount + accs []app.GenesisAccount genFiles []string ) // generate private keys, node IDs, and initial transactions for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) - nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) - clientDir := filepath.Join(outputDir, nodeDirName, nodeCLIHome) - gentxsDir := filepath.Join(outputDir, "gentxs") + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(flagNodeDirPrefix), i) + nodeDaemonHomeName := viper.GetString(flagNodeDaemonHome) + nodeCliHomeName := viper.GetString(flagNodeCliHome) + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) + clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) + gentxsDir := filepath.Join(outDir, "gentxs") config.SetRoot(nodeDir) err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } err = os.MkdirAll(clientDir, nodeDirPerm) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } monikers = append(monikers, nodeDirName) config.Moniker = nodeDirName - ip, err := getIP(i, startingIPAddress) + ip, err := getIP(i, viper.GetString(flagStartingIPAddress)) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } - nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config) + nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } @@ -159,7 +157,7 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana buf := client.BufferStdin() prompt := fmt.Sprintf( - "Password for account '%s' (default %s):", nodeDirName, client.DefaultKeyPass, + "Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass, ) keyPass, err := client.GetPassword(prompt, buf) @@ -171,12 +169,12 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana } if keyPass == "" { - keyPass = client.DefaultKeyPass + keyPass = app.DefaultKeyPass } addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } @@ -195,7 +193,7 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana accTokens := sdk.TokensFromTendermintPower(1000) accStakingTokens := sdk.TokensFromTendermintPower(500) - accs = append(accs, genaccounts.GenesisAccount{ + accs = append(accs, app.GenesisAccount{ Address: addr, Coins: sdk.Coins{ sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens), @@ -209,7 +207,7 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana valPubKeys[i], sdk.NewCoin(sdk.DefaultBondDenom, valTokens), staking.NewDescription(nodeDirName, "", "", ""), - staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), sdk.OneInt(), ) kb, err := keys.NewKeyBaseFromDir(clientDir) @@ -217,40 +215,38 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana return err } tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) - txBldr := auth.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb) + txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb) - signedTx, err := txBldr.SignStdTx(nodeDirName, client.DefaultKeyPass, tx, false) + signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } txBytes, err := cdc.MarshalJSON(signedTx) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } // gather gentxs folder err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) if err != nil { - _ = os.RemoveAll(outputDir) + _ = os.RemoveAll(outDir) return err } - // TODO: Rename config file to server.toml as it's not particular to Gaia - // (REF: https://github.com/cosmos/cosmos-sdk/issues/4125). - appConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml") + appConfigFilePath := filepath.Join(nodeDir, "config/kvd.toml") srvconfig.WriteConfigFile(appConfigFilePath, appConfig) } - if err := initGenFiles(cdc, mbm, chainID, accs, genFiles, numValidators); err != nil { + if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil { return err } err := collectGenFiles( cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators, - outputDir, nodeDirPrefix, nodeDaemonHome, genAccIterator, + outDir, viper.GetString(flagNodeDirPrefix), viper.GetString(flagNodeDaemonHome), ) if err != nil { return err @@ -260,13 +256,13 @@ func InitTestnet(config *tmconfig.Config, cdc *codec.Codec, mbm module.BasicMana return nil } -func initGenFiles(cdc *codec.Codec, mbm module.BasicManager, chainID string, - accs []genaccounts.GenesisAccount, genFiles []string, numValidators int) error { +func initGenFiles( + cdc *codec.Codec, chainID string, accs []app.GenesisAccount, + genFiles []string, numValidators int, +) error { - appGenState := mbm.DefaultGenesis() - - // set the accounts in the genesis state - appGenState = genaccounts.SetGenesisStateInAppState(cdc, appGenState, accs) + appGenState := app.NewDefaultGenesisState() + appGenState.Accounts = accs appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) if err != nil { @@ -285,36 +281,37 @@ func initGenFiles(cdc *codec.Codec, mbm module.BasicManager, chainID string, return err } } + return nil } func collectGenFiles( cdc *codec.Codec, config *tmconfig.Config, chainID string, monikers, nodeIDs []string, valPubKeys []crypto.PubKey, - numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string, - genAccIterator genutil.GenesisAccountsIterator) error { + numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string, +) error { var appState json.RawMessage genTime := tmtime.Now() for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) - nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) - gentxsDir := filepath.Join(outputDir, "gentxs") + nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) + gentxsDir := filepath.Join(outDir, "gentxs") moniker := monikers[i] config.Moniker = nodeDirName config.SetRoot(nodeDir) nodeID, valPubKey := nodeIDs[i], valPubKeys[i] - initCfg := genutil.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) + initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) - genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err } - nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genAccIterator) + nodeAppState, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) if err != nil { return err } @@ -327,7 +324,7 @@ func collectGenFiles( genFile := config.GenesisFile() // overwrite each validator's genesis file to have a canonical genesis time - err = genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime) + err = ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime) if err != nil { return err } @@ -336,28 +333,25 @@ func collectGenFiles( return nil } -func getIP(i int, startingIPAddr string) (ip string, err error) { +func getIP(i int, startingIPAddr string) (string, error) { + var ( + ip string + err error + ) + if len(startingIPAddr) == 0 { ip, err = server.ExternalIP() if err != nil { return "", err } - return ip, nil - } - return calculateIP(startingIPAddr, i) -} - -func calculateIP(ip string, i int) (string, error) { - ipv4 := net.ParseIP(ip).To4() - if ipv4 == nil { - return "", fmt.Errorf("%v: non ipv4 address", ip) + } else { + ip, err = calculateIP(startingIPAddr, i) + if err != nil { + return "", err + } } - for j := 0; j < i; j++ { - ipv4[3]++ - } - - return ipv4.String(), nil + return ip, nil } func writeFile(name string, dir string, contents []byte) error { @@ -376,3 +370,16 @@ func writeFile(name string, dir string, contents []byte) error { return nil } + +func calculateIP(ip string, i int) (string, error) { + ipv4 := net.ParseIP(ip).To4() + if ipv4 == nil { + return "", fmt.Errorf("%v: non ipv4 address", ip) + } + + for j := 0; j < i; j++ { + ipv4[3]++ + } + + return ipv4.String(), nil +} diff --git a/init/utils.go b/init/utils.go new file mode 100644 index 00000000..ec8bcab2 --- /dev/null +++ b/init/utils.go @@ -0,0 +1,114 @@ +package init + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "time" + + amino "github.com/tendermint/go-amino" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" + + "github.com/kava-labs/kava/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" +) + +// ExportGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. +func ExportGenesisFile( + genFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage, +) error { + + genDoc := types.GenesisDoc{ + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// ExportGenesisFileWithTime creates and writes the genesis configuration to disk. +// An error is returned if building or writing the configuration to file fails. +func ExportGenesisFileWithTime( + genFile, chainID string, validators []types.GenesisValidator, + appState json.RawMessage, genTime time.Time, +) error { + + genDoc := types.GenesisDoc{ + GenesisTime: genTime, + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// InitializeNodeValidatorFiles creates private validator and p2p configuration files. +func InitializeNodeValidatorFiles( + config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error, +) { + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return nodeID, valPubKey, err + } + + nodeID = string(nodeKey.ID()) + server.UpgradeOldPrivValFile(config) + + pvKeyFile := config.PrivValidatorKeyFile() + if err := common.EnsureDir(filepath.Dir(pvKeyFile), 0777); err != nil { + return nodeID, valPubKey, nil + } + + pvStateFile := config.PrivValidatorStateFile() + if err := common.EnsureDir(filepath.Dir(pvStateFile), 0777); err != nil { + return nodeID, valPubKey, nil + } + + valPubKey = privval.LoadOrGenFilePV(pvKeyFile, pvStateFile).GetPubKey() + + return nodeID, valPubKey, nil +} + +// LoadGenesisDoc reads and unmarshals GenesisDoc from the given file. +func LoadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { + genContents, err := ioutil.ReadFile(genFile) + if err != nil { + return genDoc, err + } + + if err := cdc.UnmarshalJSON(genContents, &genDoc); err != nil { + return genDoc, err + } + + return genDoc, err +} + +func initializeEmptyGenesis( + cdc *codec.Codec, genFile, chainID string, overwrite bool, +) (appState json.RawMessage, err error) { + + if !overwrite && common.FileExists(genFile) { + return nil, fmt.Errorf("genesis.json file already exists: %v", genFile) + } + + return codec.MarshalJSONIndent(cdc, app.NewDefaultGenesisState()) +} diff --git a/init/utils_test.go b/init/utils_test.go new file mode 100644 index 00000000..3a5a1369 --- /dev/null +++ b/init/utils_test.go @@ -0,0 +1,49 @@ +package init + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/tests" + + "github.com/stretchr/testify/require" +) + +func TestExportGenesisFileWithTime(t *testing.T) { + t.Parallel() + dir, cleanup := tests.NewTestCaseDir(t) + defer cleanup() + + fname := filepath.Join(dir, "genesis.json") + require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now())) +} + +func TestLoadGenesisDoc(t *testing.T) { + t.Parallel() + dir, cleanup := tests.NewTestCaseDir(t) + defer cleanup() + + fname := filepath.Join(dir, "genesis.json") + require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now())) + + _, err := LoadGenesisDoc(codec.Cdc, fname) + require.NoError(t, err) + + // Non-existing file + _, err = LoadGenesisDoc(codec.Cdc, "non-existing-file") + require.Error(t, err) + + malformedFilename := filepath.Join(dir, "malformed") + malformedFile, err := os.Create(malformedFilename) + require.NoError(t, err) + fmt.Fprint(malformedFile, "invalidjson") + malformedFile.Close() + // Non-existing file + _, err = LoadGenesisDoc(codec.Cdc, malformedFilename) + require.Error(t, err) +} diff --git a/init/validate_genesis.go b/init/validate_genesis.go new file mode 100644 index 00000000..7535fe73 --- /dev/null +++ b/init/validate_genesis.go @@ -0,0 +1,51 @@ +package init + +import ( + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "github.com/kava-labs/kava/app" + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/types" +) + +// Validate genesis command takes +func ValidateGenesisCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validate-genesis [file]", + Args: cobra.RangeArgs(0, 1), + Short: "validates the genesis file at the default location or at the location passed as an arg", + RunE: func(cmd *cobra.Command, args []string) (err error) { + + // Load default if passed no args, otherwise load passed file + var genesis string + if len(args) == 0 { + genesis = ctx.Config.GenesisFile() + } else { + genesis = args[0] + } + + //nolint + fmt.Fprintf(os.Stderr, "validating genesis file at %s\n", genesis) + + var genDoc types.GenesisDoc + if genDoc, err = LoadGenesisDoc(cdc, genesis); err != nil { + return fmt.Errorf("Error loading genesis doc from %s: %s", genesis, err.Error()) + } + + var genstate app.GenesisState + if err = cdc.UnmarshalJSON(genDoc.AppState, &genstate); err != nil { + return fmt.Errorf("Error unmarshaling genesis doc %s: %s", genesis, err.Error()) + } + + if err = app.ValidateGenesisState(genstate); err != nil { + return fmt.Errorf("Error validating genesis file %s: %s", genesis, err.Error()) + } + + fmt.Printf("File at %s is a valid genesis file for kvd\n", genesis) + return nil + }, + } +} diff --git a/kava-logo.svg b/kava-logo.svg new file mode 100644 index 00000000..01a6c6f5 --- /dev/null +++ b/kava-logo.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + +